fix(piece) : persist slot quantity on blur and send prix as string

- Save composant piece slot quantity via PATCH on blur
- Pass slotId through hierarchy and selection entries
- Send prix as string (not number) to match backend expectation
- Show quantity in view mode when > 1
- Allow quantity edit for all pieces (not just root-level)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-13 11:19:09 +01:00
parent 139ba183de
commit 5912216a89
7 changed files with 44 additions and 15 deletions

View File

@@ -69,7 +69,7 @@
<div v-show="!isCollapsed" class="space-y-4"> <div v-show="!isCollapsed" class="space-y-4">
<div class="p-4 bg-base-100 border border-base-200 rounded-lg"> <div class="p-4 bg-base-100 border border-base-200 rounded-lg">
<div class="space-y-2 text-sm"> <div class="space-y-2 text-sm">
<div v-if="!piece.parentComponentLinkId && isEditMode" class="form-control"> <div v-if="isEditMode" class="form-control">
<label class="label"> <label class="label">
<span class="label-text text-sm">Quantité</span> <span class="label-text text-sm">Quantité</span>
</label> </label>
@@ -82,6 +82,10 @@
@blur="updatePiece" @blur="updatePiece"
/> />
</div> </div>
<div v-else-if="displayQuantity > 1">
<span class="font-medium">Quantité:</span>
<span class="ml-2">{{ displayQuantity }}</span>
</div>
<div> <div>
<span class="font-medium">Référence:</span> <span class="font-medium">Référence:</span>
<input <input
@@ -456,7 +460,7 @@ const updatePiece = () => {
let parsedPrice = null let parsedPrice = null
if (prixValue !== null && prixValue !== undefined && String(prixValue).trim().length > 0) { if (prixValue !== null && prixValue !== undefined && String(prixValue).trim().length > 0) {
const numeric = Number(prixValue) const numeric = Number(prixValue)
if (!Number.isNaN(numeric)) parsedPrice = numeric if (!Number.isNaN(numeric)) parsedPrice = String(numeric)
} }
const product = selectedProduct.value ? { ...selectedProduct.value } : null const product = selectedProduct.value ? { ...selectedProduct.value } : null
emit('update', { emit('update', {

View File

@@ -7,6 +7,7 @@ import { useProductTypes } from '~/composables/useProductTypes'
import { usePieces } from '~/composables/usePieces' import { usePieces } from '~/composables/usePieces'
import { useProducts } from '~/composables/useProducts' import { useProducts } from '~/composables/useProducts'
import { useCustomFields } from '~/composables/useCustomFields' import { useCustomFields } from '~/composables/useCustomFields'
import type { SelectionEntry } from '~/shared/utils/structureSelectionUtils'
import { useApi } from '~/composables/useApi' import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast' import { useToast } from '~/composables/useToast'
import { extractRelationId } from '~/shared/apiRelations' import { extractRelationId } from '~/shared/apiRelations'
@@ -53,7 +54,7 @@ const historyFieldLabels: Record<string, string> = {
export function useComponentEdit(componentId: string) { export function useComponentEdit(componentId: string) {
const { canEdit } = usePermissions() const { canEdit } = usePermissions()
const router = useRouter() const router = useRouter()
const { get } = useApi() const { get, patch } = useApi()
const { componentTypes, loadComponentTypes } = useComponentTypes() const { componentTypes, loadComponentTypes } = useComponentTypes()
const { pieceTypes, loadPieceTypes } = usePieceTypes() const { pieceTypes, loadPieceTypes } = usePieceTypes()
const { productTypes, loadProductTypes } = useProductTypes() const { productTypes, loadProductTypes } = useProductTypes()
@@ -269,6 +270,21 @@ export function useComponentEdit(componentId: string) {
} }
}) })
const saveSlotQuantity = async (entry: SelectionEntry) => {
const slotId = entry.slotId
const quantity = typeof entry._definition?.quantity === 'number'
? Math.max(1, entry._definition.quantity)
: null
if (!slotId || quantity === null) return
try {
await patch(`/composant-piece-slots/${slotId}`, { quantity })
toast.showSuccess('Quantité mise à jour')
}
catch (error: any) {
toast.showError(error?.message || 'Erreur lors de la mise à jour de la quantité')
}
}
const submitEdition = async () => { const submitEdition = async () => {
if (!component.value) { if (!component.value) {
return return
@@ -299,10 +315,6 @@ export function useComponentEdit(componentId: string) {
payload.prix = null payload.prix = null
} }
if (component.value.structure) {
payload.structure = component.value.structure
}
saving.value = true saving.value = true
try { try {
const result = await updateComposant(component.value.id, payload) const result = await updateComposant(component.value.id, payload)
@@ -457,6 +469,7 @@ export function useComponentEdit(componentId: string) {
handleFilesAdded, handleFilesAdded,
refreshDocuments, refreshDocuments,
submitEdition, submitEdition,
saveSlotQuantity,
resolvePieceLabel, resolvePieceLabel,
resolveProductLabel, resolveProductLabel,
resolveSubcomponentLabel, resolveSubcomponentLabel,

View File

@@ -110,18 +110,18 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
const productId = updatedComponent.productId const productId = updatedComponent.productId
? String(updatedComponent.productId) ? String(updatedComponent.productId)
: null : null
const prix = const prixStr =
updatedComponent.prix !== null && updatedComponent.prix !== null &&
updatedComponent.prix !== undefined && updatedComponent.prix !== undefined &&
String(updatedComponent.prix).trim() !== '' String(updatedComponent.prix).trim() !== ''
? Number(updatedComponent.prix) ? String(updatedComponent.prix)
: null : null
const result: any = await updateComposantApi(updatedComponent.id as string, { const result: any = await updateComposantApi(updatedComponent.id as string, {
name: updatedComponent.name, name: updatedComponent.name,
reference: updatedComponent.reference, reference: updatedComponent.reference,
constructeurIds: cIds, constructeurIds: cIds,
prix: Number.isNaN(prix) ? null : prix, prix: prixStr,
productId, productId,
} as any) } as any)
if (result.success) { if (result.success) {
@@ -140,18 +140,18 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
updatedPiece.constructeur, updatedPiece.constructeur,
) )
const productId = updatedPiece.productId ? String(updatedPiece.productId) : null const productId = updatedPiece.productId ? String(updatedPiece.productId) : null
const prix = const prixStr =
updatedPiece.prix !== null && updatedPiece.prix !== null &&
updatedPiece.prix !== undefined && updatedPiece.prix !== undefined &&
String(updatedPiece.prix).trim() !== '' String(updatedPiece.prix).trim() !== ''
? Number(updatedPiece.prix) ? String(updatedPiece.prix)
: null : null
const result: any = await updatePieceApi(updatedPiece.id as string, { const result: any = await updatePieceApi(updatedPiece.id as string, {
name: updatedPiece.name, name: updatedPiece.name,
reference: updatedPiece.reference || null, reference: updatedPiece.reference || null,
constructeurIds: cIds, constructeurIds: cIds,
prix: Number.isNaN(prix) ? null : prix, prix: prixStr,
productId, productId,
} as any) } as any)
if (result.success) { if (result.success) {
@@ -181,6 +181,13 @@ export function useMachineDetailUpdates(deps: UseMachineDetailUpdatesDeps) {
) )
} }
} }
// 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) { } catch (error) {
console.error('Erreur lors de la mise à jour de la pièce:', error) console.error('Erreur lors de la mise à jour de la pièce:', error)
} }

View File

@@ -237,6 +237,7 @@ export const buildMachineHierarchyFromLinks = (
constructeurs: resolved?.constructeurs || [], constructeurs: resolved?.constructeurs || [],
documents: [], documents: [],
quantity, quantity,
slotId: def.slotId || definition.slotId || null,
typePieceId: resolved?.typePieceId || definition.typePieceId || def.typePieceId || null, typePieceId: resolved?.typePieceId || definition.typePieceId || def.typePieceId || null,
typePiece: resolved?.typePiece || null, typePiece: resolved?.typePiece || null,
parentComponentLinkId: machineComponentLinkId, parentComponentLinkId: machineComponentLinkId,

View File

@@ -179,6 +179,7 @@
placeholder="Qté" placeholder="Qté"
class="input input-bordered input-xs w-16 ml-auto" class="input input-bordered input-xs w-16 ml-auto"
@input="entry._definition.quantity = Math.max(1, entry._definition.quantity || 1)" @input="entry._definition.quantity = Math.max(1, entry._definition.quantity || 1)"
@blur="saveSlotQuantity(entry)"
/> />
</li> </li>
</ul> </ul>
@@ -319,6 +320,7 @@ const {
removeDocument, removeDocument,
handleFilesAdded, handleFilesAdded,
submitEdition, submitEdition,
saveSlotQuantity,
resolvePieceLabel, resolvePieceLabel,
resolveProductLabel, resolveProductLabel,
resolveSubcomponentLabel, resolveSubcomponentLabel,

View File

@@ -4,7 +4,7 @@ export interface DefinitionOverridePayload {
name?: string name?: string
reference?: string reference?: string
constructeurIds?: string[] constructeurIds?: string[]
prix?: number prix?: string
} }
export const sanitizeDefinitionOverrides = (definition: any): DefinitionOverridePayload | null => { export const sanitizeDefinitionOverrides = (definition: any): DefinitionOverridePayload | null => {
@@ -41,7 +41,7 @@ export const sanitizeDefinitionOverrides = (definition: any): DefinitionOverride
if (definition.prix !== undefined && definition.prix !== null && definition.prix !== '') { if (definition.prix !== undefined && definition.prix !== null && definition.prix !== '') {
const parsed = Number(definition.prix) const parsed = Number(definition.prix)
if (!Number.isNaN(parsed)) { if (!Number.isNaN(parsed)) {
payload.prix = parsed payload.prix = String(parsed)
} }
} }

View File

@@ -4,6 +4,7 @@ export type SelectionEntry = {
requirementLabel: string requirementLabel: string
resolvedName: string resolvedName: string
quantity?: number quantity?: number
slotId?: string
_definition?: Record<string, any> _definition?: Record<string, any>
} }
@@ -62,6 +63,7 @@ export function collectStructureSelections(
requirementLabel: resolvers.resolvePieceLabel(definition), requirementLabel: resolvers.resolvePieceLabel(definition),
resolvedName: catalogPiece?.name || selectedId, resolvedName: catalogPiece?.name || selectedId,
quantity: typeof definition?.quantity === 'number' ? definition.quantity : undefined, quantity: typeof definition?.quantity === 'number' ? definition.quantity : undefined,
slotId: isNonEmptyString(entry?.slotId) ? entry.slotId : undefined,
_definition: definition, _definition: definition,
}) })
}) })