From b10a6baf4746ec3257d1996ab0595c51d6011dd0 Mon Sep 17 00:00:00 2001 From: MatthieuTD <39524319+MatthieuTD@users.noreply.github.com> Date: Thu, 25 Sep 2025 16:15:37 +0200 Subject: [PATCH] Refactor types service into modular services and repositories --- src/common/constants/custom-field.constant.ts | 7 + src/common/mappers/model-type.mapper.spec.ts | 55 ++ src/common/mappers/model-type.mapper.ts | 170 +++++ .../mappers/type-machine.mapper.spec.ts | 83 +++ src/common/mappers/type-machine.mapper.ts | 188 +++++ .../composant-models.repository.ts | 58 ++ .../model-types.repository.spec.ts | 71 ++ .../repositories/model-types.repository.ts | 172 +++++ .../repositories/piece-models.repository.ts | 55 ++ .../type-machines.repository.spec.ts | 117 ++++ .../repositories/type-machines.repository.ts | 141 ++++ src/common/utils/slug.util.ts | 9 + src/types/services/composant-model.service.ts | 53 ++ src/types/services/piece-model.service.ts | 49 ++ src/types/services/type-component.service.ts | 47 ++ src/types/services/type-machine.service.ts | 72 ++ src/types/services/type-piece.service.ts | 54 ++ src/types/types.controller.spec.ts | 23 +- src/types/types.module.ts | 22 +- src/types/types.service.spec.ts | 23 +- src/types/types.service.ts | 653 ++---------------- 21 files changed, 1533 insertions(+), 589 deletions(-) create mode 100644 src/common/constants/custom-field.constant.ts create mode 100644 src/common/mappers/model-type.mapper.spec.ts create mode 100644 src/common/mappers/model-type.mapper.ts create mode 100644 src/common/mappers/type-machine.mapper.spec.ts create mode 100644 src/common/mappers/type-machine.mapper.ts create mode 100644 src/common/repositories/composant-models.repository.ts create mode 100644 src/common/repositories/model-types.repository.spec.ts create mode 100644 src/common/repositories/model-types.repository.ts create mode 100644 src/common/repositories/piece-models.repository.ts create mode 100644 src/common/repositories/type-machines.repository.spec.ts create mode 100644 src/common/repositories/type-machines.repository.ts create mode 100644 src/common/utils/slug.util.ts create mode 100644 src/types/services/composant-model.service.ts create mode 100644 src/types/services/piece-model.service.ts create mode 100644 src/types/services/type-component.service.ts create mode 100644 src/types/services/type-machine.service.ts create mode 100644 src/types/services/type-piece.service.ts diff --git a/src/common/constants/custom-field.constant.ts b/src/common/constants/custom-field.constant.ts new file mode 100644 index 0000000..65bd78d --- /dev/null +++ b/src/common/constants/custom-field.constant.ts @@ -0,0 +1,7 @@ +export const CUSTOM_FIELD_SELECT = { + id: true, + name: true, + type: true, + required: true, + options: true, +} as const; diff --git a/src/common/mappers/model-type.mapper.spec.ts b/src/common/mappers/model-type.mapper.spec.ts new file mode 100644 index 0000000..1524ed4 --- /dev/null +++ b/src/common/mappers/model-type.mapper.spec.ts @@ -0,0 +1,55 @@ +import { ModelTypeMapper } from './model-type.mapper'; + +describe('ModelTypeMapper', () => { + it('should map component create input', () => { + const dto = { + name: 'Comp', + description: 'Desc', + customFields: [{ name: 'Field', type: 'string', required: false, options: [] }], + } as any; + + const input = ModelTypeMapper.toComponentCreateInput(dto, 'code'); + + expect(input).toMatchObject({ + name: 'Comp', + code: 'code', + description: 'Desc', + notes: 'Desc', + }); + expect(input.customFields?.create?.[0]).toMatchObject({ name: 'Field' }); + }); + + it('should map piece model type to DTO shape', () => { + const mapped = ModelTypeMapper.mapPieceModelType({ + id: '1', + name: 'Piece', + pieceCustomFields: [{ id: 'cf' }], + pieceModels: [{ id: 'model' }], + pieceRequirements: [{ id: 'req' }], + pieces: [{ id: 'piece' }], + }); + + expect(mapped).toMatchObject({ + id: '1', + customFields: [{ id: 'cf' }], + models: [{ id: 'model' }], + pieceRequirements: [{ id: 'req' }], + pieces: [{ id: 'piece' }], + }); + }); + + it('should map piece update input', () => { + const input = ModelTypeMapper.toPieceUpdateInput({ + name: 'New', + description: 'D', + customFields: [], + } as any); + + expect(input).toMatchObject({ + name: 'New', + description: 'D', + notes: 'D', + }); + expect(input.pieceCustomFields).toBeUndefined(); + }); +}); diff --git a/src/common/mappers/model-type.mapper.ts b/src/common/mappers/model-type.mapper.ts new file mode 100644 index 0000000..059be19 --- /dev/null +++ b/src/common/mappers/model-type.mapper.ts @@ -0,0 +1,170 @@ +import { Prisma } from '@prisma/client'; +import { + CreateTypeComposantDto, + CreateTypePieceDto, + UpdateTypeComposantDto, + UpdateTypePieceDto, +} from '../../shared/dto/type.dto'; +import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant'; + +export const COMPONENT_TYPE_INCLUDE: Prisma.ModelTypeInclude = { + customFields: { select: CUSTOM_FIELD_SELECT }, + composants: true, + models: true, +}; + +export const PIECE_TYPE_INCLUDE: Prisma.ModelTypeInclude = { + pieceCustomFields: { select: CUSTOM_FIELD_SELECT }, + pieceModels: true, + pieceRequirements: true, + pieces: true, +}; + +type ModelTypeCreateWithoutCategory = Omit< + Prisma.ModelTypeCreateInput, + 'category' +>; + +export class ModelTypeMapper { + static toComponentCreateInput( + dto: CreateTypeComposantDto, + code: string, + ): ModelTypeCreateWithoutCategory { + const { customFields, description, name } = dto; + + return { + name, + code, + description: description ?? null, + notes: description ?? null, + customFields: customFields + ? { + create: customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })), + } + : undefined, + }; + } + + static toComponentUpdateInput( + dto: UpdateTypeComposantDto, + ): Prisma.ModelTypeUpdateInput { + const { customFields, description, name } = dto; + const data: Prisma.ModelTypeUpdateInput = {}; + + if (name !== undefined) { + data.name = name; + } + + if (description !== undefined) { + data.description = description; + data.notes = description; + } + + if (customFields !== undefined) { + data.customFields = undefined; + } + + return data; + } + + static toPieceCreateInput( + dto: CreateTypePieceDto, + code: string, + ): ModelTypeCreateWithoutCategory { + const { customFields, description, name } = dto; + + return { + name, + code, + description: description ?? null, + notes: description ?? null, + pieceCustomFields: customFields + ? { + create: customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })), + } + : undefined, + }; + } + + static toPieceUpdateInput(dto: UpdateTypePieceDto): Prisma.ModelTypeUpdateInput { + const { customFields, description, name } = dto; + const data: Prisma.ModelTypeUpdateInput = {}; + + if (name !== undefined) { + data.name = name; + } + + if (description !== undefined) { + data.description = description; + data.notes = description; + } + + if (customFields !== undefined) { + data.pieceCustomFields = undefined; + } + + return data; + } + + static mapPieceModelType(modelType: any) { + if (!modelType) { + return null; + } + + const { + pieceCustomFields, + pieceModels, + pieceRequirements, + pieces, + ...rest + } = modelType; + + return { + ...rest, + customFields: pieceCustomFields ?? [], + models: pieceModels ?? [], + pieceRequirements: pieceRequirements ?? [], + pieces: pieces ?? [], + }; + } + + static mapComponentCustomFieldInputs( + fields?: CreateTypeComposantDto['customFields'], + ) { + if (!fields || fields.length === 0) { + return []; + } + + return fields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })); + } + + static mapPieceCustomFieldInputs( + fields?: CreateTypePieceDto['customFields'], + ) { + if (!fields || fields.length === 0) { + return []; + } + + return fields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })); + } +} diff --git a/src/common/mappers/type-machine.mapper.spec.ts b/src/common/mappers/type-machine.mapper.spec.ts new file mode 100644 index 0000000..bf101dc --- /dev/null +++ b/src/common/mappers/type-machine.mapper.spec.ts @@ -0,0 +1,83 @@ +import { TypeMachineMapper } from './type-machine.mapper'; + +const baseDto = { + name: 'Machine A', + description: 'Desc', + customFields: [ + { name: 'Field', type: 'string', required: true, options: ['a'] }, + ], + componentRequirements: [ + { + label: 'Comp', + minCount: 2, + maxCount: 4, + required: true, + allowNewModels: false, + typeComposantId: 'comp-id', + }, + ], + pieceRequirements: [ + { + label: 'Piece', + minCount: 0, + maxCount: 2, + required: false, + allowNewModels: true, + typePieceId: 'piece-id', + }, + ], +}; + +describe('TypeMachineMapper', () => { + it('should map create input with nested relations', () => { + const input = TypeMachineMapper.toCreateInput(baseDto as any); + + expect(input.customFields?.create).toHaveLength(1); + expect(input.componentRequirements?.create?.[0]).toMatchObject({ + label: 'Comp', + minCount: 2, + maxCount: 4, + required: true, + allowNewModels: false, + }); + expect(input.pieceRequirements?.create?.[0]).toMatchObject({ + label: 'Piece', + minCount: 0, + maxCount: 2, + required: false, + allowNewModels: true, + }); + }); + + it('should map custom field inputs for create many', () => { + const result = TypeMachineMapper.mapCustomFieldInputs(baseDto.customFields as any); + expect(result).toEqual([ + { + name: 'Field', + type: 'string', + required: true, + options: ['a'], + }, + ]); + }); + + it('should map requirements for bulk insert', () => { + const component = TypeMachineMapper.mapComponentRequirementInputs( + baseDto.componentRequirements as any, + ); + const piece = TypeMachineMapper.mapPieceRequirementInputs( + baseDto.pieceRequirements as any, + ); + + expect(component[0]).toMatchObject({ + typeComposantId: 'comp-id', + minCount: 2, + maxCount: 4, + }); + expect(piece[0]).toMatchObject({ + typePieceId: 'piece-id', + minCount: 0, + maxCount: 2, + }); + }); +}); diff --git a/src/common/mappers/type-machine.mapper.ts b/src/common/mappers/type-machine.mapper.ts new file mode 100644 index 0000000..e61924b --- /dev/null +++ b/src/common/mappers/type-machine.mapper.ts @@ -0,0 +1,188 @@ +import { Prisma } from '@prisma/client'; +import { + CreateTypeMachineDto, + UpdateTypeMachineDto, +} from '../../shared/dto/type.dto'; +import { CUSTOM_FIELD_SELECT } from '../constants/custom-field.constant'; + +type RequirementDto = { + label?: string | null; + minCount?: number | null; + maxCount?: number | null; + required?: boolean | null; + allowNewModels?: boolean | null; + typeComposantId?: string; + typePieceId?: string; +}; + +export const TYPE_MACHINE_DEFAULT_INCLUDE: Prisma.TypeMachineInclude = { + customFields: { select: CUSTOM_FIELD_SELECT }, + componentRequirements: { + include: { typeComposant: true }, + }, + pieceRequirements: { + include: { typePiece: true }, + }, +}; + +export const TYPE_MACHINE_WITH_MACHINES_INCLUDE: Prisma.TypeMachineInclude = { + ...TYPE_MACHINE_DEFAULT_INCLUDE, + machines: true, +}; + +export class TypeMachineMapper { + static toCreateInput( + dto: CreateTypeMachineDto, + ): Prisma.TypeMachineCreateInput { + const { customFields, componentRequirements, pieceRequirements, ...data } = + dto; + + return { + ...data, + customFields: this.mapCustomFields(customFields), + componentRequirements: this.mapComponentRequirements(componentRequirements), + pieceRequirements: this.mapPieceRequirements(pieceRequirements), + }; + } + + static toUpdateData(dto: UpdateTypeMachineDto): Prisma.TypeMachineUpdateInput { + const { customFields, componentRequirements, pieceRequirements, ...data } = + dto; + + const payload: Prisma.TypeMachineUpdateInput = { ...data }; + + if (customFields !== undefined) { + payload.customFields = undefined; + } + + if (componentRequirements !== undefined) { + payload.componentRequirements = undefined; + } + + if (pieceRequirements !== undefined) { + payload.pieceRequirements = undefined; + } + + return payload; + } + + static mapCustomFields( + fields?: CreateTypeMachineDto['customFields'], + ): Prisma.CustomFieldCreateNestedManyWithoutTypeMachineInput | undefined { + if (!fields || fields.length === 0) { + return undefined; + } + + return { + create: fields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })), + }; + } + + static mapCustomFieldInputs( + fields?: CreateTypeMachineDto['customFields'], + ) { + if (!fields || fields.length === 0) { + return []; + } + + return fields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + options: field.options, + })); + } + + static mapComponentRequirements( + requirements?: RequirementDto[] | null, + ): Prisma.TypeMachineComponentRequirementCreateNestedManyWithoutTypeMachineInput | undefined { + if (!requirements || requirements.length === 0) { + return undefined; + } + + return { + create: requirements.map((requirement) => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 1, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? true, + allowNewModels: requirement.allowNewModels ?? true, + typeComposant: requirement.typeComposantId + ? { + connect: { id: requirement.typeComposantId }, + } + : (() => { + throw new Error( + 'typeComposantId est requis pour créer une contrainte de composant.', + ); + })(), + })), + }; + } + + static mapComponentRequirementInputs( + requirements?: RequirementDto[] | null, + ) { + if (!requirements || requirements.length === 0) { + return []; + } + + return requirements.map((requirement) => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 1, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? true, + allowNewModels: requirement.allowNewModels ?? true, + typeComposantId: requirement.typeComposantId!, + })); + } + + static mapPieceRequirements( + requirements?: RequirementDto[] | null, + ): Prisma.TypeMachinePieceRequirementCreateNestedManyWithoutTypeMachineInput | undefined { + if (!requirements || requirements.length === 0) { + return undefined; + } + + return { + create: requirements.map((requirement) => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 0, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? false, + allowNewModels: requirement.allowNewModels ?? true, + typePiece: requirement.typePieceId + ? { + connect: { id: requirement.typePieceId }, + } + : (() => { + throw new Error( + 'typePieceId est requis pour créer une contrainte de pièce.', + ); + })(), + })), + }; + } + + static mapPieceRequirementInputs( + requirements?: RequirementDto[] | null, + ) { + if (!requirements || requirements.length === 0) { + return []; + } + + return requirements.map((requirement) => ({ + label: requirement.label ?? null, + minCount: requirement.minCount ?? 0, + maxCount: requirement.maxCount ?? null, + required: requirement.required ?? false, + allowNewModels: requirement.allowNewModels ?? true, + typePieceId: requirement.typePieceId!, + })); + } +} diff --git a/src/common/repositories/composant-models.repository.ts b/src/common/repositories/composant-models.repository.ts new file mode 100644 index 0000000..eb39bfa --- /dev/null +++ b/src/common/repositories/composant-models.repository.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@nestjs/common'; +import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaService } from '../../prisma/prisma.service'; + +@Injectable() +export class ComposantModelsRepository { + constructor(private readonly prisma: PrismaService) {} + + private get client(): PrismaClient { + return this.prisma; + } + + async create( + data: Prisma.ComposantModelCreateInput, + include?: Prisma.ComposantModelInclude, + ) { + return this.client.composantModel.create({ + data, + include, + }); + } + + async findAll( + typeComposantId?: string, + include?: Prisma.ComposantModelInclude, + ) { + return this.client.composantModel.findMany({ + where: typeComposantId ? { typeComposantId } : undefined, + include, + orderBy: { name: 'asc' }, + }); + } + + async findOne(id: string, include?: Prisma.ComposantModelInclude) { + return this.client.composantModel.findUnique({ + where: { id }, + include, + }); + } + + async update( + id: string, + data: Prisma.ComposantModelUpdateInput, + include?: Prisma.ComposantModelInclude, + ) { + return this.client.composantModel.update({ + where: { id }, + data, + include, + }); + } + + async delete(id: string) { + return this.client.composantModel.delete({ + where: { id }, + }); + } +} diff --git a/src/common/repositories/model-types.repository.spec.ts b/src/common/repositories/model-types.repository.spec.ts new file mode 100644 index 0000000..cf87aa4 --- /dev/null +++ b/src/common/repositories/model-types.repository.spec.ts @@ -0,0 +1,71 @@ +import { ModelTypesRepository } from './model-types.repository'; + +describe('ModelTypesRepository', () => { + const prismaMock = { + modelType: { + findUnique: jest.fn(), + create: jest.fn(), + findMany: jest.fn(), + findFirst: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }, + customField: { + deleteMany: jest.fn(), + createMany: jest.fn(), + }, + } as any; + + const repository = new ModelTypesRepository(prismaMock); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should generate unique code when conflicts exist', async () => { + prismaMock.modelType.findUnique + .mockResolvedValueOnce({ id: 'existing' }) + .mockResolvedValueOnce(null); + + const code = await repository.generateUniqueCode('My Type'); + + expect(code).toBe('my-type-1'); + expect(prismaMock.modelType.findUnique).toHaveBeenCalledTimes(2); + }); + + it('should create component type custom fields with relation id', async () => { + await repository.createComponentTypeCustomFields('comp-id', [ + { name: 'Field', type: 'string', required: true, options: [] }, + ]); + + expect(prismaMock.customField.createMany).toHaveBeenCalledWith({ + data: [ + { + name: 'Field', + type: 'string', + required: true, + options: [], + typeComposantId: 'comp-id', + }, + ], + }); + }); + + it('should create piece type custom fields with relation id', async () => { + await repository.createPieceTypeCustomFields('piece-id', [ + { name: 'Field', type: 'string', required: false, options: [] }, + ]); + + expect(prismaMock.customField.createMany).toHaveBeenCalledWith({ + data: [ + { + name: 'Field', + type: 'string', + required: false, + options: [], + typePieceId: 'piece-id', + }, + ], + }); + }); +}); diff --git a/src/common/repositories/model-types.repository.ts b/src/common/repositories/model-types.repository.ts new file mode 100644 index 0000000..d601e1c --- /dev/null +++ b/src/common/repositories/model-types.repository.ts @@ -0,0 +1,172 @@ +import { Injectable } from '@nestjs/common'; +import { ModelCategory, Prisma, PrismaClient } from '@prisma/client'; +import { PrismaService } from '../../prisma/prisma.service'; +import { slugifyName } from '../utils/slug.util'; + +type CustomFieldInput = Omit< + Prisma.CustomFieldCreateManyInput, + 'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId' +>; + +type PieceCustomFieldInput = Omit< + Prisma.CustomFieldCreateManyInput, + 'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId' +>; + +type ModelTypeCreateData = Omit; + +@Injectable() +export class ModelTypesRepository { + constructor(private readonly prisma: PrismaService) {} + + private get client(): PrismaClient { + return this.prisma; + } + + async generateUniqueCode(name: string): Promise { + const base = slugifyName(name) || 'type'; + let candidate = base; + let suffix = 1; + + while (await this.client.modelType.findUnique({ where: { code: candidate } })) { + candidate = `${base}-${suffix++}`; + } + + return candidate; + } + + async createComponentType( + data: ModelTypeCreateData, + include?: Prisma.ModelTypeInclude, + ) { + return this.client.modelType.create({ + data: { + ...data, + category: ModelCategory.COMPONENT, + }, + include, + }); + } + + async findComponentTypes(include?: Prisma.ModelTypeInclude) { + return this.client.modelType.findMany({ + where: { category: ModelCategory.COMPONENT }, + include, + }); + } + + async findComponentType(id: string, include?: Prisma.ModelTypeInclude) { + return this.client.modelType.findFirst({ + where: { id, category: ModelCategory.COMPONENT }, + include, + }); + } + + async updateComponentType( + id: string, + data: Prisma.ModelTypeUpdateInput, + include?: Prisma.ModelTypeInclude, + ) { + return this.client.modelType.update({ + where: { id }, + data, + include, + }); + } + + async deleteComponentType(id: string) { + return this.client.modelType.delete({ + where: { id }, + }); + } + + async deleteComponentTypeCustomFields(typeComposantId: string) { + await this.client.customField.deleteMany({ + where: { typeComposantId }, + }); + } + + async createComponentTypeCustomFields( + typeComposantId: string, + customFields: CustomFieldInput[], + ) { + if (!customFields.length) { + return; + } + + await this.client.customField.createMany({ + data: customFields.map((field) => ({ + ...field, + typeComposantId, + })), + }); + } + + async createPieceType( + data: ModelTypeCreateData, + include?: Prisma.ModelTypeInclude, + ) { + return this.client.modelType.create({ + data: { + ...data, + category: ModelCategory.PIECE, + }, + include, + }); + } + + async findPieceTypes(include?: Prisma.ModelTypeInclude) { + return this.client.modelType.findMany({ + where: { category: ModelCategory.PIECE }, + include, + orderBy: { name: 'asc' }, + }); + } + + async findPieceType(id: string, include?: Prisma.ModelTypeInclude) { + return this.client.modelType.findFirst({ + where: { id, category: ModelCategory.PIECE }, + include, + }); + } + + async updatePieceType( + id: string, + data: Prisma.ModelTypeUpdateInput, + include?: Prisma.ModelTypeInclude, + ) { + return this.client.modelType.update({ + where: { id }, + data, + include, + }); + } + + async deletePieceType(id: string) { + return this.client.modelType.delete({ + where: { id }, + }); + } + + async deletePieceTypeCustomFields(typePieceId: string) { + await this.client.customField.deleteMany({ + where: { typePieceId }, + }); + } + + async createPieceTypeCustomFields( + typePieceId: string, + customFields: PieceCustomFieldInput[], + ) { + if (!customFields.length) { + return; + } + + await this.client.customField.createMany({ + data: customFields.map((field) => ({ + ...field, + typePieceId, + })), + }); + } +} diff --git a/src/common/repositories/piece-models.repository.ts b/src/common/repositories/piece-models.repository.ts new file mode 100644 index 0000000..3b29087 --- /dev/null +++ b/src/common/repositories/piece-models.repository.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaService } from '../../prisma/prisma.service'; + +@Injectable() +export class PieceModelsRepository { + constructor(private readonly prisma: PrismaService) {} + + private get client(): PrismaClient { + return this.prisma; + } + + async create( + data: Prisma.PieceModelCreateInput, + include?: Prisma.PieceModelInclude, + ) { + return this.client.pieceModel.create({ + data, + include, + }); + } + + async findAll(typePieceId?: string, include?: Prisma.PieceModelInclude) { + return this.client.pieceModel.findMany({ + where: typePieceId ? { typePieceId } : undefined, + include, + orderBy: { name: 'asc' }, + }); + } + + async findOne(id: string, include?: Prisma.PieceModelInclude) { + return this.client.pieceModel.findUnique({ + where: { id }, + include, + }); + } + + async update( + id: string, + data: Prisma.PieceModelUpdateInput, + include?: Prisma.PieceModelInclude, + ) { + return this.client.pieceModel.update({ + where: { id }, + data, + include, + }); + } + + async delete(id: string) { + return this.client.pieceModel.delete({ + where: { id }, + }); + } +} diff --git a/src/common/repositories/type-machines.repository.spec.ts b/src/common/repositories/type-machines.repository.spec.ts new file mode 100644 index 0000000..b56b827 --- /dev/null +++ b/src/common/repositories/type-machines.repository.spec.ts @@ -0,0 +1,117 @@ +import { TypeMachinesRepository } from './type-machines.repository'; + +describe('TypeMachinesRepository', () => { + const prismaMock = { + customField: { + deleteMany: jest.fn(), + createMany: jest.fn(), + }, + typeMachineComponentRequirement: { + deleteMany: jest.fn(), + createMany: jest.fn(), + }, + typeMachinePieceRequirement: { + deleteMany: jest.fn(), + createMany: jest.fn(), + }, + machine: { + findMany: jest.fn().mockResolvedValue([{ id: '1', name: 'Machine' }]), + }, + typeMachine: { + create: jest.fn(), + findMany: jest.fn(), + findUnique: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }, + } as any; + + const repository = new TypeMachinesRepository(prismaMock); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should append typeMachineId when creating custom fields', async () => { + await repository.createCustomFields('machine-id', [ + { name: 'Field', type: 'string', required: true, options: [] }, + ]); + + expect(prismaMock.customField.createMany).toHaveBeenCalledWith({ + data: [ + { + name: 'Field', + type: 'string', + required: true, + options: [], + typeMachineId: 'machine-id', + }, + ], + }); + }); + + it('should append typeMachineId when creating component requirements', async () => { + await repository.createComponentRequirements('machine-id', [ + { + label: 'Comp', + minCount: 1, + maxCount: 2, + required: true, + allowNewModels: true, + typeComposantId: 'comp', + }, + ]); + + expect( + prismaMock.typeMachineComponentRequirement.createMany, + ).toHaveBeenCalledWith({ + data: [ + { + label: 'Comp', + minCount: 1, + maxCount: 2, + required: true, + allowNewModels: true, + typeComposantId: 'comp', + typeMachineId: 'machine-id', + }, + ], + }); + }); + + it('should append typeMachineId when creating piece requirements', async () => { + await repository.createPieceRequirements('machine-id', [ + { + label: 'Piece', + minCount: 0, + maxCount: 1, + required: false, + allowNewModels: true, + typePieceId: 'piece', + }, + ]); + + expect(prismaMock.typeMachinePieceRequirement.createMany).toHaveBeenCalledWith({ + data: [ + { + label: 'Piece', + minCount: 0, + maxCount: 1, + required: false, + allowNewModels: true, + typePieceId: 'piece', + typeMachineId: 'machine-id', + }, + ], + }); + }); + + it('should find machines using type', async () => { + const result = await repository.findMachinesUsingType('machine-id'); + expect(result).toEqual([{ id: '1', name: 'Machine' }]); + expect(prismaMock.machine.findMany).toHaveBeenCalledWith({ + where: { typeMachineId: 'machine-id' }, + select: { id: true, name: true }, + }); + }); +}); diff --git a/src/common/repositories/type-machines.repository.ts b/src/common/repositories/type-machines.repository.ts new file mode 100644 index 0000000..9e59bd4 --- /dev/null +++ b/src/common/repositories/type-machines.repository.ts @@ -0,0 +1,141 @@ +import { Injectable } from '@nestjs/common'; +import { Prisma, PrismaClient } from '@prisma/client'; +import { PrismaService } from '../../prisma/prisma.service'; + +type CustomFieldInput = Omit< + Prisma.CustomFieldCreateManyInput, + 'id' | 'typeMachineId' | 'typePieceId' | 'typeComposantId' +>; + +type ComponentRequirementInput = Omit< + Prisma.TypeMachineComponentRequirementCreateManyInput, + 'id' | 'typeMachineId' +>; + +type PieceRequirementInput = Omit< + Prisma.TypeMachinePieceRequirementCreateManyInput, + 'id' | 'typeMachineId' +>; + +@Injectable() +export class TypeMachinesRepository { + constructor(private readonly prisma: PrismaService) {} + + private get client(): PrismaClient { + return this.prisma; + } + + async create( + data: Prisma.TypeMachineCreateInput, + include?: Prisma.TypeMachineInclude, + ) { + return this.client.typeMachine.create({ + data, + include, + }); + } + + async findAll(include?: Prisma.TypeMachineInclude) { + return this.client.typeMachine.findMany({ + include, + }); + } + + async findOne(id: string, include?: Prisma.TypeMachineInclude) { + return this.client.typeMachine.findUnique({ + where: { id }, + include, + }); + } + + async update( + id: string, + data: Prisma.TypeMachineUpdateInput, + include?: Prisma.TypeMachineInclude, + ) { + return this.client.typeMachine.update({ + where: { id }, + data, + include, + }); + } + + async delete(id: string) { + return this.client.typeMachine.delete({ + where: { id }, + }); + } + + async deleteCustomFields(typeMachineId: string) { + await this.client.customField.deleteMany({ + where: { typeMachineId }, + }); + } + + async createCustomFields( + typeMachineId: string, + customFields: CustomFieldInput[], + ) { + if (!customFields.length) { + return; + } + + await this.client.customField.createMany({ + data: customFields.map((field) => ({ + ...field, + typeMachineId, + })), + }); + } + + async deleteComponentRequirements(typeMachineId: string) { + await this.client.typeMachineComponentRequirement.deleteMany({ + where: { typeMachineId }, + }); + } + + async createComponentRequirements( + typeMachineId: string, + requirements: ComponentRequirementInput[], + ) { + if (!requirements.length) { + return; + } + + await this.client.typeMachineComponentRequirement.createMany({ + data: requirements.map((requirement) => ({ + ...requirement, + typeMachineId, + })), + }); + } + + async deletePieceRequirements(typeMachineId: string) { + await this.client.typeMachinePieceRequirement.deleteMany({ + where: { typeMachineId }, + }); + } + + async createPieceRequirements( + typeMachineId: string, + requirements: PieceRequirementInput[], + ) { + if (!requirements.length) { + return; + } + + await this.client.typeMachinePieceRequirement.createMany({ + data: requirements.map((requirement) => ({ + ...requirement, + typeMachineId, + })), + }); + } + + async findMachinesUsingType(typeMachineId: string) { + return this.client.machine.findMany({ + where: { typeMachineId }, + select: { id: true, name: true }, + }); + } +} diff --git a/src/common/utils/slug.util.ts b/src/common/utils/slug.util.ts new file mode 100644 index 0000000..1d000dc --- /dev/null +++ b/src/common/utils/slug.util.ts @@ -0,0 +1,9 @@ +export function slugifyName(name: string): string { + return name + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .replace(/-+/g, '-'); +} diff --git a/src/types/services/composant-model.service.ts b/src/types/services/composant-model.service.ts new file mode 100644 index 0000000..61c4bac --- /dev/null +++ b/src/types/services/composant-model.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { ComposantModelsRepository } from '../../common/repositories/composant-models.repository'; +import { + CreateComposantModelDto, + UpdateComposantModelDto, +} from '../../shared/dto/type.dto'; + +const COMPOSANT_MODEL_INCLUDE = { + typeComposant: true, +} as const; + +@Injectable() +export class ComposantModelService { + constructor(private readonly repository: ComposantModelsRepository) {} + + async create(dto: CreateComposantModelDto) { + const { typeComposantId, ...data } = dto; + return this.repository.create( + { + ...data, + typeComposant: { connect: { id: typeComposantId } }, + }, + COMPOSANT_MODEL_INCLUDE, + ); + } + + async findAll(typeComposantId?: string) { + return this.repository.findAll(typeComposantId, COMPOSANT_MODEL_INCLUDE); + } + + async findOne(id: string) { + return this.repository.findOne(id, COMPOSANT_MODEL_INCLUDE); + } + + async update(id: string, dto: UpdateComposantModelDto) { + const { typeComposantId, ...data } = dto; + + return this.repository.update( + id, + { + ...data, + ...(typeComposantId + ? { typeComposant: { connect: { id: typeComposantId } } } + : {}), + }, + COMPOSANT_MODEL_INCLUDE, + ); + } + + async remove(id: string) { + return this.repository.delete(id); + } +} diff --git a/src/types/services/piece-model.service.ts b/src/types/services/piece-model.service.ts new file mode 100644 index 0000000..95b8009 --- /dev/null +++ b/src/types/services/piece-model.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { PieceModelsRepository } from '../../common/repositories/piece-models.repository'; +import { CreatePieceModelDto, UpdatePieceModelDto } from '../../shared/dto/type.dto'; + +const PIECE_MODEL_INCLUDE = { + typePiece: true, +} as const; + +@Injectable() +export class PieceModelService { + constructor(private readonly repository: PieceModelsRepository) {} + + async create(dto: CreatePieceModelDto) { + const { typePieceId, ...data } = dto; + + return this.repository.create( + { + ...data, + typePiece: { connect: { id: typePieceId } }, + }, + PIECE_MODEL_INCLUDE, + ); + } + + async findAll(typePieceId?: string) { + return this.repository.findAll(typePieceId, PIECE_MODEL_INCLUDE); + } + + async findOne(id: string) { + return this.repository.findOne(id, PIECE_MODEL_INCLUDE); + } + + async update(id: string, dto: UpdatePieceModelDto) { + const { typePieceId, ...data } = dto; + + return this.repository.update( + id, + { + ...data, + ...(typePieceId ? { typePiece: { connect: { id: typePieceId } } } : {}), + }, + PIECE_MODEL_INCLUDE, + ); + } + + async remove(id: string) { + return this.repository.delete(id); + } +} diff --git a/src/types/services/type-component.service.ts b/src/types/services/type-component.service.ts new file mode 100644 index 0000000..3a526f8 --- /dev/null +++ b/src/types/services/type-component.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { ModelTypesRepository } from '../../common/repositories/model-types.repository'; +import { + COMPONENT_TYPE_INCLUDE, + ModelTypeMapper, +} from '../../common/mappers/model-type.mapper'; +import { + CreateTypeComposantDto, + UpdateTypeComposantDto, +} from '../../shared/dto/type.dto'; + +@Injectable() +export class TypeComponentService { + constructor(private readonly repository: ModelTypesRepository) {} + + async create(dto: CreateTypeComposantDto) { + const code = await this.repository.generateUniqueCode(dto.name); + const data = ModelTypeMapper.toComponentCreateInput(dto, code); + + return this.repository.createComponentType(data, COMPONENT_TYPE_INCLUDE); + } + + async findAll() { + return this.repository.findComponentTypes(COMPONENT_TYPE_INCLUDE); + } + + async findOne(id: string) { + return this.repository.findComponentType(id, COMPONENT_TYPE_INCLUDE); + } + + async update(id: string, dto: UpdateTypeComposantDto) { + if (dto.customFields !== undefined) { + await this.repository.deleteComponentTypeCustomFields(id); + const fields = ModelTypeMapper.mapComponentCustomFieldInputs( + dto.customFields, + ); + await this.repository.createComponentTypeCustomFields(id, fields); + } + + const data = ModelTypeMapper.toComponentUpdateInput(dto); + return this.repository.updateComponentType(id, data, COMPONENT_TYPE_INCLUDE); + } + + async remove(id: string) { + return this.repository.deleteComponentType(id); + } +} diff --git a/src/types/services/type-machine.service.ts b/src/types/services/type-machine.service.ts new file mode 100644 index 0000000..355f525 --- /dev/null +++ b/src/types/services/type-machine.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import { TypeMachinesRepository } from '../../common/repositories/type-machines.repository'; +import { + TYPE_MACHINE_DEFAULT_INCLUDE, + TYPE_MACHINE_WITH_MACHINES_INCLUDE, + TypeMachineMapper, +} from '../../common/mappers/type-machine.mapper'; +import { + CreateTypeMachineDto, + UpdateTypeMachineDto, +} from '../../shared/dto/type.dto'; + +@Injectable() +export class TypeMachineService { + constructor(private readonly repository: TypeMachinesRepository) {} + + async create(dto: CreateTypeMachineDto) { + const data = TypeMachineMapper.toCreateInput(dto); + + return this.repository.create(data, TYPE_MACHINE_DEFAULT_INCLUDE); + } + + async findAll() { + return this.repository.findAll(TYPE_MACHINE_WITH_MACHINES_INCLUDE); + } + + async findOne(id: string) { + return this.repository.findOne(id, TYPE_MACHINE_WITH_MACHINES_INCLUDE); + } + + async update(id: string, dto: UpdateTypeMachineDto) { + const updateData = TypeMachineMapper.toUpdateData(dto); + + if (dto.customFields !== undefined) { + await this.repository.deleteCustomFields(id); + const fields = TypeMachineMapper.mapCustomFieldInputs(dto.customFields); + await this.repository.createCustomFields(id, fields); + } + + if (dto.componentRequirements !== undefined) { + await this.repository.deleteComponentRequirements(id); + const requirements = TypeMachineMapper.mapComponentRequirementInputs( + dto.componentRequirements, + ); + await this.repository.createComponentRequirements(id, requirements); + } + + if (dto.pieceRequirements !== undefined) { + await this.repository.deletePieceRequirements(id); + const requirements = TypeMachineMapper.mapPieceRequirementInputs( + dto.pieceRequirements, + ); + await this.repository.createPieceRequirements(id, requirements); + } + + return this.repository.update(id, updateData, TYPE_MACHINE_DEFAULT_INCLUDE); + } + + async remove(id: string) { + const machines = await this.repository.findMachinesUsingType(id); + + if (machines.length > 0) { + const names = machines.map((machine) => machine.name).join(', '); + throw new Error( + `Impossible de supprimer ce type de machine car il est utilisé par ${machines.length} machine(s): ${names}. Veuillez d'abord supprimer ou modifier ces machines.`, + ); + } + + await this.repository.deleteCustomFields(id); + return this.repository.delete(id); + } +} diff --git a/src/types/services/type-piece.service.ts b/src/types/services/type-piece.service.ts new file mode 100644 index 0000000..d5feb36 --- /dev/null +++ b/src/types/services/type-piece.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; +import { ModelTypesRepository } from '../../common/repositories/model-types.repository'; +import { + ModelTypeMapper, + PIECE_TYPE_INCLUDE, +} from '../../common/mappers/model-type.mapper'; +import { + CreateTypePieceDto, + UpdateTypePieceDto, +} from '../../shared/dto/type.dto'; + +@Injectable() +export class TypePieceService { + constructor(private readonly repository: ModelTypesRepository) {} + + async create(dto: CreateTypePieceDto) { + const code = await this.repository.generateUniqueCode(dto.name); + const data = ModelTypeMapper.toPieceCreateInput(dto, code); + + const created = await this.repository.createPieceType(data, PIECE_TYPE_INCLUDE); + return ModelTypeMapper.mapPieceModelType(created); + } + + async findAll() { + const items = await this.repository.findPieceTypes(PIECE_TYPE_INCLUDE); + return items.map((item) => ModelTypeMapper.mapPieceModelType(item)); + } + + async findOne(id: string) { + const item = await this.repository.findPieceType(id, PIECE_TYPE_INCLUDE); + return ModelTypeMapper.mapPieceModelType(item); + } + + async update(id: string, dto: UpdateTypePieceDto) { + if (dto.customFields !== undefined) { + await this.repository.deletePieceTypeCustomFields(id); + const fields = ModelTypeMapper.mapPieceCustomFieldInputs(dto.customFields); + await this.repository.createPieceTypeCustomFields(id, fields); + } + + const data = ModelTypeMapper.toPieceUpdateInput(dto); + const updated = await this.repository.updatePieceType( + id, + data, + PIECE_TYPE_INCLUDE, + ); + + return ModelTypeMapper.mapPieceModelType(updated); + } + + async remove(id: string) { + return this.repository.deletePieceType(id); + } +} diff --git a/src/types/types.controller.spec.ts b/src/types/types.controller.spec.ts index 1072fa3..072d91c 100644 --- a/src/types/types.controller.spec.ts +++ b/src/types/types.controller.spec.ts @@ -2,6 +2,15 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TypesController } from './types.controller'; import { TypesService } from './types.service'; import { PrismaService } from '../prisma/prisma.service'; +import { TypeMachineService } from './services/type-machine.service'; +import { TypeComponentService } from './services/type-component.service'; +import { TypePieceService } from './services/type-piece.service'; +import { ComposantModelService } from './services/composant-model.service'; +import { PieceModelService } from './services/piece-model.service'; +import { TypeMachinesRepository } from '../common/repositories/type-machines.repository'; +import { ModelTypesRepository } from '../common/repositories/model-types.repository'; +import { ComposantModelsRepository } from '../common/repositories/composant-models.repository'; +import { PieceModelsRepository } from '../common/repositories/piece-models.repository'; describe('TypesController', () => { let controller: TypesController; @@ -9,7 +18,19 @@ describe('TypesController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [TypesController], - providers: [TypesService, PrismaService], + providers: [ + TypesService, + TypeMachineService, + TypeComponentService, + TypePieceService, + ComposantModelService, + PieceModelService, + TypeMachinesRepository, + ModelTypesRepository, + ComposantModelsRepository, + PieceModelsRepository, + PrismaService, + ], }).compile(); controller = module.get(TypesController); diff --git a/src/types/types.module.ts b/src/types/types.module.ts index ba17211..6d58702 100644 --- a/src/types/types.module.ts +++ b/src/types/types.module.ts @@ -1,9 +1,29 @@ import { Module } from '@nestjs/common'; +import { ComposantModelsRepository } from '../common/repositories/composant-models.repository'; +import { ModelTypesRepository } from '../common/repositories/model-types.repository'; +import { PieceModelsRepository } from '../common/repositories/piece-models.repository'; +import { TypeMachinesRepository } from '../common/repositories/type-machines.repository'; import { TypesController } from './types.controller'; import { TypesService } from './types.service'; +import { ComposantModelService } from './services/composant-model.service'; +import { PieceModelService } from './services/piece-model.service'; +import { TypeComponentService } from './services/type-component.service'; +import { TypeMachineService } from './services/type-machine.service'; +import { TypePieceService } from './services/type-piece.service'; @Module({ controllers: [TypesController], - providers: [TypesService], + providers: [ + TypesService, + TypeMachineService, + TypeComponentService, + TypePieceService, + ComposantModelService, + PieceModelService, + TypeMachinesRepository, + ModelTypesRepository, + ComposantModelsRepository, + PieceModelsRepository, + ], }) export class TypesModule {} diff --git a/src/types/types.service.spec.ts b/src/types/types.service.spec.ts index ade99ca..6b97562 100644 --- a/src/types/types.service.spec.ts +++ b/src/types/types.service.spec.ts @@ -1,13 +1,34 @@ import { Test, TestingModule } from '@nestjs/testing'; import { TypesService } from './types.service'; import { PrismaService } from '../prisma/prisma.service'; +import { TypeMachineService } from './services/type-machine.service'; +import { TypeComponentService } from './services/type-component.service'; +import { TypePieceService } from './services/type-piece.service'; +import { ComposantModelService } from './services/composant-model.service'; +import { PieceModelService } from './services/piece-model.service'; +import { TypeMachinesRepository } from '../common/repositories/type-machines.repository'; +import { ModelTypesRepository } from '../common/repositories/model-types.repository'; +import { ComposantModelsRepository } from '../common/repositories/composant-models.repository'; +import { PieceModelsRepository } from '../common/repositories/piece-models.repository'; describe('TypesService', () => { let service: TypesService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [TypesService, PrismaService], + providers: [ + TypesService, + TypeMachineService, + TypeComponentService, + TypePieceService, + ComposantModelService, + PieceModelService, + TypeMachinesRepository, + ModelTypesRepository, + ComposantModelsRepository, + PieceModelsRepository, + PrismaService, + ], }).compile(); service = module.get(TypesService); diff --git a/src/types/types.service.ts b/src/types/types.service.ts index 95a6be1..c472df1 100644 --- a/src/types/types.service.ts +++ b/src/types/types.service.ts @@ -1,6 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { ModelCategory } from '@prisma/client'; -import { PrismaService } from '../prisma/prisma.service'; +import { ComposantModelService } from './services/composant-model.service'; +import { PieceModelService } from './services/piece-model.service'; +import { TypeComponentService } from './services/type-component.service'; +import { TypeMachineService } from './services/type-machine.service'; +import { TypePieceService } from './services/type-piece.service'; import { CreateTypeMachineDto, UpdateTypeMachineDto, @@ -14,640 +17,118 @@ import { UpdatePieceModelDto, } from '../shared/dto/type.dto'; -const CUSTOM_FIELD_SELECT = { - id: true, - name: true, - type: true, - required: true, - options: true, -} as const; - @Injectable() export class TypesService { - constructor(private prisma: PrismaService) {} + constructor( + private readonly typeMachineService: TypeMachineService, + private readonly typeComponentService: TypeComponentService, + private readonly typePieceService: TypePieceService, + private readonly composantModelService: ComposantModelService, + private readonly pieceModelService: PieceModelService, + ) {} - private slugifyName(name: string): string { - return name - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+|-+$/g, '') - .replace(/-+/g, '-'); + // TypeMachine + createTypeMachine(dto: CreateTypeMachineDto) { + return this.typeMachineService.create(dto); } - 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; + findAllTypeMachines() { + return this.typeMachineService.findAll(); } - // TypeMachine methods - async createTypeMachine(createTypeMachineDto: 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, - 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: { select: CUSTOM_FIELD_SELECT }, - componentRequirements: { - include: { - typeComposant: true, - }, - }, - pieceRequirements: { - include: { - typePiece: true, - }, - }, - }, - }); + findOneTypeMachine(id: string) { + return this.typeMachineService.findOne(id); } - async findAllTypeMachines() { - return this.prisma.typeMachine.findMany({ - include: { - machines: true, - customFields: { select: CUSTOM_FIELD_SELECT }, - componentRequirements: { - include: { - typeComposant: true, - }, - }, - pieceRequirements: { - include: { - typePiece: true, - }, - }, - }, - }); + updateTypeMachine(id: string, dto: UpdateTypeMachineDto) { + return this.typeMachineService.update(id, dto); } - async findOneTypeMachine(id: string) { - return this.prisma.typeMachine.findUnique({ - where: { id }, - include: { - machines: true, - customFields: { select: CUSTOM_FIELD_SELECT }, - componentRequirements: { - include: { - typeComposant: true, - }, - }, - pieceRequirements: { - include: { - typePiece: true, - }, - }, - }, - }); + removeTypeMachine(id: string) { + return this.typeMachineService.remove(id); } - 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 }, - }); - - // Créer les nouveaux champs personnalisés - if (customFields.length > 0) { - await this.prisma.customField.createMany({ - data: customFields.map((field) => ({ - name: field.name, - type: field.type, - required: field.required || false, - options: field.options, - typeMachineId: id, - })), - }); - } - } - - if (componentRequirements !== undefined) { - await this.prisma.typeMachineComponentRequirement.deleteMany({ - where: { typeMachineId: id }, - }); - - if (componentRequirements.length > 0) { - await this.prisma.typeMachineComponentRequirement.createMany({ - data: componentRequirements.map((requirement) => ({ - label: requirement.label ?? null, - minCount: requirement.minCount ?? 1, - maxCount: requirement.maxCount ?? null, - required: requirement.required ?? true, - allowNewModels: requirement.allowNewModels ?? true, - typeMachineId: id, - typeComposantId: requirement.typeComposantId, - })), - skipDuplicates: false, - }); - } - } - - if (pieceRequirements !== undefined) { - await this.prisma.typeMachinePieceRequirement.deleteMany({ - where: { typeMachineId: id }, - }); - - if (pieceRequirements.length > 0) { - await this.prisma.typeMachinePieceRequirement.createMany({ - data: pieceRequirements.map((requirement) => ({ - label: requirement.label ?? null, - minCount: requirement.minCount ?? 0, - maxCount: requirement.maxCount ?? null, - required: requirement.required ?? false, - allowNewModels: requirement.allowNewModels ?? true, - typeMachineId: id, - typePieceId: requirement.typePieceId, - })), - skipDuplicates: false, - }); - } - } - - return this.prisma.typeMachine.update({ - where: { id }, - data: typeData, - include: { - customFields: { select: CUSTOM_FIELD_SELECT }, - componentRequirements: { - include: { - typeComposant: true, - }, - }, - pieceRequirements: { - include: { - typePiece: true, - }, - }, - }, - }); + // TypeComposant + createTypeComposant(dto: CreateTypeComposantDto) { + return this.typeComponentService.create(dto); } - async removeTypeMachine(id: string) { - // Vérifier s'il y a des machines liées à ce type - const machinesWithType = await this.prisma.machine.findMany({ - where: { typeMachineId: id }, - select: { id: true, name: true }, - }); - - if (machinesWithType.length > 0) { - 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.`, - ); - } - - // Supprimer les champs personnalisés associés - await this.prisma.customField.deleteMany({ - where: { typeMachineId: id }, - }); - - return this.prisma.typeMachine.delete({ - where: { id }, - }); + findAllTypeComposants() { + return this.typeComponentService.findAll(); } - // TypeComposant methods (backed by ModelType with COMPONENT category) - async createTypeComposant(createTypeComposantDto: CreateTypeComposantDto) { - const { customFields, description, name } = createTypeComposantDto; - - const code = await this.generateUniqueModelTypeCode(name); - - return this.prisma.modelType.create({ - data: { - 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, - options: field.options, - })), - } - : undefined, - }, - include: { - customFields: { select: CUSTOM_FIELD_SELECT }, - }, - }); + findOneTypeComposant(id: string) { + return this.typeComponentService.findOne(id); } - async findAllTypeComposants() { - return this.prisma.modelType.findMany({ - where: { category: ModelCategory.COMPONENT }, - include: { - composants: true, - customFields: { select: CUSTOM_FIELD_SELECT }, - models: true, - }, - }); + updateTypeComposant(id: string, dto: UpdateTypeComposantDto) { + return this.typeComponentService.update(id, dto); } - async findOneTypeComposant(id: string) { - return this.prisma.modelType.findFirst({ - where: { - id, - category: ModelCategory.COMPONENT, - }, - include: { - composants: true, - customFields: { select: CUSTOM_FIELD_SELECT }, - models: true, - }, - }); + removeTypeComposant(id: string) { + return this.typeComponentService.remove(id); } - async updateTypeComposant( - id: string, - updateTypeComposantDto: UpdateTypeComposantDto, - ) { - const { customFields, description, name } = updateTypeComposantDto; - - if (customFields !== undefined) { - await this.prisma.customField.deleteMany({ - where: { typeComposantId: id }, - }); - - if (customFields.length > 0) { - await this.prisma.customField.createMany({ - data: customFields.map((field) => ({ - name: field.name, - type: field.type, - required: field.required || false, - options: field.options, - typeComposantId: id, - })), - }); - } - } - - 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, - include: { - customFields: { select: CUSTOM_FIELD_SELECT }, - }, - }); + // TypePiece + createTypePiece(dto: CreateTypePieceDto) { + return this.typePieceService.create(dto); } - async removeTypeComposant(id: string) { - return this.prisma.modelType.delete({ - where: { id }, - }); + findAllTypePieces() { + return this.typePieceService.findAll(); } - // 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 ?? [], - }; + findOneTypePiece(id: string) { + return this.typePieceService.findOne(id); } - async createTypePiece(createTypePieceDto: CreateTypePieceDto) { - const { customFields, description, name } = createTypePieceDto; - - const code = await this.generateUniqueModelTypeCode(name); - - const created = await this.prisma.modelType.create({ - data: { - 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, - options: field.options, - })), - } - : undefined, - }, - include: { - pieceCustomFields: { select: CUSTOM_FIELD_SELECT }, - pieceModels: true, - pieceRequirements: true, - pieces: true, - }, - }); - - return this.mapModelTypePiece(created); + updateTypePiece(id: string, dto: UpdateTypePieceDto) { + return this.typePieceService.update(id, dto); } - async findAllTypePieces() { - const items = await this.prisma.modelType.findMany({ - where: { category: ModelCategory.PIECE }, - include: { - pieceCustomFields: { select: CUSTOM_FIELD_SELECT }, - pieceModels: true, - pieceRequirements: true, - pieces: true, - }, - orderBy: { name: 'asc' }, - }); - - return items.map((item) => this.mapModelTypePiece(item)); + removeTypePiece(id: string) { + return this.typePieceService.remove(id); } - async findOneTypePiece(id: string) { - const item = await this.prisma.modelType.findFirst({ - where: { id, category: ModelCategory.PIECE }, - include: { - pieceCustomFields: { select: CUSTOM_FIELD_SELECT }, - pieceModels: true, - pieceRequirements: true, - pieces: true, - }, - }); - - return this.mapModelTypePiece(item); + // ComposantModel + createComposantModel(dto: CreateComposantModelDto) { + return this.composantModelService.create(dto); } - async updateTypePiece(id: string, updateTypePieceDto: UpdateTypePieceDto) { - const { customFields, description, name } = updateTypePieceDto; - - if (customFields !== undefined) { - await this.prisma.customField.deleteMany({ - where: { typePieceId: id }, - }); - - if (customFields.length > 0) { - await this.prisma.customField.createMany({ - data: customFields.map((field) => ({ - name: field.name, - type: field.type, - required: field.required || false, - options: field.options, - typePieceId: id, - })), - }); - } - } - - 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: updatePayload, - include: { - pieceCustomFields: { select: CUSTOM_FIELD_SELECT }, - pieceModels: true, - pieceRequirements: true, - pieces: true, - }, - }); - - return this.mapModelTypePiece(updated); + findAllComposantModels(typeComposantId?: string) { + return this.composantModelService.findAll(typeComposantId); } - async removeTypePiece(id: string) { - return this.prisma.modelType.delete({ - where: { id }, - }); + findOneComposantModel(id: string) { + return this.composantModelService.findOne(id); } - // ComposantModel methods - async createComposantModel(createComposantModelDto: CreateComposantModelDto) { - const { typeComposantId, ...data } = createComposantModelDto; - - return this.prisma.composantModel.create({ - data: { - ...data, - typeComposant: { - connect: { id: typeComposantId }, - }, - }, - include: { - typeComposant: true, - }, - }); + updateComposantModel(id: string, dto: UpdateComposantModelDto) { + return this.composantModelService.update(id, dto); } - async findAllComposantModels(typeComposantId?: string) { - return this.prisma.composantModel.findMany({ - where: typeComposantId ? { typeComposantId } : undefined, - include: { - typeComposant: true, - }, - orderBy: { - name: 'asc', - }, - }); + removeComposantModel(id: string) { + return this.composantModelService.remove(id); } - async findOneComposantModel(id: string) { - return this.prisma.composantModel.findUnique({ - where: { id }, - include: { - typeComposant: true, - }, - }); + // PieceModel + createPieceModel(dto: CreatePieceModelDto) { + return this.pieceModelService.create(dto); } - async updateComposantModel( - id: string, - updateComposantModelDto: UpdateComposantModelDto, - ) { - const { typeComposantId, ...data } = updateComposantModelDto; - - return this.prisma.composantModel.update({ - where: { id }, - data: { - ...data, - ...(typeComposantId - ? { - typeComposant: { - connect: { id: typeComposantId }, - }, - } - : {}), - }, - include: { - typeComposant: true, - }, - }); + findAllPieceModels(typePieceId?: string) { + return this.pieceModelService.findAll(typePieceId); } - async removeComposantModel(id: string) { - return this.prisma.composantModel.delete({ - where: { id }, - }); + findOnePieceModel(id: string) { + return this.pieceModelService.findOne(id); } - // PieceModel methods - async createPieceModel(createPieceModelDto: CreatePieceModelDto) { - const { typePieceId, ...data } = createPieceModelDto; - - return this.prisma.pieceModel.create({ - data: { - ...data, - typePiece: { - connect: { id: typePieceId }, - }, - }, - include: { - typePiece: true, - }, - }); + updatePieceModel(id: string, dto: UpdatePieceModelDto) { + return this.pieceModelService.update(id, dto); } - async findAllPieceModels(typePieceId?: string) { - return this.prisma.pieceModel.findMany({ - where: typePieceId ? { typePieceId } : undefined, - include: { - typePiece: true, - }, - orderBy: { - name: 'asc', - }, - }); - } - - async findOnePieceModel(id: string) { - return this.prisma.pieceModel.findUnique({ - where: { id }, - include: { - typePiece: true, - }, - }); - } - - async updatePieceModel(id: string, updatePieceModelDto: UpdatePieceModelDto) { - const { typePieceId, ...data } = updatePieceModelDto; - - return this.prisma.pieceModel.update({ - where: { id }, - data: { - ...data, - ...(typePieceId - ? { - typePiece: { - connect: { id: typePieceId }, - }, - } - : {}), - }, - include: { - typePiece: true, - }, - }); - } - - async removePieceModel(id: string) { - return this.prisma.pieceModel.delete({ - where: { id }, - }); + removePieceModel(id: string) { + return this.pieceModelService.remove(id); } }