From e64fba3ae79f57e8f144bcea51cc89ebcadcf438 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 23 Sep 2025 15:05:33 +0200 Subject: [PATCH] feat: Add Model gestion for piece and component --- package-lock.json | 21 + package.json | 1 + .../migration.sql | 21 + .../migration.sql | 35 ++ .../migration.sql | 43 ++ prisma/schema.prisma | 75 ++- src/app.module.ts | 2 + src/composants/composants.controller.ts | 20 +- src/composants/composants.module.ts | 2 +- src/composants/composants.service.spec.ts | 11 +- src/composants/composants.service.ts | 9 +- src/constructeurs/constructeurs.controller.ts | 28 +- src/constructeurs/constructeurs.module.ts | 6 +- src/constructeurs/constructeurs.service.ts | 29 +- src/custom-fields/custom-fields.controller.ts | 25 +- src/custom-fields/custom-fields.module.ts | 2 +- src/custom-fields/custom-fields.service.ts | 36 +- src/documents/documents.controller.ts | 20 +- src/documents/documents.module.ts | 2 +- src/documents/documents.service.ts | 5 +- src/machines/machines.controller.ts | 10 +- src/machines/machines.module.ts | 2 +- src/machines/machines.service.ts | 532 +++++++++++++----- src/model-type/dto/create-model-type.dto.ts | 28 + src/model-type/dto/update-model-type.dto.ts | 5 + src/model-type/model-type.controller.ts | 80 +++ src/model-type/model-type.module.ts | 9 + src/model-type/model-type.service.ts | 159 ++++++ src/pieces/pieces.controller.ts | 10 +- src/pieces/pieces.module.ts | 2 +- src/pieces/pieces.service.spec.ts | 8 +- src/pieces/pieces.service.ts | 4 +- src/prisma/prisma.module.ts | 2 +- src/prisma/prisma.service.ts | 7 +- src/profiles/profiles.controller.ts | 12 +- src/profiles/profiles.module.ts | 6 +- src/profiles/profiles.service.ts | 76 ++- src/session/session.controller.ts | 42 +- src/session/session.module.ts | 6 +- src/shared/dto/composant.dto.ts | 4 +- src/shared/dto/constructeur.dto.ts | 14 +- src/shared/dto/custom-field.dto.ts | 2 +- src/shared/dto/machine.dto.ts | 6 +- src/shared/dto/piece.dto.ts | 4 +- src/shared/dto/profile.dto.ts | 8 +- src/shared/dto/type.dto.ts | 12 +- src/sites/sites.controller.ts | 10 +- src/sites/sites.module.ts | 2 +- src/sites/sites.service.ts | 4 +- src/types/express-session.d.ts | 4 +- src/types/types.controller.ts | 46 +- src/types/types.module.ts | 2 +- src/types/types.service.ts | 369 ++++++++---- test/app.e2e-spec.ts | 329 +++++++---- 54 files changed, 1640 insertions(+), 569 deletions(-) create mode 100644 prisma/migrations/20250922100330_add_model_type/migration.sql create mode 100644 prisma/migrations/20250922143316_model_type_components/migration.sql create mode 100644 prisma/migrations/20250923093000_model_type_pieces/migration.sql create mode 100644 src/model-type/dto/create-model-type.dto.ts create mode 100644 src/model-type/dto/update-model-type.dto.ts create mode 100644 src/model-type/model-type.controller.ts create mode 100644 src/model-type/model-type.module.ts create mode 100644 src/model-type/model-type.service.ts diff --git a/package-lock.json b/package-lock.json index 5b90712..9488ee9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@nestjs/common": "^11.0.1", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", + "@nestjs/mapped-types": "^2.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/typeorm": "^11.0.0", "@prisma/client": "^6.12.0", @@ -2535,6 +2536,26 @@ } } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.5.tgz", diff --git a/package.json b/package.json index 680c6b4..5629107 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@nestjs/common": "^11.0.1", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", + "@nestjs/mapped-types": "^2.0.5", "@nestjs/platform-express": "^11.0.1", "@nestjs/typeorm": "^11.0.0", "@prisma/client": "^6.12.0", diff --git a/prisma/migrations/20250922100330_add_model_type/migration.sql b/prisma/migrations/20250922100330_add_model_type/migration.sql new file mode 100644 index 0000000..7656b8d --- /dev/null +++ b/prisma/migrations/20250922100330_add_model_type/migration.sql @@ -0,0 +1,21 @@ +-- CreateEnum +CREATE TYPE "ModelCategory" AS ENUM ('COMPONENT', 'PIECE'); + +-- CreateTable +CREATE TABLE "ModelType" ( + "id" TEXT NOT NULL, + "name" VARCHAR(120) NOT NULL, + "code" VARCHAR(60) NOT NULL, + "category" "ModelCategory" NOT NULL, + "notes" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ModelType_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ModelType_code_key" ON "ModelType"("code"); + +-- CreateIndex +CREATE INDEX "ModelType_category_name_idx" ON "ModelType"("category", "name"); diff --git a/prisma/migrations/20250922143316_model_type_components/migration.sql b/prisma/migrations/20250922143316_model_type_components/migration.sql new file mode 100644 index 0000000..9362296 --- /dev/null +++ b/prisma/migrations/20250922143316_model_type_components/migration.sql @@ -0,0 +1,35 @@ +/* + Warnings: + + - You are about to drop the `type_composants` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "composant_models" DROP CONSTRAINT "composant_models_typeComposantId_fkey"; + +-- DropForeignKey +ALTER TABLE "composants" DROP CONSTRAINT "composants_typeComposantId_fkey"; + +-- DropForeignKey +ALTER TABLE "custom_fields" DROP CONSTRAINT "custom_fields_typeComposantId_fkey"; + +-- DropForeignKey +ALTER TABLE "type_machine_component_requirements" DROP CONSTRAINT "type_machine_component_requirements_typeComposantId_fkey"; + +-- AlterTable +ALTER TABLE "ModelType" ADD COLUMN "description" TEXT; + +-- DropTable +DROP TABLE "type_composants"; + +-- AddForeignKey +ALTER TABLE "composants" ADD CONSTRAINT "composants_typeComposantId_fkey" FOREIGN KEY ("typeComposantId") REFERENCES "ModelType"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "custom_fields" ADD CONSTRAINT "custom_fields_typeComposantId_fkey" FOREIGN KEY ("typeComposantId") REFERENCES "ModelType"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "composant_models" ADD CONSTRAINT "composant_models_typeComposantId_fkey" FOREIGN KEY ("typeComposantId") REFERENCES "ModelType"("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 "ModelType"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250923093000_model_type_pieces/migration.sql b/prisma/migrations/20250923093000_model_type_pieces/migration.sql new file mode 100644 index 0000000..2bd0478 --- /dev/null +++ b/prisma/migrations/20250923093000_model_type_pieces/migration.sql @@ -0,0 +1,43 @@ +/* + Migration: migrate legacy TypePiece references to ModelType entries +*/ + +-- Insert existing type_pieces into ModelType if not already present +INSERT INTO "ModelType" ("id", "name", "code", "category", "notes", "description", "createdAt", "updatedAt") +SELECT + tp."id", + tp."name", + tp."id" AS "code", + 'PIECE'::"ModelCategory", + tp."description", + tp."description", + tp."createdAt", + tp."updatedAt" +FROM "type_pieces" tp +ON CONFLICT ("id") DO NOTHING; + +-- Drop existing foreign keys referencing type_pieces +ALTER TABLE "pieces" DROP CONSTRAINT IF EXISTS "pieces_typePieceId_fkey"; +ALTER TABLE "custom_fields" DROP CONSTRAINT IF EXISTS "custom_fields_typePieceId_fkey"; +ALTER TABLE "piece_models" DROP CONSTRAINT IF EXISTS "piece_models_typePieceId_fkey"; +ALTER TABLE "type_machine_piece_requirements" DROP CONSTRAINT IF EXISTS "type_machine_piece_requirements_typePieceId_fkey"; + +-- Drop legacy table +DROP TABLE IF EXISTS "type_pieces"; + +-- Recreate foreign keys pointing to ModelType +ALTER TABLE "pieces" + ADD CONSTRAINT "pieces_typePieceId_fkey" + FOREIGN KEY ("typePieceId") REFERENCES "ModelType"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +ALTER TABLE "custom_fields" + ADD CONSTRAINT "custom_fields_typePieceId_fkey" + FOREIGN KEY ("typePieceId") REFERENCES "ModelType"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE "piece_models" + ADD CONSTRAINT "piece_models_typePieceId_fkey" + FOREIGN KEY ("typePieceId") REFERENCES "ModelType"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE "type_machine_piece_requirements" + ADD CONSTRAINT "type_machine_piece_requirements_typePieceId_fkey" + FOREIGN KEY ("typePieceId") REFERENCES "ModelType"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eea8e52..1ba6a74 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -51,38 +51,6 @@ model TypeMachine { @@map("type_machines") } -model TypeComposant { - id String @id @default(cuid()) - name String @unique - description String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - composants Composant[] - customFields CustomField[] @relation("TypeComposantCustomFields") - models ComposantModel[] - componentRequirements TypeMachineComponentRequirement[] - - @@map("type_composants") -} - -model TypePiece { - id String @id @default(cuid()) - name String @unique - description String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // Relations - pieces Piece[] - customFields CustomField[] @relation("TypePieceCustomFields") - models PieceModel[] - pieceRequirements TypeMachinePieceRequirement[] - - @@map("type_pieces") -} - model Machine { id String @id @default(cuid()) name String @@ -128,7 +96,7 @@ model Composant { sousComposants Composant[] @relation("ComposantHierarchy") typeComposantId String? - typeComposant TypeComposant? @relation(fields: [typeComposantId], references: [id]) + typeComposant ModelType? @relation("ModelTypeComponentAssignments", fields: [typeComposantId], references: [id]) composantModelId String? composantModel ComposantModel? @relation(fields: [composantModelId], references: [id], onDelete: SetNull) @@ -163,7 +131,7 @@ model Piece { composant Composant? @relation(fields: [composantId], references: [id], onDelete: Cascade) typePieceId String? - typePiece TypePiece? @relation(fields: [typePieceId], references: [id]) + typePiece ModelType? @relation("ModelTypePieceAssignments", fields: [typePieceId], references: [id]) pieceModelId String? pieceModel PieceModel? @relation(fields: [pieceModelId], references: [id], onDelete: SetNull) @@ -180,6 +148,33 @@ model Piece { @@map("pieces") } +enum ModelCategory { + COMPONENT + PIECE +} + +model ModelType { + id String @id @default(cuid()) + name String @db.VarChar(120) + code String @unique @db.VarChar(60) + category ModelCategory + notes String? @db.Text + description String? @db.Text + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([category, name]) + + composants Composant[] @relation("ModelTypeComponentAssignments") + models ComposantModel[] @relation("ModelTypeComponentModels") + componentRequirements TypeMachineComponentRequirement[] @relation("ModelTypeComponentRequirements") + customFields CustomField[] @relation("ModelTypeCustomFields") + pieceModels PieceModel[] @relation("ModelTypePieceModels") + pieceRequirements TypeMachinePieceRequirement[] @relation("ModelTypePieceRequirements") + pieces Piece[] @relation("ModelTypePieceAssignments") + pieceCustomFields CustomField[] @relation("ModelTypePieceCustomFields") +} + model Constructeur { id String @id @default(cuid()) name String @unique @@ -247,10 +242,10 @@ model CustomField { typeMachine TypeMachine? @relation("TypeMachineCustomFields", fields: [typeMachineId], references: [id], onDelete: Cascade) typeComposantId String? - typeComposant TypeComposant? @relation("TypeComposantCustomFields", fields: [typeComposantId], references: [id], onDelete: Cascade) + typeComposant ModelType? @relation("ModelTypeCustomFields", fields: [typeComposantId], references: [id], onDelete: Cascade) typePieceId String? - typePiece TypePiece? @relation("TypePieceCustomFields", fields: [typePieceId], references: [id], onDelete: Cascade) + typePiece ModelType? @relation("ModelTypePieceCustomFields", fields: [typePieceId], references: [id], onDelete: Cascade) // Relations avec les valeurs customFieldValues CustomFieldValue[] @@ -289,7 +284,7 @@ model ComposantModel { updatedAt DateTime @updatedAt typeComposantId String - typeComposant TypeComposant @relation(fields: [typeComposantId], references: [id], onDelete: Cascade) + typeComposant ModelType @relation("ModelTypeComponentModels", fields: [typeComposantId], references: [id], onDelete: Cascade) composants Composant[] @@ -305,7 +300,7 @@ model PieceModel { updatedAt DateTime @updatedAt typePieceId String - typePiece TypePiece @relation(fields: [typePieceId], references: [id], onDelete: Cascade) + typePiece ModelType @relation("ModelTypePieceModels", fields: [typePieceId], references: [id], onDelete: Cascade) pieces Piece[] @@ -326,7 +321,7 @@ model TypeMachineComponentRequirement { typeMachine TypeMachine @relation(fields: [typeMachineId], references: [id], onDelete: Cascade) typeComposantId String - typeComposant TypeComposant @relation(fields: [typeComposantId], references: [id]) + typeComposant ModelType @relation("ModelTypeComponentRequirements", fields: [typeComposantId], references: [id]) composants Composant[] @@ -347,7 +342,7 @@ model TypeMachinePieceRequirement { typeMachine TypeMachine @relation(fields: [typeMachineId], references: [id], onDelete: Cascade) typePieceId String - typePiece TypePiece @relation(fields: [typePieceId], references: [id]) + typePiece ModelType @relation("ModelTypePieceRequirements", fields: [typePieceId], references: [id]) pieces Piece[] diff --git a/src/app.module.ts b/src/app.module.ts index fa703f2..3fd6ec0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { CustomFieldsModule } from './custom-fields/custom-fields.module'; import { ConstructeursModule } from './constructeurs/constructeurs.module'; import { ProfilesModule } from './profiles/profiles.module'; import { SessionModule } from './session/session.module'; +import { ModelTypeModule } from './model-type/model-type.module'; @Module({ imports: [ @@ -30,6 +31,7 @@ import { SessionModule } from './session/session.module'; ConstructeursModule, ProfilesModule, SessionModule, + ModelTypeModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/composants/composants.controller.ts b/src/composants/composants.controller.ts index a01daa3..bce234b 100644 --- a/src/composants/composants.controller.ts +++ b/src/composants/composants.controller.ts @@ -1,6 +1,17 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { ComposantsService } from './composants.service'; -import { CreateComposantDto, UpdateComposantDto } from '../shared/dto/composant.dto'; +import { + CreateComposantDto, + UpdateComposantDto, +} from '../shared/dto/composant.dto'; @Controller('composants') export class ComposantsController { @@ -32,7 +43,10 @@ export class ComposantsController { } @Patch(':id') - update(@Param('id') id: string, @Body() updateComposantDto: UpdateComposantDto) { + update( + @Param('id') id: string, + @Body() updateComposantDto: UpdateComposantDto, + ) { return this.composantsService.update(id, updateComposantDto); } diff --git a/src/composants/composants.module.ts b/src/composants/composants.module.ts index f970c8c..535b62b 100644 --- a/src/composants/composants.module.ts +++ b/src/composants/composants.module.ts @@ -4,6 +4,6 @@ import { ComposantsService } from './composants.service'; @Module({ controllers: [ComposantsController], - providers: [ComposantsService] + providers: [ComposantsService], }) export class ComposantsModule {} diff --git a/src/composants/composants.service.spec.ts b/src/composants/composants.service.spec.ts index 05ac739..51a564d 100644 --- a/src/composants/composants.service.spec.ts +++ b/src/composants/composants.service.spec.ts @@ -20,7 +20,10 @@ describe('ComposantsService', () => { }; const module: TestingModule = await Test.createTestingModule({ - providers: [ComposantsService, { provide: PrismaService, useValue: prisma }], + providers: [ + ComposantsService, + { provide: PrismaService, useValue: prisma }, + ], }).compile(); service = module.get(ComposantsService); @@ -53,9 +56,9 @@ describe('ComposantsService', () => { await expect(service.create(dto)).resolves.toEqual(created); expect(prisma.composant.create).toHaveBeenCalled(); - expect( - prisma.composant.create.mock.calls[0][0].data.typeComposantId, - ).toBe('type-comp-1'); + expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe( + 'type-comp-1', + ); }); it('should refuse creation when requirement does not belong to machine skeleton', async () => { diff --git a/src/composants/composants.service.ts b/src/composants/composants.service.ts index 266c69f..61bcff8 100644 --- a/src/composants/composants.service.ts +++ b/src/composants/composants.service.ts @@ -1,6 +1,9 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateComposantDto, UpdateComposantDto } from '../shared/dto/composant.dto'; +import { + CreateComposantDto, + UpdateComposantDto, +} from '../shared/dto/composant.dto'; @Injectable() export class ComposantsService { @@ -324,9 +327,9 @@ export class ComposantsService { async findHierarchy(machineId: string) { // Récupérer tous les composants de premier niveau (sans parent) const rootComposants = await this.prisma.composant.findMany({ - where: { + where: { machineId, - parentComposantId: null + parentComposantId: null, }, include: { typeComposant: true, diff --git a/src/constructeurs/constructeurs.controller.ts b/src/constructeurs/constructeurs.controller.ts index 8aa0a00..566214b 100644 --- a/src/constructeurs/constructeurs.controller.ts +++ b/src/constructeurs/constructeurs.controller.ts @@ -1,6 +1,18 @@ -import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common' -import { ConstructeursService } from './constructeurs.service' -import { CreateConstructeurDto, UpdateConstructeurDto } from '../shared/dto/constructeur.dto' +import { + Controller, + Get, + Post, + Patch, + Delete, + Param, + Body, + Query, +} from '@nestjs/common'; +import { ConstructeursService } from './constructeurs.service'; +import { + CreateConstructeurDto, + UpdateConstructeurDto, +} from '../shared/dto/constructeur.dto'; @Controller('constructeurs') export class ConstructeursController { @@ -8,26 +20,26 @@ export class ConstructeursController { @Post() create(@Body() payload: CreateConstructeurDto) { - return this.constructeursService.create(payload) + return this.constructeursService.create(payload); } @Get() findAll(@Query('search') search?: string) { - return this.constructeursService.findAll(search) + return this.constructeursService.findAll(search); } @Get(':id') findOne(@Param('id') id: string) { - return this.constructeursService.findOne(id) + return this.constructeursService.findOne(id); } @Patch(':id') update(@Param('id') id: string, @Body() payload: UpdateConstructeurDto) { - return this.constructeursService.update(id, payload) + return this.constructeursService.update(id, payload); } @Delete(':id') remove(@Param('id') id: string) { - return this.constructeursService.remove(id) + return this.constructeursService.remove(id); } } diff --git a/src/constructeurs/constructeurs.module.ts b/src/constructeurs/constructeurs.module.ts index 80256e7..2528c15 100644 --- a/src/constructeurs/constructeurs.module.ts +++ b/src/constructeurs/constructeurs.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common' -import { ConstructeursService } from './constructeurs.service' -import { ConstructeursController } from './constructeurs.controller' +import { Module } from '@nestjs/common'; +import { ConstructeursService } from './constructeurs.service'; +import { ConstructeursController } from './constructeurs.controller'; @Module({ controllers: [ConstructeursController], diff --git a/src/constructeurs/constructeurs.service.ts b/src/constructeurs/constructeurs.service.ts index 6c46c87..96862f3 100644 --- a/src/constructeurs/constructeurs.service.ts +++ b/src/constructeurs/constructeurs.service.ts @@ -1,23 +1,26 @@ -import { Injectable } from '@nestjs/common' -import { PrismaService } from '../prisma/prisma.service' -import { CreateConstructeurDto, UpdateConstructeurDto } from '../shared/dto/constructeur.dto' -import { Prisma } from '@prisma/client' +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { + CreateConstructeurDto, + UpdateConstructeurDto, +} from '../shared/dto/constructeur.dto'; +import { Prisma } from '@prisma/client'; @Injectable() export class ConstructeursService { constructor(private prisma: PrismaService) {} private buildSearchWhere(search?: string): Prisma.ConstructeurWhereInput { - if (!search) return {} - const term = search.trim() - if (!term) return {} + if (!search) return {}; + const term = search.trim(); + if (!term) return {}; return { OR: [ { name: { contains: term, mode: 'insensitive' } }, { email: { contains: term, mode: 'insensitive' } }, { phone: { contains: term, mode: 'insensitive' } }, ], - } + }; } async create(data: CreateConstructeurDto) { @@ -28,20 +31,20 @@ export class ConstructeursService { email: data.email?.trim(), phone: data.phone?.trim(), }, - }) + }); } async findAll(search?: string) { return this.prisma.constructeur.findMany({ where: this.buildSearchWhere(search), orderBy: { name: 'asc' }, - }) + }); } async findOne(id: string) { return this.prisma.constructeur.findUnique({ where: { id }, - }) + }); } async update(id: string, data: UpdateConstructeurDto) { @@ -53,12 +56,12 @@ export class ConstructeursService { email: data.email?.trim(), phone: data.phone?.trim(), }, - }) + }); } async remove(id: string) { return this.prisma.constructeur.delete({ where: { id }, - }) + }); } } diff --git a/src/custom-fields/custom-fields.controller.ts b/src/custom-fields/custom-fields.controller.ts index b8e8aec..0d079a1 100644 --- a/src/custom-fields/custom-fields.controller.ts +++ b/src/custom-fields/custom-fields.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { CustomFieldsService } from './custom-fields.service'; import { CreateCustomFieldValueDto, @@ -12,8 +20,12 @@ export class CustomFieldsController { constructor(private readonly customFieldsService: CustomFieldsService) {} @Post('values') - createCustomFieldValue(@Body() createCustomFieldValueDto: CreateCustomFieldValueDto) { - return this.customFieldsService.createCustomFieldValue(createCustomFieldValueDto); + createCustomFieldValue( + @Body() createCustomFieldValueDto: CreateCustomFieldValueDto, + ) { + return this.customFieldsService.createCustomFieldValue( + createCustomFieldValueDto, + ); } @Get('values/:entityType/:entityId') @@ -34,7 +46,10 @@ export class CustomFieldsController { @Param('id') id: string, @Body() updateCustomFieldValueDto: UpdateCustomFieldValueDto, ) { - return this.customFieldsService.updateCustomFieldValue(id, updateCustomFieldValueDto); + return this.customFieldsService.updateCustomFieldValue( + id, + updateCustomFieldValueDto, + ); } @Delete('values/:id') @@ -51,4 +66,4 @@ export class CustomFieldsController { body.value, ); } -} \ No newline at end of file +} diff --git a/src/custom-fields/custom-fields.module.ts b/src/custom-fields/custom-fields.module.ts index 91938a5..bc86d91 100644 --- a/src/custom-fields/custom-fields.module.ts +++ b/src/custom-fields/custom-fields.module.ts @@ -9,4 +9,4 @@ import { PrismaModule } from '../prisma/prisma.module'; providers: [CustomFieldsService], exports: [CustomFieldsService], }) -export class CustomFieldsModule {} \ No newline at end of file +export class CustomFieldsModule {} diff --git a/src/custom-fields/custom-fields.service.ts b/src/custom-fields/custom-fields.service.ts index 7e979bb..be538b8 100644 --- a/src/custom-fields/custom-fields.service.ts +++ b/src/custom-fields/custom-fields.service.ts @@ -1,4 +1,8 @@ -import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateCustomFieldValueDto, @@ -11,7 +15,9 @@ export class CustomFieldsService { constructor(private prisma: PrismaService) {} // Créer une valeur de champ personnalisé - async createCustomFieldValue(createCustomFieldValueDto: CreateCustomFieldValueDto) { + async createCustomFieldValue( + createCustomFieldValueDto: CreateCustomFieldValueDto, + ) { return this.prisma.customFieldValue.create({ data: createCustomFieldValueDto, include: { @@ -30,11 +36,16 @@ export class CustomFieldsService { case CustomFieldEntityType.PIECE: return 'pieceId' as const; default: - throw new BadRequestException('Type d\'entité de champ personnalisé invalide.'); + throw new BadRequestException( + "Type d'entité de champ personnalisé invalide.", + ); } } - private async resolveEntityContext(entityType: CustomFieldEntityType, entityId: string) { + private async resolveEntityContext( + entityType: CustomFieldEntityType, + entityId: string, + ) { switch (entityType) { case CustomFieldEntityType.MACHINE: { const machine = await this.prisma.machine.findUnique({ @@ -103,7 +114,9 @@ export class CustomFieldsService { }; } default: - throw new BadRequestException('Type d\'entité de champ personnalisé invalide.'); + throw new BadRequestException( + "Type d'entité de champ personnalisé invalide.", + ); } } @@ -135,7 +148,10 @@ export class CustomFieldsService { } // Mettre à jour une valeur de champ personnalisé - async updateCustomFieldValue(id: string, updateCustomFieldValueDto: UpdateCustomFieldValueDto) { + async updateCustomFieldValue( + id: string, + updateCustomFieldValueDto: UpdateCustomFieldValueDto, + ) { return this.prisma.customFieldValue.update({ where: { id }, data: updateCustomFieldValueDto, @@ -159,10 +175,8 @@ export class CustomFieldsService { entityId: string, value: string, ) { - const { typeId, customFieldTypeField, valueKey } = await this.resolveEntityContext( - entityType, - entityId, - ); + const { typeId, customFieldTypeField, valueKey } = + await this.resolveEntityContext(entityType, entityId); const allowedCustomField = await this.prisma.customField.findFirst({ where: { @@ -173,7 +187,7 @@ export class CustomFieldsService { if (!allowedCustomField) { throw new BadRequestException( - 'Le champ personnalisé n\'est pas autorisé pour cette entité.', + "Le champ personnalisé n'est pas autorisé pour cette entité.", ); } diff --git a/src/documents/documents.controller.ts b/src/documents/documents.controller.ts index 8650ffc..47797c8 100644 --- a/src/documents/documents.controller.ts +++ b/src/documents/documents.controller.ts @@ -1,6 +1,17 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { DocumentsService } from './documents.service'; -import { CreateDocumentDto, UpdateDocumentDto } from '../shared/dto/document.dto'; +import { + CreateDocumentDto, + UpdateDocumentDto, +} from '../shared/dto/document.dto'; @Controller('documents') export class DocumentsController { @@ -42,7 +53,10 @@ export class DocumentsController { } @Patch(':id') - update(@Param('id') id: string, @Body() updateDocumentDto: UpdateDocumentDto) { + update( + @Param('id') id: string, + @Body() updateDocumentDto: UpdateDocumentDto, + ) { return this.documentsService.update(id, updateDocumentDto); } diff --git a/src/documents/documents.module.ts b/src/documents/documents.module.ts index 6b8cc3d..8849227 100644 --- a/src/documents/documents.module.ts +++ b/src/documents/documents.module.ts @@ -4,6 +4,6 @@ import { DocumentsService } from './documents.service'; @Module({ controllers: [DocumentsController], - providers: [DocumentsService] + providers: [DocumentsService], }) export class DocumentsModule {} diff --git a/src/documents/documents.service.ts b/src/documents/documents.service.ts index bb88a05..bae2249 100644 --- a/src/documents/documents.service.ts +++ b/src/documents/documents.service.ts @@ -1,6 +1,9 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateDocumentDto, UpdateDocumentDto } from '../shared/dto/document.dto'; +import { + CreateDocumentDto, + UpdateDocumentDto, +} from '../shared/dto/document.dto'; @Injectable() export class DocumentsService { diff --git a/src/machines/machines.controller.ts b/src/machines/machines.controller.ts index 60dc435..8903141 100644 --- a/src/machines/machines.controller.ts +++ b/src/machines/machines.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { MachinesService } from './machines.service'; import { CreateMachineDto, diff --git a/src/machines/machines.module.ts b/src/machines/machines.module.ts index f118532..3eda2c3 100644 --- a/src/machines/machines.module.ts +++ b/src/machines/machines.module.ts @@ -4,6 +4,6 @@ import { MachinesService } from './machines.service'; @Module({ controllers: [MachinesController], - providers: [MachinesService] + providers: [MachinesService], }) export class MachinesModule {} diff --git a/src/machines/machines.service.ts b/src/machines/machines.service.ts index 5cd4262..8cdaf86 100644 --- a/src/machines/machines.service.ts +++ b/src/machines/machines.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { ModelCategory } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { CreateMachineDto, @@ -90,6 +91,36 @@ const MACHINE_DEFAULT_INCLUDE = { export class MachinesService { constructor(private prisma: PrismaService) {} + private slugifyName(name: string): string { + return name + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .replace(/-+/g, '-'); + } + + private async generateUniqueComponentTypeCode( + prisma: any, + name: string, + ): Promise { + const base = this.slugifyName(name) || 'type'; + let candidate = base; + let suffix = 1; + + while ( + await prisma.modelType.findUnique({ + where: { code: candidate }, + select: { id: true }, + }) + ) { + candidate = `${base}-${suffix++}`; + } + + return candidate; + } + private async getTypeMachineConfiguration(typeMachineId: string) { const typeMachine = await this.prisma.typeMachine.findUnique({ where: { id: typeMachineId }, @@ -108,25 +139,40 @@ export class MachinesService { componentSelections: MachineComponentSelectionDto[], pieceSelections: MachinePieceSelectionDto[], ) { - const componentRequirements = (Array.isArray(typeMachine.componentRequirements) - ? typeMachine.componentRequirements - : []) as any[]; - const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements) - ? typeMachine.pieceRequirements - : []) as any[]; + const componentRequirements = ( + Array.isArray(typeMachine.componentRequirements) + ? typeMachine.componentRequirements + : [] + ) as any[]; + const pieceRequirements = ( + Array.isArray(typeMachine.pieceRequirements) + ? typeMachine.pieceRequirements + : [] + ) as any[]; const componentRequirementMap = new Map( - componentRequirements.map((requirement: any) => [requirement.id, requirement]), + componentRequirements.map((requirement: any) => [ + requirement.id, + requirement, + ]), ); const pieceRequirementMap = new Map( - pieceRequirements.map((requirement: any) => [requirement.id, requirement]), + pieceRequirements.map((requirement: any) => [ + requirement.id, + requirement, + ]), ); - const componentSelectionMap = new Map(); + const componentSelectionMap = new Map< + string, + MachineComponentSelectionDto[] + >(); for (const selection of componentSelections) { const requirement = componentRequirementMap.get(selection.requirementId); if (!requirement) { - throw new Error(`Sélection de composant invalide: requirementId=${selection.requirementId}`); + throw new Error( + `Sélection de composant invalide: requirementId=${selection.requirementId}`, + ); } if (!componentSelectionMap.has(requirement.id)) { componentSelectionMap.set(requirement.id, []); @@ -138,7 +184,16 @@ export class MachinesService { 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}`); + throw new Error( + `Sélection de pièce invalide: requirementId=${selection.requirementId}`, + ); + } + if (!selection.pieceModelId) { + throw new Error( + `Le groupe de pièces "${ + requirement.label || requirement.typePiece?.name || requirement.id + }" nécessite la sélection d'un modèle de pièce.`, + ); } if (!pieceSelectionMap.has(requirement.id)) { pieceSelectionMap.set(requirement.id, []); @@ -147,24 +202,36 @@ export class MachinesService { } const componentModelIds = Array.from( - new Set(componentSelections.map((selection) => selection.componentModelId).filter(Boolean)), + 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 componentModelMap = new Map( + componentModels.map((model) => [model.id, model]), + ); const pieceModelIds = Array.from( - new Set(pieceSelections.map((selection) => selection.pieceModelId).filter(Boolean)), + 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])); + const pieceModelMap = new Map( + pieceModels.map((model) => [model.id, model]), + ); for (const requirement of componentRequirements) { const selections = componentSelectionMap.get(requirement.id) ?? []; @@ -184,7 +251,9 @@ export class MachinesService { } if (!requirement.allowNewModels) { - const missingModel = selections.find((selection) => !selection.componentModelId); + 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.`, @@ -210,14 +279,6 @@ export class MachinesService { ); } - 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) { @@ -226,11 +287,15 @@ export class MachinesService { } const model = componentModelMap.get(selection.componentModelId); if (!model) { - throw new Error(`Modèle de composant introuvable: ${selection.componentModelId}`); + 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}`); + throw new Error( + `Requirement de composant introuvable: ${selection.requirementId}`, + ); } if (model.typeComposantId !== requirement.typeComposantId) { throw new Error( @@ -240,16 +305,17 @@ export class MachinesService { } 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}`); + 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}`); + throw new Error( + `Requirement de pièce introuvable: ${selection.requirementId}`, + ); } if (model.typePieceId !== requirement.typePieceId) { throw new Error( @@ -274,24 +340,36 @@ export class MachinesService { } = createMachineDto; if (!machineData.typeMachineId) { - throw new Error('typeMachineId est requis pour créer une machine à partir d\'un squelette.'); + throw new Error( + "typeMachineId est requis pour créer une machine à partir d'un squelette.", + ); } - const typeMachine = await this.getTypeMachineConfiguration(machineData.typeMachineId); + const typeMachine = await this.getTypeMachineConfiguration( + machineData.typeMachineId, + ); const { componentSelectionMap, pieceSelectionMap, componentModelMap, pieceModelMap, - } = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections); + } = await this.buildConfigurationContext( + typeMachine, + componentSelections, + pieceSelections, + ); - const componentRequirements = (Array.isArray(typeMachine.componentRequirements) - ? typeMachine.componentRequirements - : []) as any[]; - const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements) - ? typeMachine.pieceRequirements - : []) as any[]; + const componentRequirements = ( + Array.isArray(typeMachine.componentRequirements) + ? typeMachine.componentRequirements + : [] + ) as any[]; + const pieceRequirements = ( + Array.isArray(typeMachine.pieceRequirements) + ? typeMachine.pieceRequirements + : [] + ) as any[]; return this.prisma.$transaction(async (prisma) => { const machine = await prisma.machine.create({ @@ -307,15 +385,27 @@ export class MachinesService { for (const requirement of 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]); + 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); + await this.createComponentsFromType( + prisma, + machine.id, + legacyComponents, + ); } } @@ -323,20 +413,36 @@ export class MachinesService { for (const requirement of 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]); + 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); + await this.createMachinePiecesFromType( + prisma, + machine.id, + legacyPieces, + ); } } if (typeMachine.customFields && typeMachine.customFields.length > 0) { - await this.createMachineCustomFieldsFromType(prisma, machine.id, typeMachine.customFields); + await this.createMachineCustomFieldsFromType( + prisma, + machine.id, + typeMachine.customFields, + ); } return prisma.machine.findUnique({ @@ -371,28 +477,43 @@ export class MachinesService { requirement: any, model?: any, ): any { - const baseDefinition = selection.definition ?? (model?.structure ?? {}); + const baseDefinition = selection.definition ?? model?.structure ?? {}; const definition = this.cloneStructure(baseDefinition); - const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {}; + 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.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.customFields = Array.isArray(prepared.customFields) + ? prepared.customFields + : []; prepared.pieces = Array.isArray(prepared.pieces) ? prepared.pieces : prepared.pieces - ? [prepared.pieces] - : []; + ? [prepared.pieces] + : []; prepared.subComponents = Array.isArray(prepared.subComponents) ? prepared.subComponents : prepared.subComponents - ? [prepared.subComponents] - : []; + ? [prepared.subComponents] + : []; - prepared.typeComposantId = prepared.typeComposantId || requirement?.typeComposantId || model?.typeComposantId || null; + prepared.typeComposantId = + prepared.typeComposantId || + requirement?.typeComposantId || + model?.typeComposantId || + null; prepared.__componentModelId = selection.componentModelId ?? null; prepared.__requirementId = requirement?.id ?? null; @@ -404,43 +525,80 @@ export class MachinesService { 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 : {}; + if (!model) { + throw new Error( + `Modèle de pièce introuvable: ${selection.pieceModelId}`, + ); + } - 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; + const baseDefinition = model.structure ?? {}; + const definition = this.cloneStructure(baseDefinition); + const prepared: any = + definition && typeof definition === 'object' && !Array.isArray(definition) + ? definition + : {}; + + prepared.name = prepared.name || model.name || 'Pièce'; + prepared.customFields = Array.isArray(prepared.customFields) + ? prepared.customFields + : []; + prepared.typePieceId = + prepared.typePieceId || + model.typePieceId || + requirement?.typePieceId || + null; + prepared.__pieceModelId = selection.pieceModelId; prepared.__requirementId = requirement?.id ?? null; return prepared; } - private async createComponentsFromType(prisma: any, machineId: string, components: any[], parentComposantId?: string) { + private async createComponentsFromType( + prisma: any, + machineId: string, + components: any[], + parentComposantId?: string, + ) { for (const component of components) { if (!component || !component.name) continue; - 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 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); + 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 }, + let typeComposant = await prisma.modelType.findFirst({ + where: { + name: component.name, + category: ModelCategory.COMPONENT, + }, }); if (!typeComposant) { - typeComposant = await prisma.typeComposant.create({ + typeComposant = await prisma.modelType.create({ data: { name: component.name, + code: await this.generateUniqueComponentTypeCode( + prisma, + component.name, + ), + category: ModelCategory.COMPONENT, description: component.description || '', }, }); @@ -466,7 +624,10 @@ export class MachinesService { data: { name: component.name, reference: component.reference || '', - constructeurId: await this.resolveConstructeurId(prisma, component.constructeur), + constructeurId: await this.resolveConstructeurId( + prisma, + component.constructeur, + ), emplacement: component.emplacement || '', prix: component.prix ?? null, machineId, @@ -483,7 +644,9 @@ export class MachinesService { }); for (const customField of typeCustomFields) { - const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + customFields.find((cf) => cf.name === customField.name) + ?.defaultValue || ''; await prisma.customFieldValue.create({ data: { value: defaultValue, @@ -497,11 +660,14 @@ export class MachinesService { for (const piece of componentPieces) { if (!piece || !piece.name) continue; - const pieceCustomFields = Array.isArray(piece.customFields) ? piece.customFields : []; + 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); + const providedTypePieceId = + piece.typePieceId ?? + (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null); let typePieceId: string | null = providedTypePieceId ?? null; @@ -539,7 +705,10 @@ export class MachinesService { data: { name: piece.name, reference: piece.reference || '', - constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur), + constructeurId: await this.resolveConstructeurId( + prisma, + piece.constructeur, + ), emplacement: piece.emplacement || '', prix: piece.prix ?? null, composantId: createdComposant.id, @@ -555,7 +724,9 @@ export class MachinesService { }); for (const customField of typePieceCustomFields) { - const defaultValue = pieceCustomFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + pieceCustomFields.find((cf) => cf.name === customField.name) + ?.defaultValue || ''; await prisma.customFieldValue.create({ data: { value: defaultValue, @@ -568,20 +739,32 @@ export class MachinesService { } if (subComponents.length > 0) { - await this.createComponentsFromType(prisma, machineId, subComponents, createdComposant.id); + await this.createComponentsFromType( + prisma, + machineId, + subComponents, + createdComposant.id, + ); } } } - private async createMachinePiecesFromType(prisma: any, machineId: string, machinePieces: any[]) { + 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 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); + const providedTypePieceId = + piece.typePieceId ?? + (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null); let typePieceId: string | null = providedTypePieceId ?? null; @@ -619,7 +802,10 @@ export class MachinesService { data: { name: piece.name, reference: piece.reference || '', - constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur), + constructeurId: await this.resolveConstructeurId( + prisma, + piece.constructeur, + ), emplacement: piece.emplacement || '', prix: piece.prix ?? null, machineId, @@ -635,7 +821,9 @@ export class MachinesService { }); for (const customField of typePieceCustomFields) { - const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + customFields.find((cf) => cf.name === customField.name) + ?.defaultValue || ''; await prisma.customFieldValue.create({ data: { value: defaultValue, @@ -669,10 +857,14 @@ export class MachinesService { } } - private async createMachineCustomFieldsFromType(prisma: any, machineId: string, machineCustomFields: any[]) { + private async createMachineCustomFieldsFromType( + prisma: any, + machineId: string, + machineCustomFields: any[], + ) { for (const customField of machineCustomFields) { if (!customField || !customField.name) continue; - + // Créer le champ personnalisé pour la machine const createdCustomField = await prisma.customField.create({ data: { @@ -710,10 +902,8 @@ export class MachinesService { } async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) { - const { - componentSelections = [], - pieceSelections = [], - } = reconfigureMachineDto; + const { componentSelections = [], pieceSelections = [] } = + reconfigureMachineDto; const machine = await this.prisma.machine.findUnique({ where: { id }, @@ -729,7 +919,9 @@ export class MachinesService { } if (!machine.typeMachineId || !machine.typeMachine) { - throw new Error('Impossible de reconfigurer une machine sans type de machine associé.'); + throw new Error( + 'Impossible de reconfigurer une machine sans type de machine associé.', + ); } const typeMachine = machine.typeMachine; @@ -739,14 +931,22 @@ export class MachinesService { pieceSelectionMap, componentModelMap, pieceModelMap, - } = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections); + } = await this.buildConfigurationContext( + typeMachine, + componentSelections, + pieceSelections, + ); - const componentRequirements = (Array.isArray(typeMachine.componentRequirements) - ? typeMachine.componentRequirements - : []) as any[]; - const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements) - ? typeMachine.pieceRequirements - : []) as any[]; + const componentRequirements = ( + Array.isArray(typeMachine.componentRequirements) + ? typeMachine.componentRequirements + : [] + ) as any[]; + const pieceRequirements = ( + Array.isArray(typeMachine.pieceRequirements) + ? typeMachine.pieceRequirements + : [] + ) as any[]; return this.prisma.$transaction(async (prisma) => { await prisma.customFieldValue.deleteMany({ @@ -797,7 +997,11 @@ export class MachinesService { const model = selection.componentModelId ? componentModelMap.get(selection.componentModelId) : undefined; - const definition = this.normalizeComponentSelection(selection, requirement, model); + const definition = this.normalizeComponentSelection( + selection, + requirement, + model, + ); await this.createComponentsFromType(prisma, id, [definition]); } } @@ -815,7 +1019,11 @@ export class MachinesService { const model = selection.pieceModelId ? pieceModelMap.get(selection.pieceModelId) : undefined; - const definition = this.normalizePieceSelection(selection, requirement, model); + const definition = this.normalizePieceSelection( + selection, + requirement, + model, + ); await this.createMachinePiecesFromType(prisma, id, [definition]); } } @@ -842,9 +1050,9 @@ export class MachinesService { } private async resolveConstructeurId(prisma: any, rawName?: string) { - if (!rawName) return null - const name = String(rawName).trim() - if (!name) return null + if (!rawName) return null; + const name = String(rawName).trim(); + if (!name) return null; const existing = await prisma.constructeur.findFirst({ where: { @@ -853,15 +1061,15 @@ export class MachinesService { mode: 'insensitive', }, }, - }) + }); - if (existing) return existing.id + if (existing) return existing.id; const created = await prisma.constructeur.create({ data: { name }, - }) + }); - return created.id + return created.id; } async remove(id: string) { @@ -983,17 +1191,31 @@ export class MachinesService { // Traiter les composants existants for (const component of machine.composants) { - const typeComponent = components.find((c: any) => c.name === component.name); - if (typeComponent && typeComponent.customFields && typeComponent.customFields.length > 0) { + const typeComponent = components.find( + (c: any) => c.name === component.name, + ); + if ( + typeComponent && + typeComponent.customFields && + typeComponent.customFields.length > 0 + ) { // Créer le type de composant s'il n'existe pas - let typeComposant = await this.prisma.typeComposant.findFirst({ - where: { name: component.name }, + let typeComposant = await this.prisma.modelType.findFirst({ + where: { + name: component.name, + category: ModelCategory.COMPONENT, + }, }); if (!typeComposant) { - typeComposant = await this.prisma.typeComposant.create({ + typeComposant = await this.prisma.modelType.create({ data: { name: component.name, + code: await this.generateUniqueComponentTypeCode( + this.prisma, + component.name, + ), + category: ModelCategory.COMPONENT, description: typeComponent.description || '', }, }); @@ -1042,7 +1264,10 @@ export class MachinesService { }); if (!existingValue) { - const defaultValue = typeComponent.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + typeComponent.customFields.find( + (cf: any) => cf.name === customField.name, + )?.defaultValue || ''; await this.prisma.customFieldValue.create({ data: { value: defaultValue, @@ -1055,18 +1280,33 @@ export class MachinesService { // Traiter les pièces du composant for (const piece of component.pieces) { - const typePiece = typeComponent.pieces?.find((p: any) => p.name === piece.name); - if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) { + const typePiece = typeComponent.pieces?.find( + (p: any) => p.name === piece.name, + ); + if ( + typePiece && + typePiece.customFields && + typePiece.customFields.length > 0 + ) { // Créer le type de pièce s'il n'existe pas - let typePieceEntity = await this.prisma.typePiece.findFirst({ - where: { name: piece.name }, + let typePieceEntity = await this.prisma.modelType.findFirst({ + where: { + name: piece.name, + category: ModelCategory.PIECE, + }, }); if (!typePieceEntity) { - typePieceEntity = await this.prisma.typePiece.create({ + typePieceEntity = await this.prisma.modelType.create({ data: { name: piece.name, + code: await this.generateUniqueComponentTypeCode( + this.prisma, + piece.name, + ), + category: ModelCategory.PIECE, description: typePiece.description || '', + notes: typePiece.description || '', }, }); } @@ -1106,15 +1346,19 @@ export class MachinesService { }); for (const customField of customFields) { - const existingValue = await this.prisma.customFieldValue.findFirst({ - where: { - customFieldId: customField.id, - pieceId: piece.id, - }, - }); + const existingValue = + await this.prisma.customFieldValue.findFirst({ + where: { + customFieldId: customField.id, + pieceId: piece.id, + }, + }); if (!existingValue) { - const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + typePiece.customFields.find( + (cf: any) => cf.name === customField.name, + )?.defaultValue || ''; await this.prisma.customFieldValue.create({ data: { value: defaultValue, @@ -1132,17 +1376,30 @@ export class MachinesService { // Traiter les pièces de machine for (const piece of machine.pieces) { const typePiece = machinePieces.find((p: any) => p.name === piece.name); - if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) { + if ( + typePiece && + typePiece.customFields && + typePiece.customFields.length > 0 + ) { // Créer le type de pièce s'il n'existe pas - let typePieceEntity = await this.prisma.typePiece.findFirst({ - where: { name: piece.name }, + let typePieceEntity = await this.prisma.modelType.findFirst({ + where: { + name: piece.name, + category: ModelCategory.PIECE, + }, }); if (!typePieceEntity) { - typePieceEntity = await this.prisma.typePiece.create({ + typePieceEntity = await this.prisma.modelType.create({ data: { name: piece.name, + code: await this.generateUniqueComponentTypeCode( + this.prisma, + piece.name, + ), + category: ModelCategory.PIECE, description: typePiece.description || '', + notes: typePiece.description || '', }, }); } @@ -1190,7 +1447,10 @@ export class MachinesService { }); if (!existingValue) { - const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || ''; + const defaultValue = + typePiece.customFields.find( + (cf: any) => cf.name === customField.name, + )?.defaultValue || ''; await this.prisma.customFieldValue.create({ data: { value: defaultValue, diff --git a/src/model-type/dto/create-model-type.dto.ts b/src/model-type/dto/create-model-type.dto.ts new file mode 100644 index 0000000..87c92d0 --- /dev/null +++ b/src/model-type/dto/create-model-type.dto.ts @@ -0,0 +1,28 @@ +import { IsEnum, IsOptional, IsString, Length, Matches } from 'class-validator'; + +export enum ModelCategory { + COMPONENT = 'COMPONENT', + PIECE = 'PIECE', +} + +export class CreateModelTypeDto { + @IsString() + @Length(2, 120) + name!: string; + + @IsString() + @Length(2, 60) + @Matches(/^[a-z0-9\-_.]+$/i) + code!: string; + + @IsEnum(ModelCategory) + category!: ModelCategory; + + @IsOptional() + @IsString() + notes?: string; + + @IsOptional() + @IsString() + description?: string; +} diff --git a/src/model-type/dto/update-model-type.dto.ts b/src/model-type/dto/update-model-type.dto.ts new file mode 100644 index 0000000..012bdd4 --- /dev/null +++ b/src/model-type/dto/update-model-type.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateModelTypeDto } from './create-model-type.dto'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-call +export class UpdateModelTypeDto extends PartialType(CreateModelTypeDto) {} diff --git a/src/model-type/model-type.controller.ts b/src/model-type/model-type.controller.ts new file mode 100644 index 0000000..e5c4df1 --- /dev/null +++ b/src/model-type/model-type.controller.ts @@ -0,0 +1,80 @@ +import { + Body, + Controller, + Delete, + Get, + Header, + HttpCode, + HttpStatus, + Param, + Patch, + Post, + Query, +} from '@nestjs/common'; +import { CreateModelTypeDto, ModelCategory } from './dto/create-model-type.dto'; +import { UpdateModelTypeDto } from './dto/update-model-type.dto'; +import { ModelTypeService } from './model-type.service'; + +@Controller('api/model-types') +export class ModelTypeController { + constructor(private readonly modelTypeService: ModelTypeService) {} + + @Get() + list( + @Query('q') q?: string, + @Query('category') category?: string, + @Query('sort') sort?: 'name' | 'code' | 'createdAt', + @Query('dir') dir?: 'asc' | 'desc', + @Query('limit') limit?: string, + @Query('offset') offset?: string, + ) { + return this.modelTypeService.list({ + q, + category: this.normalizeCategory(category), + sort, + dir, + limit: this.parsePositiveNumber(limit), + offset: this.parsePositiveNumber(offset), + }); + } + + @Post() + @Header('Cache-Control', 'no-store') + create(@Body() dto: CreateModelTypeDto) { + return this.modelTypeService.create(dto); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.modelTypeService.findOne(id); + } + + @Patch(':id') + @Header('Cache-Control', 'no-store') + update(@Param('id') id: string, @Body() dto: UpdateModelTypeDto) { + return this.modelTypeService.update(id, dto); + } + + @Delete(':id') + @Header('Cache-Control', 'no-store') + @HttpCode(HttpStatus.NO_CONTENT) + async remove(@Param('id') id: string) { + await this.modelTypeService.remove(id); + } + + private normalizeCategory(value?: string) { + if (!value) return undefined; + return Object.values(ModelCategory).includes(value as ModelCategory) + ? (value as ModelCategory) + : undefined; + } + + private parsePositiveNumber(value?: string) { + if (value === undefined) return undefined; + const parsed = Number.parseInt(value, 10); + if (Number.isNaN(parsed) || parsed < 0) { + return undefined; + } + return parsed; + } +} diff --git a/src/model-type/model-type.module.ts b/src/model-type/model-type.module.ts new file mode 100644 index 0000000..f3dafff --- /dev/null +++ b/src/model-type/model-type.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ModelTypeController } from './model-type.controller'; +import { ModelTypeService } from './model-type.service'; + +@Module({ + controllers: [ModelTypeController], + providers: [ModelTypeService], +}) +export class ModelTypeModule {} diff --git a/src/model-type/model-type.service.ts b/src/model-type/model-type.service.ts new file mode 100644 index 0000000..c7fec94 --- /dev/null +++ b/src/model-type/model-type.service.ts @@ -0,0 +1,159 @@ +import { + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { ModelType as PrismaModelType, Prisma } from '@prisma/client'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateModelTypeDto, ModelCategory } from './dto/create-model-type.dto'; +import { UpdateModelTypeDto } from './dto/update-model-type.dto'; + +type SortField = 'name' | 'code' | 'createdAt'; +type SortDirection = 'asc' | 'desc'; + +interface ListParams { + q?: string; + category?: ModelCategory; + sort?: SortField; + dir?: SortDirection; + limit?: number; + offset?: number; +} + +@Injectable() +export class ModelTypeService { + private readonly allowedSortFields: SortField[] = [ + 'name', + 'code', + 'createdAt', + ]; + + constructor(private readonly prisma: PrismaService) {} + + async list(params: ListParams): Promise<{ + items: PrismaModelType[]; + total: number; + offset: number; + limit: number; + }> { + const { + q, + category, + sort = 'createdAt', + dir = 'desc', + limit = 20, + offset = 0, + } = params; + + const cappedLimit = Math.min(Math.max(limit ?? 20, 1), 100); + const safeOffset = Math.max(offset ?? 0, 0); + + const orderByField = this.allowedSortFields.includes(sort) + ? sort + : 'createdAt'; + const orderByDir: SortDirection = dir === 'asc' ? 'asc' : 'desc'; + + const where: Prisma.ModelTypeWhereInput = {}; + + if (category) { + where.category = category; + } + + if (q?.trim()) { + const term = q.trim(); + where.OR = [ + { name: { contains: term, mode: 'insensitive' } }, + { code: { contains: term, mode: 'insensitive' } }, + ]; + } + + const [items, total] = await this.prisma.$transaction([ + this.prisma.modelType.findMany({ + where, + orderBy: { [orderByField]: orderByDir }, + skip: safeOffset, + take: cappedLimit, + }), + this.prisma.modelType.count({ where }), + ]); + + return { + items, + total, + offset: safeOffset, + limit: cappedLimit, + }; + } + + async create(dto: CreateModelTypeDto): Promise { + try { + return await this.prisma.modelType.create({ + data: { + name: dto.name, + code: dto.code, + category: dto.category, + notes: dto.notes, + description: dto.description ?? null, + }, + }); + } catch (error) { + this.handlePrismaError(error); + } + } + + async update(id: string, dto: UpdateModelTypeDto): Promise { + try { + return await this.prisma.modelType.update({ + where: { id }, + data: { + ...dto, + description: + dto.description === undefined ? undefined : dto.description ?? null, + }, + }); + } catch (error) { + this.handlePrismaError(error); + } + } + + async remove(id: string): Promise { + try { + await this.prisma.modelType.delete({ where: { id } }); + } catch (error) { + this.handlePrismaError(error); + } + } + + async findOne(id: string): Promise { + const modelType = await this.prisma.modelType.findUnique({ where: { id } }); + if (!modelType) { + throw new NotFoundException('Type de modèle introuvable.'); + } + return modelType; + } + + private handlePrismaError(error: unknown): never { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === 'P2002' && this.isUniqueCodeConstraint(error)) { + throw new ConflictException('Ce code est déjà utilisé.'); + } + + if (error.code === 'P2025') { + throw new NotFoundException('Type de modèle introuvable.'); + } + } + + throw error; + } + + private isUniqueCodeConstraint(error: Prisma.PrismaClientKnownRequestError) { + const { target } = error.meta ?? {}; + if (Array.isArray(target)) { + return target.includes('code'); + } + if (typeof target === 'string') { + return target === 'code'; + } + return false; + } +} diff --git a/src/pieces/pieces.controller.ts b/src/pieces/pieces.controller.ts index a03f4bf..25b65ff 100644 --- a/src/pieces/pieces.controller.ts +++ b/src/pieces/pieces.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { PiecesService } from './pieces.service'; import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto'; diff --git a/src/pieces/pieces.module.ts b/src/pieces/pieces.module.ts index 36c953d..819f5a1 100644 --- a/src/pieces/pieces.module.ts +++ b/src/pieces/pieces.module.ts @@ -4,6 +4,6 @@ import { PiecesService } from './pieces.service'; @Module({ controllers: [PiecesController], - providers: [PiecesService] + providers: [PiecesService], }) export class PiecesModule {} diff --git a/src/pieces/pieces.service.spec.ts b/src/pieces/pieces.service.spec.ts index 6837166..02d180a 100644 --- a/src/pieces/pieces.service.spec.ts +++ b/src/pieces/pieces.service.spec.ts @@ -43,9 +43,7 @@ describe('PiecesService', () => { prisma.machine.findUnique.mockResolvedValue({ id: 'machine-1', typeMachine: { - pieceRequirements: [ - { id: 'req-1', typePieceId: 'type-piece-1' }, - ], + pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }], }, }); @@ -70,9 +68,7 @@ describe('PiecesService', () => { prisma.machine.findUnique.mockResolvedValue({ id: 'machine-1', typeMachine: { - pieceRequirements: [ - { id: 'req-1', typePieceId: 'type-piece-1' }, - ], + pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }], }, }); diff --git a/src/pieces/pieces.service.ts b/src/pieces/pieces.service.ts index 5980c22..2a3188e 100644 --- a/src/pieces/pieces.service.ts +++ b/src/pieces/pieces.service.ts @@ -180,9 +180,7 @@ export class PiecesService { }); if (!composant) { - throw new BadRequestException( - 'Le composant spécifié est introuvable.', - ); + throw new BadRequestException('Le composant spécifié est introuvable.'); } if (composant.machineId) { diff --git a/src/prisma/prisma.module.ts b/src/prisma/prisma.module.ts index 68092fe..7207426 100644 --- a/src/prisma/prisma.module.ts +++ b/src/prisma/prisma.module.ts @@ -6,4 +6,4 @@ import { PrismaService } from './prisma.service'; providers: [PrismaService], exports: [PrismaService], }) -export class PrismaModule {} \ No newline at end of file +export class PrismaModule {} diff --git a/src/prisma/prisma.service.ts b/src/prisma/prisma.service.ts index d6c54bb..5541f1e 100644 --- a/src/prisma/prisma.service.ts +++ b/src/prisma/prisma.service.ts @@ -2,7 +2,10 @@ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { +export class PrismaService + extends PrismaClient + implements OnModuleInit, OnModuleDestroy +{ constructor() { super({ log: ['query', 'info', 'warn', 'error'], @@ -16,4 +19,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul async onModuleDestroy() { await this.$disconnect(); } -} \ No newline at end of file +} diff --git a/src/profiles/profiles.controller.ts b/src/profiles/profiles.controller.ts index 745de1f..2591a25 100644 --- a/src/profiles/profiles.controller.ts +++ b/src/profiles/profiles.controller.ts @@ -1,6 +1,6 @@ -import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common' -import { ProfilesService } from './profiles.service' -import { CreateProfileDto } from '../shared/dto/profile.dto' +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { ProfilesService } from './profiles.service'; +import { CreateProfileDto } from '../shared/dto/profile.dto'; @Controller('profiles') export class ProfilesController { @@ -8,16 +8,16 @@ export class ProfilesController { @Get() async findAll() { - return this.profilesService.findAllActive() + return this.profilesService.findAllActive(); } @Post() async create(@Body() dto: CreateProfileDto) { - return this.profilesService.create(dto) + return this.profilesService.create(dto); } @Delete(':id') async delete(@Param('id') id: string) { - return this.profilesService.deactivate(id) + return this.profilesService.deactivate(id); } } diff --git a/src/profiles/profiles.module.ts b/src/profiles/profiles.module.ts index 22407c5..f39623c 100644 --- a/src/profiles/profiles.module.ts +++ b/src/profiles/profiles.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common' -import { ProfilesController } from './profiles.controller' -import { ProfilesService } from './profiles.service' +import { Module } from '@nestjs/common'; +import { ProfilesController } from './profiles.controller'; +import { ProfilesService } from './profiles.service'; @Module({ controllers: [ProfilesController], diff --git a/src/profiles/profiles.service.ts b/src/profiles/profiles.service.ts index de29893..f41ec75 100644 --- a/src/profiles/profiles.service.ts +++ b/src/profiles/profiles.service.ts @@ -1,13 +1,18 @@ -import { Injectable, NotFoundException, BadRequestException, OnModuleInit } from '@nestjs/common' -import { PrismaService } from '../prisma/prisma.service' -import { CreateProfileDto } from '../shared/dto/profile.dto' +import { + Injectable, + NotFoundException, + BadRequestException, + OnModuleInit, +} from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { CreateProfileDto } from '../shared/dto/profile.dto'; @Injectable() export class ProfilesService implements OnModuleInit { constructor(private readonly prisma: PrismaService) {} async onModuleInit() { - await this.ensureDefaultProfile() + await this.ensureDefaultProfile(); } async findAllActive() { @@ -21,11 +26,11 @@ export class ProfilesService implements OnModuleInit { createdAt: true, updatedAt: true, }, - }) + }); } async findActiveById(profileId: string) { - if (!profileId) return null + if (!profileId) return null; return this.prisma.profile.findFirst({ where: { @@ -40,15 +45,15 @@ export class ProfilesService implements OnModuleInit { updatedAt: true, isActive: true, }, - }) + }); } async create(dto: CreateProfileDto) { - const firstName = dto.firstName.trim() - const lastName = dto.lastName.trim() + const firstName = dto.firstName.trim(); + const lastName = dto.lastName.trim(); if (!firstName || !lastName) { - throw new BadRequestException('Le prénom et le nom sont obligatoires.') + throw new BadRequestException('Le prénom et le nom sont obligatoires.'); } return this.prisma.profile.create({ @@ -64,13 +69,15 @@ export class ProfilesService implements OnModuleInit { createdAt: true, updatedAt: true, }, - }) + }); } async deactivate(profileId: string) { - const existing = await this.prisma.profile.findUnique({ where: { id: profileId } }) + const existing = await this.prisma.profile.findUnique({ + where: { id: profileId }, + }); if (!existing) { - throw new NotFoundException('Profil introuvable') + throw new NotFoundException('Profil introuvable'); } if (!existing.isActive) { @@ -84,7 +91,7 @@ export class ProfilesService implements OnModuleInit { createdAt: true, updatedAt: true, }, - }) + }); } return this.prisma.profile.update({ @@ -98,27 +105,34 @@ export class ProfilesService implements OnModuleInit { createdAt: true, updatedAt: true, }, - }) + }); } private async ensureDefaultProfile() { - const count = await this.prisma.profile.count({ where: { isActive: true } }).catch((err) => { - console.error('Failed to count profiles during ensureDefaultProfile:', err.message) - return 0 - }) - if (count > 0) return + const count = await this.prisma.profile + .count({ where: { isActive: true } }) + .catch((err) => { + console.error( + 'Failed to count profiles during ensureDefaultProfile:', + err.message, + ); + return 0; + }); + if (count > 0) return; - const firstName = process.env.DEFAULT_PROFILE_FIRST_NAME?.trim() || 'Admin' - const lastName = process.env.DEFAULT_PROFILE_LAST_NAME?.trim() || 'Général' + const firstName = process.env.DEFAULT_PROFILE_FIRST_NAME?.trim() || 'Admin'; + const lastName = process.env.DEFAULT_PROFILE_LAST_NAME?.trim() || 'Général'; - await this.prisma.profile.create({ - data: { - firstName, - lastName, - isActive: true, - }, - }).catch((err) => { - console.error('Failed to create default profile:', err.message) - }) + await this.prisma.profile + .create({ + data: { + firstName, + lastName, + isActive: true, + }, + }) + .catch((err) => { + console.error('Failed to create default profile:', err.message); + }); } } diff --git a/src/session/session.controller.ts b/src/session/session.controller.ts index 3eea150..fdf60c7 100644 --- a/src/session/session.controller.ts +++ b/src/session/session.controller.ts @@ -7,10 +7,10 @@ import { Post, Req, UnauthorizedException, -} from '@nestjs/common' -import { Request } from 'express' -import { ProfilesService } from '../profiles/profiles.service' -import { ActivateProfileDto } from '../shared/dto/profile.dto' +} from '@nestjs/common'; +import { Request } from 'express'; +import { ProfilesService } from '../profiles/profiles.service'; +import { ActivateProfileDto } from '../shared/dto/profile.dto'; @Controller('session/profile') export class ProfileSessionController { @@ -19,46 +19,50 @@ export class ProfileSessionController { @Get() async getActiveProfile(@Req() req: Request) { if (!req.session?.profileId) { - throw new UnauthorizedException('Aucun profil actif.') + throw new UnauthorizedException('Aucun profil actif.'); } - const profile = await this.profilesService.findActiveById(req.session.profileId) + const profile = await this.profilesService.findActiveById( + req.session.profileId, + ); if (!profile) { - req.session.profileId = undefined - throw new UnauthorizedException('Profil introuvable ou inactif.') + req.session.profileId = undefined; + throw new UnauthorizedException('Profil introuvable ou inactif.'); } - return profile + return profile; } @Post() async activateProfile(@Req() req: Request, @Body() dto: ActivateProfileDto) { if (!dto.profileId) { - throw new BadRequestException('profileId est requis.') + throw new BadRequestException('profileId est requis.'); } - const profile = await this.profilesService.findActiveById(dto.profileId) + const profile = await this.profilesService.findActiveById(dto.profileId); if (!profile) { - throw new UnauthorizedException('Profil introuvable ou inactif.') + throw new UnauthorizedException('Profil introuvable ou inactif.'); } - req.session.profileId = profile.id - return profile + req.session.profileId = profile.id; + return profile; } @Delete() async logout(@Req() req: Request) { if (!req.session) { - return { success: true } + return { success: true }; } return new Promise((resolve, reject) => { req.session.destroy((err) => { if (err) { - return reject(new BadRequestException('Impossible de déconnecter la session.')) + return reject( + new BadRequestException('Impossible de déconnecter la session.'), + ); } - resolve({ success: true }) - }) - }) + resolve({ success: true }); + }); + }); } } diff --git a/src/session/session.module.ts b/src/session/session.module.ts index a3d917d..d270928 100644 --- a/src/session/session.module.ts +++ b/src/session/session.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common' -import { ProfileSessionController } from './session.controller' -import { ProfilesModule } from '../profiles/profiles.module' +import { Module } from '@nestjs/common'; +import { ProfileSessionController } from './session.controller'; +import { ProfilesModule } from '../profiles/profiles.module'; @Module({ imports: [ProfilesModule], diff --git a/src/shared/dto/composant.dto.ts b/src/shared/dto/composant.dto.ts index ba02f9d..87442b6 100644 --- a/src/shared/dto/composant.dto.ts +++ b/src/shared/dto/composant.dto.ts @@ -22,7 +22,7 @@ export class CreateComposantDto { constructeurId?: string; @IsOptional() - @Transform(({ value }) => value === '' ? null : value) + @Transform(({ value }) => (value === '' ? null : value)) @IsNumber({}, { message: 'prix must be a valid number' }) prix?: number | null; @@ -56,7 +56,7 @@ export class UpdateComposantDto { constructeurId?: string; @IsOptional() - @Transform(({ value }) => value === '' ? null : value) + @Transform(({ value }) => (value === '' ? null : value)) @IsNumber({}, { message: 'prix must be a valid number' }) prix?: number | null; diff --git a/src/shared/dto/constructeur.dto.ts b/src/shared/dto/constructeur.dto.ts index 756da9b..9ab86b5 100644 --- a/src/shared/dto/constructeur.dto.ts +++ b/src/shared/dto/constructeur.dto.ts @@ -1,28 +1,28 @@ -import { IsEmail, IsOptional, IsString } from 'class-validator' +import { IsEmail, IsOptional, IsString } from 'class-validator'; export class CreateConstructeurDto { @IsString() - name: string + name: string; @IsOptional() @IsEmail() - email?: string + email?: string; @IsOptional() @IsString() - phone?: string + phone?: string; } export class UpdateConstructeurDto { @IsOptional() @IsString() - name?: string + name?: string; @IsOptional() @IsEmail() - email?: string + email?: string; @IsOptional() @IsString() - phone?: string + phone?: string; } diff --git a/src/shared/dto/custom-field.dto.ts b/src/shared/dto/custom-field.dto.ts index 70aa9d5..1631853 100644 --- a/src/shared/dto/custom-field.dto.ts +++ b/src/shared/dto/custom-field.dto.ts @@ -58,4 +58,4 @@ export class UpdateCustomFieldValueDto { @IsOptional() @IsString() value?: string; -} \ No newline at end of file +} diff --git a/src/shared/dto/machine.dto.ts b/src/shared/dto/machine.dto.ts index a798000..8a73694 100644 --- a/src/shared/dto/machine.dto.ts +++ b/src/shared/dto/machine.dto.ts @@ -18,12 +18,8 @@ export class MachinePieceSelectionDto { @IsString() requirementId: string; - @IsOptional() @IsString() - pieceModelId?: string; - - @IsOptional() - definition?: any; + pieceModelId: string; } export class CreateMachineDto { diff --git a/src/shared/dto/piece.dto.ts b/src/shared/dto/piece.dto.ts index b7d7d2f..eb49600 100644 --- a/src/shared/dto/piece.dto.ts +++ b/src/shared/dto/piece.dto.ts @@ -22,7 +22,7 @@ export class CreatePieceDto { constructeurId?: string; @IsOptional() - @Transform(({ value }) => value === '' ? null : value) + @Transform(({ value }) => (value === '' ? null : value)) @IsNumber({}, { message: 'prix must be a valid number' }) prix?: number | null; @@ -56,7 +56,7 @@ export class UpdatePieceDto { constructeurId?: string; @IsOptional() - @Transform(({ value }) => value === '' ? null : value) + @Transform(({ value }) => (value === '' ? null : value)) @IsNumber({}, { message: 'prix must be a valid number' }) prix?: number | null; diff --git a/src/shared/dto/profile.dto.ts b/src/shared/dto/profile.dto.ts index 1a797ee..567b9f1 100644 --- a/src/shared/dto/profile.dto.ts +++ b/src/shared/dto/profile.dto.ts @@ -1,19 +1,19 @@ -import { IsNotEmpty, IsString, MaxLength } from 'class-validator' +import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; export class CreateProfileDto { @IsString() @IsNotEmpty() @MaxLength(100) - firstName!: string + firstName!: string; @IsString() @IsNotEmpty() @MaxLength(100) - lastName!: string + lastName!: string; } export class ActivateProfileDto { @IsString() @IsNotEmpty() - profileId!: string + profileId!: string; } diff --git a/src/shared/dto/type.dto.ts b/src/shared/dto/type.dto.ts index 71719c2..ebbe017 100644 --- a/src/shared/dto/type.dto.ts +++ b/src/shared/dto/type.dto.ts @@ -1,4 +1,12 @@ -import { IsString, IsOptional, IsArray, IsObject, IsBoolean, IsEnum, IsInt } from 'class-validator'; +import { + IsString, + IsOptional, + IsArray, + IsObject, + IsBoolean, + IsEnum, + IsInt, +} from 'class-validator'; import { Type } from 'class-transformer'; import { ValidateNested } from 'class-validator'; @@ -7,7 +15,7 @@ export enum CustomFieldType { NUMBER = 'number', SELECT = 'select', BOOLEAN = 'boolean', - DATE = 'date' + DATE = 'date', } export class CreateCustomFieldDto { diff --git a/src/sites/sites.controller.ts b/src/sites/sites.controller.ts index 0adf2cc..90024c4 100644 --- a/src/sites/sites.controller.ts +++ b/src/sites/sites.controller.ts @@ -1,4 +1,12 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, +} from '@nestjs/common'; import { SitesService } from './sites.service'; import { CreateSiteDto, UpdateSiteDto } from '../shared/dto/site.dto'; diff --git a/src/sites/sites.module.ts b/src/sites/sites.module.ts index a6291b0..59e9c42 100644 --- a/src/sites/sites.module.ts +++ b/src/sites/sites.module.ts @@ -4,6 +4,6 @@ import { SitesService } from './sites.service'; @Module({ controllers: [SitesController], - providers: [SitesService] + providers: [SitesService], }) export class SitesModule {} diff --git a/src/sites/sites.service.ts b/src/sites/sites.service.ts index 07edaf9..fa71db5 100644 --- a/src/sites/sites.service.ts +++ b/src/sites/sites.service.ts @@ -70,10 +70,10 @@ export class SitesService { }); if (machinesInSite.length > 0) { - const machineNames = machinesInSite.map(m => m.name).join(', '); + const machineNames = machinesInSite.map((m) => m.name).join(', '); throw new Error( `Impossible de supprimer ce site car il contient ${machinesInSite.length} machine(s): ${machineNames}. ` + - `Veuillez d'abord supprimer ou déplacer ces machines vers un autre site.` + `Veuillez d'abord supprimer ou déplacer ces machines vers un autre site.`, ); } diff --git a/src/types/express-session.d.ts b/src/types/express-session.d.ts index c978692..91830be 100644 --- a/src/types/express-session.d.ts +++ b/src/types/express-session.d.ts @@ -1,7 +1,7 @@ -import 'express-session' +import 'express-session'; declare module 'express-session' { interface SessionData { - profileId?: string + profileId?: string; } } diff --git a/src/types/types.controller.ts b/src/types/types.controller.ts index 39c9e19..08d7a2e 100644 --- a/src/types/types.controller.ts +++ b/src/types/types.controller.ts @@ -1,7 +1,16 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Patch, + Param, + Delete, + Query, +} from '@nestjs/common'; import { TypesService } from './types.service'; -import { - CreateTypeMachineDto, +import { + CreateTypeMachineDto, UpdateTypeMachineDto, CreateTypeComposantDto, UpdateTypeComposantDto, @@ -10,7 +19,7 @@ import { CreateComposantModelDto, UpdateComposantModelDto, CreatePieceModelDto, - UpdatePieceModelDto + UpdatePieceModelDto, } from '../shared/dto/type.dto'; @Controller('types') @@ -34,7 +43,10 @@ export class TypesController { } @Patch('machines/:id') - updateTypeMachine(@Param('id') id: string, @Body() updateTypeMachineDto: UpdateTypeMachineDto) { + updateTypeMachine( + @Param('id') id: string, + @Body() updateTypeMachineDto: UpdateTypeMachineDto, + ) { return this.typesService.updateTypeMachine(id, updateTypeMachineDto); } @@ -56,7 +68,9 @@ export class TypesController { // ComposantModel routes @Post('composants/models') - createComposantModel(@Body() createComposantModelDto: CreateComposantModelDto) { + createComposantModel( + @Body() createComposantModelDto: CreateComposantModelDto, + ) { return this.typesService.createComposantModel(createComposantModelDto); } @@ -71,7 +85,10 @@ export class TypesController { } @Patch('composants/models/:id') - updateComposantModel(@Param('id') id: string, @Body() updateComposantModelDto: UpdateComposantModelDto) { + updateComposantModel( + @Param('id') id: string, + @Body() updateComposantModelDto: UpdateComposantModelDto, + ) { return this.typesService.updateComposantModel(id, updateComposantModelDto); } @@ -86,7 +103,10 @@ export class TypesController { } @Patch('composants/:id') - updateTypeComposant(@Param('id') id: string, @Body() updateTypeComposantDto: UpdateTypeComposantDto) { + updateTypeComposant( + @Param('id') id: string, + @Body() updateTypeComposantDto: UpdateTypeComposantDto, + ) { return this.typesService.updateTypeComposant(id, updateTypeComposantDto); } @@ -123,7 +143,10 @@ export class TypesController { } @Patch('pieces/models/:id') - updatePieceModel(@Param('id') id: string, @Body() updatePieceModelDto: UpdatePieceModelDto) { + updatePieceModel( + @Param('id') id: string, + @Body() updatePieceModelDto: UpdatePieceModelDto, + ) { return this.typesService.updatePieceModel(id, updatePieceModelDto); } @@ -138,7 +161,10 @@ export class TypesController { } @Patch('pieces/:id') - updateTypePiece(@Param('id') id: string, @Body() updateTypePieceDto: UpdateTypePieceDto) { + updateTypePiece( + @Param('id') id: string, + @Body() updateTypePieceDto: UpdateTypePieceDto, + ) { return this.typesService.updateTypePiece(id, updateTypePieceDto); } diff --git a/src/types/types.module.ts b/src/types/types.module.ts index 91755f0..ba17211 100644 --- a/src/types/types.module.ts +++ b/src/types/types.module.ts @@ -4,6 +4,6 @@ import { TypesService } from './types.service'; @Module({ controllers: [TypesController], - providers: [TypesService] + providers: [TypesService], }) export class TypesModule {} diff --git a/src/types/types.service.ts b/src/types/types.service.ts index 7598946..9315d95 100644 --- a/src/types/types.service.ts +++ b/src/types/types.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; +import { ModelCategory } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; -import { - CreateTypeMachineDto, +import { + CreateTypeMachineDto, UpdateTypeMachineDto, CreateTypeComposantDto, UpdateTypeComposantDto, @@ -10,53 +11,93 @@ import { CreateComposantModelDto, UpdateComposantModelDto, CreatePieceModelDto, - UpdatePieceModelDto + UpdatePieceModelDto, } from '../shared/dto/type.dto'; @Injectable() export class TypesService { constructor(private prisma: PrismaService) {} + private slugifyName(name: string): string { + return name + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .replace(/-+/g, '-'); + } + + private async generateUniqueModelTypeCode(name: string): Promise { + const base = this.slugifyName(name) || 'type'; + let candidate = base; + let suffix = 1; + + while ( + await this.prisma.modelType.findUnique({ + where: { code: candidate }, + select: { id: true }, + }) + ) { + candidate = `${base}-${suffix++}`; + } + + return candidate; + } + // TypeMachine methods async createTypeMachine(createTypeMachineDto: CreateTypeMachineDto) { - const { customFields, componentRequirements, pieceRequirements, ...typeData } = createTypeMachineDto; - + const { + customFields, + componentRequirements, + pieceRequirements, + ...typeData + } = createTypeMachineDto; + return this.prisma.typeMachine.create({ data: { ...typeData, - customFields: customFields ? { - create: customFields.map(field => ({ - name: field.name, - type: field.type, - required: field.required || false, - defaultValue: field.defaultValue, - options: field.options - })) - } : 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, + customFields: customFields + ? { + create: customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required || false, + defaultValue: field.defaultValue, + options: field.options, + })), + } + : 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, @@ -113,27 +154,35 @@ export class TypesService { }); } - async updateTypeMachine(id: string, updateTypeMachineDto: UpdateTypeMachineDto) { - const { customFields, componentRequirements, pieceRequirements, ...typeData } = updateTypeMachineDto; - + async updateTypeMachine( + id: string, + updateTypeMachineDto: UpdateTypeMachineDto, + ) { + const { + customFields, + componentRequirements, + pieceRequirements, + ...typeData + } = updateTypeMachineDto; + // Si des champs personnalisés sont fournis, on les met à jour if (customFields !== undefined) { // Supprimer tous les champs personnalisés existants await this.prisma.customField.deleteMany({ - where: { typeMachineId: id } + where: { typeMachineId: id }, }); - + // Créer les nouveaux champs personnalisés if (customFields.length > 0) { await this.prisma.customField.createMany({ - data: customFields.map(field => ({ + data: customFields.map((field) => ({ name: field.name, type: field.type, required: field.required || false, defaultValue: field.defaultValue, options: field.options, - typeMachineId: id - })) + typeMachineId: id, + })), }); } } @@ -145,7 +194,7 @@ export class TypesService { if (componentRequirements.length > 0) { await this.prisma.typeMachineComponentRequirement.createMany({ - data: componentRequirements.map(requirement => ({ + data: componentRequirements.map((requirement) => ({ label: requirement.label ?? null, minCount: requirement.minCount ?? 1, maxCount: requirement.maxCount ?? null, @@ -166,7 +215,7 @@ export class TypesService { if (pieceRequirements.length > 0) { await this.prisma.typeMachinePieceRequirement.createMany({ - data: pieceRequirements.map(requirement => ({ + data: pieceRequirements.map((requirement) => ({ label: requirement.label ?? null, minCount: requirement.minCount ?? 0, maxCount: requirement.maxCount ?? null, @@ -179,7 +228,7 @@ export class TypesService { }); } } - + return this.prisma.typeMachine.update({ where: { id }, data: typeData, @@ -207,10 +256,10 @@ export class TypesService { }); if (machinesWithType.length > 0) { - const machineNames = machinesWithType.map(m => m.name).join(', '); + const machineNames = machinesWithType.map((m) => m.name).join(', '); throw new Error( `Impossible de supprimer ce type de machine car il est utilisé par ${machinesWithType.length} machine(s): ${machineNames}. ` + - `Veuillez d'abord supprimer ou modifier ces machines.` + `Veuillez d'abord supprimer ou modifier ces machines.`, ); } @@ -224,22 +273,30 @@ export class TypesService { }); } - // TypeComposant methods + // TypeComposant methods (backed by ModelType with COMPONENT category) async createTypeComposant(createTypeComposantDto: CreateTypeComposantDto) { - const { customFields, ...typeData } = createTypeComposantDto; - - return this.prisma.typeComposant.create({ + const { customFields, description, name } = createTypeComposantDto; + + const code = await this.generateUniqueModelTypeCode(name); + + return this.prisma.modelType.create({ data: { - ...typeData, - customFields: customFields ? { - create: customFields.map(field => ({ - name: field.name, - type: field.type, - required: field.required || false, - defaultValue: field.defaultValue, - options: field.options - })) - } : undefined + name, + code, + category: ModelCategory.COMPONENT, + description: description ?? null, + notes: description ?? null, + customFields: customFields + ? { + create: customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required || false, + defaultValue: field.defaultValue, + options: field.options, + })), + } + : undefined, }, include: { customFields: true, @@ -248,7 +305,8 @@ export class TypesService { } async findAllTypeComposants() { - return this.prisma.typeComposant.findMany({ + return this.prisma.modelType.findMany({ + where: { category: ModelCategory.COMPONENT }, include: { composants: true, customFields: true, @@ -258,8 +316,11 @@ export class TypesService { } async findOneTypeComposant(id: string) { - return this.prisma.typeComposant.findUnique({ - where: { id }, + return this.prisma.modelType.findFirst({ + where: { + id, + category: ModelCategory.COMPONENT, + }, include: { composants: true, customFields: true, @@ -268,34 +329,43 @@ export class TypesService { }); } - async updateTypeComposant(id: string, updateTypeComposantDto: UpdateTypeComposantDto) { - const { customFields, ...typeData } = updateTypeComposantDto; - - // Si des champs personnalisés sont fournis, on les met à jour + async updateTypeComposant( + id: string, + updateTypeComposantDto: UpdateTypeComposantDto, + ) { + const { customFields, description, name } = updateTypeComposantDto; + if (customFields !== undefined) { - // Supprimer tous les champs personnalisés existants await this.prisma.customField.deleteMany({ - where: { typeComposantId: id } + where: { typeComposantId: id }, }); - - // Créer les nouveaux champs personnalisés + if (customFields.length > 0) { await this.prisma.customField.createMany({ - data: customFields.map(field => ({ + data: customFields.map((field) => ({ name: field.name, type: field.type, required: field.required || false, defaultValue: field.defaultValue, options: field.options, - typeComposantId: id - })) + typeComposantId: id, + })), }); } } - - return this.prisma.typeComposant.update({ + + const data: Record = {}; + if (name !== undefined) { + data.name = name; + } + if (description !== undefined) { + data.description = description; + data.notes = description; + } + + return this.prisma.modelType.update({ where: { id }, - data: typeData, + data, include: { customFields: true, }, @@ -303,91 +373,145 @@ export class TypesService { } async removeTypeComposant(id: string) { - return this.prisma.typeComposant.delete({ + return this.prisma.modelType.delete({ where: { id }, }); } - // TypePiece methods + // TypePiece methods backed by ModelType + private mapModelTypePiece(modelType: any) { + if (!modelType) { + return null; + } + + const { + pieceCustomFields, + pieceModels, + pieceRequirements, + pieces, + ...rest + } = modelType; + + return { + ...rest, + customFields: pieceCustomFields ?? [], + models: pieceModels ?? [], + pieceRequirements: pieceRequirements ?? [], + pieces: pieces ?? [], + }; + } + async createTypePiece(createTypePieceDto: CreateTypePieceDto) { - const { customFields, ...typeData } = createTypePieceDto; - - return this.prisma.typePiece.create({ + const { customFields, description, name } = createTypePieceDto; + + const code = await this.generateUniqueModelTypeCode(name); + + const created = await this.prisma.modelType.create({ data: { - ...typeData, - customFields: customFields ? { - create: customFields.map(field => ({ - name: field.name, - type: field.type, - required: field.required || false, - defaultValue: field.defaultValue, - options: field.options - })) - } : undefined + name, + code, + category: ModelCategory.PIECE, + description: description ?? null, + notes: description ?? null, + pieceCustomFields: customFields + ? { + create: customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required || false, + defaultValue: field.defaultValue, + options: field.options, + })), + } + : undefined, }, include: { - customFields: true, + pieceCustomFields: true, + pieceModels: true, + pieceRequirements: true, + pieces: true, }, }); + + return this.mapModelTypePiece(created); } async findAllTypePieces() { - return this.prisma.typePiece.findMany({ + const items = await this.prisma.modelType.findMany({ + where: { category: ModelCategory.PIECE }, include: { + pieceCustomFields: true, + pieceModels: true, + pieceRequirements: true, pieces: true, - customFields: true, - models: true, }, + orderBy: { name: 'asc' }, }); + + return items.map((item) => this.mapModelTypePiece(item)); } async findOneTypePiece(id: string) { - return this.prisma.typePiece.findUnique({ - where: { id }, + const item = await this.prisma.modelType.findFirst({ + where: { id, category: ModelCategory.PIECE }, include: { + pieceCustomFields: true, + pieceModels: true, + pieceRequirements: true, pieces: true, - customFields: true, - models: true, }, }); + + return this.mapModelTypePiece(item); } async updateTypePiece(id: string, updateTypePieceDto: UpdateTypePieceDto) { - const { customFields, ...typeData } = updateTypePieceDto; - - // Si des champs personnalisés sont fournis, on les met à jour + const { customFields, description, name } = updateTypePieceDto; + if (customFields !== undefined) { - // Supprimer tous les champs personnalisés existants await this.prisma.customField.deleteMany({ - where: { typePieceId: id } + where: { typePieceId: id }, }); - - // Créer les nouveaux champs personnalisés + if (customFields.length > 0) { await this.prisma.customField.createMany({ - data: customFields.map(field => ({ + data: customFields.map((field) => ({ name: field.name, type: field.type, required: field.required || false, defaultValue: field.defaultValue, options: field.options, - typePieceId: id - })) + typePieceId: id, + })), }); } } - - return this.prisma.typePiece.update({ + + const updatePayload: Record = {}; + if (name !== undefined) { + updatePayload.name = name; + } + if (description !== undefined) { + updatePayload.description = description; + updatePayload.notes = description; + } + + const updated = await this.prisma.modelType.update({ where: { id }, - data: typeData, + data: updatePayload, include: { - customFields: true, + pieceCustomFields: true, + pieceModels: true, + pieceRequirements: true, + pieces: true, }, }); + + return this.mapModelTypePiece(updated); } async removeTypePiece(id: string) { - return this.prisma.typePiece.delete({ + return this.prisma.modelType.delete({ where: { id }, }); } @@ -430,7 +554,10 @@ export class TypesService { }); } - async updateComposantModel(id: string, updateComposantModelDto: UpdateComposantModelDto) { + async updateComposantModel( + id: string, + updateComposantModelDto: UpdateComposantModelDto, + ) { const { typeComposantId, ...data } = updateComposantModelDto; return this.prisma.composantModel.update({ diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index 550df86..6085c30 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -156,9 +156,12 @@ class InMemoryPrismaService { private sites: SiteRecord[] = []; private typeComposants: TypeComposantRecord[] = []; private typePieces: TypePieceRecord[] = []; + private modelTypeCodeCounter = 0; private typeMachines: TypeMachineRecord[] = []; - private typeMachineComponentRequirements: TypeMachineComponentRequirementRecord[] = []; - private typeMachinePieceRequirements: TypeMachinePieceRequirementRecord[] = []; + private typeMachineComponentRequirements: TypeMachineComponentRequirementRecord[] = + []; + private typeMachinePieceRequirements: TypeMachinePieceRequirementRecord[] = + []; private machines: MachineRecord[] = []; private composants: ComposantRecord[] = []; private pieces: PieceRecord[] = []; @@ -179,6 +182,7 @@ class InMemoryPrismaService { this.sites = []; this.typeComposants = []; this.typePieces = []; + this.modelTypeCodeCounter = 0; this.typeMachines = []; this.typeMachineComponentRequirements = []; this.typeMachinePieceRequirements = []; @@ -268,7 +272,9 @@ class InMemoryPrismaService { return include?.customFields ? { ...record, - customFields: this.customFields.filter((field) => field.typeComposantId === record.id), + customFields: this.customFields.filter( + (field) => field.typeComposantId === record.id, + ), } : { ...record }; }, @@ -280,7 +286,9 @@ class InMemoryPrismaService { return this.typeComposants.find((item) => item.id === where.id) ?? null; } if (where.name) { - return this.typeComposants.find((item) => item.name === where.name) ?? null; + return ( + this.typeComposants.find((item) => item.name === where.name) ?? null + ); } return null; }, @@ -325,7 +333,9 @@ class InMemoryPrismaService { return include?.customFields ? { ...record, - customFields: this.customFields.filter((field) => field.typePieceId === record.id), + customFields: this.customFields.filter( + (field) => field.typePieceId === record.id, + ), } : { ...record }; }, @@ -383,20 +393,22 @@ class InMemoryPrismaService { let componentRequirements: TypeMachineComponentRequirementRecord[] = []; if (data.componentRequirements?.create) { - componentRequirements = data.componentRequirements.create.map((requirement) => { - const req: TypeMachineComponentRequirementRecord = { - id: generateId('tmc_req'), - typeMachineId: record.id, - typeComposantId: requirement.typeComposant.connect.id, - label: requirement.label ?? null, - minCount: requirement.minCount ?? 1, - maxCount: requirement.maxCount ?? null, - required: requirement.required ?? true, - allowNewModels: requirement.allowNewModels ?? true, - }; - this.typeMachineComponentRequirements.push(req); - return req; - }); + componentRequirements = data.componentRequirements.create.map( + (requirement) => { + const req: TypeMachineComponentRequirementRecord = { + id: generateId('tmc_req'), + typeMachineId: record.id, + typeComposantId: requirement.typeComposant.connect.id, + label: requirement.label ?? null, + minCount: requirement.minCount ?? 1, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? true, + allowNewModels: requirement.allowNewModels ?? true, + }; + this.typeMachineComponentRequirements.push(req); + return req; + }, + ); } let pieceRequirements: TypeMachinePieceRequirementRecord[] = []; @@ -417,28 +429,49 @@ class InMemoryPrismaService { }); } - return this.buildTypeMachine(record, include ?? {}, componentRequirements, pieceRequirements); + return this.buildTypeMachine( + record, + include ?? {}, + componentRequirements, + pieceRequirements, + ); }, findUnique: async ({ where, include }: any) => { - const typeMachine = this.typeMachines.find((item) => item.id === where.id); + const typeMachine = this.typeMachines.find( + (item) => item.id === where.id, + ); if (!typeMachine) { return null; } - const componentRequirements = this.typeMachineComponentRequirements.filter( - (item) => item.typeMachineId === typeMachine.id, - ); + const componentRequirements = + this.typeMachineComponentRequirements.filter( + (item) => item.typeMachineId === typeMachine.id, + ); const pieceRequirements = this.typeMachinePieceRequirements.filter( (item) => item.typeMachineId === typeMachine.id, ); - return this.buildTypeMachine(typeMachine, include ?? {}, componentRequirements, pieceRequirements); + return this.buildTypeMachine( + typeMachine, + include ?? {}, + componentRequirements, + pieceRequirements, + ); }, findMany: async ({ include }: any = {}) => { return this.typeMachines.map((item) => { - const componentRequirements = this.typeMachineComponentRequirements.filter( + const componentRequirements = + this.typeMachineComponentRequirements.filter( + (req) => req.typeMachineId === item.id, + ); + const pieceRequirements = this.typeMachinePieceRequirements.filter( (req) => req.typeMachineId === item.id, ); - const pieceRequirements = this.typeMachinePieceRequirements.filter((req) => req.typeMachineId === item.id); - return this.buildTypeMachine(item, include ?? {}, componentRequirements, pieceRequirements); + return this.buildTypeMachine( + item, + include ?? {}, + componentRequirements, + pieceRequirements, + ); }); }, }; @@ -471,9 +504,13 @@ class InMemoryPrismaService { findMany: async ({ include, where }: any = {}) => { let machines = this.machines; if (where?.siteId) { - machines = machines.filter((machine) => machine.siteId === where.siteId); + machines = machines.filter( + (machine) => machine.siteId === where.siteId, + ); } - return machines.map((machine) => this.buildMachine(machine, include ?? {})); + return machines.map((machine) => + this.buildMachine(machine, include ?? {}), + ); }, delete: async ({ where }: any) => { const index = this.machines.findIndex((item) => item.id === where.id); @@ -498,7 +535,8 @@ class InMemoryPrismaService { parentComposantId: data.parentComposantId ?? null, typeComposantId: data.typeComposantId ?? null, composantModelId: data.composantModelId ?? null, - typeMachineComponentRequirementId: data.typeMachineComponentRequirementId ?? null, + typeMachineComponentRequirementId: + data.typeMachineComponentRequirementId ?? null, constructeurId: data.constructeurId ?? null, createdAt: now, updatedAt: now, @@ -509,10 +547,14 @@ class InMemoryPrismaService { findMany: async ({ where }: any) => { let composants = this.composants; if (where?.machineId !== undefined) { - composants = composants.filter((item) => item.machineId === where.machineId); + composants = composants.filter( + (item) => item.machineId === where.machineId, + ); } if (where?.parentComposantId !== undefined) { - composants = composants.filter((item) => item.parentComposantId === where.parentComposantId); + composants = composants.filter( + (item) => item.parentComposantId === where.parentComposantId, + ); } return composants.map((item) => ({ ...item })); }, @@ -531,7 +573,8 @@ class InMemoryPrismaService { composantId: data.composantId ?? null, typePieceId: data.typePieceId ?? null, pieceModelId: data.pieceModelId ?? null, - typeMachinePieceRequirementId: data.typeMachinePieceRequirementId ?? null, + typeMachinePieceRequirementId: + data.typeMachinePieceRequirementId ?? null, constructeurId: data.constructeurId ?? null, createdAt: now, updatedAt: now, @@ -545,7 +588,9 @@ class InMemoryPrismaService { pieces = pieces.filter((item) => item.machineId === where.machineId); } if (where?.composantId !== undefined) { - pieces = pieces.filter((item) => item.composantId === where.composantId); + pieces = pieces.filter( + (item) => item.composantId === where.composantId, + ); } return pieces.map((item) => ({ ...item })); }, @@ -574,14 +619,18 @@ class InMemoryPrismaService { return this.customFields .filter((field) => { if (!where) return true; - return Object.entries(where).every(([key, value]) => (field as any)[key] === value); + return Object.entries(where).every( + ([key, value]) => (field as any)[key] === value, + ); }) .map((field) => ({ ...field })); }, deleteMany: async ({ where }: any) => { const before = this.customFields.length; this.customFields = this.customFields.filter((field) => { - return !Object.entries(where).every(([key, value]) => (field as any)[key] === value); + return !Object.entries(where).every( + ([key, value]) => (field as any)[key] === value, + ); }); return { count: before - this.customFields.length }; }, @@ -606,12 +655,18 @@ class InMemoryPrismaService { findMany: async ({ where, include }: any) => { const records = this.customFieldValues.filter((value) => { if (!where) return true; - return Object.entries(where).every(([key, v]) => (value as any)[key] === v); + return Object.entries(where).every( + ([key, v]) => (value as any)[key] === v, + ); }); - return records.map((record) => this.buildCustomFieldValue(record, include ?? {})); + return records.map((record) => + this.buildCustomFieldValue(record, include ?? {}), + ); }, findUnique: async ({ where, include }: any) => { - const record = this.customFieldValues.find((value) => value.id === where.id); + const record = this.customFieldValues.find( + (value) => value.id === where.id, + ); if (!record) { return null; } @@ -625,7 +680,9 @@ class InMemoryPrismaService { ); }, update: async ({ where, data, include }: any) => { - const record = this.customFieldValues.find((value) => value.id === where.id); + const record = this.customFieldValues.find( + (value) => value.id === where.id, + ); if (!record) { throw new Error('Custom field value not found'); } @@ -636,7 +693,9 @@ class InMemoryPrismaService { return this.buildCustomFieldValue(record, include ?? {}); }, delete: async ({ where }: any) => { - const index = this.customFieldValues.findIndex((value) => value.id === where.id); + const index = this.customFieldValues.findIndex( + (value) => value.id === where.id, + ); if (index === -1) { throw new Error('Custom field value not found'); } @@ -649,24 +708,32 @@ class InMemoryPrismaService { count: async ({ where }: any) => { return this.profiles.filter((profile) => { if (!where) return true; - return Object.entries(where).every(([key, value]) => (profile as any)[key] === value); + return Object.entries(where).every( + ([key, value]) => (profile as any)[key] === value, + ); }).length; }, findMany: async ({ where, orderBy, select }: any) => { let profiles = this.profiles; if (where) { profiles = profiles.filter((profile) => - Object.entries(where).every(([key, value]) => (profile as any)[key] === value), + Object.entries(where).every( + ([key, value]) => (profile as any)[key] === value, + ), ); } if (orderBy?.createdAt === 'asc') { - profiles = [...profiles].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); + profiles = [...profiles].sort( + (a, b) => a.createdAt.getTime() - b.createdAt.getTime(), + ); } return profiles.map((profile) => this.applySelect(profile, select)); }, findFirst: async ({ where, select }: any) => { const profile = this.profiles.find((item) => - Object.entries(where).every(([key, value]) => (item as any)[key] === value), + Object.entries(where).every( + ([key, value]) => (item as any)[key] === value, + ), ); return profile ? this.applySelect(profile, select) : null; }, @@ -713,8 +780,12 @@ class InMemoryPrismaService { private buildSite(site: SiteRecord, include: any) { const base: any = { ...site }; if (include?.machines) { - const machines = this.machines.filter((machine) => machine.siteId === site.id); - base.machines = machines.map((machine) => this.buildMachine(machine, include.machines.include ?? {})); + const machines = this.machines.filter( + (machine) => machine.siteId === site.id, + ); + base.machines = machines.map((machine) => + this.buildMachine(machine, include.machines.include ?? {}), + ); } if (include?.documents) { base.documents = []; @@ -730,13 +801,17 @@ class InMemoryPrismaService { ) { const base: any = { ...record }; if (include?.customFields) { - base.customFields = this.customFields.filter((field) => field.typeMachineId === record.id); + base.customFields = this.customFields.filter( + (field) => field.typeMachineId === record.id, + ); } if (include?.componentRequirements) { base.componentRequirements = componentRequirements.map((requirement) => ({ ...requirement, typeComposant: include.componentRequirements.include?.typeComposant - ? this.typeComposants.find((item) => item.id === requirement.typeComposantId) ?? null + ? (this.typeComposants.find( + (item) => item.id === requirement.typeComposantId, + ) ?? null) : undefined, })); } @@ -744,14 +819,18 @@ class InMemoryPrismaService { base.pieceRequirements = pieceRequirements.map((requirement) => ({ ...requirement, typePiece: include.pieceRequirements.include?.typePiece - ? this.typePieces.find((item) => item.id === requirement.typePieceId) ?? null + ? (this.typePieces.find( + (item) => item.id === requirement.typePieceId, + ) ?? null) : undefined, })); } if (include?.machines) { base.machines = this.machines .filter((machine) => machine.typeMachineId === record.id) - .map((machine) => this.buildMachine(machine, include.machines.include ?? {})); + .map((machine) => + this.buildMachine(machine, include.machines.include ?? {}), + ); } return base; } @@ -768,9 +847,10 @@ class InMemoryPrismaService { ? this.typeMachines.find((item) => item.id === machine.typeMachineId) : null; if (typeMachine) { - const componentRequirements = this.typeMachineComponentRequirements.filter( - (req) => req.typeMachineId === typeMachine.id, - ); + const componentRequirements = + this.typeMachineComponentRequirements.filter( + (req) => req.typeMachineId === typeMachine.id, + ); const pieceRequirements = this.typeMachinePieceRequirements.filter( (req) => req.typeMachineId === typeMachine.id, ); @@ -790,20 +870,35 @@ class InMemoryPrismaService { } if (include?.composants) { - const composants = this.composants.filter((component) => component.machineId === machine.id); + const composants = this.composants.filter( + (component) => component.machineId === machine.id, + ); base.composants = composants .filter((component) => component.parentComposantId === null) - .map((component) => this.buildComponent(component, include.composants.include ?? {})); + .map((component) => + this.buildComponent(component, include.composants.include ?? {}), + ); } if (include?.pieces) { - const machinePieces = this.pieces.filter((piece) => piece.machineId === machine.id && piece.composantId === null); - base.pieces = machinePieces.map((piece) => this.buildPiece(piece, include.pieces.include ?? {})); + const machinePieces = this.pieces.filter( + (piece) => piece.machineId === machine.id && piece.composantId === null, + ); + base.pieces = machinePieces.map((piece) => + this.buildPiece(piece, include.pieces.include ?? {}), + ); } if (include?.customFieldValues) { - const values = this.customFieldValues.filter((value) => value.machineId === machine.id); - base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {})); + const values = this.customFieldValues.filter( + (value) => value.machineId === machine.id, + ); + base.customFieldValues = values.map((value) => + this.buildCustomFieldValue( + value, + include.customFieldValues.include ?? {}, + ), + ); } if (include?.documents) { @@ -818,7 +913,9 @@ class InMemoryPrismaService { if (include?.typeComposant) { base.typeComposant = component.typeComposantId - ? this.typeComposants.find((item) => item.id === component.typeComposantId) ?? null + ? (this.typeComposants.find( + (item) => item.id === component.typeComposantId, + ) ?? null) : null; } @@ -828,26 +925,42 @@ class InMemoryPrismaService { if (include?.typeMachineComponentRequirement) { const requirement = component.typeMachineComponentRequirementId - ? this.typeMachineComponentRequirements.find((item) => item.id === component.typeMachineComponentRequirementId) ?? null + ? (this.typeMachineComponentRequirements.find( + (item) => item.id === component.typeMachineComponentRequirementId, + ) ?? null) : null; base.typeMachineComponentRequirement = requirement ? { ...requirement, - typeComposant: include.typeMachineComponentRequirement.include?.typeComposant - ? this.typeComposants.find((item) => item.id === requirement.typeComposantId) ?? null + typeComposant: include.typeMachineComponentRequirement.include + ?.typeComposant + ? (this.typeComposants.find( + (item) => item.id === requirement.typeComposantId, + ) ?? null) : undefined, } : null; } if (include?.sousComposants) { - const children = this.composants.filter((item) => item.parentComposantId === component.id); - base.sousComposants = children.map((child) => this.buildComponent(child, include.sousComposants.include ?? {})); + const children = this.composants.filter( + (item) => item.parentComposantId === component.id, + ); + base.sousComposants = children.map((child) => + this.buildComponent(child, include.sousComposants.include ?? {}), + ); } if (include?.customFieldValues) { - const values = this.customFieldValues.filter((value) => value.composantId === component.id); - base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {})); + const values = this.customFieldValues.filter( + (value) => value.composantId === component.id, + ); + base.customFieldValues = values.map((value) => + this.buildCustomFieldValue( + value, + include.customFieldValues.include ?? {}, + ), + ); } if (include?.constructeur) { @@ -855,8 +968,12 @@ class InMemoryPrismaService { } if (include?.pieces) { - const relatedPieces = this.pieces.filter((piece) => piece.composantId === component.id); - base.pieces = relatedPieces.map((piece) => this.buildPiece(piece, include.pieces.include ?? {})); + const relatedPieces = this.pieces.filter( + (piece) => piece.composantId === component.id, + ); + base.pieces = relatedPieces.map((piece) => + this.buildPiece(piece, include.pieces.include ?? {}), + ); } return base; @@ -866,8 +983,15 @@ class InMemoryPrismaService { const base: any = { ...piece }; if (include?.customFieldValues) { - const values = this.customFieldValues.filter((value) => value.pieceId === piece.id); - base.customFieldValues = values.map((value) => this.buildCustomFieldValue(value, include.customFieldValues.include ?? {})); + const values = this.customFieldValues.filter( + (value) => value.pieceId === piece.id, + ); + base.customFieldValues = values.map((value) => + this.buildCustomFieldValue( + value, + include.customFieldValues.include ?? {}, + ), + ); } if (include?.constructeur) { @@ -880,13 +1004,17 @@ class InMemoryPrismaService { if (include?.typeMachinePieceRequirement) { const requirement = piece.typeMachinePieceRequirementId - ? this.typeMachinePieceRequirements.find((item) => item.id === piece.typeMachinePieceRequirementId) ?? null + ? (this.typeMachinePieceRequirements.find( + (item) => item.id === piece.typeMachinePieceRequirementId, + ) ?? null) : null; base.typeMachinePieceRequirement = requirement ? { ...requirement, typePiece: include.typeMachinePieceRequirement.include?.typePiece - ? this.typePieces.find((item) => item.id === requirement.typePieceId) ?? null + ? (this.typePieces.find( + (item) => item.id === requirement.typePieceId, + ) ?? null) : undefined, } : null; @@ -894,7 +1022,8 @@ class InMemoryPrismaService { if (include?.typePiece) { base.typePiece = piece.typePieceId - ? this.typePieces.find((item) => item.id === piece.typePieceId) ?? null + ? (this.typePieces.find((item) => item.id === piece.typePieceId) ?? + null) : null; } @@ -904,7 +1033,9 @@ class InMemoryPrismaService { private buildCustomFieldValue(record: CustomFieldValueRecord, include: any) { const base: any = { ...record }; if (include?.customField) { - base.customField = this.customFields.find((field) => field.id === record.customFieldId) ?? null; + base.customField = + this.customFields.find((field) => field.id === record.customFieldId) ?? + null; } return base; } @@ -918,7 +1049,6 @@ describe('Inventory flow (e2e)', () => { beforeAll(async () => { prismaStub = new InMemoryPrismaService(); - const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }) @@ -945,14 +1075,16 @@ describe('Inventory flow (e2e)', () => { }); it('should create a type, create a machine with new component/piece selections, and edit technical fields', async () => { - const siteResponse = await request(app.getHttpServer()).post('/sites').send({ - name: 'Site Principal', - contactName: 'Responsable Maintenance', - contactPhone: '+33 1 23 45 67 89', - contactAddress: '1 rue de la Paix', - contactPostalCode: '75000', - contactCity: 'Paris', - }); + const siteResponse = await request(app.getHttpServer()) + .post('/sites') + .send({ + name: 'Site Principal', + contactName: 'Responsable Maintenance', + contactPhone: '+33 1 23 45 67 89', + contactAddress: '1 rue de la Paix', + contactPostalCode: '75000', + contactCity: 'Paris', + }); expect(siteResponse.status).toBe(201); const siteId = siteResponse.body.id; @@ -1069,7 +1201,9 @@ describe('Inventory flow (e2e)', () => { expect(machineResponse.status).toBe(201); const machineId = machineResponse.body.id; - const machineDetailsResponse = await request(app.getHttpServer()).get(`/machines/${machineId}`); + const machineDetailsResponse = await request(app.getHttpServer()).get( + `/machines/${machineId}`, + ); expect(machineDetailsResponse.status).toBe(200); const machine = machineDetailsResponse.body; @@ -1092,7 +1226,9 @@ describe('Inventory flow (e2e)', () => { expect(updateResponse.status).toBe(200); expect(updateResponse.body.value).toBe('8 kW'); - const refreshedMachineResponse = await request(app.getHttpServer()).get(`/machines/${machine.id}`); + const refreshedMachineResponse = await request(app.getHttpServer()).get( + `/machines/${machine.id}`, + ); expect(refreshedMachineResponse.status).toBe(200); const refreshedComponent = refreshedMachineResponse.body.composants[0]; expect(refreshedComponent.customFieldValues[0].value).toBe('8 kW'); @@ -1110,7 +1246,9 @@ describe('Inventory flow (e2e)', () => { } as any); const created = { id: 'component-1' }; - const createSpy = jest.spyOn(prisma.composant, 'create').mockResolvedValue(created as any); + const createSpy = jest + .spyOn(prisma.composant, 'create') + .mockResolvedValue(created as any); const response = await request(app.getHttpServer()) .post('/composants') @@ -1157,14 +1295,14 @@ describe('Inventory flow (e2e)', () => { jest.spyOn(prisma.machine, 'findUnique').mockResolvedValue({ id: 'machine-1', typeMachine: { - pieceRequirements: [ - { id: 'req-1', typePieceId: 'type-piece-1' }, - ], + pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }], }, } as any); const created = { id: 'piece-1' }; - const createSpy = jest.spyOn(prisma.piece, 'create').mockResolvedValue(created as any); + const createSpy = jest + .spyOn(prisma.piece, 'create') + .mockResolvedValue(created as any); const response = await request(app.getHttpServer()) .post('/pieces') @@ -1184,9 +1322,7 @@ describe('Inventory flow (e2e)', () => { jest.spyOn(prisma.machine, 'findUnique').mockResolvedValue({ id: 'machine-1', typeMachine: { - pieceRequirements: [ - { id: 'req-1', typePieceId: 'type-piece-1' }, - ], + pieceRequirements: [{ id: 'req-1', typePieceId: 'type-piece-1' }], }, } as any); @@ -1203,7 +1339,6 @@ describe('Inventory flow (e2e)', () => { .expect(400); expect(createSpy).not.toHaveBeenCalled(); - }); }); });