diff --git a/.gitignore b/.gitignore index 9f62ec0..8acffb4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules .env /generated/prisma +dist \ No newline at end of file diff --git a/env.example b/env.example index 18e3569..5e1cddc 100644 --- a/env.example +++ b/env.example @@ -20,7 +20,8 @@ REQUEST_SIZE_LIMIT=10mb SESSION_COOKIE_SECURE=true # Configuration de l'API -API_PREFIX=api +# API_PREFIX est désormais vide car les routes sont exposées à la racine +API_PREFIX= API_VERSION=v1 # Configuration des logs diff --git a/prisma/migrations/20250921105404_remove_legacy_type_json/migration.sql b/prisma/migrations/20250921105404_remove_legacy_type_json/migration.sql new file mode 100644 index 0000000..d1503e8 --- /dev/null +++ b/prisma/migrations/20250921105404_remove_legacy_type_json/migration.sql @@ -0,0 +1,112 @@ +/* + Warnings: + + - A unique constraint covering the columns `[name]` on the table `constructeurs` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "composants" ADD COLUMN "composantModelId" TEXT, +ADD COLUMN "typeMachineComponentRequirementId" TEXT; + +-- AlterTable +ALTER TABLE "constructeurs" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMP(3), +ALTER COLUMN "updatedAt" DROP DEFAULT, +ALTER COLUMN "updatedAt" SET DATA TYPE TIMESTAMP(3); + +-- AlterTable +ALTER TABLE "pieces" ADD COLUMN "pieceModelId" TEXT, +ADD COLUMN "typeMachinePieceRequirementId" TEXT; + +-- AlterTable +ALTER TABLE "profiles" ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- CreateTable +CREATE TABLE "composant_models" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "structure" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "typeComposantId" TEXT NOT NULL, + + CONSTRAINT "composant_models_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "piece_models" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "structure" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "typePieceId" TEXT NOT NULL, + + CONSTRAINT "piece_models_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "type_machine_component_requirements" ( + "id" TEXT NOT NULL, + "label" TEXT, + "minCount" INTEGER NOT NULL DEFAULT 1, + "maxCount" INTEGER, + "required" BOOLEAN NOT NULL DEFAULT true, + "allowNewModels" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "typeMachineId" TEXT NOT NULL, + "typeComposantId" TEXT NOT NULL, + + CONSTRAINT "type_machine_component_requirements_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "type_machine_piece_requirements" ( + "id" TEXT NOT NULL, + "label" TEXT, + "minCount" INTEGER NOT NULL DEFAULT 0, + "maxCount" INTEGER, + "required" BOOLEAN NOT NULL DEFAULT false, + "allowNewModels" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "typeMachineId" TEXT NOT NULL, + "typePieceId" TEXT NOT NULL, + + CONSTRAINT "type_machine_piece_requirements_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "constructeurs_name_key" ON "constructeurs"("name"); + +-- AddForeignKey +ALTER TABLE "composants" ADD CONSTRAINT "composants_composantModelId_fkey" FOREIGN KEY ("composantModelId") REFERENCES "composant_models"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "composants" ADD CONSTRAINT "composants_typeMachineComponentRequirementId_fkey" FOREIGN KEY ("typeMachineComponentRequirementId") REFERENCES "type_machine_component_requirements"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "pieces" ADD CONSTRAINT "pieces_pieceModelId_fkey" FOREIGN KEY ("pieceModelId") REFERENCES "piece_models"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "pieces" ADD CONSTRAINT "pieces_typeMachinePieceRequirementId_fkey" FOREIGN KEY ("typeMachinePieceRequirementId") REFERENCES "type_machine_piece_requirements"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "composant_models" ADD CONSTRAINT "composant_models_typeComposantId_fkey" FOREIGN KEY ("typeComposantId") REFERENCES "type_composants"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "piece_models" ADD CONSTRAINT "piece_models_typePieceId_fkey" FOREIGN KEY ("typePieceId") REFERENCES "type_pieces"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "type_machine_component_requirements" ADD CONSTRAINT "type_machine_component_requirements_typeMachineId_fkey" FOREIGN KEY ("typeMachineId") REFERENCES "type_machines"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "type_machine_component_requirements" ADD CONSTRAINT "type_machine_component_requirements_typeComposantId_fkey" FOREIGN KEY ("typeComposantId") REFERENCES "type_composants"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "type_machine_piece_requirements" ADD CONSTRAINT "type_machine_piece_requirements_typeMachineId_fkey" FOREIGN KEY ("typeMachineId") REFERENCES "type_machines"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "type_machine_piece_requirements" ADD CONSTRAINT "type_machine_piece_requirements_typePieceId_fkey" FOREIGN KEY ("typePieceId") REFERENCES "type_pieces"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a3b8e36..eea8e52 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,39 +12,41 @@ datasource db { // Entités principales model Site { - id String @id @default(cuid()) - name String + id String @id @default(cuid()) + name String contactName String @default("") contactPhone String @default("") contactAddress String @default("") contactPostalCode String @default("") contactCity String @default("") - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations - machines Machine[] + machines Machine[] documents Document[] @relation("SiteDocuments") @@map("sites") } model TypeMachine { - id String @id @default(cuid()) - name String @unique - description String? - category String? + id String @id @default(cuid()) + name String @unique + description String? + category String? maintenanceFrequency String? - components Json? // Stockage de la structure hiérarchique des composants - criticalParts Json? // Stockage des pièces critiques - machinePieces Json? // Stockage des pièces de machine - specifications Json? // Stockage des spécifications techniques - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + components Json? // Stockage de la structure hiérarchique des composants + criticalParts Json? // Stockage des pièces critiques + machinePieces Json? // Stockage des pièces de machine + specifications Json? // Stockage des spécifications techniques + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations - machines Machine[] - customFields CustomField[] @relation("TypeMachineCustomFields") + machines Machine[] + customFields CustomField[] @relation("TypeMachineCustomFields") + componentRequirements TypeMachineComponentRequirement[] + pieceRequirements TypeMachinePieceRequirement[] @@map("type_machines") } @@ -57,8 +59,10 @@ model TypeComposant { updatedAt DateTime @updatedAt // Relations - composants Composant[] - customFields CustomField[] @relation("TypeComposantCustomFields") + composants Composant[] + customFields CustomField[] @relation("TypeComposantCustomFields") + models ComposantModel[] + componentRequirements TypeMachineComponentRequirement[] @@map("type_composants") } @@ -71,8 +75,10 @@ model TypePiece { updatedAt DateTime @updatedAt // Relations - pieces Piece[] - customFields CustomField[] @relation("TypePieceCustomFields") + pieces Piece[] + customFields CustomField[] @relation("TypePieceCustomFields") + models PieceModel[] + pieceRequirements TypeMachinePieceRequirement[] @@map("type_pieces") } @@ -87,18 +93,18 @@ model Machine { updatedAt DateTime @updatedAt // Relations - siteId String - site Site @relation(fields: [siteId], references: [id], onDelete: Cascade) - + siteId String + site Site @relation(fields: [siteId], references: [id], onDelete: Cascade) + typeMachineId String? typeMachine TypeMachine? @relation(fields: [typeMachineId], references: [id]) - + constructeurId String? constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) - - composants Composant[] - pieces Piece[] - documents Document[] @relation("MachineDocuments") + + composants Composant[] + pieces Piece[] + documents Document[] @relation("MachineDocuments") customFieldValues CustomFieldValue[] @relation("MachineCustomFieldValues") @@map("machines") @@ -114,21 +120,27 @@ model Composant { updatedAt DateTime @updatedAt // Relations hiérarchiques - machineId String? - machine Machine? @relation(fields: [machineId], references: [id], onDelete: Cascade) - + machineId String? + machine Machine? @relation(fields: [machineId], references: [id], onDelete: Cascade) + parentComposantId String? - parentComposant Composant? @relation("ComposantHierarchy", fields: [parentComposantId], references: [id], onDelete: Cascade) + parentComposant Composant? @relation("ComposantHierarchy", fields: [parentComposantId], references: [id], onDelete: Cascade) sousComposants Composant[] @relation("ComposantHierarchy") - + typeComposantId String? typeComposant TypeComposant? @relation(fields: [typeComposantId], references: [id]) - + + composantModelId String? + composantModel ComposantModel? @relation(fields: [composantModelId], references: [id], onDelete: SetNull) + + typeMachineComponentRequirementId String? + typeMachineComponentRequirement TypeMachineComponentRequirement? @relation(fields: [typeMachineComponentRequirementId], references: [id], onDelete: SetNull) + constructeurId String? constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) - - pieces Piece[] - documents Document[] @relation("ComposantDocuments") + + pieces Piece[] + documents Document[] @relation("ComposantDocuments") customFieldValues CustomFieldValue[] @relation("ComposantCustomFieldValues") @@map("composants") @@ -144,19 +156,25 @@ model Piece { updatedAt DateTime @updatedAt // Relations - machineId String? - machine Machine? @relation(fields: [machineId], references: [id], onDelete: Cascade) - + machineId String? + machine Machine? @relation(fields: [machineId], references: [id], onDelete: Cascade) + composantId String? - composant Composant? @relation(fields: [composantId], references: [id], onDelete: Cascade) - + composant Composant? @relation(fields: [composantId], references: [id], onDelete: Cascade) + typePieceId String? - typePiece TypePiece? @relation(fields: [typePieceId], references: [id]) - + typePiece TypePiece? @relation(fields: [typePieceId], references: [id]) + + pieceModelId String? + pieceModel PieceModel? @relation(fields: [pieceModelId], references: [id], onDelete: SetNull) + + typeMachinePieceRequirementId String? + typeMachinePieceRequirement TypeMachinePieceRequirement? @relation(fields: [typeMachinePieceRequirementId], references: [id], onDelete: SetNull) + constructeurId String? constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) - - documents Document[] @relation("PieceDocuments") + + documents Document[] @relation("PieceDocuments") customFieldValues CustomFieldValue[] @relation("PieceCustomFieldValues") @@map("pieces") @@ -189,50 +207,50 @@ model Profile { } model Document { - id String @id @default(cuid()) - name String - filename String - path String - mimeType String - size Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + name String + filename String + path String + mimeType String + size Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations polymorphiques - machineId String? - machine Machine? @relation("MachineDocuments", fields: [machineId], references: [id], onDelete: Cascade) - - composantId String? - composant Composant? @relation("ComposantDocuments", fields: [composantId], references: [id], onDelete: Cascade) - - pieceId String? - piece Piece? @relation("PieceDocuments", fields: [pieceId], references: [id], onDelete: Cascade) + machineId String? + machine Machine? @relation("MachineDocuments", fields: [machineId], references: [id], onDelete: Cascade) - siteId String? - site Site? @relation("SiteDocuments", fields: [siteId], references: [id], onDelete: Cascade) + composantId String? + composant Composant? @relation("ComposantDocuments", fields: [composantId], references: [id], onDelete: Cascade) + + pieceId String? + piece Piece? @relation("PieceDocuments", fields: [pieceId], references: [id], onDelete: Cascade) + + siteId String? + site Site? @relation("SiteDocuments", fields: [siteId], references: [id], onDelete: Cascade) @@map("documents") } model CustomField { - id String @id @default(cuid()) - name String - type String // 'string', 'number', 'boolean', 'date' - required Boolean @default(false) + id String @id @default(cuid()) + name String + type String // 'string', 'number', 'boolean', 'date' + required Boolean @default(false) defaultValue String? - options String[] // Pour les champs de type SELECT - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + options String[] // Pour les champs de type SELECT + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations polymorphiques pour les types - typeMachineId String? - typeMachine TypeMachine? @relation("TypeMachineCustomFields", fields: [typeMachineId], references: [id], onDelete: Cascade) - + typeMachineId String? + typeMachine TypeMachine? @relation("TypeMachineCustomFields", fields: [typeMachineId], references: [id], onDelete: Cascade) + typeComposantId String? typeComposant TypeComposant? @relation("TypeComposantCustomFields", fields: [typeComposantId], references: [id], onDelete: Cascade) - - typePieceId String? - typePiece TypePiece? @relation("TypePieceCustomFields", fields: [typePieceId], references: [id], onDelete: Cascade) + + typePieceId String? + typePiece TypePiece? @relation("TypePieceCustomFields", fields: [typePieceId], references: [id], onDelete: Cascade) // Relations avec les valeurs customFieldValues CustomFieldValue[] @@ -241,23 +259,97 @@ model CustomField { } model CustomFieldValue { - id String @id @default(cuid()) - value String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + value String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations customFieldId String customField CustomField @relation(fields: [customFieldId], references: [id], onDelete: Cascade) - - machineId String? - machine Machine? @relation("MachineCustomFieldValues", fields: [machineId], references: [id], onDelete: Cascade) - + + machineId String? + machine Machine? @relation("MachineCustomFieldValues", fields: [machineId], references: [id], onDelete: Cascade) + composantId String? - composant Composant? @relation("ComposantCustomFieldValues", fields: [composantId], references: [id], onDelete: Cascade) - - pieceId String? - piece Piece? @relation("PieceCustomFieldValues", fields: [pieceId], references: [id], onDelete: Cascade) + composant Composant? @relation("ComposantCustomFieldValues", fields: [composantId], references: [id], onDelete: Cascade) + + pieceId String? + piece Piece? @relation("PieceCustomFieldValues", fields: [pieceId], references: [id], onDelete: Cascade) @@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 TypeComposant @relation(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 TypePiece @relation(fields: [typePieceId], references: [id], onDelete: Cascade) + + pieces Piece[] + + @@map("piece_models") +} + +model TypeMachineComponentRequirement { + id String @id @default(cuid()) + label String? + minCount Int @default(1) + maxCount Int? + required Boolean @default(true) + allowNewModels Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + typeMachineId String + typeMachine TypeMachine @relation(fields: [typeMachineId], references: [id], onDelete: Cascade) + + typeComposantId String + typeComposant TypeComposant @relation(fields: [typeComposantId], references: [id]) + + composants Composant[] + + @@map("type_machine_component_requirements") +} + +model TypeMachinePieceRequirement { + id String @id @default(cuid()) + label String? + minCount Int @default(0) + maxCount Int? + required Boolean @default(false) + allowNewModels Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + typeMachineId String + typeMachine TypeMachine @relation(fields: [typeMachineId], references: [id], onDelete: Cascade) + + typePieceId String + typePiece TypePiece @relation(fields: [typePieceId], references: [id]) + + pieces Piece[] + + @@map("type_machine_piece_requirements") +} diff --git a/src/composants/composants.service.ts b/src/composants/composants.service.ts index d9b2cc1..26e750c 100644 --- a/src/composants/composants.service.ts +++ b/src/composants/composants.service.ts @@ -13,14 +13,44 @@ export class ComposantsService { machine: true, parentComposant: true, typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, sousComposants: { include: { typeComposant: true, - pieces: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, + pieces: { + include: { + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + }, + }, + }, + }, + pieces: { + include: { + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, - pieces: true, documents: true, }, }); @@ -32,6 +62,12 @@ export class ComposantsService { machine: true, parentComposant: true, typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -41,6 +77,12 @@ export class ComposantsService { sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, customFieldValues: { include: { customField: true, @@ -54,6 +96,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, @@ -67,6 +115,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, documents: true, @@ -81,6 +135,12 @@ export class ComposantsService { machine: true, parentComposant: true, typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -90,6 +150,12 @@ export class ComposantsService { sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, customFieldValues: { include: { customField: true, @@ -103,6 +169,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, @@ -116,6 +188,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, documents: true, @@ -130,10 +208,22 @@ export class ComposantsService { machine: true, parentComposant: true, typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, pieces: true, constructeur: true, }, @@ -146,6 +236,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, customFieldValues: { @@ -167,6 +263,12 @@ export class ComposantsService { }, include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -176,6 +278,12 @@ export class ComposantsService { sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -190,11 +298,23 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -209,6 +329,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, }, @@ -223,6 +349,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, }, @@ -239,6 +371,12 @@ export class ComposantsService { machine: true, parentComposant: true, typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -248,6 +386,12 @@ export class ComposantsService { sousComposants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, constructeur: true, customFieldValues: { include: { @@ -262,6 +406,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, }, @@ -274,6 +424,12 @@ export class ComposantsService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, documents: true, diff --git a/src/machines/machines.service.ts b/src/machines/machines.service.ts index d735e05..5ea07c6 100644 --- a/src/machines/machines.service.ts +++ b/src/machines/machines.service.ts @@ -1,17 +1,41 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateMachineDto, UpdateMachineDto } from '../shared/dto/machine.dto'; +import { + CreateMachineDto, + UpdateMachineDto, + MachineComponentSelectionDto, + MachinePieceSelectionDto +} from '../shared/dto/machine.dto'; @Injectable() export class MachinesService { constructor(private prisma: PrismaService) {} async create(createMachineDto: CreateMachineDto) { - // Récupérer le type de machine pour hériter de sa structure + const { + componentSelections = [], + pieceSelections = [], + ...machineData + } = createMachineDto; + + if (!machineData.typeMachineId) { + throw new Error('typeMachineId est requis pour créer une machine à partir d\'un squelette.'); + } + const typeMachine = await this.prisma.typeMachine.findUnique({ - where: { id: createMachineDto.typeMachineId }, + where: { id: machineData.typeMachineId }, include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }); @@ -19,11 +43,152 @@ export class MachinesService { throw new Error('Type de machine non trouvé'); } - // Créer la machine avec la structure héritée du type + const componentRequirementMap = new Map( + typeMachine.componentRequirements.map((requirement) => [requirement.id, requirement]), + ); + const pieceRequirementMap = new Map( + typeMachine.pieceRequirements.map((requirement) => [requirement.id, requirement]), + ); + + const componentSelectionMap = new Map(); + for (const selection of componentSelections) { + const requirement = componentRequirementMap.get(selection.requirementId); + if (!requirement) { + throw new Error(`Sélection de composant invalide: requirementId=${selection.requirementId}`); + } + if (!componentSelectionMap.has(requirement.id)) { + componentSelectionMap.set(requirement.id, []); + } + componentSelectionMap.get(requirement.id)!.push(selection); + } + + const pieceSelectionMap = new Map(); + for (const selection of pieceSelections) { + const requirement = pieceRequirementMap.get(selection.requirementId); + if (!requirement) { + throw new Error(`Sélection de pièce invalide: requirementId=${selection.requirementId}`); + } + if (!pieceSelectionMap.has(requirement.id)) { + pieceSelectionMap.set(requirement.id, []); + } + pieceSelectionMap.get(requirement.id)!.push(selection); + } + + const componentModelIds = Array.from( + new Set(componentSelections.map((selection) => selection.componentModelId).filter(Boolean)), + ) as string[]; + const componentModels = componentModelIds.length + ? await this.prisma.composantModel.findMany({ + where: { id: { in: componentModelIds } }, + }) + : []; + const componentModelMap = new Map(componentModels.map((model) => [model.id, model])); + + const pieceModelIds = Array.from( + new Set(pieceSelections.map((selection) => selection.pieceModelId).filter(Boolean)), + ) as string[]; + const pieceModels = pieceModelIds.length + ? await this.prisma.pieceModel.findMany({ + where: { id: { in: pieceModelIds } }, + }) + : []; + const pieceModelMap = new Map(pieceModels.map((model) => [model.id, model])); + + for (const requirement of typeMachine.componentRequirements) { + const selections = componentSelectionMap.get(requirement.id) ?? []; + const min = requirement.minCount ?? (requirement.required ? 1 : 0); + const max = requirement.maxCount ?? undefined; + + if (selections.length < min) { + throw new Error( + `Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" requiert au moins ${min} sélection(s).`, + ); + } + + if (max !== undefined && selections.length > max) { + throw new Error( + `Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" ne peut pas dépasser ${max} sélection(s).`, + ); + } + + if (!requirement.allowNewModels) { + const missingModel = selections.find((selection) => !selection.componentModelId); + if (missingModel) { + throw new Error( + `Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" n'autorise que la sélection de modèles existants.`, + ); + } + } + } + + for (const requirement of typeMachine.pieceRequirements) { + const selections = pieceSelectionMap.get(requirement.id) ?? []; + const min = requirement.minCount ?? (requirement.required ? 1 : 0); + const max = requirement.maxCount ?? undefined; + + if (selections.length < min) { + throw new Error( + `Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" requiert au moins ${min} sélection(s).`, + ); + } + + if (max !== undefined && selections.length > max) { + throw new Error( + `Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" ne peut pas dépasser ${max} sélection(s).`, + ); + } + + if (!requirement.allowNewModels) { + const missingModel = selections.find((selection) => !selection.pieceModelId); + if (missingModel) { + throw new Error( + `Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" n'autorise que la sélection de modèles existants.`, + ); + } + } + } + + for (const selection of componentSelections) { + if (!selection.componentModelId) { + continue; + } + const model = componentModelMap.get(selection.componentModelId); + if (!model) { + throw new Error(`Modèle de composant introuvable: ${selection.componentModelId}`); + } + const requirement = componentRequirementMap.get(selection.requirementId); + if (!requirement) { + throw new Error(`Requirement de composant introuvable: ${selection.requirementId}`); + } + if (model.typeComposantId !== requirement.typeComposantId) { + throw new Error( + `Le modèle de composant "${model.name}" n'appartient pas au type de composant attendu pour ce groupe.`, + ); + } + } + + for (const selection of pieceSelections) { + if (!selection.pieceModelId) { + continue; + } + const model = pieceModelMap.get(selection.pieceModelId); + if (!model) { + throw new Error(`Modèle de pièce introuvable: ${selection.pieceModelId}`); + } + const requirement = pieceRequirementMap.get(selection.requirementId); + if (!requirement) { + throw new Error(`Requirement de pièce introuvable: ${selection.requirementId}`); + } + if (model.typePieceId !== requirement.typePieceId) { + throw new Error( + `Le modèle de pièce "${model.name}" n'appartient pas au type de pièce attendu pour ce groupe.`, + ); + } + } + return await this.prisma.$transaction(async (prisma) => { - // 1. Créer la machine const machine = await prisma.machine.create({ - data: createMachineDto, + data: machineData, include: { site: true, typeMachine: true, @@ -31,37 +196,71 @@ export class MachinesService { }, }); - // 2. Créer les composants basés sur la structure du type - const components = (typeMachine as any).components; - if (components) { - await this.createComponentsFromType(prisma, machine.id, components); + if (typeMachine.componentRequirements.length > 0) { + for (const requirement of typeMachine.componentRequirements) { + const selections = componentSelectionMap.get(requirement.id) ?? []; + for (const selection of selections) { + const model = selection.componentModelId ? componentModelMap.get(selection.componentModelId) : undefined; + const definition = this.normalizeComponentSelection(selection, requirement, model); + await this.createComponentsFromType(prisma, machine.id, [definition]); + } + } + } else { + const legacyComponents = (typeMachine as any).components; + if (legacyComponents) { + await this.createComponentsFromType(prisma, machine.id, legacyComponents); + } } - // 3. Créer les pièces de machine basées sur le type - const machinePieces = (typeMachine as any).machinePieces; - if (machinePieces) { - await this.createMachinePiecesFromType(prisma, machine.id, machinePieces); + if (typeMachine.pieceRequirements.length > 0) { + for (const requirement of typeMachine.pieceRequirements) { + const selections = pieceSelectionMap.get(requirement.id) ?? []; + for (const selection of selections) { + const model = selection.pieceModelId ? pieceModelMap.get(selection.pieceModelId) : undefined; + const definition = this.normalizePieceSelection(selection, requirement, model); + await this.createMachinePiecesFromType(prisma, machine.id, [definition]); + } + } + } else { + const legacyPieces = (typeMachine as any).machinePieces; + if (legacyPieces) { + await this.createMachinePiecesFromType(prisma, machine.id, legacyPieces); + } } - // 4. Créer les champs personnalisés de la machine basés sur le type if (typeMachine.customFields && typeMachine.customFields.length > 0) { await this.createMachineCustomFieldsFromType(prisma, machine.id, typeMachine.customFields); } - // 5. Retourner la machine avec sa structure complète - return await prisma.machine.findUnique({ + return prisma.machine.findUnique({ where: { id: machine.id }, include: { site: true, typeMachine: { include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, composants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, sousComposants: true, pieces: { include: { @@ -71,6 +270,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, @@ -84,6 +289,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, customFieldValues: { @@ -97,19 +308,97 @@ export class MachinesService { }); } + private cloneStructure(definition: any): any { + if (definition === undefined || definition === null) { + return {}; + } + + try { + return JSON.parse(JSON.stringify(definition)); + } catch (error) { + if (Array.isArray(definition)) { + return definition.map((item) => this.cloneStructure(item)); + } + + if (typeof definition === 'object') { + return { ...definition }; + } + + return definition; + } + } + + private normalizeComponentSelection( + selection: MachineComponentSelectionDto, + requirement: any, + model?: any, + ): any { + const baseDefinition = selection.definition ?? (model?.structure ?? {}); + const definition = this.cloneStructure(baseDefinition); + const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {}; + + prepared.name = prepared.name || model?.name || requirement?.typeComposant?.name || 'Composant'; + prepared.reference = prepared.reference ?? model?.structure?.reference ?? ''; + prepared.emplacement = prepared.emplacement ?? model?.structure?.emplacement ?? ''; + prepared.prix = prepared.prix ?? model?.structure?.prix ?? null; + + prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : []; + prepared.pieces = Array.isArray(prepared.pieces) + ? prepared.pieces + : prepared.pieces + ? [prepared.pieces] + : []; + prepared.subComponents = Array.isArray(prepared.subComponents) + ? prepared.subComponents + : prepared.subComponents + ? [prepared.subComponents] + : []; + + prepared.typeComposantId = prepared.typeComposantId || requirement?.typeComposantId || model?.typeComposantId || null; + prepared.__componentModelId = selection.componentModelId ?? null; + prepared.__requirementId = requirement?.id ?? null; + + return prepared; + } + + private normalizePieceSelection( + selection: MachinePieceSelectionDto, + requirement: any, + model?: any, + ): any { + const baseDefinition = selection.definition ?? (model?.structure ?? {}); + const definition = this.cloneStructure(baseDefinition); + const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {}; + + prepared.name = prepared.name || model?.name || requirement?.typePiece?.name || 'Pièce'; + prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : []; + prepared.typePieceId = prepared.typePieceId || requirement?.typePieceId || model?.typePieceId || null; + prepared.__pieceModelId = selection.pieceModelId ?? null; + prepared.__requirementId = requirement?.id ?? null; + + return prepared; + } + private async createComponentsFromType(prisma: any, machineId: string, components: any[], parentComposantId?: string) { for (const component of components) { - if (!component.name) continue; + if (!component || !component.name) continue; - // Créer d'abord le type de composant s'il n'existe pas - let typeComposant: any = null; - if (component.customFields && component.customFields.length > 0) { - // Chercher d'abord si le type de composant existe déjà - typeComposant = await prisma.typeComposant.findFirst({ - where: { name: component.name } + const customFields = Array.isArray(component.customFields) ? component.customFields : []; + const componentPieces = Array.isArray(component.pieces) ? component.pieces : []; + const subComponents = Array.isArray(component.subComponents) ? component.subComponents : []; + + const componentModelId = component.__componentModelId ?? null; + const requirementId = component.__requirementId ?? null; + const providedTypeComposantId = component.typeComposantId + ?? (component.typeComposant && component.typeComposant.id ? component.typeComposant.id : null); + + let typeComposantId: string | null = providedTypeComposantId ?? null; + + if (!typeComposantId && customFields.length > 0) { + let typeComposant = await prisma.typeComposant.findFirst({ + where: { name: component.name }, }); - // Si le type n'existe pas, le créer if (!typeComposant) { typeComposant = await prisma.typeComposant.create({ data: { @@ -118,8 +407,7 @@ export class MachinesService { }, }); - // Créer les champs personnalisés pour le type de composant - for (const customField of component.customFields) { + for (const customField of customFields) { await prisma.customField.create({ data: { name: customField.name, @@ -132,6 +420,8 @@ export class MachinesService { }); } } + + typeComposantId = typeComposant.id; } const createdComposant = await prisma.composant.create({ @@ -140,21 +430,22 @@ export class MachinesService { reference: component.reference || '', constructeurId: await this.resolveConstructeurId(prisma, component.constructeur), emplacement: component.emplacement || '', - prix: component.prix || null, + prix: component.prix ?? null, machineId, parentComposantId, - typeComposantId: typeComposant?.id || null, + typeComposantId, + composantModelId: componentModelId, + typeMachineComponentRequirementId: requirementId, }, }); - // Créer les valeurs des champs personnalisés pour le composant - if (typeComposant && typeComposant.id) { - const customFields = await prisma.customField.findMany({ - where: { typeComposantId: typeComposant.id }, + if (typeComposantId) { + const typeCustomFields = await prisma.customField.findMany({ + where: { typeComposantId }, }); - for (const customField of customFields) { - const defaultValue = component.customFields?.find(cf => cf.name === customField.name)?.defaultValue || ''; + for (const customField of typeCustomFields) { + const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; await prisma.customFieldValue.create({ data: { value: defaultValue, @@ -165,79 +456,81 @@ export class MachinesService { } } - // Créer les pièces du composant avec leurs champs personnalisés - if (component.pieces) { - for (const piece of component.pieces) { - if (!piece || !piece.name) continue; - - // Créer d'abord le type de pièce s'il n'existe pas - let typePiece: any = null; - if (piece.customFields && piece.customFields.length > 0) { - // Chercher d'abord si le type de pièce existe déjà - typePiece = await prisma.typePiece.findFirst({ - where: { name: piece.name } - }); + for (const piece of componentPieces) { + if (!piece || !piece.name) continue; - // Si le type n'existe pas, le créer - if (!typePiece) { - typePiece = await prisma.typePiece.create({ - data: { - name: piece.name, - description: piece.description || '', - }, - }); + const pieceCustomFields = Array.isArray(piece.customFields) ? piece.customFields : []; + const pieceModelId = piece.__pieceModelId ?? null; + const pieceRequirementId = piece.__requirementId ?? null; + const providedTypePieceId = piece.typePieceId + ?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null); - // Créer les champs personnalisés pour le type de pièce - for (const customField of piece.customFields) { - await prisma.customField.create({ - data: { - name: customField.name, - type: customField.type, - required: customField.required || false, - defaultValue: customField.defaultValue, - options: customField.options || [], - typePieceId: typePiece.id, - }, - }); - } - } - } - - const createdPiece = await prisma.piece.create({ - data: { - name: piece.name, - reference: piece.reference || '', - constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur), - emplacement: piece.emplacement || '', - prix: piece.prix || null, - composantId: createdComposant.id, - typePieceId: typePiece?.id || null, - }, + let typePieceId: string | null = providedTypePieceId ?? null; + + if (!typePieceId && pieceCustomFields.length > 0) { + let typePiece = await prisma.typePiece.findFirst({ + where: { name: piece.name }, }); - // Créer les valeurs des champs personnalisés pour la pièce - if (typePiece && typePiece.id) { - const customFields = await prisma.customField.findMany({ - where: { typePieceId: typePiece.id }, + if (!typePiece) { + typePiece = await prisma.typePiece.create({ + data: { + name: piece.name, + description: piece.description || '', + }, }); - for (const customField of customFields) { - const defaultValue = piece.customFields?.find(cf => cf.name === customField.name)?.defaultValue || ''; - await prisma.customFieldValue.create({ + for (const customField of pieceCustomFields) { + await prisma.customField.create({ data: { - value: defaultValue, - customFieldId: customField.id, - pieceId: createdPiece.id, + name: customField.name, + type: customField.type, + required: customField.required || false, + defaultValue: customField.defaultValue, + options: customField.options || [], + typePieceId: typePiece.id, }, }); } } + + typePieceId = typePiece.id; + } + + const createdPiece = await prisma.piece.create({ + data: { + name: piece.name, + reference: piece.reference || '', + constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur), + emplacement: piece.emplacement || '', + prix: piece.prix ?? null, + composantId: createdComposant.id, + typePieceId, + pieceModelId, + typeMachinePieceRequirementId: pieceRequirementId, + }, + }); + + if (typePieceId) { + const typePieceCustomFields = await prisma.customField.findMany({ + where: { typePieceId }, + }); + + for (const customField of typePieceCustomFields) { + const defaultValue = pieceCustomFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; + await prisma.customFieldValue.create({ + data: { + value: defaultValue, + customFieldId: customField.id, + pieceId: createdPiece.id, + }, + }); + } } } - // Créer les sous-composants récursivement - if (component.subComponents) { - await this.createComponentsFromType(prisma, machineId, component.subComponents, createdComposant.id); + if (subComponents.length > 0) { + await this.createComponentsFromType(prisma, machineId, subComponents, createdComposant.id); } } } @@ -245,19 +538,76 @@ export class MachinesService { private async createMachinePiecesFromType(prisma: any, machineId: string, machinePieces: any[]) { for (const piece of machinePieces) { if (!piece || !piece.name) continue; - + + const customFields = Array.isArray(piece.customFields) ? piece.customFields : []; + const pieceModelId = piece.__pieceModelId ?? null; + const requirementId = piece.__requirementId ?? null; + const providedTypePieceId = piece.typePieceId + ?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null); + + let typePieceId: string | null = providedTypePieceId ?? null; + + if (!typePieceId && customFields.length > 0) { + let typePiece = await prisma.typePiece.findFirst({ + where: { name: piece.name }, + }); + + if (!typePiece) { + typePiece = await prisma.typePiece.create({ + data: { + name: piece.name, + description: piece.description || '', + }, + }); + + for (const customField of customFields) { + await prisma.customField.create({ + data: { + name: customField.name, + type: customField.type, + required: customField.required || false, + defaultValue: customField.defaultValue, + options: customField.options || [], + typePieceId: typePiece.id, + }, + }); + } + } + + typePieceId = typePiece.id; + } + const createdPiece = await prisma.piece.create({ data: { name: piece.name, - machineId, + reference: piece.reference || '', constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur), + emplacement: piece.emplacement || '', + prix: piece.prix ?? null, + machineId, + typePieceId, + pieceModelId, + typeMachinePieceRequirementId: requirementId, }, }); - // Copier les champs personnalisés du type vers la pièce - if (piece.customFields && piece.customFields.length > 0) { - for (const customField of piece.customFields) { - // Créer le champ personnalisé + if (typePieceId) { + const typePieceCustomFields = await prisma.customField.findMany({ + where: { typePieceId }, + }); + + for (const customField of typePieceCustomFields) { + const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; + await prisma.customFieldValue.create({ + data: { + value: defaultValue, + customFieldId: customField.id, + pieceId: createdPiece.id, + }, + }); + } + } else if (customFields.length > 0) { + for (const customField of customFields) { const createdCustomField = await prisma.customField.create({ data: { name: customField.name, @@ -265,11 +615,10 @@ export class MachinesService { required: customField.required || false, defaultValue: customField.defaultValue, options: customField.options || [], - typePieceId: null, // Ce champ sera lié à la pièce individuelle + typePieceId: null, }, }); - // Créer la valeur par défaut await prisma.customFieldValue.create({ data: { value: customField.defaultValue || '', @@ -316,12 +665,28 @@ export class MachinesService { typeMachine: { include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, composants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, sousComposants: true, customFieldValues: { include: { @@ -337,6 +702,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, }, @@ -349,6 +720,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, customFieldValues: { @@ -369,12 +746,28 @@ export class MachinesService { typeMachine: { include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, composants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, sousComposants: true, customFieldValues: { include: { @@ -390,6 +783,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, }, @@ -402,6 +801,12 @@ export class MachinesService { }, }, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, }, }, customFieldValues: { @@ -423,17 +828,39 @@ export class MachinesService { typeMachine: { include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }, constructeur: true, composants: { include: { typeComposant: true, + composantModel: true, + typeMachineComponentRequirement: { + include: { + typeComposant: true, + }, + }, sousComposants: true, constructeur: true, pieces: { include: { constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, customFieldValues: { include: { customField: true, @@ -446,6 +873,12 @@ export class MachinesService { pieces: { include: { constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, customFieldValues: { include: { customField: true, diff --git a/src/pieces/pieces.service.ts b/src/pieces/pieces.service.ts index 073321f..e507d98 100644 --- a/src/pieces/pieces.service.ts +++ b/src/pieces/pieces.service.ts @@ -15,6 +15,17 @@ export class PiecesService { typePiece: true, documents: true, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + customFieldValues: { + include: { + customField: true, + }, + }, }, }); } @@ -27,6 +38,17 @@ export class PiecesService { typePiece: true, documents: true, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + customFieldValues: { + include: { + customField: true, + }, + }, }, }); } @@ -40,6 +62,17 @@ export class PiecesService { typePiece: true, documents: true, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + customFieldValues: { + include: { + customField: true, + }, + }, }, }); } @@ -53,6 +86,17 @@ export class PiecesService { typePiece: true, documents: true, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + customFieldValues: { + include: { + customField: true, + }, + }, }, }); } @@ -66,6 +110,17 @@ export class PiecesService { typePiece: true, documents: true, constructeur: true, + pieceModel: true, + typeMachinePieceRequirement: { + include: { + typePiece: true, + }, + }, + customFieldValues: { + include: { + customField: true, + }, + }, }, }); } diff --git a/src/shared/dto/composant.dto.ts b/src/shared/dto/composant.dto.ts index 69df66c..1c9d827 100644 --- a/src/shared/dto/composant.dto.ts +++ b/src/shared/dto/composant.dto.ts @@ -33,6 +33,10 @@ export class CreateComposantDto { @IsOptional() @IsString() typeComposantId?: string; + + @IsOptional() + @IsString() + composantModelId?: string; } export class UpdateComposantDto { @@ -60,4 +64,8 @@ export class UpdateComposantDto { @IsOptional() @IsString() typeComposantId?: string; -} + + @IsOptional() + @IsString() + composantModelId?: string; +} diff --git a/src/shared/dto/machine.dto.ts b/src/shared/dto/machine.dto.ts index 8640413..01f2168 100644 --- a/src/shared/dto/machine.dto.ts +++ b/src/shared/dto/machine.dto.ts @@ -1,4 +1,30 @@ -import { IsString, IsOptional, IsDecimal } from 'class-validator'; +import { IsString, IsOptional, IsDecimal, IsArray } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ValidateNested } from 'class-validator'; + +export class MachineComponentSelectionDto { + @IsString() + requirementId: string; + + @IsOptional() + @IsString() + componentModelId?: string; + + @IsOptional() + definition?: any; +} + +export class MachinePieceSelectionDto { + @IsString() + requirementId: string; + + @IsOptional() + @IsString() + pieceModelId?: string; + + @IsOptional() + definition?: any; +} export class CreateMachineDto { @IsString() @@ -26,6 +52,18 @@ export class CreateMachineDto { @IsOptional() @IsString() typeMachineId?: string; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => MachineComponentSelectionDto) + componentSelections?: MachineComponentSelectionDto[]; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => MachinePieceSelectionDto) + pieceSelections?: MachinePieceSelectionDto[]; } export class UpdateMachineDto { diff --git a/src/shared/dto/piece.dto.ts b/src/shared/dto/piece.dto.ts index 2f42073..7b65e5f 100644 --- a/src/shared/dto/piece.dto.ts +++ b/src/shared/dto/piece.dto.ts @@ -33,6 +33,10 @@ export class CreatePieceDto { @IsOptional() @IsString() typePieceId?: string; + + @IsOptional() + @IsString() + pieceModelId?: string; } export class UpdatePieceDto { @@ -60,4 +64,8 @@ export class UpdatePieceDto { @IsOptional() @IsString() typePieceId?: string; -} + + @IsOptional() + @IsString() + pieceModelId?: string; +} diff --git a/src/shared/dto/type.dto.ts b/src/shared/dto/type.dto.ts index 07337dc..71719c2 100644 --- a/src/shared/dto/type.dto.ts +++ b/src/shared/dto/type.dto.ts @@ -1,4 +1,6 @@ -import { IsString, IsOptional, IsArray, IsObject, IsBoolean, IsEnum } from 'class-validator'; +import { IsString, IsOptional, IsArray, IsObject, IsBoolean, IsEnum, IsInt } from 'class-validator'; +import { Type } from 'class-transformer'; +import { ValidateNested } from 'class-validator'; export enum CustomFieldType { TEXT = 'text', @@ -50,6 +52,56 @@ export class UpdateCustomFieldDto { options?: string[]; } +export class TypeMachineComponentRequirementDto { + @IsString() + typeComposantId: string; + + @IsOptional() + @IsString() + label?: string; + + @IsOptional() + @IsInt() + minCount?: number; + + @IsOptional() + @IsInt() + maxCount?: number | null; + + @IsOptional() + @IsBoolean() + required?: boolean; + + @IsOptional() + @IsBoolean() + allowNewModels?: boolean; +} + +export class TypeMachinePieceRequirementDto { + @IsString() + typePieceId: string; + + @IsOptional() + @IsString() + label?: string; + + @IsOptional() + @IsInt() + minCount?: number; + + @IsOptional() + @IsInt() + maxCount?: number | null; + + @IsOptional() + @IsBoolean() + required?: boolean; + + @IsOptional() + @IsBoolean() + allowNewModels?: boolean; +} + export class CreateTypeMachineDto { @IsString() name: string; @@ -77,6 +129,18 @@ export class CreateTypeMachineDto { @IsOptional() @IsArray() customFields?: CreateCustomFieldDto[]; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => TypeMachineComponentRequirementDto) + componentRequirements?: TypeMachineComponentRequirementDto[]; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => TypeMachinePieceRequirementDto) + pieceRequirements?: TypeMachinePieceRequirementDto[]; } export class UpdateTypeMachineDto { @@ -107,6 +171,18 @@ export class UpdateTypeMachineDto { @IsOptional() @IsArray() customFields?: CreateCustomFieldDto[]; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => TypeMachineComponentRequirementDto) + componentRequirements?: TypeMachineComponentRequirementDto[]; + + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => TypeMachinePieceRequirementDto) + pieceRequirements?: TypeMachinePieceRequirementDto[]; } export class CreateTypeComposantDto { @@ -161,4 +237,68 @@ export class UpdateTypePieceDto { @IsOptional() @IsArray() customFields?: CreateCustomFieldDto[]; -} \ No newline at end of file +} + +export class CreateComposantModelDto { + @IsString() + name: string; + + @IsOptional() + @IsString() + description?: string; + + @IsString() + typeComposantId: string; + + @IsOptional() + structure?: any; +} + +export class UpdateComposantModelDto { + @IsOptional() + @IsString() + name?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + typeComposantId?: string; + + @IsOptional() + structure?: any; +} + +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; +} diff --git a/src/types/types.controller.ts b/src/types/types.controller.ts index 1c5b9b1..39c9e19 100644 --- a/src/types/types.controller.ts +++ b/src/types/types.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; import { TypesService } from './types.service'; import { CreateTypeMachineDto, @@ -6,7 +6,11 @@ import { CreateTypeComposantDto, UpdateTypeComposantDto, CreateTypePieceDto, - UpdateTypePieceDto + UpdateTypePieceDto, + CreateComposantModelDto, + UpdateComposantModelDto, + CreatePieceModelDto, + UpdatePieceModelDto } from '../shared/dto/type.dto'; @Controller('types') @@ -50,6 +54,32 @@ export class TypesController { 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') findOneTypeComposant(@Param('id') id: string) { return this.typesService.findOneTypeComposant(id); @@ -76,6 +106,32 @@ export class TypesController { 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') findOneTypePiece(@Param('id') id: string) { return this.typesService.findOneTypePiece(id); diff --git a/src/types/types.service.ts b/src/types/types.service.ts index 891e61f..7598946 100644 --- a/src/types/types.service.ts +++ b/src/types/types.service.ts @@ -6,7 +6,11 @@ import { CreateTypeComposantDto, UpdateTypeComposantDto, CreateTypePieceDto, - UpdateTypePieceDto + UpdateTypePieceDto, + CreateComposantModelDto, + UpdateComposantModelDto, + CreatePieceModelDto, + UpdatePieceModelDto } from '../shared/dto/type.dto'; @Injectable() @@ -15,7 +19,7 @@ export class TypesService { // TypeMachine methods async createTypeMachine(createTypeMachineDto: CreateTypeMachineDto) { - const { customFields, ...typeData } = createTypeMachineDto; + const { customFields, componentRequirements, pieceRequirements, ...typeData } = createTypeMachineDto; return this.prisma.typeMachine.create({ data: { @@ -28,10 +32,44 @@ export class TypesService { defaultValue: field.defaultValue, options: field.options })) - } : undefined + } : undefined, + componentRequirements: componentRequirements && componentRequirements.length > 0 ? { + create: componentRequirements.map(requirement => ({ + label: requirement.label, + minCount: requirement.minCount ?? 1, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? true, + allowNewModels: requirement.allowNewModels ?? true, + typeComposant: { + connect: { id: requirement.typeComposantId }, + }, + })) + } : undefined, + pieceRequirements: pieceRequirements && pieceRequirements.length > 0 ? { + create: pieceRequirements.map(requirement => ({ + label: requirement.label, + minCount: requirement.minCount ?? 0, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? false, + allowNewModels: requirement.allowNewModels ?? true, + typePiece: { + connect: { id: requirement.typePieceId }, + }, + })) + } : undefined, }, include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }); } @@ -41,6 +79,16 @@ export class TypesService { include: { machines: true, customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }); } @@ -51,12 +99,22 @@ export class TypesService { include: { machines: true, customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }); } async updateTypeMachine(id: string, updateTypeMachineDto: UpdateTypeMachineDto) { - const { customFields, ...typeData } = updateTypeMachineDto; + const { customFields, componentRequirements, pieceRequirements, ...typeData } = updateTypeMachineDto; // Si des champs personnalisés sont fournis, on les met à jour if (customFields !== undefined) { @@ -79,12 +137,64 @@ export class TypesService { }); } } + + if (componentRequirements !== undefined) { + await this.prisma.typeMachineComponentRequirement.deleteMany({ + where: { typeMachineId: id }, + }); + + if (componentRequirements.length > 0) { + await this.prisma.typeMachineComponentRequirement.createMany({ + data: componentRequirements.map(requirement => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 1, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? true, + allowNewModels: requirement.allowNewModels ?? true, + typeMachineId: id, + typeComposantId: requirement.typeComposantId, + })), + skipDuplicates: false, + }); + } + } + + if (pieceRequirements !== undefined) { + await this.prisma.typeMachinePieceRequirement.deleteMany({ + where: { typeMachineId: id }, + }); + + if (pieceRequirements.length > 0) { + await this.prisma.typeMachinePieceRequirement.createMany({ + data: pieceRequirements.map(requirement => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 0, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? false, + allowNewModels: requirement.allowNewModels ?? true, + typeMachineId: id, + typePieceId: requirement.typePieceId, + })), + skipDuplicates: false, + }); + } + } return this.prisma.typeMachine.update({ where: { id }, data: typeData, include: { customFields: true, + componentRequirements: { + include: { + typeComposant: true, + }, + }, + pieceRequirements: { + include: { + typePiece: true, + }, + }, }, }); } @@ -142,6 +252,7 @@ export class TypesService { include: { composants: true, customFields: true, + models: true, }, }); } @@ -152,6 +263,7 @@ export class TypesService { include: { composants: true, customFields: true, + models: true, }, }); } @@ -224,6 +336,7 @@ export class TypesService { include: { pieces: true, customFields: true, + models: true, }, }); } @@ -234,6 +347,7 @@ export class TypesService { include: { pieces: true, customFields: true, + models: true, }, }); } @@ -277,4 +391,134 @@ export class TypesService { where: { id }, }); } + + // ComposantModel methods + async createComposantModel(createComposantModelDto: CreateComposantModelDto) { + const { typeComposantId, ...data } = createComposantModelDto; + + return this.prisma.composantModel.create({ + data: { + ...data, + typeComposant: { + connect: { id: typeComposantId }, + }, + }, + include: { + typeComposant: true, + }, + }); + } + + async findAllComposantModels(typeComposantId?: string) { + return this.prisma.composantModel.findMany({ + where: typeComposantId ? { typeComposantId } : undefined, + include: { + typeComposant: true, + }, + orderBy: { + name: 'asc', + }, + }); + } + + async findOneComposantModel(id: string) { + return this.prisma.composantModel.findUnique({ + where: { id }, + include: { + typeComposant: true, + }, + }); + } + + async updateComposantModel(id: string, updateComposantModelDto: UpdateComposantModelDto) { + const { typeComposantId, ...data } = updateComposantModelDto; + + return this.prisma.composantModel.update({ + where: { id }, + data: { + ...data, + ...(typeComposantId + ? { + typeComposant: { + connect: { id: typeComposantId }, + }, + } + : {}), + }, + include: { + typeComposant: true, + }, + }); + } + + async removeComposantModel(id: string) { + return this.prisma.composantModel.delete({ + where: { id }, + }); + } + + // PieceModel methods + async createPieceModel(createPieceModelDto: CreatePieceModelDto) { + const { typePieceId, ...data } = createPieceModelDto; + + return this.prisma.pieceModel.create({ + data: { + ...data, + typePiece: { + connect: { id: typePieceId }, + }, + }, + include: { + typePiece: true, + }, + }); + } + + async findAllPieceModels(typePieceId?: string) { + return this.prisma.pieceModel.findMany({ + where: typePieceId ? { typePieceId } : undefined, + include: { + typePiece: true, + }, + orderBy: { + name: 'asc', + }, + }); + } + + async findOnePieceModel(id: string) { + return this.prisma.pieceModel.findUnique({ + where: { id }, + include: { + typePiece: true, + }, + }); + } + + async updatePieceModel(id: string, updatePieceModelDto: UpdatePieceModelDto) { + const { typePieceId, ...data } = updatePieceModelDto; + + return this.prisma.pieceModel.update({ + where: { id }, + data: { + ...data, + ...(typePieceId + ? { + typePiece: { + connect: { id: typePieceId }, + }, + } + : {}), + }, + include: { + typePiece: true, + }, + }); + } + + async removePieceModel(id: string) { + return this.prisma.pieceModel.delete({ + where: { id }, + }); + } }