DispatchPage.vue: 1320→1217 lines - Extract SbModal.vue + SbContextMenu.vue reusable components - Extract useAbsenceResize composable - Extract dispatch constants to config/dispatch.js ProjectWizard.vue: 1185→673 lines (-43%) - Extract useWizardPublish composable (270-line publish function) - Extract useWizardCatalog composable - Extract wizard-constants.js (step labels, options, categories) SettingsPage.vue: 1172→850 lines (-27%) - Extract usePermissionMatrix composable - Extract useUserGroups composable - Extract useLegacySync composable ClientDetailPage.vue: 1169→864 lines (-26%) - Extract useClientData composable (loadCustomer broken into sub-functions) - Extract useEquipmentActions composable - Extract client-constants.js + erp-pdf.js utility checkout.js: 639→408 lines (-36%) - Extract address-search.js module - Extract otp.js module - Extract email-templates.js module - Extract project-templates.js module - Add erpQuery() helper to DRY repeated URL construction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
4.7 KiB
JavaScript
138 lines
4.7 KiB
JavaScript
import { ref, watch } from 'vue'
|
|
import { Notify } from 'quasar'
|
|
import { listDocs, createDoc, updateDoc } from 'src/api/erp'
|
|
import { useScanner } from 'src/composables/useScanner'
|
|
import { equipScanTypeMap, defaultNewEquip } from 'src/data/client-constants'
|
|
|
|
export function useEquipmentActions (customer, equipment) {
|
|
const scannerState = useScanner()
|
|
const addEquipOpen = ref(false)
|
|
const addEquipLoc = ref(null)
|
|
const addingEquip = ref(false)
|
|
const equipLookupResult = ref(null)
|
|
const equipLookingUp = ref(false)
|
|
const newEquip = ref(defaultNewEquip())
|
|
|
|
let lookupTimer = null
|
|
watch(() => newEquip.value.serial_number, (sn) => {
|
|
clearTimeout(lookupTimer)
|
|
equipLookupResult.value = null
|
|
if (!sn || sn.length < 3) return
|
|
lookupTimer = setTimeout(() => lookupSerial(sn), 500)
|
|
})
|
|
|
|
async function lookupSerial (sn) {
|
|
equipLookingUp.value = true
|
|
try {
|
|
const results = await listDocs('Service Equipment', {
|
|
filters: { serial_number: sn },
|
|
fields: ['name', 'equipment_type', 'brand', 'model', 'serial_number', 'service_location', 'customer'],
|
|
limit: 1,
|
|
})
|
|
if (results.length) {
|
|
equipLookupResult.value = { found: true, equipment: results[0] }
|
|
}
|
|
} catch { /* ignore */ }
|
|
finally { equipLookingUp.value = false }
|
|
}
|
|
|
|
function openAddEquipment (loc) {
|
|
addEquipLoc.value = loc
|
|
newEquip.value = defaultNewEquip()
|
|
equipLookupResult.value = null
|
|
scannerState.clearBarcodes()
|
|
addEquipOpen.value = true
|
|
}
|
|
|
|
function closeAddEquipment () {
|
|
addEquipOpen.value = false
|
|
addEquipLoc.value = null
|
|
scannerState.clearBarcodes()
|
|
}
|
|
|
|
async function onScanPhoto (e) {
|
|
const file = e.target?.files?.[0]
|
|
if (!file) return
|
|
const result = await scannerState.scanEquipmentLabel(file)
|
|
if (result) {
|
|
if (result.serial_number && !newEquip.value.serial_number) newEquip.value.serial_number = result.serial_number
|
|
if (result.brand) newEquip.value.brand = result.brand
|
|
if (result.model) newEquip.value.model = result.model
|
|
if (result.mac_address) newEquip.value.mac_address = result.mac_address
|
|
if (result.ip_address) newEquip.value.ip_address = result.ip_address
|
|
if (result.equipment_type) {
|
|
const mapped = equipScanTypeMap[result.equipment_type.toLowerCase()]
|
|
if (mapped) newEquip.value.equipment_type = mapped
|
|
}
|
|
}
|
|
e.target.value = ''
|
|
}
|
|
|
|
function applyScannedCode (code) {
|
|
newEquip.value.serial_number = code
|
|
}
|
|
|
|
async function createEquipment () {
|
|
addingEquip.value = true
|
|
try {
|
|
const doc = await createDoc('Service Equipment', {
|
|
...newEquip.value,
|
|
customer: customer.value.name,
|
|
service_location: addEquipLoc.value.name,
|
|
})
|
|
equipment.value.push({
|
|
name: doc.name,
|
|
equipment_type: newEquip.value.equipment_type,
|
|
brand: newEquip.value.brand,
|
|
model: newEquip.value.model,
|
|
serial_number: newEquip.value.serial_number,
|
|
mac_address: newEquip.value.mac_address,
|
|
ip_address: newEquip.value.ip_address,
|
|
status: newEquip.value.status,
|
|
service_location: addEquipLoc.value.name,
|
|
})
|
|
Notify.create({ type: 'positive', message: `Equipement ${doc.name} cree`, timeout: 2000 })
|
|
closeAddEquipment()
|
|
} catch (e) {
|
|
Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
|
|
} finally {
|
|
addingEquip.value = false
|
|
}
|
|
}
|
|
|
|
async function linkExistingEquipment () {
|
|
if (!equipLookupResult.value?.found) return
|
|
addingEquip.value = true
|
|
try {
|
|
const existing = equipLookupResult.value.equipment
|
|
await updateDoc('Service Equipment', existing.name, {
|
|
customer: customer.value.name,
|
|
service_location: addEquipLoc.value.name,
|
|
})
|
|
const idx = equipment.value.findIndex(e => e.name === existing.name)
|
|
if (idx >= 0) {
|
|
equipment.value[idx].service_location = addEquipLoc.value.name
|
|
} else {
|
|
equipment.value.push({
|
|
...existing,
|
|
service_location: addEquipLoc.value.name,
|
|
customer: customer.value.name,
|
|
})
|
|
}
|
|
Notify.create({ type: 'positive', message: `Equipement ${existing.name} lie a ${addEquipLoc.value.address_line || addEquipLoc.value.location_name}`, timeout: 2000 })
|
|
closeAddEquipment()
|
|
} catch (e) {
|
|
Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
|
|
} finally {
|
|
addingEquip.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
scannerState, addEquipOpen, addEquipLoc, addingEquip,
|
|
equipLookupResult, equipLookingUp, newEquip,
|
|
openAddEquipment, closeAddEquipment, onScanPhoto,
|
|
applyScannedCode, createEquipment, linkExistingEquipment,
|
|
}
|
|
}
|