refactor(custom-fields) : unify 3 parallel implementations into 1 module
Replace ~2900 lines across 9 files with ~400 lines in 2 files: - shared/utils/customFields.ts (types + pure helpers) - composables/useCustomFieldInputs.ts (reactive composable) Migrated all consumers: - Backend: add defaultValue to API Platform serialization groups - Standalone pages: component edit/create, piece edit/create, product edit/create/detail - Machine page: MachineCustomFieldsCard, MachineInfoCard, useMachineDetailCustomFields - Hierarchy: ComponentItem, PieceItem - Shared: CustomFieldDisplay, CustomFieldInputGrid - Category editor: componentStructure.ts Deleted: - entityCustomFieldLogic.ts (335 lines) - customFieldUtils.ts (440 lines) - customFieldFormUtils.ts (404 lines) - useEntityCustomFields.ts (181 lines) - customFieldFormUtils.test.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -177,7 +177,6 @@ import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||
import { useProductTypes } from '~/composables/useProductTypes'
|
||||
import { useProducts } from '~/composables/useProducts'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
import { useDocuments } from '~/composables/useDocuments'
|
||||
import { formatProductStructurePreview, normalizeProductStructureForSave } from '~/shared/modelUtils'
|
||||
import { constructeurIdsFromLinks } from '~/shared/constructeurUtils'
|
||||
@@ -186,10 +185,7 @@ import { useConstructeurLinks } from '~/composables/useConstructeurLinks'
|
||||
import { useConstructeurs } from '~/composables/useConstructeurs'
|
||||
import type { ProductModelStructure } from '~/shared/types/inventory'
|
||||
import type { ModelType } from '~/services/modelTypes'
|
||||
import {
|
||||
type CustomFieldInput,
|
||||
normalizeCustomFieldInputs as normalizeCustomFieldInputsFromUtils,
|
||||
} from '~/shared/utils/customFieldFormUtils'
|
||||
import { useCustomFieldInputs, type CustomFieldEntityType } from '~/composables/useCustomFieldInputs'
|
||||
|
||||
interface ProductCatalogType extends ModelType {
|
||||
structure: ProductModelStructure | null
|
||||
@@ -202,7 +198,6 @@ const router = useRouter()
|
||||
const { productTypes, loadProductTypes, loadingProductTypes: loadingTypes } = useProductTypes()
|
||||
const { createProduct } = useProducts()
|
||||
const toast = useToast()
|
||||
const { upsertCustomFieldValue } = useCustomFields()
|
||||
const { uploadDocuments } = useDocuments()
|
||||
const { canEdit } = usePermissions()
|
||||
const { syncLinks } = useConstructeurLinks()
|
||||
@@ -221,7 +216,14 @@ const constructeurLinks = ref<ConstructeurLinkEntry[]>([])
|
||||
const selectedDocuments = ref<File[]>([])
|
||||
const uploadingDocuments = ref(false)
|
||||
|
||||
const customFieldInputs = ref<CustomFieldInput[]>([])
|
||||
const cfDefinitions = ref<any[]>([])
|
||||
const createdEntityId = ref<string | null>(null)
|
||||
const { fields: customFieldInputs, requiredFilled: requiredCustomFieldsFilled, saveAll: saveAllCustomFields } = useCustomFieldInputs({
|
||||
definitions: cfDefinitions,
|
||||
values: [] as any[],
|
||||
entityType: 'product' as CustomFieldEntityType,
|
||||
entityId: createdEntityId,
|
||||
})
|
||||
|
||||
const productTypeList = computed<ProductCatalogType[]>(() =>
|
||||
(productTypes.value || []) as ProductCatalogType[],
|
||||
@@ -264,26 +266,17 @@ watch(selectedTypeId, (id) => {
|
||||
watch(selectedType, (type) => {
|
||||
if (!type) {
|
||||
clearForm()
|
||||
customFieldInputs.value = []
|
||||
cfDefinitions.value = []
|
||||
return
|
||||
}
|
||||
if (!creationForm.name) {
|
||||
creationForm.name = type.name
|
||||
}
|
||||
customFieldInputs.value = normalizeCustomFieldInputsFromUtils(normalizeProductStructureForSave(type.structure))
|
||||
const normalized = normalizeProductStructureForSave(type.structure)
|
||||
cfDefinitions.value = normalized?.customFields ?? []
|
||||
})
|
||||
|
||||
const requiredCustomFieldsFilled = computed(() =>
|
||||
customFieldInputs.value.every((field) => {
|
||||
if (!field.required) {
|
||||
return true
|
||||
}
|
||||
if (field.type === 'boolean') {
|
||||
return field.value === 'true' || field.value === 'false'
|
||||
}
|
||||
return field.value.trim().length > 0
|
||||
}),
|
||||
)
|
||||
// requiredCustomFieldsFilled comes from useCustomFieldInputs composable
|
||||
|
||||
const canSubmit = computed(() => Boolean(
|
||||
canEdit.value &&
|
||||
@@ -336,7 +329,8 @@ const submitCreation = async () => {
|
||||
const result = await createProduct(payload)
|
||||
if (result.success && result.data?.id) {
|
||||
const productId = result.data.id
|
||||
const failedFields = await saveCustomFieldValues(result.data.id)
|
||||
createdEntityId.value = productId
|
||||
const failedFields = await saveAllCustomFields()
|
||||
if (failedFields.length) {
|
||||
toast.showError(`Produit créé, mais impossible d'enregistrer ${failedFields.length} champ(s): ${failedFields.join(', ')}`)
|
||||
await router.replace(`/product/${result.data.id}?edit=true`)
|
||||
@@ -375,39 +369,6 @@ const submitCreation = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const saveCustomFieldValues = async (productId: string) => {
|
||||
const failed: string[] = []
|
||||
for (const field of customFieldInputs.value) {
|
||||
if (!field.name) {
|
||||
continue
|
||||
}
|
||||
const value = field.value ?? ''
|
||||
const metadata = field.customFieldId
|
||||
? undefined
|
||||
: { customFieldName: field.name, customFieldType: field.type, customFieldRequired: field.required }
|
||||
const result = await upsertCustomFieldValue(
|
||||
field.customFieldId,
|
||||
'product',
|
||||
productId,
|
||||
String(value ?? ''),
|
||||
metadata,
|
||||
)
|
||||
if (!result.success) {
|
||||
failed.push(field.name)
|
||||
} else {
|
||||
const createdValue = result.data
|
||||
if (createdValue?.id) {
|
||||
field.customFieldValueId = createdValue.id
|
||||
}
|
||||
const resolvedId = createdValue?.customField?.id || field.customFieldId
|
||||
if (resolvedId) {
|
||||
field.customFieldId = resolvedId
|
||||
}
|
||||
}
|
||||
}
|
||||
return failed
|
||||
}
|
||||
|
||||
// Sync constructeurIds → constructeurLinks when IDs are added via ConstructeurSelect
|
||||
watch(
|
||||
() => creationForm.constructeurIds,
|
||||
|
||||
Reference in New Issue
Block a user