1209 lines
40 KiB
TypeScript
1209 lines
40 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import {
|
|
CreateMachineDto,
|
|
UpdateMachineDto,
|
|
ReconfigureMachineDto,
|
|
MachineComponentSelectionDto,
|
|
MachinePieceSelectionDto,
|
|
} 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()
|
|
export class MachinesService {
|
|
constructor(private prisma: PrismaService) {}
|
|
|
|
private async getTypeMachineConfiguration(typeMachineId: string) {
|
|
const typeMachine = await this.prisma.typeMachine.findUnique({
|
|
where: { id: typeMachineId },
|
|
include: TYPE_MACHINE_CONFIGURATION_INCLUDE,
|
|
});
|
|
|
|
if (!typeMachine) {
|
|
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(
|
|
componentRequirements.map((requirement: any) => [requirement.id, requirement]),
|
|
);
|
|
const pieceRequirementMap = new Map(
|
|
pieceRequirements.map((requirement: any) => [requirement.id, requirement]),
|
|
);
|
|
|
|
const componentSelectionMap = new Map<string, MachineComponentSelectionDto[]>();
|
|
for (const selection of componentSelections) {
|
|
const requirement = componentRequirementMap.get(selection.requirementId);
|
|
if (!requirement) {
|
|
throw new Error(`Sélection de composant invalide: requirementId=${selection.requirementId}`);
|
|
}
|
|
if (!componentSelectionMap.has(requirement.id)) {
|
|
componentSelectionMap.set(requirement.id, []);
|
|
}
|
|
componentSelectionMap.get(requirement.id)!.push(selection);
|
|
}
|
|
|
|
const pieceSelectionMap = new Map<string, MachinePieceSelectionDto[]>();
|
|
for (const selection of pieceSelections) {
|
|
const requirement = pieceRequirementMap.get(selection.requirementId);
|
|
if (!requirement) {
|
|
throw new Error(`Sélection de pièce invalide: requirementId=${selection.requirementId}`);
|
|
}
|
|
if (!pieceSelectionMap.has(requirement.id)) {
|
|
pieceSelectionMap.set(requirement.id, []);
|
|
}
|
|
pieceSelectionMap.get(requirement.id)!.push(selection);
|
|
}
|
|
|
|
const componentModelIds = Array.from(
|
|
new Set(componentSelections.map((selection) => selection.componentModelId).filter(Boolean)),
|
|
) as string[];
|
|
const componentModels = componentModelIds.length
|
|
? await this.prisma.composantModel.findMany({
|
|
where: { id: { in: componentModelIds } },
|
|
})
|
|
: [];
|
|
const componentModelMap = new Map(componentModels.map((model) => [model.id, model]));
|
|
|
|
const pieceModelIds = Array.from(
|
|
new Set(pieceSelections.map((selection) => selection.pieceModelId).filter(Boolean)),
|
|
) as string[];
|
|
const pieceModels = pieceModelIds.length
|
|
? await this.prisma.pieceModel.findMany({
|
|
where: { id: { in: pieceModelIds } },
|
|
})
|
|
: [];
|
|
const pieceModelMap = new Map(pieceModels.map((model) => [model.id, model]));
|
|
|
|
for (const requirement of componentRequirements) {
|
|
const selections = componentSelectionMap.get(requirement.id) ?? [];
|
|
const min = requirement.minCount ?? (requirement.required ? 1 : 0);
|
|
const max = requirement.maxCount ?? undefined;
|
|
|
|
if (selections.length < min) {
|
|
throw new Error(
|
|
`Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" requiert au moins ${min} sélection(s).`,
|
|
);
|
|
}
|
|
|
|
if (max !== undefined && selections.length > max) {
|
|
throw new Error(
|
|
`Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" ne peut pas dépasser ${max} sélection(s).`,
|
|
);
|
|
}
|
|
|
|
if (!requirement.allowNewModels) {
|
|
const missingModel = selections.find((selection) => !selection.componentModelId);
|
|
if (missingModel) {
|
|
throw new Error(
|
|
`Le groupe de composants "${requirement.label || requirement.typeComposant?.name || requirement.id}" n'autorise que la sélection de modèles existants.`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const requirement of pieceRequirements) {
|
|
const selections = pieceSelectionMap.get(requirement.id) ?? [];
|
|
const min = requirement.minCount ?? (requirement.required ? 1 : 0);
|
|
const max = requirement.maxCount ?? undefined;
|
|
|
|
if (selections.length < min) {
|
|
throw new Error(
|
|
`Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" requiert au moins ${min} sélection(s).`,
|
|
);
|
|
}
|
|
|
|
if (max !== undefined && selections.length > max) {
|
|
throw new Error(
|
|
`Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" ne peut pas dépasser ${max} sélection(s).`,
|
|
);
|
|
}
|
|
|
|
if (!requirement.allowNewModels) {
|
|
const missingModel = selections.find((selection) => !selection.pieceModelId);
|
|
if (missingModel) {
|
|
throw new Error(
|
|
`Le groupe de pièces "${requirement.label || requirement.typePiece?.name || requirement.id}" n'autorise que la sélection de modèles existants.`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const selection of componentSelections) {
|
|
if (!selection.componentModelId) {
|
|
continue;
|
|
}
|
|
const model = componentModelMap.get(selection.componentModelId);
|
|
if (!model) {
|
|
throw new Error(`Modèle de composant introuvable: ${selection.componentModelId}`);
|
|
}
|
|
const requirement = componentRequirementMap.get(selection.requirementId);
|
|
if (!requirement) {
|
|
throw new Error(`Requirement de composant introuvable: ${selection.requirementId}`);
|
|
}
|
|
if (model.typeComposantId !== requirement.typeComposantId) {
|
|
throw new Error(
|
|
`Le modèle de composant "${model.name}" n'appartient pas au type de composant attendu pour ce groupe.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
for (const selection of pieceSelections) {
|
|
if (!selection.pieceModelId) {
|
|
continue;
|
|
}
|
|
const model = pieceModelMap.get(selection.pieceModelId);
|
|
if (!model) {
|
|
throw new Error(`Modèle de pièce introuvable: ${selection.pieceModelId}`);
|
|
}
|
|
const requirement = pieceRequirementMap.get(selection.requirementId);
|
|
if (!requirement) {
|
|
throw new Error(`Requirement de pièce introuvable: ${selection.requirementId}`);
|
|
}
|
|
if (model.typePieceId !== requirement.typePieceId) {
|
|
throw new Error(
|
|
`Le modèle de pièce "${model.name}" n'appartient pas au type de pièce attendu pour ce groupe.`,
|
|
);
|
|
}
|
|
}
|
|
|
|
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({
|
|
data: machineData,
|
|
include: {
|
|
site: true,
|
|
typeMachine: true,
|
|
constructeur: true,
|
|
},
|
|
});
|
|
|
|
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, machine.id, [definition]);
|
|
}
|
|
}
|
|
} else {
|
|
const legacyComponents = (typeMachine as any).components;
|
|
if (legacyComponents) {
|
|
await this.createComponentsFromType(prisma, machine.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, machine.id, [definition]);
|
|
}
|
|
}
|
|
} else {
|
|
const legacyPieces = (typeMachine as any).machinePieces;
|
|
if (legacyPieces) {
|
|
await this.createMachinePiecesFromType(prisma, machine.id, legacyPieces);
|
|
}
|
|
}
|
|
|
|
if (typeMachine.customFields && typeMachine.customFields.length > 0) {
|
|
await this.createMachineCustomFieldsFromType(prisma, machine.id, typeMachine.customFields);
|
|
}
|
|
|
|
return prisma.machine.findUnique({
|
|
where: { id: machine.id },
|
|
include: MACHINE_DEFAULT_INCLUDE,
|
|
});
|
|
});
|
|
}
|
|
|
|
private cloneStructure(definition: any): any {
|
|
if (definition === undefined || definition === null) {
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(JSON.stringify(definition));
|
|
} catch (error) {
|
|
if (Array.isArray(definition)) {
|
|
return definition.map((item) => this.cloneStructure(item));
|
|
}
|
|
|
|
if (typeof definition === 'object') {
|
|
return { ...definition };
|
|
}
|
|
|
|
return definition;
|
|
}
|
|
}
|
|
|
|
private normalizeComponentSelection(
|
|
selection: MachineComponentSelectionDto,
|
|
requirement: any,
|
|
model?: any,
|
|
): any {
|
|
const baseDefinition = selection.definition ?? (model?.structure ?? {});
|
|
const definition = this.cloneStructure(baseDefinition);
|
|
const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {};
|
|
|
|
prepared.name = prepared.name || model?.name || requirement?.typeComposant?.name || 'Composant';
|
|
prepared.reference = prepared.reference ?? model?.structure?.reference ?? '';
|
|
prepared.emplacement = prepared.emplacement ?? model?.structure?.emplacement ?? '';
|
|
prepared.prix = prepared.prix ?? model?.structure?.prix ?? null;
|
|
|
|
prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : [];
|
|
prepared.pieces = Array.isArray(prepared.pieces)
|
|
? prepared.pieces
|
|
: prepared.pieces
|
|
? [prepared.pieces]
|
|
: [];
|
|
prepared.subComponents = Array.isArray(prepared.subComponents)
|
|
? prepared.subComponents
|
|
: prepared.subComponents
|
|
? [prepared.subComponents]
|
|
: [];
|
|
|
|
prepared.typeComposantId = prepared.typeComposantId || requirement?.typeComposantId || model?.typeComposantId || null;
|
|
prepared.__componentModelId = selection.componentModelId ?? null;
|
|
prepared.__requirementId = requirement?.id ?? null;
|
|
|
|
return prepared;
|
|
}
|
|
|
|
private normalizePieceSelection(
|
|
selection: MachinePieceSelectionDto,
|
|
requirement: any,
|
|
model?: any,
|
|
): any {
|
|
const baseDefinition = selection.definition ?? (model?.structure ?? {});
|
|
const definition = this.cloneStructure(baseDefinition);
|
|
const prepared: any = definition && typeof definition === 'object' && !Array.isArray(definition) ? definition : {};
|
|
|
|
prepared.name = prepared.name || model?.name || requirement?.typePiece?.name || 'Pièce';
|
|
prepared.customFields = Array.isArray(prepared.customFields) ? prepared.customFields : [];
|
|
prepared.typePieceId = prepared.typePieceId || requirement?.typePieceId || model?.typePieceId || null;
|
|
prepared.__pieceModelId = selection.pieceModelId ?? null;
|
|
prepared.__requirementId = requirement?.id ?? null;
|
|
|
|
return prepared;
|
|
}
|
|
|
|
private async createComponentsFromType(prisma: any, machineId: string, components: any[], parentComposantId?: string) {
|
|
for (const component of components) {
|
|
if (!component || !component.name) continue;
|
|
|
|
const customFields = Array.isArray(component.customFields) ? component.customFields : [];
|
|
const componentPieces = Array.isArray(component.pieces) ? component.pieces : [];
|
|
const subComponents = Array.isArray(component.subComponents) ? component.subComponents : [];
|
|
|
|
const componentModelId = component.__componentModelId ?? null;
|
|
const requirementId = component.__requirementId ?? null;
|
|
const providedTypeComposantId = component.typeComposantId
|
|
?? (component.typeComposant && component.typeComposant.id ? component.typeComposant.id : null);
|
|
|
|
let typeComposantId: string | null = providedTypeComposantId ?? null;
|
|
|
|
if (!typeComposantId && customFields.length > 0) {
|
|
let typeComposant = await prisma.typeComposant.findFirst({
|
|
where: { name: component.name },
|
|
});
|
|
|
|
if (!typeComposant) {
|
|
typeComposant = await prisma.typeComposant.create({
|
|
data: {
|
|
name: component.name,
|
|
description: component.description || '',
|
|
},
|
|
});
|
|
|
|
for (const customField of customFields) {
|
|
await prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typeComposantId: typeComposant.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
typeComposantId = typeComposant.id;
|
|
}
|
|
|
|
const createdComposant = await prisma.composant.create({
|
|
data: {
|
|
name: component.name,
|
|
reference: component.reference || '',
|
|
constructeurId: await this.resolveConstructeurId(prisma, component.constructeur),
|
|
emplacement: component.emplacement || '',
|
|
prix: component.prix ?? null,
|
|
machineId,
|
|
parentComposantId,
|
|
typeComposantId,
|
|
composantModelId: componentModelId,
|
|
typeMachineComponentRequirementId: requirementId,
|
|
},
|
|
});
|
|
|
|
if (typeComposantId) {
|
|
const typeCustomFields = await prisma.customField.findMany({
|
|
where: { typeComposantId },
|
|
});
|
|
|
|
for (const customField of typeCustomFields) {
|
|
const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
|
await prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
composantId: createdComposant.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const piece of componentPieces) {
|
|
if (!piece || !piece.name) continue;
|
|
|
|
const pieceCustomFields = Array.isArray(piece.customFields) ? piece.customFields : [];
|
|
const pieceModelId = piece.__pieceModelId ?? null;
|
|
const pieceRequirementId = piece.__requirementId ?? null;
|
|
const providedTypePieceId = piece.typePieceId
|
|
?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
|
|
|
let typePieceId: string | null = providedTypePieceId ?? null;
|
|
|
|
if (!typePieceId && pieceCustomFields.length > 0) {
|
|
let typePiece = await prisma.typePiece.findFirst({
|
|
where: { name: piece.name },
|
|
});
|
|
|
|
if (!typePiece) {
|
|
typePiece = await prisma.typePiece.create({
|
|
data: {
|
|
name: piece.name,
|
|
description: piece.description || '',
|
|
},
|
|
});
|
|
|
|
for (const customField of pieceCustomFields) {
|
|
await prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typePieceId: typePiece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
typePieceId = typePiece.id;
|
|
}
|
|
|
|
const createdPiece = await prisma.piece.create({
|
|
data: {
|
|
name: piece.name,
|
|
reference: piece.reference || '',
|
|
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
|
|
emplacement: piece.emplacement || '',
|
|
prix: piece.prix ?? null,
|
|
composantId: createdComposant.id,
|
|
typePieceId,
|
|
pieceModelId,
|
|
typeMachinePieceRequirementId: pieceRequirementId,
|
|
},
|
|
});
|
|
|
|
if (typePieceId) {
|
|
const typePieceCustomFields = await prisma.customField.findMany({
|
|
where: { typePieceId },
|
|
});
|
|
|
|
for (const customField of typePieceCustomFields) {
|
|
const defaultValue = pieceCustomFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
|
await prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
pieceId: createdPiece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (subComponents.length > 0) {
|
|
await this.createComponentsFromType(prisma, machineId, subComponents, createdComposant.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async createMachinePiecesFromType(prisma: any, machineId: string, machinePieces: any[]) {
|
|
for (const piece of machinePieces) {
|
|
if (!piece || !piece.name) continue;
|
|
|
|
const customFields = Array.isArray(piece.customFields) ? piece.customFields : [];
|
|
const pieceModelId = piece.__pieceModelId ?? null;
|
|
const requirementId = piece.__requirementId ?? null;
|
|
const providedTypePieceId = piece.typePieceId
|
|
?? (piece.typePiece && piece.typePiece.id ? piece.typePiece.id : null);
|
|
|
|
let typePieceId: string | null = providedTypePieceId ?? null;
|
|
|
|
if (!typePieceId && customFields.length > 0) {
|
|
let typePiece = await prisma.typePiece.findFirst({
|
|
where: { name: piece.name },
|
|
});
|
|
|
|
if (!typePiece) {
|
|
typePiece = await prisma.typePiece.create({
|
|
data: {
|
|
name: piece.name,
|
|
description: piece.description || '',
|
|
},
|
|
});
|
|
|
|
for (const customField of customFields) {
|
|
await prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typePieceId: typePiece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
typePieceId = typePiece.id;
|
|
}
|
|
|
|
const createdPiece = await prisma.piece.create({
|
|
data: {
|
|
name: piece.name,
|
|
reference: piece.reference || '',
|
|
constructeurId: await this.resolveConstructeurId(prisma, piece.constructeur),
|
|
emplacement: piece.emplacement || '',
|
|
prix: piece.prix ?? null,
|
|
machineId,
|
|
typePieceId,
|
|
pieceModelId,
|
|
typeMachinePieceRequirementId: requirementId,
|
|
},
|
|
});
|
|
|
|
if (typePieceId) {
|
|
const typePieceCustomFields = await prisma.customField.findMany({
|
|
where: { typePieceId },
|
|
});
|
|
|
|
for (const customField of typePieceCustomFields) {
|
|
const defaultValue = customFields.find((cf) => cf.name === customField.name)?.defaultValue || '';
|
|
await prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
pieceId: createdPiece.id,
|
|
},
|
|
});
|
|
}
|
|
} else if (customFields.length > 0) {
|
|
for (const customField of customFields) {
|
|
const createdCustomField = await prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typePieceId: null,
|
|
},
|
|
});
|
|
|
|
await prisma.customFieldValue.create({
|
|
data: {
|
|
value: customField.defaultValue || '',
|
|
customFieldId: createdCustomField.id,
|
|
pieceId: createdPiece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private async createMachineCustomFieldsFromType(prisma: any, machineId: string, machineCustomFields: any[]) {
|
|
for (const customField of machineCustomFields) {
|
|
if (!customField || !customField.name) continue;
|
|
|
|
// Créer le champ personnalisé pour la machine
|
|
const createdCustomField = await prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typeMachineId: null, // Ce champ sera lié à la machine individuelle
|
|
},
|
|
});
|
|
|
|
// Créer la valeur par défaut pour la machine
|
|
await prisma.customFieldValue.create({
|
|
data: {
|
|
value: customField.defaultValue || '',
|
|
customFieldId: createdCustomField.id,
|
|
machineId: machineId,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
async findAll() {
|
|
return this.prisma.machine.findMany({
|
|
include: MACHINE_DEFAULT_INCLUDE,
|
|
});
|
|
}
|
|
|
|
async findOne(id: string) {
|
|
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 },
|
|
include: {
|
|
typeMachine: {
|
|
include: TYPE_MACHINE_CONFIGURATION_INCLUDE,
|
|
},
|
|
},
|
|
});
|
|
|
|
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) {
|
|
return this.prisma.machine.update({
|
|
where: { id },
|
|
data: updateMachineDto,
|
|
include: MACHINE_DEFAULT_INCLUDE,
|
|
});
|
|
}
|
|
|
|
private async resolveConstructeurId(prisma: any, rawName?: string) {
|
|
if (!rawName) return null
|
|
const name = String(rawName).trim()
|
|
if (!name) return null
|
|
|
|
const existing = await prisma.constructeur.findFirst({
|
|
where: {
|
|
name: {
|
|
equals: name,
|
|
mode: 'insensitive',
|
|
},
|
|
},
|
|
})
|
|
|
|
if (existing) return existing.id
|
|
|
|
const created = await prisma.constructeur.create({
|
|
data: { name },
|
|
})
|
|
|
|
return created.id
|
|
}
|
|
|
|
async remove(id: string) {
|
|
// Vérifier que la machine existe
|
|
const machine = await this.prisma.machine.findUnique({
|
|
where: { id },
|
|
include: {
|
|
composants: true,
|
|
pieces: true,
|
|
documents: true,
|
|
customFieldValues: true,
|
|
},
|
|
});
|
|
|
|
if (!machine) {
|
|
throw new Error('Machine non trouvée');
|
|
}
|
|
|
|
// Supprimer la machine et tous ses éléments associés en cascade
|
|
return await this.prisma.$transaction(async (prisma) => {
|
|
// Supprimer les valeurs de champs personnalisés
|
|
if (machine.customFieldValues.length > 0) {
|
|
await prisma.customFieldValue.deleteMany({
|
|
where: { machineId: id },
|
|
});
|
|
}
|
|
|
|
// Supprimer les documents
|
|
if (machine.documents.length > 0) {
|
|
await prisma.document.deleteMany({
|
|
where: { machineId: id },
|
|
});
|
|
}
|
|
|
|
// Supprimer les pièces (sera fait en cascade via la relation)
|
|
if (machine.pieces.length > 0) {
|
|
await prisma.piece.deleteMany({
|
|
where: { machineId: id },
|
|
});
|
|
}
|
|
|
|
// Supprimer les composants (sera fait en cascade via la relation)
|
|
if (machine.composants.length > 0) {
|
|
await prisma.composant.deleteMany({
|
|
where: { machineId: id },
|
|
});
|
|
}
|
|
|
|
// Supprimer la machine
|
|
return await prisma.machine.delete({
|
|
where: { id },
|
|
});
|
|
});
|
|
}
|
|
|
|
// Méthode pour ajouter les champs personnalisés manquants aux composants et pièces existants
|
|
async addMissingCustomFields(machineId: string) {
|
|
const machine = await this.prisma.machine.findUnique({
|
|
where: { id: machineId },
|
|
include: {
|
|
typeMachine: true,
|
|
composants: {
|
|
include: {
|
|
pieces: true,
|
|
},
|
|
},
|
|
pieces: true,
|
|
},
|
|
});
|
|
|
|
if (!machine || !machine.typeMachine) {
|
|
throw new Error('Machine ou type de machine non trouvé');
|
|
}
|
|
|
|
const typeMachine = machine.typeMachine as any;
|
|
const components = typeMachine.components || [];
|
|
const machinePieces = typeMachine.machinePieces || [];
|
|
const machineCustomFields = typeMachine.customFields || [];
|
|
|
|
// Traiter les champs personnalisés de la machine
|
|
if (machineCustomFields && machineCustomFields.length > 0) {
|
|
for (const customField of machineCustomFields) {
|
|
const existingValue = await this.prisma.customFieldValue.findFirst({
|
|
where: {
|
|
machineId: machineId,
|
|
customField: {
|
|
name: customField.name,
|
|
},
|
|
},
|
|
include: {
|
|
customField: true,
|
|
},
|
|
});
|
|
|
|
if (!existingValue) {
|
|
// Créer le champ personnalisé pour la machine
|
|
const createdCustomField = await this.prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typeMachineId: null, // Ce champ sera lié à la machine individuelle
|
|
},
|
|
});
|
|
|
|
// Créer la valeur par défaut pour la machine
|
|
await this.prisma.customFieldValue.create({
|
|
data: {
|
|
value: customField.defaultValue || '',
|
|
customFieldId: createdCustomField.id,
|
|
machineId: machineId,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Traiter les composants existants
|
|
for (const component of machine.composants) {
|
|
const typeComponent = components.find((c: any) => c.name === component.name);
|
|
if (typeComponent && typeComponent.customFields && typeComponent.customFields.length > 0) {
|
|
// Créer le type de composant s'il n'existe pas
|
|
let typeComposant = await this.prisma.typeComposant.findFirst({
|
|
where: { name: component.name },
|
|
});
|
|
|
|
if (!typeComposant) {
|
|
typeComposant = await this.prisma.typeComposant.create({
|
|
data: {
|
|
name: component.name,
|
|
description: typeComponent.description || '',
|
|
},
|
|
});
|
|
}
|
|
|
|
// Créer les champs personnalisés pour le type de composant
|
|
for (const customField of typeComponent.customFields) {
|
|
const existingField = await this.prisma.customField.findFirst({
|
|
where: {
|
|
name: customField.name,
|
|
typeComposantId: typeComposant.id,
|
|
},
|
|
});
|
|
|
|
if (!existingField) {
|
|
await this.prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typeComposantId: typeComposant.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour le composant avec le type
|
|
await this.prisma.composant.update({
|
|
where: { id: component.id },
|
|
data: { typeComposantId: typeComposant.id },
|
|
});
|
|
|
|
// Créer les valeurs des champs personnalisés pour le composant
|
|
const customFields = await this.prisma.customField.findMany({
|
|
where: { typeComposantId: typeComposant.id },
|
|
});
|
|
|
|
for (const customField of customFields) {
|
|
const existingValue = await this.prisma.customFieldValue.findFirst({
|
|
where: {
|
|
customFieldId: customField.id,
|
|
composantId: component.id,
|
|
},
|
|
});
|
|
|
|
if (!existingValue) {
|
|
const defaultValue = typeComponent.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
|
await this.prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
composantId: component.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Traiter les pièces du composant
|
|
for (const piece of component.pieces) {
|
|
const typePiece = typeComponent.pieces?.find((p: any) => p.name === piece.name);
|
|
if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) {
|
|
// Créer le type de pièce s'il n'existe pas
|
|
let typePieceEntity = await this.prisma.typePiece.findFirst({
|
|
where: { name: piece.name },
|
|
});
|
|
|
|
if (!typePieceEntity) {
|
|
typePieceEntity = await this.prisma.typePiece.create({
|
|
data: {
|
|
name: piece.name,
|
|
description: typePiece.description || '',
|
|
},
|
|
});
|
|
}
|
|
|
|
// Créer les champs personnalisés pour le type de pièce
|
|
for (const customField of typePiece.customFields) {
|
|
const existingField = await this.prisma.customField.findFirst({
|
|
where: {
|
|
name: customField.name,
|
|
typePieceId: typePieceEntity.id,
|
|
},
|
|
});
|
|
|
|
if (!existingField) {
|
|
await this.prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typePieceId: typePieceEntity.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour la pièce avec le type
|
|
await this.prisma.piece.update({
|
|
where: { id: piece.id },
|
|
data: { typePieceId: typePieceEntity.id },
|
|
});
|
|
|
|
// Créer les valeurs des champs personnalisés pour la pièce
|
|
const customFields = await this.prisma.customField.findMany({
|
|
where: { typePieceId: typePieceEntity.id },
|
|
});
|
|
|
|
for (const customField of customFields) {
|
|
const existingValue = await this.prisma.customFieldValue.findFirst({
|
|
where: {
|
|
customFieldId: customField.id,
|
|
pieceId: piece.id,
|
|
},
|
|
});
|
|
|
|
if (!existingValue) {
|
|
const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
|
await this.prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
pieceId: piece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Traiter les pièces de machine
|
|
for (const piece of machine.pieces) {
|
|
const typePiece = machinePieces.find((p: any) => p.name === piece.name);
|
|
if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) {
|
|
// Créer le type de pièce s'il n'existe pas
|
|
let typePieceEntity = await this.prisma.typePiece.findFirst({
|
|
where: { name: piece.name },
|
|
});
|
|
|
|
if (!typePieceEntity) {
|
|
typePieceEntity = await this.prisma.typePiece.create({
|
|
data: {
|
|
name: piece.name,
|
|
description: typePiece.description || '',
|
|
},
|
|
});
|
|
}
|
|
|
|
// Créer les champs personnalisés pour le type de pièce
|
|
for (const customField of typePiece.customFields) {
|
|
const existingField = await this.prisma.customField.findFirst({
|
|
where: {
|
|
name: customField.name,
|
|
typePieceId: typePieceEntity.id,
|
|
},
|
|
});
|
|
|
|
if (!existingField) {
|
|
await this.prisma.customField.create({
|
|
data: {
|
|
name: customField.name,
|
|
type: customField.type,
|
|
required: customField.required || false,
|
|
defaultValue: customField.defaultValue,
|
|
options: customField.options || [],
|
|
typePieceId: typePieceEntity.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour la pièce avec le type
|
|
await this.prisma.piece.update({
|
|
where: { id: piece.id },
|
|
data: { typePieceId: typePieceEntity.id },
|
|
});
|
|
|
|
// Créer les valeurs des champs personnalisés pour la pièce
|
|
const customFields = await this.prisma.customField.findMany({
|
|
where: { typePieceId: typePieceEntity.id },
|
|
});
|
|
|
|
for (const customField of customFields) {
|
|
const existingValue = await this.prisma.customFieldValue.findFirst({
|
|
where: {
|
|
customFieldId: customField.id,
|
|
pieceId: piece.id,
|
|
},
|
|
});
|
|
|
|
if (!existingValue) {
|
|
const defaultValue = typePiece.customFields.find((cf: any) => cf.name === customField.name)?.defaultValue || '';
|
|
await this.prisma.customFieldValue.create({
|
|
data: {
|
|
value: defaultValue,
|
|
customFieldId: customField.id,
|
|
pieceId: piece.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.findOne(machineId);
|
|
}
|
|
}
|