Normalize machine link responses
This commit is contained in:
@@ -203,16 +203,18 @@
|
||||
Constructeur :
|
||||
{{ findComponentById(entry.composantId)?.constructeur?.name || findComponentById(entry.composantId)?.constructeurName || "—" }}
|
||||
</div>
|
||||
<div>
|
||||
Machine actuelle :
|
||||
{{ findComponentById(entry.composantId)?.machine?.name || findComponentById(entry.composantId)?.machineId || "Non affecté" }}
|
||||
</div>
|
||||
<div
|
||||
v-if="findComponentById(entry.composantId)?.machine?.name"
|
||||
class="text-warning mt-1"
|
||||
>
|
||||
La réaffectation détachera ce composant de sa machine actuelle lors de la création.
|
||||
</div>
|
||||
<div>
|
||||
Machines liées :
|
||||
{{ formatAssignmentList(getComponentMachineAssignments(findComponentById(entry.composantId))) || 'Aucune' }}
|
||||
</div>
|
||||
<div
|
||||
v-if="formatAssignmentList(getComponentMachineAssignments(findComponentById(entry.composantId)))"
|
||||
class="text-warning mt-1"
|
||||
>
|
||||
Ce composant est déjà lié à
|
||||
{{ formatAssignmentList(getComponentMachineAssignments(findComponentById(entry.composantId))) }}.
|
||||
La création ajoutera un nouveau lien.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -319,20 +321,20 @@
|
||||
Constructeur :
|
||||
{{ findPieceById(entry.pieceId)?.constructeur?.name || findPieceById(entry.pieceId)?.constructeurName || "—" }}
|
||||
</div>
|
||||
<div>
|
||||
Machine actuelle :
|
||||
{{ findPieceById(entry.pieceId)?.machine?.name || findPieceById(entry.pieceId)?.machineId || "Non affecté" }}
|
||||
</div>
|
||||
<div>
|
||||
Composant actuel :
|
||||
{{ findPieceById(entry.pieceId)?.composant?.name || findPieceById(entry.pieceId)?.composantId || "Non affecté" }}
|
||||
</div>
|
||||
<div
|
||||
v-if="findPieceById(entry.pieceId)?.machine?.name || findPieceById(entry.pieceId)?.composant?.name"
|
||||
class="text-warning mt-1"
|
||||
>
|
||||
Cette pièce sera détachée de son affectation actuelle pendant la création.
|
||||
</div>
|
||||
<div>
|
||||
Machines liées :
|
||||
{{ formatAssignmentList(getPieceMachineAssignments(findPieceById(entry.pieceId))) || 'Aucune' }}
|
||||
</div>
|
||||
<div>
|
||||
Composants liés :
|
||||
{{ formatAssignmentList(getPieceComponentAssignments(findPieceById(entry.pieceId))) || 'Aucun' }}
|
||||
</div>
|
||||
<div
|
||||
v-if="formatAssignmentList(getPieceMachineAssignments(findPieceById(entry.pieceId))) || formatAssignmentList(getPieceComponentAssignments(findPieceById(entry.pieceId)))"
|
||||
class="text-warning mt-1"
|
||||
>
|
||||
Cette pièce dispose déjà de liaisons existantes. La création ajoutera un nouveau lien.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -583,6 +585,7 @@ import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
|
||||
import { useComposants } from '~/composables/useComposants'
|
||||
import { usePieces } from '~/composables/usePieces'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
||||
import IconLucidePlus from '~icons/lucide/plus'
|
||||
import IconLucideX from '~icons/lucide/x'
|
||||
import IconLucideEye from '~icons/lucide/eye'
|
||||
@@ -639,6 +642,215 @@ const pieceById = computed(() => {
|
||||
const componentInventory = computed(() => composants.value || [])
|
||||
const pieceInventory = computed(() => pieces.value || [])
|
||||
|
||||
const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
|
||||
const toTrimmedString = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return String(value)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const dedupeAssignments = (assignments) => {
|
||||
const seen = new Set()
|
||||
return assignments.filter((assignment) => {
|
||||
if (!assignment) {
|
||||
return false
|
||||
}
|
||||
const id = assignment.id != null ? String(assignment.id) : ''
|
||||
const name = assignment.name != null ? String(assignment.name) : ''
|
||||
const key = `${id}::${name}`
|
||||
if (!id && !name) {
|
||||
return false
|
||||
}
|
||||
if (seen.has(key)) {
|
||||
return false
|
||||
}
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const normalizeMachineAssignment = (input) => {
|
||||
if (!input) {
|
||||
return null
|
||||
}
|
||||
if (typeof input === 'string') {
|
||||
const name = toTrimmedString(input)
|
||||
return name ? { id: name, name } : null
|
||||
}
|
||||
if (typeof input === 'number' && Number.isFinite(input)) {
|
||||
const value = String(input)
|
||||
return { id: value, name: value }
|
||||
}
|
||||
|
||||
const container = input.machine || input.machineData || input
|
||||
if (!isPlainObject(container)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const id = container.id ?? input.machineId ?? input.id ?? null
|
||||
const name =
|
||||
container.name
|
||||
|| input.machineName
|
||||
|| container.label
|
||||
|| container.title
|
||||
|| (typeof id === 'string' ? id : null)
|
||||
|| (typeof id === 'number' ? String(id) : null)
|
||||
|
||||
if (id == null && name == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: id != null ? id : null,
|
||||
name: name != null ? name : null,
|
||||
}
|
||||
}
|
||||
|
||||
const collectMachineAssignments = (source) => {
|
||||
if (!isPlainObject(source)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
source.machines,
|
||||
source.machineLinks,
|
||||
source.machineAssignments,
|
||||
source.machinesAssignments,
|
||||
source.linkedMachines,
|
||||
]
|
||||
|
||||
const assignments = []
|
||||
|
||||
candidates.forEach((list) => {
|
||||
if (Array.isArray(list)) {
|
||||
list.forEach((item) => {
|
||||
const normalized = normalizeMachineAssignment(item)
|
||||
if (normalized) {
|
||||
assignments.push(normalized)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!assignments.length) {
|
||||
const direct = normalizeMachineAssignment(source.machine)
|
||||
if (direct) {
|
||||
assignments.push(direct)
|
||||
}
|
||||
}
|
||||
|
||||
if (!assignments.length) {
|
||||
const idCandidate = source.machineId ?? source.machineID ?? null
|
||||
const nameCandidate = source.machineName ?? null
|
||||
const normalized = normalizeMachineAssignment(nameCandidate || idCandidate)
|
||||
if (normalized) {
|
||||
assignments.push(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
return dedupeAssignments(assignments)
|
||||
}
|
||||
|
||||
const normalizeComponentAssignment = (input) => {
|
||||
if (!input) {
|
||||
return null
|
||||
}
|
||||
if (typeof input === 'string') {
|
||||
const value = toTrimmedString(input)
|
||||
return value ? { id: value, name: value } : null
|
||||
}
|
||||
if (typeof input === 'number' && Number.isFinite(input)) {
|
||||
const value = String(input)
|
||||
return { id: value, name: value }
|
||||
}
|
||||
|
||||
const container = input.component || input.composant || input
|
||||
if (!isPlainObject(container)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const id = container.id ?? input.componentId ?? input.composantId ?? input.id ?? null
|
||||
const name =
|
||||
container.name
|
||||
|| input.componentName
|
||||
|| input.composantName
|
||||
|| container.label
|
||||
|| (typeof id === 'string' ? id : null)
|
||||
|| (typeof id === 'number' ? String(id) : null)
|
||||
|
||||
if (id == null && name == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: id != null ? id : null,
|
||||
name: name != null ? name : null,
|
||||
}
|
||||
}
|
||||
|
||||
const collectComponentAssignments = (source) => {
|
||||
if (!isPlainObject(source)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const candidates = [
|
||||
source.components,
|
||||
source.composants,
|
||||
source.componentLinks,
|
||||
source.linkedComponents,
|
||||
]
|
||||
|
||||
const assignments = []
|
||||
|
||||
candidates.forEach((list) => {
|
||||
if (Array.isArray(list)) {
|
||||
list.forEach((item) => {
|
||||
const normalized = normalizeComponentAssignment(item)
|
||||
if (normalized) {
|
||||
assignments.push(normalized)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!assignments.length) {
|
||||
const direct = normalizeComponentAssignment(source.component || source.composant)
|
||||
if (direct) {
|
||||
assignments.push(direct)
|
||||
}
|
||||
}
|
||||
|
||||
if (!assignments.length) {
|
||||
const idCandidate = source.componentId ?? source.composantId ?? null
|
||||
const normalized = normalizeComponentAssignment(idCandidate)
|
||||
if (normalized) {
|
||||
assignments.push(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
return dedupeAssignments(assignments)
|
||||
}
|
||||
|
||||
const getComponentMachineAssignments = component => collectMachineAssignments(component || {})
|
||||
const getPieceMachineAssignments = piece => collectMachineAssignments(piece || {})
|
||||
const getPieceComponentAssignments = piece => collectComponentAssignments(piece || {})
|
||||
|
||||
const formatAssignmentList = (assignments) => {
|
||||
if (!Array.isArray(assignments) || assignments.length === 0) {
|
||||
return ''
|
||||
}
|
||||
return assignments
|
||||
.map((assignment) => assignment?.name || assignment?.id)
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
const selectedComponentIds = computed(() => {
|
||||
const ids = []
|
||||
Object.values(componentRequirementSelections).forEach((entries) => {
|
||||
@@ -715,10 +927,9 @@ const formatComponentOption = (component) => {
|
||||
if (constructeurName) {
|
||||
parts.push(constructeurName)
|
||||
}
|
||||
if (component.machine?.name) {
|
||||
parts.push(`Machine: ${component.machine.name}`)
|
||||
} else if (component.machineId) {
|
||||
parts.push(`Machine: ${component.machineId}`)
|
||||
const machineAssignments = getComponentMachineAssignments(component)
|
||||
if (machineAssignments.length) {
|
||||
parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
|
||||
}
|
||||
return parts.join(' • ')
|
||||
}
|
||||
@@ -735,15 +946,13 @@ const formatPieceOption = (piece) => {
|
||||
if (constructeurName) {
|
||||
parts.push(constructeurName)
|
||||
}
|
||||
if (piece.machine?.name) {
|
||||
parts.push(`Machine: ${piece.machine.name}`)
|
||||
} else if (piece.machineId) {
|
||||
parts.push(`Machine: ${piece.machineId}`)
|
||||
const machineAssignments = getPieceMachineAssignments(piece)
|
||||
if (machineAssignments.length) {
|
||||
parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
|
||||
}
|
||||
if (piece.composant?.name) {
|
||||
parts.push(`Composant: ${piece.composant.name}`)
|
||||
} else if (piece.composantId) {
|
||||
parts.push(`Composant: ${piece.composantId}`)
|
||||
const componentAssignments = getPieceComponentAssignments(piece)
|
||||
if (componentAssignments.length) {
|
||||
parts.push(`Composants: ${formatAssignmentList(componentAssignments)}`)
|
||||
}
|
||||
return parts.join(' • ')
|
||||
}
|
||||
@@ -877,10 +1086,58 @@ const removePieceSelectionEntry = (requirementId, index) => {
|
||||
pieceRequirementSelections[requirementId] = entries.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
const extractParentIdentifiers = (source) => {
|
||||
if (!isPlainObject(source)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const identifiers = {}
|
||||
|
||||
const idKeys = [
|
||||
'parentRequirementId',
|
||||
'parentComponentRequirementId',
|
||||
'parentPieceRequirementId',
|
||||
'parentMachineComponentRequirementId',
|
||||
'parentMachinePieceRequirementId',
|
||||
'parentLinkId',
|
||||
'parentComponentId',
|
||||
'parentPieceId',
|
||||
]
|
||||
|
||||
idKeys.forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
||||
const value = source[key]
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
identifiers[key] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const objectKeys = [
|
||||
'parentRequirement',
|
||||
'parentComponentRequirement',
|
||||
'parentPieceRequirement',
|
||||
'parentMachineComponentRequirement',
|
||||
'parentMachinePieceRequirement',
|
||||
]
|
||||
|
||||
objectKeys.forEach((key) => {
|
||||
const value = source[key]
|
||||
if (isPlainObject(value) && value.id !== undefined && value.id !== null && value.id !== '') {
|
||||
const idKey = `${key}Id`
|
||||
if (!Object.prototype.hasOwnProperty.call(identifiers, idKey)) {
|
||||
identifiers[idKey] = value.id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return identifiers
|
||||
}
|
||||
|
||||
const validateRequirementSelections = (type) => {
|
||||
const errors = []
|
||||
const componentSelectionsPayload = []
|
||||
const pieceSelectionsPayload = []
|
||||
const componentLinksPayload = []
|
||||
const pieceLinksPayload = []
|
||||
|
||||
for (const requirement of type.componentRequirements || []) {
|
||||
const entries = getComponentRequirementEntries(requirement.id)
|
||||
@@ -907,16 +1164,6 @@ const validateRequirementSelections = (type) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (component.machineId) {
|
||||
errors.push(`Le composant "${component.name || component.id}" est déjà affecté à une machine.`)
|
||||
return
|
||||
}
|
||||
|
||||
if (component.parentComposantId) {
|
||||
errors.push(`Le composant "${component.name || component.id}" est déjà rattaché à un autre composant.`)
|
||||
return
|
||||
}
|
||||
|
||||
const requiredTypeId = requirement.typeComposantId || requirement.typeComposant?.id || null
|
||||
if (
|
||||
requiredTypeId &&
|
||||
@@ -927,10 +1174,19 @@ const validateRequirementSelections = (type) => {
|
||||
return
|
||||
}
|
||||
|
||||
componentSelectionsPayload.push({
|
||||
const payload = {
|
||||
requirementId: requirement.id,
|
||||
composantId: entry.composantId,
|
||||
})
|
||||
}
|
||||
|
||||
const overrides = sanitizeDefinitionOverrides(entry.definition)
|
||||
if (overrides) {
|
||||
payload.overrides = overrides
|
||||
}
|
||||
|
||||
Object.assign(payload, extractParentIdentifiers(requirement), extractParentIdentifiers(entry))
|
||||
|
||||
componentLinksPayload.push(payload)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -959,11 +1215,6 @@ const validateRequirementSelections = (type) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (piece.machineId || piece.composantId) {
|
||||
errors.push(`La pièce "${piece.name || piece.id}" est déjà affectée.`)
|
||||
return
|
||||
}
|
||||
|
||||
const requiredTypeId = requirement.typePieceId || requirement.typePiece?.id || null
|
||||
if (
|
||||
requiredTypeId &&
|
||||
@@ -974,10 +1225,19 @@ const validateRequirementSelections = (type) => {
|
||||
return
|
||||
}
|
||||
|
||||
pieceSelectionsPayload.push({
|
||||
const payload = {
|
||||
requirementId: requirement.id,
|
||||
pieceId: entry.pieceId,
|
||||
})
|
||||
}
|
||||
|
||||
const overrides = sanitizeDefinitionOverrides(entry.definition)
|
||||
if (overrides) {
|
||||
payload.overrides = overrides
|
||||
}
|
||||
|
||||
Object.assign(payload, extractParentIdentifiers(requirement), extractParentIdentifiers(entry))
|
||||
|
||||
pieceLinksPayload.push(payload)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -987,8 +1247,8 @@ const validateRequirementSelections = (type) => {
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
componentSelections: componentSelectionsPayload,
|
||||
pieceSelections: pieceSelectionsPayload,
|
||||
componentLinks: componentLinksPayload,
|
||||
pieceLinks: pieceLinksPayload,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1059,6 +1319,11 @@ const machinePreview = computed(() => {
|
||||
if (constructeurName) {
|
||||
subtitleParts.push(constructeurName)
|
||||
}
|
||||
const machineAssignments = selectedComponent ? getComponentMachineAssignments(selectedComponent) : []
|
||||
const assignmentLabel = formatAssignmentList(machineAssignments)
|
||||
if (assignmentLabel) {
|
||||
subtitleParts.push(`Liée à ${assignmentLabel}`)
|
||||
}
|
||||
const status = entry.composantId ? 'complete' : 'pending'
|
||||
|
||||
return {
|
||||
@@ -1066,6 +1331,8 @@ const machinePreview = computed(() => {
|
||||
status,
|
||||
title: displayName,
|
||||
subtitle: subtitleParts.join(' • ') || null,
|
||||
assignmentLabel,
|
||||
assignments: machineAssignments,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1086,9 +1353,22 @@ const machinePreview = computed(() => {
|
||||
issues.push({ message: 'Sélectionner un composant pour chaque entrée.', kind: 'error', anchor: `component-group-${requirement.id}` })
|
||||
}
|
||||
|
||||
const status = issues.some(issue => issue.kind === 'error')
|
||||
normalizedEntries.forEach((entrySummary) => {
|
||||
if (entrySummary.assignmentLabel) {
|
||||
issues.push({
|
||||
message: `Le composant "${entrySummary.title}" est déjà lié à ${entrySummary.assignmentLabel}.`,
|
||||
kind: 'warning',
|
||||
anchor: `component-group-${requirement.id}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const hasErrors = issues.some(issue => issue.kind === 'error')
|
||||
const hasWarnings = issues.some(issue => issue.kind === 'warning') || completed < entries.length
|
||||
|
||||
const status = hasErrors
|
||||
? 'error'
|
||||
: completed < entries.length
|
||||
: hasWarnings
|
||||
? 'warning'
|
||||
: 'ready'
|
||||
|
||||
@@ -1120,6 +1400,16 @@ const machinePreview = computed(() => {
|
||||
if (constructeurName) {
|
||||
subtitleParts.push(constructeurName)
|
||||
}
|
||||
const machineAssignments = selectedPiece ? getPieceMachineAssignments(selectedPiece) : []
|
||||
const machineAssignmentLabel = formatAssignmentList(machineAssignments)
|
||||
if (machineAssignmentLabel) {
|
||||
subtitleParts.push(`Machines: ${machineAssignmentLabel}`)
|
||||
}
|
||||
const componentAssignments = selectedPiece ? getPieceComponentAssignments(selectedPiece) : []
|
||||
const componentAssignmentLabel = formatAssignmentList(componentAssignments)
|
||||
if (componentAssignmentLabel) {
|
||||
subtitleParts.push(`Composants: ${componentAssignmentLabel}`)
|
||||
}
|
||||
const status = entry.pieceId ? 'complete' : 'pending'
|
||||
|
||||
return {
|
||||
@@ -1127,6 +1417,10 @@ const machinePreview = computed(() => {
|
||||
status,
|
||||
title: displayName,
|
||||
subtitle: subtitleParts.join(' • ') || null,
|
||||
machineAssignmentLabel,
|
||||
componentAssignmentLabel,
|
||||
machineAssignments,
|
||||
componentAssignments,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1147,9 +1441,29 @@ const machinePreview = computed(() => {
|
||||
issues.push({ message: 'Sélectionner une pièce pour chaque entrée.', kind: 'error', anchor: `piece-group-${requirement.id}` })
|
||||
}
|
||||
|
||||
const status = issues.some(issue => issue.kind === 'error')
|
||||
normalizedEntries.forEach((entrySummary) => {
|
||||
if (entrySummary.machineAssignmentLabel) {
|
||||
issues.push({
|
||||
message: `La pièce "${entrySummary.title}" est déjà liée aux machines ${entrySummary.machineAssignmentLabel}.`,
|
||||
kind: 'warning',
|
||||
anchor: `piece-group-${requirement.id}`,
|
||||
})
|
||||
}
|
||||
if (entrySummary.componentAssignmentLabel) {
|
||||
issues.push({
|
||||
message: `La pièce "${entrySummary.title}" est déjà rattachée aux composants ${entrySummary.componentAssignmentLabel}.`,
|
||||
kind: 'warning',
|
||||
anchor: `piece-group-${requirement.id}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const hasErrors = issues.some(issue => issue.kind === 'error')
|
||||
const hasWarnings = issues.some(issue => issue.kind === 'warning') || completed < entries.length
|
||||
|
||||
const status = hasErrors
|
||||
? 'error'
|
||||
: completed < entries.length
|
||||
: hasWarnings
|
||||
? 'warning'
|
||||
: 'ready'
|
||||
|
||||
@@ -1293,8 +1607,8 @@ const finalizeMachineCreation = async () => {
|
||||
|
||||
const hasRequirements = (type.componentRequirements?.length || 0) > 0 || (type.pieceRequirements?.length || 0) > 0
|
||||
|
||||
let componentSelections = []
|
||||
let pieceSelections = []
|
||||
let componentLinks = []
|
||||
let pieceLinks = []
|
||||
|
||||
if (hasRequirements) {
|
||||
const validationResult = validateRequirementSelections(type)
|
||||
@@ -1302,16 +1616,16 @@ const finalizeMachineCreation = async () => {
|
||||
toast.showError(validationResult.error)
|
||||
return
|
||||
}
|
||||
componentSelections = validationResult.componentSelections
|
||||
pieceSelections = validationResult.pieceSelections
|
||||
componentLinks = validationResult.componentLinks
|
||||
pieceLinks = validationResult.pieceLinks
|
||||
}
|
||||
|
||||
const payload = {
|
||||
...baseMachineData,
|
||||
...(hasRequirements
|
||||
? {
|
||||
componentSelections,
|
||||
pieceSelections
|
||||
componentLinks,
|
||||
pieceLinks
|
||||
}
|
||||
: {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user