feat(constructeur) : update product edit flow with supplier references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-31 17:02:32 +02:00
parent 80739a4528
commit e0f761da2b
4 changed files with 131 additions and 30 deletions

View File

@@ -105,6 +105,11 @@
</div>
</div>
<ConstructeurLinksTable
v-if="constructeurLinks.length"
v-model="constructeurLinks"
/>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="form-control">
<label class="label">
@@ -237,9 +242,11 @@ import { useToast } from '~/composables/useToast'
import { humanizeError } from '~/shared/utils/errorMessages'
import { useDocuments } from '~/composables/useDocuments'
import { useConstructeurs } from '~/composables/useConstructeurs'
import { useConstructeurLinks } from '~/composables/useConstructeurLinks'
import { useProductHistory } from '~/composables/useProductHistory'
import { formatProductStructurePreview, normalizeProductStructureForSave } from '~/shared/modelUtils'
import { uniqueConstructeurIds } from '~/shared/constructeurUtils'
import { constructeurIdsFromLinks } from '~/shared/constructeurUtils'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
import { getModelType } from '~/services/modelTypes'
import type { ProductModelStructure } from '~/shared/types/inventory'
import { canPreviewDocument } from '~/utils/documentPreview'
@@ -263,7 +270,8 @@ const {
deleteDocument: deleteProductDocument,
updateDocument,
} = useDocuments()
const { ensureConstructeurs } = useConstructeurs()
const { ensureConstructeurs, getConstructeurById } = useConstructeurs()
const { fetchLinks, syncLinks } = useConstructeurLinks()
const {
history,
loading: historyLoading,
@@ -286,6 +294,9 @@ const previewVisible = ref(false)
const editingDocument = ref<any | null>(null)
const editModalVisible = ref(false)
const constructeurLinks = ref<ConstructeurLinkEntry[]>([])
const originalConstructeurLinks = ref<ConstructeurLinkEntry[]>([])
const historyFieldLabels: Record<string, string> = {
name: 'Nom',
reference: 'Référence',
@@ -457,18 +468,19 @@ const hydrateForm = () => {
}
editionForm.name = product.value.name || ''
editionForm.reference = product.value.reference || ''
editionForm.constructeurIds = uniqueConstructeurIds(
product.value,
Array.isArray(product.value.constructeurs) ? product.value.constructeurs : [],
)
// Load constructeur links
fetchLinks('product', String(route.params.id)).then((links) => {
constructeurLinks.value = links
originalConstructeurLinks.value = links.map(l => ({ ...l }))
editionForm.constructeurIds = constructeurIdsFromLinks(links)
if (editionForm.constructeurIds.length) {
void ensureConstructeurs(editionForm.constructeurIds)
}
})
editionForm.supplierPrice = product.value.supplierPrice !== null && product.value.supplierPrice !== undefined
? String(product.value.supplierPrice)
: ''
refreshCustomFieldInputs(structure.value, product.value.customFieldValues)
if (editionForm.constructeurIds.length) {
// Smart-cached + deduped — fire-and-forget, ConstructeurSelect handles its own loading
ensureConstructeurs(editionForm.constructeurIds).catch(() => {})
}
}
watch(
@@ -486,12 +498,9 @@ const submitEdition = async () => {
return
}
const constructeurIds = uniqueConstructeurIds(editionForm.constructeurIds)
const payload: Record<string, any> = {
name: editionForm.name.trim(),
reference: editionForm.reference.trim() || null,
constructeurIds,
}
const rawPrice = typeof editionForm.supplierPrice === 'string'
@@ -518,6 +527,8 @@ const submitEdition = async () => {
toast.showError(`Impossible d'enregistrer ${failedFields.length} champ(s): ${failedFields.join(', ')}`)
return
}
await syncLinks('product', product.value.id, originalConstructeurLinks.value, constructeurLinks.value)
originalConstructeurLinks.value = constructeurLinks.value.map(l => ({ ...l }))
toast.showSuccess('Produit mis à jour avec succès')
versionRefreshKey.value++
}
@@ -528,6 +539,25 @@ const submitEdition = async () => {
}
}
// Sync constructeurIds → constructeurLinks when IDs are added via ConstructeurSelect
watch(
() => editionForm.constructeurIds,
(ids) => {
const currentIds = new Set(constructeurLinks.value.map(l => l.constructeurId))
for (const id of ids) {
if (!currentIds.has(id)) {
const resolved = getConstructeurById(id)
constructeurLinks.value.push({
constructeurId: id,
constructeur: resolved ? { id: resolved.id, name: resolved.name } : null,
supplierReference: null,
})
}
}
constructeurLinks.value = constructeurLinks.value.filter(l => ids.includes(l.constructeurId))
},
)
onMounted(async () => {
await loadProduct()
})

View File

@@ -133,6 +133,17 @@
</div>
</div>
<!-- Constructeur links table -->
<ConstructeurLinksTable
v-if="isEditMode && constructeurLinks.length"
v-model="constructeurLinks"
/>
<ConstructeurLinksTable
v-else-if="!isEditMode && constructeurLinks.length"
:model-value="constructeurLinks"
:readonly="true"
/>
<!-- Prix fournisseur (if value or edit mode) -->
<div v-if="isEditMode || product.supplierPrice" class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="form-control">
@@ -303,10 +314,12 @@ import { useToast } from '~/composables/useToast'
import { humanizeError } from '~/shared/utils/errorMessages'
import { useDocuments } from '~/composables/useDocuments'
import { useConstructeurs } from '~/composables/useConstructeurs'
import { useConstructeurLinks } from '~/composables/useConstructeurLinks'
import { useProductHistory } from '~/composables/useProductHistory'
import { usePermissions } from '~/composables/usePermissions'
import { formatProductStructurePreview, normalizeProductStructureForSave } from '~/shared/modelUtils'
import { uniqueConstructeurIds } from '~/shared/constructeurUtils'
import { constructeurIdsFromLinks } from '~/shared/constructeurUtils'
import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils'
import { getModelType } from '~/services/modelTypes'
import type { ProductModelStructure } from '~/shared/types/inventory'
import { canPreviewDocument } from '~/utils/documentPreview'
@@ -330,6 +343,7 @@ const {
updateDocument,
} = useDocuments()
const { ensureConstructeurs, getConstructeurById } = useConstructeurs()
const { fetchLinks, syncLinks } = useConstructeurLinks()
const {
history,
loading: historyLoading,
@@ -339,6 +353,9 @@ const {
const isEditMode = ref(false)
const constructeurLinks = ref<ConstructeurLinkEntry[]>([])
const originalConstructeurLinks = ref<ConstructeurLinkEntry[]>([])
const product = ref<any | null>(null)
const productType = ref<any | null>(null)
const structure = ref<ProductModelStructure | null>(null)
@@ -529,17 +546,19 @@ const hydrateForm = () => {
}
editionForm.name = product.value.name || ''
editionForm.reference = product.value.reference || ''
editionForm.constructeurIds = uniqueConstructeurIds(
product.value,
Array.isArray(product.value.constructeurs) ? product.value.constructeurs : [],
)
// Load constructeur links
fetchLinks('product', String(route.params.id)).then((links) => {
constructeurLinks.value = links
originalConstructeurLinks.value = links.map(l => ({ ...l }))
editionForm.constructeurIds = constructeurIdsFromLinks(links)
if (editionForm.constructeurIds.length) {
void ensureConstructeurs(editionForm.constructeurIds)
}
})
editionForm.supplierPrice = product.value.supplierPrice !== null && product.value.supplierPrice !== undefined
? String(product.value.supplierPrice)
: ''
refreshCustomFieldInputs(structure.value, product.value.customFieldValues)
if (editionForm.constructeurIds.length) {
ensureConstructeurs(editionForm.constructeurIds).catch(() => {})
}
}
watch(
@@ -557,12 +576,9 @@ const submitEdition = async () => {
return
}
const constructeurIds = uniqueConstructeurIds(editionForm.constructeurIds)
const payload: Record<string, any> = {
name: editionForm.name.trim(),
reference: editionForm.reference.trim() || null,
constructeurIds,
}
const rawPrice = typeof editionForm.supplierPrice === 'string'
@@ -589,6 +605,8 @@ const submitEdition = async () => {
toast.showError(`Impossible d'enregistrer ${failedFields.length} champ(s): ${failedFields.join(', ')}`)
return
}
await syncLinks('product', product.value.id, originalConstructeurLinks.value, constructeurLinks.value)
originalConstructeurLinks.value = constructeurLinks.value.map(l => ({ ...l }))
toast.showSuccess('Produit mis à jour avec succès')
await loadProduct()
isEditMode.value = false
@@ -600,6 +618,25 @@ const submitEdition = async () => {
}
}
// Sync constructeurIds → constructeurLinks when IDs are added via ConstructeurSelect
watch(
() => editionForm.constructeurIds,
(ids) => {
const currentIds = new Set(constructeurLinks.value.map(l => l.constructeurId))
for (const id of ids) {
if (!currentIds.has(id)) {
const resolved = getConstructeurById(id)
constructeurLinks.value.push({
constructeurId: id,
constructeur: resolved ? { id: resolved.id, name: resolved.name } : null,
supplierReference: null,
})
}
}
constructeurLinks.value = constructeurLinks.value.filter(l => ids.includes(l.constructeurId))
},
)
onMounted(async () => {
await loadProduct()
if (route.query.edit === 'true' && canEdit.value) {