feat: auto populate machine structures and seed sample data

This commit is contained in:
Matthieu
2025-10-13 09:01:33 +02:00
parent b7682ac312
commit dc4a12440b
21 changed files with 2218 additions and 7267 deletions

View File

@@ -1,4 +1,4 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { CreatePieceDto, UpdatePieceDto } from '../shared/dto/piece.dto';
@@ -6,124 +6,56 @@ import { PieceModelStructureSchema } from '../shared/schemas/inventory';
import type { PieceModelStructure } from '../shared/types/inventory';
const PIECE_WITH_RELATIONS_INCLUDE = {
machine: true,
composant: true,
typePiece: {
include: {
pieceCustomFields: true,
},
},
documents: true,
constructeur: true,
typeMachinePieceRequirement: {
include: {
typePiece: {
include: {
pieceCustomFields: true,
},
},
},
},
documents: true,
customFieldValues: {
include: {
customField: true,
},
},
machineLinks: {
include: {
machine: true,
typeMachinePieceRequirement: true,
parentLink: true,
},
},
} as const;
@Injectable()
export class PiecesService {
constructor(private prisma: PrismaService) {}
async create(createPieceDto: CreatePieceDto) {
const requirementId = createPieceDto.typeMachinePieceRequirementId ?? null;
if (requirementId && !createPieceDto.machineId) {
throw new BadRequestException(
'Un requirement ne peut pas être utilisé sans machine ciblée.',
);
}
let machineId = createPieceDto.machineId ?? null;
if (createPieceDto.composantId) {
const composantMachineId = await this.resolveMachineIdFromComposant(
createPieceDto.composantId,
);
if (machineId && machineId !== composantMachineId) {
throw new BadRequestException(
'Le composant ciblé appartient à une autre machine que celle fournie.',
);
}
machineId = composantMachineId ?? machineId;
}
let requirement: PieceRequirementWithType | null = null;
if (machineId) {
const machine = await this.prisma.machine.findUnique({
where: { id: machineId },
include: {
typeMachine: {
include: {
pieceRequirements: {
include: {
typePiece: true,
},
},
},
},
},
});
if (!machine || !machine.typeMachine) {
throw new BadRequestException(
'La machine ciblée doit être associée à un type de machine pour valider les requirements.',
);
}
if (requirementId) {
requirement =
(
machine.typeMachine.pieceRequirements as PieceRequirementWithType[]
).find((pieceRequirement) => pieceRequirement.id === requirementId) ??
null;
if (!requirement) {
throw new BadRequestException(
'Le requirement de pièce fourni ne correspond pas au squelette de la machine.',
);
}
if (
createPieceDto.typePieceId &&
createPieceDto.typePieceId !== requirement.typePieceId
) {
throw new BadRequestException(
'Le type de pièce fourni ne correspond pas au requirement pour cette machine.',
);
}
}
}
const typePieceId =
createPieceDto.typePieceId ?? requirement?.typePieceId ?? null;
const data: Prisma.PieceUncheckedCreateInput = {
private buildCreateInput(createPieceDto: CreatePieceDto): Prisma.PieceCreateInput {
const data: Prisma.PieceCreateInput = {
name: createPieceDto.name,
reference: createPieceDto.reference ?? null,
constructeurId: createPieceDto.constructeurId ?? null,
prix: createPieceDto.prix !== undefined ? createPieceDto.prix : null,
machineId,
composantId: createPieceDto.composantId ?? null,
typePieceId,
typeMachinePieceRequirementId: requirement?.id ?? requirementId ?? null,
};
if (createPieceDto.constructeurId) {
data.constructeur = {
connect: { id: createPieceDto.constructeurId },
};
}
if (createPieceDto.typePieceId) {
data.typePiece = {
connect: { id: createPieceDto.typePieceId },
};
}
return data;
}
async create(createPieceDto: CreatePieceDto) {
const created = await this.prisma.piece.create({
data,
data: this.buildCreateInput(createPieceDto),
include: PIECE_WITH_RELATIONS_INCLUDE,
});
@@ -141,6 +73,7 @@ export class PiecesService {
async findAll() {
return this.prisma.piece.findMany({
include: PIECE_WITH_RELATIONS_INCLUDE,
orderBy: { name: 'asc' },
});
}
@@ -151,53 +84,36 @@ export class PiecesService {
});
}
async findByMachine(machineId: string) {
return this.prisma.piece.findMany({
where: { machineId },
include: PIECE_WITH_RELATIONS_INCLUDE,
});
}
private async resolveMachineIdFromComposant(
composantId: string,
): Promise<string> {
const composant = await this.prisma.composant.findUnique({
where: { id: composantId },
select: {
id: true,
machineId: true,
parentComposantId: true,
},
});
if (!composant) {
throw new BadRequestException('Le composant spécifié est introuvable.');
}
if (composant.machineId) {
return composant.machineId;
}
if (composant.parentComposantId) {
return this.resolveMachineIdFromComposant(composant.parentComposantId);
}
throw new BadRequestException(
'Impossible de déterminer la machine associée à ce composant.',
);
}
async findByComposant(composantId: string) {
return this.prisma.piece.findMany({
where: { composantId },
include: PIECE_WITH_RELATIONS_INCLUDE,
});
}
async update(id: string, updatePieceDto: UpdatePieceDto) {
const data: Prisma.PieceUpdateInput = {};
if (updatePieceDto.name !== undefined) {
data.name = updatePieceDto.name;
}
if (updatePieceDto.reference !== undefined) {
data.reference = updatePieceDto.reference;
}
if (updatePieceDto.prix !== undefined) {
data.prix = updatePieceDto.prix;
}
if (updatePieceDto.constructeurId !== undefined) {
data.constructeur = updatePieceDto.constructeurId
? { connect: { id: updatePieceDto.constructeurId } }
: { disconnect: true };
}
if (updatePieceDto.typePieceId !== undefined) {
data.typePiece = updatePieceDto.typePieceId
? { connect: { id: updatePieceDto.typePieceId } }
: { disconnect: true };
}
const updated = await this.prisma.piece.update({
where: { id },
data: updatePieceDto,
data,
include: PIECE_WITH_RELATIONS_INCLUDE,
});
@@ -241,7 +157,6 @@ export class PiecesService {
const customFields = skeleton.customFields ?? [];
await this.ensurePieceCustomFieldDefinitions(typePiece.id, customFields);
await this.createPieceCustomFieldValues(
pieceId,
typePiece.id,
@@ -291,11 +206,7 @@ export class PiecesService {
}
const name = this.normalizeIdentifier(field.name);
if (!name) {
continue;
}
if (existingByName.has(name)) {
if (!name || existingByName.has(name)) {
continue;
}
@@ -426,16 +337,16 @@ export class PiecesService {
return '';
}
return String(value);
if (typeof value === 'string') {
return value;
}
return JSON.stringify(value);
}
}
type PieceRequirementWithType = Prisma.TypeMachinePieceRequirementGetPayload<{
include: { typePiece: true };
type PieceTypeWithSkeleton = Prisma.ModelTypeGetPayload<{
include: { pieceCustomFields: true };
}>;
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
type PieceCustomFieldEntry = NonNullable<
PieceModelStructure['customFields']
>[number];
type PieceCustomFieldEntry = NonNullable<PieceModelStructure['customFields']>[number];