feat: auto populate machine structures and seed sample data
This commit is contained in:
@@ -1,42 +0,0 @@
|
||||
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()
|
||||
@@ -1,593 +0,0 @@
|
||||
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();
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
550
scripts/seed-sample-data.ts
Normal file
550
scripts/seed-sample-data.ts
Normal 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 d’huile', 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 d’huile': '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 d’huile',
|
||||
},
|
||||
],
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user