cmms/frontend/src/stores/customers.js

733 lines
21 KiB
JavaScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { customerRepository } from '@/services/repositories/CustomerRepository'
import { CUSTOMER_STATUS, CUSTOMER_TYPES, CUSTOMER_PRIORITY } from '@/types/customer'
export const useCustomersStore = defineStore('customers', () => {
// State
const customers = ref([])
const currentCustomer = ref(null)
const loading = ref(false)
const error = ref(null)
const searchCriteria = ref({})
const pagination = ref({
page: 0,
size: 20,
totalElements: 0,
totalPages: 0
})
const lastFetch = ref(null)
// Getters
const customersById = computed(() => {
return customers.value.reduce((acc, customer) => {
acc[customer.id] = customer
return acc
}, {})
})
const activeCustomers = computed(() => {
return customers.value.filter(customer => customer && customer.status === CUSTOMER_STATUS.ACTIVE)
})
const highPriorityCustomers = computed(() => {
return customers.value.filter(customer =>
customer &&
(customer.priority === CUSTOMER_PRIORITY.HIGH || customer.priority === CUSTOMER_PRIORITY.CRITICAL) &&
customer.status === CUSTOMER_STATUS.ACTIVE
)
})
const customersByType = computed(() => {
return Object.values(CUSTOMER_TYPES).reduce((acc, type) => {
acc[type] = customers.value.filter(customer => customer && customer.type === type)
return acc
}, {})
})
const customersByDepartment = computed(() => {
return customers.value.reduce((acc, customer) => {
if (customer && customer.department) {
if (!acc[customer.department]) {
acc[customer.department] = []
}
acc[customer.department].push(customer)
}
return acc
}, {})
})
const customersByLocation = computed(() => {
return customers.value.reduce((acc, customer) => {
if (customer && customer.location) {
if (!acc[customer.location]) {
acc[customer.location] = []
}
acc[customer.location].push(customer)
}
return acc
}, {})
})
const customerStats = computed(() => {
const total = customers.value.length
const active = customers.value.filter(c => c && c.status === CUSTOMER_STATUS.ACTIVE).length
const inactive = customers.value.filter(c => c && c.status === CUSTOMER_STATUS.INACTIVE).length
const pending = customers.value.filter(c => c && c.status === CUSTOMER_STATUS.PENDING).length
const suspended = customers.value.filter(c => c && c.status === CUSTOMER_STATUS.SUSPENDED).length
// Group by type
const byType = Object.values(CUSTOMER_TYPES).reduce((acc, type) => {
acc[type] = customers.value.filter(c => c && c.type === type).length
return acc
}, {})
// Group by priority
const byPriority = Object.values(CUSTOMER_PRIORITY).reduce((acc, priority) => {
acc[priority] = customers.value.filter(c => c && c.priority === priority).length
return acc
}, {})
// SLA levels
const slaLevels = customers.value.reduce((acc, customer) => {
if (customer && customer.slaLevel) {
acc[customer.slaLevel] = (acc[customer.slaLevel] || 0) + 1
}
return acc
}, {})
// Notification preferences
const emailNotifications = customers.value.filter(c => c && c.emailNotifications).length
const smsNotifications = customers.value.filter(c => c && c.smsNotifications).length
const selfServiceEnabled = customers.value.filter(c => c && c.allowSelfService).length
// Unique departments and locations
const departments = [...new Set(customers.value.map(c => c && c.department).filter(Boolean))]
const locations = [...new Set(customers.value.map(c => c && c.location).filter(Boolean))]
return {
total,
active,
inactive,
pending,
suspended,
byType,
byPriority,
slaLevels,
emailNotifications,
smsNotifications,
selfServiceEnabled,
departments: departments.length,
locations: locations.length
}
})
const getCustomerById = computed(() => (id) => {
return customersById.value[id] || null
})
const getCustomersByType = computed(() => (type) => {
return customersByType.value[type] || []
})
const getCustomersByDepartment = computed(() => (department) => {
return customersByDepartment.value[department] || []
})
const getCustomersByLocation = computed(() => (location) => {
return customersByLocation.value[location] || []
})
const filteredCustomers = computed(() => {
if (!searchCriteria.value || Object.keys(searchCriteria.value).length === 0) {
return customers.value
}
return customers.value.filter(customer => {
// Apply search filters
if (searchCriteria.value.search) {
const searchTerm = searchCriteria.value.search.toLowerCase()
const matchesName = customer.name.toLowerCase().includes(searchTerm)
const matchesCode = customer.code?.toLowerCase().includes(searchTerm)
const matchesContact = customer.contactPerson?.toLowerCase().includes(searchTerm)
const matchesEmail = customer.email?.toLowerCase().includes(searchTerm)
const matchesDepartment = customer.department?.toLowerCase().includes(searchTerm)
const matchesLocation = customer.location?.toLowerCase().includes(searchTerm)
if (!matchesName && !matchesCode && !matchesContact && !matchesEmail && !matchesDepartment && !matchesLocation) {
return false
}
}
if (searchCriteria.value.type && customer.type !== searchCriteria.value.type) {
return false
}
if (searchCriteria.value.status && customer.status !== searchCriteria.value.status) {
return false
}
if (searchCriteria.value.priority && customer.priority !== searchCriteria.value.priority) {
return false
}
if (searchCriteria.value.department && customer.department !== searchCriteria.value.department) {
return false
}
if (searchCriteria.value.location && customer.location !== searchCriteria.value.location) {
return false
}
if (searchCriteria.value.slaLevel && customer.slaLevel !== searchCriteria.value.slaLevel) {
return false
}
return true
})
})
// Actions
const initializeCustomers = async (force = false) => {
const cacheExpiry = 5 * 60 * 1000 // 5 minutes
const now = Date.now()
if (!force && lastFetch.value && (now - lastFetch.value) < cacheExpiry) {
return
}
loading.value = true
error.value = null
try {
const data = await customerRepository.getAll()
customers.value = data
lastFetch.value = now
} catch (err) {
error.value = err.message
console.error('Failed to initialize customers:', err)
throw err
} finally {
loading.value = false
}
}
const fetchCustomers = async (filters = {}) => {
loading.value = true
error.value = null
searchCriteria.value = { ...filters }
try {
const searchFilters = {
...filters,
page: filters.page || 0,
size: filters.size || 20
}
const response = await customerRepository.search(searchFilters)
if (response.content) {
// Paginated response
customers.value = response.content
pagination.value = {
page: response.number || 0,
size: response.size || 20,
totalElements: response.totalElements || 0,
totalPages: response.totalPages || 0
}
} else {
// Simple array response
customers.value = response
}
} catch (err) {
error.value = err.message
console.error('Failed to fetch customers:', err)
throw err
} finally {
loading.value = false
}
}
const createCustomer = async (customerData) => {
loading.value = true
error.value = null
try {
const newCustomer = await customerRepository.create(customerData)
customers.value.push(newCustomer)
return newCustomer
} catch (err) {
error.value = err.message
console.error('Failed to create customer:', err)
throw err
} finally {
loading.value = false
}
}
const updateCustomer = async (id, customerData) => {
loading.value = true
error.value = null
try {
const updatedCustomer = await customerRepository.update(id, customerData)
// Update in local state
const index = customers.value.findIndex(c => c.id === id)
if (index !== -1) {
customers.value[index] = updatedCustomer
}
// Update current customer if it's the one being edited
if (currentCustomer.value?.id === id) {
currentCustomer.value = updatedCustomer
}
return updatedCustomer
} catch (err) {
error.value = err.message
console.error('Failed to update customer:', err)
throw err
} finally {
loading.value = false
}
}
const deleteCustomer = async (id) => {
loading.value = true
error.value = null
try {
await customerRepository.delete(id)
// Remove from local state
customers.value = customers.value.filter(c => c.id !== id)
// Clear current customer if it's the one being deleted
if (currentCustomer.value?.id === id) {
currentCustomer.value = null
}
} catch (err) {
error.value = err.message
console.error('Failed to delete customer:', err)
throw err
} finally {
loading.value = false
}
}
const setCurrentCustomer = async (id) => {
if (!id) {
currentCustomer.value = null
return
}
// Check if customer is already in local state
const customer = getCustomerById.value(id)
if (customer) {
currentCustomer.value = customer
return customer
}
// Fetch from API
loading.value = true
error.value = null
try {
const customer = await customerRepository.getById(id)
currentCustomer.value = customer
// Add to local state if not already there
if (!customers.value.find(c => c.id === id)) {
customers.value.push(customer)
}
return customer
} catch (err) {
error.value = err.message
console.error('Failed to fetch customer:', err)
throw err
} finally {
loading.value = false
}
}
const updateCustomerStatus = async (id, status) => {
loading.value = true
error.value = null
try {
const updatedCustomer = await customerRepository.updateStatus(id, status)
// Update in local state
const index = customers.value.findIndex(c => c.id === id)
if (index !== -1) {
customers.value[index] = { ...customers.value[index], status }
}
return updatedCustomer
} catch (err) {
error.value = err.message
console.error('Failed to update customer status:', err)
throw err
} finally {
loading.value = false
}
}
const updateCustomerPriority = async (id, priority) => {
loading.value = true
error.value = null
try {
const updatedCustomer = await customerRepository.updatePriority(id, priority)
// Update in local state
const index = customers.value.findIndex(c => c.id === id)
if (index !== -1) {
customers.value[index] = { ...customers.value[index], priority }
}
return updatedCustomer
} catch (err) {
error.value = err.message
console.error('Failed to update customer priority:', err)
throw err
} finally {
loading.value = false
}
}
const updateNotificationSettings = async (id, settings) => {
loading.value = true
error.value = null
try {
const updatedCustomer = await customerRepository.updateNotificationSettings(id, settings)
// Update in local state
const index = customers.value.findIndex(c => c.id === id)
if (index !== -1) {
customers.value[index] = { ...customers.value[index], ...settings }
}
return updatedCustomer
} catch (err) {
error.value = err.message
console.error('Failed to update notification settings:', err)
throw err
} finally {
loading.value = false
}
}
const bulkUpdateCustomers = async (updates) => {
loading.value = true
error.value = null
try {
const updatedCustomers = await customerRepository.bulkUpdate(updates)
// Update local state
updatedCustomers.forEach(updatedCustomer => {
const index = customers.value.findIndex(c => c.id === updatedCustomer.id)
if (index !== -1) {
customers.value[index] = updatedCustomer
}
})
return updatedCustomers
} catch (err) {
error.value = err.message
console.error('Failed to bulk update customers:', err)
throw err
} finally {
loading.value = false
}
}
const validateCustomerCode = async (code, excludeId = null) => {
try {
const result = await customerRepository.validateCode(code, excludeId)
return result.isValid
} catch (err) {
console.error('Failed to validate customer code:', err)
return false
}
}
const getDepartments = async () => {
try {
return await customerRepository.getDepartments()
} catch (err) {
console.error('Failed to fetch departments:', err)
return []
}
}
const getLocations = async () => {
try {
return await customerRepository.getLocations()
} catch (err) {
console.error('Failed to fetch locations:', err)
return []
}
}
const refreshCustomers = async () => {
lastFetch.value = null
await initializeCustomers(true)
}
const clearError = () => {
error.value = null
}
const clearCurrentCustomer = () => {
currentCustomer.value = null
}
const clearCustomers = () => {
customers.value = []
currentCustomer.value = null
lastFetch.value = null
searchCriteria.value = {}
pagination.value = {
page: 0,
size: 20,
totalElements: 0,
totalPages: 0
}
}
const loadMockData = () => {
customers.value = [
{
id: 1,
customerCode: 'CUST-001',
name: 'ABC Corporation',
email: 'contact@abc-corp.com',
phone: '+1 (555) 123-4567',
type: CUSTOMER_TYPES.EXTERNAL,
priority: CUSTOMER_PRIORITY.HIGH,
status: CUSTOMER_STATUS.ACTIVE,
description: 'Large manufacturing company requiring 24/7 support',
serviceHours: '24x7',
sla: {
responseTime: '2h',
resolutionTime: '8h',
availability: 99.9
},
notifications: {
email: true,
sms: true,
phone: false,
portal: true
},
primaryContact: {
name: 'John Smith',
title: 'Facilities Manager',
email: 'j.smith@abc-corp.com',
phone: '+1 (555) 123-4568'
},
address: {
street: '123 Industrial Blvd',
city: 'Manufacturing City',
state: 'CA',
zipCode: '90210'
},
lastContactDate: '2024-01-15T10:30:00Z',
lastContactMethod: 'Email',
createdAt: '2023-06-15T09:00:00Z',
updatedAt: '2024-01-15T10:30:00Z'
},
{
id: 2,
customerCode: 'CUST-002',
name: 'City Health Department',
email: 'facilities@cityhealth.gov',
phone: '+1 (555) 987-6543',
type: CUSTOMER_TYPES.GOVERNMENT,
priority: CUSTOMER_PRIORITY.MEDIUM,
status: CUSTOMER_STATUS.ACTIVE,
description: 'Municipal health department with multiple facilities',
serviceHours: 'business',
sla: {
responseTime: '4h',
resolutionTime: '24h',
availability: 99.5
},
notifications: {
email: true,
sms: false,
phone: true,
portal: false
},
primaryContact: {
name: 'Sarah Johnson',
title: 'Operations Director',
email: 's.johnson@cityhealth.gov',
phone: '+1 (555) 987-6544'
},
address: {
street: '456 Government Plaza',
city: 'Capital City',
state: 'CA',
zipCode: '90211'
},
lastContactDate: '2024-01-10T14:15:00Z',
lastContactMethod: 'Phone',
createdAt: '2023-08-20T11:30:00Z',
updatedAt: '2024-01-10T14:15:00Z'
},
{
id: 3,
customerCode: 'CUST-003',
name: 'Tech Innovations LLC',
email: 'admin@techinnovations.com',
phone: '+1 (555) 456-7890',
type: CUSTOMER_TYPES.PARTNER,
priority: CUSTOMER_PRIORITY.LOW,
status: CUSTOMER_STATUS.INACTIVE,
description: 'Technology partner requiring occasional maintenance support',
serviceHours: 'extended',
sla: {
responseTime: '8h',
resolutionTime: '48h',
availability: 95.0
},
notifications: {
email: true,
sms: false,
phone: false,
portal: true
},
primaryContact: {
name: 'Mike Chen',
title: 'IT Manager',
email: 'm.chen@techinnovations.com',
phone: '+1 (555) 456-7891'
},
address: {
street: '789 Tech Park Dr',
city: 'Silicon Valley',
state: 'CA',
zipCode: '90212'
},
lastContactDate: '2023-12-05T16:45:00Z',
lastContactMethod: 'Portal',
createdAt: '2023-04-10T13:20:00Z',
updatedAt: '2023-12-05T16:45:00Z'
}
]
lastFetch.value = Date.now()
}
return {
// State
customers: computed(() => customers.value),
currentCustomer: computed(() => currentCustomer.value),
loading: computed(() => loading.value),
error: computed(() => error.value),
searchCriteria: computed(() => searchCriteria.value),
pagination: computed(() => pagination.value),
// Getters
customersById,
activeCustomers,
highPriorityCustomers,
customersByType,
customersByDepartment,
customersByLocation,
customerStats,
getCustomerById,
getCustomersByType,
getCustomersByDepartment,
getCustomersByLocation,
filteredCustomers,
// Options for dropdowns
customerTypeOptions: computed(() => {
return Object.values(CUSTOMER_TYPES).map(type => ({
label: type.charAt(0).toUpperCase() + type.slice(1),
value: type
}))
}),
priorityOptions: computed(() => {
return Object.values(CUSTOMER_PRIORITY).map(priority => ({
label: priority.charAt(0).toUpperCase() + priority.slice(1),
value: priority
}))
}),
statusOptions: computed(() => {
return Object.values(CUSTOMER_STATUS).map(status => ({
label: status.charAt(0).toUpperCase() + status.slice(1),
value: status
}))
}),
responseTimeOptions: computed(() => [
{ label: '1 Hour', value: '1h' },
{ label: '2 Hours', value: '2h' },
{ label: '4 Hours', value: '4h' },
{ label: '8 Hours', value: '8h' },
{ label: '24 Hours', value: '24h' },
{ label: '48 Hours', value: '48h' }
]),
resolutionTimeOptions: computed(() => [
{ label: '4 Hours', value: '4h' },
{ label: '8 Hours', value: '8h' },
{ label: '24 Hours', value: '24h' },
{ label: '48 Hours', value: '48h' },
{ label: '72 Hours', value: '72h' },
{ label: '1 Week', value: '1w' }
]),
serviceHoursOptions: computed(() => [
{ label: '24x7', value: '24x7' },
{ label: 'Business Hours (9AM-5PM)', value: 'business' },
{ label: 'Extended Hours (7AM-7PM)', value: 'extended' },
{ label: 'Custom', value: 'custom' }
]),
// Statistics
totalCustomers: computed(() => customers.value.length),
activeCustomersCount: computed(() => activeCustomers.value.length),
inactiveCustomersCount: computed(() => customers.value.filter(c => c && c.status === CUSTOMER_STATUS.INACTIVE).length),
highPriorityCustomersCount: computed(() => highPriorityCustomers.value.length),
averageResponseTime: computed(() => {
const responseTimes = customers.value
.map(c => c && c.sla?.responseTime)
.filter(Boolean)
.map(time => parseInt(time))
if (responseTimes.length === 0) return null
const avg = responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length
return `${Math.round(avg)}h`
}),
// Actions
initializeCustomers,
fetchCustomers,
createCustomer,
updateCustomer,
deleteCustomer,
setCurrentCustomer,
updateCustomerStatus,
updateCustomerPriority,
updateNotificationSettings,
bulkUpdateCustomers,
validateCustomerCode,
getDepartments,
getLocations,
refreshCustomers,
clearError,
clearCurrentCustomer,
clearCustomers,
loadMockData
}
})