Files
Inventory/frontend/app/composables/useMachineDetailUpdates.ts
r-dev 0049638e3c fix(custom-fields) : context fields display, batch save, hierarchy propagation and UniqueEntity fix
- ComponentItem/PieceItem: DaisyUI divider, emit context-field-update for batch save
- CustomFieldDisplay: support editable/emit-blur/title/show-header props
- MachineComponentsCard/MachinePiecesCard: propagate custom-field-update events
- useMachineDetailCustomFields: pendingContextFieldUpdates + saveAllContextCustomFields
- useMachineDetailData: wire context field save into submitEdition
- useMachineDetailUpdates: only PATCH changed machine fields
- useMachineHierarchy: propagate contextCustomFields/Values from link to nodes
- componentStructure: include machineContextOnly in normalizeStructureForEditor
- Machine entity: convert empty reference to null, ignoreNull on UniqueEntity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:46:39 +02:00

245 lines
8.6 KiB
TypeScript

/**
* Machine detail page — update/mutation methods.
*
* Extracted from useMachineDetailData.ts to keep the orchestrator under 500 lines.
*/
import type { Ref } from 'vue'
import { uniqueConstructeurIds, constructeurIdsFromLinks } from '~/shared/constructeurUtils'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
type AnyRecord = Record<string, unknown>
export interface UseMachineDetailUpdatesDeps {
machine: Ref<AnyRecord | null>
machineName: Ref<string>
machineReference: Ref<string>
machineSiteId: Ref<string>
machineConstructeurIds: Ref<string[]>
constructeurLinks: Ref<ConstructeurLinkEntry[]>
originalConstructeurLinks: Ref<ConstructeurLinkEntry[]>
machineDocumentsLoaded: Ref<boolean>
machineComponentLinks: Ref<AnyRecord[]>
machinePieceLinks: Ref<AnyRecord[]>
machineProductLinks: Ref<AnyRecord[]>
applyMachineLinks: (data: AnyRecord) => boolean
refreshMachineDocuments: () => Promise<void>
transformComponentCustomFields: (items: AnyRecord[]) => AnyRecord[]
transformCustomFields: (items: AnyRecord[]) => AnyRecord[]
loadProductDocuments: () => Promise<void>
upsertCustomFieldValue: (
fieldId: string,
entityType: string,
entityId: string,
value: unknown,
) => Promise<unknown>
updateMachineApi: (id: string, data: any) => Promise<unknown>
updateComposantApi: (id: string, data: any) => Promise<unknown>
updatePieceApi: (id: string, data: any) => Promise<unknown>
apiPatch: (endpoint: string, data?: unknown) => Promise<any>
toast: { showInfo: (msg: string) => void }
syncLinks: (
entityType: 'machine' | 'piece' | 'composant' | 'product',
entityId: string,
originalLinks: ConstructeurLinkEntry[],
formLinks: ConstructeurLinkEntry[],
) => Promise<void>
}
export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
const {
machine,
machineName,
machineReference,
machineSiteId,
machineConstructeurIds,
constructeurLinks,
originalConstructeurLinks,
machineComponentLinks,
machinePieceLinks,
applyMachineLinks,
loadProductDocuments,
transformComponentCustomFields,
transformCustomFields,
upsertCustomFieldValue,
updateMachineApi,
updateComposantApi,
updatePieceApi,
apiPatch,
toast,
syncLinks,
} = deps
const updateMachineInfo = async () => {
if (!machine.value) return
try {
const payload: Record<string, unknown> = {}
if (machineName.value !== machine.value.name) payload.name = machineName.value
if (machineReference.value !== machine.value.reference) payload.reference = machineReference.value
if ((machineSiteId.value || undefined) !== ((machine.value.siteId as string) || (machine.value.site as any)?.id || undefined)) payload.siteId = machineSiteId.value || undefined
const result: any = await updateMachineApi(machine.value.id as string, payload as any)
if (result.success) {
const machinePayload =
result.data?.machine && typeof result.data.machine === 'object'
? result.data.machine
: result.data
if (machinePayload && typeof machinePayload === 'object') {
machine.value = {
...machine.value,
...machinePayload,
documents: machinePayload.documents || machine.value.documents || [],
customFieldValues: machinePayload.customFieldValues || machine.value.customFieldValues || [],
}
const linksApplied = applyMachineLinks(result.data)
if (linksApplied && machine.value) {
machine.value.componentLinks = machineComponentLinks.value
machine.value.pieceLinks = machinePieceLinks.value
}
loadProductDocuments().catch(() => {})
}
}
// Sync constructeur links after entity save
await syncLinks('machine', machine.value!.id as string, originalConstructeurLinks.value, constructeurLinks.value)
originalConstructeurLinks.value = constructeurLinks.value.map(l => ({ ...l }))
} catch (error) {
console.error('Erreur lors de la mise à jour de la machine:', error)
}
}
const updateComponent = async (updatedComponent: AnyRecord) => {
try {
const cIds = uniqueConstructeurIds(
updatedComponent.constructeurIds,
updatedComponent.constructeurId,
updatedComponent.constructeur,
)
const productId = updatedComponent.productId
? String(updatedComponent.productId)
: null
const prixStr =
updatedComponent.prix !== null &&
updatedComponent.prix !== undefined &&
String(updatedComponent.prix).trim() !== ''
? String(updatedComponent.prix)
: null
const result: any = await updateComposantApi(updatedComponent.id as string, {
name: updatedComponent.name,
reference: updatedComponent.reference,
constructeurIds: cIds,
prix: prixStr,
productId,
} as any)
if (result.success) {
const transformed = transformComponentCustomFields([result.data])[0]
Object.assign(updatedComponent, transformed)
}
} catch (error) {
console.error('Erreur lors de la mise à jour du composant:', error)
}
}
const _buildAndUpdatePiece = async (updatedPiece: AnyRecord) => {
const cIds = uniqueConstructeurIds(
updatedPiece.constructeurIds,
updatedPiece.constructeurId,
updatedPiece.constructeur,
)
const productId = updatedPiece.productId ? String(updatedPiece.productId) : null
const prixStr =
updatedPiece.prix !== null &&
updatedPiece.prix !== undefined &&
String(updatedPiece.prix).trim() !== ''
? String(updatedPiece.prix)
: null
const result: any = await updatePieceApi(updatedPiece.id as string, {
name: updatedPiece.name,
reference: updatedPiece.reference || null,
constructeurIds: cIds,
prix: prixStr,
productId,
} as any)
if (result.success) {
const transformed = transformCustomFields([result.data])[0]
Object.assign(updatedPiece, transformed)
}
return result
}
const updatePieceFromComponent = async (updatedPiece: AnyRecord) => {
try {
const result = await _buildAndUpdatePiece(updatedPiece)
if (result?.success && updatedPiece.customFields) {
const fieldsToSave = (updatedPiece.customFields as AnyRecord[]).filter(
(field) => field.value !== undefined,
)
if (fieldsToSave.length) {
await Promise.allSettled(
fieldsToSave.map((field) =>
upsertCustomFieldValue(
field.id as string,
'piece',
updatedPiece.id as string,
field.value,
),
),
)
}
}
// Update slot quantity if this is a composant structure piece
const slotId = updatedPiece.slotId as string | null
const quantity = typeof updatedPiece.quantity === 'number' ? Math.max(1, updatedPiece.quantity) : null
if (slotId && quantity !== null) {
await apiPatch(`/composant-piece-slots/${slotId}`, { quantity })
}
} catch (error) {
console.error('Erreur lors de la mise à jour de la pièce:', error)
}
}
const updatePieceInfo = async (updatedPiece: AnyRecord) => {
try {
await _buildAndUpdatePiece(updatedPiece)
// Update link quantity if this is a direct machine piece
const linkId = updatedPiece.linkId || updatedPiece.machinePieceLinkId
const quantity = typeof updatedPiece.quantity === 'number' ? Math.max(1, updatedPiece.quantity) : null
if (linkId && quantity !== null) {
await apiPatch(`/machine_piece_links/${linkId}`, { quantity })
}
} catch (error) {
console.error('Erreur lors de la mise à jour de la pièce:', error)
}
}
const handleMachineConstructeurChange = (value: unknown) => {
const newIds = uniqueConstructeurIds(value)
machineConstructeurIds.value = newIds
// Sync constructeurLinks: keep existing entries, add new ones
const existingMap = new Map(constructeurLinks.value.map(l => [l.constructeurId, l]))
constructeurLinks.value = newIds.map(id =>
existingMap.get(id) ?? { constructeurId: id, supplierReference: null },
)
}
const editComponent = () => {
toast.showInfo('La modification des composants sera bientôt disponible')
}
const editPiece = () => {
toast.showInfo('La modification des pièces sera bientôt disponible')
}
return {
updateMachineInfo,
updateComponent,
updatePieceFromComponent,
updatePieceInfo,
handleMachineConstructeurChange,
editComponent,
editPiece,
}
}