feat: auto complete missing custom fields on machine page

This commit is contained in:
MatthieuTD
2025-09-22 11:12:00 +02:00
parent ae103a38be
commit 5501b3b5ef
2 changed files with 355 additions and 5 deletions

View File

@@ -97,6 +97,34 @@ export function useMachines() {
}
}
const addMissingCustomFields = async (machineId, { showToast: shouldShowToast = true } = {}) => {
if (!machineId) {
const error = 'Identifiant de machine manquant'
if (shouldShowToast) {
showError(error)
}
return { success: false, error }
}
try {
const result = await post(`/machines/${machineId}/add-custom-fields`)
if (result.success) {
if (shouldShowToast) {
showSuccess('Champs personnalisés complétés avec succès')
}
} else if (shouldShowToast && result.error) {
showError(result.error)
}
return result
} catch (error) {
console.error('Erreur lors de lajout des champs personnalisés manquants:', error)
if (shouldShowToast) {
showError('Erreur lors de la complétion des champs personnalisés')
}
return { success: false, error: error.message }
}
}
const deleteMachine = async (id) => {
loading.value = true
try {
@@ -143,6 +171,7 @@ export function useMachines() {
getMachinesBySite,
getMachinesByType,
getMachines,
isLoading
isLoading,
addMissingCustomFields
}
}
}

View File

@@ -43,6 +43,20 @@
>
Modifier les éléments du squelette
</button>
<button
type="button"
class="btn btn-outline"
:class="{ 'btn-primary': hasMissingRequiredCustomFields }"
:disabled="completingCustomFields"
@click="handleCompleteCustomFieldsClick"
>
<span
v-if="completingCustomFields"
class="loading loading-spinner loading-sm mr-2"
aria-hidden="true"
/>
Compléter les champs personnalisés
</button>
<button
v-if="!isEditMode"
@click="openPrintModal"
@@ -918,7 +932,11 @@ if (!machineId) {
}
// Composables
const { updateMachine: updateMachineApi, reconfigureSkeleton: reconfigureMachineSkeleton } = useMachines()
const {
updateMachine: updateMachineApi,
reconfigureSkeleton: reconfigureMachineSkeleton,
addMissingCustomFields: addMissingCustomFieldsApi,
} = useMachines()
const {
getComposantsByMachine,
updateComposant: updateComposantApi
@@ -954,6 +972,7 @@ const loading = ref(true)
const machine = ref(null)
const components = ref([])
const pieces = ref([])
const completingCustomFields = ref(false)
const printAreaRef = ref(null)
const { constructeurs, loadConstructeurs } = useConstructeurs()
@@ -1350,6 +1369,7 @@ const applySkeletonReconfigurationResult = async (data) => {
}
await ensureModelsForExistingEntities()
await autoCompleteMissingCustomFieldsIfNeeded()
}
const saveSkeletonConfiguration = async () => {
@@ -1489,6 +1509,26 @@ const flattenComponents = (list = []) => {
const flattenedComponents = computed(() => flattenComponents(components.value))
const hasMissingRequiredCustomFields = computed(() => {
if (!machine.value) {
return false
}
if (hasMissingRequiredCustomFieldsInMachine(machine.value)) {
return true
}
if (components.value.some(component => hasMissingRequiredCustomFieldsInComponent(component))) {
return true
}
if (pieces.value.some(piece => hasMissingRequiredCustomFieldsInPiece(piece))) {
return true
}
return false
})
const preloadModelsForTypeMachine = async (typeMachine) => {
if (!typeMachine) return
const componentTypeIds = new Set(
@@ -1854,7 +1894,7 @@ const transformCustomFields = (pieces) => {
// Transform custom fields for components (now handles nested structure)
const transformComponentCustomFields = (componentsData) => {
console.log('transformComponentCustomFields called with:', componentsData)
return componentsData.map(component => {
console.log('Processing component:', component.name, 'with sousComposants:', component.sousComposants?.length || 0)
@@ -1895,6 +1935,281 @@ const transformComponentCustomFields = (componentsData) => {
});
};
function getCustomFieldIdentifier(field) {
if (!field) {
return null
}
return field.customFieldId ?? field.customField?.id ?? field.id ?? null
}
function normalizeCustomFieldEntries(entries = []) {
return entries
.map(entry => ({
id: getCustomFieldIdentifier(entry),
required: entry?.customField?.required ?? entry?.required ?? false,
value: entry?.value ?? null,
}))
.filter(entry => entry.id !== null)
}
function collectCustomFieldDefinitions(...sources) {
return sources.flatMap(source => (Array.isArray(source) ? source : []))
}
function isEmptyCustomFieldValue(value) {
if (value === null || value === undefined) {
return true
}
if (typeof value === 'string') {
return value.trim() === ''
}
return false
}
function hasMissingCustomFieldValues(values = []) {
return values.some(entry => entry.required && isEmptyCustomFieldValue(entry.value))
}
function hasMissingCustomFieldDefinitions(definitions = [], values = []) {
if (!definitions.length) {
return false
}
const valueIds = new Set(values.map(entry => entry.id))
return definitions.some(definition => {
if (!definition?.required) {
return false
}
const id = getCustomFieldIdentifier(definition)
return id !== null && !valueIds.has(id)
})
}
function hasMissingRequiredCustomFieldsInMachine(machineData) {
if (!machineData) {
return false
}
const values = normalizeCustomFieldEntries(machineData.customFieldValues || [])
const definitions = collectCustomFieldDefinitions(machineData.typeMachine?.customFields)
return (
hasMissingCustomFieldDefinitions(definitions, values)
|| hasMissingCustomFieldValues(values)
)
}
function hasMissingRequiredCustomFieldsInComponent(component) {
if (!component) {
return false
}
const sourceValues = component.customFields?.length
? component.customFields
: component.customFieldValues || []
const values = normalizeCustomFieldEntries(sourceValues)
const definitions = collectCustomFieldDefinitions(
component.typeComposant?.customFields,
component.composantModel?.customFields,
component.typeMachineComponentRequirement?.customFields,
)
if (
hasMissingCustomFieldDefinitions(definitions, values)
|| hasMissingCustomFieldValues(values)
) {
return true
}
if (
Array.isArray(component.pieces)
&& component.pieces.some(piece => hasMissingRequiredCustomFieldsInPiece(piece))
) {
return true
}
if (
Array.isArray(component.subComponents)
&& component.subComponents.some(sub => hasMissingRequiredCustomFieldsInComponent(sub))
) {
return true
}
return false
}
function hasMissingRequiredCustomFieldsInPiece(piece) {
if (!piece) {
return false
}
const sourceValues = piece.customFields?.length
? piece.customFields
: piece.customFieldValues || []
const values = normalizeCustomFieldEntries(sourceValues)
const definitions = collectCustomFieldDefinitions(
piece.typePiece?.customFields,
piece.pieceModel?.customFields,
piece.typeMachinePieceRequirement?.customFields,
)
return (
hasMissingCustomFieldDefinitions(definitions, values)
|| hasMissingCustomFieldValues(values)
)
}
function mergePieceLists(existing = [], updates = []) {
if (!existing.length) {
return updates
}
if (!updates.length) {
return existing
}
const updateMap = new Map(updates.map(piece => [piece.id, piece]))
const merged = existing.map(piece => {
const update = updateMap.get(piece.id)
if (!update) {
return piece
}
return {
...piece,
...update,
customFields: update.customFields ?? piece.customFields,
}
})
updates.forEach(update => {
if (!existing.some(piece => piece.id === update.id)) {
merged.push(update)
}
})
return merged
}
function mergeComponentTrees(existing = [], updates = []) {
if (!existing.length) {
return updates
}
if (!updates.length) {
return existing
}
const updateMap = new Map(updates.map(component => [component.id, component]))
const merged = existing.map(component => {
const update = updateMap.get(component.id)
if (!update) {
return component
}
return {
...component,
...update,
customFields: update.customFields ?? component.customFields,
pieces: mergePieceLists(component.pieces || [], update.pieces || []),
subComponents: mergeComponentTrees(component.subComponents || [], update.subComponents || []),
}
})
updates.forEach(update => {
if (!existing.some(component => component.id === update.id)) {
merged.push(update)
}
})
return merged
}
async function applyCustomFieldsCompletionResult(payload) {
if (!payload) {
return
}
const updatedMachine = payload.machine || payload
if (machine.value && updatedMachine?.customFieldValues) {
machine.value = {
...machine.value,
customFieldValues: updatedMachine.customFieldValues,
}
}
const updatedComponentsRaw = payload.components ?? updatedMachine?.components ?? null
if (Array.isArray(updatedComponentsRaw)) {
const transformedComponents = transformComponentCustomFields(updatedComponentsRaw)
components.value = mergeComponentTrees(components.value, transformedComponents)
}
const updatedPiecesRaw = payload.pieces ?? updatedMachine?.pieces ?? null
if (Array.isArray(updatedPiecesRaw)) {
const transformedPieces = transformCustomFields(updatedPiecesRaw)
pieces.value = mergePieceLists(pieces.value, transformedPieces)
}
await ensureModelsForExistingEntities()
}
async function completeMissingCustomFields({ silent = false, checkMissing = true } = {}) {
if (!machine.value?.id) {
const error = 'Machine introuvable'
if (!silent) {
toast.showError(error)
}
return { success: false, error }
}
if (checkMissing && !hasMissingRequiredCustomFields.value) {
return { success: true, skipped: true }
}
if (completingCustomFields.value) {
if (!silent) {
toast.showInfo('Complétion des champs personnalisés déjà en cours')
}
return { success: false, error: 'Complétion déjà en cours' }
}
completingCustomFields.value = true
try {
const result = await addMissingCustomFieldsApi(machine.value.id, { showToast: false })
if (result.success) {
await applyCustomFieldsCompletionResult(result.data)
if (!silent) {
toast.showSuccess('Champs personnalisés complétés avec succès')
}
} else if (!silent) {
toast.showError(result.error || 'Impossible de compléter les champs personnalisés')
}
return result
} catch (error) {
console.error('Erreur lors de la complétion des champs personnalisés:', error)
if (!silent) {
toast.showError('Impossible de compléter les champs personnalisés')
}
return { success: false, error: error?.message || 'Erreur inconnue' }
} finally {
completingCustomFields.value = false
}
}
async function autoCompleteMissingCustomFieldsIfNeeded() {
await nextTick()
if (completingCustomFields.value) {
return
}
if (!machine.value?.id) {
return
}
if (!hasMissingRequiredCustomFields.value) {
return
}
await completeMissingCustomFields({ silent: true, checkMissing: true })
}
// Methods
const loadMachineData = async () => {
loading.value = true
@@ -1970,6 +2285,8 @@ const loadMachineData = async () => {
console.log('Aucune pièce trouvée dans la réponse de la machine')
}
await autoCompleteMissingCustomFieldsIfNeeded()
if (!machineDocumentsLoaded.value) {
await refreshMachineDocuments()
}
@@ -2211,7 +2528,7 @@ const updateMachineCustomField = async (fieldValueId) => {
const updatePieceCustomField = async (fieldUpdate) => {
const { showSuccess, showError } = useToast()
try {
const result = await upsertCustomFieldValue(
fieldUpdate.fieldId,
@@ -2230,6 +2547,10 @@ const updatePieceCustomField = async (fieldUpdate) => {
}
}
const handleCompleteCustomFieldsClick = async () => {
await completeMissingCustomFields({ silent: false, checkMissing: false })
}
const editComponent = (component) => {
// TODO: Implement edit modal
console.log('Edit component:', component)