feat: improve machine component hierarchy handling
This commit is contained in:
@@ -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