feat: improve machine component hierarchy handling
This commit is contained in:
@@ -25,6 +25,12 @@
|
||||
{{ component.name }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2 mt-2">
|
||||
<span
|
||||
v-if="component.skeletonOnly"
|
||||
class="badge badge-warning badge-sm"
|
||||
>
|
||||
Défini dans le catalogue
|
||||
</span>
|
||||
<span v-if="component.reference" class="badge badge-outline badge-sm">{{ component.reference }}</span>
|
||||
<span v-if="component.constructeur" class="badge badge-outline badge-sm">{{ component.constructeur?.name }}</span>
|
||||
<span v-if="component.prix" class="badge badge-primary badge-sm">{{ component.prix }}€</span>
|
||||
@@ -264,8 +270,8 @@
|
||||
v-for="piece in component.pieces"
|
||||
:key="piece.id"
|
||||
:piece="piece"
|
||||
:is-edit-mode="isEditMode"
|
||||
:piece-model-options="pieceModelOptionsProvider(piece)"
|
||||
:is-edit-mode="isEditMode && !piece.skeletonOnly"
|
||||
|
||||
@update="updatePiece"
|
||||
@edit="editPiece"
|
||||
@custom-field-update="updatePieceCustomField"
|
||||
@@ -283,7 +289,7 @@
|
||||
v-for="subComponent in childComponents"
|
||||
:key="subComponent.id"
|
||||
:component="subComponent"
|
||||
:is-edit-mode="isEditMode"
|
||||
:is-edit-mode="isEditMode && !subComponent.skeletonOnly"
|
||||
:collapse-all="collapseAll"
|
||||
:toggle-token="toggleToken"
|
||||
@update="$emit('update', $event)"
|
||||
@@ -358,11 +364,41 @@ const extractStructureCustomFields = (structure) => {
|
||||
}
|
||||
|
||||
function fieldKeyFromNameAndType(name, type) {
|
||||
const normalizedName = typeof name === 'string' ? name : ''
|
||||
const normalizedName = typeof name === 'string' ? name.trim() : ''
|
||||
const normalizedType = typeof type === 'string' ? type : ''
|
||||
return normalizedName ? `${normalizedName}::${normalizedType}` : null
|
||||
}
|
||||
|
||||
function deduplicateFieldDefinitions(definitions) {
|
||||
const result = []
|
||||
const seen = new Set()
|
||||
|
||||
;(Array.isArray(definitions) ? definitions : []).forEach((field) => {
|
||||
if (!field || typeof field !== 'object') {
|
||||
return
|
||||
}
|
||||
const id =
|
||||
field.id ??
|
||||
field.customFieldId ??
|
||||
field.customField?.id ??
|
||||
null
|
||||
const nameKey = fieldKeyFromNameAndType(field.name, field.type)
|
||||
if (!id && !nameKey) {
|
||||
return
|
||||
}
|
||||
const key = id || nameKey
|
||||
if (key && seen.has(key)) {
|
||||
return
|
||||
}
|
||||
if (key) {
|
||||
seen.add(key)
|
||||
}
|
||||
result.push(field)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function mergeFieldDefinitionsWithValues(definitions, values) {
|
||||
const definitionList = Array.isArray(definitions) ? definitions : []
|
||||
const valueList = Array.isArray(values) ? values : []
|
||||
@@ -458,6 +494,62 @@ function mergeFieldDefinitionsWithValues(definitions, values) {
|
||||
return merged
|
||||
}
|
||||
|
||||
function dedupeMergedFields(fields) {
|
||||
if (!Array.isArray(fields) || fields.length <= 1) {
|
||||
return Array.isArray(fields) ? fields : []
|
||||
}
|
||||
|
||||
const seen = new Map()
|
||||
const result = []
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (!field || typeof field !== 'object') {
|
||||
return
|
||||
}
|
||||
|
||||
const rawName = resolveFieldName(field)
|
||||
const normalizedName = typeof rawName === 'string' ? rawName.trim() : ''
|
||||
if (!normalizedName) {
|
||||
return
|
||||
}
|
||||
field.name = normalizedName
|
||||
field.type = resolveFieldType(field)
|
||||
|
||||
const fieldId = ensureCustomFieldId(field)
|
||||
const nameKey = fieldKeyFromNameAndType(normalizedName, field.type)
|
||||
const key = fieldId || nameKey
|
||||
|
||||
if (!key) {
|
||||
result.push(field)
|
||||
return
|
||||
}
|
||||
|
||||
const existing = seen.get(key)
|
||||
if (!existing) {
|
||||
seen.set(key, field)
|
||||
result.push(field)
|
||||
return
|
||||
}
|
||||
|
||||
const existingHasValue =
|
||||
existing.value !== undefined &&
|
||||
existing.value !== null &&
|
||||
String(existing.value).trim().length > 0
|
||||
|
||||
const incomingHasValue =
|
||||
field.value !== undefined &&
|
||||
field.value !== null &&
|
||||
String(field.value).trim().length > 0
|
||||
|
||||
if (!existingHasValue && incomingHasValue) {
|
||||
Object.assign(existing, field)
|
||||
seen.set(key, existing)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const componentDefinitionSources = computed(() => {
|
||||
const requirement = props.component.typeMachineComponentRequirement || {}
|
||||
const type = requirement.typeComposant || props.component.typeComposant || {}
|
||||
@@ -488,13 +580,15 @@ const componentDefinitionSources = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
return definitions
|
||||
return deduplicateFieldDefinitions(definitions)
|
||||
})
|
||||
|
||||
const displayedCustomFields = computed(() =>
|
||||
mergeFieldDefinitionsWithValues(
|
||||
componentDefinitionSources.value,
|
||||
props.component.customFieldValues,
|
||||
dedupeMergedFields(
|
||||
mergeFieldDefinitionsWithValues(
|
||||
componentDefinitionSources.value,
|
||||
props.component.customFieldValues,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
312
app/components/ComponentStructureAssignmentNode.vue
Normal file
312
app/components/ComponentStructureAssignmentNode.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<section v-if="!isRoot" class="rounded-lg border border-base-200 bg-base-100 p-4 space-y-3">
|
||||
<div class="space-y-1">
|
||||
<h4 class="text-sm font-semibold text-base-content">
|
||||
{{ requirementLabel }}
|
||||
</h4>
|
||||
<p class="text-xs text-base-content/70">
|
||||
{{ requirementDescription }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text text-xs">Sélectionner un composant</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="assignment.selectedComponentId"
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="">
|
||||
{{ componentOptions.length ? 'Choisir un composant compatible' : 'Aucun composant disponible' }}
|
||||
</option>
|
||||
<option
|
||||
v-for="component in componentOptions"
|
||||
:key="component.id"
|
||||
:value="component.id"
|
||||
>
|
||||
{{ formatComponentOption(component) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="assignment.pieces.length" class="rounded-lg border border-dashed border-base-300 bg-base-200/40 p-4 space-y-4">
|
||||
<header class="space-y-1">
|
||||
<h4 class="text-sm font-semibold text-base-content">
|
||||
{{ isRoot ? 'Pièces requises par le squelette' : 'Pièces associées à ce sous-composant' }}
|
||||
</h4>
|
||||
<p class="text-xs text-base-content/70">
|
||||
Sélectionnez les pièces concrètes à associer pour chaque emplacement.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div
|
||||
v-for="pieceAssignment in assignment.pieces"
|
||||
:key="pieceAssignment.path"
|
||||
class="rounded-md border border-base-200 bg-base-100 p-3 space-y-2"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<p class="text-xs font-medium text-base-content">
|
||||
{{ describePieceRequirement(pieceAssignment.definition) }}
|
||||
</p>
|
||||
<p v-if="!getPieceOptions(pieceAssignment.definition).length" class="text-[11px] text-error">
|
||||
Aucune pièce disponible pour cette famille.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<select
|
||||
v-model="pieceAssignment.selectedPieceId"
|
||||
class="select select-bordered select-xs"
|
||||
>
|
||||
<option value="">
|
||||
{{ getPieceOptions(pieceAssignment.definition).length ? 'Choisir une pièce' : 'Sélection impossible' }}
|
||||
</option>
|
||||
<option
|
||||
v-for="piece in getPieceOptions(pieceAssignment.definition)"
|
||||
:key="piece.id"
|
||||
:value="piece.id"
|
||||
>
|
||||
{{ formatPieceOption(piece) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="assignment.subcomponents.length" class="space-y-4">
|
||||
<header class="space-y-1">
|
||||
<h4 class="text-sm font-semibold text-base-content">
|
||||
{{ isRoot ? 'Sous-composants définis par le squelette' : 'Sous-composants imbriqués' }}
|
||||
</h4>
|
||||
<p class="text-xs text-base-content/70">
|
||||
Choisissez un composant existant pour chaque sous-niveau requis.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<ComponentStructureAssignmentNode
|
||||
v-for="subAssignment in assignment.subcomponents"
|
||||
:key="subAssignment.path"
|
||||
:assignment="subAssignment"
|
||||
:pieces="pieces"
|
||||
:components="components"
|
||||
:depth="depth + 1"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch } from 'vue';
|
||||
import type {
|
||||
ComponentModelPiece,
|
||||
ComponentModelStructureNode,
|
||||
} from '~/shared/types/inventory';
|
||||
|
||||
interface ComponentOption {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
reference?: string | null;
|
||||
typeComposantId?: string | null;
|
||||
typeComposant?: {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
code?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface PieceOption {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
reference?: string | null;
|
||||
typePieceId?: string | null;
|
||||
typePiece?: {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
code?: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface StructurePieceAssignment {
|
||||
path: string;
|
||||
definition: ComponentModelPiece;
|
||||
selectedPieceId: string;
|
||||
}
|
||||
|
||||
export interface StructureAssignmentNode {
|
||||
path: string;
|
||||
definition: ComponentModelStructureNode;
|
||||
selectedComponentId: string;
|
||||
pieces: StructurePieceAssignment[];
|
||||
subcomponents: StructureAssignmentNode[];
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
assignment: StructureAssignmentNode;
|
||||
pieces: PieceOption[] | null;
|
||||
components: ComponentOption[] | null;
|
||||
depth?: number;
|
||||
}>(),
|
||||
{
|
||||
depth: 0,
|
||||
pieces: () => [],
|
||||
components: () => [],
|
||||
},
|
||||
);
|
||||
|
||||
const depth = computed(() => props.depth ?? 0);
|
||||
const isRoot = computed(() => depth.value === 0);
|
||||
|
||||
const wrapperClass = computed(() =>
|
||||
depth.value === 0 ? 'space-y-6' : 'space-y-6 border-l border-base-300 pl-4',
|
||||
);
|
||||
|
||||
const componentOptions = computed(() => {
|
||||
if (isRoot.value) {
|
||||
return [];
|
||||
}
|
||||
const definition = props.assignment.definition || {};
|
||||
const requiredTypeId =
|
||||
definition.typeComposantId || definition.modelId || null;
|
||||
const requiredFamilyCode = definition.familyCode || null;
|
||||
|
||||
return (props.components || []).filter((component) => {
|
||||
if (!component || typeof component !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (requiredTypeId) {
|
||||
return component.typeComposantId === requiredTypeId;
|
||||
}
|
||||
if (requiredFamilyCode) {
|
||||
return (
|
||||
component.typeComposant?.code === requiredFamilyCode ||
|
||||
component.typeComposantId === requiredFamilyCode
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
componentOptions,
|
||||
(options) => {
|
||||
if (isRoot.value) {
|
||||
return;
|
||||
}
|
||||
const hasMatch = options.some(
|
||||
(component) => component.id === props.assignment.selectedComponentId,
|
||||
);
|
||||
if (!hasMatch) {
|
||||
props.assignment.selectedComponentId = '';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const formatComponentOption = (component: ComponentOption) => {
|
||||
const name = component.name || 'Composant sans nom';
|
||||
const reference = component.reference ? ` • Ref. ${component.reference}` : '';
|
||||
const typeLabel =
|
||||
component.typeComposant?.name || component.typeComposant?.code || '';
|
||||
const typed =
|
||||
typeLabel && component.typeComposant?.name !== name
|
||||
? ` (${typeLabel})`
|
||||
: '';
|
||||
return `${name}${typed}${reference}`;
|
||||
};
|
||||
|
||||
const describePieceRequirement = (definition: ComponentModelPiece) => {
|
||||
const parts: string[] = [];
|
||||
if (definition.role) {
|
||||
parts.push(definition.role);
|
||||
}
|
||||
if (definition.typePieceLabel) {
|
||||
parts.push(definition.typePieceLabel);
|
||||
} else if ((definition as any).typePiece?.name) {
|
||||
parts.push((definition as any).typePiece.name);
|
||||
} else if (definition.familyCode) {
|
||||
parts.push(definition.familyCode);
|
||||
}
|
||||
return parts.length ? parts.join(' • ') : 'Pièce du squelette';
|
||||
};
|
||||
|
||||
const requirementLabel = computed(() => {
|
||||
const definition = props.assignment.definition || {};
|
||||
const alias = definition.alias || definition.typeComposantLabel;
|
||||
if (alias) {
|
||||
return alias;
|
||||
}
|
||||
if (definition.typeComposant?.name) {
|
||||
return definition.typeComposant.name;
|
||||
}
|
||||
if (definition.familyCode) {
|
||||
return `Famille ${definition.familyCode}`;
|
||||
}
|
||||
return 'Sous-composant';
|
||||
});
|
||||
|
||||
const requirementDescription = computed(() => {
|
||||
const definition = props.assignment.definition || {};
|
||||
const family =
|
||||
definition.typeComposantLabel ||
|
||||
definition.typeComposant?.name ||
|
||||
definition.familyCode;
|
||||
if (family) {
|
||||
return `Doit appartenir à la famille "${family}".`;
|
||||
}
|
||||
return 'Sélectionnez un composant enfant conforme à cette position.';
|
||||
});
|
||||
|
||||
const getPieceOptions = (definition: ComponentModelPiece) => {
|
||||
const requiredTypeId =
|
||||
definition.typePieceId ||
|
||||
(definition as any).typePiece?.id ||
|
||||
definition.familyCode ||
|
||||
null;
|
||||
|
||||
return (props.pieces || []).filter((piece) => {
|
||||
if (!piece || typeof piece !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if (!requiredTypeId) {
|
||||
return true;
|
||||
}
|
||||
if (definition.typePieceId || (definition as any).typePiece?.id) {
|
||||
return (
|
||||
piece.typePieceId === requiredTypeId ||
|
||||
piece.typePiece?.id === requiredTypeId
|
||||
);
|
||||
}
|
||||
if (definition.familyCode) {
|
||||
return (
|
||||
piece.typePiece?.code === requiredTypeId ||
|
||||
piece.typePieceId === requiredTypeId
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const formatPieceOption = (piece: PieceOption) => {
|
||||
const name = piece.name || 'Pièce';
|
||||
const reference = piece.reference ? ` • Ref. ${piece.reference}` : '';
|
||||
return `${name}${reference}`;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [props.pieces, props.assignment.pieces],
|
||||
() => {
|
||||
for (const pieceAssignment of props.assignment.pieces) {
|
||||
const options = getPieceOptions(pieceAssignment.definition);
|
||||
if (
|
||||
pieceAssignment.selectedPieceId &&
|
||||
!options.some((piece) => piece.id === pieceAssignment.selectedPieceId)
|
||||
) {
|
||||
pieceAssignment.selectedPieceId = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
</script>
|
||||
@@ -25,6 +25,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span
|
||||
v-if="piece.skeletonOnly"
|
||||
class="badge badge-warning badge-sm"
|
||||
>
|
||||
Défini dans le catalogue
|
||||
</span>
|
||||
<span
|
||||
v-if="piece.typeMachinePieceRequirement"
|
||||
class="badge badge-outline badge-sm"
|
||||
@@ -387,6 +393,33 @@ function fieldKeyFromNameAndType(name, type) {
|
||||
return normalizedName ? `${normalizedName}::${normalizedType}` : null;
|
||||
}
|
||||
|
||||
function deduplicateFieldDefinitions(definitions) {
|
||||
const result = [];
|
||||
const seen = new Set();
|
||||
|
||||
(Array.isArray(definitions) ? definitions : []).forEach((field) => {
|
||||
if (!field || typeof field !== 'object') {
|
||||
return;
|
||||
}
|
||||
const id =
|
||||
field.id ??
|
||||
field.customFieldId ??
|
||||
field.customField?.id ??
|
||||
null;
|
||||
const nameKey = fieldKeyFromNameAndType(field.name, field.type);
|
||||
const key = id || nameKey;
|
||||
if (key && seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
if (key) {
|
||||
seen.add(key);
|
||||
}
|
||||
result.push(field);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeFieldDefinitionsWithValues(definitions, values) {
|
||||
const definitionList = Array.isArray(definitions) ? definitions : [];
|
||||
const valueList = Array.isArray(values) ? values : [];
|
||||
@@ -494,6 +527,72 @@ function mergeFieldDefinitionsWithValues(definitions, values) {
|
||||
return merged;
|
||||
}
|
||||
|
||||
function dedupeMergedFields(fields) {
|
||||
if (!Array.isArray(fields) || fields.length <= 1) {
|
||||
return Array.isArray(fields) ? fields : [];
|
||||
}
|
||||
|
||||
const seen = new Map();
|
||||
const result = [];
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (!field || typeof field !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawName = resolveFieldName(field);
|
||||
const normalizedName =
|
||||
typeof rawName === 'string' ? rawName.trim() : '';
|
||||
|
||||
if (!normalizedName) {
|
||||
return;
|
||||
}
|
||||
|
||||
field.type = field.type || 'text';
|
||||
|
||||
if (typeof field.name === 'string') {
|
||||
field.name = field.name.trim();
|
||||
} else {
|
||||
field.name = normalizedName;
|
||||
}
|
||||
|
||||
const fieldId = resolveCustomFieldId(field);
|
||||
const nameKey = fieldKeyFromNameAndType(
|
||||
normalizedName,
|
||||
resolveFieldType(field),
|
||||
);
|
||||
const key = fieldId || nameKey;
|
||||
|
||||
if (!key) {
|
||||
result.push(field);
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = seen.get(key);
|
||||
if (!existing) {
|
||||
seen.set(key, field);
|
||||
result.push(field);
|
||||
return;
|
||||
}
|
||||
|
||||
const existingHasValue =
|
||||
existing.value !== undefined &&
|
||||
existing.value !== null &&
|
||||
String(existing.value).trim().length > 0;
|
||||
const incomingHasValue =
|
||||
field.value !== undefined &&
|
||||
field.value !== null &&
|
||||
String(field.value).trim().length > 0;
|
||||
|
||||
if (!existingHasValue && incomingHasValue) {
|
||||
Object.assign(existing, field);
|
||||
seen.set(key, existing);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const pieceDefinitionSources = computed(() => {
|
||||
const requirement = props.piece.typeMachinePieceRequirement || {};
|
||||
const type = requirement.typePiece || props.piece.typePiece || {};
|
||||
@@ -528,13 +627,15 @@ const pieceDefinitionSources = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
return definitions;
|
||||
return deduplicateFieldDefinitions(definitions);
|
||||
});
|
||||
|
||||
const displayedCustomFields = computed(() =>
|
||||
mergeFieldDefinitionsWithValues(
|
||||
pieceDefinitionSources.value,
|
||||
props.piece.customFieldValues,
|
||||
dedupeMergedFields(
|
||||
mergeFieldDefinitionsWithValues(
|
||||
pieceDefinitionSources.value,
|
||||
props.piece.customFieldValues,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -9,52 +9,20 @@ export function useComposants () {
|
||||
const { showSuccess, showError, showInfo } = useToast()
|
||||
const { get, post, patch, delete: del } = useApi()
|
||||
|
||||
const loadComposants = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get('/composants')
|
||||
if (result.success) {
|
||||
composants.value = result.data
|
||||
showInfo(`Chargement de ${composants.value.length} composant(s) réussi`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des composants:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getComposantsByMachine = async (machineId) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get(`/composants/machine/${machineId}`)
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data }
|
||||
}
|
||||
return { success: false, error: result.error }
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des composants:', error)
|
||||
return { success: false, error: error.message }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getComposantHierarchy = async (machineId) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get(`/composants/hierarchy/${machineId}`)
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data }
|
||||
}
|
||||
return { success: false, error: result.error }
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement de la hiérarchie:', error)
|
||||
return { success: false, error: error.message }
|
||||
} finally {
|
||||
loading.value = false
|
||||
const loadComposants = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get('/composants')
|
||||
if (result.success) {
|
||||
composants.value = result.data
|
||||
showInfo(`Chargement de ${composants.value.length} composant(s) réussi`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des composants:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const createComposant = async (composantData) => {
|
||||
loading.value = true
|
||||
@@ -116,10 +84,6 @@ export function useComposants () {
|
||||
}
|
||||
}
|
||||
|
||||
const getComposantById = (id) => {
|
||||
return composants.value.find(comp => comp.id === id)
|
||||
}
|
||||
|
||||
const getComposants = () => composants.value
|
||||
const isLoading = () => loading.value
|
||||
|
||||
@@ -127,12 +91,9 @@ export function useComposants () {
|
||||
composants,
|
||||
loading,
|
||||
loadComposants,
|
||||
getComposantsByMachine,
|
||||
getComposantHierarchy,
|
||||
createComposant,
|
||||
updateComposant: updateComposantData,
|
||||
deleteComposant,
|
||||
getComposantById,
|
||||
getComposants,
|
||||
isLoading
|
||||
}
|
||||
|
||||
@@ -24,38 +24,6 @@ export function usePieces () {
|
||||
}
|
||||
}
|
||||
|
||||
const getPiecesByMachine = async (machineId) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get(`/pieces/machine/${machineId}`)
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data }
|
||||
}
|
||||
return { success: false, error: result.error }
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des pièces:', error)
|
||||
return { success: false, error: error.message }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getPiecesByComposant = async (composantId) => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await get(`/pieces/composant/${composantId}`)
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data }
|
||||
}
|
||||
return { success: false, error: result.error }
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des pièces:', error)
|
||||
return { success: false, error: error.message }
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const createPiece = async (pieceData) => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -116,10 +84,6 @@ export function usePieces () {
|
||||
}
|
||||
}
|
||||
|
||||
const getPieceById = (id) => {
|
||||
return pieces.value.find(piece => piece.id === id)
|
||||
}
|
||||
|
||||
const getPieces = () => pieces.value
|
||||
const isLoading = () => loading.value
|
||||
|
||||
@@ -127,12 +91,9 @@ export function usePieces () {
|
||||
pieces,
|
||||
loading,
|
||||
loadPieces,
|
||||
getPiecesByMachine,
|
||||
getPiecesByComposant,
|
||||
createPiece,
|
||||
updatePiece: updatePieceData,
|
||||
deletePiece,
|
||||
getPieceById,
|
||||
getPieces,
|
||||
isLoading
|
||||
}
|
||||
|
||||
@@ -156,6 +156,45 @@
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="structureHasRequirements"
|
||||
class="space-y-4 rounded-lg border border-primary/30 bg-primary/5 p-4"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="font-semibold text-base-content">
|
||||
Sélection des éléments du squelette
|
||||
</h2>
|
||||
<p class="text-xs text-base-content/70">
|
||||
Affectez les pièces et sous-composants concrets correspondant à la catégorie choisie.
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
class="badge"
|
||||
:class="structureSelectionsComplete ? 'badge-success' : structureDataLoading ? 'badge-info' : 'badge-warning'"
|
||||
>
|
||||
{{ structureSelectionsComplete ? 'Complet' : structureDataLoading ? 'Chargement…' : 'Incomplet' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="structureDataLoading"
|
||||
class="flex items-center gap-3 rounded-md border border-base-200 bg-base-100 p-3 text-sm text-base-content/70"
|
||||
>
|
||||
<span class="loading loading-spinner loading-sm" aria-hidden="true"></span>
|
||||
Chargement du catalogue de pièces et de composants…
|
||||
</div>
|
||||
<ComponentStructureAssignmentNode
|
||||
v-else-if="structureAssignments"
|
||||
:assignment="structureAssignments"
|
||||
:pieces="availablePieces"
|
||||
:components="availableComponents"
|
||||
/>
|
||||
<p v-else class="text-xs text-error">
|
||||
Impossible de générer les emplacements définis par le squelette.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="customFieldInputs.length" class="space-y-4 rounded-lg border border-base-200 bg-base-200/40 p-4">
|
||||
<header class="space-y-1">
|
||||
<h2 class="font-semibold text-base-content">Champs personnalisés</h2>
|
||||
@@ -255,12 +294,20 @@
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from '#imports'
|
||||
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
|
||||
import ComponentStructureAssignmentNode, {
|
||||
type StructureAssignmentNode,
|
||||
} from '~/components/ComponentStructureAssignmentNode.vue'
|
||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||
import { useComposants } from '~/composables/useComposants'
|
||||
import { usePieces } from '~/composables/usePieces'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
import { formatStructurePreview } from '~/shared/modelUtils'
|
||||
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
||||
import type {
|
||||
ComponentModelPiece,
|
||||
ComponentModelStructure,
|
||||
ComponentModelStructureNode,
|
||||
} from '~/shared/types/inventory'
|
||||
import type { ModelType } from '~/services/modelTypes'
|
||||
|
||||
interface ComponentCatalogType extends ModelType {
|
||||
@@ -272,7 +319,17 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const { componentTypes, loadComponentTypes, loadingComponentTypes } = useComponentTypes()
|
||||
const { createComposant } = useComposants()
|
||||
const {
|
||||
createComposant,
|
||||
composants: componentCatalogRef,
|
||||
loadComposants,
|
||||
loading: componentsLoading,
|
||||
} = useComposants()
|
||||
const {
|
||||
pieces: pieceCatalogRef,
|
||||
loadPieces,
|
||||
loading: piecesLoading,
|
||||
} = usePieces()
|
||||
const toast = useToast()
|
||||
const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
|
||||
|
||||
@@ -287,6 +344,13 @@ const creationForm = reactive({
|
||||
})
|
||||
const lastSuggestedName = ref('')
|
||||
const customFieldInputs = ref<CustomFieldInput[]>([])
|
||||
const structureAssignments = ref<StructureAssignmentNode | null>(null)
|
||||
|
||||
const availablePieces = computed(() => pieceCatalogRef.value ?? [])
|
||||
const availableComponents = computed(() => componentCatalogRef.value ?? [])
|
||||
const structureDataLoading = computed(
|
||||
() => piecesLoading.value || componentsLoading.value,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => route.query.typeId,
|
||||
@@ -328,6 +392,7 @@ watch(selectedType, (type) => {
|
||||
if (!type) {
|
||||
clearCreationForm()
|
||||
customFieldInputs.value = []
|
||||
structureAssignments.value = null
|
||||
return
|
||||
}
|
||||
if (!creationForm.name || creationForm.name === lastSuggestedName.value) {
|
||||
@@ -335,8 +400,195 @@ watch(selectedType, (type) => {
|
||||
}
|
||||
lastSuggestedName.value = creationForm.name
|
||||
customFieldInputs.value = normalizeCustomFieldInputs(type.structure)
|
||||
structureAssignments.value = initializeStructureAssignments(type.structure)
|
||||
})
|
||||
|
||||
const extractSubcomponents = (
|
||||
definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined,
|
||||
): ComponentModelStructureNode[] => {
|
||||
if (!definition || typeof definition !== 'object') {
|
||||
return []
|
||||
}
|
||||
const raw = Array.isArray((definition as any).subcomponents)
|
||||
? (definition as any).subcomponents
|
||||
: Array.isArray((definition as any).subComponents)
|
||||
? (definition as any).subComponents
|
||||
: []
|
||||
return raw.filter(
|
||||
(item: unknown): item is ComponentModelStructureNode =>
|
||||
!!item && typeof item === 'object',
|
||||
)
|
||||
}
|
||||
|
||||
const extractPiecesFromNode = (
|
||||
definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined,
|
||||
): ComponentModelPiece[] => {
|
||||
if (!definition || typeof definition !== 'object') {
|
||||
return []
|
||||
}
|
||||
const raw = Array.isArray((definition as any).pieces)
|
||||
? (definition as any).pieces
|
||||
: []
|
||||
return raw.filter(
|
||||
(item: unknown): item is ComponentModelPiece =>
|
||||
!!item && typeof item === 'object',
|
||||
)
|
||||
}
|
||||
|
||||
const buildAssignmentNode = (
|
||||
definition: ComponentModelStructureNode | ComponentModelStructure,
|
||||
path: string,
|
||||
): StructureAssignmentNode => {
|
||||
const pieces = extractPiecesFromNode(definition).map((piece, index) => ({
|
||||
path: `${path}:piece-${index}`,
|
||||
definition: piece,
|
||||
selectedPieceId: '',
|
||||
}))
|
||||
|
||||
const subcomponents = extractSubcomponents(definition).map(
|
||||
(child, index) => buildAssignmentNode(child, `${path}:sub-${index}`),
|
||||
)
|
||||
|
||||
return {
|
||||
path,
|
||||
definition,
|
||||
selectedComponentId: '',
|
||||
pieces,
|
||||
subcomponents,
|
||||
}
|
||||
}
|
||||
|
||||
const initializeStructureAssignments = (
|
||||
structure: ComponentModelStructure | null,
|
||||
): StructureAssignmentNode | null => {
|
||||
if (!structure || typeof structure !== 'object') {
|
||||
return null
|
||||
}
|
||||
return buildAssignmentNode(structure, 'root')
|
||||
}
|
||||
|
||||
const hasAssignments = (node: StructureAssignmentNode | null): boolean => {
|
||||
if (!node) {
|
||||
return false
|
||||
}
|
||||
if (node.pieces.length > 0 || node.subcomponents.length > 0) {
|
||||
return true
|
||||
}
|
||||
return node.subcomponents.some((child) => hasAssignments(child))
|
||||
}
|
||||
|
||||
const structureHasRequirements = computed(() =>
|
||||
hasAssignments(structureAssignments.value),
|
||||
)
|
||||
|
||||
const isAssignmentNodeComplete = (
|
||||
node: StructureAssignmentNode,
|
||||
isRootNode = false,
|
||||
): boolean => {
|
||||
const piecesComplete = node.pieces.every(
|
||||
(piece) => !!piece.selectedPieceId && piece.selectedPieceId.length > 0,
|
||||
)
|
||||
const subcomponentsComplete = node.subcomponents.every(
|
||||
(child) =>
|
||||
!!child.selectedComponentId &&
|
||||
child.selectedComponentId.length > 0 &&
|
||||
isAssignmentNodeComplete(child, false),
|
||||
)
|
||||
return piecesComplete && subcomponentsComplete && (isRootNode || !!node.selectedComponentId)
|
||||
}
|
||||
|
||||
const structureSelectionsComplete = computed(() => {
|
||||
if (!structureHasRequirements.value) {
|
||||
return true
|
||||
}
|
||||
if (structureDataLoading.value) {
|
||||
return false
|
||||
}
|
||||
if (!structureAssignments.value) {
|
||||
return false
|
||||
}
|
||||
return isAssignmentNodeComplete(structureAssignments.value, true)
|
||||
})
|
||||
|
||||
const stripNullish = (input: Record<string, any>) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(input).filter(
|
||||
([, value]) => value !== null && value !== undefined && value !== '',
|
||||
),
|
||||
)
|
||||
|
||||
const sanitizeStructureDefinition = (
|
||||
definition: ComponentModelStructureNode,
|
||||
) =>
|
||||
stripNullish({
|
||||
alias: definition.alias ?? null,
|
||||
typeComposantId: definition.typeComposantId ?? null,
|
||||
typeComposantLabel: definition.typeComposantLabel ?? null,
|
||||
modelId: definition.modelId ?? null,
|
||||
familyCode: (definition as any).familyCode ?? null,
|
||||
})
|
||||
|
||||
const sanitizePieceDefinition = (definition: ComponentModelPiece) =>
|
||||
stripNullish({
|
||||
role: (definition as any).role ?? null,
|
||||
typePieceId: definition.typePieceId ?? null,
|
||||
typePieceLabel: definition.typePieceLabel ?? null,
|
||||
reference: definition.reference ?? null,
|
||||
})
|
||||
|
||||
const serializeStructureAssignments = (
|
||||
root: StructureAssignmentNode | null,
|
||||
) => {
|
||||
if (!root) {
|
||||
return null
|
||||
}
|
||||
|
||||
const serializeNode = (
|
||||
assignment: StructureAssignmentNode,
|
||||
isRootNode = false,
|
||||
): Record<string, any> => {
|
||||
const serializedPieces = assignment.pieces
|
||||
.filter((piece) => !!piece.selectedPieceId)
|
||||
.map((piece) =>
|
||||
stripNullish({
|
||||
path: piece.path,
|
||||
definition: sanitizePieceDefinition(piece.definition),
|
||||
selectedPieceId: piece.selectedPieceId,
|
||||
}),
|
||||
)
|
||||
|
||||
const serializedSubcomponents = assignment.subcomponents
|
||||
.map((child) => serializeNode(child, false))
|
||||
.filter((child) => Object.keys(child).length > 0)
|
||||
|
||||
const base: Record<string, any> = {
|
||||
path: assignment.path,
|
||||
definition: sanitizeStructureDefinition(assignment.definition),
|
||||
}
|
||||
|
||||
if (!isRootNode) {
|
||||
base.selectedComponentId = assignment.selectedComponentId
|
||||
}
|
||||
if (serializedPieces.length) {
|
||||
base.pieces = serializedPieces
|
||||
}
|
||||
if (serializedSubcomponents.length) {
|
||||
base.subcomponents = serializedSubcomponents
|
||||
}
|
||||
|
||||
return stripNullish(base)
|
||||
}
|
||||
|
||||
const serializedRoot = serializeNode(root, true)
|
||||
if (
|
||||
(!serializedRoot.pieces || serializedRoot.pieces.length === 0) &&
|
||||
(!serializedRoot.subcomponents || serializedRoot.subcomponents.length === 0)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return serializedRoot
|
||||
}
|
||||
|
||||
const requiredCustomFieldsFilled = computed(() =>
|
||||
customFieldInputs.value.every((field) => {
|
||||
if (!field.required) {
|
||||
@@ -353,6 +605,7 @@ const canSubmit = computed(() => Boolean(
|
||||
selectedType.value &&
|
||||
creationForm.name &&
|
||||
requiredCustomFieldsFilled.value &&
|
||||
structureSelectionsComplete.value &&
|
||||
!submitting.value,
|
||||
))
|
||||
|
||||
@@ -421,6 +674,7 @@ const clearCreationForm = () => {
|
||||
creationForm.constructeurId = null
|
||||
creationForm.prix = ''
|
||||
lastSuggestedName.value = ''
|
||||
structureAssignments.value = null
|
||||
}
|
||||
|
||||
const submitCreation = async () => {
|
||||
@@ -455,6 +709,19 @@ const submitCreation = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (structureHasRequirements.value && !structureSelectionsComplete.value) {
|
||||
toast.showError('Complétez la sélection des pièces et sous-composants.')
|
||||
return
|
||||
}
|
||||
|
||||
const serializedStructure = structureHasRequirements.value
|
||||
? serializeStructureAssignments(structureAssignments.value)
|
||||
: null
|
||||
|
||||
if (serializedStructure) {
|
||||
payload.structure = serializedStructure
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const result = await createComposant(payload)
|
||||
@@ -473,7 +740,11 @@ const submitCreation = async () => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadComponentTypes()
|
||||
await Promise.allSettled([
|
||||
loadComponentTypes(),
|
||||
loadPieces(),
|
||||
loadComposants(),
|
||||
])
|
||||
})
|
||||
|
||||
interface CustomFieldInput {
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
<div class="my-8">
|
||||
<!-- Header with Add Button -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800">
|
||||
Squelettes de machine
|
||||
</h2>
|
||||
<h2 class="text-2xl font-bold text-gray-800">Squelettes de machine</h2>
|
||||
<NuxtLink to="/machine-skeleton/new" class="btn btn-primary">
|
||||
<IconLucidePlus
|
||||
class="w-5 h-5 mr-2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<IconLucidePlus class="w-5 h-5 mr-2" aria-hidden="true" />
|
||||
Créer un type
|
||||
</NuxtLink>
|
||||
</div>
|
||||
@@ -51,23 +46,29 @@
|
||||
<div class="space-y-2 text-sm text-gray-500">
|
||||
<div class="flex items-center gap-2">
|
||||
<IconLucidePackage class="w-4 h-4" aria-hidden="true" />
|
||||
<span>{{ type.componentRequirements?.length || 0 }} famille(s) de composants</span>
|
||||
<span
|
||||
>{{ type.componentRequirements?.length || 0 }} famille(s) de
|
||||
composants</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconLucideLayoutGrid class="w-4 h-4" aria-hidden="true" />
|
||||
<span>{{ type.pieceRequirements?.length || 0 }} groupe(s) de pièces</span>
|
||||
<span
|
||||
>{{ type.pieceRequirements?.length || 0 }} groupe(s) de
|
||||
pièces</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button class="btn btn-sm btn-error" @click.stop="confirmDeleteType(type)">
|
||||
<button
|
||||
class="btn btn-sm btn-error"
|
||||
@click.stop="confirmDeleteType(type)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
<NuxtLink :to="`/type/${type.id}`" class="btn btn-sm btn-outline">
|
||||
Voir détails
|
||||
</NuxtLink>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Utiliser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,52 +93,59 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import IconLucidePlus from '~icons/lucide/plus'
|
||||
import IconLucidePackage from '~icons/lucide/package'
|
||||
import IconLucideLayoutGrid from '~icons/lucide/layout-grid'
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useMachineTypesApi } from "~/composables/useMachineTypesApi";
|
||||
import { useToast } from "~/composables/useToast";
|
||||
import IconLucidePlus from "~icons/lucide/plus";
|
||||
import IconLucidePackage from "~icons/lucide/package";
|
||||
import IconLucideLayoutGrid from "~icons/lucide/layout-grid";
|
||||
|
||||
const { machineTypes, loading, loadMachineTypes, deleteMachineType } = useMachineTypesApi()
|
||||
const { machineTypes, loading, loadMachineTypes, deleteMachineType } =
|
||||
useMachineTypesApi();
|
||||
|
||||
const categories = ref([
|
||||
'Toutes',
|
||||
'Production',
|
||||
'Transformation',
|
||||
'Manutention',
|
||||
'Traitement',
|
||||
'Contrôle'
|
||||
])
|
||||
"Toutes",
|
||||
"Production",
|
||||
"Transformation",
|
||||
"Manutention",
|
||||
"Traitement",
|
||||
"Contrôle",
|
||||
]);
|
||||
|
||||
const selectedCategory = ref('Toutes')
|
||||
const selectedCategory = ref("Toutes");
|
||||
|
||||
const filteredTypes = computed(() => {
|
||||
if (selectedCategory.value === 'Toutes') {
|
||||
return machineTypes.value
|
||||
if (selectedCategory.value === "Toutes") {
|
||||
return machineTypes.value;
|
||||
}
|
||||
return machineTypes.value.filter(type => type.category === selectedCategory.value)
|
||||
})
|
||||
return machineTypes.value.filter(
|
||||
(type) => type.category === selectedCategory.value
|
||||
);
|
||||
});
|
||||
|
||||
const confirmDeleteType = async (type) => {
|
||||
const { showError, showSuccess } = useToast()
|
||||
const { showError, showSuccess } = useToast();
|
||||
|
||||
if (confirm(`Êtes-vous sûr de vouloir supprimer le type "${type.name}" ? Cette action est irréversible.`)) {
|
||||
if (
|
||||
confirm(
|
||||
`Êtes-vous sûr de vouloir supprimer le type "${type.name}" ? Cette action est irréversible.`
|
||||
)
|
||||
) {
|
||||
try {
|
||||
const result = await deleteMachineType(type.id)
|
||||
const result = await deleteMachineType(type.id);
|
||||
if (result.success) {
|
||||
showSuccess(`Type "${type.name}" supprimé avec succès`)
|
||||
showSuccess(`Type "${type.name}" supprimé avec succès`);
|
||||
} else {
|
||||
showError(`Erreur lors de la suppression: ${result.error}`)
|
||||
showError(`Erreur lors de la suppression: ${result.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(`Erreur lors de la suppression: ${error.message}`)
|
||||
showError(`Erreur lors de la suppression: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Load machine types on mount
|
||||
onMounted(async () => {
|
||||
await loadMachineTypes()
|
||||
})
|
||||
await loadMachineTypes();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2108,27 +2108,83 @@ const mergeCustomFieldValuesWithDefinitions = (valueEntries = [], ...definitionS
|
||||
return result
|
||||
}
|
||||
|
||||
const dedupeCustomFieldEntries = (fields) => {
|
||||
if (!Array.isArray(fields) || fields.length <= 1) {
|
||||
return Array.isArray(fields) ? fields : []
|
||||
}
|
||||
|
||||
const seen = new Set()
|
||||
const result = []
|
||||
|
||||
for (const field of fields) {
|
||||
if (!field) {
|
||||
continue
|
||||
}
|
||||
|
||||
field.type = field.type || 'text'
|
||||
|
||||
let normalizedName =
|
||||
typeof field.name === 'string' ? field.name.trim() : ''
|
||||
|
||||
if (!normalizedName && field.customField?.name) {
|
||||
normalizedName = String(field.customField.name).trim()
|
||||
field.name = normalizedName
|
||||
} else if (typeof field.name === 'string') {
|
||||
field.name = normalizedName
|
||||
}
|
||||
|
||||
const key =
|
||||
field.customFieldId ||
|
||||
field.id ||
|
||||
(normalizedName ? `${normalizedName}::${field.type || 'text'}` : null)
|
||||
|
||||
if (!key && !normalizedName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key && seen.has(key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!normalizedName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key) {
|
||||
seen.add(key)
|
||||
}
|
||||
if (normalizedName) {
|
||||
seen.add(`${normalizedName}::${field.type || 'text'}`)
|
||||
}
|
||||
result.push(field)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const transformCustomFields = (pieces) => {
|
||||
return (pieces || []).map((piece) => {
|
||||
const requirement = piece.typeMachinePieceRequirement || {}
|
||||
const typePiece = requirement.typePiece || piece.typePiece || {}
|
||||
|
||||
const customFields = mergeCustomFieldValuesWithDefinitions(
|
||||
piece.customFieldValues,
|
||||
piece.customFields,
|
||||
piece.definition?.customFields,
|
||||
piece.typePiece?.customFields,
|
||||
typePiece.customFields,
|
||||
requirement.typePiece?.customFields,
|
||||
requirement.customFields,
|
||||
requirement.definition?.customFields,
|
||||
getStructureCustomFields(piece.definition?.structure),
|
||||
getStructureCustomFields(piece.typePiece?.structure),
|
||||
getStructureCustomFields(typePiece.structure),
|
||||
getStructureCustomFields(typePiece.pieceSkeleton),
|
||||
getStructureCustomFields(piece.typePiece?.pieceSkeleton),
|
||||
getStructureCustomFields(requirement.structure),
|
||||
getStructureCustomFields(requirement.pieceSkeleton),
|
||||
const customFields = dedupeCustomFieldEntries(
|
||||
mergeCustomFieldValuesWithDefinitions(
|
||||
piece.customFieldValues,
|
||||
piece.customFields,
|
||||
piece.definition?.customFields,
|
||||
piece.typePiece?.customFields,
|
||||
typePiece.customFields,
|
||||
requirement.typePiece?.customFields,
|
||||
requirement.customFields,
|
||||
requirement.definition?.customFields,
|
||||
getStructureCustomFields(piece.definition?.structure),
|
||||
getStructureCustomFields(piece.typePiece?.structure),
|
||||
getStructureCustomFields(typePiece.structure),
|
||||
getStructureCustomFields(typePiece.pieceSkeleton),
|
||||
getStructureCustomFields(piece.typePiece?.pieceSkeleton),
|
||||
getStructureCustomFields(requirement.structure),
|
||||
getStructureCustomFields(requirement.pieceSkeleton),
|
||||
),
|
||||
)
|
||||
|
||||
return {
|
||||
@@ -2151,21 +2207,23 @@ const transformComponentCustomFields = (componentsData) => {
|
||||
const requirement = component.typeMachineComponentRequirement || {}
|
||||
const type = requirement.typeComposant || component.typeComposant || {}
|
||||
|
||||
const customFields = mergeCustomFieldValuesWithDefinitions(
|
||||
component.customFieldValues,
|
||||
component.customFields,
|
||||
component.definition?.customFields,
|
||||
component.typeComposant?.customFields,
|
||||
type.customFields,
|
||||
requirement.typeComposant?.customFields,
|
||||
requirement.customFields,
|
||||
requirement.definition?.customFields,
|
||||
getStructureCustomFields(component.definition?.structure),
|
||||
getStructureCustomFields(component.typeComposant?.structure),
|
||||
getStructureCustomFields(type.structure),
|
||||
getStructureCustomFields(type.componentSkeleton),
|
||||
getStructureCustomFields(requirement.structure),
|
||||
getStructureCustomFields(requirement.componentSkeleton),
|
||||
const customFields = dedupeCustomFieldEntries(
|
||||
mergeCustomFieldValuesWithDefinitions(
|
||||
component.customFieldValues,
|
||||
component.customFields,
|
||||
component.definition?.customFields,
|
||||
component.typeComposant?.customFields,
|
||||
type.customFields,
|
||||
requirement.typeComposant?.customFields,
|
||||
requirement.customFields,
|
||||
requirement.definition?.customFields,
|
||||
getStructureCustomFields(component.definition?.structure),
|
||||
getStructureCustomFields(component.typeComposant?.structure),
|
||||
getStructureCustomFields(type.structure),
|
||||
getStructureCustomFields(type.componentSkeleton),
|
||||
getStructureCustomFields(requirement.structure),
|
||||
getStructureCustomFields(requirement.componentSkeleton),
|
||||
),
|
||||
)
|
||||
|
||||
const pieces = component.pieces
|
||||
@@ -2198,14 +2256,14 @@ const transformComponentCustomFields = (componentsData) => {
|
||||
const syncMachineCustomFields = () => {
|
||||
if (!machine.value) {
|
||||
machineCustomFields.value = []
|
||||
return
|
||||
return
|
||||
}
|
||||
|
||||
const merged = mergeCustomFieldValuesWithDefinitions(
|
||||
const merged = dedupeCustomFieldEntries(mergeCustomFieldValuesWithDefinitions(
|
||||
machine.value.customFieldValues,
|
||||
machine.value.customFields,
|
||||
machine.value.typeMachine?.customFields,
|
||||
).map((field) => ({
|
||||
)).map((field) => ({
|
||||
...field,
|
||||
readOnly: false,
|
||||
}))
|
||||
@@ -2276,210 +2334,271 @@ function mergeComponentTrees(existing = [], updates = []) {
|
||||
}
|
||||
|
||||
const buildMachineHierarchyFromLinks = (componentLinks = [], pieceLinks = []) => {
|
||||
const componentMap = new Map()
|
||||
const componentRoots = []
|
||||
const normalizeComponentLinkId = (link) =>
|
||||
resolveIdentifier(link?.id, link?.linkId, link?.machineComponentLinkId)
|
||||
|
||||
componentLinks.forEach((link, index) => {
|
||||
if (!isPlainObject(link)) {
|
||||
return
|
||||
const normalizePieceLinkId = (link) =>
|
||||
resolveIdentifier(link?.id, link?.linkId, link?.machinePieceLinkId)
|
||||
|
||||
const createPieceNode = (link, parentComponentName = null) => {
|
||||
if (!link || typeof link !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const baseComponent = isPlainObject(link.composant)
|
||||
? link.composant
|
||||
: isPlainObject(link.component)
|
||||
? link.component
|
||||
: isPlainObject(link.targetComponent)
|
||||
? link.targetComponent
|
||||
: {}
|
||||
const appliedPiece =
|
||||
(link.piece && typeof link.piece === 'object' && link.piece) || {}
|
||||
const originalPiece =
|
||||
(link.originalPiece && typeof link.originalPiece === 'object' && link.originalPiece) || null
|
||||
|
||||
const linkId = resolveIdentifier(link.id, link.linkId, link.machineComponentLinkId)
|
||||
const requirement =
|
||||
link.typeMachinePieceRequirement ||
|
||||
appliedPiece.typeMachinePieceRequirement ||
|
||||
originalPiece?.typeMachinePieceRequirement ||
|
||||
null
|
||||
|
||||
const node = {
|
||||
...baseComponent,
|
||||
machineComponentLink: link,
|
||||
machineComponentLinkId: linkId,
|
||||
linkId,
|
||||
componentLinkId: linkId,
|
||||
composantId: resolveIdentifier(
|
||||
baseComponent.composantId,
|
||||
baseComponent.componentId,
|
||||
link.composantId,
|
||||
link.componentId,
|
||||
baseComponent.id,
|
||||
),
|
||||
const machinePieceLinkId = normalizePieceLinkId(link)
|
||||
const pieceId = resolveIdentifier(appliedPiece.id, appliedPiece.pieceId, link.pieceId)
|
||||
|
||||
const basePiece = {
|
||||
...appliedPiece,
|
||||
id: appliedPiece.id || pieceId || machinePieceLinkId || `piece-${machinePieceLinkId}`,
|
||||
pieceId,
|
||||
name:
|
||||
link.overrides?.name ||
|
||||
appliedPiece.name ||
|
||||
appliedPiece.definition?.name ||
|
||||
appliedPiece.definition?.role ||
|
||||
originalPiece?.name ||
|
||||
'Pièce',
|
||||
reference:
|
||||
link.overrides?.reference ||
|
||||
appliedPiece.reference ||
|
||||
appliedPiece.definition?.reference ||
|
||||
originalPiece?.reference ||
|
||||
null,
|
||||
prix:
|
||||
link.overrides?.prix ??
|
||||
appliedPiece.prix ??
|
||||
originalPiece?.prix ??
|
||||
null,
|
||||
constructeur:
|
||||
appliedPiece.constructeur ||
|
||||
originalPiece?.constructeur ||
|
||||
null,
|
||||
constructeurId:
|
||||
appliedPiece.constructeurId ||
|
||||
appliedPiece.constructeur?.id ||
|
||||
originalPiece?.constructeurId ||
|
||||
null,
|
||||
documents:
|
||||
Array.isArray(appliedPiece.documents)
|
||||
? appliedPiece.documents
|
||||
: Array.isArray(originalPiece?.documents)
|
||||
? originalPiece.documents
|
||||
: [],
|
||||
typePiece: appliedPiece.typePiece || requirement?.typePiece || null,
|
||||
typePieceId:
|
||||
appliedPiece.typePieceId ||
|
||||
appliedPiece.typePiece?.id ||
|
||||
requirement?.typePieceId ||
|
||||
requirement?.typePiece?.id ||
|
||||
null,
|
||||
typeMachinePieceRequirement: requirement,
|
||||
typeMachinePieceRequirementId: requirement?.id || null,
|
||||
requirementId: requirement?.id || null,
|
||||
overrides: link.overrides || null,
|
||||
originalPiece,
|
||||
machinePieceLink: link,
|
||||
machinePieceLinkId,
|
||||
linkId: machinePieceLinkId,
|
||||
parentComponentLinkId: resolveIdentifier(
|
||||
link.parentComponentLinkId,
|
||||
link.parentLinkId,
|
||||
link.parentMachineComponentLinkId,
|
||||
baseComponent.parentComponentLinkId,
|
||||
baseComponent.parentLinkId,
|
||||
appliedPiece.parentComponentLinkId,
|
||||
),
|
||||
parentComposantId: resolveIdentifier(
|
||||
baseComponent.parentComposantId,
|
||||
parentComponentId: resolveIdentifier(
|
||||
appliedPiece.parentComponentId,
|
||||
link.parentComponentId,
|
||||
),
|
||||
parentRequirementId: resolveIdentifier(
|
||||
baseComponent.parentRequirementId,
|
||||
link.parentRequirementId,
|
||||
),
|
||||
parentMachineComponentRequirementId: resolveIdentifier(
|
||||
baseComponent.parentMachineComponentRequirementId,
|
||||
link.parentMachineComponentRequirementId,
|
||||
),
|
||||
parentMachinePieceRequirementId: resolveIdentifier(
|
||||
baseComponent.parentMachinePieceRequirementId,
|
||||
link.parentMachinePieceRequirementId,
|
||||
),
|
||||
typeMachineComponentRequirement:
|
||||
link.requirement
|
||||
|| link.typeMachineComponentRequirement
|
||||
|| baseComponent.typeMachineComponentRequirement
|
||||
|| null,
|
||||
typeMachineComponentRequirementId: resolveIdentifier(
|
||||
link.requirementId,
|
||||
link.typeMachineComponentRequirementId,
|
||||
(link.requirement || link.typeMachineComponentRequirement)?.id,
|
||||
baseComponent.typeMachineComponentRequirementId,
|
||||
),
|
||||
definition: baseComponent.definition || {},
|
||||
pieces: [],
|
||||
subComponents: [],
|
||||
sousComposants: [],
|
||||
}
|
||||
|
||||
if (!node.id) {
|
||||
node.id = resolveIdentifier(
|
||||
baseComponent.id,
|
||||
node.composantId,
|
||||
link.composantId,
|
||||
link.componentId,
|
||||
`component-${index}`,
|
||||
)
|
||||
}
|
||||
|
||||
node.requirementId = node.typeMachineComponentRequirementId
|
||||
node.machineComponentLinkOverrides = link.overrides || null
|
||||
node.overrides = link.overrides || null
|
||||
node.definitionOverrides = link.overrides || null
|
||||
node.subcomponents = node.subComponents
|
||||
|
||||
componentMap.set(node.machineComponentLinkId || node.id, node)
|
||||
})
|
||||
|
||||
componentMap.forEach((node) => {
|
||||
const parentLinkId = resolveIdentifier(node.parentComponentLinkId)
|
||||
if (parentLinkId && componentMap.has(parentLinkId)) {
|
||||
const parent = componentMap.get(parentLinkId)
|
||||
parent.subComponents.push(node)
|
||||
parent.sousComposants = parent.subComponents
|
||||
parent.subcomponents = parent.subComponents
|
||||
node.parentComposantId = resolveIdentifier(
|
||||
node.parentComposantId,
|
||||
parent.composantId,
|
||||
parent.id,
|
||||
)
|
||||
} else {
|
||||
componentRoots.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
const machinePieces = []
|
||||
|
||||
pieceLinks.forEach((link, index) => {
|
||||
if (!isPlainObject(link)) {
|
||||
return
|
||||
}
|
||||
|
||||
const basePiece = isPlainObject(link.piece)
|
||||
? link.piece
|
||||
: isPlainObject(link.targetPiece)
|
||||
? link.targetPiece
|
||||
: isPlainObject(link.pieceModel)
|
||||
? link.pieceModel
|
||||
: {}
|
||||
|
||||
const linkId = resolveIdentifier(link.id, link.linkId, link.machinePieceLinkId)
|
||||
const parentComponentLinkId = resolveIdentifier(
|
||||
link.parentComponentLinkId,
|
||||
link.parentLinkId,
|
||||
link.parentMachineComponentLinkId,
|
||||
basePiece.parentComponentLinkId,
|
||||
)
|
||||
|
||||
const pieceEntry = {
|
||||
...basePiece,
|
||||
id: resolveIdentifier(basePiece.id, link.pieceId, linkId, `piece-${index}`),
|
||||
pieceId: resolveIdentifier(basePiece.id, link.pieceId),
|
||||
machinePieceLink: link,
|
||||
machinePieceLinkId: linkId,
|
||||
linkId,
|
||||
parentComponentLinkId,
|
||||
parentComponentName,
|
||||
parentLinkId: resolveIdentifier(
|
||||
link.parentLinkId,
|
||||
link.parentMachinePieceLinkId,
|
||||
basePiece.parentLinkId,
|
||||
appliedPiece.parentLinkId,
|
||||
),
|
||||
parentPieceLinkId: resolveIdentifier(
|
||||
link.parentPieceLinkId,
|
||||
basePiece.parentPieceLinkId,
|
||||
appliedPiece.parentPieceLinkId,
|
||||
),
|
||||
parentPieceId: resolveIdentifier(
|
||||
basePiece.parentPieceId,
|
||||
appliedPiece.parentPieceId,
|
||||
link.parentPieceId,
|
||||
),
|
||||
parentComponentId: resolveIdentifier(
|
||||
basePiece.parentComponentId,
|
||||
parentMachineComponentRequirementId: resolveIdentifier(
|
||||
appliedPiece.parentMachineComponentRequirementId,
|
||||
link.parentMachineComponentRequirementId,
|
||||
),
|
||||
parentMachinePieceRequirementId: resolveIdentifier(
|
||||
appliedPiece.parentMachinePieceRequirementId,
|
||||
link.parentMachinePieceRequirementId,
|
||||
),
|
||||
definition: appliedPiece.definition || originalPiece?.definition || {},
|
||||
customFields: appliedPiece.customFields || [],
|
||||
skeletonOnly: !pieceId,
|
||||
}
|
||||
|
||||
return basePiece
|
||||
}
|
||||
|
||||
const createComponentNode = (link) => {
|
||||
if (!link || typeof link !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
const appliedComponent =
|
||||
(link.composant && typeof link.composant === 'object' && link.composant) || {}
|
||||
const originalComponent =
|
||||
(link.originalComposant && typeof link.originalComposant === 'object' && link.originalComposant) || null
|
||||
|
||||
const requirement =
|
||||
link.typeMachineComponentRequirement ||
|
||||
appliedComponent.typeMachineComponentRequirement ||
|
||||
originalComponent?.typeMachineComponentRequirement ||
|
||||
null
|
||||
|
||||
const machineComponentLinkId = normalizeComponentLinkId(link)
|
||||
const composantId = resolveIdentifier(
|
||||
appliedComponent.id,
|
||||
appliedComponent.composantId,
|
||||
link.composantId,
|
||||
)
|
||||
|
||||
const componentName =
|
||||
link.overrides?.name ||
|
||||
appliedComponent.name ||
|
||||
appliedComponent.definition?.alias ||
|
||||
appliedComponent.definition?.name ||
|
||||
originalComponent?.name ||
|
||||
'Composant'
|
||||
|
||||
const pieces = Array.isArray(link.pieceLinks)
|
||||
? link.pieceLinks.map((pieceLink) => createPieceNode(pieceLink, componentName)).filter(Boolean)
|
||||
: []
|
||||
|
||||
const subComponents = Array.isArray(link.childLinks)
|
||||
? link.childLinks.map(createComponentNode).filter(Boolean)
|
||||
: []
|
||||
|
||||
const baseComponent = {
|
||||
...appliedComponent,
|
||||
id: appliedComponent.id || composantId || machineComponentLinkId || `component-${machineComponentLinkId}`,
|
||||
composantId,
|
||||
name: componentName,
|
||||
reference:
|
||||
link.overrides?.reference ||
|
||||
appliedComponent.reference ||
|
||||
appliedComponent.definition?.reference ||
|
||||
originalComponent?.reference ||
|
||||
null,
|
||||
prix:
|
||||
link.overrides?.prix ??
|
||||
appliedComponent.prix ??
|
||||
originalComponent?.prix ??
|
||||
null,
|
||||
constructeur:
|
||||
appliedComponent.constructeur ||
|
||||
originalComponent?.constructeur ||
|
||||
null,
|
||||
constructeurId:
|
||||
appliedComponent.constructeurId ||
|
||||
appliedComponent.constructeur?.id ||
|
||||
originalComponent?.constructeurId ||
|
||||
null,
|
||||
documents:
|
||||
Array.isArray(appliedComponent.documents)
|
||||
? appliedComponent.documents
|
||||
: Array.isArray(originalComponent?.documents)
|
||||
? originalComponent.documents
|
||||
: [],
|
||||
typeComposant:
|
||||
appliedComponent.typeComposant ||
|
||||
requirement?.typeComposant ||
|
||||
null,
|
||||
typeComposantId:
|
||||
appliedComponent.typeComposantId ||
|
||||
appliedComponent.typeComposant?.id ||
|
||||
requirement?.typeComposantId ||
|
||||
requirement?.typeComposant?.id ||
|
||||
null,
|
||||
typeMachineComponentRequirement: requirement,
|
||||
typeMachineComponentRequirementId: requirement?.id || null,
|
||||
requirementId: requirement?.id || null,
|
||||
overrides: link.overrides || null,
|
||||
machineComponentLinkOverrides: link.overrides || null,
|
||||
definitionOverrides: link.overrides || null,
|
||||
originalComposant: originalComponent,
|
||||
machineComponentLink: link,
|
||||
machineComponentLinkId,
|
||||
componentLinkId: machineComponentLinkId,
|
||||
parentComponentLinkId: resolveIdentifier(
|
||||
link.parentComponentLinkId,
|
||||
link.parentLinkId,
|
||||
link.parentMachineComponentLinkId,
|
||||
appliedComponent.parentComponentLinkId,
|
||||
),
|
||||
parentComposantId: resolveIdentifier(
|
||||
appliedComponent.parentComposantId,
|
||||
link.parentComponentId,
|
||||
),
|
||||
composantId: resolveIdentifier(
|
||||
basePiece.composantId,
|
||||
basePiece.componentId,
|
||||
link.composantId,
|
||||
link.componentId,
|
||||
parentRequirementId: resolveIdentifier(
|
||||
appliedComponent.parentRequirementId,
|
||||
link.parentRequirementId,
|
||||
),
|
||||
typeMachinePieceRequirement:
|
||||
link.requirement
|
||||
|| link.typeMachinePieceRequirement
|
||||
|| basePiece.typeMachinePieceRequirement
|
||||
|| null,
|
||||
parentMachineComponentRequirementId: resolveIdentifier(
|
||||
appliedComponent.parentMachineComponentRequirementId,
|
||||
link.parentMachineComponentRequirementId,
|
||||
),
|
||||
parentMachinePieceRequirementId: resolveIdentifier(
|
||||
appliedComponent.parentMachinePieceRequirementId,
|
||||
link.parentMachinePieceRequirementId,
|
||||
),
|
||||
definition: appliedComponent.definition || originalComponent?.definition || {},
|
||||
customFields: appliedComponent.customFields || [],
|
||||
pieces,
|
||||
subComponents,
|
||||
subcomponents: subComponents,
|
||||
sousComposants: subComponents,
|
||||
skeletonOnly: !composantId,
|
||||
}
|
||||
|
||||
pieceEntry.typeMachinePieceRequirementId = resolveIdentifier(
|
||||
link.requirementId,
|
||||
link.typeMachinePieceRequirementId,
|
||||
pieceEntry.typeMachinePieceRequirement?.id,
|
||||
basePiece.typeMachinePieceRequirementId,
|
||||
)
|
||||
pieceEntry.parentMachineComponentRequirementId = resolveIdentifier(
|
||||
basePiece.parentMachineComponentRequirementId,
|
||||
link.parentMachineComponentRequirementId,
|
||||
)
|
||||
pieceEntry.parentMachinePieceRequirementId = resolveIdentifier(
|
||||
basePiece.parentMachinePieceRequirementId,
|
||||
link.parentMachinePieceRequirementId,
|
||||
)
|
||||
pieceEntry.definition = basePiece.definition || {}
|
||||
pieceEntry.overrides = link.overrides || null
|
||||
if (!pieceEntry.name && link.overrides?.name) {
|
||||
pieceEntry.name = link.overrides.name
|
||||
}
|
||||
return baseComponent
|
||||
}
|
||||
|
||||
if (parentComponentLinkId && componentMap.has(parentComponentLinkId)) {
|
||||
const parent = componentMap.get(parentComponentLinkId)
|
||||
parent.pieces.push(pieceEntry)
|
||||
pieceEntry.parentComponentName = parent.name || parent.nom || null
|
||||
} else {
|
||||
machinePieces.push(pieceEntry)
|
||||
}
|
||||
})
|
||||
const rootComponents = (Array.isArray(componentLinks) ? componentLinks : [])
|
||||
.filter((link) =>
|
||||
!resolveIdentifier(
|
||||
link?.parentComponentLinkId,
|
||||
link?.parentLinkId,
|
||||
link?.parentMachineComponentLinkId,
|
||||
),
|
||||
)
|
||||
.map(createComponentNode)
|
||||
.filter(Boolean)
|
||||
|
||||
componentMap.forEach((node) => {
|
||||
node.sousComposants = node.subComponents
|
||||
node.subcomponents = node.subComponents
|
||||
})
|
||||
const machinePieces = (Array.isArray(pieceLinks) ? pieceLinks : [])
|
||||
.filter((link) =>
|
||||
!resolveIdentifier(
|
||||
link?.parentComponentLinkId,
|
||||
link?.parentLinkId,
|
||||
link?.parentMachineComponentLinkId,
|
||||
),
|
||||
)
|
||||
.map((link) => createPieceNode(link, null))
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
components: componentRoots,
|
||||
components: rootComponents,
|
||||
machinePieces,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,18 +203,7 @@
|
||||
Constructeur :
|
||||
{{ findComponentById(entry.composantId)?.constructeur?.name || findComponentById(entry.composantId)?.constructeurName || "—" }}
|
||||
</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>
|
||||
@@ -321,20 +310,7 @@
|
||||
Constructeur :
|
||||
{{ findPieceById(entry.pieceId)?.constructeur?.name || findPieceById(entry.pieceId)?.constructeurName || "—" }}
|
||||
</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>
|
||||
@@ -1353,18 +1329,8 @@ const machinePreview = computed(() => {
|
||||
issues.push({ message: 'Sélectionner un composant pour chaque entrée.', kind: 'error', anchor: `component-group-${requirement.id}` })
|
||||
}
|
||||
|
||||
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 hasWarnings = completed < entries.length
|
||||
|
||||
const status = hasErrors
|
||||
? 'error'
|
||||
@@ -1441,25 +1407,8 @@ const machinePreview = computed(() => {
|
||||
issues.push({ message: 'Sélectionner une pièce pour chaque entrée.', kind: 'error', anchor: `piece-group-${requirement.id}` })
|
||||
}
|
||||
|
||||
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 hasWarnings = completed < entries.length
|
||||
|
||||
const status = hasErrors
|
||||
? 'error'
|
||||
|
||||
Reference in New Issue
Block a user