+
+
@@ -343,32 +321,12 @@
>
Aucun modèle disponible pour ce type.
-
-
-
-
-
-
-
-
-
@@ -1055,9 +1013,7 @@ const createComponentSelectionEntry = () => ({
})
const createPieceSelectionEntry = () => ({
- mode: 'model',
pieceModelId: '',
- name: '',
})
const resetSkeletonRequirementSelections = () => {
@@ -1121,24 +1077,20 @@ const removePieceSelectionEntry = (requirementId, index) => {
pieceRequirementSelections[requirementId] = entries.filter((_, i) => i !== index)
}
-const setPieceSelectionMode = (requirementId, index, mode) => {
+const updatePieceSelectionEntry = (requirementId, index, patch) => {
const entries = getPieceRequirementEntries(requirementId)
pieceRequirementSelections[requirementId] = entries.map((entry, i) => {
if (i !== index) return entry
- if (mode === 'model') {
- return { ...entry, mode: 'model', pieceModelId: entry.pieceModelId || '', name: '' }
+ const updated = { ...entry, ...patch }
+ if (Object.prototype.hasOwnProperty.call(patch, 'pieceModelId')) {
+ if (patch.pieceModelId) {
+ delete updated.legacyName
+ }
}
- return { ...entry, mode: 'manual', pieceModelId: '', name: entry.name || '' }
+ return updated
})
}
-const updatePieceSelectionEntry = (requirementId, index, patch) => {
- const entries = getPieceRequirementEntries(requirementId)
- pieceRequirementSelections[requirementId] = entries.map((entry, i) =>
- i === index ? { ...entry, ...patch } : entry
- )
-}
-
const collectPiecesForSkeleton = () => {
const aggregated = []
machinePieces.value.forEach((piece) => {
@@ -1195,9 +1147,12 @@ const initializeSkeletonRequirementSelections = async () => {
const entries = existingPieces.map((piece) => {
const modelId = piece.pieceModelId || piece.pieceModel?.id || null
if (modelId) {
- return { mode: 'model', pieceModelId: modelId, name: '' }
+ return { pieceModelId: modelId }
+ }
+ return {
+ pieceModelId: '',
+ legacyName: piece.name || piece.reference || '',
}
- return { mode: 'manual', pieceModelId: '', name: piece.name || '' }
})
const min = requirement.minCount ?? (requirement.required ? 1 : 0)
while (entries.length < min) {
@@ -1287,12 +1242,7 @@ const validateSkeletonSelections = (type) => {
for (const requirement of type.pieceRequirements || []) {
const entries = getPieceRequirementEntries(requirement.id)
- const usableEntries = entries.filter((entry) => {
- if (entry.mode === 'model') {
- return !!entry.pieceModelId
- }
- return !!entry.name && entry.name.trim().length > 0
- })
+ const usableEntries = entries.filter((entry) => !!entry.pieceModelId)
const min = requirement.minCount ?? (requirement.required ? 1 : 0)
const max = requirement.maxCount ?? null
@@ -1309,26 +1259,11 @@ const validateSkeletonSelections = (type) => {
)
}
- if (!requirement.allowNewModels && usableEntries.some((entry) => entry.mode === 'manual')) {
- errors.push(
- `Le groupe "${requirement.label || requirement.typePiece?.name || 'Pièces'}" n'autorise que les modèles existants.`
- )
- }
-
usableEntries.forEach((entry) => {
- if (entry.mode === 'model') {
- pieceSelectionsPayload.push({
- requirementId: requirement.id,
- pieceModelId: entry.pieceModelId,
- })
- } else {
- pieceSelectionsPayload.push({
- requirementId: requirement.id,
- definition: {
- name: entry.name.trim(),
- },
- })
- }
+ pieceSelectionsPayload.push({
+ requirementId: requirement.id,
+ pieceModelId: entry.pieceModelId,
+ })
})
}
diff --git a/app/pages/machines/new.vue b/app/pages/machines/new.vue
index da10863..a26a7b1 100644
--- a/app/pages/machines/new.vue
+++ b/app/pages/machines/new.vue
@@ -293,29 +293,7 @@
:key="`${requirement.id}-piece-${entryIndex}`"
class="bg-base-200/60 rounded-md p-3 space-y-3"
>
-
-
-
-
-
-
+
-
-
-
manuel
@@ -903,23 +854,12 @@ const machinePreview = computed(() => {
const entriesSource = getPieceRequirementEntries(requirement.id)
const entriesList = entriesSource ? [...entriesSource] : []
const normalizedEntries = entriesList.map((entry, index) => {
- if (entry.mode === 'model') {
- const model = resolvePieceModel(requirement, entry.pieceModelId)
- return {
- key: `${requirement.id}-${index}`,
- mode: 'model',
- status: model ? 'complete' : 'pending',
- title: model ? model.name : 'Sélectionner un modèle',
- subtitle: model?.description || null,
- }
- }
- const manualName = (entry.name || '').trim()
+ const model = resolvePieceModel(requirement, entry.pieceModelId)
return {
key: `${requirement.id}-${index}`,
- mode: 'manual',
- status: manualName ? 'complete' : 'pending',
- title: manualName || 'Nom à renseigner',
- subtitle: manualName ? null : null,
+ status: model ? 'complete' : 'pending',
+ title: model ? model.name : 'Sélectionner un modèle',
+ subtitle: model?.description || null,
}
})
@@ -936,10 +876,6 @@ const machinePreview = computed(() => {
issues.push({ message: `Maximum ${max} dépassé`, kind: 'error', anchor: `piece-group-${requirement.id}` })
}
- if (!requirement.allowNewModels && normalizedEntries.some((entry) => entry.mode === 'manual' && entry.status === 'complete')) {
- issues.push({ message: "Ce groupe n'autorise que les modèles existants.", kind: 'error', anchor: `piece-group-${requirement.id}` })
- }
-
if (normalizedEntries.some((entry) => entry.status !== 'complete')) {
issues.push({ message: 'Compléter les sélections restantes.', kind: 'warning', anchor: `piece-group-${requirement.id}` })
}
@@ -1056,9 +992,7 @@ const createComponentSelectionEntry = () => ({
})
const createPieceSelectionEntry = () => ({
- mode: 'model',
pieceModelId: '',
- name: '',
})
const addComponentSelectionEntry = (requirement) => {
@@ -1109,17 +1043,6 @@ const removePieceSelectionEntry = (requirementId, index) => {
pieceRequirementSelections[requirementId] = entries.filter((_, i) => i !== index)
}
-const setPieceSelectionMode = (requirementId, index, mode) => {
- const entries = getPieceRequirementEntries(requirementId)
- pieceRequirementSelections[requirementId] = entries.map((entry, i) => {
- if (i !== index) return entry
- if (mode === 'model') {
- return { ...entry, mode: 'model', pieceModelId: entry.pieceModelId || '', name: '' }
- }
- return { ...entry, mode: 'manual', pieceModelId: '', name: entry.name || '' }
- })
-}
-
const updatePieceSelectionEntry = (requirementId, index, patch) => {
const entries = getPieceRequirementEntries(requirementId)
pieceRequirementSelections[requirementId] = entries.map((entry, i) =>
@@ -1175,12 +1098,7 @@ const validateRequirementSelections = (type) => {
for (const requirement of type.pieceRequirements || []) {
const entries = getPieceRequirementEntries(requirement.id)
- const usableEntries = entries.filter((entry) => {
- if (entry.mode === 'model') {
- return !!entry.pieceModelId
- }
- return !!entry.name && entry.name.trim().length > 0
- })
+ const usableEntries = entries.filter((entry) => !!entry.pieceModelId)
const min = requirement.minCount ?? (requirement.required ? 1 : 0)
const max = requirement.maxCount ?? null
@@ -1193,24 +1111,11 @@ const validateRequirementSelections = (type) => {
errors.push(`Le groupe "${requirement.label || requirement.typePiece?.name || 'Pièces'}" ne peut dépasser ${max} élément(s).`)
}
- if (!requirement.allowNewModels && usableEntries.some((entry) => entry.mode === 'manual')) {
- errors.push(`Le groupe "${requirement.label || requirement.typePiece?.name || 'Pièces'}" n'autorise que les modèles existants.`)
- }
-
usableEntries.forEach((entry) => {
- if (entry.mode === 'model') {
- pieceSelectionsPayload.push({
- requirementId: requirement.id,
- pieceModelId: entry.pieceModelId,
- })
- } else {
- pieceSelectionsPayload.push({
- requirementId: requirement.id,
- definition: {
- name: entry.name.trim(),
- },
- })
- }
+ pieceSelectionsPayload.push({
+ requirementId: requirement.id,
+ pieceModelId: entry.pieceModelId,
+ })
})
}
@@ -1322,10 +1227,9 @@ const submitCreatePieceModel = async () => {
if (result.success) {
await loadPieceModels(createPieceModelModal.requirement.typePieceId)
const entries = getPieceRequirementEntries(createPieceModelModal.requirement.id)
- const targetIndex = entries.findIndex((entry) => entry.mode === 'model' && !entry.pieceModelId)
+ const targetIndex = entries.findIndex((entry) => !entry.pieceModelId)
if (targetIndex !== -1) {
updatePieceSelectionEntry(createPieceModelModal.requirement.id, targetIndex, {
- mode: 'model',
pieceModelId: result.data.id,
})
} else {
@@ -1334,7 +1238,6 @@ const submitCreatePieceModel = async () => {
createPieceModelModal.requirement.id,
getPieceRequirementEntries(createPieceModelModal.requirement.id).length - 1,
{
- mode: 'model',
pieceModelId: result.data.id,
},
)
diff --git a/app/pages/model-types.vue b/app/pages/model-types.vue
new file mode 100644
index 0000000..d2a1da7
--- /dev/null
+++ b/app/pages/model-types.vue
@@ -0,0 +1,284 @@
+
+
+
+ Administration
+ Types de modèles
+
+ Gérez les types de modèles pour les composants et les pièces. Ajoutez, modifiez ou supprimez des entrées avec tri, recherche et pagination.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/models.vue b/app/pages/models.vue
deleted file mode 100644
index d39ef31..0000000
--- a/app/pages/models.vue
+++ /dev/null
@@ -1,585 +0,0 @@
-
-
-
-
-
Catalogue de modèles
-
- Administrez les modèles de composants et de pièces disponibles lors de la création des machines.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ componentModelsList.length }} modèle(s)
-
-
-
-
-
-
-
-
-
-
- Aucun modèle trouvé pour ce filtre.
-
-
-
-
-
-
- | Nom |
- Description |
- Type |
- Structure |
- Dernière modification |
- Actions |
-
-
-
-
- |
-
-
- {{ model.name }}
-
- |
- {{ model.description || '—' }} |
- {{ model.typeComposant?.name || 'Non défini' }} |
- {{ formatStructurePreview(model.structure) }} |
- {{ formatDate(model.updatedAt || model.createdAt) }} |
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ pieceModelsList.length }} modèle(s)
-
-
-
-
-
-
-
-
-
-
- Aucun modèle trouvé pour ce filtre.
-
-
-
-
-
-
- | Nom |
- Description |
- Type |
- Dernière modification |
- Actions |
-
-
-
-
- |
-
-
- {{ model.name }}
-
- |
- {{ model.description || '—' }} |
- {{ model.typePiece?.name || 'Non défini' }} |
- {{ formatDate(model.updatedAt || model.createdAt) }} |
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
- {{ componentModal.mode === 'create' ? 'Nouveau modèle de composant' : 'Modifier le modèle de composant' }}
-
-
- Définissez le modèle de composant ainsi que sa structure par défaut (sous-composants, pièces et champs personnalisés).
-
-
-
-
-
-
-
-
-
- {{ pieceModal.mode === 'create' ? 'Nouveau modèle de pièce' : 'Modifier le modèle de pièce' }}
-
-
-
-
-
-
-
-
diff --git a/app/pages/models/components.vue b/app/pages/models/components.vue
new file mode 100644
index 0000000..9a4d2ea
--- /dev/null
+++ b/app/pages/models/components.vue
@@ -0,0 +1,347 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ filteredModels.length }} modèle(s)
+
+
+
+
+
+
+
+
+
+ Aucun modèle ne correspond à ce filtre.
+
+
+
+
+
+
+ | Nom |
+ Description |
+ Type |
+ Structure |
+ Modifié |
+ Actions |
+
+
+
+
+ |
+
+
+ {{ model.name }}
+
+ |
+ {{ model.description || '—' }} |
+ {{ model.typeComposant?.name || 'Non défini' }} |
+ {{ formatStructurePreview(model.structure) }} |
+ {{ formatDate(model.updatedAt || model.createdAt) }} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/models/index.vue b/app/pages/models/index.vue
new file mode 100644
index 0000000..ed13d21
--- /dev/null
+++ b/app/pages/models/index.vue
@@ -0,0 +1,25 @@
+
+
+
+
Gestion des modèles
+
+ Administrez les modèles de composants et de pièces utilisés lors de la configuration des machines.
+
+
+
+
+ Modèles de composants
+
+
+ Modèles de pièces
+
+
+
+
+
+
diff --git a/app/pages/models/pieces.vue b/app/pages/models/pieces.vue
new file mode 100644
index 0000000..9e41dd4
--- /dev/null
+++ b/app/pages/models/pieces.vue
@@ -0,0 +1,349 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ filteredModels.length }} modèle(s)
+
+
+
+
+
+
+
+
+
+ Aucun modèle ne correspond à ce filtre.
+
+
+
+
+
+
+ | Nom |
+ Description |
+ Type |
+ Modifié |
+ Actions |
+
+
+
+
+ |
+
+
+ {{ model.name }}
+
+ |
+ {{ model.description || '—' }} |
+ {{ model.typePiece?.name || 'Non défini' }} |
+ {{ formatDate(model.updatedAt || model.createdAt) }} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/services/modelTypes.ts b/app/services/modelTypes.ts
new file mode 100644
index 0000000..b08d021
--- /dev/null
+++ b/app/services/modelTypes.ts
@@ -0,0 +1,113 @@
+import { useRequestFetch } from '#imports';
+import type { FetchOptions } from 'ofetch';
+
+export type ModelCategory = 'COMPONENT' | 'PIECE';
+
+export interface ModelTypePayload {
+ name: string;
+ code: string;
+ category: ModelCategory;
+ notes?: string | null;
+ description?: string | null;
+}
+
+export interface ModelType extends ModelTypePayload {
+ id: string;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface ModelTypeListParams {
+ q?: string;
+ category?: ModelCategory;
+ sort?: 'name' | 'code' | 'createdAt';
+ dir?: 'asc' | 'desc';
+ limit?: number;
+ offset?: number;
+}
+
+export interface ModelTypeListResponse {
+ items: ModelType[];
+ total: number;
+ offset: number;
+ limit: number;
+}
+
+const ENDPOINT = '/api/model-types';
+
+function resolveBaseUrl() {
+ const runtimeConfig = useRuntimeConfig();
+ return runtimeConfig.public.apiBaseUrl || '';
+}
+
+function createOptions
(options: FetchOptions = {}) {
+ return {
+ baseURL: resolveBaseUrl(),
+ credentials: 'include' as const,
+ ...options,
+ };
+}
+
+export function listModelTypes(params: ModelTypeListParams = {}, opts: { signal?: AbortSignal } = {}) {
+ const requestFetch = useRequestFetch();
+ const query: Record = {};
+
+ if (params.q) {
+ query.q = params.q;
+ }
+ if (params.category) {
+ query.category = params.category;
+ }
+ if (params.sort) {
+ query.sort = params.sort;
+ }
+ if (params.dir) {
+ query.dir = params.dir;
+ }
+ if (typeof params.limit === 'number') {
+ query.limit = params.limit;
+ }
+ if (typeof params.offset === 'number') {
+ query.offset = params.offset;
+ }
+
+ return requestFetch(ENDPOINT, createOptions({
+ method: 'GET',
+ query,
+ signal: opts.signal,
+ }));
+}
+
+export function createModelType(payload: ModelTypePayload, opts: { signal?: AbortSignal } = {}) {
+ const requestFetch = useRequestFetch();
+ return requestFetch(ENDPOINT, createOptions({
+ method: 'POST',
+ body: payload,
+ signal: opts.signal,
+ }));
+}
+
+export function updateModelType(id: string, payload: Partial, opts: { signal?: AbortSignal } = {}) {
+ const requestFetch = useRequestFetch();
+ return requestFetch(`${ENDPOINT}/${id}`, createOptions({
+ method: 'PATCH',
+ body: payload,
+ signal: opts.signal,
+ }));
+}
+
+export function deleteModelType(id: string, opts: { signal?: AbortSignal } = {}) {
+ const requestFetch = useRequestFetch();
+ return requestFetch(`${ENDPOINT}/${id}`, createOptions({
+ method: 'DELETE',
+ signal: opts.signal,
+ }));
+}
+
+export function getModelType(id: string, opts: { signal?: AbortSignal } = {}) {
+ const requestFetch = useRequestFetch();
+ return requestFetch(`${ENDPOINT}/${id}`, createOptions({
+ method: 'GET',
+ signal: opts.signal,
+ }));
+}