From 635ea0e84ee2884cdaac6971d0b04801bf5d88fc Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 28 Oct 2025 16:37:06 +0100 Subject: [PATCH] fix: corrige les associations constructeurs --- .../migration.sql | 64 ++++++ .../migration.sql | 66 +++++++ .../migration.sql | 45 +++++ prisma/schema.prisma | 15 +- scripts/seed-sample-data.ts | 88 ++++++--- src/common/constants/component-includes.ts | 2 +- src/composants/composants.service.spec.ts | 10 +- src/composants/composants.service.ts | 53 ++++- src/machines/machines.service.spec.ts | 21 +- src/machines/machines.service.ts | 182 +++++++++++------- src/model-type/model-type.service.ts | 4 +- src/pieces/pieces.service.spec.ts | 20 +- src/pieces/pieces.service.ts | 60 ++++-- src/shared/dto/composant.dto.ts | 19 +- src/shared/dto/machine.dto.ts | 10 +- src/shared/dto/piece.dto.ts | 14 +- test/app.e2e-spec.ts | 105 ++++++---- 17 files changed, 578 insertions(+), 200 deletions(-) create mode 100644 prisma/migrations/20251106120000_multi_constructeurs/migration.sql create mode 100644 prisma/migrations/20251107120000_fix_constructeur_join_tables/migration.sql create mode 100644 prisma/migrations/20251107130000_restore_machine_composant_orientation/migration.sql diff --git a/prisma/migrations/20251106120000_multi_constructeurs/migration.sql b/prisma/migrations/20251106120000_multi_constructeurs/migration.sql new file mode 100644 index 0000000..f109d2e --- /dev/null +++ b/prisma/migrations/20251106120000_multi_constructeurs/migration.sql @@ -0,0 +1,64 @@ +-- Convert single constructeur relation to many-to-many for machines, composants et pièces + +-- Machines → Constructeurs +ALTER TABLE "machines" DROP CONSTRAINT IF EXISTS "machines_constructeurId_fkey"; + +CREATE TABLE "_MachineConstructeurs" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_MachineConstructeurs_A_fkey" FOREIGN KEY ("A") REFERENCES "machines"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_MachineConstructeurs_B_fkey" FOREIGN KEY ("B") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE UNIQUE INDEX "_MachineConstructeurs_AB_unique" ON "_MachineConstructeurs"("A", "B"); +CREATE INDEX "_MachineConstructeurs_B_index" ON "_MachineConstructeurs"("B"); + +INSERT INTO "_MachineConstructeurs" ("A", "B") +SELECT "id", "constructeurId" +FROM "machines" +WHERE "constructeurId" IS NOT NULL +ON CONFLICT DO NOTHING; + +ALTER TABLE "machines" DROP COLUMN IF EXISTS "constructeurId"; + +-- Composants → Constructeurs +ALTER TABLE "composants" DROP CONSTRAINT IF EXISTS "composants_constructeurId_fkey"; + +CREATE TABLE "_ComposantConstructeurs" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_ComposantConstructeurs_A_fkey" FOREIGN KEY ("A") REFERENCES "composants"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_ComposantConstructeurs_B_fkey" FOREIGN KEY ("B") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE UNIQUE INDEX "_ComposantConstructeurs_AB_unique" ON "_ComposantConstructeurs"("A", "B"); +CREATE INDEX "_ComposantConstructeurs_B_index" ON "_ComposantConstructeurs"("B"); + +INSERT INTO "_ComposantConstructeurs" ("A", "B") +SELECT "id", "constructeurId" +FROM "composants" +WHERE "constructeurId" IS NOT NULL +ON CONFLICT DO NOTHING; + +ALTER TABLE "composants" DROP COLUMN IF EXISTS "constructeurId"; + +-- Pièces → Constructeurs +ALTER TABLE "pieces" DROP CONSTRAINT IF EXISTS "pieces_constructeurId_fkey"; + +CREATE TABLE "_PieceConstructeurs" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_PieceConstructeurs_A_fkey" FOREIGN KEY ("A") REFERENCES "pieces"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_PieceConstructeurs_B_fkey" FOREIGN KEY ("B") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE UNIQUE INDEX "_PieceConstructeurs_AB_unique" ON "_PieceConstructeurs"("A", "B"); +CREATE INDEX "_PieceConstructeurs_B_index" ON "_PieceConstructeurs"("B"); + +INSERT INTO "_PieceConstructeurs" ("A", "B") +SELECT "id", "constructeurId" +FROM "pieces" +WHERE "constructeurId" IS NOT NULL +ON CONFLICT DO NOTHING; + +ALTER TABLE "pieces" DROP COLUMN IF EXISTS "constructeurId"; diff --git a/prisma/migrations/20251107120000_fix_constructeur_join_tables/migration.sql b/prisma/migrations/20251107120000_fix_constructeur_join_tables/migration.sql new file mode 100644 index 0000000..55b2c60 --- /dev/null +++ b/prisma/migrations/20251107120000_fix_constructeur_join_tables/migration.sql @@ -0,0 +1,66 @@ +-- Fix the orientation of implicit many-to-many join tables between constructeurs +-- and machines/composants/pièces so that Prisma inserts target IDs into the +-- matching foreign key columns. + +-- Machines ↔ Constructeurs +CREATE TABLE "_MachineConstructeurs_new" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_MachineConstructeurs_new_A_fkey" FOREIGN KEY ("A") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_MachineConstructeurs_new_B_fkey" FOREIGN KEY ("B") REFERENCES "machines"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO "_MachineConstructeurs_new" ("A", "B") +SELECT "B", "A" +FROM "_MachineConstructeurs"; + +DROP TABLE "_MachineConstructeurs"; + +ALTER TABLE "_MachineConstructeurs_new" RENAME TO "_MachineConstructeurs"; +ALTER TABLE "_MachineConstructeurs" RENAME CONSTRAINT "_MachineConstructeurs_new_A_fkey" TO "_MachineConstructeurs_A_fkey"; +ALTER TABLE "_MachineConstructeurs" RENAME CONSTRAINT "_MachineConstructeurs_new_B_fkey" TO "_MachineConstructeurs_B_fkey"; + +CREATE UNIQUE INDEX "_MachineConstructeurs_AB_unique" ON "_MachineConstructeurs"("A", "B"); +CREATE INDEX "_MachineConstructeurs_B_index" ON "_MachineConstructeurs"("B"); + +-- Composants ↔ Constructeurs +CREATE TABLE "_ComposantConstructeurs_new" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_ComposantConstructeurs_new_A_fkey" FOREIGN KEY ("A") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_ComposantConstructeurs_new_B_fkey" FOREIGN KEY ("B") REFERENCES "composants"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO "_ComposantConstructeurs_new" ("A", "B") +SELECT "B", "A" +FROM "_ComposantConstructeurs"; + +DROP TABLE "_ComposantConstructeurs"; + +ALTER TABLE "_ComposantConstructeurs_new" RENAME TO "_ComposantConstructeurs"; +ALTER TABLE "_ComposantConstructeurs" RENAME CONSTRAINT "_ComposantConstructeurs_new_A_fkey" TO "_ComposantConstructeurs_A_fkey"; +ALTER TABLE "_ComposantConstructeurs" RENAME CONSTRAINT "_ComposantConstructeurs_new_B_fkey" TO "_ComposantConstructeurs_B_fkey"; + +CREATE UNIQUE INDEX "_ComposantConstructeurs_AB_unique" ON "_ComposantConstructeurs"("A", "B"); +CREATE INDEX "_ComposantConstructeurs_B_index" ON "_ComposantConstructeurs"("B"); + +-- Pièces ↔ Constructeurs +CREATE TABLE "_PieceConstructeurs_new" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_PieceConstructeurs_new_A_fkey" FOREIGN KEY ("A") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_PieceConstructeurs_new_B_fkey" FOREIGN KEY ("B") REFERENCES "pieces"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO "_PieceConstructeurs_new" ("A", "B") +SELECT "B", "A" +FROM "_PieceConstructeurs"; + +DROP TABLE "_PieceConstructeurs"; + +ALTER TABLE "_PieceConstructeurs_new" RENAME TO "_PieceConstructeurs"; +ALTER TABLE "_PieceConstructeurs" RENAME CONSTRAINT "_PieceConstructeurs_new_A_fkey" TO "_PieceConstructeurs_A_fkey"; +ALTER TABLE "_PieceConstructeurs" RENAME CONSTRAINT "_PieceConstructeurs_new_B_fkey" TO "_PieceConstructeurs_B_fkey"; + +CREATE UNIQUE INDEX "_PieceConstructeurs_AB_unique" ON "_PieceConstructeurs"("A", "B"); +CREATE INDEX "_PieceConstructeurs_B_index" ON "_PieceConstructeurs"("B"); diff --git a/prisma/migrations/20251107130000_restore_machine_composant_orientation/migration.sql b/prisma/migrations/20251107130000_restore_machine_composant_orientation/migration.sql new file mode 100644 index 0000000..1547d7c --- /dev/null +++ b/prisma/migrations/20251107130000_restore_machine_composant_orientation/migration.sql @@ -0,0 +1,45 @@ +-- Restore the original orientation of the machine/composant ↔ constructeur +-- implicit join tables after the previous corrective migration, while keeping +-- the pièce ↔ constructeur table aligned with Prisma's expectations. + +-- Machines ↔ Constructeurs must map column A → machine, B → constructeur +CREATE TABLE "_MachineConstructeurs_restored" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_MachineConstructeurs_restored_A_fkey" FOREIGN KEY ("A") REFERENCES "machines"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_MachineConstructeurs_restored_B_fkey" FOREIGN KEY ("B") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO "_MachineConstructeurs_restored" ("A", "B") +SELECT "B", "A" +FROM "_MachineConstructeurs"; + +DROP TABLE "_MachineConstructeurs"; + +ALTER TABLE "_MachineConstructeurs_restored" RENAME TO "_MachineConstructeurs"; +ALTER TABLE "_MachineConstructeurs" RENAME CONSTRAINT "_MachineConstructeurs_restored_A_fkey" TO "_MachineConstructeurs_A_fkey"; +ALTER TABLE "_MachineConstructeurs" RENAME CONSTRAINT "_MachineConstructeurs_restored_B_fkey" TO "_MachineConstructeurs_B_fkey"; + +CREATE UNIQUE INDEX "_MachineConstructeurs_AB_unique" ON "_MachineConstructeurs"("A", "B"); +CREATE INDEX "_MachineConstructeurs_B_index" ON "_MachineConstructeurs"("B"); + +-- Composants ↔ Constructeurs must map column A → composant, B → constructeur +CREATE TABLE "_ComposantConstructeurs_restored" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_ComposantConstructeurs_restored_A_fkey" FOREIGN KEY ("A") REFERENCES "composants"("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_ComposantConstructeurs_restored_B_fkey" FOREIGN KEY ("B") REFERENCES "constructeurs"("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +INSERT INTO "_ComposantConstructeurs_restored" ("A", "B") +SELECT "B", "A" +FROM "_ComposantConstructeurs"; + +DROP TABLE "_ComposantConstructeurs"; + +ALTER TABLE "_ComposantConstructeurs_restored" RENAME TO "_ComposantConstructeurs"; +ALTER TABLE "_ComposantConstructeurs" RENAME CONSTRAINT "_ComposantConstructeurs_restored_A_fkey" TO "_ComposantConstructeurs_A_fkey"; +ALTER TABLE "_ComposantConstructeurs" RENAME CONSTRAINT "_ComposantConstructeurs_restored_B_fkey" TO "_ComposantConstructeurs_B_fkey"; + +CREATE UNIQUE INDEX "_ComposantConstructeurs_AB_unique" ON "_ComposantConstructeurs"("A", "B"); +CREATE INDEX "_ComposantConstructeurs_B_index" ON "_ComposantConstructeurs"("B"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 74898c4..d4ff08d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -66,8 +66,7 @@ model Machine { typeMachineId String? typeMachine TypeMachine? @relation(fields: [typeMachineId], references: [id]) - constructeurId String? - constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) + constructeurs Constructeur[] @relation("MachineConstructeurs") componentLinks MachineComponentLink[] pieceLinks MachinePieceLink[] @@ -89,8 +88,7 @@ model Composant { typeComposantId String? typeComposant ModelType? @relation("ModelTypeComponentAssignments", fields: [typeComposantId], references: [id]) - constructeurId String? - constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) + constructeurs Constructeur[] @relation("ComposantConstructeurs") documents Document[] @relation("ComposantDocuments") customFieldValues CustomFieldValue[] @relation("ComposantCustomFieldValues") @@ -110,8 +108,7 @@ model Piece { typePieceId String? typePiece ModelType? @relation("ModelTypePieceAssignments", fields: [typePieceId], references: [id]) - constructeurId String? - constructeur Constructeur? @relation(fields: [constructeurId], references: [id], onDelete: SetNull) + constructeurs Constructeur[] @relation("PieceConstructeurs") documents Document[] @relation("PieceDocuments") customFieldValues CustomFieldValue[] @relation("PieceCustomFieldValues") @@ -197,9 +194,9 @@ model Constructeur { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - machines Machine[] - composants Composant[] - pieces Piece[] + machines Machine[] @relation("MachineConstructeurs") + composants Composant[] @relation("ComposantConstructeurs") + pieces Piece[] @relation("PieceConstructeurs") @@map("constructeurs") } diff --git a/scripts/seed-sample-data.ts b/scripts/seed-sample-data.ts index 1d59d84..a4eabae 100644 --- a/scripts/seed-sample-data.ts +++ b/scripts/seed-sample-data.ts @@ -120,7 +120,7 @@ async function createPiece(options: { name: string; reference: string; price: number; - constructeurId?: string | null; + constructeurIds?: string[] | null; typeId: string; fieldValues: Record; }) { @@ -143,17 +143,35 @@ async function createPiece(options: { }, ); - return prisma.piece.create({ - data: { - name: options.name, - reference: options.reference, - prix: new Prisma.Decimal(options.price), - typePieceId: options.typeId, - constructeurId: options.constructeurId ?? null, - customFieldValues: { - create: customFieldValues, - }, + const constructeurIds = Array.isArray(options.constructeurIds) + ? Array.from( + new Set( + options.constructeurIds + .filter((value): value is string => typeof value === 'string') + .map((value) => value.trim()) + .filter((value) => value.length > 0), + ), + ) + : []; + + const data: any = { + name: options.name, + reference: options.reference, + prix: new Prisma.Decimal(options.price), + typePieceId: options.typeId, + customFieldValues: { + create: customFieldValues, }, + }; + + if (constructeurIds.length) { + data.constructeurs = { + connect: constructeurIds.map((id) => ({ id })), + }; + } + + return prisma.piece.create({ + data, }); } @@ -161,7 +179,7 @@ async function createComponent(options: { name: string; reference: string; price: number; - constructeurId?: string | null; + constructeurIds?: string[] | null; typeId: string; fieldValues: Record; structure?: Prisma.InputJsonValue; @@ -185,21 +203,39 @@ async function createComponent(options: { }, ); - return prisma.composant.create({ - data: { - name: options.name, - reference: options.reference, - prix: new Prisma.Decimal(options.price), - typeComposantId: options.typeId, - constructeurId: options.constructeurId ?? null, - structure: - options.structure === undefined - ? Prisma.JsonNull - : options.structure ?? Prisma.JsonNull, - customFieldValues: { - create: customFieldValues, - }, + const constructeurIds = Array.isArray(options.constructeurIds) + ? Array.from( + new Set( + options.constructeurIds + .filter((value): value is string => typeof value === 'string') + .map((value) => value.trim()) + .filter((value) => value.length > 0), + ), + ) + : []; + + const data: any = { + name: options.name, + reference: options.reference, + prix: new Prisma.Decimal(options.price), + typeComposantId: options.typeId, + structure: + options.structure === undefined + ? Prisma.JsonNull + : options.structure ?? Prisma.JsonNull, + customFieldValues: { + create: customFieldValues, }, + }; + + if (constructeurIds.length) { + data.constructeurs = { + connect: constructeurIds.map((id) => ({ id })), + }; + } + + return prisma.composant.create({ + data, }); } diff --git a/src/common/constants/component-includes.ts b/src/common/constants/component-includes.ts index 537bbcb..c5efef1 100644 --- a/src/common/constants/component-includes.ts +++ b/src/common/constants/component-includes.ts @@ -14,7 +14,7 @@ export const COMPONENT_WITH_RELATIONS_INCLUDE = { customFields: true, }, }, - constructeur: true, + constructeurs: true, customFieldValues: { include: { customField: { select: CUSTOM_FIELD_SELECT }, diff --git a/src/composants/composants.service.spec.ts b/src/composants/composants.service.spec.ts index 15e1d1b..6f4cc2a 100644 --- a/src/composants/composants.service.spec.ts +++ b/src/composants/composants.service.spec.ts @@ -1,7 +1,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ComposantsService } from './composants.service'; import { PrismaService } from '../prisma/prisma.service'; -import { CreateComposantDto, UpdateComposantDto } from '../shared/dto/composant.dto'; +import { + CreateComposantDto, + UpdateComposantDto, +} from '../shared/dto/composant.dto'; describe('ComposantsService', () => { let service: ComposantsService; @@ -45,7 +48,10 @@ describe('ComposantsService', () => { it('updates a component', async () => { const dto: UpdateComposantDto = { name: 'Updated' }; - prisma.composant.update.mockResolvedValue({ id: 'comp-1', name: 'Updated' }); + prisma.composant.update.mockResolvedValue({ + id: 'comp-1', + name: 'Updated', + }); await service.update('comp-1', dto); diff --git a/src/composants/composants.service.ts b/src/composants/composants.service.ts index 24f73ae..faf8f01 100644 --- a/src/composants/composants.service.ts +++ b/src/composants/composants.service.ts @@ -14,9 +14,9 @@ import { export class ComposantsService { constructor(private prisma: PrismaService) {} - private buildCreateInput( + private async buildCreateInput( createComposantDto: CreateComposantDto, - ): Prisma.ComposantCreateInput { + ): Promise { const data: Prisma.ComposantCreateInput = { name: createComposantDto.name, reference: createComposantDto.reference ?? null, @@ -24,9 +24,14 @@ export class ComposantsService { createComposantDto.prix !== undefined ? createComposantDto.prix : null, }; - if (createComposantDto.constructeurId) { - data.constructeur = { - connect: { id: createComposantDto.constructeurId }, + const constructeurIds = this.normalizeConstructeurIds( + createComposantDto.constructeurIds, + ); + const resolvedConstructeurIds = + await this.resolveExistingConstructeurIds(constructeurIds); + if (resolvedConstructeurIds.length) { + data.constructeurs = { + connect: resolvedConstructeurIds.map((id) => ({ id })), }; } @@ -46,7 +51,7 @@ export class ComposantsService { async create(createComposantDto: CreateComposantDto) { try { const created = await this.prisma.composant.create({ - data: this.buildCreateInput(createComposantDto), + data: await this.buildCreateInput(createComposantDto), include: COMPONENT_WITH_RELATIONS_INCLUDE, }); @@ -85,10 +90,15 @@ export class ComposantsService { data.prix = updateComposantDto.prix; } - if (updateComposantDto.constructeurId !== undefined) { - data.constructeur = updateComposantDto.constructeurId - ? { connect: { id: updateComposantDto.constructeurId } } - : { disconnect: true }; + if (updateComposantDto.constructeurIds !== undefined) { + const constructeurIds = this.normalizeConstructeurIds( + updateComposantDto.constructeurIds, + ); + const resolvedConstructeurIds = + await this.resolveExistingConstructeurIds(constructeurIds); + data.constructeurs = { + set: resolvedConstructeurIds.map((id) => ({ id })), + }; } if (updateComposantDto.typeComposantId !== undefined) { @@ -170,6 +180,16 @@ export class ComposantsService { }); } + private normalizeConstructeurIds(ids?: string[] | null): string[] { + if (!Array.isArray(ids)) { + return []; + } + const cleaned = ids + .map((item) => (typeof item === 'string' ? item.trim() : '')) + .filter((item) => item.length > 0); + return Array.from(new Set(cleaned)); + } + private handlePrismaError(error: unknown): never { if (error instanceof Prisma.PrismaClientKnownRequestError) { if (error.code === 'P2002' && this.isNameConstraint(error)) { @@ -190,4 +210,17 @@ export class ComposantsService { } return false; } + private async resolveExistingConstructeurIds( + ids: string[], + ): Promise { + if (!ids.length) { + return []; + } + const existing = await this.prisma.constructeur.findMany({ + where: { id: { in: ids } }, + select: { id: true }, + }); + const existingIds = new Set(existing.map(({ id }) => id)); + return ids.filter((id) => existingIds.has(id)); + } } diff --git a/src/machines/machines.service.spec.ts b/src/machines/machines.service.spec.ts index 0c05a63..588ee7c 100644 --- a/src/machines/machines.service.spec.ts +++ b/src/machines/machines.service.spec.ts @@ -59,11 +59,10 @@ describe('MachinesService', () => { prix: null, createdAt: timestamp, updatedAt: timestamp, - constructeurId: null, + constructeurs: [], typePieceId: null, documents: [], customFieldValues: [], - constructeur: null, typePiece: null, }, typeMachinePieceRequirement: null, @@ -81,7 +80,7 @@ describe('MachinesService', () => { id: 'piece-root', name: 'Root piece', }, - } as any; + }; const componentChildLink = { id: 'component-child', @@ -101,11 +100,10 @@ describe('MachinesService', () => { prix: null, createdAt: timestamp, updatedAt: timestamp, - constructeurId: null, + constructeurs: [], typeComposantId: null, documents: [], customFieldValues: [], - constructeur: null, typeComposant: null, }, typeMachineComponentRequirement: null, @@ -125,7 +123,7 @@ describe('MachinesService', () => { name: 'Root component', }, pieceLinks: [componentPieceLink], - } as any; + }; return { id: 'machine-1', @@ -135,11 +133,10 @@ describe('MachinesService', () => { createdAt: timestamp, updatedAt: timestamp, typeMachineId: null, - constructeurId: null, + constructeurs: [], siteId: 'site-1', site: null, typeMachine: null, - constructeur: null, componentLinks: [componentRootLink, componentChildLink], pieceLinks: [rootPieceLink, componentPieceLink], customFieldValues: [], @@ -165,9 +162,7 @@ describe('MachinesService', () => { expect(rootLink.pieceLinks[0].parent?.overrides.name).toBe( 'Root component override', ); - expect(rootLink.pieceLinks[0].originalPiece.name).toBe( - 'Piece component', - ); + expect(rootLink.pieceLinks[0].originalPiece.name).toBe('Piece component'); expect(rootLink.pieceLinks[0].piece.name).toBe('Component piece name'); expect(rootLink.pieceLinks[0].overrides.reference).toBe('CP-001'); expect(rootLink.overrides.name).toBe('Root component override'); @@ -193,9 +188,7 @@ describe('MachinesService', () => { const root = result?.componentLinks[0]; expect(root?.childLinks[0].parent?.id).toBe('component-root'); expect(root?.childLinks[0].parent?.composantId).toBe('component-root'); - expect(root?.childLinks[0].originalComposant.name).toBe( - 'Child component', - ); + expect(root?.childLinks[0].originalComposant.name).toBe('Child component'); expect(root?.childLinks[0].composant.name).toBe('Child component'); expect(root?.pieceLinks[0].parent?.id).toBe('component-root'); expect(root?.pieceLinks[0].parent?.composantId).toBe('component-root'); diff --git a/src/machines/machines.service.ts b/src/machines/machines.service.ts index 5033214..c577da1 100644 --- a/src/machines/machines.service.ts +++ b/src/machines/machines.service.ts @@ -49,7 +49,7 @@ const MACHINE_PIECE_LINK_INCLUDE: Prisma.MachinePieceLinkInclude = { customField: { select: CUSTOM_FIELD_SELECT }, }, }, - constructeur: true, + constructeurs: true, typePiece: { include: { customFields: true, @@ -75,7 +75,7 @@ const buildComponentLinkInclude = ( const include: Prisma.MachineComponentLinkInclude = { composant: { include: { - constructeur: true, + constructeurs: true, typeComposant: { include: { customFields: true, @@ -119,7 +119,7 @@ const MACHINE_DEFAULT_INCLUDE = { typeMachine: { include: TYPE_MACHINE_CONFIGURATION_INCLUDE, }, - constructeur: true, + constructeurs: true, componentLinks: { include: MACHINE_COMPONENT_LINK_INCLUDE, }, @@ -200,7 +200,7 @@ type ComponentWithType = Prisma.ComposantGetPayload<{ }>; type PieceWithType = Prisma.PieceGetPayload<{ - include: { typePiece: true }; + include: { typePiece: true; constructeurs: true }; }>; type CreatedComponentLinkInfo = { @@ -246,9 +246,7 @@ type PendingPieceLink = { @Injectable() export class MachinesService { - constructor( - private prisma: PrismaService, - ) {} + constructor(private prisma: PrismaService) {} private toLinkOverride(source: { nameOverride?: string | null; @@ -273,7 +271,7 @@ export class MachinesService { const prix = overrides.prix !== null && overrides.prix !== undefined ? overrides.prix - : composant.prix ?? null; + : (composant.prix ?? null); return { ...composant, @@ -294,7 +292,7 @@ export class MachinesService { const prix = overrides.prix !== null && overrides.prix !== undefined ? overrides.prix - : piece.prix ?? null; + : (piece.prix ?? null); return { ...piece, @@ -346,7 +344,10 @@ export class MachinesService { : []; const hydratedLink: HydratedComponentLink = { - ...(rest as Omit), + ...(rest as Omit< + MachineComponentLinkWithRelations, + 'pieceLinks' | 'composant' + >), parent: parentSummary, overrides, originalComposant: composant, @@ -375,26 +376,27 @@ export class MachinesService { sousComposants: [] as HierarchicalComponentLink[], })); - const hierarchy = buildComponentHierarchy(decorated); + const hierarchy = + buildComponentHierarchy(decorated); return hierarchy.map((link) => this.convertComponentLinkNode(link)); } - private hydrateMachine( - machine: MachineWithRelations | null, - ): (MachineWithRelations & { - componentLinks: HydratedComponentLink[]; - pieceLinks: HydratedPieceLink[]; - }) | null { + private hydrateMachine(machine: MachineWithRelations | null): + | (MachineWithRelations & { + componentLinks: HydratedComponentLink[]; + pieceLinks: HydratedPieceLink[]; + }) + | null { if (!machine) { return machine; } const componentLinks = this.hydrateComponentLinks( - (machine.componentLinks ?? []) as MachineComponentLinkWithRelations[], + machine.componentLinks ?? [], ); - const rootPieceLinks = ((machine.pieceLinks ?? []) as MachinePieceLinkWithRelations[]) + const rootPieceLinks = (machine.pieceLinks ?? []) .filter((link) => !link.parentLinkId) .map((link) => this.hydratePieceLink(link)); @@ -533,7 +535,8 @@ export class MachinesService { } for (const requirement of componentRequirements) { - const linksForRequirement = componentLinksByRequirement.get(requirement.id) ?? []; + const linksForRequirement = + componentLinksByRequirement.get(requirement.id) ?? []; const min = requirement.minCount ?? (requirement.required ? 1 : 0); const max = requirement.maxCount ?? undefined; @@ -559,7 +562,8 @@ export class MachinesService { } for (const requirement of pieceRequirements) { - const linksForRequirement = pieceLinksByRequirement.get(requirement.id) ?? []; + const linksForRequirement = + pieceLinksByRequirement.get(requirement.id) ?? []; const min = requirement.minCount ?? (requirement.required ? 1 : 0); const max = requirement.maxCount ?? undefined; @@ -646,6 +650,30 @@ export class MachinesService { return undefined; } + private normalizeConstructeurIds(ids?: string[] | null): string[] { + if (!Array.isArray(ids)) { + return []; + } + const cleaned = ids + .map((item) => (typeof item === 'string' ? item.trim() : '')) + .filter((item) => item.length > 0); + return Array.from(new Set(cleaned)); + } + + private async resolveExistingConstructeurIds( + ids: string[], + ): Promise { + if (!ids.length) { + return []; + } + const existing = await this.prisma.constructeur.findMany({ + where: { id: { in: ids } }, + select: { id: true }, + }); + const existingIds = new Set(existing.map(({ id }) => id)); + return ids.filter((id) => existingIds.has(id)); + } + private async resolveConstructeurId( input: unknown, ): Promise { @@ -692,9 +720,12 @@ export class MachinesService { return undefined; } - private resolveLinkIdentifier(link: { id?: string; linkId?: string }): string | undefined { + private resolveLinkIdentifier(link: { + id?: string; + linkId?: string; + }): string | undefined { const candidate = - (typeof link.id === 'string' && link.id.trim()) + typeof link.id === 'string' && link.id.trim() ? link.id.trim() : typeof link.linkId === 'string' ? link.linkId.trim() @@ -703,9 +734,7 @@ export class MachinesService { return candidate && candidate.length > 0 ? candidate : undefined; } - private buildLinkOverrideMutation( - overrides?: Record, - ): { + private buildLinkOverrideMutation(overrides?: Record): { nameOverride?: string | null; referenceOverride?: string | null; prixOverride?: Prisma.Decimal | null; @@ -725,9 +754,7 @@ export class MachinesService { Object.prototype.hasOwnProperty.call(container, 'name') || Object.prototype.hasOwnProperty.call(container, 'nom') ) { - const value = this.extractString( - container.name ?? container.nom, - ); + const value = this.extractString(container.name ?? container.nom); mutation.nameOverride = value ?? null; } @@ -755,7 +782,9 @@ export class MachinesService { container.priceOverride; const normalized = this.normalizePrice(rawPrice); if (normalized === undefined) { - throw new Error('La valeur de prix fournie dans les overrides est invalide.'); + throw new Error( + 'La valeur de prix fournie dans les overrides est invalide.', + ); } mutation.prixOverride = normalized === null ? null : new Prisma.Decimal(normalized); @@ -800,7 +829,9 @@ export class MachinesService { return [...direct, ...legacy]; } - private extractStructurePieces(structure: Record | null | undefined) { + private extractStructurePieces( + structure: Record | null | undefined, + ) { if (!structure || typeof structure !== 'object') { return []; } @@ -815,7 +846,9 @@ export class MachinesService { return [...pieces, ...legacy]; } - private extractStructureAlias(entry: Record | null | undefined) { + private extractStructureAlias( + entry: Record | null | undefined, + ) { if (!entry || typeof entry !== 'object') { return null; } @@ -848,7 +881,9 @@ export class MachinesService { return this.extractString(definition.reference) ?? null; } - private extractStructurePieceName(entry: Record | null | undefined) { + private extractStructurePieceName( + entry: Record | null | undefined, + ) { if (!entry || typeof entry !== 'object') { return null; } @@ -959,7 +994,7 @@ export class MachinesService { where: { id: pieceId }, include: { typePiece: true, - constructeur: true, + constructeurs: true, }, }); @@ -994,9 +1029,7 @@ export class MachinesService { for (const rawPiece of structurePieces) { const pieceEntry = this.ensurePlainObject(rawPiece); const selectedPieceId = this.extractString( - pieceEntry.selectedPieceId ?? - pieceEntry.pieceId ?? - pieceEntry.id, + pieceEntry.selectedPieceId ?? pieceEntry.pieceId ?? pieceEntry.id, ); if (!selectedPieceId) { @@ -1045,7 +1078,8 @@ export class MachinesService { data: { parentLinkId: linkInfo.id, nameOverride: - pieceNameOverride !== null && pieceNameOverride !== undefined + pieceNameOverride !== null && + pieceNameOverride !== undefined ? pieceNameOverride : existingPieceLink.nameOverride, referenceOverride: @@ -1069,7 +1103,8 @@ export class MachinesService { where: { id: existingPieceLink.id }, data: { nameOverride: - pieceNameOverride !== null && pieceNameOverride !== undefined + pieceNameOverride !== null && + pieceNameOverride !== undefined ? pieceNameOverride : existingPieceLink.nameOverride, referenceOverride: @@ -1116,9 +1151,7 @@ export class MachinesService { for (const rawEntry of subcomponents) { const entry = this.ensurePlainObject(rawEntry); const selectedComponentId = this.extractString( - entry.selectedComponentId ?? - entry.componentId ?? - entry.composantId, + entry.selectedComponentId ?? entry.componentId ?? entry.composantId, ); if (!selectedComponentId) { @@ -1299,7 +1332,8 @@ export class MachinesService { createData.nameOverride = entry.overrideMutation.nameOverride; } if (entry.overrideMutation?.referenceOverride !== undefined) { - createData.referenceOverride = entry.overrideMutation.referenceOverride; + createData.referenceOverride = + entry.overrideMutation.referenceOverride; } if (entry.overrideMutation?.prixOverride !== undefined) { createData.prixOverride = entry.overrideMutation.prixOverride; @@ -1469,7 +1503,7 @@ export class MachinesService { const pieces = await prisma.piece.findMany({ where: { id: { in: Array.from(pieceIds) } }, - include: { typePiece: true }, + include: { typePiece: true, constructeurs: true }, }); const pieceMap = new Map( pieces.map((piece) => [piece.id, piece]), @@ -1567,7 +1601,8 @@ export class MachinesService { if (parentComponentRequirementId) { const matches = - componentLinkIndex.byRequirementId.get(parentComponentRequirementId) ?? []; + componentLinkIndex.byRequirementId.get(parentComponentRequirementId) ?? + []; if (matches.length === 1) { return matches[0].id; } @@ -1658,6 +1693,7 @@ export class MachinesService { const { componentLinks = [], pieceLinks = [], + constructeurIds, ...machineData } = createMachineDto; @@ -1667,27 +1703,38 @@ export class MachinesService { ); } + const normalizedConstructeurIds = + this.normalizeConstructeurIds(constructeurIds); + + const resolvedConstructeurIds = await this.resolveExistingConstructeurIds( + normalizedConstructeurIds, + ); + const typeMachine = await this.getTypeMachineConfiguration( machineData.typeMachineId, ); - const { - componentRequirementMap, - pieceRequirementMap, - } = this.buildConfigurationContext( - typeMachine, - componentLinks, - pieceLinks, - ); + const { componentRequirementMap, pieceRequirementMap } = + this.buildConfigurationContext(typeMachine, componentLinks, pieceLinks); let machine: Awaited>; try { + const createData: any = { + ...machineData, + }; + + if (resolvedConstructeurIds.length) { + createData.constructeurs = { + connect: resolvedConstructeurIds.map((id) => ({ id })), + }; + } + machine = await this.prisma.machine.create({ - data: machineData, + data: createData, include: { site: true, typeMachine: true, - constructeur: true, + constructeurs: true, }, }); } catch (error) { @@ -1776,11 +1823,7 @@ export class MachinesService { const typeMachine = machine.typeMachine as TypeMachineConfiguration; const { componentRequirementMap, pieceRequirementMap } = - this.buildConfigurationContext( - typeMachine, - componentLinks, - pieceLinks, - ); + this.buildConfigurationContext(typeMachine, componentLinks, pieceLinks); await this.prisma.$transaction(async (tx) => { await tx.machinePieceLink.deleteMany({ where: { machineId: id } }); @@ -1811,7 +1854,7 @@ export class MachinesService { } async update(id: string, updateMachineDto: UpdateMachineDto) { - const { name, reference, constructeurId, prix, typeMachineId } = + const { name, reference, constructeurIds, prix, typeMachineId } = updateMachineDto; const data: Prisma.MachineUpdateInput = {}; @@ -1824,11 +1867,15 @@ export class MachinesService { data.reference = reference; } - if (constructeurId !== undefined) { - const resolvedConstructeurId = this.extractString(constructeurId); - data.constructeur = resolvedConstructeurId - ? { connect: { id: resolvedConstructeurId } } - : { disconnect: true }; + if (constructeurIds !== undefined) { + const normalizedConstructeurIds = + this.normalizeConstructeurIds(constructeurIds); + const resolvedConstructeurIds = await this.resolveExistingConstructeurIds( + normalizedConstructeurIds, + ); + data.constructeurs = { + set: resolvedConstructeurIds.map((id) => ({ id })), + }; } if (prix !== undefined) { @@ -1836,7 +1883,8 @@ export class MachinesService { if (normalizedPrice === undefined) { throw new Error('Le prix fourni est invalide.'); } - data.prix = normalizedPrice === null ? null : new Prisma.Decimal(normalizedPrice); + data.prix = + normalizedPrice === null ? null : new Prisma.Decimal(normalizedPrice); } if (typeMachineId !== undefined) { diff --git a/src/model-type/model-type.service.ts b/src/model-type/model-type.service.ts index 7c57648..f5c22cb 100644 --- a/src/model-type/model-type.service.ts +++ b/src/model-type/model-type.service.ts @@ -218,9 +218,7 @@ export class ModelTypeService { } if (this.isUniqueNameConstraint(error)) { - throw new ConflictException( - 'Une catégorie avec ce nom existe déjà.', - ); + throw new ConflictException('Une catégorie avec ce nom existe déjà.'); } } diff --git a/src/pieces/pieces.service.spec.ts b/src/pieces/pieces.service.spec.ts index 8dcac4a..0b1a4dc 100644 --- a/src/pieces/pieces.service.spec.ts +++ b/src/pieces/pieces.service.spec.ts @@ -27,10 +27,7 @@ describe('PiecesService', () => { }; const module: TestingModule = await Test.createTestingModule({ - providers: [ - PiecesService, - { provide: PrismaService, useValue: prisma }, - ], + providers: [PiecesService, { provide: PrismaService, useValue: prisma }], }).compile(); service = module.get(PiecesService); @@ -43,7 +40,10 @@ describe('PiecesService', () => { }; prisma.piece.create.mockResolvedValue({ id: 'piece-1', name: dto.name }); - prisma.piece.findUnique.mockResolvedValue({ id: 'piece-1', name: dto.name }); + prisma.piece.findUnique.mockResolvedValue({ + id: 'piece-1', + name: dto.name, + }); prisma.customField.findMany.mockResolvedValue([]); prisma.customFieldValue.findMany.mockResolvedValue([]); @@ -56,8 +56,14 @@ describe('PiecesService', () => { it('updates a piece', async () => { const dto: UpdatePieceDto = { name: 'Updated piece' }; - prisma.piece.update.mockResolvedValue({ id: 'piece-1', name: 'Updated piece' }); - prisma.piece.findUnique.mockResolvedValue({ id: 'piece-1', name: 'Updated piece' }); + prisma.piece.update.mockResolvedValue({ + id: 'piece-1', + name: 'Updated piece', + }); + prisma.piece.findUnique.mockResolvedValue({ + id: 'piece-1', + name: 'Updated piece', + }); prisma.customField.findMany.mockResolvedValue([]); prisma.customFieldValue.findMany.mockResolvedValue([]); diff --git a/src/pieces/pieces.service.ts b/src/pieces/pieces.service.ts index 8bcfbed..bcf68f5 100644 --- a/src/pieces/pieces.service.ts +++ b/src/pieces/pieces.service.ts @@ -11,7 +11,7 @@ const PIECE_WITH_RELATIONS_INCLUDE = { pieceCustomFields: true, }, }, - constructeur: true, + constructeurs: true, documents: true, customFieldValues: { include: { @@ -31,16 +31,23 @@ const PIECE_WITH_RELATIONS_INCLUDE = { export class PiecesService { constructor(private prisma: PrismaService) {} - private buildCreateInput(createPieceDto: CreatePieceDto): Prisma.PieceCreateInput { + private async buildCreateInput( + createPieceDto: CreatePieceDto, + ): Promise { const data: Prisma.PieceCreateInput = { name: createPieceDto.name, reference: createPieceDto.reference ?? null, prix: createPieceDto.prix !== undefined ? createPieceDto.prix : null, }; - if (createPieceDto.constructeurId) { - data.constructeur = { - connect: { id: createPieceDto.constructeurId }, + const constructeurIds = this.normalizeConstructeurIds( + createPieceDto.constructeurIds, + ); + const resolvedConstructeurIds = + await this.resolveExistingConstructeurIds(constructeurIds); + if (resolvedConstructeurIds.length) { + data.constructeurs = { + connect: resolvedConstructeurIds.map((id) => ({ id })), }; } @@ -56,7 +63,7 @@ export class PiecesService { async create(createPieceDto: CreatePieceDto) { try { const created = await this.prisma.piece.create({ - data: this.buildCreateInput(createPieceDto), + data: await this.buildCreateInput(createPieceDto), include: PIECE_WITH_RELATIONS_INCLUDE, }); @@ -103,10 +110,15 @@ export class PiecesService { data.prix = updatePieceDto.prix; } - if (updatePieceDto.constructeurId !== undefined) { - data.constructeur = updatePieceDto.constructeurId - ? { connect: { id: updatePieceDto.constructeurId } } - : { disconnect: true }; + if (updatePieceDto.constructeurIds !== undefined) { + const constructeurIds = this.normalizeConstructeurIds( + updatePieceDto.constructeurIds, + ); + const resolvedConstructeurIds = + await this.resolveExistingConstructeurIds(constructeurIds); + data.constructeurs = { + set: resolvedConstructeurIds.map((id) => ({ id })), + }; } if (updatePieceDto.typePieceId !== undefined) { @@ -224,6 +236,30 @@ export class PiecesService { ); } + private normalizeConstructeurIds(ids?: string[] | null): string[] { + if (!Array.isArray(ids)) { + return []; + } + const cleaned = ids + .map((item) => (typeof item === 'string' ? item.trim() : '')) + .filter((item) => item.length > 0); + return Array.from(new Set(cleaned)); + } + + private async resolveExistingConstructeurIds( + ids: string[], + ): Promise { + if (!ids.length) { + return []; + } + const existing = await this.prisma.constructeur.findMany({ + where: { id: { in: ids } }, + select: { id: true }, + }); + const existingIds = new Set(existing.map(({ id }) => id)); + return ids.filter((id) => existingIds.has(id)); + } + private parsePieceSkeleton(value: unknown): PieceModelStructure | null { if (!value) { return null; @@ -430,4 +466,6 @@ type PieceTypeWithSkeleton = Prisma.ModelTypeGetPayload<{ include: { pieceCustomFields: true }; }>; -type PieceCustomFieldEntry = NonNullable[number]; +type PieceCustomFieldEntry = NonNullable< + PieceModelStructure['customFields'] +>[number]; diff --git a/src/shared/dto/composant.dto.ts b/src/shared/dto/composant.dto.ts index 6a841a5..9e3f37e 100644 --- a/src/shared/dto/composant.dto.ts +++ b/src/shared/dto/composant.dto.ts @@ -1,4 +1,10 @@ -import { IsString, IsOptional, IsNumber, IsObject } from 'class-validator'; +import { + IsString, + IsOptional, + IsNumber, + IsObject, + IsArray, +} from 'class-validator'; import { Transform } from 'class-transformer'; export class CreateComposantDto { @@ -18,8 +24,10 @@ export class CreateComposantDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsOptional() + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @Transform(({ value }) => (value === '' ? null : value)) @@ -49,8 +57,9 @@ export class UpdateComposantDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @Transform(({ value }) => (value === '' ? null : value)) diff --git a/src/shared/dto/machine.dto.ts b/src/shared/dto/machine.dto.ts index cee14cc..9fc77bf 100644 --- a/src/shared/dto/machine.dto.ts +++ b/src/shared/dto/machine.dto.ts @@ -154,8 +154,9 @@ export class CreateMachineDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @IsDecimal() @@ -188,8 +189,9 @@ export class UpdateMachineDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @IsDecimal() diff --git a/src/shared/dto/piece.dto.ts b/src/shared/dto/piece.dto.ts index ab56326..fdc6ddf 100644 --- a/src/shared/dto/piece.dto.ts +++ b/src/shared/dto/piece.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional, IsNumber } from 'class-validator'; +import { IsString, IsOptional, IsNumber, IsArray } from 'class-validator'; import { Transform } from 'class-transformer'; export class CreatePieceDto { @@ -18,8 +18,10 @@ export class CreatePieceDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsOptional() + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @Transform(({ value }) => (value === '' ? null : value)) @@ -45,8 +47,10 @@ export class UpdatePieceDto { reference?: string; @IsOptional() - @IsString() - constructeurId?: string; + @IsOptional() + @IsArray() + @IsString({ each: true }) + constructeurIds?: string[]; @IsOptional() @Transform(({ value }) => (value === '' ? null : value)) diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index d5c6e9a..2d1ba4f 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -73,7 +73,7 @@ type MachineRecord = { id: string; name: string; reference: Nullable; - constructeurId: Nullable; + constructeurIds: string[]; prix: Nullable; siteId: string; typeMachineId: Nullable; @@ -90,7 +90,7 @@ type ComposantRecord = { parentComposantId: Nullable; typeComposantId: Nullable; typeMachineComponentRequirementId: Nullable; - constructeurId: Nullable; + constructeurIds: string[]; createdAt: Date; updatedAt: Date; }; @@ -104,7 +104,7 @@ type PieceRecord = { composantId: Nullable; typePieceId: Nullable; typeMachinePieceRequirementId: Nullable; - constructeurId: Nullable; + constructeurIds: string[]; createdAt: Date; updatedAt: Date; }; @@ -638,11 +638,12 @@ class InMemoryPrismaService { machine = { create: async ({ data, include }: any) => { const now = new Date(); + const constructeurIds = this.extractConstructeurIds(data.constructeurs); const record: MachineRecord = { id: generateId('machine'), name: data.name, reference: data.reference ?? null, - constructeurId: data.constructeurId ?? null, + constructeurIds, prix: data.prix ?? null, siteId: data.siteId, typeMachineId: data.typeMachineId ?? null, @@ -683,6 +684,7 @@ class InMemoryPrismaService { composant = { create: async ({ data }: any) => { const now = new Date(); + const constructeurIds = this.extractConstructeurIds(data.constructeurs); const record: ComposantRecord = { id: generateId('component'), name: data.name, @@ -693,7 +695,7 @@ class InMemoryPrismaService { typeComposantId: data.typeComposantId ?? null, typeMachineComponentRequirementId: data.typeMachineComponentRequirementId ?? null, - constructeurId: data.constructeurId ?? null, + constructeurIds, createdAt: now, updatedAt: now, }; @@ -719,6 +721,7 @@ class InMemoryPrismaService { piece = { create: async ({ data }: any) => { const now = new Date(); + const constructeurIds = this.extractConstructeurIds(data.constructeurs); const record: PieceRecord = { id: generateId('piece'), name: data.name, @@ -729,7 +732,7 @@ class InMemoryPrismaService { typePieceId: data.typePieceId ?? null, typeMachinePieceRequirementId: data.typeMachinePieceRequirementId ?? null, - constructeurId: data.constructeurId ?? null, + constructeurIds, createdAt: now, updatedAt: now, }; @@ -767,7 +770,7 @@ class InMemoryPrismaService { prixOverride: data.prixOverride !== undefined && data.prixOverride !== null ? String(data.prixOverride) - : data.prixOverride ?? null, + : (data.prixOverride ?? null), createdAt: now, updatedAt: now, }; @@ -819,7 +822,7 @@ class InMemoryPrismaService { prixOverride: data.prixOverride !== undefined && data.prixOverride !== null ? String(data.prixOverride) - : data.prixOverride ?? null, + : (data.prixOverride ?? null), createdAt: now, updatedAt: now, }; @@ -848,7 +851,9 @@ class InMemoryPrismaService { (link) => link.parentLinkId === where.parentLinkId, ); } - return links.map((link) => this.buildMachinePieceLink(link, include ?? {})); + return links.map((link) => + this.buildMachinePieceLink(link, include ?? {}), + ); }, }; @@ -1279,6 +1284,37 @@ class InMemoryPrismaService { return base; } + private extractConstructeurIds(input: any): string[] { + if (!input) { + return []; + } + + const source = Array.isArray(input.set) + ? input.set + : Array.isArray(input.connect) + ? input.connect + : []; + + return source + .map((entry: any) => (typeof entry?.id === 'string' ? entry.id : null)) + .filter((id: string | null): id is string => Boolean(id)); + } + + private mapConstructeurs(ids: string[] = []) { + if (!Array.isArray(ids) || ids.length === 0) { + return []; + } + + return ids + .map((id) => + this.constructeurs.find((constructeur) => constructeur.id === id), + ) + .filter((item): item is (typeof this.constructeurs)[number] => + Boolean(item), + ) + .map((item) => ({ ...item })); + } + private buildMachine(machine: MachineRecord, include: any) { const base: any = { ...machine }; @@ -1309,8 +1345,8 @@ class InMemoryPrismaService { } } - if (include?.constructeur) { - base.constructeur = null; + if (include?.constructeurs) { + base.constructeurs = this.mapConstructeurs(machine.constructeurIds); } if (include?.componentLinks) { @@ -1389,19 +1425,19 @@ class InMemoryPrismaService { if (include?.typeMachineComponentRequirement) { const requirement = link.typeMachineComponentRequirementId - ? this.typeMachineComponentRequirements.find( + ? (this.typeMachineComponentRequirements.find( (item) => item.id === link.typeMachineComponentRequirementId, - ) ?? null + ) ?? null) : null; base.typeMachineComponentRequirement = requirement ? { ...requirement, - typeComposant: - include.typeMachineComponentRequirement.include?.typeComposant - ? this.typeComposants.find( - (item) => item.id === requirement.typeComposantId, - ) ?? null - : undefined, + typeComposant: include.typeMachineComponentRequirement.include + ?.typeComposant + ? (this.typeComposants.find( + (item) => item.id === requirement.typeComposantId, + ) ?? null) + : undefined, } : null; } @@ -1419,14 +1455,12 @@ class InMemoryPrismaService { return base; } - private buildMachinePieceLink( - link: MachinePieceLinkRecord, - include: any, - ) { + private buildMachinePieceLink(link: MachinePieceLinkRecord, include: any) { const base: any = { ...link }; if (include?.piece) { - const piece = this.pieces.find((item) => item.id === link.pieceId) ?? null; + const piece = + this.pieces.find((item) => item.id === link.pieceId) ?? null; base.piece = piece ? this.buildPiece(piece, include.piece.include ?? {}) : null; @@ -1434,19 +1468,18 @@ class InMemoryPrismaService { if (include?.typeMachinePieceRequirement) { const requirement = link.typeMachinePieceRequirementId - ? this.typeMachinePieceRequirements.find( + ? (this.typeMachinePieceRequirements.find( (item) => item.id === link.typeMachinePieceRequirementId, - ) ?? null + ) ?? null) : null; base.typeMachinePieceRequirement = requirement ? { ...requirement, - typePiece: - include.typeMachinePieceRequirement.include?.typePiece - ? this.typePieces.find( - (item) => item.id === requirement.typePieceId, - ) ?? null - : undefined, + typePiece: include.typeMachinePieceRequirement.include?.typePiece + ? (this.typePieces.find( + (item) => item.id === requirement.typePieceId, + ) ?? null) + : undefined, } : null; } @@ -1505,8 +1538,8 @@ class InMemoryPrismaService { ); } - if (include?.constructeur) { - base.constructeur = null; + if (include?.constructeurs) { + base.constructeurs = this.mapConstructeurs(component.constructeurIds); } if (include?.pieces) { @@ -1536,8 +1569,8 @@ class InMemoryPrismaService { ); } - if (include?.constructeur) { - base.constructeur = null; + if (include?.constructeurs) { + base.constructeurs = this.mapConstructeurs(piece.constructeurIds); } if (include?.typeMachinePieceRequirement) {