refactor: prepare multi-machine inventory associations
This commit is contained in:
42
scripts/cleanup-custom-fields.ts
Normal file
42
scripts/cleanup-custom-fields.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
async function main () {
|
||||
try {
|
||||
console.log('Starting custom fields cleanup...')
|
||||
|
||||
const deletedValues = await prisma.customFieldValue.deleteMany({
|
||||
where: {
|
||||
customField: {
|
||||
OR: [
|
||||
{ typeComposantId: { not: null } },
|
||||
{ typePieceId: { not: null } },
|
||||
{ typeMachineId: { not: null } }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(`Deleted ${deletedValues.count} custom field values linked to type-level definitions.`)
|
||||
|
||||
const deletedFields = await prisma.customField.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ typeComposantId: { not: null } },
|
||||
{ typePieceId: { not: null } },
|
||||
{ typeMachineId: { not: null } }
|
||||
]
|
||||
}
|
||||
})
|
||||
console.log(`Deleted ${deletedFields.count} custom field definitions linked to model types.`)
|
||||
|
||||
console.log('Cleanup complete.')
|
||||
} catch (error) {
|
||||
console.error('Cleanup failed:', error)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
await prisma.$disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
593
scripts/seed-basic-categories.ts
Normal file
593
scripts/seed-basic-categories.ts
Normal file
@@ -0,0 +1,593 @@
|
||||
import { Prisma, PrismaClient, ModelCategory } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
type CustomFieldInput = {
|
||||
name: string;
|
||||
type: 'text' | 'number' | 'select';
|
||||
required?: boolean;
|
||||
options?: readonly string[];
|
||||
};
|
||||
|
||||
type ModelTypeSeed = {
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
customFields: readonly CustomFieldInput[];
|
||||
};
|
||||
|
||||
type ComponentRequirementSeed = {
|
||||
typeCode: string;
|
||||
label: string;
|
||||
minCount: number;
|
||||
maxCount?: number | null;
|
||||
required?: boolean;
|
||||
allowNewModels?: boolean;
|
||||
};
|
||||
|
||||
type PieceRequirementSeed = {
|
||||
typeCode: string;
|
||||
label: string;
|
||||
minCount: number;
|
||||
maxCount?: number | null;
|
||||
required?: boolean;
|
||||
allowNewModels?: boolean;
|
||||
};
|
||||
|
||||
const componentTypes: readonly ModelTypeSeed[] = [
|
||||
{
|
||||
code: 'drive-module',
|
||||
name: 'Module d entrainement',
|
||||
description: 'Sous-ensemble moteur et reducteur pour entrainements principaux.',
|
||||
customFields: [
|
||||
{ name: 'Puissance nominale (kW)', type: 'number', required: true },
|
||||
{ name: 'Indice de protection', type: 'select', options: ['IP55', 'IP65', 'IP66'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'sensor-array',
|
||||
name: 'Chaine de capteurs',
|
||||
description: 'Groupe de capteurs industriels (temperature, vibration, debit).',
|
||||
customFields: [
|
||||
{ name: 'Type principal', type: 'select', options: ['Temperature', 'Vibration', 'Debit'] },
|
||||
{ name: 'Plage de mesure', type: 'text' },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'control-cabinet',
|
||||
name: 'Armoire de controle',
|
||||
description: 'Armoire electrique avec automate, protection et distribution.',
|
||||
customFields: [
|
||||
{ name: 'Tension alimentation (V)', type: 'number' },
|
||||
{ name: 'Nombre de departs', type: 'number' },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'hydraulic-pack',
|
||||
name: 'Groupe hydraulique',
|
||||
description: 'Bloc hydraulique complet (pompes, accumulateurs, filtration).',
|
||||
customFields: [
|
||||
{ name: 'Pression nominale (bar)', type: 'number', required: true },
|
||||
{ name: 'Debit nominal (L/min)', type: 'number' },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'structure-frame',
|
||||
name: 'Chassis structurel',
|
||||
description: 'Structure porteuse ou chassis mecano-soude.',
|
||||
customFields: [
|
||||
{ name: 'Matiere', type: 'select', options: ['Acier', 'Inox', 'Aluminium'] },
|
||||
{ name: 'Charge admissible (kg)', type: 'number' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const pieceTypes: readonly ModelTypeSeed[] = [
|
||||
{
|
||||
code: 'belt-kit',
|
||||
name: 'Kit courroie',
|
||||
description: 'Courroie et accessoires pour entrainements.',
|
||||
customFields: [
|
||||
{ name: 'Type', type: 'select', options: ['Poly-V', 'Trapezoidale', 'Synchronisee'] },
|
||||
{ name: 'Longueur (mm)', type: 'number' },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'bearing-set',
|
||||
name: 'Jeu de roulements',
|
||||
description: 'Paire de roulements avec bagues et graisse.',
|
||||
customFields: [
|
||||
{ name: 'Diametre interieur (mm)', type: 'number', required: true },
|
||||
{ name: 'Classe', type: 'select', options: ['P0', 'P6', 'P5'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'filter-cartridge',
|
||||
name: 'Cartouche filtrante',
|
||||
description: 'Element filtrant pour fluides ou air.',
|
||||
customFields: [
|
||||
{ name: 'Grade de filtration (um)', type: 'number' },
|
||||
{ name: 'Type de media', type: 'select', options: ['Cellulose', 'Synthetique', 'Inox'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'sensor-probe',
|
||||
name: 'Sonde de mesure',
|
||||
description: 'Sonde ou capteur unitaire avec cable.',
|
||||
customFields: [
|
||||
{ name: 'Signal de sortie', type: 'select', options: ['4-20 mA', '0-10 V', 'PT100'] },
|
||||
{ name: 'Indice IP', type: 'select', options: ['IP67', 'IP68'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'maintenance-kit',
|
||||
name: 'Kit maintenance',
|
||||
description: 'Ensemble de pieces pour maintenance planifiee.',
|
||||
customFields: [
|
||||
{ name: 'Niveau de maintenance', type: 'select', options: ['Preventif', 'Correctif', 'Lourde'] },
|
||||
{ name: 'Duree estimee (h)', type: 'number' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const constructors = [
|
||||
{ name: 'ElectroMec Industrie', email: 'contact@electromec.fr', phone: '+33 4 72 00 11 22' },
|
||||
{ name: 'Hydraulic Systems Europe', email: 'sales@hydraulics-eu.com', phone: '+33 5 56 12 34 56' },
|
||||
{ name: 'Automation Lyon', email: 'support@automation-lyon.fr', phone: '+33 4 37 50 60 70' },
|
||||
{ name: 'ThermoTech Solutions', email: 'info@thermotech.eu', phone: '+33 1 44 55 66 77' },
|
||||
{ name: 'BearingWorks', email: 'service@bearingworks.com', phone: '+33 3 88 90 12 45' },
|
||||
] as const;
|
||||
|
||||
const machineCustomFields: readonly CustomFieldInput[] = [
|
||||
{ name: 'Reference installation', type: 'text' },
|
||||
{ name: 'Puissance installee (kW)', type: 'number' },
|
||||
{ name: 'Zone critique', type: 'select', options: ['Zone A', 'Zone B', 'Zone C'] },
|
||||
];
|
||||
|
||||
const componentRequirementSeeds: readonly ComponentRequirementSeed[] = [
|
||||
{
|
||||
typeCode: 'structure-frame',
|
||||
label: 'Chassis principal',
|
||||
minCount: 1,
|
||||
maxCount: 1,
|
||||
required: true,
|
||||
allowNewModels: false,
|
||||
},
|
||||
{
|
||||
typeCode: 'drive-module',
|
||||
label: 'Module d entrainement principal',
|
||||
minCount: 1,
|
||||
maxCount: 1,
|
||||
required: true,
|
||||
allowNewModels: false,
|
||||
},
|
||||
{
|
||||
typeCode: 'control-cabinet',
|
||||
label: 'Armoire de controle',
|
||||
minCount: 1,
|
||||
maxCount: 1,
|
||||
required: true,
|
||||
allowNewModels: false,
|
||||
},
|
||||
{
|
||||
typeCode: 'sensor-array',
|
||||
label: 'Capteurs de surveillance',
|
||||
minCount: 1,
|
||||
maxCount: 3,
|
||||
required: true,
|
||||
allowNewModels: true,
|
||||
},
|
||||
{
|
||||
typeCode: 'hydraulic-pack',
|
||||
label: 'Groupe hydraulique auxiliaire',
|
||||
minCount: 0,
|
||||
maxCount: 1,
|
||||
required: false,
|
||||
allowNewModels: true,
|
||||
},
|
||||
];
|
||||
|
||||
const pieceRequirementSeeds: readonly PieceRequirementSeed[] = [
|
||||
{
|
||||
typeCode: 'belt-kit',
|
||||
label: 'Kit courroie de rechange',
|
||||
minCount: 1,
|
||||
maxCount: 2,
|
||||
required: true,
|
||||
allowNewModels: true,
|
||||
},
|
||||
{
|
||||
typeCode: 'bearing-set',
|
||||
label: 'Roulements de secours',
|
||||
minCount: 1,
|
||||
maxCount: 2,
|
||||
required: true,
|
||||
allowNewModels: true,
|
||||
},
|
||||
{
|
||||
typeCode: 'filter-cartridge',
|
||||
label: 'Cartouches de filtration',
|
||||
minCount: 0,
|
||||
maxCount: 4,
|
||||
required: false,
|
||||
allowNewModels: true,
|
||||
},
|
||||
{
|
||||
typeCode: 'maintenance-kit',
|
||||
label: 'Kit maintenance planifiee',
|
||||
minCount: 0,
|
||||
maxCount: 1,
|
||||
required: false,
|
||||
allowNewModels: true,
|
||||
},
|
||||
{
|
||||
typeCode: 'sensor-probe',
|
||||
label: 'Sondes de rechange',
|
||||
minCount: 1,
|
||||
maxCount: 4,
|
||||
required: true,
|
||||
allowNewModels: true,
|
||||
},
|
||||
];
|
||||
|
||||
function mapCustomFields(fields: readonly CustomFieldInput[]) {
|
||||
if (!fields.length) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
create: fields.map((field) => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required ?? false,
|
||||
options: field.options ? [...field.options] : [],
|
||||
})),
|
||||
} as const;
|
||||
}
|
||||
|
||||
async function upsertComponentType(type: ModelTypeSeed) {
|
||||
const customFields = mapCustomFields(type.customFields);
|
||||
await prisma.modelType.upsert({
|
||||
where: { code: type.code },
|
||||
update: {
|
||||
name: type.name,
|
||||
description: type.description,
|
||||
notes: type.description,
|
||||
customFields: {
|
||||
deleteMany: {},
|
||||
...(customFields ?? {}),
|
||||
},
|
||||
},
|
||||
create: {
|
||||
code: type.code,
|
||||
name: type.name,
|
||||
description: type.description,
|
||||
notes: type.description,
|
||||
category: ModelCategory.COMPONENT,
|
||||
...(customFields ? { customFields } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function upsertPieceType(type: ModelTypeSeed) {
|
||||
const customFields = mapCustomFields(type.customFields);
|
||||
await prisma.modelType.upsert({
|
||||
where: { code: type.code },
|
||||
update: {
|
||||
name: type.name,
|
||||
description: type.description,
|
||||
notes: type.description,
|
||||
pieceCustomFields: {
|
||||
deleteMany: {},
|
||||
...(customFields ?? {}),
|
||||
},
|
||||
},
|
||||
create: {
|
||||
code: type.code,
|
||||
name: type.name,
|
||||
description: type.description,
|
||||
notes: type.description,
|
||||
category: ModelCategory.PIECE,
|
||||
...(customFields ? { pieceCustomFields: customFields } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function applyPieceSkeletons(pieceMap: Map<string, { id: string }>) {
|
||||
type PieceSkeleton = {
|
||||
customFields?: Array<{ name: string; value?: unknown; type?: string; required?: boolean; options?: unknown }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const definitions: Record<string, PieceSkeleton> = {
|
||||
'belt-kit': {
|
||||
customFields: [
|
||||
{ name: 'Type', value: 'Poly-V' },
|
||||
{ name: 'Longueur (mm)', value: 1800 },
|
||||
],
|
||||
remplacementHeures: 1500,
|
||||
stockageRecommande: 'Local sec et tempere',
|
||||
},
|
||||
'bearing-set': {
|
||||
customFields: [
|
||||
{ name: 'Diametre interieur (mm)', value: 45 },
|
||||
{ name: 'Classe', value: 'P6' },
|
||||
],
|
||||
graisseRecommandee: 'Lithium NLGI2',
|
||||
},
|
||||
'filter-cartridge': {
|
||||
customFields: [
|
||||
{ name: 'Grade de filtration (um)', value: 10 },
|
||||
{ name: 'Type de media', value: 'Synthetique' },
|
||||
],
|
||||
remplacementMensuel: true,
|
||||
},
|
||||
'sensor-probe': {
|
||||
customFields: [
|
||||
{ name: 'Signal de sortie', value: '4-20 mA' },
|
||||
{ name: 'Indice IP', value: 'IP67' },
|
||||
],
|
||||
calibrationIntervalJours: 180,
|
||||
},
|
||||
'maintenance-kit': {
|
||||
customFields: [
|
||||
{ name: 'Niveau de maintenance', value: 'Preventif' },
|
||||
{ name: 'Duree estimee (h)', value: 4 },
|
||||
],
|
||||
contenuStandard: ['Filtres', 'Joints', 'Visserie'],
|
||||
},
|
||||
};
|
||||
|
||||
for (const [code, structure] of Object.entries(definitions)) {
|
||||
const record = pieceMap.get(code);
|
||||
if (!record) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.modelType.update({
|
||||
where: { id: record.id },
|
||||
data: {
|
||||
pieceSkeleton: structure as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function applyComponentSkeletons(
|
||||
componentMap: Map<string, { id: string }>,
|
||||
pieceMap: Map<string, { id: string }>,
|
||||
) {
|
||||
const pieceRef = (code: string, role?: string) => {
|
||||
const piece = pieceMap.get(code);
|
||||
if (!piece) {
|
||||
throw new Error(`Piece type ${code} requis pour le squelette`);
|
||||
}
|
||||
return {
|
||||
typePieceId: piece.id,
|
||||
...(role ? { role } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
const componentRef = (code: string, alias?: string) => {
|
||||
const component = componentMap.get(code);
|
||||
if (!component) {
|
||||
throw new Error(`Component type ${code} requis pour le squelette`);
|
||||
}
|
||||
return {
|
||||
typeComposantId: component.id,
|
||||
...(alias ? { alias } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
type ComponentSkeleton = {
|
||||
pieces: Array<{ typePieceId: string; role?: string }>;
|
||||
customFields: Array<{ key: string; value: unknown }>;
|
||||
subcomponents: Array<{ typeComposantId?: string; alias?: string; familyCode?: string; modelId?: string }>;
|
||||
};
|
||||
|
||||
const definitions: Record<string, ComponentSkeleton> = {
|
||||
'drive-module': {
|
||||
pieces: [
|
||||
pieceRef('belt-kit', 'Courroie principale'),
|
||||
pieceRef('bearing-set', 'Roulements de sortie'),
|
||||
],
|
||||
customFields: [
|
||||
{ key: 'Lubrification', value: 'Graissage centralise' },
|
||||
{ key: 'ControleVibration', value: 'Capteurs integres' },
|
||||
],
|
||||
subcomponents: [componentRef('sensor-array', 'Capteurs integres')],
|
||||
},
|
||||
'sensor-array': {
|
||||
pieces: [pieceRef('sensor-probe', 'Sonde principale')],
|
||||
customFields: [
|
||||
{ key: 'Calibration', value: 'A effectuer tous les 6 mois' },
|
||||
{ key: 'NombreCapteursMax', value: 6 },
|
||||
],
|
||||
subcomponents: [],
|
||||
},
|
||||
'control-cabinet': {
|
||||
pieces: [
|
||||
pieceRef('maintenance-kit', 'Kit rechange armoire'),
|
||||
pieceRef('sensor-probe', 'Sonde ambiance'),
|
||||
],
|
||||
customFields: [
|
||||
{ key: 'ClassementLocal', value: 'Non ATEX' },
|
||||
{ key: 'RefAutomate', value: 'PLC-STD-200' },
|
||||
],
|
||||
subcomponents: [],
|
||||
},
|
||||
'hydraulic-pack': {
|
||||
pieces: [
|
||||
pieceRef('filter-cartridge', 'Filtre hydraulique'),
|
||||
pieceRef('maintenance-kit', 'Kit joints hydrauliques'),
|
||||
],
|
||||
customFields: [
|
||||
{ key: 'ReservoirLitres', value: 120 },
|
||||
{ key: 'TypeHuile', value: 'HLP46' },
|
||||
],
|
||||
subcomponents: [componentRef('sensor-array', 'Capteurs pression et debit')],
|
||||
},
|
||||
'structure-frame': {
|
||||
pieces: [],
|
||||
customFields: [
|
||||
{ key: 'Revêtement', value: 'Peinture epoxy' },
|
||||
{ key: 'PointsLevage', value: 4 },
|
||||
],
|
||||
subcomponents: [componentRef('sensor-array', 'Capteurs deformation')],
|
||||
},
|
||||
};
|
||||
|
||||
for (const [code, structure] of Object.entries(definitions)) {
|
||||
const record = componentMap.get(code);
|
||||
if (!record) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.modelType.update({
|
||||
where: { id: record.id },
|
||||
data: {
|
||||
componentSkeleton: structure as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildComponentRequirements(
|
||||
componentMap: Map<string, { id: string }>,
|
||||
seeds: readonly ComponentRequirementSeed[],
|
||||
) {
|
||||
return seeds.map((seed) => {
|
||||
const type = componentMap.get(seed.typeCode);
|
||||
if (!type) {
|
||||
throw new Error(`Type composant ${seed.typeCode} introuvable pour le requirement`);
|
||||
}
|
||||
return {
|
||||
label: seed.label,
|
||||
minCount: seed.minCount,
|
||||
maxCount: seed.maxCount ?? null,
|
||||
required: seed.required ?? true,
|
||||
allowNewModels: seed.allowNewModels ?? true,
|
||||
typeComposant: { connect: { id: type.id } },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function buildPieceRequirements(
|
||||
pieceMap: Map<string, { id: string }>,
|
||||
seeds: readonly PieceRequirementSeed[],
|
||||
) {
|
||||
return seeds.map((seed) => {
|
||||
const type = pieceMap.get(seed.typeCode);
|
||||
if (!type) {
|
||||
throw new Error(`Type piece ${seed.typeCode} introuvable pour le requirement`);
|
||||
}
|
||||
return {
|
||||
label: seed.label,
|
||||
minCount: seed.minCount,
|
||||
maxCount: seed.maxCount ?? null,
|
||||
required: seed.required ?? true,
|
||||
allowNewModels: seed.allowNewModels ?? true,
|
||||
typePiece: { connect: { id: type.id } },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function seedMachineTemplate(
|
||||
componentMap: Map<string, { id: string }>,
|
||||
pieceMap: Map<string, { id: string }>,
|
||||
) {
|
||||
const name = 'Cellule Modulaire Standard';
|
||||
const description = 'Module generique compose d un chassis, d un entrainement, de capteurs et d une armoire de controle.';
|
||||
const componentRequirements = buildComponentRequirements(componentMap, componentRequirementSeeds);
|
||||
const pieceRequirements = buildPieceRequirements(pieceMap, pieceRequirementSeeds);
|
||||
|
||||
await prisma.typeMachine.upsert({
|
||||
where: { name },
|
||||
update: {
|
||||
description,
|
||||
category: 'Module',
|
||||
maintenanceFrequency: 'Mensuelle',
|
||||
customFields: {
|
||||
deleteMany: {},
|
||||
...(mapCustomFields(machineCustomFields) ?? {}),
|
||||
},
|
||||
componentRequirements: {
|
||||
deleteMany: {},
|
||||
create: componentRequirements,
|
||||
},
|
||||
pieceRequirements: {
|
||||
deleteMany: {},
|
||||
create: pieceRequirements,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
name,
|
||||
description,
|
||||
category: 'Module',
|
||||
maintenanceFrequency: 'Mensuelle',
|
||||
...(mapCustomFields(machineCustomFields) ? { customFields: mapCustomFields(machineCustomFields)! } : {}),
|
||||
componentRequirements: {
|
||||
create: componentRequirements,
|
||||
},
|
||||
pieceRequirements: {
|
||||
create: pieceRequirements,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Seeding component categories...');
|
||||
for (const component of componentTypes) {
|
||||
await upsertComponentType(component);
|
||||
}
|
||||
|
||||
console.log('Seeding piece categories...');
|
||||
for (const piece of pieceTypes) {
|
||||
await upsertPieceType(piece);
|
||||
}
|
||||
|
||||
const componentRecords = await prisma.modelType.findMany({
|
||||
where: { code: { in: componentTypes.map((type) => type.code) } },
|
||||
select: { id: true, code: true },
|
||||
});
|
||||
const pieceRecords = await prisma.modelType.findMany({
|
||||
where: { code: { in: pieceTypes.map((type) => type.code) } },
|
||||
select: { id: true, code: true },
|
||||
});
|
||||
|
||||
const componentMap = new Map(componentRecords.map((record) => [record.code, { id: record.id }]));
|
||||
const pieceMap = new Map(pieceRecords.map((record) => [record.code, { id: record.id }]));
|
||||
|
||||
console.log('Applying piece skeletons...');
|
||||
await applyPieceSkeletons(pieceMap);
|
||||
|
||||
console.log('Applying component skeletons...');
|
||||
await applyComponentSkeletons(componentMap, pieceMap);
|
||||
|
||||
console.log('Seeding constructors...');
|
||||
for (const constructeur of constructors) {
|
||||
await prisma.constructeur.upsert({
|
||||
where: { name: constructeur.name },
|
||||
update: {
|
||||
email: constructeur.email,
|
||||
phone: constructeur.phone,
|
||||
},
|
||||
create: constructeur,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Configuring machine template...');
|
||||
await seedMachineTemplate(componentMap, pieceMap);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
console.log('Seed completed.');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Seed failed:', error);
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
@@ -50,7 +50,9 @@ export class ModelTypeMapper {
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
...(skeleton ? { componentSkeleton: skeleton as Prisma.InputJsonValue } : {}),
|
||||
...(skeleton
|
||||
? { componentSkeleton: skeleton as Prisma.InputJsonValue }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -53,28 +53,38 @@ export function normalizeComponentModelStructure(
|
||||
return {
|
||||
familyCode:
|
||||
ensureString(
|
||||
candidate?.familyCode ?? candidate?.name ?? candidate?.typePieceLabel ?? 'UNKNOWN',
|
||||
candidate?.familyCode ??
|
||||
candidate?.name ??
|
||||
candidate?.typePieceLabel ??
|
||||
'UNKNOWN',
|
||||
).trim() || 'UNKNOWN',
|
||||
role: sanitizeRole(candidate?.role),
|
||||
} as ComponentModelStructure['pieces'][number];
|
||||
});
|
||||
|
||||
const customFields = toArray((structure as any)?.customFields).map((field) => {
|
||||
const candidate = field as Record<string, unknown> | null | undefined;
|
||||
const key = ensureString(candidate?.key ?? candidate?.name ?? 'unknown').trim();
|
||||
const customFields = toArray((structure as any)?.customFields).map(
|
||||
(field) => {
|
||||
const candidate = field as Record<string, unknown> | null | undefined;
|
||||
const key = ensureString(
|
||||
candidate?.key ?? candidate?.name ?? 'unknown',
|
||||
).trim();
|
||||
|
||||
return {
|
||||
key: key || 'unknown',
|
||||
value: candidate?.value ?? null,
|
||||
};
|
||||
});
|
||||
return {
|
||||
key: key || 'unknown',
|
||||
value: candidate?.value ?? null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const rawSubcomponents = toArray(
|
||||
(structure as any)?.subcomponents ?? (structure as any)?.subComponents,
|
||||
);
|
||||
|
||||
const subcomponents = rawSubcomponents.map((subcomponent) => {
|
||||
const candidate = subcomponent as Record<string, unknown> | null | undefined;
|
||||
const candidate = subcomponent as
|
||||
| Record<string, unknown>
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
if (candidate?.modelId) {
|
||||
return {
|
||||
@@ -90,13 +100,15 @@ export function normalizeComponentModelStructure(
|
||||
}
|
||||
if (candidate?.typeComposantId) {
|
||||
return {
|
||||
typeComposantId: ensureString(candidate.typeComposantId).trim() || 'UNKNOWN',
|
||||
typeComposantId:
|
||||
ensureString(candidate.typeComposantId).trim() || 'UNKNOWN',
|
||||
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||
} as ComponentModelStructure['subcomponents'][number];
|
||||
}
|
||||
|
||||
return {
|
||||
familyCode: ensureString(candidate?.name ?? 'UNKNOWN').trim() || 'UNKNOWN',
|
||||
familyCode:
|
||||
ensureString(candidate?.name ?? 'UNKNOWN').trim() || 'UNKNOWN',
|
||||
alias: sanitizeAlias(candidate?.alias ?? candidate?.name),
|
||||
} as ComponentModelStructure['subcomponents'][number];
|
||||
});
|
||||
|
||||
@@ -97,7 +97,9 @@ describe('ComposantsService', () => {
|
||||
});
|
||||
prisma.composant.findMany.mockResolvedValue([]);
|
||||
|
||||
await expect(service.create(dto)).resolves.toMatchObject({ id: 'component-1' });
|
||||
await expect(service.create(dto)).resolves.toMatchObject({
|
||||
id: 'component-1',
|
||||
});
|
||||
|
||||
expect(prisma.composant.create).toHaveBeenCalled();
|
||||
expect(prisma.composant.create.mock.calls[0][0].data.typeComposantId).toBe(
|
||||
@@ -193,7 +195,9 @@ describe('ComposantsService', () => {
|
||||
},
|
||||
});
|
||||
|
||||
prisma.customField.findMany.mockResolvedValue([{ id: 'cf-color', name: 'color' }]);
|
||||
prisma.customField.findMany.mockResolvedValue([
|
||||
{ id: 'cf-color', name: 'color' },
|
||||
]);
|
||||
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||
|
||||
const rootComponent = {
|
||||
|
||||
@@ -20,10 +20,9 @@ type ComponentRequirementWithType =
|
||||
Prisma.TypeMachineComponentRequirementGetPayload<{
|
||||
include: { typeComposant: true };
|
||||
}>;
|
||||
type PieceRequirementWithType =
|
||||
Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
type PieceRequirementWithType = Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
type ModelTypeWithSkeleton = ComponentRequirementWithType['typeComposant'];
|
||||
type PieceTypeWithSkeleton = PieceRequirementWithType['typePiece'];
|
||||
|
||||
@@ -65,112 +64,137 @@ export class ComposantsService {
|
||||
}
|
||||
|
||||
async create(createComposantDto: CreateComposantDto) {
|
||||
const requirementId = createComposantDto.typeMachineComponentRequirementId;
|
||||
const requirementId =
|
||||
createComposantDto.typeMachineComponentRequirementId ?? null;
|
||||
|
||||
let machineId = createComposantDto.machineId;
|
||||
if (requirementId && !createComposantDto.machineId) {
|
||||
throw new BadRequestException(
|
||||
'Un requirement ne peut pas être utilisé sans machine ciblée.',
|
||||
);
|
||||
}
|
||||
|
||||
let machineId = createComposantDto.machineId ?? null;
|
||||
|
||||
if (createComposantDto.parentComposantId) {
|
||||
const parentMachineId = await this.resolveMachineIdFromComposant(
|
||||
createComposantDto.parentComposantId,
|
||||
);
|
||||
|
||||
if (machineId && machineId !== parentMachineId) {
|
||||
if (machineId && parentMachineId && machineId !== parentMachineId) {
|
||||
throw new BadRequestException(
|
||||
'Le composant parent ne correspond pas à la machine ciblée.',
|
||||
);
|
||||
}
|
||||
|
||||
machineId = parentMachineId;
|
||||
machineId = parentMachineId ?? machineId;
|
||||
}
|
||||
|
||||
if (!machineId) {
|
||||
throw new BadRequestException(
|
||||
'Un machineId ou un parentComposantId valide est requis pour créer un composant.',
|
||||
);
|
||||
}
|
||||
let requirement: ComponentRequirementWithType | null = null;
|
||||
let componentRequirements: ComponentRequirementWithType[] = [];
|
||||
let pieceRequirements: PieceRequirementWithType[] = [];
|
||||
|
||||
const machine = await this.prisma.machine.findUnique({
|
||||
where: { id: machineId },
|
||||
include: {
|
||||
typeMachine: {
|
||||
include: {
|
||||
componentRequirements: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
if (machineId) {
|
||||
const machine = await this.prisma.machine.findUnique({
|
||||
where: { id: machineId },
|
||||
include: {
|
||||
typeMachine: {
|
||||
include: {
|
||||
componentRequirements: {
|
||||
include: {
|
||||
typeComposant: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
pieceRequirements: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
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 (!machine || !machine.typeMachine) {
|
||||
throw new BadRequestException(
|
||||
'La machine ciblée doit être associée à un type de machine pour valider les requirements.',
|
||||
);
|
||||
}
|
||||
|
||||
const componentRequirements =
|
||||
(machine.typeMachine.componentRequirements as ComponentRequirementWithType[]) ?? [];
|
||||
const pieceRequirements =
|
||||
(machine.typeMachine.pieceRequirements as PieceRequirementWithType[]) ?? [];
|
||||
componentRequirements =
|
||||
(machine.typeMachine
|
||||
.componentRequirements as ComponentRequirementWithType[]) ?? [];
|
||||
pieceRequirements =
|
||||
(machine.typeMachine.pieceRequirements as PieceRequirementWithType[]) ??
|
||||
[];
|
||||
|
||||
const requirement = componentRequirements.find(
|
||||
(componentRequirement) => componentRequirement.id === requirementId,
|
||||
);
|
||||
if (requirementId) {
|
||||
requirement =
|
||||
componentRequirements.find(
|
||||
(componentRequirement) => componentRequirement.id === requirementId,
|
||||
) ?? null;
|
||||
|
||||
if (!requirement) {
|
||||
throw new BadRequestException(
|
||||
'Le requirement de composant fourni ne correspond pas au squelette de la machine.',
|
||||
);
|
||||
}
|
||||
if (!requirement) {
|
||||
throw new BadRequestException(
|
||||
'Le requirement de composant fourni ne correspond pas au squelette de la machine.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
createComposantDto.typeComposantId &&
|
||||
createComposantDto.typeComposantId !== requirement.typeComposantId
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
'Le type de composant fourni ne correspond pas au requirement pour cette machine.',
|
||||
);
|
||||
if (
|
||||
createComposantDto.typeComposantId &&
|
||||
createComposantDto.typeComposantId !== requirement.typeComposantId
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
'Le type de composant fourni ne correspond pas au requirement pour cette machine.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const typeComposantId =
|
||||
createComposantDto.typeComposantId ?? requirement.typeComposantId;
|
||||
createComposantDto.typeComposantId ??
|
||||
requirement?.typeComposantId ??
|
||||
null;
|
||||
|
||||
const created = await this.prisma.composant.create({
|
||||
data: {
|
||||
...createComposantDto,
|
||||
machineId,
|
||||
typeComposantId,
|
||||
},
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
});
|
||||
|
||||
const componentRequirementUsage = new Map<string, number>();
|
||||
componentRequirementUsage.set(requirement.id, 1);
|
||||
const pieceRequirementUsage = new Map<string, number>();
|
||||
|
||||
await this.populateComponentFromSkeleton({
|
||||
componentId: created.id,
|
||||
componentName: created.name,
|
||||
componentType:
|
||||
(requirement.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
(created.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
null,
|
||||
const data: Prisma.ComposantUncheckedCreateInput = {
|
||||
name: createComposantDto.name,
|
||||
reference: createComposantDto.reference ?? null,
|
||||
constructeurId: createComposantDto.constructeurId ?? null,
|
||||
prix:
|
||||
createComposantDto.prix !== undefined ? createComposantDto.prix : null,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
componentRequirementUsage,
|
||||
pieceRequirementUsage,
|
||||
});
|
||||
parentComposantId: createComposantDto.parentComposantId ?? null,
|
||||
typeComposantId,
|
||||
typeMachineComponentRequirementId:
|
||||
requirement?.id ?? requirementId ?? null,
|
||||
};
|
||||
|
||||
const created = (await this.prisma.composant.create({
|
||||
data,
|
||||
include: COMPONENT_WITH_RELATIONS_INCLUDE,
|
||||
})) as ComposantWithRelations;
|
||||
|
||||
if (machineId && requirement?.id) {
|
||||
const componentRequirementUsage = new Map<string, number>();
|
||||
componentRequirementUsage.set(requirement.id, 1);
|
||||
const pieceRequirementUsage = new Map<string, number>();
|
||||
|
||||
await this.populateComponentFromSkeleton({
|
||||
componentId: created.id,
|
||||
componentName: created.name,
|
||||
componentType:
|
||||
(requirement.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
(created.typeComposant as ModelTypeWithSkeleton | null) ??
|
||||
null,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
componentRequirementUsage,
|
||||
pieceRequirementUsage,
|
||||
});
|
||||
}
|
||||
|
||||
const component = await this.getComponentWithHierarchy(created.id);
|
||||
return (component as ComposantWithRelations | null) ?? (created as ComposantWithRelations);
|
||||
return component ?? created;
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
@@ -225,8 +249,8 @@ export class ComposantsService {
|
||||
pieceRequirementUsage: Map<string, number>;
|
||||
}) {
|
||||
const skeleton = this.parseComponentSkeleton(
|
||||
(componentType as { componentSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||
componentSkeleton,
|
||||
(componentType as { componentSkeleton?: Prisma.JsonValue | null } | null)
|
||||
?.componentSkeleton,
|
||||
);
|
||||
if (!skeleton) {
|
||||
return;
|
||||
@@ -274,15 +298,12 @@ export class ComposantsService {
|
||||
},
|
||||
});
|
||||
|
||||
this.incrementRequirementUsage(
|
||||
componentRequirementUsage,
|
||||
requirement.id,
|
||||
);
|
||||
this.incrementRequirementUsage(componentRequirementUsage, requirement.id);
|
||||
|
||||
await this.populateComponentFromSkeleton({
|
||||
componentId: createdChild.id,
|
||||
componentName: createdChild.name,
|
||||
componentType: requirement.typeComposant as ModelTypeWithSkeleton,
|
||||
componentType: requirement.typeComposant,
|
||||
machineId,
|
||||
componentRequirements,
|
||||
pieceRequirements,
|
||||
@@ -311,7 +332,11 @@ export class ComposantsService {
|
||||
typeComposantId: string | null,
|
||||
customFields: ComponentModelStructure['customFields'],
|
||||
) {
|
||||
if (!typeComposantId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
if (
|
||||
!typeComposantId ||
|
||||
!Array.isArray(customFields) ||
|
||||
customFields.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -324,12 +349,16 @@ export class ComposantsService {
|
||||
return;
|
||||
}
|
||||
|
||||
const definitionMap = new Map(definitions.map((field) => [field.name, field.id]));
|
||||
const definitionMap = new Map(
|
||||
definitions.map((field) => [field.name, field.id]),
|
||||
);
|
||||
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||
where: { composantId: componentId },
|
||||
select: { customFieldId: true },
|
||||
});
|
||||
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||
const existingIds = new Set(
|
||||
existingValues.map((value) => value.customFieldId),
|
||||
);
|
||||
|
||||
for (const field of customFields) {
|
||||
const key = this.normalizeIdentifier(field?.key);
|
||||
@@ -384,7 +413,11 @@ export class ComposantsService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = this.buildPieceName(entry, requirement.typePiece, componentName);
|
||||
const name = this.buildPieceName(
|
||||
entry,
|
||||
requirement.typePiece,
|
||||
componentName,
|
||||
);
|
||||
|
||||
await this.prisma.piece.create({
|
||||
data: {
|
||||
@@ -418,7 +451,9 @@ export class ComposantsService {
|
||||
}
|
||||
|
||||
if (familyCode && requirement.typeComposant?.code) {
|
||||
return this.normalizeCode(requirement.typeComposant.code) === familyCode;
|
||||
return (
|
||||
this.normalizeCode(requirement.typeComposant.code) === familyCode
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -516,7 +551,9 @@ export class ComposantsService {
|
||||
typeComposant: ModelTypeWithSkeleton | null,
|
||||
parentName?: string,
|
||||
): string {
|
||||
const alias = this.normalizeIdentifier((subcomponent as { alias?: string }).alias);
|
||||
const alias = this.normalizeIdentifier(
|
||||
(subcomponent as { alias?: string }).alias,
|
||||
);
|
||||
if (alias) {
|
||||
return alias;
|
||||
}
|
||||
|
||||
@@ -170,9 +170,7 @@ export class CustomFieldsService {
|
||||
}
|
||||
|
||||
// Créer ou mettre à jour une valeur de champ personnalisé
|
||||
async upsertCustomFieldValue(
|
||||
dto: UpsertCustomFieldValueDto,
|
||||
) {
|
||||
async upsertCustomFieldValue(dto: UpsertCustomFieldValueDto) {
|
||||
const {
|
||||
customFieldId: rawCustomFieldId,
|
||||
customFieldName,
|
||||
|
||||
@@ -9,7 +9,9 @@ describe('MachinesController', () => {
|
||||
let controller: MachinesController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||
const mockComposantsService = {
|
||||
create: jest.fn(),
|
||||
} as Partial<ComposantsService>;
|
||||
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
|
||||
@@ -8,7 +8,9 @@ describe('MachinesService', () => {
|
||||
let service: MachinesService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockComposantsService = { create: jest.fn() } as Partial<ComposantsService>;
|
||||
const mockComposantsService = {
|
||||
create: jest.fn(),
|
||||
} as Partial<ComposantsService>;
|
||||
const mockPiecesService = { create: jest.fn() } as Partial<PiecesService>;
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
import { buildComponentHierarchy } from '../common/utils/component-tree.util';
|
||||
import { ComposantsService } from '../composants/composants.service';
|
||||
import { PiecesService } from '../pieces/pieces.service';
|
||||
import { CreateComposantDto } from '../shared/dto/composant.dto';
|
||||
import { CreatePieceDto } from '../shared/dto/piece.dto';
|
||||
|
||||
const CUSTOM_FIELD_SELECT = {
|
||||
id: true,
|
||||
@@ -103,10 +101,9 @@ type ComponentRequirementWithType =
|
||||
include: { typeComposant: true };
|
||||
}>;
|
||||
|
||||
type PieceRequirementWithType =
|
||||
Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
type PieceRequirementWithType = Prisma.TypeMachinePieceRequirementGetPayload<{
|
||||
include: { typePiece: true };
|
||||
}>;
|
||||
|
||||
@Injectable()
|
||||
export class MachinesService {
|
||||
@@ -238,7 +235,10 @@ export class MachinesService {
|
||||
);
|
||||
}
|
||||
|
||||
if (selection.typePieceId && selection.typePieceId !== requirement.typePieceId) {
|
||||
if (
|
||||
selection.typePieceId &&
|
||||
selection.typePieceId !== requirement.typePieceId
|
||||
) {
|
||||
throw new Error(
|
||||
`Le type de pièce sélectionné ne correspond pas au requirement ${requirement.id}.`,
|
||||
);
|
||||
@@ -258,7 +258,9 @@ export class MachinesService {
|
||||
if (selections.length < min) {
|
||||
throw new Error(
|
||||
`Le groupe de composants "${
|
||||
requirement.label || requirement.typeComposant?.name || requirement.id
|
||||
requirement.label ||
|
||||
requirement.typeComposant?.name ||
|
||||
requirement.id
|
||||
}" requiert au moins ${min} sélection(s).`,
|
||||
);
|
||||
}
|
||||
@@ -266,7 +268,9 @@ export class MachinesService {
|
||||
if (max !== undefined && selections.length > max) {
|
||||
throw new Error(
|
||||
`Le groupe de composants "${
|
||||
requirement.label || requirement.typeComposant?.name || requirement.id
|
||||
requirement.label ||
|
||||
requirement.typeComposant?.name ||
|
||||
requirement.id
|
||||
}" ne peut pas dépasser ${max} sélection(s).`,
|
||||
);
|
||||
}
|
||||
@@ -404,86 +408,82 @@ export class MachinesService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async buildComponentCreationDto(
|
||||
private async attachExistingComponentToMachine(
|
||||
machineId: string,
|
||||
requirement: ComponentRequirementWithType,
|
||||
selection: MachineComponentSelectionDto,
|
||||
): Promise<CreateComposantDto> {
|
||||
const definition = this.ensurePlainObject(selection.definition);
|
||||
const dto: CreateComposantDto = {
|
||||
name: this.resolveName(
|
||||
definition.name,
|
||||
requirement.label,
|
||||
requirement.typeComposant?.name,
|
||||
'Composant',
|
||||
),
|
||||
machineId,
|
||||
typeMachineComponentRequirementId: requirement.id,
|
||||
};
|
||||
|
||||
if (selection.typeComposantId) {
|
||||
dto.typeComposantId = selection.typeComposantId;
|
||||
) {
|
||||
const componentId = selection.composantId;
|
||||
if (!componentId) {
|
||||
throw new Error('composantId manquant pour la sélection.');
|
||||
}
|
||||
|
||||
const reference = this.extractString(definition.reference);
|
||||
if (reference) {
|
||||
dto.reference = reference;
|
||||
const component = await this.prisma.composant.findUnique({
|
||||
where: { id: componentId },
|
||||
include: { typeComposant: true },
|
||||
});
|
||||
|
||||
if (!component) {
|
||||
throw new Error(`Composant introuvable (${componentId}).`);
|
||||
}
|
||||
|
||||
const constructeurId = await this.resolveConstructeurId(
|
||||
definition.constructeurId ?? definition.constructeur,
|
||||
);
|
||||
if (constructeurId) {
|
||||
dto.constructeurId = constructeurId;
|
||||
if (
|
||||
requirement.typeComposantId &&
|
||||
component.typeComposantId &&
|
||||
component.typeComposantId !== requirement.typeComposantId
|
||||
) {
|
||||
throw new Error(
|
||||
`Le composant sélectionné (${component.name || component.id}) n'appartient pas à la famille attendue pour ce requirement.`,
|
||||
);
|
||||
}
|
||||
|
||||
const prix = this.normalizePrice(definition.prix);
|
||||
if (prix !== undefined) {
|
||||
dto.prix = prix;
|
||||
}
|
||||
|
||||
return dto;
|
||||
await this.prisma.composant.update({
|
||||
where: { id: component.id },
|
||||
data: {
|
||||
machineId,
|
||||
parentComposantId: null,
|
||||
typeMachineComponentRequirementId: requirement.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async buildPieceCreationDto(
|
||||
private async attachExistingPieceToMachine(
|
||||
machineId: string,
|
||||
requirement: PieceRequirementWithType,
|
||||
selection: MachinePieceSelectionDto,
|
||||
): Promise<CreatePieceDto> {
|
||||
const definition = this.ensurePlainObject(selection.definition);
|
||||
const dto: CreatePieceDto = {
|
||||
name: this.resolveName(
|
||||
definition.name,
|
||||
requirement.label,
|
||||
requirement.typePiece?.name,
|
||||
'Pièce',
|
||||
),
|
||||
machineId,
|
||||
typeMachinePieceRequirementId: requirement.id,
|
||||
};
|
||||
|
||||
if (selection.typePieceId) {
|
||||
dto.typePieceId = selection.typePieceId;
|
||||
) {
|
||||
const pieceId = selection.pieceId;
|
||||
if (!pieceId) {
|
||||
throw new Error('pieceId manquant pour la sélection.');
|
||||
}
|
||||
|
||||
const reference = this.extractString(definition.reference);
|
||||
if (reference) {
|
||||
dto.reference = reference;
|
||||
const piece = await this.prisma.piece.findUnique({
|
||||
where: { id: pieceId },
|
||||
include: { typePiece: true },
|
||||
});
|
||||
|
||||
if (!piece) {
|
||||
throw new Error(`Pièce introuvable (${pieceId}).`);
|
||||
}
|
||||
|
||||
const constructeurId = await this.resolveConstructeurId(
|
||||
definition.constructeurId ?? definition.constructeur,
|
||||
);
|
||||
if (constructeurId) {
|
||||
dto.constructeurId = constructeurId;
|
||||
if (
|
||||
requirement.typePieceId &&
|
||||
piece.typePieceId &&
|
||||
piece.typePieceId !== requirement.typePieceId
|
||||
) {
|
||||
throw new Error(
|
||||
`La pièce sélectionnée (${piece.name || piece.id}) n'appartient pas à la famille attendue pour ce requirement.`,
|
||||
);
|
||||
}
|
||||
|
||||
const prix = this.normalizePrice(definition.prix);
|
||||
if (prix !== undefined) {
|
||||
dto.prix = prix;
|
||||
}
|
||||
|
||||
return dto;
|
||||
await this.prisma.piece.update({
|
||||
where: { id: piece.id },
|
||||
data: {
|
||||
machineId,
|
||||
composantId: null,
|
||||
typeMachinePieceRequirementId: requirement.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async createComponentsForMachine(
|
||||
@@ -500,12 +500,20 @@ export class MachinesService {
|
||||
for (const requirement of requirements) {
|
||||
const selections = selectionMap.get(requirement.id) ?? [];
|
||||
for (const selection of selections) {
|
||||
const dto = await this.buildComponentCreationDto(
|
||||
machineId,
|
||||
requirement,
|
||||
selection,
|
||||
if (selection.composantId) {
|
||||
await this.attachExistingComponentToMachine(
|
||||
machineId,
|
||||
requirement,
|
||||
selection,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Aucun composant existant fourni pour le requirement "${
|
||||
requirement.label || requirement.typeComposant?.name || requirement.id
|
||||
}".`,
|
||||
);
|
||||
await this.composantsService.create(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,12 +532,20 @@ export class MachinesService {
|
||||
for (const requirement of requirements) {
|
||||
const selections = selectionMap.get(requirement.id) ?? [];
|
||||
for (const selection of selections) {
|
||||
const dto = await this.buildPieceCreationDto(
|
||||
machineId,
|
||||
requirement,
|
||||
selection,
|
||||
if (selection.pieceId) {
|
||||
await this.attachExistingPieceToMachine(
|
||||
machineId,
|
||||
requirement,
|
||||
selection,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Aucune pièce existante fournie pour le requirement "${
|
||||
requirement.label || requirement.typePiece?.name || requirement.id
|
||||
}".`,
|
||||
);
|
||||
await this.piecesService.create(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1048,12 +1064,13 @@ export class MachinesService {
|
||||
});
|
||||
|
||||
for (const customField of pieceCustomFields) {
|
||||
const existingValue = await this.prisma.customFieldValue.findFirst({
|
||||
where: {
|
||||
customFieldId: customField.id,
|
||||
pieceId: piece.id,
|
||||
},
|
||||
});
|
||||
const existingValue =
|
||||
await this.prisma.customFieldValue.findFirst({
|
||||
where: {
|
||||
customFieldId: customField.id,
|
||||
pieceId: piece.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingValue) {
|
||||
const providedValue = this.extractCustomFieldValue(
|
||||
@@ -1076,10 +1093,12 @@ export class MachinesService {
|
||||
}
|
||||
|
||||
for (const piece of machine.pieces) {
|
||||
const typePiece = machinePieces.find(
|
||||
(p: any) => p.name === piece.name,
|
||||
);
|
||||
if (typePiece && typePiece.customFields && typePiece.customFields.length > 0) {
|
||||
const typePiece = machinePieces.find((p: any) => p.name === piece.name);
|
||||
if (
|
||||
typePiece &&
|
||||
typePiece.customFields &&
|
||||
typePiece.customFields.length > 0
|
||||
) {
|
||||
const typePieceFields = Array.isArray(typePiece.customFields)
|
||||
? typePiece.customFields
|
||||
: [];
|
||||
|
||||
@@ -25,4 +25,7 @@ export class CreateModelTypeDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
structure?: any;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
@@ -7,6 +8,10 @@ import { ModelType as PrismaModelType, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateModelTypeDto, ModelCategory } from './dto/create-model-type.dto';
|
||||
import { UpdateModelTypeDto } from './dto/update-model-type.dto';
|
||||
import {
|
||||
ComponentModelStructureSchema,
|
||||
PieceModelStructureSchema,
|
||||
} from '../shared/schemas/inventory';
|
||||
|
||||
type SortField = 'name' | 'code' | 'createdAt';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
@@ -31,7 +36,7 @@ export class ModelTypeService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async list(params: ListParams): Promise<{
|
||||
items: PrismaModelType[];
|
||||
items: ReturnType<ModelTypeService['mapModelType']>[];
|
||||
total: number;
|
||||
offset: number;
|
||||
limit: number;
|
||||
@@ -78,41 +83,110 @@ export class ModelTypeService {
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
items: items.map((item) => this.mapModelType(item)),
|
||||
total,
|
||||
offset: safeOffset,
|
||||
limit: cappedLimit,
|
||||
};
|
||||
}
|
||||
|
||||
async create(dto: CreateModelTypeDto): Promise<PrismaModelType> {
|
||||
async create(
|
||||
dto: CreateModelTypeDto,
|
||||
): Promise<ReturnType<ModelTypeService['mapModelType']>> {
|
||||
try {
|
||||
return await this.prisma.modelType.create({
|
||||
data: {
|
||||
name: dto.name,
|
||||
code: dto.code,
|
||||
category: dto.category,
|
||||
notes: dto.notes,
|
||||
description: dto.description ?? null,
|
||||
},
|
||||
});
|
||||
const { structure, ...rest } = dto;
|
||||
|
||||
const data: Prisma.ModelTypeCreateInput = {
|
||||
name: rest.name,
|
||||
code: rest.code,
|
||||
category: rest.category,
|
||||
notes: rest.notes,
|
||||
description: rest.description ?? null,
|
||||
};
|
||||
|
||||
const normalizedStructure = this.normalizeStructure(
|
||||
rest.category,
|
||||
structure,
|
||||
);
|
||||
|
||||
if (normalizedStructure !== undefined) {
|
||||
const skeletonValue =
|
||||
normalizedStructure === null ? Prisma.JsonNull : normalizedStructure;
|
||||
if (rest.category === ModelCategory.COMPONENT) {
|
||||
data.componentSkeleton = skeletonValue;
|
||||
data.pieceSkeleton = Prisma.JsonNull;
|
||||
} else {
|
||||
data.pieceSkeleton = skeletonValue;
|
||||
data.componentSkeleton = Prisma.JsonNull;
|
||||
}
|
||||
}
|
||||
|
||||
const created = await this.prisma.modelType.create({ data });
|
||||
return this.mapModelType(created);
|
||||
} catch (error) {
|
||||
this.handlePrismaError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateModelTypeDto): Promise<PrismaModelType> {
|
||||
async update(
|
||||
id: string,
|
||||
dto: UpdateModelTypeDto,
|
||||
): Promise<ReturnType<ModelTypeService['mapModelType']>> {
|
||||
try {
|
||||
return await this.prisma.modelType.update({
|
||||
const existing = await this.prisma.modelType.findUnique({
|
||||
where: { id },
|
||||
data: {
|
||||
...dto,
|
||||
description:
|
||||
dto.description === undefined
|
||||
? undefined
|
||||
: (dto.description ?? null),
|
||||
},
|
||||
});
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Type de modèle introuvable.');
|
||||
}
|
||||
|
||||
const targetCategory =
|
||||
dto.category ?? (existing.category as ModelCategory);
|
||||
|
||||
const data: Prisma.ModelTypeUpdateInput = {};
|
||||
|
||||
if (dto.name !== undefined) {
|
||||
data.name = dto.name;
|
||||
}
|
||||
|
||||
if (dto.code !== undefined) {
|
||||
data.code = dto.code;
|
||||
}
|
||||
|
||||
if (dto.category !== undefined) {
|
||||
data.category = dto.category;
|
||||
}
|
||||
|
||||
if (dto.notes !== undefined) {
|
||||
data.notes = dto.notes;
|
||||
}
|
||||
|
||||
data.description =
|
||||
dto.description === undefined ? undefined : (dto.description ?? null);
|
||||
|
||||
const normalizedStructure = this.normalizeStructure(
|
||||
targetCategory,
|
||||
dto.structure,
|
||||
);
|
||||
|
||||
if (normalizedStructure !== undefined) {
|
||||
const skeletonValue =
|
||||
normalizedStructure === null ? Prisma.JsonNull : normalizedStructure;
|
||||
if (targetCategory === ModelCategory.COMPONENT) {
|
||||
data.componentSkeleton = skeletonValue;
|
||||
data.pieceSkeleton = Prisma.JsonNull;
|
||||
} else {
|
||||
data.pieceSkeleton = skeletonValue;
|
||||
data.componentSkeleton = Prisma.JsonNull;
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await this.prisma.modelType.update({
|
||||
where: { id },
|
||||
data,
|
||||
});
|
||||
|
||||
return this.mapModelType(updated);
|
||||
} catch (error) {
|
||||
this.handlePrismaError(error);
|
||||
}
|
||||
@@ -126,12 +200,14 @@ export class ModelTypeService {
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<PrismaModelType> {
|
||||
async findOne(
|
||||
id: string,
|
||||
): Promise<ReturnType<ModelTypeService['mapModelType']>> {
|
||||
const modelType = await this.prisma.modelType.findUnique({ where: { id } });
|
||||
if (!modelType) {
|
||||
throw new NotFoundException('Type de modèle introuvable.');
|
||||
}
|
||||
return modelType;
|
||||
return this.mapModelType(modelType);
|
||||
}
|
||||
|
||||
private handlePrismaError(error: unknown): never {
|
||||
@@ -158,4 +234,44 @@ export class ModelTypeService {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private normalizeStructure(
|
||||
category: ModelCategory,
|
||||
structure: unknown,
|
||||
): Prisma.InputJsonValue | null | undefined {
|
||||
if (structure === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (structure === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (category === ModelCategory.COMPONENT) {
|
||||
return ComponentModelStructureSchema.parse(
|
||||
structure,
|
||||
) as Prisma.InputJsonValue;
|
||||
}
|
||||
return PieceModelStructureSchema.parse(
|
||||
structure,
|
||||
) as Prisma.InputJsonValue;
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Structure invalide.';
|
||||
throw new BadRequestException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private mapModelType(modelType: PrismaModelType) {
|
||||
const structure =
|
||||
modelType.category === ModelCategory.COMPONENT
|
||||
? (modelType.componentSkeleton ?? null)
|
||||
: (modelType.pieceSkeleton ?? null);
|
||||
|
||||
return {
|
||||
...modelType,
|
||||
structure,
|
||||
} as PrismaModelType & { structure: Prisma.InputJsonValue | null };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,7 @@ describe('PiecesService', () => {
|
||||
|
||||
prisma.customField.findMany
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([
|
||||
{ id: 'field-1', name: 'Numéro de série' },
|
||||
]);
|
||||
.mockResolvedValueOnce([{ id: 'field-1', name: 'Numéro de série' }]);
|
||||
prisma.customField.create.mockResolvedValue({ id: 'field-1' });
|
||||
prisma.customFieldValue.findMany.mockResolvedValue([]);
|
||||
prisma.customFieldValue.create.mockResolvedValue({
|
||||
|
||||
@@ -36,9 +36,15 @@ export class PiecesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(createPieceDto: CreatePieceDto) {
|
||||
const requirementId = createPieceDto.typeMachinePieceRequirementId;
|
||||
const requirementId = createPieceDto.typeMachinePieceRequirementId ?? null;
|
||||
|
||||
let machineId = createPieceDto.machineId;
|
||||
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(
|
||||
@@ -51,59 +57,69 @@ export class PiecesService {
|
||||
);
|
||||
}
|
||||
|
||||
machineId = composantMachineId;
|
||||
machineId = composantMachineId ?? machineId;
|
||||
}
|
||||
|
||||
if (!machineId) {
|
||||
throw new BadRequestException(
|
||||
'Un machineId ou un composantId valide est requis pour créer une pièce.',
|
||||
);
|
||||
}
|
||||
let requirement: PieceRequirementWithType | null = null;
|
||||
|
||||
const machine = await this.prisma.machine.findUnique({
|
||||
where: { id: machineId },
|
||||
include: {
|
||||
typeMachine: {
|
||||
include: {
|
||||
pieceRequirements: {
|
||||
include: {
|
||||
typePiece: true,
|
||||
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 (!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 requirement = machine.typeMachine.pieceRequirements.find(
|
||||
(pieceRequirement) => pieceRequirement.id === requirementId,
|
||||
);
|
||||
const typePieceId =
|
||||
createPieceDto.typePieceId ?? requirement?.typePieceId ?? 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 data = {
|
||||
...createPieceDto,
|
||||
const data: Prisma.PieceUncheckedCreateInput = {
|
||||
name: createPieceDto.name,
|
||||
reference: createPieceDto.reference ?? null,
|
||||
constructeurId: createPieceDto.constructeurId ?? null,
|
||||
prix: createPieceDto.prix !== undefined ? createPieceDto.prix : null,
|
||||
machineId,
|
||||
typePieceId: createPieceDto.typePieceId ?? requirement.typePieceId,
|
||||
composantId: createPieceDto.composantId ?? null,
|
||||
typePieceId,
|
||||
typeMachinePieceRequirementId: requirement?.id ?? requirementId ?? null,
|
||||
};
|
||||
|
||||
const created = await this.prisma.piece.create({
|
||||
@@ -113,10 +129,7 @@ export class PiecesService {
|
||||
|
||||
await this.applyPieceSkeleton({
|
||||
pieceId: created.id,
|
||||
typePiece:
|
||||
(requirement.typePiece as PieceTypeWithSkeleton | null) ??
|
||||
(created.typePiece as PieceTypeWithSkeleton | null) ??
|
||||
null,
|
||||
typePiece: created.typePiece as PieceTypeWithSkeleton | null,
|
||||
});
|
||||
|
||||
return this.prisma.piece.findUnique({
|
||||
@@ -217,8 +230,8 @@ export class PiecesService {
|
||||
}
|
||||
|
||||
const skeleton = this.parsePieceSkeleton(
|
||||
(typePiece as { pieceSkeleton?: Prisma.JsonValue | null } | null)?.
|
||||
pieceSkeleton,
|
||||
(typePiece as { pieceSkeleton?: Prisma.JsonValue | null } | null)
|
||||
?.pieceSkeleton,
|
||||
);
|
||||
|
||||
if (!skeleton) {
|
||||
@@ -227,10 +240,7 @@ export class PiecesService {
|
||||
|
||||
const customFields = skeleton.customFields ?? [];
|
||||
|
||||
await this.ensurePieceCustomFieldDefinitions(
|
||||
typePiece.id,
|
||||
customFields,
|
||||
);
|
||||
await this.ensurePieceCustomFieldDefinitions(typePiece.id, customFields);
|
||||
|
||||
await this.createPieceCustomFieldValues(
|
||||
pieceId,
|
||||
@@ -255,7 +265,11 @@ export class PiecesService {
|
||||
typePieceId: string,
|
||||
customFields: PieceModelStructure['customFields'],
|
||||
) {
|
||||
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
if (
|
||||
!typePieceId ||
|
||||
!Array.isArray(customFields) ||
|
||||
customFields.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +279,10 @@ export class PiecesService {
|
||||
});
|
||||
|
||||
const existingByName = new Map(
|
||||
existing.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||
existing.map((field) => [
|
||||
this.normalizeIdentifier(field.name) ?? field.name,
|
||||
field.id,
|
||||
]),
|
||||
);
|
||||
|
||||
for (const field of customFields) {
|
||||
@@ -306,7 +323,11 @@ export class PiecesService {
|
||||
typePieceId: string,
|
||||
customFields: PieceModelStructure['customFields'],
|
||||
) {
|
||||
if (!typePieceId || !Array.isArray(customFields) || customFields.length === 0) {
|
||||
if (
|
||||
!typePieceId ||
|
||||
!Array.isArray(customFields) ||
|
||||
customFields.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -320,7 +341,10 @@ export class PiecesService {
|
||||
}
|
||||
|
||||
const definitionMap = new Map(
|
||||
definitions.map((field) => [this.normalizeIdentifier(field.name) ?? field.name, field.id]),
|
||||
definitions.map((field) => [
|
||||
this.normalizeIdentifier(field.name) ?? field.name,
|
||||
field.id,
|
||||
]),
|
||||
);
|
||||
|
||||
const existingValues = await this.prisma.customFieldValue.findMany({
|
||||
@@ -328,7 +352,9 @@ export class PiecesService {
|
||||
select: { customFieldId: true },
|
||||
});
|
||||
|
||||
const existingIds = new Set(existingValues.map((value) => value.customFieldId));
|
||||
const existingIds = new Set(
|
||||
existingValues.map((value) => value.customFieldId),
|
||||
);
|
||||
|
||||
for (const field of customFields) {
|
||||
if (!field) {
|
||||
@@ -363,9 +389,7 @@ export class PiecesService {
|
||||
const rawOptions = field?.options;
|
||||
if (Array.isArray(rawOptions)) {
|
||||
const normalized = rawOptions
|
||||
.map((option) =>
|
||||
typeof option === 'string' ? option.trim() : '',
|
||||
)
|
||||
.map((option) => (typeof option === 'string' ? option.trim() : ''))
|
||||
.filter((option) => option.length > 0);
|
||||
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { IsString, IsOptional, IsNumber, ValidateIf } from 'class-validator';
|
||||
import { IsString, IsOptional, IsNumber } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class CreateComposantDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ValidateIf((dto) => !dto.parentComposantId)
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
machineId?: string;
|
||||
|
||||
@ValidateIf((dto) => !dto.machineId)
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
parentComposantId?: string;
|
||||
|
||||
@@ -30,8 +30,9 @@ export class CreateComposantDto {
|
||||
@IsString()
|
||||
typeComposantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typeMachineComponentRequirementId: string;
|
||||
typeMachineComponentRequirementId?: string;
|
||||
}
|
||||
|
||||
export class UpdateComposantDto {
|
||||
|
||||
@@ -10,6 +10,10 @@ export class MachineComponentSelectionDto {
|
||||
@IsString()
|
||||
typeComposantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composantId?: string;
|
||||
|
||||
@IsOptional()
|
||||
definition?: any;
|
||||
}
|
||||
@@ -22,6 +26,10 @@ export class MachinePieceSelectionDto {
|
||||
@IsString()
|
||||
typePieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
definition?: any;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { IsString, IsOptional, IsNumber, ValidateIf } from 'class-validator';
|
||||
import { IsString, IsOptional, IsNumber } from 'class-validator';
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
export class CreatePieceDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@ValidateIf((dto) => !dto.composantId)
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
machineId?: string;
|
||||
|
||||
@ValidateIf((dto) => !dto.machineId)
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
composantId?: string;
|
||||
|
||||
@@ -30,8 +30,9 @@ export class CreatePieceDto {
|
||||
@IsString()
|
||||
typePieceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
typeMachinePieceRequirementId: string;
|
||||
typeMachinePieceRequirementId?: string;
|
||||
}
|
||||
|
||||
export class UpdatePieceDto {
|
||||
|
||||
@@ -258,4 +258,3 @@ export class UpdateTypePieceDto {
|
||||
@IsObject()
|
||||
structure?: PieceModelStructure;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Patch,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { TypesService } from './types.service';
|
||||
import {
|
||||
CreateTypeMachineDto,
|
||||
|
||||
Reference in New Issue
Block a user