feat: auto populate machine structures and seed sample data

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

550
scripts/seed-sample-data.ts Normal file
View File

@@ -0,0 +1,550 @@
import { PrismaClient, Prisma } from '@prisma/client';
const prisma = new PrismaClient();
type CreatedFields = Record<string, string>;
async function deleteExistingData() {
await prisma.machineComponentLink.deleteMany();
await prisma.machinePieceLink.deleteMany();
await prisma.machine.deleteMany();
await prisma.customFieldValue.deleteMany();
await prisma.composant.deleteMany();
await prisma.piece.deleteMany();
await prisma.modelType.deleteMany({
where: {
code: {
in: [
'hydraulic-pump',
'hydraulic-reservoir',
'cooling-fan',
'cooling-module',
'structural-frame',
'hydraulic-power-unit',
],
},
},
});
}
async function createPieceType(
name: string,
code: string,
description: string,
fields: Array<{
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}>,
skeleton?: Record<string, unknown>,
) {
const type = await prisma.modelType.create({
data: {
name,
code,
category: 'PIECE',
description,
pieceSkeleton: skeleton
? (skeleton as Prisma.InputJsonValue)
: Prisma.JsonNull,
pieceCustomFields: {
create: fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options ?? [],
})),
},
},
});
const customFields = await prisma.customField.findMany({
where: { typePieceId: type.id },
});
const fieldMap: CreatedFields = {};
customFields.forEach((field) => {
fieldMap[field.name] = field.id;
});
return { type, fieldMap };
}
async function createComponentType(
name: string,
code: string,
description: string,
fields: Array<{
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}>,
skeleton?: Record<string, unknown>,
) {
const type = await prisma.modelType.create({
data: {
name,
code,
category: 'COMPONENT',
description,
componentSkeleton: skeleton
? (skeleton as Prisma.InputJsonValue)
: Prisma.JsonNull,
customFields: {
create: fields.map((field) => ({
name: field.name,
type: field.type,
required: field.required ?? false,
options: field.options ?? [],
})),
},
},
});
const customFields = await prisma.customField.findMany({
where: { typeComposantId: type.id },
});
const fieldMap: CreatedFields = {};
customFields.forEach((field) => {
fieldMap[field.name] = field.id;
});
return { type, fieldMap };
}
async function createPiece(options: {
name: string;
reference: string;
price: number;
constructeurId?: string | null;
typeId: string;
fieldValues: Record<string, string>;
}) {
const customFields = await prisma.customField.findMany({
where: { typePieceId: options.typeId },
});
const customFieldValues = Object.entries(options.fieldValues).map(
([fieldName, value]) => {
const target = customFields.find((field) => field.name === fieldName);
if (!target) {
throw new Error(
`Custom field "${fieldName}" introuvable pour le type de pièce ${options.typeId}`,
);
}
return {
value,
customFieldId: target.id,
};
},
);
return prisma.piece.create({
data: {
name: options.name,
reference: options.reference,
prix: new Prisma.Decimal(options.price),
typePieceId: options.typeId,
constructeurId: options.constructeurId ?? null,
customFieldValues: {
create: customFieldValues,
},
},
});
}
async function createComponent(options: {
name: string;
reference: string;
price: number;
constructeurId?: string | null;
typeId: string;
fieldValues: Record<string, string>;
structure?: Prisma.InputJsonValue;
}) {
const customFields = await prisma.customField.findMany({
where: { typeComposantId: options.typeId },
});
const customFieldValues = Object.entries(options.fieldValues).map(
([fieldName, value]) => {
const target = customFields.find((field) => field.name === fieldName);
if (!target) {
throw new Error(
`Custom field "${fieldName}" introuvable pour le type de composant ${options.typeId}`,
);
}
return {
value,
customFieldId: target.id,
};
},
);
return prisma.composant.create({
data: {
name: options.name,
reference: options.reference,
prix: new Prisma.Decimal(options.price),
typeComposantId: options.typeId,
constructeurId: options.constructeurId ?? null,
structure:
options.structure === undefined
? Prisma.JsonNull
: options.structure ?? Prisma.JsonNull,
customFieldValues: {
create: customFieldValues,
},
},
});
}
async function main() {
console.log('Nettoyage des données existantes…');
await deleteExistingData();
console.log('Création des types de pièces…');
const pumpPieceFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Pression nominale (bar)', type: 'number', required: true },
{ name: 'Débit nominal (L/min)', type: 'number', required: true },
{ name: 'Puissance (kW)', type: 'number', required: true },
{ name: 'Indice de protection', type: 'text' },
{ name: 'Vitesse max (rpm)', type: 'number' },
] as const;
const pumpPieceType = await createPieceType(
'Pompe hydraulique',
'hydraulic-pump',
'Pompes à pistons pour unités hydrauliques',
pumpPieceFields,
{
customFields: pumpPieceFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
},
);
const reservoirPieceFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Pression nominale (bar)', type: 'number', required: true },
{ name: 'Débit nominal (L/min)', type: 'number', required: true },
{ name: 'Capacité (L)', type: 'number', required: true },
{ name: 'Type dhuile', type: 'text', required: true },
{ name: 'Filtration', type: 'text' },
] as const;
const reservoirPieceType = await createPieceType(
'Réservoir hydraulique',
'hydraulic-reservoir',
'Réservoirs haute capacité pour fluide hydraulique',
reservoirPieceFields,
{
customFields: reservoirPieceFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
},
);
const fanPieceFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Diamètre (mm)', type: 'number', required: true },
{ name: 'Débit air (m³/h)', type: 'number', required: true },
{ name: 'Consommation (A)', type: 'number' },
{ name: 'Tension (V)', type: 'number', required: true },
{ name: 'Niveau sonore (dB)', type: 'number' },
] as const;
const fanPieceType = await createPieceType(
'Ventilateur de refroidissement',
'cooling-fan',
'Ventilateurs axiaux pour modules de refroidissement',
fanPieceFields,
{
customFields: fanPieceFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
},
);
console.log('Création des pièces…');
const pumpPiece = await createPiece({
name: 'Pompe à pistons PX-300',
reference: 'PX-300',
price: 1850,
typeId: pumpPieceType.type.id,
fieldValues: {
'Pression nominale (bar)': '180',
'Débit nominal (L/min)': '260',
'Puissance (kW)': '45',
'Indice de protection': 'IP55',
'Vitesse max (rpm)': '3200',
},
});
const reservoirPiece = await createPiece({
name: 'Réservoir 120L Inox',
reference: 'RES-120-INX',
price: 720,
typeId: reservoirPieceType.type.id,
fieldValues: {
'Pression nominale (bar)': '12',
'Débit nominal (L/min)': '280',
'Capacité (L)': '120',
'Type dhuile': 'HLP46',
Filtration: '10µ absolu',
},
});
const fanPiece = await createPiece({
name: 'Ventilateur axial VE-450',
reference: 'VE-450',
price: 390,
typeId: fanPieceType.type.id,
fieldValues: {
'Diamètre (mm)': '450',
'Débit air (m³/h)': '5200',
'Consommation (A)': '3.2',
'Tension (V)': '400',
'Niveau sonore (dB)': '68',
},
});
console.log('Création des types de composants…');
const coolingComponentFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Type de fluide', type: 'text', required: true },
{ name: 'Puissance thermique (kW)', type: 'number', required: true },
{ name: 'Température max (°C)', type: 'number' },
{ name: 'Débit eau (L/min)', type: 'number' },
];
const coolingComponentType = await createComponentType(
'Module de refroidissement',
'cooling-module',
'Modules compacts pour dissipation thermique du circuit hydraulique',
coolingComponentFields,
{
customFields: coolingComponentFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
pieces: [
{
typePieceId: fanPieceType.type.id,
role: 'Ventilation principale',
},
],
subcomponents: [],
},
);
const frameComponentFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Matière', type: 'text', required: true },
{ name: 'Charge admissible (kg)', type: 'number', required: true },
{ name: 'Revêtement', type: 'text' },
{ name: 'Points de levage', type: 'number' },
];
const frameComponentType = await createComponentType(
'Châssis structurel',
'structural-frame',
'Châssis mécano-soudé pour unités hydrauliques',
frameComponentFields,
{
customFields: frameComponentFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
pieces: [],
subcomponents: [],
},
);
const powerUnitComponentFields: {
name: string;
type: 'text' | 'number' | 'select' | 'boolean' | 'date';
required?: boolean;
options?: string[];
}[] = [
{ name: 'Pression service (bar)', type: 'number', required: true },
{ name: 'Débit maxi (L/min)', type: 'number', required: true },
{ name: 'Niveau sonore (dB)', type: 'number' },
{ name: 'Indice de protection', type: 'text' },
];
const powerUnitComponentType = await createComponentType(
'Centrale hydraulique',
'hydraulic-power-unit',
'Unités hydrauliques complètes pour machines industrielles',
powerUnitComponentFields,
{
customFields: powerUnitComponentFields.map((field) => ({
name: field.name,
key: field.name,
type: field.type,
required: field.required ?? false,
})),
pieces: [
{
typePieceId: pumpPieceType.type.id,
role: 'Pompe principale',
},
{
typePieceId: reservoirPieceType.type.id,
role: 'Réservoir dhuile',
},
],
subcomponents: [
{
typeComposantId: coolingComponentType.type.id,
alias: 'Module de refroidissement',
},
{
typeComposantId: frameComponentType.type.id,
alias: 'Châssis structurel',
},
],
},
);
console.log('Création des composants (sous-ensembles)…');
const coolingModule = await createComponent({
name: 'Module de refroidissement AquaCool 50',
reference: 'AC-50',
price: 2450,
typeId: coolingComponentType.type.id,
fieldValues: {
'Type de fluide': 'Huile minérale',
'Puissance thermique (kW)': '35',
'Température max (°C)': '85',
'Débit eau (L/min)': '65',
},
structure: {
path: 'root',
pieces: [
{
path: 'root:piece-0',
definition: {
typePieceId: fanPieceType.type.id,
},
selectedPieceId: fanPiece.id,
},
],
subcomponents: [],
} as Prisma.InputJsonValue,
});
const structuralFrame = await createComponent({
name: 'Châssis structurel XC-800',
reference: 'FRAME-XC800',
price: 1280,
typeId: frameComponentType.type.id,
fieldValues: {
Matière: 'Acier S355',
'Charge admissible (kg)': '1800',
Revêtement: 'Peinture epoxy',
'Points de levage': '4',
},
});
console.log('Création du composant principal…');
await createComponent({
name: 'Centrale hydraulique HX-500',
reference: 'HX-500',
price: 12900,
typeId: powerUnitComponentType.type.id,
fieldValues: {
'Pression service (bar)': '210',
'Débit maxi (L/min)': '320',
'Niveau sonore (dB)': '72',
'Indice de protection': 'IP54',
},
structure: {
path: 'root',
pieces: [
{
path: 'root:piece-0',
definition: {
typePieceId: pumpPieceType.type.id,
},
selectedPieceId: pumpPiece.id,
},
{
path: 'root:piece-1',
definition: {
typePieceId: reservoirPieceType.type.id,
},
selectedPieceId: reservoirPiece.id,
},
],
subcomponents: [
{
path: 'root:sub-0',
definition: {
alias: 'Module de refroidissement',
typeComposantId: coolingComponentType.type.id,
},
selectedComponentId: coolingModule.id,
},
{
path: 'root:sub-1',
definition: {
alias: 'Châssis structurel',
typeComposantId: frameComponentType.type.id,
},
selectedComponentId: structuralFrame.id,
},
],
} as Prisma.InputJsonValue,
});
console.log('Population terminée ✅');
}
main()
.catch((error) => {
console.error(error);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});