Merge branch 'master' into codex/exiger-machineid-ou-composantid-dans-dtos

This commit is contained in:
MatthieuTD
2025-09-22 10:22:22 +02:00
committed by GitHub
4 changed files with 312 additions and 325 deletions

View File

@@ -1,6 +1,10 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common'; import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { MachinesService } from './machines.service'; import { MachinesService } from './machines.service';
import { CreateMachineDto, UpdateMachineDto } from '../shared/dto/machine.dto'; import {
CreateMachineDto,
UpdateMachineDto,
ReconfigureMachineDto,
} from '../shared/dto/machine.dto';
@Controller('machines') @Controller('machines')
export class MachinesController { export class MachinesController {
@@ -26,6 +30,14 @@ export class MachinesController {
return this.machinesService.update(id, updateMachineDto); return this.machinesService.update(id, updateMachineDto);
} }
@Patch(':id/skeleton')
reconfigure(
@Param('id') id: string,
@Body() reconfigureMachineDto: ReconfigureMachineDto,
) {
return this.machinesService.reconfigure(id, reconfigureMachineDto);
}
@Delete(':id') @Delete(':id')
remove(@Param('id') id: string) { remove(@Param('id') id: string) {
return this.machinesService.remove(id); return this.machinesService.remove(id);

View File

@@ -1,53 +1,125 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { import {
CreateMachineDto, CreateMachineDto,
UpdateMachineDto, UpdateMachineDto,
ReconfigureMachineDto,
MachineComponentSelectionDto, MachineComponentSelectionDto,
MachinePieceSelectionDto MachinePieceSelectionDto,
} from '../shared/dto/machine.dto'; } from '../shared/dto/machine.dto';
const TYPE_MACHINE_CONFIGURATION_INCLUDE = {
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
};
const MACHINE_DEFAULT_INCLUDE = {
site: true,
typeMachine: {
include: TYPE_MACHINE_CONFIGURATION_INCLUDE,
},
constructeur: true,
composants: {
include: {
typeComposant: true,
composantModel: true,
typeMachineComponentRequirement: {
include: {
typeComposant: true,
},
},
sousComposants: true,
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
},
},
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
documents: true,
};
@Injectable() @Injectable()
export class MachinesService { export class MachinesService {
constructor(private prisma: PrismaService) {} constructor(private prisma: PrismaService) {}
async create(createMachineDto: CreateMachineDto) { private async getTypeMachineConfiguration(typeMachineId: string) {
const {
componentSelections = [],
pieceSelections = [],
...machineData
} = createMachineDto;
if (!machineData.typeMachineId) {
throw new Error('typeMachineId est requis pour créer une machine à partir d\'un squelette.');
}
const typeMachine = await this.prisma.typeMachine.findUnique({ const typeMachine = await this.prisma.typeMachine.findUnique({
where: { id: machineData.typeMachineId }, where: { id: typeMachineId },
include: { include: TYPE_MACHINE_CONFIGURATION_INCLUDE,
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
},
}); });
if (!typeMachine) { if (!typeMachine) {
throw new Error('Type de machine non trouvé'); throw new Error('Type de machine non trouvé');
} }
return typeMachine;
}
private async buildConfigurationContext(
typeMachine: any,
componentSelections: MachineComponentSelectionDto[],
pieceSelections: MachinePieceSelectionDto[],
) {
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
? typeMachine.componentRequirements
: []) as any[];
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
? typeMachine.pieceRequirements
: []) as any[];
const componentRequirementMap = new Map( const componentRequirementMap = new Map(
typeMachine.componentRequirements.map((requirement) => [requirement.id, requirement]), componentRequirements.map((requirement: any) => [requirement.id, requirement]),
); );
const pieceRequirementMap = new Map( const pieceRequirementMap = new Map(
typeMachine.pieceRequirements.map((requirement) => [requirement.id, requirement]), pieceRequirements.map((requirement: any) => [requirement.id, requirement]),
); );
const componentSelectionMap = new Map<string, MachineComponentSelectionDto[]>(); const componentSelectionMap = new Map<string, MachineComponentSelectionDto[]>();
@@ -94,7 +166,7 @@ export class MachinesService {
: []; : [];
const pieceModelMap = new Map(pieceModels.map((model) => [model.id, model])); const pieceModelMap = new Map(pieceModels.map((model) => [model.id, model]));
for (const requirement of typeMachine.componentRequirements) { for (const requirement of componentRequirements) {
const selections = componentSelectionMap.get(requirement.id) ?? []; const selections = componentSelectionMap.get(requirement.id) ?? [];
const min = requirement.minCount ?? (requirement.required ? 1 : 0); const min = requirement.minCount ?? (requirement.required ? 1 : 0);
const max = requirement.maxCount ?? undefined; const max = requirement.maxCount ?? undefined;
@@ -121,7 +193,7 @@ export class MachinesService {
} }
} }
for (const requirement of typeMachine.pieceRequirements) { for (const requirement of pieceRequirements) {
const selections = pieceSelectionMap.get(requirement.id) ?? []; const selections = pieceSelectionMap.get(requirement.id) ?? [];
const min = requirement.minCount ?? (requirement.required ? 1 : 0); const min = requirement.minCount ?? (requirement.required ? 1 : 0);
const max = requirement.maxCount ?? undefined; const max = requirement.maxCount ?? undefined;
@@ -186,7 +258,42 @@ export class MachinesService {
} }
} }
return await this.prisma.$transaction(async (prisma) => { return {
componentSelectionMap,
pieceSelectionMap,
componentModelMap,
pieceModelMap,
};
}
async create(createMachineDto: CreateMachineDto) {
const {
componentSelections = [],
pieceSelections = [],
...machineData
} = createMachineDto;
if (!machineData.typeMachineId) {
throw new Error('typeMachineId est requis pour créer une machine à partir d\'un squelette.');
}
const typeMachine = await this.getTypeMachineConfiguration(machineData.typeMachineId);
const {
componentSelectionMap,
pieceSelectionMap,
componentModelMap,
pieceModelMap,
} = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections);
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
? typeMachine.componentRequirements
: []) as any[];
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
? typeMachine.pieceRequirements
: []) as any[];
return this.prisma.$transaction(async (prisma) => {
const machine = await prisma.machine.create({ const machine = await prisma.machine.create({
data: machineData, data: machineData,
include: { include: {
@@ -196,8 +303,8 @@ export class MachinesService {
}, },
}); });
if (typeMachine.componentRequirements.length > 0) { if (componentRequirements.length > 0) {
for (const requirement of typeMachine.componentRequirements) { for (const requirement of componentRequirements) {
const selections = componentSelectionMap.get(requirement.id) ?? []; const selections = componentSelectionMap.get(requirement.id) ?? [];
for (const selection of selections) { for (const selection of selections) {
const model = selection.componentModelId ? componentModelMap.get(selection.componentModelId) : undefined; const model = selection.componentModelId ? componentModelMap.get(selection.componentModelId) : undefined;
@@ -212,8 +319,8 @@ export class MachinesService {
} }
} }
if (typeMachine.pieceRequirements.length > 0) { if (pieceRequirements.length > 0) {
for (const requirement of typeMachine.pieceRequirements) { for (const requirement of pieceRequirements) {
const selections = pieceSelectionMap.get(requirement.id) ?? []; const selections = pieceSelectionMap.get(requirement.id) ?? [];
for (const selection of selections) { for (const selection of selections) {
const model = selection.pieceModelId ? pieceModelMap.get(selection.pieceModelId) : undefined; const model = selection.pieceModelId ? pieceModelMap.get(selection.pieceModelId) : undefined;
@@ -234,76 +341,7 @@ export class MachinesService {
return prisma.machine.findUnique({ return prisma.machine.findUnique({
where: { id: machine.id }, where: { id: machine.id },
include: { include: MACHINE_DEFAULT_INCLUDE,
site: true,
typeMachine: {
include: {
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
composantModel: true,
typeMachineComponentRequirement: {
include: {
typeComposant: true,
},
},
sousComposants: true,
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
constructeur: true,
},
},
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
documents: true,
},
}); });
}); });
} }
@@ -660,239 +698,146 @@ export class MachinesService {
async findAll() { async findAll() {
return this.prisma.machine.findMany({ return this.prisma.machine.findMany({
include: { include: MACHINE_DEFAULT_INCLUDE,
site: true,
typeMachine: {
include: {
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
composantModel: true,
typeMachineComponentRequirement: {
include: {
typeComposant: true,
},
},
sousComposants: true,
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
},
},
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
documents: true,
},
}); });
} }
async findOne(id: string) { async findOne(id: string) {
return this.prisma.machine.findUnique({ return this.prisma.machine.findUnique({
where: { id },
include: MACHINE_DEFAULT_INCLUDE,
});
}
async reconfigure(id: string, reconfigureMachineDto: ReconfigureMachineDto) {
const {
componentSelections = [],
pieceSelections = [],
} = reconfigureMachineDto;
const machine = await this.prisma.machine.findUnique({
where: { id }, where: { id },
include: { include: {
site: true,
typeMachine: { typeMachine: {
include: { include: TYPE_MACHINE_CONFIGURATION_INCLUDE,
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
},
}, },
constructeur: true,
composants: {
include: {
typeComposant: true,
composantModel: true,
typeMachineComponentRequirement: {
include: {
typeComposant: true,
},
},
sousComposants: true,
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
},
},
pieces: {
include: {
customFieldValues: {
include: {
customField: true,
},
},
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
documents: true,
}, },
}); });
if (!machine) {
throw new Error('Machine non trouvée');
}
if (!machine.typeMachineId || !machine.typeMachine) {
throw new Error('Impossible de reconfigurer une machine sans type de machine associé.');
}
const typeMachine = machine.typeMachine;
const {
componentSelectionMap,
pieceSelectionMap,
componentModelMap,
pieceModelMap,
} = await this.buildConfigurationContext(typeMachine, componentSelections, pieceSelections);
const componentRequirements = (Array.isArray(typeMachine.componentRequirements)
? typeMachine.componentRequirements
: []) as any[];
const pieceRequirements = (Array.isArray(typeMachine.pieceRequirements)
? typeMachine.pieceRequirements
: []) as any[];
return this.prisma.$transaction(async (prisma) => {
await prisma.customFieldValue.deleteMany({
where: {
OR: [
{
composant: {
machineId: id,
typeMachineComponentRequirementId: { not: null },
},
},
{
piece: {
machineId: id,
typeMachinePieceRequirementId: { not: null },
},
},
{
piece: {
composant: {
machineId: id,
typeMachineComponentRequirementId: { not: null },
},
},
},
],
},
});
await prisma.piece.deleteMany({
where: {
machineId: id,
typeMachinePieceRequirementId: { not: null },
},
});
await prisma.composant.deleteMany({
where: {
machineId: id,
typeMachineComponentRequirementId: { not: null },
},
});
if (componentRequirements.length > 0) {
for (const requirement of componentRequirements) {
const selections = componentSelectionMap.get(requirement.id) ?? [];
for (const selection of selections) {
const model = selection.componentModelId
? componentModelMap.get(selection.componentModelId)
: undefined;
const definition = this.normalizeComponentSelection(selection, requirement, model);
await this.createComponentsFromType(prisma, id, [definition]);
}
}
} else {
const legacyComponents = (typeMachine as any).components;
if (legacyComponents) {
await this.createComponentsFromType(prisma, id, legacyComponents);
}
}
if (pieceRequirements.length > 0) {
for (const requirement of pieceRequirements) {
const selections = pieceSelectionMap.get(requirement.id) ?? [];
for (const selection of selections) {
const model = selection.pieceModelId
? pieceModelMap.get(selection.pieceModelId)
: undefined;
const definition = this.normalizePieceSelection(selection, requirement, model);
await this.createMachinePiecesFromType(prisma, id, [definition]);
}
}
} else {
const legacyPieces = (typeMachine as any).machinePieces;
if (legacyPieces) {
await this.createMachinePiecesFromType(prisma, id, legacyPieces);
}
}
return prisma.machine.findUnique({
where: { id },
include: MACHINE_DEFAULT_INCLUDE,
});
});
} }
async update(id: string, updateMachineDto: UpdateMachineDto) { async update(id: string, updateMachineDto: UpdateMachineDto) {
return this.prisma.machine.update({ return this.prisma.machine.update({
where: { id }, where: { id },
data: updateMachineDto, data: updateMachineDto,
include: { include: MACHINE_DEFAULT_INCLUDE,
site: true,
typeMachine: {
include: {
customFields: true,
componentRequirements: {
include: {
typeComposant: true,
},
},
pieceRequirements: {
include: {
typePiece: true,
},
},
},
},
constructeur: true,
composants: {
include: {
typeComposant: true,
composantModel: true,
typeMachineComponentRequirement: {
include: {
typeComposant: true,
},
},
sousComposants: true,
constructeur: true,
pieces: {
include: {
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
customFieldValues: {
include: {
customField: true,
},
},
},
},
},
},
pieces: {
include: {
constructeur: true,
pieceModel: true,
typeMachinePieceRequirement: {
include: {
typePiece: true,
},
},
customFieldValues: {
include: {
customField: true,
},
},
},
},
customFieldValues: {
include: {
customField: true,
},
},
documents: true,
},
}); });
} }

View File

@@ -90,4 +90,18 @@ export class UpdateMachineDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
typeMachineId?: string; typeMachineId?: string;
} }
export class ReconfigureMachineDto {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => MachineComponentSelectionDto)
componentSelections?: MachineComponentSelectionDto[];
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => MachinePieceSelectionDto)
pieceSelections?: MachinePieceSelectionDto[];
}

View File

@@ -7,6 +7,8 @@ import { PrismaService } from '../src/prisma/prisma.service';
describe('AppController (e2e)', () => { describe('AppController (e2e)', () => {
let app: INestApplication<App>; let app: INestApplication<App>;
let prisma: PrismaService;
function createMockPrismaService() { function createMockPrismaService() {
return { return {
@@ -34,6 +36,7 @@ describe('AppController (e2e)', () => {
beforeEach(async () => { beforeEach(async () => {
prisma = createMockPrismaService(); prisma = createMockPrismaService();
const moduleFixture: TestingModule = await Test.createTestingModule({ const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule], imports: [AppModule],
}) })
@@ -43,6 +46,18 @@ describe('AppController (e2e)', () => {
app = moduleFixture.createNestApplication(); app = moduleFixture.createNestApplication();
await app.init(); await app.init();
prisma = app.get(PrismaService);
});
afterAll(async () => {
await app.close();
});
beforeEach(async () => {
await prisma.$executeRawUnsafe(
'TRUNCATE TABLE custom_field_values, documents, pieces, composants, machines, composant_models, piece_models, type_machine_component_requirements, type_machine_piece_requirements, custom_fields, type_machines, type_composants, type_pieces, constructeurs, sites RESTART IDENTITY CASCADE',
);
}); });
afterEach(async () => { afterEach(async () => {
@@ -157,6 +172,7 @@ describe('AppController (e2e)', () => {
.expect(400); .expect(400);
expect(prisma.piece.create).not.toHaveBeenCalled(); expect(prisma.piece.create).not.toHaveBeenCalled();
}); });
}); });
}); });