Files
Inventory/app/composables/useEntityCustomFields.ts
Matthieu 519fa3a8f4 refactor(components): extract shared entity utilities and simplify item components (F1.3, F1.4)
Extract 3 entity composables (useEntityCustomFields, useEntityDocuments,
useEntityProductDisplay) and entityCustomFieldLogic utility shared across
ComponentItem (1336→585 LOC) and PieceItem (1588→740 LOC).
Improve type safety in edit/create pages with explicit casts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 11:19:40 +01:00

182 lines
5.6 KiB
TypeScript

/**
* Reactive custom field management for entity items (ComponentItem, PieceItem).
*
* Wraps the pure logic from entityCustomFieldLogic.ts with Vue reactivity,
* watchers, and API calls for updating/upserting custom field values.
*/
import { computed, watch } from 'vue'
import { useCustomFields } from '~/composables/useCustomFields'
import { useToast } from '~/composables/useToast'
import {
buildDefinitionSources,
buildCandidateCustomFields,
mergeFieldDefinitionsWithValues,
dedupeMergedFields,
ensureCustomFieldId,
resolveFieldId,
resolveFieldName,
resolveFieldType,
resolveFieldReadOnly,
resolveCustomFieldId,
buildCustomFieldMetadata,
} from '~/shared/utils/entityCustomFieldLogic'
export interface EntityCustomFieldsDeps {
entity: () => any
entityType: 'composant' | 'piece'
}
export function useEntityCustomFields(deps: EntityCustomFieldsDeps) {
const { entity, entityType } = deps
const {
updateCustomFieldValue: updateCustomFieldValueApi,
upsertCustomFieldValue,
} = useCustomFields()
const { showSuccess, showError } = useToast()
const definitionSources = computed(() =>
buildDefinitionSources(entity(), entityType),
)
const displayedCustomFields = computed(() =>
dedupeMergedFields(
mergeFieldDefinitionsWithValues(
definitionSources.value,
entity().customFieldValues,
),
),
)
const candidateCustomFields = computed(() =>
buildCandidateCustomFields(entity(), definitionSources.value),
)
// Watchers to ensure field IDs are resolved
watch(
candidateCustomFields,
() => {
const candidates = candidateCustomFields.value
;(displayedCustomFields.value || []).forEach((field: any) => {
if (field) ensureCustomFieldId(field, candidates)
})
},
{ immediate: true, deep: true },
)
watch(
displayedCustomFields,
(fields) => {
const candidates = candidateCustomFields.value
;(fields || []).forEach((field: any) => {
if (field) ensureCustomFieldId(field, candidates)
})
},
{ immediate: true, deep: true },
)
const updateCustomField = async (field: any) => {
if (!field || resolveFieldReadOnly(field)) return
const e = entity()
const fieldValueId = resolveFieldId(field)
// Update existing field value
if (fieldValueId) {
const result: any = await updateCustomFieldValueApi(fieldValueId, { value: field.value ?? '' })
if (result.success) {
const existingValue = e.customFieldValues?.find((v: any) => v.id === fieldValueId)
if (existingValue?.customField?.id) {
field.customFieldId = existingValue.customField.id
field.customField = existingValue.customField
}
showSuccess(`Champ "${resolveFieldName(field)}" mis à jour avec succès`)
} else {
showError(`Erreur lors de la mise à jour du champ "${resolveFieldName(field)}"`)
}
return
}
// Create new field value
const customFieldId = ensureCustomFieldId(field, candidateCustomFields.value)
const fieldName = resolveFieldName(field)
if (!e?.id) {
showError(`Impossible de créer la valeur pour ce champ`)
return
}
if (!customFieldId && (!fieldName || fieldName === 'Champ')) {
showError(`Impossible de créer la valeur pour ce champ`)
return
}
const metadata = customFieldId ? undefined : buildCustomFieldMetadata(field)
const result: any = await upsertCustomFieldValue(
customFieldId,
entityType,
e.id,
field.value ?? '',
metadata,
)
if (result.success) {
const newValue = result.data
if (newValue?.id) {
field.customFieldValueId = newValue.id
field.value = newValue.value ?? field.value ?? ''
if (newValue.customField?.id) {
field.customFieldId = newValue.customField.id
field.customField = newValue.customField
}
if (Array.isArray(e.customFieldValues)) {
const index = e.customFieldValues.findIndex((v: any) => v.id === newValue.id)
if (index !== -1) {
e.customFieldValues.splice(index, 1, newValue)
} else {
e.customFieldValues.push(newValue)
}
} else {
e.customFieldValues = [newValue]
}
}
showSuccess(`Champ "${resolveFieldName(field)}" créé avec succès`)
// Update definitions list
const definitions = Array.isArray(e.customFields) ? [...e.customFields] : []
const fieldIdentifier = ensureCustomFieldId(field, candidateCustomFields.value)
const existingIndex = definitions.findIndex((definition: any) => {
const definitionId = resolveCustomFieldId(definition)
if (fieldIdentifier && definitionId) return definitionId === fieldIdentifier
return definition?.name === resolveFieldName(field)
})
const updatedDefinition = {
...(existingIndex !== -1 ? definitions[existingIndex] : {}),
customFieldValueId: field.customFieldValueId,
customFieldId: fieldIdentifier,
name: resolveFieldName(field),
type: resolveFieldType(field),
required: field.required ?? false,
options: field.options ?? [],
value: field.value ?? '',
customField: field.customField ?? null,
}
if (existingIndex !== -1) {
definitions.splice(existingIndex, 1, updatedDefinition)
} else {
definitions.push(updatedDefinition)
}
e.customFields = definitions
} else {
showError(`Erreur lors de la sauvegarde du champ "${resolveFieldName(field)}"`)
}
}
return {
displayedCustomFields,
candidateCustomFields,
updateCustomField,
}
}