Migrate away from legacy component and piece models
This commit is contained in:
@@ -113,7 +113,7 @@ Lors de la création d'une machine à partir d'un type, il est possible de fourn
|
|||||||
"componentSelections": [
|
"componentSelections": [
|
||||||
{
|
{
|
||||||
"requirementId": "<id d'une TypeMachineComponentRequirement>",
|
"requirementId": "<id d'une TypeMachineComponentRequirement>",
|
||||||
"componentModelId": "<optionnel : modèle existant>",
|
"typeComposantId": "<optionnel : forcer un type spécifique>",
|
||||||
"definition": {
|
"definition": {
|
||||||
"name": "Bloc moteur série X",
|
"name": "Bloc moteur série X",
|
||||||
"reference": "COMP-001",
|
"reference": "COMP-001",
|
||||||
@@ -132,7 +132,7 @@ Lors de la création d'une machine à partir d'un type, il est possible de fourn
|
|||||||
"pieceSelections": [
|
"pieceSelections": [
|
||||||
{
|
{
|
||||||
"requirementId": "<id d'une TypeMachinePieceRequirement>",
|
"requirementId": "<id d'une TypeMachinePieceRequirement>",
|
||||||
"pieceModelId": "<optionnel : modèle existant>",
|
"typePieceId": "<optionnel : forcer un type spécifique>",
|
||||||
"definition": {
|
"definition": {
|
||||||
"name": "Kit maintenance niveau 1",
|
"name": "Kit maintenance niveau 1",
|
||||||
"reference": "KIT-001",
|
"reference": "KIT-001",
|
||||||
@@ -153,7 +153,7 @@ Principales règles de validation :
|
|||||||
|
|
||||||
- `requirementId` doit correspondre à une exigence déclarée dans le type de machine (composant ou pièce).
|
- `requirementId` doit correspondre à une exigence déclarée dans le type de machine (composant ou pièce).
|
||||||
- Le nombre de sélections pour une exigence doit respecter `minCount` et `maxCount` (si défini). Les exigences marquées `required` imposent au moins une sélection.
|
- Le nombre de sélections pour une exigence doit respecter `minCount` et `maxCount` (si défini). Les exigences marquées `required` imposent au moins une sélection.
|
||||||
- Si `allowNewModels` vaut `false`, il est obligatoire de fournir un `componentModelId`/`pieceModelId` existant. Sinon un `definition` sans modèle peut être utilisé pour créer un nouvel élément.
|
- Si `allowNewModels` vaut `false`, la sélection doit réutiliser un composant ou une pièce existante et respecter strictement le type imposé par le requirement. Les squelettes définis sur les types sont instanciés automatiquement lors de la création.
|
||||||
- Les modèles sélectionnés doivent appartenir au type attendu (`typeComposantId` ou `typePieceId`) sous peine d'échec de la création.
|
- Les modèles sélectionnés doivent appartenir au type attendu (`typeComposantId` ou `typePieceId`) sous peine d'échec de la création.
|
||||||
- Les champs personnalisés du `definition.customFields` permettent de surcharger la valeur par défaut définie au niveau du type; la valeur est automatiquement injectée dans les `customFieldValues` de la machine, du composant ou de la pièce créée.
|
- Les champs personnalisés du `definition.customFields` permettent de surcharger la valeur par défaut définie au niveau du type; la valeur est automatiquement injectée dans les `customFieldValues` de la machine, du composant ou de la pièce créée.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ModelType"
|
||||||
|
ADD COLUMN "componentSkeleton" JSONB,
|
||||||
|
ADD COLUMN "pieceSkeleton" JSONB;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- Migrate legacy component and piece models into ModelType skeletons, then drop obsolete tables
|
||||||
|
|
||||||
|
-- Transfer component model structures into ModelType.componentSkeleton when missing
|
||||||
|
UPDATE "ModelType" mt
|
||||||
|
SET "componentSkeleton" = cm."structure"
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT ON ("typeComposantId")
|
||||||
|
"typeComposantId",
|
||||||
|
"structure"
|
||||||
|
FROM "composant_models"
|
||||||
|
WHERE "structure" IS NOT NULL
|
||||||
|
ORDER BY "typeComposantId", "updatedAt" DESC, "createdAt" DESC
|
||||||
|
) cm
|
||||||
|
WHERE mt."id" = cm."typeComposantId"
|
||||||
|
AND mt."componentSkeleton" IS NULL;
|
||||||
|
|
||||||
|
-- Transfer piece model structures into ModelType.pieceSkeleton when missing
|
||||||
|
UPDATE "ModelType" mt
|
||||||
|
SET "pieceSkeleton" = pm."structure"
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT ON ("typePieceId")
|
||||||
|
"typePieceId",
|
||||||
|
"structure"
|
||||||
|
FROM "piece_models"
|
||||||
|
WHERE "structure" IS NOT NULL
|
||||||
|
ORDER BY "typePieceId", "updatedAt" DESC, "createdAt" DESC
|
||||||
|
) pm
|
||||||
|
WHERE mt."id" = pm."typePieceId"
|
||||||
|
AND mt."pieceSkeleton" IS NULL;
|
||||||
|
|
||||||
|
-- Drop foreign keys before removing the legacy columns
|
||||||
|
ALTER TABLE "composants" DROP CONSTRAINT IF EXISTS "composants_composantModelId_fkey";
|
||||||
|
ALTER TABLE "pieces" DROP CONSTRAINT IF EXISTS "pieces_pieceModelId_fkey";
|
||||||
|
|
||||||
|
-- Remove columns referencing the legacy model tables
|
||||||
|
ALTER TABLE "composants" DROP COLUMN IF EXISTS "composantModelId";
|
||||||
|
ALTER TABLE "pieces" DROP COLUMN IF EXISTS "pieceModelId";
|
||||||
|
|
||||||
|
-- Drop obsolete model tables
|
||||||
|
DROP TABLE IF EXISTS "composant_models";
|
||||||
|
DROP TABLE IF EXISTS "piece_models";
|
||||||
@@ -96,9 +96,6 @@ model Composant {
|
|||||||
typeComposantId String?
|
typeComposantId String?
|
||||||
typeComposant ModelType? @relation("ModelTypeComponentAssignments", fields: [typeComposantId], references: [id])
|
typeComposant ModelType? @relation("ModelTypeComponentAssignments", fields: [typeComposantId], references: [id])
|
||||||
|
|
||||||
composantModelId String?
|
|
||||||
composantModel ComposantModel? @relation(fields: [composantModelId], references: [id], onDelete: SetNull)
|
|
||||||
|
|
||||||
typeMachineComponentRequirementId String?
|
typeMachineComponentRequirementId String?
|
||||||
typeMachineComponentRequirement TypeMachineComponentRequirement? @relation(fields: [typeMachineComponentRequirementId], references: [id], onDelete: SetNull)
|
typeMachineComponentRequirement TypeMachineComponentRequirement? @relation(fields: [typeMachineComponentRequirementId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
@@ -130,9 +127,6 @@ model Piece {
|
|||||||
typePieceId String?
|
typePieceId String?
|
||||||
typePiece ModelType? @relation("ModelTypePieceAssignments", fields: [typePieceId], references: [id])
|
typePiece ModelType? @relation("ModelTypePieceAssignments", fields: [typePieceId], references: [id])
|
||||||
|
|
||||||
pieceModelId String?
|
|
||||||
pieceModel PieceModel? @relation(fields: [pieceModelId], references: [id], onDelete: SetNull)
|
|
||||||
|
|
||||||
typeMachinePieceRequirementId String?
|
typeMachinePieceRequirementId String?
|
||||||
typeMachinePieceRequirement TypeMachinePieceRequirement? @relation(fields: [typeMachinePieceRequirementId], references: [id], onDelete: SetNull)
|
typeMachinePieceRequirement TypeMachinePieceRequirement? @relation(fields: [typeMachinePieceRequirementId], references: [id], onDelete: SetNull)
|
||||||
|
|
||||||
@@ -157,16 +151,16 @@ model ModelType {
|
|||||||
category ModelCategory
|
category ModelCategory
|
||||||
notes String? @db.Text
|
notes String? @db.Text
|
||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
|
componentSkeleton Json?
|
||||||
|
pieceSkeleton Json?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@index([category, name])
|
@@index([category, name])
|
||||||
|
|
||||||
composants Composant[] @relation("ModelTypeComponentAssignments")
|
composants Composant[] @relation("ModelTypeComponentAssignments")
|
||||||
models ComposantModel[] @relation("ModelTypeComponentModels")
|
|
||||||
componentRequirements TypeMachineComponentRequirement[] @relation("ModelTypeComponentRequirements")
|
componentRequirements TypeMachineComponentRequirement[] @relation("ModelTypeComponentRequirements")
|
||||||
customFields CustomField[] @relation("ModelTypeCustomFields")
|
customFields CustomField[] @relation("ModelTypeCustomFields")
|
||||||
pieceModels PieceModel[] @relation("ModelTypePieceModels")
|
|
||||||
pieceRequirements TypeMachinePieceRequirement[] @relation("ModelTypePieceRequirements")
|
pieceRequirements TypeMachinePieceRequirement[] @relation("ModelTypePieceRequirements")
|
||||||
pieces Piece[] @relation("ModelTypePieceAssignments")
|
pieces Piece[] @relation("ModelTypePieceAssignments")
|
||||||
pieceCustomFields CustomField[] @relation("ModelTypePieceCustomFields")
|
pieceCustomFields CustomField[] @relation("ModelTypePieceCustomFields")
|
||||||
@@ -272,37 +266,6 @@ model CustomFieldValue {
|
|||||||
@@map("custom_field_values")
|
@@map("custom_field_values")
|
||||||
}
|
}
|
||||||
|
|
||||||
model ComposantModel {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String
|
|
||||||
description String?
|
|
||||||
structure Json? // Définition du composant (sous-composants, pièces, champs personnalisés)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
typeComposantId String
|
|
||||||
typeComposant ModelType @relation("ModelTypeComponentModels", fields: [typeComposantId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
composants Composant[]
|
|
||||||
|
|
||||||
@@map("composant_models")
|
|
||||||
}
|
|
||||||
|
|
||||||
model PieceModel {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String
|
|
||||||
description String?
|
|
||||||
structure Json? // Définition de la pièce (champs personnalisés par défaut, etc.)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
|
|
||||||
typePieceId String
|
|
||||||
typePiece ModelType @relation("ModelTypePieceModels", fields: [typePieceId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
pieces Piece[]
|
|
||||||
|
|
||||||
@@map("piece_models")
|
|
||||||
}
|
|
||||||
|
|
||||||
model TypeMachineComponentRequirement {
|
model TypeMachineComponentRequirement {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { PrismaClient, Prisma, ModelCategory } from '@prisma/client';
|
import { PrismaClient, Prisma, ModelCategory } from '@prisma/client';
|
||||||
import { normalizeComponentModelStructure } from '../src/component-models/structure.normalizer';
|
import { normalizeComponentModelStructure } from '../src/component-models/structure.normalizer';
|
||||||
|
import {
|
||||||
|
ComponentModelStructureSchema,
|
||||||
|
PieceModelStructureSchema,
|
||||||
|
} from '../src/shared/schemas/inventory';
|
||||||
import type { ComponentModelStructure } from '../src/shared/types/inventory';
|
import type { ComponentModelStructure } from '../src/shared/types/inventory';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
@@ -4066,8 +4070,6 @@ async function clearDatabaseExceptSitesAndProfiles() {
|
|||||||
prisma.typeMachineComponentRequirement.deleteMany(),
|
prisma.typeMachineComponentRequirement.deleteMany(),
|
||||||
prisma.typeMachinePieceRequirement.deleteMany(),
|
prisma.typeMachinePieceRequirement.deleteMany(),
|
||||||
prisma.customField.deleteMany(),
|
prisma.customField.deleteMany(),
|
||||||
prisma.pieceModel.deleteMany(),
|
|
||||||
prisma.composantModel.deleteMany(),
|
|
||||||
prisma.typeMachine.deleteMany(),
|
prisma.typeMachine.deleteMany(),
|
||||||
prisma.modelType.deleteMany(),
|
prisma.modelType.deleteMany(),
|
||||||
prisma.constructeur.deleteMany(),
|
prisma.constructeur.deleteMany(),
|
||||||
@@ -4190,44 +4192,51 @@ async function createModelTypes() {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const componentTypesMap = Object.fromEntries(componentTypeEntries) as Record<
|
||||||
|
string,
|
||||||
|
{ id: string; customFields: Record<string, string> }
|
||||||
|
>;
|
||||||
|
const pieceTypesMap = Object.fromEntries(pieceTypeEntries) as Record<
|
||||||
|
string,
|
||||||
|
{ id: string; customFields: Record<string, string> }
|
||||||
|
>;
|
||||||
|
|
||||||
|
await applyPieceTypeSkeletons(pieceTypesMap);
|
||||||
|
await applyComponentTypeSkeletons(componentTypesMap, pieceTypesMap);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
componentTypes: Object.fromEntries(componentTypeEntries) as Record<
|
componentTypes: componentTypesMap,
|
||||||
string,
|
pieceTypes: pieceTypesMap,
|
||||||
{ id: string; customFields: Record<string, string> }
|
|
||||||
>,
|
|
||||||
pieceTypes: Object.fromEntries(pieceTypeEntries) as Record<
|
|
||||||
string,
|
|
||||||
{ id: string; customFields: Record<string, string> }
|
|
||||||
>,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPieceModels(
|
async function applyPieceTypeSkeletons(
|
||||||
pieceTypes: Record<string, { id: string }>,
|
pieceTypes: Record<string, { id: string }>,
|
||||||
) {
|
) {
|
||||||
console.log('🧩 Création des modèles de pièces...');
|
console.log('🧩 Application des squelettes de pièces...');
|
||||||
|
|
||||||
const entries = await Promise.all(
|
const applied = new Set<string>();
|
||||||
pieceModelDefinitions.map(async (definition) => {
|
|
||||||
const type = pieceTypes[definition.typeCode];
|
|
||||||
if (!type) {
|
|
||||||
throw new Error(`Type de pièce introuvable: ${definition.typeCode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const record = await prisma.pieceModel.create({
|
for (const definition of pieceModelDefinitions) {
|
||||||
data: {
|
const type = pieceTypes[definition.typeCode];
|
||||||
name: definition.name,
|
if (!type || !definition.structure || applied.has(type.id)) {
|
||||||
description: definition.description,
|
continue;
|
||||||
typePiece: { connect: { id: type.id } },
|
}
|
||||||
structure: definition.structure,
|
|
||||||
},
|
try {
|
||||||
|
const skeleton = PieceModelStructureSchema.parse(definition.structure);
|
||||||
|
await prisma.modelType.update({
|
||||||
|
where: { id: type.id },
|
||||||
|
data: { pieceSkeleton: skeleton as Prisma.InputJsonValue },
|
||||||
});
|
});
|
||||||
|
applied.add(type.id);
|
||||||
return [definition.code, record] as const;
|
} catch (error) {
|
||||||
}),
|
console.warn(
|
||||||
);
|
`⚠️ Impossible d'appliquer le squelette de pièce ${definition.code}:`,
|
||||||
|
error,
|
||||||
return Object.fromEntries(entries) as Record<string, { id: string }>;
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildComponentModelStructure(
|
function buildComponentModelStructure(
|
||||||
@@ -4409,37 +4418,44 @@ function buildComponentModelStructure(
|
|||||||
return normalizeComponentModelStructure(canonical) as Prisma.InputJsonValue;
|
return normalizeComponentModelStructure(canonical) as Prisma.InputJsonValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createComponentModels(
|
async function applyComponentTypeSkeletons(
|
||||||
componentTypes: Record<string, { id: string }>,
|
componentTypes: Record<string, { id: string }>,
|
||||||
pieceTypes: Record<string, { id: string }>,
|
pieceTypes: Record<string, { id: string }>,
|
||||||
) {
|
) {
|
||||||
console.log('🛠️ Création des modèles de composants...');
|
console.log('🛠️ Application des squelettes de composants...');
|
||||||
|
|
||||||
const entries = await Promise.all(
|
const applied = new Set<string>();
|
||||||
componentModelDefinitions.map(async (definition) => {
|
|
||||||
const type = componentTypes[definition.typeCode];
|
|
||||||
if (!type) {
|
|
||||||
throw new Error(`Type de composant introuvable: ${definition.typeCode}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const record = await prisma.composantModel.create({
|
for (const definition of componentModelDefinitions) {
|
||||||
data: {
|
const type = componentTypes[definition.typeCode];
|
||||||
name: definition.name,
|
if (!type || applied.has(type.id)) {
|
||||||
description: definition.description,
|
continue;
|
||||||
typeComposant: { connect: { id: type.id } },
|
}
|
||||||
structure: buildComponentModelStructure(
|
|
||||||
definition.structure,
|
const structure = buildComponentModelStructure(
|
||||||
componentTypes,
|
definition.structure,
|
||||||
pieceTypes,
|
componentTypes,
|
||||||
),
|
pieceTypes,
|
||||||
},
|
);
|
||||||
|
|
||||||
|
if (!structure) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const skeleton = ComponentModelStructureSchema.parse(structure);
|
||||||
|
await prisma.modelType.update({
|
||||||
|
where: { id: type.id },
|
||||||
|
data: { componentSkeleton: skeleton as Prisma.InputJsonValue },
|
||||||
});
|
});
|
||||||
|
applied.add(type.id);
|
||||||
return [definition.code, record] as const;
|
} catch (error) {
|
||||||
}),
|
console.warn(
|
||||||
);
|
`⚠️ Impossible d'appliquer le squelette de composant ${definition.code}:`,
|
||||||
|
error,
|
||||||
return Object.fromEntries(entries) as Record<string, { id: string }>;
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTypeMachines(
|
async function createTypeMachines(
|
||||||
@@ -4544,9 +4560,7 @@ async function createComponentHierarchy(
|
|||||||
component: ComponentInstance,
|
component: ComponentInstance,
|
||||||
context: {
|
context: {
|
||||||
componentTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
componentTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
||||||
componentModels: Record<string, { id: string }>;
|
|
||||||
pieceTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
pieceTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
||||||
pieceModels: Record<string, { id: string }>;
|
|
||||||
constructeurs: Record<string, { id: string }>;
|
constructeurs: Record<string, { id: string }>;
|
||||||
requirementMap: Map<string, string>;
|
requirementMap: Map<string, string>;
|
||||||
},
|
},
|
||||||
@@ -4564,9 +4578,6 @@ async function createComponentHierarchy(
|
|||||||
machine: { connect: { id: machineId } },
|
machine: { connect: { id: machineId } },
|
||||||
parentComposant: parentId ? { connect: { id: parentId } } : undefined,
|
parentComposant: parentId ? { connect: { id: parentId } } : undefined,
|
||||||
typeComposant: { connect: { id: context.componentTypes[component.typeCode].id } },
|
typeComposant: { connect: { id: context.componentTypes[component.typeCode].id } },
|
||||||
composantModel: {
|
|
||||||
connect: { id: context.componentModels[component.modelCode].id },
|
|
||||||
},
|
|
||||||
constructeur: component.constructeur
|
constructeur: component.constructeur
|
||||||
? { connect: { id: context.constructeurs[component.constructeur].id } }
|
? { connect: { id: context.constructeurs[component.constructeur].id } }
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -4589,9 +4600,6 @@ async function createComponentHierarchy(
|
|||||||
reference: piece.reference,
|
reference: piece.reference,
|
||||||
prix: piece.prix ? new Prisma.Decimal(piece.prix) : undefined,
|
prix: piece.prix ? new Prisma.Decimal(piece.prix) : undefined,
|
||||||
typePiece: { connect: { id: type.id } },
|
typePiece: { connect: { id: type.id } },
|
||||||
pieceModel: {
|
|
||||||
connect: { id: context.pieceModels[piece.modelCode].id },
|
|
||||||
},
|
|
||||||
constructeur: piece.constructeur
|
constructeur: piece.constructeur
|
||||||
? { connect: { id: context.constructeurs[piece.constructeur].id } }
|
? { connect: { id: context.constructeurs[piece.constructeur].id } }
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -4620,9 +4628,7 @@ async function createMachines(
|
|||||||
typeMachines: Record<string, TypeMachineRecord>,
|
typeMachines: Record<string, TypeMachineRecord>,
|
||||||
context: {
|
context: {
|
||||||
componentTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
componentTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
||||||
componentModels: Record<string, { id: string }>;
|
|
||||||
pieceTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
pieceTypes: Record<string, { id: string; customFields: Record<string, string> }>;
|
||||||
pieceModels: Record<string, { id: string }>;
|
|
||||||
constructeurs: Record<string, { id: string }>;
|
constructeurs: Record<string, { id: string }>;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -4696,7 +4702,6 @@ async function createMachines(
|
|||||||
prix: spare.prix ? new Prisma.Decimal(spare.prix) : undefined,
|
prix: spare.prix ? new Prisma.Decimal(spare.prix) : undefined,
|
||||||
machine: { connect: { id: machine.id } },
|
machine: { connect: { id: machine.id } },
|
||||||
typePiece: { connect: { id: pieceType.id } },
|
typePiece: { connect: { id: pieceType.id } },
|
||||||
pieceModel: { connect: { id: context.pieceModels[spare.modelCode].id } },
|
|
||||||
constructeur: spare.constructeur
|
constructeur: spare.constructeur
|
||||||
? { connect: { id: context.constructeurs[spare.constructeur].id } }
|
? { connect: { id: context.constructeurs[spare.constructeur].id } }
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -4724,17 +4729,11 @@ async function main() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const { componentTypes, pieceTypes } = await createModelTypes();
|
const { componentTypes, pieceTypes } = await createModelTypes();
|
||||||
const [pieceModels, componentModels, typeMachines] = await Promise.all([
|
const typeMachines = await createTypeMachines(componentTypes, pieceTypes);
|
||||||
createPieceModels(pieceTypes),
|
|
||||||
createComponentModels(componentTypes, pieceTypes),
|
|
||||||
createTypeMachines(componentTypes, pieceTypes),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await createMachines(site.id, typeMachines, {
|
await createMachines(site.id, typeMachines, {
|
||||||
componentTypes,
|
componentTypes,
|
||||||
componentModels,
|
|
||||||
pieceTypes,
|
pieceTypes,
|
||||||
pieceModels,
|
|
||||||
constructeurs,
|
constructeurs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
|||||||
customFields: true,
|
customFields: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
composantModel: true,
|
|
||||||
typeMachineComponentRequirement: {
|
typeMachineComponentRequirement: {
|
||||||
include: {
|
include: {
|
||||||
typeComposant: {
|
typeComposant: {
|
||||||
@@ -40,7 +39,6 @@ export const COMPONENT_WITH_RELATIONS_INCLUDE = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
constructeur: true,
|
constructeur: true,
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
typeMachinePieceRequirement: {
|
||||||
include: {
|
include: {
|
||||||
typePiece: {
|
typePiece: {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { ModelTypeMapper } from './model-type.mapper';
|
import { ModelTypeMapper } from './model-type.mapper';
|
||||||
|
import {
|
||||||
|
ComponentModelStructureSchema,
|
||||||
|
PieceModelStructureSchema,
|
||||||
|
} from '../../shared/schemas/inventory';
|
||||||
|
|
||||||
describe('ModelTypeMapper', () => {
|
describe('ModelTypeMapper', () => {
|
||||||
it('should map component create input', () => {
|
it('should map component create input', () => {
|
||||||
@@ -8,9 +12,30 @@ describe('ModelTypeMapper', () => {
|
|||||||
customFields: [
|
customFields: [
|
||||||
{ name: 'Field', type: 'string', required: false, options: [] },
|
{ name: 'Field', type: 'string', required: false, options: [] },
|
||||||
],
|
],
|
||||||
|
structure: {
|
||||||
|
pieces: [
|
||||||
|
{
|
||||||
|
familyCode: 'bolt',
|
||||||
|
role: 'Fixation',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
key: 'color',
|
||||||
|
value: 'red',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subcomponents: [
|
||||||
|
{
|
||||||
|
familyCode: 'sub-family',
|
||||||
|
alias: 'Secondary',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const input = ModelTypeMapper.toComponentCreateInput(dto, 'code');
|
const skeleton = ComponentModelStructureSchema.parse(dto.structure);
|
||||||
|
const input = ModelTypeMapper.toComponentCreateInput(dto, 'code', skeleton);
|
||||||
|
|
||||||
expect(input).toMatchObject({
|
expect(input).toMatchObject({
|
||||||
name: 'Comp',
|
name: 'Comp',
|
||||||
@@ -19,6 +44,72 @@ describe('ModelTypeMapper', () => {
|
|||||||
notes: 'Desc',
|
notes: 'Desc',
|
||||||
});
|
});
|
||||||
expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' });
|
expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' });
|
||||||
|
expect((input as any).componentSkeleton).toEqual({
|
||||||
|
pieces: [
|
||||||
|
{
|
||||||
|
familyCode: 'bolt',
|
||||||
|
role: 'Fixation',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
key: 'color',
|
||||||
|
value: 'red',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subcomponents: [
|
||||||
|
{
|
||||||
|
familyCode: 'sub-family',
|
||||||
|
alias: 'Secondary',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map piece create input with skeleton', () => {
|
||||||
|
const dto = {
|
||||||
|
name: 'Piece type',
|
||||||
|
description: 'Desc',
|
||||||
|
customFields: [],
|
||||||
|
structure: {
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
name: 'Length',
|
||||||
|
value: 12,
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'color',
|
||||||
|
value: 'blue',
|
||||||
|
optionsText: 'blue\nred',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
typePieceId: ' piece-id ',
|
||||||
|
standard: 'ISO',
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const skeleton = PieceModelStructureSchema.parse(dto.structure);
|
||||||
|
const input = ModelTypeMapper.toPieceCreateInput(dto, 'code', skeleton);
|
||||||
|
|
||||||
|
expect((input as any).pieceSkeleton).toEqual({
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
name: 'Length',
|
||||||
|
value: 12,
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'color',
|
||||||
|
value: 'blue',
|
||||||
|
options: ['blue', 'red'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
typePieceId: 'piece-id',
|
||||||
|
standard: 'ISO',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map piece model type to DTO shape', () => {
|
it('should map piece model type to DTO shape', () => {
|
||||||
@@ -26,7 +117,7 @@ describe('ModelTypeMapper', () => {
|
|||||||
id: '1',
|
id: '1',
|
||||||
name: 'Piece',
|
name: 'Piece',
|
||||||
pieceCustomFields: [{ id: 'cf' }],
|
pieceCustomFields: [{ id: 'cf' }],
|
||||||
pieceModels: [{ id: 'model' }],
|
pieceSkeleton: { customFields: [{ name: 'Length' }] },
|
||||||
pieceRequirements: [{ id: 'req' }],
|
pieceRequirements: [{ id: 'req' }],
|
||||||
pieces: [{ id: 'piece' }],
|
pieces: [{ id: 'piece' }],
|
||||||
});
|
});
|
||||||
@@ -34,18 +125,21 @@ describe('ModelTypeMapper', () => {
|
|||||||
expect(mapped).toMatchObject({
|
expect(mapped).toMatchObject({
|
||||||
id: '1',
|
id: '1',
|
||||||
customFields: [{ id: 'cf' }],
|
customFields: [{ id: 'cf' }],
|
||||||
models: [{ id: 'model' }],
|
|
||||||
pieceRequirements: [{ id: 'req' }],
|
pieceRequirements: [{ id: 'req' }],
|
||||||
pieces: [{ id: 'piece' }],
|
pieces: [{ id: 'piece' }],
|
||||||
|
structure: { customFields: [{ name: 'Length' }] },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map piece update input', () => {
|
it('should map piece update input', () => {
|
||||||
const input = ModelTypeMapper.toPieceUpdateInput({
|
const dto: any = {
|
||||||
name: 'New',
|
name: 'New',
|
||||||
description: 'D',
|
description: 'D',
|
||||||
customFields: [],
|
customFields: [],
|
||||||
} as any);
|
structure: { customFields: [{ name: 'Length' }] },
|
||||||
|
};
|
||||||
|
const skeleton = PieceModelStructureSchema.parse(dto.structure);
|
||||||
|
const input = ModelTypeMapper.toPieceUpdateInput(dto, skeleton);
|
||||||
|
|
||||||
expect(input).toMatchObject({
|
expect(input).toMatchObject({
|
||||||
name: 'New',
|
name: 'New',
|
||||||
@@ -53,5 +147,36 @@ describe('ModelTypeMapper', () => {
|
|||||||
notes: 'D',
|
notes: 'D',
|
||||||
});
|
});
|
||||||
expect(input.pieceCustomFields).toBeUndefined();
|
expect(input.pieceCustomFields).toBeUndefined();
|
||||||
|
expect((input as any).pieceSkeleton).toEqual({
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
name: 'Length',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should map component update input with skeleton', () => {
|
||||||
|
const dto: any = {
|
||||||
|
name: 'Updated',
|
||||||
|
structure: {
|
||||||
|
pieces: [{ typePieceId: 'piece-1' }],
|
||||||
|
customFields: [],
|
||||||
|
subcomponents: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const skeleton = ComponentModelStructureSchema.parse(dto.structure);
|
||||||
|
const input = ModelTypeMapper.toComponentUpdateInput(dto, skeleton);
|
||||||
|
|
||||||
|
expect(input).toMatchObject({ name: 'Updated' });
|
||||||
|
expect((input as any).componentSkeleton).toEqual({
|
||||||
|
pieces: [
|
||||||
|
{
|
||||||
|
typePieceId: 'piece-1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customFields: [],
|
||||||
|
subcomponents: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,17 +5,19 @@ import {
|
|||||||
UpdateTypeComposantDto,
|
UpdateTypeComposantDto,
|
||||||
UpdateTypePieceDto,
|
UpdateTypePieceDto,
|
||||||
} from '../../shared/dto/type.dto';
|
} from '../../shared/dto/type.dto';
|
||||||
|
import type {
|
||||||
|
ComponentModelStructure,
|
||||||
|
PieceModelStructure,
|
||||||
|
} from '../../shared/types/inventory';
|
||||||
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
|
import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant';
|
||||||
|
|
||||||
export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||||
customFields: { select: CUSTOM_FIELD_SELECT },
|
customFields: { select: CUSTOM_FIELD_SELECT },
|
||||||
composants: true,
|
composants: true,
|
||||||
models: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = {
|
||||||
pieceCustomFields: { select: CUSTOM_FIELD_SELECT },
|
pieceCustomFields: { select: CUSTOM_FIELD_SELECT },
|
||||||
pieceModels: true,
|
|
||||||
pieceRequirements: true,
|
pieceRequirements: true,
|
||||||
pieces: true,
|
pieces: true,
|
||||||
};
|
};
|
||||||
@@ -29,6 +31,7 @@ export class ModelTypeMapper {
|
|||||||
static toComponentCreateInput(
|
static toComponentCreateInput(
|
||||||
dto: CreateTypeComposantDto,
|
dto: CreateTypeComposantDto,
|
||||||
code: string,
|
code: string,
|
||||||
|
skeleton?: ComponentModelStructure,
|
||||||
): ModelTypeCreateWithoutCategory {
|
): ModelTypeCreateWithoutCategory {
|
||||||
const { customFields, description, name } = dto;
|
const { customFields, description, name } = dto;
|
||||||
|
|
||||||
@@ -47,11 +50,13 @@ export class ModelTypeMapper {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
...(skeleton ? { componentSkeleton: skeleton as Prisma.InputJsonValue } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static toComponentUpdateInput(
|
static toComponentUpdateInput(
|
||||||
dto: UpdateTypeComposantDto,
|
dto: UpdateTypeComposantDto,
|
||||||
|
skeleton?: ComponentModelStructure,
|
||||||
): Prisma.ModelTypeUpdateInput {
|
): Prisma.ModelTypeUpdateInput {
|
||||||
const { customFields, description, name } = dto;
|
const { customFields, description, name } = dto;
|
||||||
const data: Prisma.ModelTypeUpdateInput = {};
|
const data: Prisma.ModelTypeUpdateInput = {};
|
||||||
@@ -69,12 +74,17 @@ export class ModelTypeMapper {
|
|||||||
data.customFields = undefined;
|
data.customFields = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skeleton !== undefined) {
|
||||||
|
data.componentSkeleton = skeleton as Prisma.InputJsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
static toPieceCreateInput(
|
static toPieceCreateInput(
|
||||||
dto: CreateTypePieceDto,
|
dto: CreateTypePieceDto,
|
||||||
code: string,
|
code: string,
|
||||||
|
skeleton?: PieceModelStructure,
|
||||||
): ModelTypeCreateWithoutCategory {
|
): ModelTypeCreateWithoutCategory {
|
||||||
const { customFields, description, name } = dto;
|
const { customFields, description, name } = dto;
|
||||||
|
|
||||||
@@ -93,11 +103,13 @@ export class ModelTypeMapper {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
...(skeleton ? { pieceSkeleton: skeleton as Prisma.InputJsonValue } : {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static toPieceUpdateInput(
|
static toPieceUpdateInput(
|
||||||
dto: UpdateTypePieceDto,
|
dto: UpdateTypePieceDto,
|
||||||
|
skeleton?: PieceModelStructure,
|
||||||
): Prisma.ModelTypeUpdateInput {
|
): Prisma.ModelTypeUpdateInput {
|
||||||
const { customFields, description, name } = dto;
|
const { customFields, description, name } = dto;
|
||||||
const data: Prisma.ModelTypeUpdateInput = {};
|
const data: Prisma.ModelTypeUpdateInput = {};
|
||||||
@@ -115,6 +127,10 @@ export class ModelTypeMapper {
|
|||||||
data.pieceCustomFields = undefined;
|
data.pieceCustomFields = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (skeleton !== undefined) {
|
||||||
|
data.pieceSkeleton = skeleton as Prisma.InputJsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,18 +141,18 @@ export class ModelTypeMapper {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
pieceCustomFields,
|
pieceCustomFields,
|
||||||
pieceModels,
|
|
||||||
pieceRequirements,
|
pieceRequirements,
|
||||||
pieces,
|
pieces,
|
||||||
|
pieceSkeleton,
|
||||||
...rest
|
...rest
|
||||||
} = modelType;
|
} = modelType;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
customFields: pieceCustomFields ?? [],
|
customFields: pieceCustomFields ?? [],
|
||||||
models: pieceModels ?? [],
|
|
||||||
pieceRequirements: pieceRequirements ?? [],
|
pieceRequirements: pieceRequirements ?? [],
|
||||||
pieces: pieces ?? [],
|
pieces: pieces ?? [],
|
||||||
|
structure: pieceSkeleton ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { Prisma, PrismaClient } from '@prisma/client';
|
|
||||||
import { PrismaService } from '../../prisma/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ComposantModelsRepository {
|
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
|
||||||
|
|
||||||
private get client(): PrismaClient {
|
|
||||||
return this.prisma;
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(
|
|
||||||
data: Prisma.ComposantModelCreateInput,
|
|
||||||
include?: Prisma.ComposantModelInclude,
|
|
||||||
) {
|
|
||||||
return this.client.composantModel.create({
|
|
||||||
data,
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(
|
|
||||||
typeComposantId?: string,
|
|
||||||
include?: Prisma.ComposantModelInclude,
|
|
||||||
) {
|
|
||||||
return this.client.composantModel.findMany({
|
|
||||||
where: typeComposantId ? { typeComposantId } : undefined,
|
|
||||||
include,
|
|
||||||
orderBy: { name: 'asc' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(id: string, include?: Prisma.ComposantModelInclude) {
|
|
||||||
return this.client.composantModel.findUnique({
|
|
||||||
where: { id },
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(
|
|
||||||
id: string,
|
|
||||||
data: Prisma.ComposantModelUpdateInput,
|
|
||||||
include?: Prisma.ComposantModelInclude,
|
|
||||||
) {
|
|
||||||
return this.client.composantModel.update({
|
|
||||||
where: { id },
|
|
||||||
data,
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(id: string) {
|
|
||||||
return this.client.composantModel.delete({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { Prisma, PrismaClient } from '@prisma/client';
|
|
||||||
import { PrismaService } from '../../prisma/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PieceModelsRepository {
|
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
|
||||||
|
|
||||||
private get client(): PrismaClient {
|
|
||||||
return this.prisma;
|
|
||||||
}
|
|
||||||
|
|
||||||
async create(
|
|
||||||
data: Prisma.PieceModelCreateInput,
|
|
||||||
include?: Prisma.PieceModelInclude,
|
|
||||||
) {
|
|
||||||
return this.client.pieceModel.create({
|
|
||||||
data,
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(typePieceId?: string, include?: Prisma.PieceModelInclude) {
|
|
||||||
return this.client.pieceModel.findMany({
|
|
||||||
where: typePieceId ? { typePieceId } : undefined,
|
|
||||||
include,
|
|
||||||
orderBy: { name: 'asc' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(id: string, include?: Prisma.PieceModelInclude) {
|
|
||||||
return this.client.pieceModel.findUnique({
|
|
||||||
where: { id },
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(
|
|
||||||
id: string,
|
|
||||||
data: Prisma.PieceModelUpdateInput,
|
|
||||||
include?: Prisma.PieceModelInclude,
|
|
||||||
) {
|
|
||||||
return this.client.pieceModel.update({
|
|
||||||
where: { id },
|
|
||||||
data,
|
|
||||||
include,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(id: string) {
|
|
||||||
return this.client.pieceModel.delete({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,10 +13,21 @@ describe('ComposantsService', () => {
|
|||||||
composant: {
|
composant: {
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
findUnique: jest.fn(),
|
findUnique: jest.fn(),
|
||||||
|
findMany: jest.fn(),
|
||||||
},
|
},
|
||||||
machine: {
|
machine: {
|
||||||
findUnique: jest.fn(),
|
findUnique: jest.fn(),
|
||||||
},
|
},
|
||||||
|
customField: {
|
||||||
|
findMany: jest.fn(),
|
||||||
|
},
|
||||||
|
customFieldValue: {
|
||||||
|
findMany: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
piece: {
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@@ -45,15 +56,48 @@ describe('ComposantsService', () => {
|
|||||||
id: 'machine-1',
|
id: 'machine-1',
|
||||||
typeMachine: {
|
typeMachine: {
|
||||||
componentRequirements: [
|
componentRequirements: [
|
||||||
{ id: 'req-1', typeComposantId: 'type-comp-1' },
|
{
|
||||||
|
id: 'req-1',
|
||||||
|
typeComposantId: 'type-comp-1',
|
||||||
|
typeComposant: {
|
||||||
|
id: 'type-comp-1',
|
||||||
|
name: 'Comp type',
|
||||||
|
code: 'comp-type',
|
||||||
|
componentSkeleton: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
pieceRequirements: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const created = { id: 'component-1' };
|
const created = {
|
||||||
|
id: 'component-1',
|
||||||
|
name: 'Comp A',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
typeComposantId: 'type-comp-1',
|
||||||
|
};
|
||||||
prisma.composant.create.mockResolvedValue(created);
|
prisma.composant.create.mockResolvedValue(created);
|
||||||
|
prisma.composant.findUnique.mockResolvedValue({
|
||||||
|
...created,
|
||||||
|
machine: null,
|
||||||
|
parentComposant: null,
|
||||||
|
typeComposant: {
|
||||||
|
id: 'type-comp-1',
|
||||||
|
name: 'Comp type',
|
||||||
|
code: 'comp-type',
|
||||||
|
componentSkeleton: null,
|
||||||
|
customFields: [],
|
||||||
|
},
|
||||||
|
typeMachineComponentRequirement: null,
|
||||||
|
constructeur: null,
|
||||||
|
customFieldValues: [],
|
||||||
|
pieces: [],
|
||||||
|
documents: [],
|
||||||
|
});
|
||||||
|
prisma.composant.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(service.create(dto)).resolves.toEqual(created);
|
await expect(service.create(dto)).resolves.toMatchObject({ id: 'component-1' });
|
||||||
|
|
||||||
expect(prisma.composant.create).toHaveBeenCalled();
|
expect(prisma.composant.create).toHaveBeenCalled();
|
||||||
expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe(
|
expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe(
|
||||||
@@ -75,6 +119,7 @@ describe('ComposantsService', () => {
|
|||||||
componentRequirements: [
|
componentRequirements: [
|
||||||
{ id: 'req-1', typeComposantId: 'type-comp-1' },
|
{ id: 'req-1', typeComposantId: 'type-comp-1' },
|
||||||
],
|
],
|
||||||
|
pieceRequirements: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,4 +129,155 @@ describe('ComposantsService', () => {
|
|||||||
|
|
||||||
expect(prisma.composant.create).not.toHaveBeenCalled();
|
expect(prisma.composant.create).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create nested components, pieces, and custom field values from the type skeleton', async () => {
|
||||||
|
const dto: CreateComposantDto = {
|
||||||
|
name: 'Comp B',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
typeMachineComponentRequirementId: 'req-root',
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
prisma.machine.findUnique.mockResolvedValue({
|
||||||
|
id: 'machine-1',
|
||||||
|
typeMachine: {
|
||||||
|
componentRequirements: [
|
||||||
|
{
|
||||||
|
id: 'req-root',
|
||||||
|
typeComposantId: 'type-root',
|
||||||
|
typeComposant: {
|
||||||
|
id: 'type-root',
|
||||||
|
name: 'Root type',
|
||||||
|
code: 'root',
|
||||||
|
componentSkeleton: {
|
||||||
|
customFields: [{ key: 'color', value: 'red' }],
|
||||||
|
pieces: [
|
||||||
|
{
|
||||||
|
typePieceId: 'type-piece',
|
||||||
|
role: 'Primary piece',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subcomponents: [
|
||||||
|
{
|
||||||
|
typeComposantId: 'type-child',
|
||||||
|
alias: 'Child component',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
maxCount: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'req-child',
|
||||||
|
typeComposantId: 'type-child',
|
||||||
|
typeComposant: {
|
||||||
|
id: 'type-child',
|
||||||
|
name: 'Child type',
|
||||||
|
code: 'child',
|
||||||
|
componentSkeleton: null,
|
||||||
|
},
|
||||||
|
maxCount: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pieceRequirements: [
|
||||||
|
{
|
||||||
|
id: 'req-piece',
|
||||||
|
typePieceId: 'type-piece',
|
||||||
|
typePiece: {
|
||||||
|
id: 'type-piece',
|
||||||
|
name: 'Piece type',
|
||||||
|
code: 'piece',
|
||||||
|
},
|
||||||
|
maxCount: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
prisma.customField.findMany.mockResolvedValue([{ id: 'cf-color', name: 'color' }]);
|
||||||
|
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
|
const rootComponent = {
|
||||||
|
id: 'component-1',
|
||||||
|
name: 'Comp B',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
typeComposantId: 'type-root',
|
||||||
|
typeComposant: {
|
||||||
|
id: 'type-root',
|
||||||
|
name: 'Root type',
|
||||||
|
code: 'root',
|
||||||
|
componentSkeleton: {
|
||||||
|
customFields: [{ key: 'color', value: 'red' }],
|
||||||
|
pieces: [],
|
||||||
|
subcomponents: [],
|
||||||
|
},
|
||||||
|
customFields: [],
|
||||||
|
},
|
||||||
|
machine: null,
|
||||||
|
parentComposant: null,
|
||||||
|
typeMachineComponentRequirement: null,
|
||||||
|
constructeur: null,
|
||||||
|
customFieldValues: [],
|
||||||
|
pieces: [],
|
||||||
|
documents: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
prisma.composant.create
|
||||||
|
.mockResolvedValueOnce(rootComponent)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
id: 'component-child',
|
||||||
|
name: 'Child component',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
parentComposantId: 'component-1',
|
||||||
|
typeComposantId: 'type-child',
|
||||||
|
});
|
||||||
|
|
||||||
|
prisma.composant.findUnique.mockResolvedValue(rootComponent);
|
||||||
|
prisma.composant.findMany.mockResolvedValue([
|
||||||
|
{ ...rootComponent, parentComposantId: null },
|
||||||
|
{
|
||||||
|
id: 'component-child',
|
||||||
|
name: 'Child component',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
parentComposantId: 'component-1',
|
||||||
|
typeComposantId: 'type-child',
|
||||||
|
machine: null,
|
||||||
|
parentComposant: rootComponent,
|
||||||
|
typeComposant: null,
|
||||||
|
typeMachineComponentRequirement: null,
|
||||||
|
constructeur: null,
|
||||||
|
customFieldValues: [],
|
||||||
|
pieces: [],
|
||||||
|
documents: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await service.create(dto);
|
||||||
|
|
||||||
|
expect(prisma.customField.findMany).toHaveBeenCalledWith({
|
||||||
|
where: { typeComposantId: 'type-root' },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
expect(prisma.customFieldValue.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
customFieldId: 'cf-color',
|
||||||
|
composantId: 'component-1',
|
||||||
|
value: 'red',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(prisma.piece.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
name: 'Primary piece',
|
||||||
|
machineId: 'machine-1',
|
||||||
|
composantId: 'component-1',
|
||||||
|
typePieceId: 'type-piece',
|
||||||
|
typeMachinePieceRequirementId: 'req-piece',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(prisma.composant.create).toHaveBeenCalledTimes(2);
|
||||||
|
expect(prisma.composant.create.mock.calls[1][0].data).toMatchObject({
|
||||||
|
parentComposantId: 'component-1',
|
||||||
|
typeComposantId: 'type-child',
|
||||||
|
typeMachineComponentRequirementId: 'req-child',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import {
|
import {
|
||||||
CreateComposantDto,
|
CreateComposantDto,
|
||||||
@@ -12,6 +13,19 @@ import {
|
|||||||
buildComponentHierarchy,
|
buildComponentHierarchy,
|
||||||
buildComponentSubtree,
|
buildComponentSubtree,
|
||||||
} from '../common/utils/component-tree.util';
|
} from '../common/utils/component-tree.util';
|
||||||
|
import { ComponentModelStructureSchema } from '../shared/schemas/inventory';
|
||||||
|
import type { ComponentModelStructure } from '../shared/types/inventory';
|
||||||
|
|
||||||
|
type ComponentRequirementWithType =
|
||||||
|
Prisma.TypeMachineComponentRequirementGetPayload<{
|
||||||
|
include: { typeComposant: true };
|
||||||
|
}>;
|
||||||
|
type PieceRequirementWithType =
|
||||||
|
Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||||
|
include: { typePiece: true };
|
||||||
|
}>;
|
||||||
|
type ModelTypeWithSkeleton = ComponentRequirementWithType['typeComposant'];
|
||||||
|
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ComposantsService {
|
export class ComposantsService {
|
||||||
@@ -80,7 +94,16 @@ export class ComposantsService {
|
|||||||
include: {
|
include: {
|
||||||
typeMachine: {
|
typeMachine: {
|
||||||
include: {
|
include: {
|
||||||
componentRequirements: true,
|
componentRequirements: {
|
||||||
|
include: {
|
||||||
|
typeComposant: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pieceRequirements: {
|
||||||
|
include: {
|
||||||
|
typePiece: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -92,7 +115,12 @@ export class ComposantsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const requirement = machine.typeMachine.componentRequirements.find(
|
const componentRequirements =
|
||||||
|
(machine.typeMachine.componentRequirements as ComponentRequirementWithType[]) ?? [];
|
||||||
|
const pieceRequirements =
|
||||||
|
(machine.typeMachine.pieceRequirements as PieceRequirementWithType[]) ?? [];
|
||||||
|
|
||||||
|
const requirement = componentRequirements.find(
|
||||||
(componentRequirement) => componentRequirement.id === requirementId,
|
(componentRequirement) => componentRequirement.id === requirementId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -111,20 +139,38 @@ export class ComposantsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const typeComposantId =
|
||||||
...createComposantDto,
|
createComposantDto.typeComposantId ?? requirement.typeComposantId;
|
||||||
machineId,
|
|
||||||
typeComposantId:
|
|
||||||
createComposantDto.typeComposantId ?? requirement.typeComposantId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const created = (await this.prisma.composant.create({
|
const created = await this.prisma.composant.create({
|
||||||
data,
|
data: {
|
||||||
|
...createComposantDto,
|
||||||
|
machineId,
|
||||||
|
typeComposantId,
|
||||||
|
},
|
||||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
})) as ComposantWithRelations;
|
});
|
||||||
|
|
||||||
|
const componentRequirementUsage = new Map<string, number>();
|
||||||
|
componentRequirementUsage.set(requirement.id, 1);
|
||||||
|
const pieceRequirementUsage = new Map<string, number>();
|
||||||
|
|
||||||
|
await this.populateComponentFromSkeleton({
|
||||||
|
componentId: created.id,
|
||||||
|
componentName: created.name,
|
||||||
|
componentType:
|
||||||
|
(requirement.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||||
|
(created.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||||
|
null,
|
||||||
|
machineId,
|
||||||
|
componentRequirements,
|
||||||
|
pieceRequirements,
|
||||||
|
componentRequirementUsage,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
});
|
||||||
|
|
||||||
const component = await this.getComponentWithHierarchy(created.id);
|
const component = await this.getComponentWithHierarchy(created.id);
|
||||||
return component ?? created;
|
return (component as ComposantWithRelations | null) ?? (created as ComposantWithRelations);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
@@ -156,11 +202,379 @@ export class ComposantsService {
|
|||||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||||
})) as ComposantWithRelations;
|
})) as ComposantWithRelations;
|
||||||
|
|
||||||
await this.syncComponentModelCustomFields(updated);
|
|
||||||
|
|
||||||
return this.getComponentWithHierarchy(updated.id);
|
return this.getComponentWithHierarchy(updated.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async populateComponentFromSkeleton({
|
||||||
|
componentId,
|
||||||
|
componentName,
|
||||||
|
componentType,
|
||||||
|
machineId,
|
||||||
|
componentRequirements,
|
||||||
|
pieceRequirements,
|
||||||
|
componentRequirementUsage,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
}: {
|
||||||
|
componentId: string;
|
||||||
|
componentName?: string;
|
||||||
|
componentType: ModelTypeWithSkeleton | null;
|
||||||
|
machineId: string;
|
||||||
|
componentRequirements: ComponentRequirementWithType[];
|
||||||
|
pieceRequirements: PieceRequirementWithType[];
|
||||||
|
componentRequirementUsage: Map<string, number>;
|
||||||
|
pieceRequirementUsage: Map<string, number>;
|
||||||
|
}) {
|
||||||
|
const skeleton = this.parseComponentSkeleton(
|
||||||
|
(componentType as { componentSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||||
|
componentSkeleton,
|
||||||
|
);
|
||||||
|
if (!skeleton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.createComponentCustomFieldValues(
|
||||||
|
componentId,
|
||||||
|
componentType?.id ?? null,
|
||||||
|
skeleton.customFields,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.createPiecesFromSkeleton({
|
||||||
|
componentId,
|
||||||
|
componentName,
|
||||||
|
machineId,
|
||||||
|
pieces: skeleton.pieces,
|
||||||
|
pieceRequirements,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const subcomponent of skeleton.subcomponents ?? []) {
|
||||||
|
const requirement = this.resolveComponentRequirement(
|
||||||
|
subcomponent,
|
||||||
|
componentRequirements,
|
||||||
|
componentRequirementUsage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!requirement?.typeComposant) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = this.buildComponentName(
|
||||||
|
subcomponent,
|
||||||
|
requirement.typeComposant,
|
||||||
|
componentName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const createdChild = await this.prisma.composant.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
machineId,
|
||||||
|
parentComposantId: componentId,
|
||||||
|
typeComposantId: requirement.typeComposantId,
|
||||||
|
typeMachineComponentRequirementId: requirement.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.incrementRequirementUsage(
|
||||||
|
componentRequirementUsage,
|
||||||
|
requirement.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.populateComponentFromSkeleton({
|
||||||
|
componentId: createdChild.id,
|
||||||
|
componentName: createdChild.name,
|
||||||
|
componentType: requirement.typeComposant as ModelTypeWithSkeleton,
|
||||||
|
machineId,
|
||||||
|
componentRequirements,
|
||||||
|
pieceRequirements,
|
||||||
|
componentRequirementUsage,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseComponentSkeleton(
|
||||||
|
value: unknown,
|
||||||
|
): ComponentModelStructure | null {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ComponentModelStructureSchema.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createComponentCustomFieldValues(
|
||||||
|
componentId: string,
|
||||||
|
typeComposantId: string | null,
|
||||||
|
customFields: ComponentModelStructure['customFields'],
|
||||||
|
) {
|
||||||
|
if (!typeComposantId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitions = await this.prisma.customField.findMany({
|
||||||
|
where: { typeComposantId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (definitions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitionMap = new Map(definitions.map((field) => [field.name, field.id]));
|
||||||
|
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||||
|
where: { composantId: componentId },
|
||||||
|
select: { customFieldId: true },
|
||||||
|
});
|
||||||
|
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||||
|
|
||||||
|
for (const field of customFields) {
|
||||||
|
const key = this.normalizeIdentifier(field?.key);
|
||||||
|
if (!key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitionId = definitionMap.get(key);
|
||||||
|
if (!definitionId || existingIds.has(definitionId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.customFieldValue.create({
|
||||||
|
data: {
|
||||||
|
customFieldId: definitionId,
|
||||||
|
composantId: componentId,
|
||||||
|
value: this.toCustomFieldValue(field?.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
existingIds.add(definitionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createPiecesFromSkeleton({
|
||||||
|
componentId,
|
||||||
|
componentName,
|
||||||
|
machineId,
|
||||||
|
pieces,
|
||||||
|
pieceRequirements,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
}: {
|
||||||
|
componentId: string;
|
||||||
|
componentName?: string;
|
||||||
|
machineId: string;
|
||||||
|
pieces: ComponentModelStructure['pieces'];
|
||||||
|
pieceRequirements: PieceRequirementWithType[];
|
||||||
|
pieceRequirementUsage: Map<string, number>;
|
||||||
|
}) {
|
||||||
|
if (!Array.isArray(pieces) || pieces.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of pieces) {
|
||||||
|
const requirement = this.resolvePieceRequirement(
|
||||||
|
entry,
|
||||||
|
pieceRequirements,
|
||||||
|
pieceRequirementUsage,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!requirement?.typePiece) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = this.buildPieceName(entry, requirement.typePiece, componentName);
|
||||||
|
|
||||||
|
await this.prisma.piece.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
machineId,
|
||||||
|
composantId: componentId,
|
||||||
|
typePieceId: requirement.typePieceId,
|
||||||
|
typeMachinePieceRequirementId: requirement.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.incrementRequirementUsage(pieceRequirementUsage, requirement.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveComponentRequirement(
|
||||||
|
entry: ComponentModelStructure['subcomponents'][number],
|
||||||
|
requirements: ComponentRequirementWithType[],
|
||||||
|
usage: Map<string, number>,
|
||||||
|
): ComponentRequirementWithType | null {
|
||||||
|
const typeComposantId = this.normalizeIdentifier(
|
||||||
|
(entry as { typeComposantId?: string }).typeComposantId,
|
||||||
|
);
|
||||||
|
const familyCode = this.normalizeCode(
|
||||||
|
(entry as { familyCode?: string }).familyCode,
|
||||||
|
);
|
||||||
|
|
||||||
|
const candidates = requirements.filter((requirement) => {
|
||||||
|
if (typeComposantId && requirement.typeComposantId === typeComposantId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (familyCode && requirement.typeComposant?.code) {
|
||||||
|
return this.normalizeCode(requirement.typeComposant.code) === familyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
if (typeComposantId || familyCode) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Aucun requirement de composant ne correspond au squelette (${typeComposantId ?? familyCode}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Le squelette du composant référence un sous-composant sans identifiant de type.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (this.hasRequirementCapacity(candidate, usage)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException(
|
||||||
|
`La capacité maximale du requirement de composant (${typeComposantId ?? familyCode}) est atteinte pour la machine visée.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolvePieceRequirement(
|
||||||
|
entry: ComponentModelStructure['pieces'][number],
|
||||||
|
requirements: PieceRequirementWithType[],
|
||||||
|
usage: Map<string, number>,
|
||||||
|
): PieceRequirementWithType | null {
|
||||||
|
const typePieceId = this.normalizeIdentifier(
|
||||||
|
(entry as { typePieceId?: string }).typePieceId,
|
||||||
|
);
|
||||||
|
const familyCode = this.normalizeCode(
|
||||||
|
(entry as { familyCode?: string }).familyCode,
|
||||||
|
);
|
||||||
|
|
||||||
|
const candidates = requirements.filter((requirement) => {
|
||||||
|
if (typePieceId && requirement.typePieceId === typePieceId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (familyCode && requirement.typePiece?.code) {
|
||||||
|
return this.normalizeCode(requirement.typePiece.code) === familyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
if (typePieceId || familyCode) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`Aucun requirement de pièce ne correspond au squelette (${typePieceId ?? familyCode}).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException(
|
||||||
|
'Le squelette du composant référence une pièce sans identifiant de type.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (this.hasRequirementCapacity(candidate, usage)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException(
|
||||||
|
`La capacité maximale du requirement de pièce (${typePieceId ?? familyCode}) est atteinte pour la machine visée.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasRequirementCapacity(
|
||||||
|
requirement: { id: string; maxCount: number | null | undefined },
|
||||||
|
usage: Map<string, number>,
|
||||||
|
): boolean {
|
||||||
|
const max = requirement.maxCount;
|
||||||
|
if (max === null || max === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = usage.get(requirement.id) ?? 0;
|
||||||
|
return current < max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private incrementRequirementUsage(usage: Map<string, number>, id: string) {
|
||||||
|
usage.set(id, (usage.get(id) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildComponentName(
|
||||||
|
subcomponent: ComponentModelStructure['subcomponents'][number],
|
||||||
|
typeComposant: ModelTypeWithSkeleton | null,
|
||||||
|
parentName?: string,
|
||||||
|
): string {
|
||||||
|
const alias = this.normalizeIdentifier((subcomponent as { alias?: string }).alias);
|
||||||
|
if (alias) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeComposant?.name) {
|
||||||
|
return typeComposant.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentName) {
|
||||||
|
return `${parentName} - Sous-composant`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Sous-composant';
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildPieceName(
|
||||||
|
piece: ComponentModelStructure['pieces'][number],
|
||||||
|
typePiece: PieceTypeWithSkeleton | null,
|
||||||
|
componentName?: string,
|
||||||
|
): string {
|
||||||
|
const role = this.normalizeIdentifier((piece as { role?: string }).role);
|
||||||
|
if (role) {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typePiece?.name) {
|
||||||
|
return typePiece.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (componentName) {
|
||||||
|
return `${componentName} - Pièce`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Pièce';
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeIdentifier(value: unknown): string | null {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeCode(value: unknown): string | null {
|
||||||
|
const identifier = this.normalizeIdentifier(value);
|
||||||
|
return identifier ? identifier.toLowerCase() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toCustomFieldValue(value: unknown): string {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
private async resolveMachineIdFromComposant(
|
private async resolveMachineIdFromComposant(
|
||||||
composantId: string,
|
composantId: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
@@ -197,155 +611,4 @@ export class ComposantsService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncComponentModelCustomFields(
|
|
||||||
component: ComposantWithRelations,
|
|
||||||
) {
|
|
||||||
const { composantModelId, typeComposantId } = component;
|
|
||||||
if (!composantModelId || !typeComposantId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = await this.prisma.composantModel.findUnique({
|
|
||||||
where: { id: composantModelId },
|
|
||||||
select: { structure: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!model?.structure) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.syncComponentStructureCustomFields(
|
|
||||||
model.structure,
|
|
||||||
typeComposantId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async syncComponentStructureCustomFields(
|
|
||||||
structure: any,
|
|
||||||
typeComposantId: string | null,
|
|
||||||
) {
|
|
||||||
if (typeComposantId) {
|
|
||||||
await this.ensureCustomFieldsForType(
|
|
||||||
'typeComposantId',
|
|
||||||
typeComposantId,
|
|
||||||
structure?.customFields,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pieces = Array.isArray(structure?.pieces) ? structure.pieces : [];
|
|
||||||
for (const piece of pieces) {
|
|
||||||
const typePieceId = this.extractTypePieceId(piece);
|
|
||||||
if (typePieceId) {
|
|
||||||
await this.ensureCustomFieldsForType(
|
|
||||||
'typePieceId',
|
|
||||||
typePieceId,
|
|
||||||
piece?.customFields,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawSubcomponents =
|
|
||||||
(structure as any)?.subcomponents ?? structure?.subComponents;
|
|
||||||
const subComponents = Array.isArray(rawSubcomponents)
|
|
||||||
? rawSubcomponents
|
|
||||||
: rawSubcomponents
|
|
||||||
? [rawSubcomponents]
|
|
||||||
: [];
|
|
||||||
for (const sub of subComponents) {
|
|
||||||
const subTypeId = this.extractTypeComposantId(sub);
|
|
||||||
if (!subTypeId) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await this.syncComponentStructureCustomFields(sub, subTypeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractTypePieceId(entry: any): string | null {
|
|
||||||
if (!entry || typeof entry !== 'object') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
entry.typePieceId ||
|
|
||||||
entry.typePiece?.id ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractTypeComposantId(entry: any): string | null {
|
|
||||||
if (!entry || typeof entry !== 'object') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
entry.typeComposantId ||
|
|
||||||
entry.typeComposant?.id ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ensureCustomFieldsForType(
|
|
||||||
typeKey: 'typeComposantId' | 'typePieceId',
|
|
||||||
typeId: string | null,
|
|
||||||
fields: any,
|
|
||||||
) {
|
|
||||||
if (!typeId || !Array.isArray(fields)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const field of fields) {
|
|
||||||
if (!field || typeof field !== 'object') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const name = typeof field.name === 'string' ? field.name.trim() : '';
|
|
||||||
if (!name) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const type = typeof field.type === 'string' && field.type.trim()
|
|
||||||
? field.type.trim()
|
|
||||||
: 'text';
|
|
||||||
const required = !!field.required;
|
|
||||||
const options = this.normalizeOptions(field);
|
|
||||||
|
|
||||||
const existing = await this.prisma.customField.findFirst({
|
|
||||||
where: {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
[typeKey]: typeId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
await this.prisma.customField.create({
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
required,
|
|
||||||
options,
|
|
||||||
[typeKey]: typeId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private normalizeOptions(field: any): string[] | undefined {
|
|
||||||
if (Array.isArray(field?.options)) {
|
|
||||||
const options = field.options
|
|
||||||
.map((option: any) =>
|
|
||||||
typeof option === 'string' ? option.trim() : '',
|
|
||||||
)
|
|
||||||
.filter((option: string) => option.length > 0);
|
|
||||||
return options.length ? options : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof field?.optionsText === 'string') {
|
|
||||||
const options = field.optionsText
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.map((option: string) => option.trim())
|
|
||||||
.filter((option: string) => option.length > 0);
|
|
||||||
return options.length ? options : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,24 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { MachinesController } from './machines.controller';
|
import { MachinesController } from './machines.controller';
|
||||||
import { MachinesService } from './machines.service';
|
import { MachinesService } from './machines.service';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { ComposantsService } from '../composants/composants.service';
|
||||||
|
import { PiecesService } from '../pieces/pieces.service';
|
||||||
|
|
||||||
describe('MachinesController', () => {
|
describe('MachinesController', () => {
|
||||||
let controller: MachinesController;
|
let controller: MachinesController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||||
|
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [MachinesController],
|
controllers: [MachinesController],
|
||||||
providers: [MachinesService, PrismaService],
|
providers: [
|
||||||
|
MachinesService,
|
||||||
|
PrismaService,
|
||||||
|
{ provide: ComposantsService, useValue: mockComposantsService },
|
||||||
|
{ provide: PiecesService, useValue: mockPiecesService },
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<MachinesController>(MachinesController);
|
controller = module.get<MachinesController>(MachinesController);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MachinesController } from './machines.controller';
|
import { MachinesController } from './machines.controller';
|
||||||
import { MachinesService } from './machines.service';
|
import { MachinesService } from './machines.service';
|
||||||
|
import { ComposantsService } from '../composants/composants.service';
|
||||||
|
import { PiecesService } from '../pieces/pieces.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [MachinesController],
|
controllers: [MachinesController],
|
||||||
providers: [MachinesService],
|
providers: [MachinesService, ComposantsService, PiecesService],
|
||||||
})
|
})
|
||||||
export class MachinesModule {}
|
export class MachinesModule {}
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { MachinesService } from './machines.service';
|
import { MachinesService } from './machines.service';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { ComposantsService } from '../composants/composants.service';
|
||||||
|
import { PiecesService } from '../pieces/pieces.service';
|
||||||
|
|
||||||
describe('MachinesService', () => {
|
describe('MachinesService', () => {
|
||||||
let service: MachinesService;
|
let service: MachinesService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||||
|
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [MachinesService, PrismaService],
|
providers: [
|
||||||
|
MachinesService,
|
||||||
|
PrismaService,
|
||||||
|
{ provide: ComposantsService, useValue: mockComposantsService },
|
||||||
|
{ provide: PiecesService, useValue: mockPiecesService },
|
||||||
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<MachinesService>(MachinesService);
|
service = module.get<MachinesService>(MachinesService);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,10 @@ describe('PiecesService', () => {
|
|||||||
prisma = {
|
prisma = {
|
||||||
piece: {
|
piece: {
|
||||||
create: jest.fn(),
|
create: jest.fn(),
|
||||||
|
findMany: jest.fn(),
|
||||||
|
findUnique: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
},
|
},
|
||||||
machine: {
|
machine: {
|
||||||
findUnique: jest.fn(),
|
findUnique: jest.fn(),
|
||||||
@@ -19,6 +23,14 @@ describe('PiecesService', () => {
|
|||||||
composant: {
|
composant: {
|
||||||
findUnique: jest.fn(),
|
findUnique: jest.fn(),
|
||||||
},
|
},
|
||||||
|
customField: {
|
||||||
|
findMany: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
|
customFieldValue: {
|
||||||
|
findMany: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@@ -43,18 +55,94 @@ describe('PiecesService', () => {
|
|||||||
prisma.machine.findUnique.mockResolvedValue({
|
prisma.machine.findUnique.mockResolvedValue({
|
||||||
id: 'machine-1',
|
id: 'machine-1',
|
||||||
typeMachine: {
|
typeMachine: {
|
||||||
pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }],
|
pieceRequirements: [
|
||||||
|
{
|
||||||
|
id: 'req-1',
|
||||||
|
typePieceId: 'type-piece-1',
|
||||||
|
typePiece: {
|
||||||
|
id: 'type-piece-1',
|
||||||
|
pieceSkeleton: {
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
name: 'Numéro de série',
|
||||||
|
value: 'AUTO',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const created = { id: 'piece-1' };
|
const created = {
|
||||||
|
id: 'piece-1',
|
||||||
|
typePieceId: 'type-piece-1',
|
||||||
|
typePiece: {
|
||||||
|
id: 'type-piece-1',
|
||||||
|
pieceSkeleton: {
|
||||||
|
customFields: [
|
||||||
|
{
|
||||||
|
name: 'Numéro de série',
|
||||||
|
value: 'AUTO',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
prisma.piece.create.mockResolvedValue(created);
|
prisma.piece.create.mockResolvedValue(created);
|
||||||
|
|
||||||
await expect(service.create(dto)).resolves.toEqual(created);
|
prisma.customField.findMany
|
||||||
expect(prisma.piece.create).toHaveBeenCalled();
|
.mockResolvedValueOnce([])
|
||||||
expect(prisma.piece.create.mock.calls[0][0].data.machineId).toBe(
|
.mockResolvedValueOnce([
|
||||||
'machine-1',
|
{ id: 'field-1', name: 'Numéro de série' },
|
||||||
);
|
]);
|
||||||
|
prisma.customField.create.mockResolvedValue({ id: 'field-1' });
|
||||||
|
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||||
|
prisma.customFieldValue.create.mockResolvedValue({
|
||||||
|
id: 'value-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalPiece = { ...created, customFieldValues: [] };
|
||||||
|
prisma.piece.findUnique.mockResolvedValue(finalPiece);
|
||||||
|
|
||||||
|
await expect(service.create(dto)).resolves.toEqual(finalPiece);
|
||||||
|
|
||||||
|
expect(prisma.piece.create).toHaveBeenCalledWith({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
machineId: 'machine-1',
|
||||||
|
typePieceId: 'type-piece-1',
|
||||||
|
}),
|
||||||
|
include: expect.any(Object),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.customField.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
name: 'Numéro de série',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
options: undefined,
|
||||||
|
typePieceId: 'type-piece-1',
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.customFieldValue.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
customFieldId: 'field-1',
|
||||||
|
pieceId: 'piece-1',
|
||||||
|
value: 'AUTO',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.piece.findUnique).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'piece-1' },
|
||||||
|
include: expect.any(Object),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should refuse creation when requirement does not belong to machine skeleton', async () => {
|
it('should refuse creation when requirement does not belong to machine skeleton', async () => {
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
|
||||||
|
import { PieceModelStructureSchema } from '../shared/schemas/inventory';
|
||||||
|
import type { PieceModelStructure } from '../shared/types/inventory';
|
||||||
|
|
||||||
const PIECE_WITH_RELATIONS_INCLUDE = {
|
const PIECE_WITH_RELATIONS_INCLUDE = {
|
||||||
machine: true,
|
machine: true,
|
||||||
composant: true,
|
composant: true,
|
||||||
typePiece: {
|
typePiece: {
|
||||||
include: {
|
include: {
|
||||||
customFields: true,
|
pieceCustomFields: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
documents: true,
|
documents: true,
|
||||||
constructeur: true,
|
constructeur: true,
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
typeMachinePieceRequirement: {
|
||||||
include: {
|
include: {
|
||||||
typePiece: {
|
typePiece: {
|
||||||
include: {
|
include: {
|
||||||
customFields: true,
|
pieceCustomFields: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -63,7 +65,11 @@ export class PiecesService {
|
|||||||
include: {
|
include: {
|
||||||
typeMachine: {
|
typeMachine: {
|
||||||
include: {
|
include: {
|
||||||
pieceRequirements: true,
|
pieceRequirements: {
|
||||||
|
include: {
|
||||||
|
typePiece: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -100,73 +106,35 @@ export class PiecesService {
|
|||||||
typePieceId: createPieceDto.typePieceId ?? requirement.typePieceId,
|
typePieceId: createPieceDto.typePieceId ?? requirement.typePieceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.prisma.piece.create({
|
const created = await this.prisma.piece.create({
|
||||||
data,
|
data,
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
});
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
await this.applyPieceSkeleton({
|
||||||
documents: true,
|
pieceId: created.id,
|
||||||
constructeur: true,
|
typePiece:
|
||||||
pieceModel: true,
|
(requirement.typePiece as PieceTypeWithSkeleton | null) ??
|
||||||
typeMachinePieceRequirement: {
|
(created.typePiece as PieceTypeWithSkeleton | null) ??
|
||||||
include: {
|
null,
|
||||||
typePiece: true,
|
});
|
||||||
},
|
|
||||||
},
|
return this.prisma.piece.findUnique({
|
||||||
customFieldValues: {
|
where: { id: created.id },
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
customField: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll() {
|
async findAll() {
|
||||||
return this.prisma.piece.findMany({
|
return this.prisma.piece.findMany({
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
|
||||||
documents: true,
|
|
||||||
constructeur: true,
|
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
|
||||||
include: {
|
|
||||||
typePiece: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne(id: string) {
|
async findOne(id: string) {
|
||||||
return this.prisma.piece.findUnique({
|
return this.prisma.piece.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
machine: true,
|
|
||||||
composant: true,
|
|
||||||
typePiece: true,
|
|
||||||
documents: true,
|
|
||||||
constructeur: true,
|
|
||||||
pieceModel: true,
|
|
||||||
typeMachinePieceRequirement: {
|
|
||||||
include: {
|
|
||||||
typePiece: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
customFieldValues: {
|
|
||||||
include: {
|
|
||||||
customField: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +188,15 @@ export class PiecesService {
|
|||||||
include: PIECE_WITH_RELATIONS_INCLUDE,
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.syncPieceModelCustomFields(updated);
|
await this.applyPieceSkeleton({
|
||||||
|
pieceId: updated.id,
|
||||||
|
typePiece: updated.typePiece as PieceTypeWithSkeleton | null,
|
||||||
|
});
|
||||||
|
|
||||||
return updated;
|
return this.prisma.piece.findUnique({
|
||||||
|
where: { id: updated.id },
|
||||||
|
include: PIECE_WITH_RELATIONS_INCLUDE,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string) {
|
async remove(id: string) {
|
||||||
@@ -231,136 +205,213 @@ export class PiecesService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncPieceModelCustomFields(piece: any) {
|
private async applyPieceSkeleton({
|
||||||
const pieceModelId = piece?.pieceModelId;
|
pieceId,
|
||||||
|
typePiece,
|
||||||
if (!pieceModelId) {
|
}: {
|
||||||
|
pieceId: string;
|
||||||
|
typePiece: PieceTypeWithSkeleton | null;
|
||||||
|
}) {
|
||||||
|
if (!typePiece?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = await this.prisma.pieceModel.findUnique({
|
const skeleton = this.parsePieceSkeleton(
|
||||||
where: { id: pieceModelId },
|
(typePiece as { pieceSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||||
select: { structure: true },
|
pieceSkeleton,
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!model?.structure) {
|
if (!skeleton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const structure = this.asRecord(model.structure);
|
const customFields = skeleton.customFields ?? [];
|
||||||
const customFields = this.extractCustomFields(structure);
|
|
||||||
|
|
||||||
const targetTypePieceId = this.getTypePieceIdForPiece(piece, structure);
|
await this.ensurePieceCustomFieldDefinitions(
|
||||||
if (!targetTypePieceId) {
|
typePiece.id,
|
||||||
return;
|
customFields,
|
||||||
}
|
);
|
||||||
|
|
||||||
await this.ensureCustomFieldsForType(
|
await this.createPieceCustomFieldValues(
|
||||||
targetTypePieceId,
|
pieceId,
|
||||||
|
typePiece.id,
|
||||||
customFields,
|
customFields,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ensureCustomFieldsForType(
|
private parsePieceSkeleton(value: unknown): PieceModelStructure | null {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return PieceModelStructureSchema.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensurePieceCustomFieldDefinitions(
|
||||||
typePieceId: string,
|
typePieceId: string,
|
||||||
fields: any,
|
customFields: PieceModelStructure['customFields'],
|
||||||
) {
|
) {
|
||||||
if (!typePieceId || !Array.isArray(fields)) {
|
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const field of fields) {
|
const existing = await this.prisma.customField.findMany({
|
||||||
if (!field || typeof field !== 'object') {
|
where: { typePieceId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingByName = new Map(
|
||||||
|
existing.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const field of customFields) {
|
||||||
|
if (!field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = typeof field.name === 'string' ? field.name.trim() : '';
|
const name = this.normalizeIdentifier(field.name);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = typeof field.type === 'string' && field.type.trim()
|
if (existingByName.has(name)) {
|
||||||
? field.type.trim()
|
continue;
|
||||||
: 'text';
|
}
|
||||||
const required = !!field.required;
|
|
||||||
|
const type = this.normalizeIdentifier(field.type) ?? 'text';
|
||||||
|
const required = Boolean(field.required);
|
||||||
const options = this.normalizeOptions(field);
|
const options = this.normalizeOptions(field);
|
||||||
|
|
||||||
const existing = await this.prisma.customField.findFirst({
|
const created = await this.prisma.customField.create({
|
||||||
where: {
|
data: {
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
required,
|
||||||
|
options,
|
||||||
typePieceId,
|
typePieceId,
|
||||||
},
|
},
|
||||||
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existing) {
|
existingByName.set(name, created.id);
|
||||||
await this.prisma.customField.create({
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
type,
|
|
||||||
required,
|
|
||||||
options,
|
|
||||||
typePieceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private normalizeOptions(field: any): string[] | undefined {
|
private async createPieceCustomFieldValues(
|
||||||
if (Array.isArray(field?.options)) {
|
pieceId: string,
|
||||||
const normalized = field.options
|
typePieceId: string,
|
||||||
.map((option: any) =>
|
customFields: PieceModelStructure['customFields'],
|
||||||
typeof option === 'string' ? option.trim() : '',
|
) {
|
||||||
)
|
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||||
.filter((option: string) => option.length > 0);
|
return;
|
||||||
|
|
||||||
return normalized.length ? normalized : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof field?.optionsText === 'string') {
|
const definitions = await this.prisma.customField.findMany({
|
||||||
const normalized = field.optionsText
|
where: { typePieceId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (definitions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitionMap = new Map(
|
||||||
|
definitions.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||||
|
where: { pieceId },
|
||||||
|
select: { customFieldId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||||
|
|
||||||
|
for (const field of customFields) {
|
||||||
|
if (!field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = this.normalizeIdentifier(field.name);
|
||||||
|
if (!name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const definitionId = definitionMap.get(name);
|
||||||
|
if (!definitionId || existingIds.has(definitionId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.customFieldValue.create({
|
||||||
|
data: {
|
||||||
|
customFieldId: definitionId,
|
||||||
|
pieceId,
|
||||||
|
value: this.toCustomFieldValue(field.value),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
existingIds.add(definitionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeOptions(
|
||||||
|
field: PieceCustomFieldEntry | undefined,
|
||||||
|
): string[] | undefined {
|
||||||
|
const rawOptions = field?.options;
|
||||||
|
if (Array.isArray(rawOptions)) {
|
||||||
|
const normalized = rawOptions
|
||||||
|
.map((option) =>
|
||||||
|
typeof option === 'string' ? option.trim() : '',
|
||||||
|
)
|
||||||
|
.filter((option) => option.length > 0);
|
||||||
|
|
||||||
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsTextValue =
|
||||||
|
field !== undefined
|
||||||
|
? (field as unknown as { optionsText?: unknown }).optionsText
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (typeof optionsTextValue === 'string') {
|
||||||
|
const normalized = optionsTextValue
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.map((option: string) => option.trim())
|
.map((option: string) => option.trim())
|
||||||
.filter((option: string) => option.length > 0);
|
.filter((option: string) => option.length > 0);
|
||||||
|
|
||||||
return normalized.length ? normalized : undefined;
|
return normalized.length > 0 ? normalized : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTypePieceIdForPiece(
|
private normalizeIdentifier(value: unknown): string | null {
|
||||||
piece: any,
|
if (typeof value !== 'string') {
|
||||||
modelStructure: Record<string, any> | null,
|
|
||||||
): string | null {
|
|
||||||
const structure = this.asRecord(modelStructure);
|
|
||||||
const structureTypePiece = this.asRecord(structure?.typePiece ?? null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
piece?.typePieceId ||
|
|
||||||
piece?.typePiece?.id ||
|
|
||||||
piece?.typeMachinePieceRequirement?.typePieceId ||
|
|
||||||
piece?.typeMachinePieceRequirement?.typePiece?.id ||
|
|
||||||
structure?.typePieceId ||
|
|
||||||
structureTypePiece?.id ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private asRecord(value: unknown): Record<string, any> | null {
|
|
||||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return value as Record<string, any>;
|
|
||||||
|
const trimmed = value.trim();
|
||||||
|
return trimmed.length > 0 ? trimmed : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractCustomFields(structure: Record<string, any> | null): any[] {
|
private toCustomFieldValue(value: unknown): string {
|
||||||
if (!structure) {
|
if (value === undefined || value === null) {
|
||||||
return [];
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { customFields } = structure;
|
return String(value);
|
||||||
return Array.isArray(customFields) ? customFields : [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PieceRequirementWithType = Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||||
|
include: { typePiece: true };
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
|
||||||
|
|
||||||
|
type PieceCustomFieldEntry = NonNullable<
|
||||||
|
PieceModelStructure['customFields']
|
||||||
|
>[number];
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ export class CreateComposantDto {
|
|||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
typeMachineComponentRequirementId: string;
|
typeMachineComponentRequirementId: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
composantModelId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateComposantDto {
|
export class UpdateComposantDto {
|
||||||
@@ -59,8 +55,4 @@ export class UpdateComposantDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
typeComposantId?: string;
|
typeComposantId?: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
composantModelId?: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class MachineComponentSelectionDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
componentModelId?: string;
|
typeComposantId?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
definition?: any;
|
definition?: any;
|
||||||
@@ -18,8 +18,12 @@ export class MachinePieceSelectionDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
requirementId: string;
|
requirementId: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
pieceModelId: string;
|
typePieceId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
definition?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateMachineDto {
|
export class CreateMachineDto {
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ export class CreatePieceDto {
|
|||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
typeMachinePieceRequirementId: string;
|
typeMachinePieceRequirementId: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
pieceModelId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdatePieceDto {
|
export class UpdatePieceDto {
|
||||||
@@ -59,8 +55,4 @@ export class UpdatePieceDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
typePieceId?: string;
|
typePieceId?: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
pieceModelId?: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { ValidateNested } from 'class-validator';
|
import { ValidateNested } from 'class-validator';
|
||||||
import type { ComponentModelStructure } from '../types/inventory';
|
import type {
|
||||||
|
ComponentModelStructure,
|
||||||
|
PieceModelStructure,
|
||||||
|
} from '../types/inventory';
|
||||||
|
|
||||||
export enum CustomFieldType {
|
export enum CustomFieldType {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
@@ -197,6 +200,10 @@ export class CreateTypeComposantDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
customFields?: CreateCustomFieldDto[];
|
customFields?: CreateCustomFieldDto[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
structure?: ComponentModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateTypeComposantDto {
|
export class UpdateTypeComposantDto {
|
||||||
@@ -211,6 +218,10 @@ export class UpdateTypeComposantDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
customFields?: CreateCustomFieldDto[];
|
customFields?: CreateCustomFieldDto[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
structure?: ComponentModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateTypePieceDto {
|
export class CreateTypePieceDto {
|
||||||
@@ -224,6 +235,10 @@ export class CreateTypePieceDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
customFields?: CreateCustomFieldDto[];
|
customFields?: CreateCustomFieldDto[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
structure?: PieceModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateTypePieceDto {
|
export class UpdateTypePieceDto {
|
||||||
@@ -238,68 +253,9 @@ export class UpdateTypePieceDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
customFields?: CreateCustomFieldDto[];
|
customFields?: CreateCustomFieldDto[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
structure?: PieceModelStructure;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateComposantModelDto {
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
typeComposantId: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
structure?: ComponentModelStructure;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateComposantModelDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
typeComposantId?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
structure?: ComponentModelStructure;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CreatePieceModelDto {
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
typePieceId: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
structure?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdatePieceModelDto {
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
typePieceId?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
structure?: any;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { normalizeComponentModelStructure } from '../../component-models/structure.normalizer';
|
import { normalizeComponentModelStructure } from '../../component-models/structure.normalizer';
|
||||||
import type { ComponentModelStructure } from '../types/inventory';
|
import type {
|
||||||
|
ComponentModelStructure,
|
||||||
|
PieceModelCustomField,
|
||||||
|
PieceModelStructure,
|
||||||
|
} from '../types/inventory';
|
||||||
|
|
||||||
export class ComponentModelStructureValidationError extends Error {
|
export class ComponentModelStructureValidationError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
@@ -150,3 +154,109 @@ export const ComponentModelStructureSchema = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class PieceModelStructureValidationError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'PieceModelStructureValidationError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStringOrNull(value: unknown): string | null {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = String(value).trim();
|
||||||
|
return trimmed ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePieceModelCustomFields(
|
||||||
|
customFields: unknown,
|
||||||
|
): PieceModelCustomField[] {
|
||||||
|
if (!Array.isArray(customFields)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized: PieceModelCustomField[] = [];
|
||||||
|
|
||||||
|
customFields.forEach((entry, index) => {
|
||||||
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = entry as Record<string, unknown>;
|
||||||
|
const rawName =
|
||||||
|
(typeof record.name === 'string' ? record.name : undefined) ??
|
||||||
|
(typeof record.key === 'string' ? record.key : undefined) ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
const name = rawName ? rawName.trim() : '';
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new PieceModelStructureValidationError(
|
||||||
|
`customFields[${index}].name doit être une chaîne non vide`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const field: PieceModelCustomField = { name };
|
||||||
|
|
||||||
|
if ('value' in record) {
|
||||||
|
field.value = record.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof record.type === 'string') {
|
||||||
|
field.type = record.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('required' in record) {
|
||||||
|
field.required = Boolean(record.required);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(record.options)) {
|
||||||
|
field.options = record.options;
|
||||||
|
} else if (typeof record.optionsText === 'string') {
|
||||||
|
const options = record.optionsText
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((option) => option.trim())
|
||||||
|
.filter((option) => option.length > 0);
|
||||||
|
if (options.length > 0) {
|
||||||
|
field.options = options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.push(field);
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PieceModelStructureSchema = {
|
||||||
|
parse(input: unknown): PieceModelStructure {
|
||||||
|
if (input === undefined || input === null) {
|
||||||
|
return { customFields: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof input !== 'object' || Array.isArray(input)) {
|
||||||
|
throw new PieceModelStructureValidationError(
|
||||||
|
'La structure de pièce doit être un objet JSON.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = input as Record<string, unknown>;
|
||||||
|
|
||||||
|
const structure: PieceModelStructure = { ...record };
|
||||||
|
const customFields = normalizePieceModelCustomFields(record.customFields);
|
||||||
|
if (customFields.length > 0 || 'customFields' in record) {
|
||||||
|
structure.customFields = customFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedTypePiece = toStringOrNull(record.typePieceId);
|
||||||
|
if (normalizedTypePiece) {
|
||||||
|
structure.typePieceId = normalizedTypePiece;
|
||||||
|
} else if ('typePieceId' in record) {
|
||||||
|
delete (structure as Record<string, unknown>).typePieceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return structure;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -39,3 +39,16 @@ export type ComponentModelStructure = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PieceModelCustomField = {
|
||||||
|
name: string;
|
||||||
|
value?: unknown;
|
||||||
|
type?: string;
|
||||||
|
required?: boolean;
|
||||||
|
options?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PieceModelStructure = {
|
||||||
|
customFields?: PieceModelCustomField[];
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { ComposantModelsRepository } from '../../common/repositories/composant-models.repository';
|
|
||||||
import type { Prisma } from '@prisma/client';
|
|
||||||
import {
|
|
||||||
CreateComposantModelDto,
|
|
||||||
UpdateComposantModelDto,
|
|
||||||
} from '../../shared/dto/type.dto';
|
|
||||||
import { ComponentModelStructureSchema } from '../../shared/schemas/inventory';
|
|
||||||
import type { ComponentModelStructure } from '../../shared/types/inventory';
|
|
||||||
|
|
||||||
const COMPOSANT_MODEL_INCLUDE = {
|
|
||||||
typeComposant: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ComposantModelService {
|
|
||||||
constructor(private readonly repository: ComposantModelsRepository) {}
|
|
||||||
|
|
||||||
async create(dto: CreateComposantModelDto) {
|
|
||||||
const { typeComposantId, structure, ...data } = dto;
|
|
||||||
const parsedStructure = this.parseStructure(structure);
|
|
||||||
|
|
||||||
const created = await this.repository.create(
|
|
||||||
{
|
|
||||||
...data,
|
|
||||||
structure: parsedStructure as Prisma.InputJsonValue,
|
|
||||||
typeComposant: { connect: { id: typeComposantId } },
|
|
||||||
},
|
|
||||||
COMPOSANT_MODEL_INCLUDE,
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.withParsedStructure(created);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(typeComposantId?: string) {
|
|
||||||
const models = await this.repository.findAll(
|
|
||||||
typeComposantId,
|
|
||||||
COMPOSANT_MODEL_INCLUDE,
|
|
||||||
);
|
|
||||||
|
|
||||||
return models.map((model) => this.mapStructure(model));
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(id: string) {
|
|
||||||
const model = await this.repository.findOne(id, COMPOSANT_MODEL_INCLUDE);
|
|
||||||
return this.withParsedStructure(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(id: string, dto: UpdateComposantModelDto) {
|
|
||||||
const { typeComposantId, structure, ...data } = dto;
|
|
||||||
|
|
||||||
const parsedStructure =
|
|
||||||
structure !== undefined ? this.parseStructure(structure) : undefined;
|
|
||||||
|
|
||||||
const updated = await this.repository.update(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
...data,
|
|
||||||
...(parsedStructure
|
|
||||||
? { structure: parsedStructure as Prisma.InputJsonValue }
|
|
||||||
: {}),
|
|
||||||
...(typeComposantId
|
|
||||||
? { typeComposant: { connect: { id: typeComposantId } } }
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
COMPOSANT_MODEL_INCLUDE,
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.withParsedStructure(updated);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(id: string) {
|
|
||||||
return this.repository.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseStructure(
|
|
||||||
structure: unknown | undefined,
|
|
||||||
): ComponentModelStructure {
|
|
||||||
return ComponentModelStructureSchema.parse(structure);
|
|
||||||
}
|
|
||||||
|
|
||||||
private mapStructure<T extends { structure?: unknown }>(
|
|
||||||
model: T,
|
|
||||||
): T & { structure: ComponentModelStructure } {
|
|
||||||
const structure = this.parseStructure((model as any).structure);
|
|
||||||
return {
|
|
||||||
...model,
|
|
||||||
structure,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private withParsedStructure<T extends { structure?: unknown }>(
|
|
||||||
model: T | null,
|
|
||||||
): (T & { structure: ComponentModelStructure }) | null {
|
|
||||||
return model ? this.mapStructure(model) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PieceModelsRepository } from '../../common/repositories/piece-models.repository';
|
|
||||||
import {
|
|
||||||
CreatePieceModelDto,
|
|
||||||
UpdatePieceModelDto,
|
|
||||||
} from '../../shared/dto/type.dto';
|
|
||||||
|
|
||||||
const PIECE_MODEL_INCLUDE = {
|
|
||||||
typePiece: true,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PieceModelService {
|
|
||||||
constructor(private readonly repository: PieceModelsRepository) {}
|
|
||||||
|
|
||||||
async create(dto: CreatePieceModelDto) {
|
|
||||||
const { typePieceId, ...data } = dto;
|
|
||||||
|
|
||||||
return this.repository.create(
|
|
||||||
{
|
|
||||||
...data,
|
|
||||||
typePiece: { connect: { id: typePieceId } },
|
|
||||||
},
|
|
||||||
PIECE_MODEL_INCLUDE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findAll(typePieceId?: string) {
|
|
||||||
return this.repository.findAll(typePieceId, PIECE_MODEL_INCLUDE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async findOne(id: string) {
|
|
||||||
return this.repository.findOne(id, PIECE_MODEL_INCLUDE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(id: string, dto: UpdatePieceModelDto) {
|
|
||||||
const { typePieceId, ...data } = dto;
|
|
||||||
|
|
||||||
return this.repository.update(
|
|
||||||
id,
|
|
||||||
{
|
|
||||||
...data,
|
|
||||||
...(typePieceId ? { typePiece: { connect: { id: typePieceId } } } : {}),
|
|
||||||
},
|
|
||||||
PIECE_MODEL_INCLUDE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove(id: string) {
|
|
||||||
return this.repository.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
CreateTypeComposantDto,
|
CreateTypeComposantDto,
|
||||||
UpdateTypeComposantDto,
|
UpdateTypeComposantDto,
|
||||||
} from '../../shared/dto/type.dto';
|
} from '../../shared/dto/type.dto';
|
||||||
|
import { ComponentModelStructureSchema } from '../../shared/schemas/inventory';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TypeComponentService {
|
export class TypeComponentService {
|
||||||
@@ -15,7 +16,11 @@ export class TypeComponentService {
|
|||||||
|
|
||||||
async create(dto: CreateTypeComposantDto) {
|
async create(dto: CreateTypeComposantDto) {
|
||||||
const code = await this.repository.generateUniqueCode(dto.name);
|
const code = await this.repository.generateUniqueCode(dto.name);
|
||||||
const data = ModelTypeMapper.toComponentCreateInput(dto, code);
|
const skeleton =
|
||||||
|
dto.structure !== undefined
|
||||||
|
? ComponentModelStructureSchema.parse(dto.structure)
|
||||||
|
: undefined;
|
||||||
|
const data = ModelTypeMapper.toComponentCreateInput(dto, code, skeleton);
|
||||||
|
|
||||||
return this.repository.createComponentType(data, COMPONENT_TYPE_INCLUDE);
|
return this.repository.createComponentType(data, COMPONENT_TYPE_INCLUDE);
|
||||||
}
|
}
|
||||||
@@ -37,7 +42,11 @@ export class TypeComponentService {
|
|||||||
await this.repository.createComponentTypeCustomFields(id, fields);
|
await this.repository.createComponentTypeCustomFields(id, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = ModelTypeMapper.toComponentUpdateInput(dto);
|
const skeleton =
|
||||||
|
dto.structure !== undefined
|
||||||
|
? ComponentModelStructureSchema.parse(dto.structure)
|
||||||
|
: undefined;
|
||||||
|
const data = ModelTypeMapper.toComponentUpdateInput(dto, skeleton);
|
||||||
return this.repository.updateComponentType(
|
return this.repository.updateComponentType(
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
CreateTypePieceDto,
|
CreateTypePieceDto,
|
||||||
UpdateTypePieceDto,
|
UpdateTypePieceDto,
|
||||||
} from '../../shared/dto/type.dto';
|
} from '../../shared/dto/type.dto';
|
||||||
|
import { PieceModelStructureSchema } from '../../shared/schemas/inventory';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TypePieceService {
|
export class TypePieceService {
|
||||||
@@ -15,7 +16,11 @@ export class TypePieceService {
|
|||||||
|
|
||||||
async create(dto: CreateTypePieceDto) {
|
async create(dto: CreateTypePieceDto) {
|
||||||
const code = await this.repository.generateUniqueCode(dto.name);
|
const code = await this.repository.generateUniqueCode(dto.name);
|
||||||
const data = ModelTypeMapper.toPieceCreateInput(dto, code);
|
const skeleton =
|
||||||
|
dto.structure !== undefined
|
||||||
|
? PieceModelStructureSchema.parse(dto.structure)
|
||||||
|
: undefined;
|
||||||
|
const data = ModelTypeMapper.toPieceCreateInput(dto, code, skeleton);
|
||||||
|
|
||||||
const created = await this.repository.createPieceType(
|
const created = await this.repository.createPieceType(
|
||||||
data,
|
data,
|
||||||
@@ -43,7 +48,11 @@ export class TypePieceService {
|
|||||||
await this.repository.createPieceTypeCustomFields(id, fields);
|
await this.repository.createPieceTypeCustomFields(id, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = ModelTypeMapper.toPieceUpdateInput(dto);
|
const skeleton =
|
||||||
|
dto.structure !== undefined
|
||||||
|
? PieceModelStructureSchema.parse(dto.structure)
|
||||||
|
: undefined;
|
||||||
|
const data = ModelTypeMapper.toPieceUpdateInput(dto, skeleton);
|
||||||
const updated = await this.repository.updatePieceType(
|
const updated = await this.repository.updatePieceType(
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { TypeMachineService } from './services/type-machine.service';
|
import { TypeMachineService } from './services/type-machine.service';
|
||||||
import { TypeComponentService } from './services/type-component.service';
|
import { TypeComponentService } from './services/type-component.service';
|
||||||
import { TypePieceService } from './services/type-piece.service';
|
import { TypePieceService } from './services/type-piece.service';
|
||||||
import { ComposantModelService } from './services/composant-model.service';
|
|
||||||
import { PieceModelService } from './services/piece-model.service';
|
|
||||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
|
||||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
|
||||||
|
|
||||||
describe('TypesController', () => {
|
describe('TypesController', () => {
|
||||||
let controller: TypesController;
|
let controller: TypesController;
|
||||||
@@ -23,12 +19,8 @@ describe('TypesController', () => {
|
|||||||
TypeMachineService,
|
TypeMachineService,
|
||||||
TypeComponentService,
|
TypeComponentService,
|
||||||
TypePieceService,
|
TypePieceService,
|
||||||
ComposantModelService,
|
|
||||||
PieceModelService,
|
|
||||||
TypeMachinesRepository,
|
TypeMachinesRepository,
|
||||||
ModelTypesRepository,
|
ModelTypesRepository,
|
||||||
ComposantModelsRepository,
|
|
||||||
PieceModelsRepository,
|
|
||||||
PrismaService,
|
PrismaService,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
import {
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Body,
|
|
||||||
Patch,
|
|
||||||
Param,
|
|
||||||
Delete,
|
|
||||||
Query,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { TypesService } from './types.service';
|
import { TypesService } from './types.service';
|
||||||
import {
|
import {
|
||||||
CreateTypeMachineDto,
|
CreateTypeMachineDto,
|
||||||
@@ -16,10 +7,6 @@ import {
|
|||||||
UpdateTypeComposantDto,
|
UpdateTypeComposantDto,
|
||||||
CreateTypePieceDto,
|
CreateTypePieceDto,
|
||||||
UpdateTypePieceDto,
|
UpdateTypePieceDto,
|
||||||
CreateComposantModelDto,
|
|
||||||
UpdateComposantModelDto,
|
|
||||||
CreatePieceModelDto,
|
|
||||||
UpdatePieceModelDto,
|
|
||||||
} from '../shared/dto/type.dto';
|
} from '../shared/dto/type.dto';
|
||||||
|
|
||||||
@Controller('types')
|
@Controller('types')
|
||||||
@@ -66,37 +53,6 @@ export class TypesController {
|
|||||||
return this.typesService.findAllTypeComposants();
|
return this.typesService.findAllTypeComposants();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposantModel routes
|
|
||||||
@Post('composants/models')
|
|
||||||
createComposantModel(
|
|
||||||
@Body() createComposantModelDto: CreateComposantModelDto,
|
|
||||||
) {
|
|
||||||
return this.typesService.createComposantModel(createComposantModelDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('composants/models')
|
|
||||||
findAllComposantModels(@Query('typeComposantId') typeComposantId?: string) {
|
|
||||||
return this.typesService.findAllComposantModels(typeComposantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('composants/models/:id')
|
|
||||||
findOneComposantModel(@Param('id') id: string) {
|
|
||||||
return this.typesService.findOneComposantModel(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch('composants/models/:id')
|
|
||||||
updateComposantModel(
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() updateComposantModelDto: UpdateComposantModelDto,
|
|
||||||
) {
|
|
||||||
return this.typesService.updateComposantModel(id, updateComposantModelDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('composants/models/:id')
|
|
||||||
removeComposantModel(@Param('id') id: string) {
|
|
||||||
return this.typesService.removeComposantModel(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('composants/:id')
|
@Get('composants/:id')
|
||||||
findOneTypeComposant(@Param('id') id: string) {
|
findOneTypeComposant(@Param('id') id: string) {
|
||||||
return this.typesService.findOneTypeComposant(id);
|
return this.typesService.findOneTypeComposant(id);
|
||||||
@@ -126,35 +82,6 @@ export class TypesController {
|
|||||||
return this.typesService.findAllTypePieces();
|
return this.typesService.findAllTypePieces();
|
||||||
}
|
}
|
||||||
|
|
||||||
// PieceModel routes
|
|
||||||
@Post('pieces/models')
|
|
||||||
createPieceModel(@Body() createPieceModelDto: CreatePieceModelDto) {
|
|
||||||
return this.typesService.createPieceModel(createPieceModelDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('pieces/models')
|
|
||||||
findAllPieceModels(@Query('typePieceId') typePieceId?: string) {
|
|
||||||
return this.typesService.findAllPieceModels(typePieceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('pieces/models/:id')
|
|
||||||
findOnePieceModel(@Param('id') id: string) {
|
|
||||||
return this.typesService.findOnePieceModel(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Patch('pieces/models/:id')
|
|
||||||
updatePieceModel(
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() updatePieceModelDto: UpdatePieceModelDto,
|
|
||||||
) {
|
|
||||||
return this.typesService.updatePieceModel(id, updatePieceModelDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('pieces/models/:id')
|
|
||||||
removePieceModel(@Param('id') id: string) {
|
|
||||||
return this.typesService.removePieceModel(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('pieces/:id')
|
@Get('pieces/:id')
|
||||||
findOneTypePiece(@Param('id') id: string) {
|
findOneTypePiece(@Param('id') id: string) {
|
||||||
return this.typesService.findOneTypePiece(id);
|
return this.typesService.findOneTypePiece(id);
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
|
||||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
|
||||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||||
import { TypesController } from './types.controller';
|
import { TypesController } from './types.controller';
|
||||||
import { TypesService } from './types.service';
|
import { TypesService } from './types.service';
|
||||||
import { ComposantModelService } from './services/composant-model.service';
|
|
||||||
import { PieceModelService } from './services/piece-model.service';
|
|
||||||
import { TypeComponentService } from './services/type-component.service';
|
import { TypeComponentService } from './services/type-component.service';
|
||||||
import { TypeMachineService } from './services/type-machine.service';
|
import { TypeMachineService } from './services/type-machine.service';
|
||||||
import { TypePieceService } from './services/type-piece.service';
|
import { TypePieceService } from './services/type-piece.service';
|
||||||
@@ -18,12 +14,8 @@ import { TypePieceService } from './services/type-piece.service';
|
|||||||
TypeMachineService,
|
TypeMachineService,
|
||||||
TypeComponentService,
|
TypeComponentService,
|
||||||
TypePieceService,
|
TypePieceService,
|
||||||
ComposantModelService,
|
|
||||||
PieceModelService,
|
|
||||||
TypeMachinesRepository,
|
TypeMachinesRepository,
|
||||||
ModelTypesRepository,
|
ModelTypesRepository,
|
||||||
ComposantModelsRepository,
|
|
||||||
PieceModelsRepository,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class TypesModule {}
|
export class TypesModule {}
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { TypeMachineService } from './services/type-machine.service';
|
import { TypeMachineService } from './services/type-machine.service';
|
||||||
import { TypeComponentService } from './services/type-component.service';
|
import { TypeComponentService } from './services/type-component.service';
|
||||||
import { TypePieceService } from './services/type-piece.service';
|
import { TypePieceService } from './services/type-piece.service';
|
||||||
import { ComposantModelService } from './services/composant-model.service';
|
|
||||||
import { PieceModelService } from './services/piece-model.service';
|
|
||||||
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
import { TypeMachinesRepository } from '../common/repositories/type-machines.repository';
|
||||||
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
import { ModelTypesRepository } from '../common/repositories/model-types.repository';
|
||||||
import { ComposantModelsRepository } from '../common/repositories/composant-models.repository';
|
|
||||||
import { PieceModelsRepository } from '../common/repositories/piece-models.repository';
|
|
||||||
|
|
||||||
describe('TypesService', () => {
|
describe('TypesService', () => {
|
||||||
let service: TypesService;
|
let service: TypesService;
|
||||||
@@ -21,12 +17,8 @@ describe('TypesService', () => {
|
|||||||
TypeMachineService,
|
TypeMachineService,
|
||||||
TypeComponentService,
|
TypeComponentService,
|
||||||
TypePieceService,
|
TypePieceService,
|
||||||
ComposantModelService,
|
|
||||||
PieceModelService,
|
|
||||||
TypeMachinesRepository,
|
TypeMachinesRepository,
|
||||||
ModelTypesRepository,
|
ModelTypesRepository,
|
||||||
ComposantModelsRepository,
|
|
||||||
PieceModelsRepository,
|
|
||||||
PrismaService,
|
PrismaService,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ComposantModelService } from './services/composant-model.service';
|
|
||||||
import { PieceModelService } from './services/piece-model.service';
|
|
||||||
import { TypeComponentService } from './services/type-component.service';
|
import { TypeComponentService } from './services/type-component.service';
|
||||||
import { TypeMachineService } from './services/type-machine.service';
|
import { TypeMachineService } from './services/type-machine.service';
|
||||||
import { TypePieceService } from './services/type-piece.service';
|
import { TypePieceService } from './services/type-piece.service';
|
||||||
@@ -11,10 +9,6 @@ import {
|
|||||||
UpdateTypeComposantDto,
|
UpdateTypeComposantDto,
|
||||||
CreateTypePieceDto,
|
CreateTypePieceDto,
|
||||||
UpdateTypePieceDto,
|
UpdateTypePieceDto,
|
||||||
CreateComposantModelDto,
|
|
||||||
UpdateComposantModelDto,
|
|
||||||
CreatePieceModelDto,
|
|
||||||
UpdatePieceModelDto,
|
|
||||||
} from '../shared/dto/type.dto';
|
} from '../shared/dto/type.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -23,8 +17,6 @@ export class TypesService {
|
|||||||
private readonly typeMachineService: TypeMachineService,
|
private readonly typeMachineService: TypeMachineService,
|
||||||
private readonly typeComponentService: TypeComponentService,
|
private readonly typeComponentService: TypeComponentService,
|
||||||
private readonly typePieceService: TypePieceService,
|
private readonly typePieceService: TypePieceService,
|
||||||
private readonly composantModelService: ComposantModelService,
|
|
||||||
private readonly pieceModelService: PieceModelService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// TypeMachine
|
// TypeMachine
|
||||||
@@ -89,46 +81,4 @@ export class TypesService {
|
|||||||
removeTypePiece(id: string) {
|
removeTypePiece(id: string) {
|
||||||
return this.typePieceService.remove(id);
|
return this.typePieceService.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ComposantModel
|
|
||||||
createComposantModel(dto: CreateComposantModelDto) {
|
|
||||||
return this.composantModelService.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
findAllComposantModels(typeComposantId?: string) {
|
|
||||||
return this.composantModelService.findAll(typeComposantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
findOneComposantModel(id: string) {
|
|
||||||
return this.composantModelService.findOne(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateComposantModel(id: string, dto: UpdateComposantModelDto) {
|
|
||||||
return this.composantModelService.update(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeComposantModel(id: string) {
|
|
||||||
return this.composantModelService.remove(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// PieceModel
|
|
||||||
createPieceModel(dto: CreatePieceModelDto) {
|
|
||||||
return this.pieceModelService.create(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
findAllPieceModels(typePieceId?: string) {
|
|
||||||
return this.pieceModelService.findAll(typePieceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
findOnePieceModel(id: string) {
|
|
||||||
return this.pieceModelService.findOne(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePieceModel(id: string, dto: UpdatePieceModelDto) {
|
|
||||||
return this.pieceModelService.update(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
removePieceModel(id: string) {
|
|
||||||
return this.pieceModelService.remove(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ type ComposantRecord = {
|
|||||||
machineId: Nullable<string>;
|
machineId: Nullable<string>;
|
||||||
parentComposantId: Nullable<string>;
|
parentComposantId: Nullable<string>;
|
||||||
typeComposantId: Nullable<string>;
|
typeComposantId: Nullable<string>;
|
||||||
composantModelId: Nullable<string>;
|
|
||||||
typeMachineComponentRequirementId: Nullable<string>;
|
typeMachineComponentRequirementId: Nullable<string>;
|
||||||
constructeurId: Nullable<string>;
|
constructeurId: Nullable<string>;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -104,7 +103,6 @@ type PieceRecord = {
|
|||||||
machineId: Nullable<string>;
|
machineId: Nullable<string>;
|
||||||
composantId: Nullable<string>;
|
composantId: Nullable<string>;
|
||||||
typePieceId: Nullable<string>;
|
typePieceId: Nullable<string>;
|
||||||
pieceModelId: Nullable<string>;
|
|
||||||
typeMachinePieceRequirementId: Nullable<string>;
|
typeMachinePieceRequirementId: Nullable<string>;
|
||||||
constructeurId: Nullable<string>;
|
constructeurId: Nullable<string>;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -663,7 +661,6 @@ class InMemoryPrismaService {
|
|||||||
machineId: data.machineId ?? null,
|
machineId: data.machineId ?? null,
|
||||||
parentComposantId: data.parentComposantId ?? null,
|
parentComposantId: data.parentComposantId ?? null,
|
||||||
typeComposantId: data.typeComposantId ?? null,
|
typeComposantId: data.typeComposantId ?? null,
|
||||||
composantModelId: data.composantModelId ?? null,
|
|
||||||
typeMachineComponentRequirementId:
|
typeMachineComponentRequirementId:
|
||||||
data.typeMachineComponentRequirementId ?? null,
|
data.typeMachineComponentRequirementId ?? null,
|
||||||
constructeurId: data.constructeurId ?? null,
|
constructeurId: data.constructeurId ?? null,
|
||||||
@@ -700,7 +697,6 @@ class InMemoryPrismaService {
|
|||||||
machineId: data.machineId ?? null,
|
machineId: data.machineId ?? null,
|
||||||
composantId: data.composantId ?? null,
|
composantId: data.composantId ?? null,
|
||||||
typePieceId: data.typePieceId ?? null,
|
typePieceId: data.typePieceId ?? null,
|
||||||
pieceModelId: data.pieceModelId ?? null,
|
|
||||||
typeMachinePieceRequirementId:
|
typeMachinePieceRequirementId:
|
||||||
data.typeMachinePieceRequirementId ?? null,
|
data.typeMachinePieceRequirementId ?? null,
|
||||||
constructeurId: data.constructeurId ?? null,
|
constructeurId: data.constructeurId ?? null,
|
||||||
@@ -1076,14 +1072,6 @@ class InMemoryPrismaService {
|
|||||||
.map((item) => ({ ...item }));
|
.map((item) => ({ ...item }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include?.models) {
|
|
||||||
base.models = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (include?.pieceModels) {
|
|
||||||
base.pieceModels = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (include?.pieceRequirements) {
|
if (include?.pieceRequirements) {
|
||||||
base.pieceRequirements = [];
|
base.pieceRequirements = [];
|
||||||
}
|
}
|
||||||
@@ -1243,10 +1231,6 @@ class InMemoryPrismaService {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include?.composantModel) {
|
|
||||||
base.composantModel = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (include?.typeMachineComponentRequirement) {
|
if (include?.typeMachineComponentRequirement) {
|
||||||
const requirement = component.typeMachineComponentRequirementId
|
const requirement = component.typeMachineComponentRequirementId
|
||||||
? (this.typeMachineComponentRequirements.find(
|
? (this.typeMachineComponentRequirements.find(
|
||||||
@@ -1322,10 +1306,6 @@ class InMemoryPrismaService {
|
|||||||
base.constructeur = null;
|
base.constructeur = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include?.pieceModel) {
|
|
||||||
base.pieceModel = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (include?.typeMachinePieceRequirement) {
|
if (include?.typeMachinePieceRequirement) {
|
||||||
const requirement = piece.typeMachinePieceRequirementId
|
const requirement = piece.typeMachinePieceRequirementId
|
||||||
? (this.typeMachinePieceRequirements.find(
|
? (this.typeMachinePieceRequirements.find(
|
||||||
|
|||||||
Reference in New Issue
Block a user