diff --git a/README.md b/README.md index 2633871..b4533f3 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,26 @@ npm run start:dev L'API sera disponible sur `http://localhost:3000/api` +## 🌾 Données de démonstration (usine de triage de céréales) + +Le script `npm run seed:demo` permet de remplir la base avec un jeu de données complet représentant une ligne de triage et de séchage de céréales. Il supprime toutes les données existantes (machines, composants, pièces, champs personnalisés, modèles, types...) tout en conservant les sites et profils déjà enregistrés. + +### Contenu généré + +- Catégories de composants et de pièces avec champs personnalisés adaptés (élévateurs à godets, convoyeurs à bande, table densimétrique, séchoir, vis sans fin, benne peseuse, armoire de contrôle, Manitou, etc.). +- Modèles de composants multi-niveaux et modèles de pièces cohérents (moteurs, capteurs, courroies, roulements, réducteurs, capteurs de pesage, sondes PT100...). +- Type de machine « Ligne de triage et séchage céréales 120 t/h » incluant exigences de composants/pièces, pièces critiques et champs personnalisés. +- Machine réelle "Ligne de triage Valgrain 2024" avec hiérarchie complète de composants, sous-composants, pièces associées et valeurs de champs personnalisés. +- Pièces de réserve répondant aux exigences de stock (moteur de secours, capteur de vitesse). + +### Exécution + +```bash +npm run seed:demo +``` + +Assurez-vous que la variable d'environnement `DATABASE_URL` pointe vers votre base PostgreSQL avant l'exécution. Le script utilise Prisma et se termine automatiquement en cas d'erreur. + ## 📊 Structure de la base de données ### Entités principales diff --git a/package.json b/package.json index 5629107..d8a3d79 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json" + "test:e2e": "jest --config ./test/jest-e2e.json", + "seed:demo": "ts-node --transpile-only scripts/seed-industrial-data.ts" }, "dependencies": { "@nestjs/common": "^11.0.1", diff --git a/scripts/seed-industrial-data.ts b/scripts/seed-industrial-data.ts new file mode 100644 index 0000000..e979a1b --- /dev/null +++ b/scripts/seed-industrial-data.ts @@ -0,0 +1,1867 @@ +import { PrismaClient, Prisma, ModelCategory } from '@prisma/client'; + +const prisma = new PrismaClient(); + +type CustomFieldSpec = { + name: string; + type: 'string' | 'number' | 'boolean' | 'date'; + required?: boolean; + defaultValue?: string; + options?: string[]; +}; + +type ComponentTypeDefinition = { + code: string; + name: string; + description: string; + customFields: CustomFieldSpec[]; +}; + +type PieceTypeDefinition = { + code: string; + name: string; + description: string; + customFields: CustomFieldSpec[]; +}; + +type PieceModelDefinition = { + code: string; + name: string; + description: string; + typeCode: string; + structure?: Prisma.InputJsonValue; +}; + +type ComponentModelDefinition = { + code: string; + name: string; + description: string; + typeCode: string; + structure?: Prisma.InputJsonValue; +}; + +type ConstructeurDefinition = { + key: string; + name: string; + email?: string; + phone?: string; +}; + +type ComponentPieceInstance = { + name: string; + reference?: string; + prix?: string; + typeCode: string; + modelCode: string; + constructeur?: string; + customValues?: Record; +}; + +type ComponentInstance = { + name: string; + reference?: string; + prix?: string; + typeCode: string; + requirementLabel?: string; + modelCode: string; + constructeur?: string; + customValues?: Record; + pieces?: ComponentPieceInstance[]; + children?: ComponentInstance[]; +}; + +const componentTypeDefinitions: ComponentTypeDefinition[] = [ + { + code: 'bucket-elevator', + name: 'Élévateur à godets', + description: + "Élévateur vertical utilisé pour monter les grains vers les étages supérieurs de l'usine.", + customFields: [ + { name: 'Débit nominal (t/h)', type: 'number', required: true }, + { name: 'Hauteur de levage (m)', type: 'number' }, + { name: 'Matériau des godets', type: 'string', defaultValue: 'Acier galvanisé' }, + ], + }, + { + code: 'belt-conveyor', + name: 'Convoyeur à bande', + description: + 'Convoyeur horizontal ou incliné assurant le transfert des grains entre les postes.', + customFields: [ + { name: 'Largeur de bande (mm)', type: 'number', required: true }, + { name: 'Longueur (m)', type: 'number' }, + { name: 'Type de bande', type: 'string', defaultValue: 'Caoutchouc anti-statique' }, + ], + }, + { + code: 'gravity-separator', + name: 'Table densimétrique', + description: + 'Séparateur gravimétrique permettant de retirer les impuretés lourdes ou légères.', + customFields: [ + { name: 'Capacité de tri (t/h)', type: 'number', required: true }, + { name: 'Fréquence de vibration (Hz)', type: 'number' }, + { name: 'Type de plateau', type: 'string', defaultValue: 'Acier perforé' }, + ], + }, + { + code: 'grain-dryer', + name: 'Séchoir à grains', + description: + 'Séchoir continu assurant la réduction du taux d\'humidité des céréales.', + customFields: [ + { name: 'Capacité sèche (t/h)', type: 'number', required: true }, + { name: "Nombre d'étages", type: 'number' }, + { name: "Type d'énergie", type: 'string', defaultValue: 'Gaz naturel' }, + ], + }, + { + code: 'screw-conveyor', + name: 'Vis sans fin', + description: 'Vis de transfert pour la reprise des grains et le rechargement des silos.', + customFields: [ + { name: 'Diamètre vis (mm)', type: 'number', required: true }, + { name: 'Inclinaison (°)', type: 'number' }, + { name: 'Vitesse (rpm)', type: 'number' }, + ], + }, + { + code: 'weigh-hopper', + name: 'Benna peseuse', + description: 'Benna équipée de capteurs de pesage pour le chargement précis des camions.', + customFields: [ + { name: 'Capacité de pesée (kg)', type: 'number', required: true }, + { name: 'Précision (%)', type: 'number' }, + { name: 'Nombre de capteurs', type: 'number', defaultValue: '4' }, + ], + }, + { + code: 'control-panel', + name: 'Armoire de contrôle', + description: 'Armoire électrique pilotant l\'ensemble de la ligne de triage.', + customFields: [ + { name: 'Automate principal', type: 'string', required: true }, + { name: 'Année de mise à jour', type: 'number' }, + { name: "Indice de protection", type: 'string', defaultValue: 'IP55' }, + ], + }, + { + code: 'telehandler', + name: 'Chariot télescopique', + description: + 'Manitou utilisé pour les manutentions ponctuelles et la gestion des sacs big-bags.', + customFields: [ + { name: 'Capacité de levage (t)', type: 'number', required: true }, + { name: 'Hauteur de levage (m)', type: 'number' }, + { name: "Type d'attache", type: 'string', defaultValue: 'Fourches FEM' }, + ], + }, + { + code: 'burner-module', + name: 'Module brûleur', + description: 'Module de combustion alimentant le séchoir en air chaud.', + customFields: [ + { name: 'Puissance thermique (kW)', type: 'number', required: true }, + { name: 'Type de carburant', type: 'string', defaultValue: 'Gaz naturel' }, + { name: "Système d'allumage", type: 'string', defaultValue: 'Double électrode' }, + ], + }, + { + code: 'dust-filter', + name: 'Filtre à poussière', + description: 'Filtre à cyclone captant les poussières en sortie de séchoir.', + customFields: [ + { name: 'Efficacité de filtration (%)', type: 'number', required: true }, + { name: 'Type de média filtrant', type: 'string' }, + { name: 'Nombre de cartouches', type: 'number' }, + ], + }, +]; + +const pieceTypeDefinitions: PieceTypeDefinition[] = [ + { + code: 'electric-motor', + name: 'Moteur électrique', + description: 'Motorisation asynchrone IE3 pour entraînements industriels.', + customFields: [ + { name: 'Puissance nominale (kW)', type: 'number', required: true }, + { name: 'Tension (V)', type: 'number' }, + { name: "Indice de protection", type: 'string', defaultValue: 'IP55' }, + ], + }, + { + code: 'speed-sensor', + name: 'Capteur de vitesse', + description: 'Capteur inductif contrôlant la vitesse des organes rotatifs.', + customFields: [ + { name: 'Type de sortie', type: 'string', required: true }, + { name: 'Plage de mesure (rpm)', type: 'number' }, + ], + }, + { + code: 'temperature-probe', + name: 'Sonde de température', + description: 'Sonde PT100 pour la surveillance thermique du séchoir.', + customFields: [ + { name: 'Plage de mesure (°C)', type: 'string', required: true }, + { name: 'Type de sonde', type: 'string', defaultValue: 'PT100' }, + ], + }, + { + code: 'belt', + name: 'Bande transporteuse', + description: 'Bande caoutchouc renforcée pour convoyeurs et élévateurs.', + customFields: [ + { name: 'Largeur (mm)', type: 'number', required: true }, + { name: 'Matériau', type: 'string' }, + ], + }, + { + code: 'bearing', + name: 'Roulement', + description: 'Roulement à semelle pour transmissions tournantes.', + customFields: [ + { name: 'Référence fournisseur', type: 'string', required: true }, + { name: "Type d'étanchéité", type: 'string' }, + ], + }, + { + code: 'gearbox', + name: 'Réducteur', + description: 'Réducteur de vitesse pour entraînement lourd.', + customFields: [ + { name: 'Rapport de réduction', type: 'string', required: true }, + { name: 'Couple nominal (Nm)', type: 'number' }, + ], + }, + { + code: 'air-filter', + name: 'Filtre à air', + description: 'Cartouche filtrante pour dépoussiérage.', + customFields: [ + { name: 'Classe de filtration', type: 'string', required: true }, + { name: 'Dimensions (mm)', type: 'string' }, + ], + }, + { + code: 'hydraulic-pump', + name: 'Pompe hydraulique', + description: 'Pompe hydraulique pour chariot télescopique.', + customFields: [ + { name: 'Débit nominal (l/min)', type: 'number', required: true }, + { name: 'Pression max (bar)', type: 'number' }, + ], + }, + { + code: 'control-module', + name: 'Module automate', + description: 'Module PLC assurant le contrôle de ligne.', + customFields: [ + { name: "Nombre d'E/S", type: 'number', required: true }, + { name: 'Version firmware', type: 'string' }, + ], + }, + { + code: 'load-cell', + name: 'Capteur de pesage', + description: 'Capteur de pesage pour benne peseuse.', + customFields: [ + { name: 'Capacité (kg)', type: 'number', required: true }, + { name: 'Sensibilité (mV/V)', type: 'string' }, + ], + }, +]; + +const pieceModelDefinitions: PieceModelDefinition[] = [ + { + code: 'motor-75kw', + name: 'Moteur IE3 75 kW', + description: 'Moteur principal pour élévateur haute capacité.', + typeCode: 'electric-motor', + structure: { + defaultCustomFieldValues: { + 'Puissance nominale (kW)': '75', + 'Tension (V)': '400', + "Indice de protection": 'IP55', + }, + }, + }, + { + code: 'motor-45kw', + name: 'Moteur IE3 45 kW', + description: 'Motorisation pour convoyeur ou ventilateur.', + typeCode: 'electric-motor', + structure: { + defaultCustomFieldValues: { + 'Puissance nominale (kW)': '45', + 'Tension (V)': '400', + "Indice de protection": 'IP55', + }, + }, + }, + { + code: 'motor-18kw', + name: 'Moteur IE3 18,5 kW', + description: 'Motorisation pour vis sans fin ou table densimétrique.', + typeCode: 'electric-motor', + structure: { + defaultCustomFieldValues: { + 'Puissance nominale (kW)': '18.5', + 'Tension (V)': '400', + "Indice de protection": 'IP55', + }, + }, + }, + { + code: 'sensor-speed-m12', + name: 'Capteur inductif M12', + description: 'Capteur de vitesse inductif 4-20 mA.', + typeCode: 'speed-sensor', + structure: { + defaultCustomFieldValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': '0-1200', + }, + }, + }, + { + code: 'temp-probe-pt100', + name: 'Sonde PT100 classe A', + description: 'Sonde PT100 pour mesure d\'air chaud.', + typeCode: 'temperature-probe', + structure: { + defaultCustomFieldValues: { + 'Plage de mesure (°C)': '0-250', + 'Type de sonde': 'PT100 classe A', + }, + }, + }, + { + code: 'belt-hd-800', + name: 'Bande renforcée 800 mm', + description: 'Bande HD 800 mm anti-statique.', + typeCode: 'belt', + structure: { + defaultCustomFieldValues: { + 'Largeur (mm)': '800', + 'Matériau': 'Caoutchouc nitrile', + }, + }, + }, + { + code: 'belt-hd-650', + name: 'Bande renforcée 650 mm', + description: 'Bande HD 650 mm résistante à l\'abrasion.', + typeCode: 'belt', + structure: { + defaultCustomFieldValues: { + 'Largeur (mm)': '650', + 'Matériau': 'Caoutchouc anti-abrasion', + }, + }, + }, + { + code: 'bearing-ucp210', + name: 'Roulement UCP210', + description: 'Roulement à semelle graissable.', + typeCode: 'bearing', + structure: { + defaultCustomFieldValues: { + 'Référence fournisseur': 'SKF UCP210', + "Type d'étanchéité": '2RS', + }, + }, + }, + { + code: 'gearbox-flender', + name: 'Réducteur Flender FZ', + description: 'Réducteur coaxial pour charge lourde.', + typeCode: 'gearbox', + structure: { + defaultCustomFieldValues: { + 'Rapport de réduction': '1:28', + 'Couple nominal (Nm)': '3200', + }, + }, + }, + { + code: 'filter-g4-500', + name: 'Filtre G4 500x500', + description: 'Cartouche filtrante G4 pour dépoussiérage.', + typeCode: 'air-filter', + structure: { + defaultCustomFieldValues: { + 'Classe de filtration': 'G4', + 'Dimensions (mm)': '500x500x50', + }, + }, + }, + { + code: 'hydraulic-pump-pvh98', + name: 'Pompe hydraulique PVH98', + description: 'Pompe haute pression pour Manitou.', + typeCode: 'hydraulic-pump', + structure: { + defaultCustomFieldValues: { + 'Débit nominal (l/min)': '160', + 'Pression max (bar)': '320', + }, + }, + }, + { + code: 'plc-module-siemens-1512', + name: 'Module PLC Siemens 1512', + description: 'Automate Siemens S7-1500 1512SP.', + typeCode: 'control-module', + structure: { + defaultCustomFieldValues: { + "Nombre d'E/S": '32', + 'Version firmware': 'V2.9', + }, + }, + }, + { + code: 'load-cell-5t', + name: 'Capteur de pesage 5 t', + description: 'Capteur de pesage IP68 pour benne peseuse.', + typeCode: 'load-cell', + structure: { + defaultCustomFieldValues: { + 'Capacité (kg)': '5000', + 'Sensibilité (mV/V)': '2.0', + }, + }, + }, +]; + +const componentModelDefinitions: ComponentModelDefinition[] = [ + { + code: 'elevator-z400', + name: 'Élévateur Z400', + description: 'Élévateur à godets 120 t/h, 38 m.', + typeCode: 'bucket-elevator', + structure: { + defaultPieces: [ + { modelCode: 'motor-75kw', role: 'Motorisation principale' }, + { modelCode: 'gearbox-flender', role: 'Réducteur' }, + { modelCode: 'belt-hd-800', role: 'Courroie élévateur' }, + { modelCode: 'sensor-speed-m12', role: 'Capteur vitesse tête' }, + { modelCode: 'bearing-ucp210', role: 'Paliers de tête' }, + ], + recommendedCustomFields: { + 'Débit nominal (t/h)': '120', + 'Hauteur de levage (m)': '38', + 'Matériau des godets': 'Acier galvanisé', + }, + }, + }, + { + code: 'conveyor-18m', + name: 'Convoyeur bande 18 m', + description: 'Convoyeur à bande 800 mm, 18 mètres.', + typeCode: 'belt-conveyor', + structure: { + defaultPieces: [ + { modelCode: 'motor-45kw', role: 'Motorisation' }, + { modelCode: 'belt-hd-800', role: 'Bande transporteuse' }, + { modelCode: 'sensor-speed-m12', role: 'Capteur rotation' }, + ], + recommendedCustomFields: { + 'Largeur de bande (mm)': '800', + 'Longueur (m)': '18', + 'Type de bande': 'Caoutchouc anti-statique', + }, + }, + }, + { + code: 'gravity-table-tqx', + name: 'Table densimétrique TQX-120', + description: 'Table densimétrique haute précision 120 t/h.', + typeCode: 'gravity-separator', + structure: { + defaultPieces: [ + { modelCode: 'motor-18kw', role: 'Motorisation vibration' }, + { modelCode: 'sensor-speed-m12', role: 'Capteur vibration' }, + ], + recommendedCustomFields: { + 'Capacité de tri (t/h)': '120', + 'Fréquence de vibration (Hz)': '45', + 'Type de plateau': 'Acier perforé', + }, + }, + }, + { + code: 'grain-dryer-cd6', + name: 'Séchoir continu CD-6', + description: 'Séchoir continu 6 étages avec récupération de chaleur.', + typeCode: 'grain-dryer', + structure: { + defaultPieces: [ + { modelCode: 'motor-45kw', role: 'Ventilateur principal' }, + { modelCode: 'temp-probe-pt100', role: 'Sonde air chaud' }, + ], + subComponents: [ + { modelCode: 'burner-module-3mw', role: 'Brûleur gaz principal' }, + { modelCode: 'dust-filter-fc12', role: 'Filtre cyclone' }, + ], + recommendedCustomFields: { + 'Capacité sèche (t/h)': '60', + "Nombre d'étages": '6', + "Type d'énergie": 'Gaz naturel', + }, + }, + }, + { + code: 'screw-conveyor-v200', + name: 'Vis sans fin V200', + description: 'Vis de reprise diamètre 200 mm.', + typeCode: 'screw-conveyor', + structure: { + defaultPieces: [ + { modelCode: 'motor-18kw', role: 'Motorisation vis' }, + { modelCode: 'bearing-ucp210', role: 'Paliers supports' }, + ], + recommendedCustomFields: { + 'Diamètre vis (mm)': '200', + 'Inclinaison (°)': '12', + 'Vitesse (rpm)': '140', + }, + }, + }, + { + code: 'weigh-hopper-bp5', + name: 'Benna peseuse BP-5', + description: 'Benna peseuse 5 tonnes pour chargement camion.', + typeCode: 'weigh-hopper', + structure: { + defaultPieces: [ + { modelCode: 'load-cell-5t', role: 'Capteurs de pesage' }, + { modelCode: 'sensor-speed-m12', role: 'Capteur rotation trappe' }, + ], + recommendedCustomFields: { + 'Capacité de pesée (kg)': '5000', + 'Précision (%)': '0.5', + 'Nombre de capteurs': '4', + }, + }, + }, + { + code: 'control-panel-m340', + name: 'Armoire Schneider M340', + description: 'Armoire Schneider Electric avec automate M340.', + typeCode: 'control-panel', + structure: { + defaultPieces: [ + { modelCode: 'plc-module-siemens-1512', role: 'Automate principal' }, + { modelCode: 'sensor-speed-m12', role: 'Entrée vitesse générale' }, + ], + recommendedCustomFields: { + 'Automate principal': 'Schneider Modicon M340', + 'Année de mise à jour': '2023', + "Indice de protection": 'IP55', + }, + }, + }, + { + code: 'burner-module-3mw', + name: 'Brûleur gaz 3 MW', + description: 'Brûleur gaz modulant 3 MW pour séchoir.', + typeCode: 'burner-module', + structure: { + defaultPieces: [ + { modelCode: 'temp-probe-pt100', role: 'Sonde sécurité flamme' }, + ], + recommendedCustomFields: { + 'Puissance thermique (kW)': '3000', + 'Type de carburant': 'Gaz naturel', + "Système d'allumage": 'Double électrode', + }, + }, + }, + { + code: 'dust-filter-fc12', + name: 'Filtre cyclone FC-12', + description: 'Filtre cyclone avec cartouches G4.', + typeCode: 'dust-filter', + structure: { + defaultPieces: [ + { modelCode: 'filter-g4-500', role: 'Cartouche filtrante' }, + ], + recommendedCustomFields: { + 'Efficacité de filtration (%)': '95', + 'Type de média filtrant': 'Polyester', + 'Nombre de cartouches': '6', + }, + }, + }, + { + code: 'telehandler-mlt1040', + name: 'Manitou MLT 1040', + description: 'Chariot télescopique Manitou 4 t.', + typeCode: 'telehandler', + structure: { + defaultPieces: [ + { modelCode: 'hydraulic-pump-pvh98', role: 'Pompe hydraulique principale' }, + { modelCode: 'bearing-ucp210', role: 'Articulation de flèche' }, + ], + recommendedCustomFields: { + 'Capacité de levage (t)': '4', + 'Hauteur de levage (m)': '9.6', + "Type d'attache": 'Fourches FEM', + }, + }, + }, +]; + +const constructeurDefinitions: ConstructeurDefinition[] = [ + { + key: 'agritech', + name: 'AgriTech Elevators', + email: 'support@agritech-elevators.fr', + phone: '+33 3 74 01 20 10', + }, + { + key: 'valmont', + name: 'Valmont Conveyors', + email: 'info@valmont-conveyors.com', + phone: '+33 3 80 45 77 20', + }, + { + key: 'buhler', + name: 'Bühler Sortex', + email: 'service@buhlergroup.com', + phone: '+41 71 955 11 11', + }, + { + key: 'agridry', + name: 'AgriDry Solutions', + email: 'contact@agridry.eu', + phone: '+33 4 74 22 55 90', + }, + { + key: 'sew', + name: 'SEW-Eurodrive', + email: 'contact@sew-eurodrive.fr', + phone: '+33 3 88 73 67 00', + }, + { + key: 'skf', + name: 'SKF France', + email: 'support@skf.com', + phone: '+33 1 30 57 67 00', + }, + { + key: 'schneider', + name: 'Schneider Electric', + email: 'contact@se.com', + phone: '+33 1 47 29 70 00', + }, + { + key: 'manitou', + name: 'Manitou BF', + email: 'support@manitou-group.com', + phone: '+33 2 40 09 10 11', + }, + { + key: 'flender', + name: 'Flender GmbH', + email: 'service@flender.com', + phone: '+49 521 525 0', + }, + { + key: 'ifm', + name: 'ifm electronic', + email: 'info.fr@ifm.com', + phone: '+33 1 45 12 24 00', + }, + { + key: 'siemens', + name: 'Siemens Industry', + email: 'industry.fr@siemens.com', + phone: '+33 1 85 57 00 00', + }, +]; + +async function clearDatabaseExceptSitesAndProfiles() { + console.log('🧹 Nettoyage des tables (hors sites et profils)...'); + + const deleteOrder = [ + prisma.customFieldValue.deleteMany(), + prisma.document.deleteMany(), + prisma.piece.deleteMany(), + prisma.composant.deleteMany(), + prisma.machine.deleteMany(), + prisma.typeMachineComponentRequirement.deleteMany(), + prisma.typeMachinePieceRequirement.deleteMany(), + prisma.customField.deleteMany(), + prisma.pieceModel.deleteMany(), + prisma.composantModel.deleteMany(), + prisma.typeMachine.deleteMany(), + prisma.modelType.deleteMany(), + prisma.constructeur.deleteMany(), + ]; + + for (const promise of deleteOrder) { + await promise; + } + + console.log('✅ Tables nettoyées.'); +} + +async function ensureDemoSite() { + const existingSite = await prisma.site.findFirst(); + if (existingSite) { + return existingSite; + } + + console.log('🏗️ Création du site de démonstration...'); + return prisma.site.create({ + data: { + name: 'Usine de triage Valgrain', + contactName: 'Lucie Bernard', + contactPhone: '+33 3 80 12 45 78', + contactAddress: 'Zone industrielle des Platanes', + contactPostalCode: '21000', + contactCity: 'Dijon', + }, + }); +} + +async function createConstructeurs() { + console.log('🏭 Création des constructeurs...'); + const entries = await Promise.all( + constructeurDefinitions.map((definition) => + prisma.constructeur + .create({ + data: { + name: definition.name, + email: definition.email, + phone: definition.phone, + }, + }) + .then((constructeur) => [definition.key, constructeur] as const), + ), + ); + + return Object.fromEntries(entries) as Record; +} + +function mapCustomFields( + fields: { id: string; name: string }[], +): Record { + return fields.reduce>((acc, field) => { + acc[field.name] = field.id; + return acc; + }, {}); +} + +async function createModelTypes() { + console.log('🗂️ Création des catégories de composants et pièces...'); + + const componentTypeEntries: [ + string, + { + id: string; + customFields: Record; + }, + ][] = []; + + for (const definition of componentTypeDefinitions) { + const record = await prisma.modelType.create({ + data: { + name: definition.name, + code: definition.code, + category: ModelCategory.COMPONENT, + description: definition.description, + customFields: { + create: definition.customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + defaultValue: field.defaultValue, + options: field.options ?? [], + })), + }, + }, + include: { + customFields: true, + }, + }); + + componentTypeEntries.push([ + definition.code, + { + id: record.id, + customFields: mapCustomFields(record.customFields), + }, + ]); + } + + const pieceTypeEntries: [ + string, + { + id: string; + customFields: Record; + }, + ][] = []; + + for (const definition of pieceTypeDefinitions) { + const record = await prisma.modelType.create({ + data: { + name: definition.name, + code: definition.code, + category: ModelCategory.PIECE, + description: definition.description, + pieceCustomFields: { + create: definition.customFields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required ?? false, + defaultValue: field.defaultValue, + options: field.options ?? [], + })), + }, + }, + include: { + pieceCustomFields: true, + }, + }); + + pieceTypeEntries.push([ + definition.code, + { + id: record.id, + customFields: mapCustomFields(record.pieceCustomFields), + }, + ]); + } + + return { + componentTypes: Object.fromEntries(componentTypeEntries) as Record< + string, + { id: string; customFields: Record } + >, + pieceTypes: Object.fromEntries(pieceTypeEntries) as Record< + string, + { id: string; customFields: Record } + >, + }; +} + +async function createPieceModels(pieceTypes: Record) { + console.log('🔧 Création des modèles de pièces...'); + const entries = await Promise.all( + pieceModelDefinitions.map(async (definition) => { + const record = await prisma.pieceModel.create({ + data: { + name: definition.name, + description: definition.description, + typePiece: { connect: { id: pieceTypes[definition.typeCode].id } }, + structure: definition.structure ?? Prisma.JsonNull, + }, + }); + return [definition.code, record] as const; + }), + ); + return Object.fromEntries(entries) as Record; +} + +async function createComponentModels( + componentTypes: Record, +) { + console.log('🧱 Création des modèles de composants...'); + const entries = await Promise.all( + componentModelDefinitions.map(async (definition) => { + const record = await prisma.composantModel.create({ + data: { + name: definition.name, + description: definition.description, + typeComposant: { connect: { id: componentTypes[definition.typeCode].id } }, + structure: definition.structure ?? Prisma.JsonNull, + }, + }); + return [definition.code, record] as const; + }), + ); + return Object.fromEntries(entries) as Record; +} + +async function createTypeMachine( + componentTypes: Record, + pieceTypes: Record, +) { + console.log('🧬 Création du squelette de machine...'); + + const typeMachine = await prisma.typeMachine.create({ + data: { + name: 'Ligne de triage et séchage céréales 120 t/h', + description: + 'Chaîne automatisée de réception, triage, séchage et expédition des céréales.', + category: 'Triage et stockage', + maintenanceFrequency: + 'Inspection quotidienne, graissage hebdomadaire, révision trimestrielle.', + specifications: { + nominalThroughput: 120, + moistureTarget: 14.5, + building: 'Hall principal', + shiftsPerDay: 2, + }, + components: { + layout: [ + { order: 1, zone: 'Réception', type: 'bucket-elevator', model: 'elevator-z400' }, + { order: 2, zone: 'Pré-nettoyage', type: 'belt-conveyor', model: 'conveyor-18m' }, + { order: 3, zone: 'Séparation', type: 'gravity-separator', model: 'gravity-table-tqx' }, + { order: 4, zone: 'Séchage', type: 'grain-dryer', model: 'grain-dryer-cd6' }, + { order: 5, zone: 'Reprise', type: 'screw-conveyor', model: 'screw-conveyor-v200' }, + { order: 6, zone: 'Expédition', type: 'weigh-hopper', model: 'weigh-hopper-bp5' }, + ], + }, + criticalParts: { + motors: ['motor-75kw', 'motor-45kw'], + sensors: ['sensor-speed-m12', 'temp-probe-pt100'], + wearItems: ['belt-hd-800', 'filter-g4-500'], + }, + machinePieces: { + recommendedStock: [ + { model: 'motor-75kw', quantity: 1 }, + { model: 'sensor-speed-m12', quantity: 3 }, + { model: 'belt-hd-800', quantity: 1 }, + ], + }, + customFields: { + create: [ + { + name: 'Capacité nominale (t/h)', + type: 'number', + required: true, + options: [], + }, + { + name: 'Produit traité', + type: 'string', + required: true, + defaultValue: 'Blé tendre', + options: [], + }, + { + name: 'Date de mise en service', + type: 'date', + required: true, + options: [], + }, + { + name: 'Responsable de ligne', + type: 'string', + required: false, + defaultValue: 'Lucie Bernard', + options: [], + }, + ], + }, + componentRequirements: { + create: [ + { + label: 'Élévateurs principaux', + minCount: 2, + maxCount: 2, + required: true, + typeComposant: { connect: { id: componentTypes['bucket-elevator'].id } }, + }, + { + label: "Convoyeurs d'alimentation", + minCount: 2, + required: true, + typeComposant: { connect: { id: componentTypes['belt-conveyor'].id } }, + }, + { + label: 'Table densimétrique', + minCount: 1, + maxCount: 1, + required: true, + typeComposant: { connect: { id: componentTypes['gravity-separator'].id } }, + }, + { + label: 'Séchoir continu', + minCount: 1, + maxCount: 1, + required: true, + typeComposant: { connect: { id: componentTypes['grain-dryer'].id } }, + }, + { + label: 'Vis de reprise', + minCount: 2, + required: true, + typeComposant: { connect: { id: componentTypes['screw-conveyor'].id } }, + }, + { + label: 'Benna peseuse', + minCount: 1, + required: true, + typeComposant: { connect: { id: componentTypes['weigh-hopper'].id } }, + }, + { + label: 'Armoire de contrôle', + minCount: 1, + maxCount: 1, + required: true, + typeComposant: { connect: { id: componentTypes['control-panel'].id } }, + }, + { + label: 'Chariot télescopique', + minCount: 1, + required: false, + typeComposant: { connect: { id: componentTypes['telehandler'].id } }, + }, + ], + }, + pieceRequirements: { + create: [ + { + label: 'Moteurs de rechange', + minCount: 1, + required: false, + typePiece: { connect: { id: pieceTypes['electric-motor'].id } }, + }, + { + label: 'Capteurs de vitesse de secours', + minCount: 2, + required: false, + typePiece: { connect: { id: pieceTypes['speed-sensor'].id } }, + }, + ], + }, + }, + include: { + customFields: true, + componentRequirements: true, + pieceRequirements: true, + }, + }); + + return typeMachine; +} + +function buildCustomFieldValues( + fieldMap: Record, + values?: Record, +) { + if (!values) { + return undefined; + } + + return { + create: Object.entries(values).map(([name, value]) => { + const fieldId = fieldMap[name]; + if (!fieldId) { + throw new Error(`Champ personnalisé inconnu: ${name}`); + } + return { + value: value instanceof Date ? value.toISOString() : String(value), + customField: { + connect: { id: fieldId }, + }, + }; + }), + }; +} + +async function createComponentHierarchy( + machineId: string, + component: ComponentInstance, + context: { + componentTypes: Record }>; + componentModels: Record; + pieceTypes: Record }>; + pieceModels: Record; + constructeurs: Record; + requirementMap: Map; + }, + parentId?: string, +) { + const requirementId = component.requirementLabel + ? context.requirementMap.get(component.requirementLabel) + : undefined; + + const record = await prisma.composant.create({ + data: { + name: component.name, + reference: component.reference, + prix: component.prix ? new Prisma.Decimal(component.prix) : undefined, + machine: { connect: { id: machineId } }, + parentComposant: parentId ? { connect: { id: parentId } } : undefined, + typeComposant: { + connect: { id: context.componentTypes[component.typeCode].id }, + }, + composantModel: { + connect: { id: context.componentModels[component.modelCode].id }, + }, + typeMachineComponentRequirement: requirementId + ? { connect: { id: requirementId } } + : undefined, + constructeur: component.constructeur + ? { connect: { id: context.constructeurs[component.constructeur].id } } + : undefined, + customFieldValues: buildCustomFieldValues( + context.componentTypes[component.typeCode].customFields, + component.customValues, + ), + pieces: component.pieces + ? { + create: component.pieces.map((piece) => { + const type = context.pieceTypes[piece.typeCode]; + if (!type) { + throw new Error(`Type de pièce introuvable: ${piece.typeCode}`); + } + return { + name: piece.name, + reference: piece.reference, + prix: piece.prix ? new Prisma.Decimal(piece.prix) : undefined, + typePiece: { connect: { id: type.id } }, + pieceModel: { + connect: { id: context.pieceModels[piece.modelCode].id }, + }, + constructeur: piece.constructeur + ? { connect: { id: context.constructeurs[piece.constructeur].id } } + : undefined, + customFieldValues: buildCustomFieldValues( + type.customFields, + piece.customValues, + ), + }; + }), + } + : undefined, + }, + }); + + if (component.children && component.children.length > 0) { + for (const child of component.children) { + await createComponentHierarchy(machineId, child, context, record.id); + } + } + + return record; +} + +async function createMachineWithComponents( + siteId: string, + typeMachine: Awaited>, + context: { + componentTypes: Record }>; + componentModels: Record; + pieceTypes: Record }>; + pieceModels: Record; + constructeurs: Record; + }, +) { + console.log('🏗️ Création de la machine de démonstration...'); + + const requirementMap = new Map(); + typeMachine.componentRequirements.forEach((requirement) => { + if (requirement.label) { + requirementMap.set(requirement.label, requirement.id); + } + }); + + const pieceRequirementMap = new Map(); + typeMachine.pieceRequirements.forEach((requirement) => { + if (requirement.label) { + pieceRequirementMap.set(requirement.label, requirement.id); + } + }); + + const machineCustomFieldMap = mapCustomFields(typeMachine.customFields); + + const machine = await prisma.machine.create({ + data: { + name: 'Ligne de triage Valgrain 2024', + reference: 'VT-TRI-2024', + prix: new Prisma.Decimal('725000'), + site: { connect: { id: siteId } }, + typeMachine: { connect: { id: typeMachine.id } }, + constructeur: { connect: { id: context.constructeurs['buhler'].id } }, + customFieldValues: buildCustomFieldValues(machineCustomFieldMap, { + 'Capacité nominale (t/h)': 120, + 'Produit traité': 'Blé tendre, orge brassicole', + 'Date de mise en service': new Date('2024-02-01'), + 'Responsable de ligne': 'Lucie Bernard', + }), + }, + }); + + console.log('⚙️ Ajout des composants et sous-ensembles...'); + + const componentContext = { + ...context, + requirementMap, + }; + + const components: ComponentInstance[] = [ + { + name: 'Élévateur amont Z400', + reference: 'BE-Z400-01', + prix: '58000', + typeCode: 'bucket-elevator', + requirementLabel: 'Élévateurs principaux', + modelCode: 'elevator-z400', + constructeur: 'agritech', + customValues: { + 'Débit nominal (t/h)': 120, + 'Hauteur de levage (m)': 38, + 'Matériau des godets': 'Acier galvanisé', + }, + pieces: [ + { + name: 'Moteur principal 75 kW', + reference: 'MTR-75-01', + prix: '8200', + typeCode: 'electric-motor', + modelCode: 'motor-75kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 75, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Réducteur Flender FZ', + reference: 'GBX-FZ-01', + prix: '5100', + typeCode: 'gearbox', + modelCode: 'gearbox-flender', + constructeur: 'flender', + customValues: { + 'Rapport de réduction': '1:28', + 'Couple nominal (Nm)': 3200, + }, + }, + { + name: 'Courroie élévateur 800 mm', + reference: 'BLT-800-01', + prix: '2300', + typeCode: 'belt', + modelCode: 'belt-hd-800', + constructeur: 'agritech', + customValues: { + 'Largeur (mm)': 800, + 'Matériau': 'Caoutchouc nitrile', + }, + }, + { + name: 'Capteur vitesse tête', + reference: 'SNS-VIT-01', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 1200, + }, + }, + { + name: 'Roulement tête élévateur', + reference: 'BRG-UCP210-01', + typeCode: 'bearing', + modelCode: 'bearing-ucp210', + constructeur: 'skf', + customValues: { + 'Référence fournisseur': 'SKF UCP210', + "Type d'étanchéité": '2RS', + }, + }, + ], + }, + { + name: 'Élévateur aval Z400', + reference: 'BE-Z400-02', + prix: '56800', + typeCode: 'bucket-elevator', + requirementLabel: 'Élévateurs principaux', + modelCode: 'elevator-z400', + constructeur: 'agritech', + customValues: { + 'Débit nominal (t/h)': 115, + 'Hauteur de levage (m)': 36, + 'Matériau des godets': 'Acier galvanisé', + }, + pieces: [ + { + name: 'Moteur principal 75 kW (aval)', + reference: 'MTR-75-02', + prix: '8150', + typeCode: 'electric-motor', + modelCode: 'motor-75kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 75, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Courroie élévateur 800 mm (aval)', + reference: 'BLT-800-02', + typeCode: 'belt', + modelCode: 'belt-hd-800', + constructeur: 'agritech', + customValues: { + 'Largeur (mm)': 800, + 'Matériau': 'Caoutchouc nitrile', + }, + }, + { + name: 'Capteur vitesse aval', + reference: 'SNS-VIT-02', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 1100, + }, + }, + ], + }, + { + name: "Convoyeur alimentation réception", + reference: 'CV-ALIM-01', + prix: '24500', + typeCode: 'belt-conveyor', + requirementLabel: "Convoyeurs d'alimentation", + modelCode: 'conveyor-18m', + constructeur: 'valmont', + customValues: { + 'Largeur de bande (mm)': 800, + 'Longueur (m)': 18, + 'Type de bande': 'Caoutchouc anti-statique', + }, + pieces: [ + { + name: 'Moteur convoyeur 45 kW', + reference: 'MTR-45-01', + prix: '6100', + typeCode: 'electric-motor', + modelCode: 'motor-45kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 45, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Bande transporteuse 800 mm', + reference: 'BLT-800-03', + typeCode: 'belt', + modelCode: 'belt-hd-800', + constructeur: 'valmont', + customValues: { + 'Largeur (mm)': 800, + 'Matériau': 'Caoutchouc anti-abrasion', + }, + }, + { + name: 'Capteur vitesse convoyeur', + reference: 'SNS-VIT-03', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 900, + }, + }, + ], + }, + { + name: "Convoyeur alimentation séparateur", + reference: 'CV-ALIM-02', + prix: '23200', + typeCode: 'belt-conveyor', + requirementLabel: "Convoyeurs d'alimentation", + modelCode: 'conveyor-18m', + constructeur: 'valmont', + customValues: { + 'Largeur de bande (mm)': 650, + 'Longueur (m)': 15, + 'Type de bande': 'Caoutchouc anti-statique', + }, + pieces: [ + { + name: 'Moteur convoyeur 45 kW (séparateur)', + reference: 'MTR-45-02', + typeCode: 'electric-motor', + modelCode: 'motor-45kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 45, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Bande transporteuse 650 mm', + reference: 'BLT-650-01', + typeCode: 'belt', + modelCode: 'belt-hd-650', + constructeur: 'valmont', + customValues: { + 'Largeur (mm)': 650, + 'Matériau': 'Caoutchouc anti-abrasion', + }, + }, + { + name: 'Capteur vitesse convoyeur 2', + reference: 'SNS-VIT-04', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 850, + }, + }, + ], + }, + { + name: 'Table densimétrique TQX-120', + reference: 'TBL-TQX-01', + prix: '68500', + typeCode: 'gravity-separator', + requirementLabel: 'Table densimétrique', + modelCode: 'gravity-table-tqx', + constructeur: 'buhler', + customValues: { + 'Capacité de tri (t/h)': 120, + 'Fréquence de vibration (Hz)': 45, + 'Type de plateau': 'Acier perforé', + }, + pieces: [ + { + name: 'Moteur vibration 18,5 kW', + reference: 'MTR-18-01', + typeCode: 'electric-motor', + modelCode: 'motor-18kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 18.5, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Capteur vibration table', + reference: 'SNS-VIB-01', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 600, + }, + }, + ], + }, + { + name: 'Séchoir continu CD-6', + reference: 'DRY-CD6-01', + prix: '148000', + typeCode: 'grain-dryer', + requirementLabel: 'Séchoir continu', + modelCode: 'grain-dryer-cd6', + constructeur: 'agridry', + customValues: { + 'Capacité sèche (t/h)': 60, + "Nombre d'étages": 6, + "Type d'énergie": 'Gaz naturel', + }, + pieces: [ + { + name: 'Ventilateur principal 45 kW', + reference: 'MTR-45-03', + typeCode: 'electric-motor', + modelCode: 'motor-45kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 45, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: "Sonde air chaud PT100", + reference: 'TMP-PT100-01', + typeCode: 'temperature-probe', + modelCode: 'temp-probe-pt100', + constructeur: 'ifm', + customValues: { + 'Plage de mesure (°C)': '0-250', + 'Type de sonde': 'PT100 classe A', + }, + }, + ], + children: [ + { + name: 'Brûleur gaz modulant', + reference: 'BRN-3MW-01', + typeCode: 'burner-module', + modelCode: 'burner-module-3mw', + constructeur: 'agridry', + customValues: { + 'Puissance thermique (kW)': 2950, + 'Type de carburant': 'Gaz naturel', + "Système d'allumage": 'Double électrode', + }, + pieces: [ + { + name: 'Sonde sécurité flamme', + reference: 'TMP-PT100-02', + typeCode: 'temperature-probe', + modelCode: 'temp-probe-pt100', + constructeur: 'ifm', + customValues: { + 'Plage de mesure (°C)': '0-250', + 'Type de sonde': 'PT100 classe A', + }, + }, + ], + }, + { + name: 'Filtre cyclone FC-12', + reference: 'FLT-FC12-01', + typeCode: 'dust-filter', + modelCode: 'dust-filter-fc12', + constructeur: 'agridry', + customValues: { + 'Efficacité de filtration (%)': 95, + 'Type de média filtrant': 'Polyester', + 'Nombre de cartouches': 6, + }, + pieces: [ + { + name: 'Cartouche filtrante G4', + reference: 'FLT-G4-01', + typeCode: 'air-filter', + modelCode: 'filter-g4-500', + constructeur: 'agridry', + customValues: { + 'Classe de filtration': 'G4', + 'Dimensions (mm)': '500x500x50', + }, + }, + ], + }, + ], + }, + { + name: 'Vis de reprise nord', + reference: 'SC-V200-01', + prix: '18400', + typeCode: 'screw-conveyor', + requirementLabel: 'Vis de reprise', + modelCode: 'screw-conveyor-v200', + constructeur: 'valmont', + customValues: { + 'Diamètre vis (mm)': 200, + 'Inclinaison (°)': 10, + 'Vitesse (rpm)': 140, + }, + pieces: [ + { + name: 'Moteur vis 18,5 kW', + reference: 'MTR-18-02', + typeCode: 'electric-motor', + modelCode: 'motor-18kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 18.5, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Palier vis nord', + reference: 'BRG-UCP210-02', + typeCode: 'bearing', + modelCode: 'bearing-ucp210', + constructeur: 'skf', + customValues: { + 'Référence fournisseur': 'SKF UCP210', + "Type d'étanchéité": '2RS', + }, + }, + ], + }, + { + name: 'Vis de reprise sud', + reference: 'SC-V200-02', + prix: '18100', + typeCode: 'screw-conveyor', + requirementLabel: 'Vis de reprise', + modelCode: 'screw-conveyor-v200', + constructeur: 'valmont', + customValues: { + 'Diamètre vis (mm)': 200, + 'Inclinaison (°)': 12, + 'Vitesse (rpm)': 145, + }, + pieces: [ + { + name: 'Moteur vis 18,5 kW (sud)', + reference: 'MTR-18-03', + typeCode: 'electric-motor', + modelCode: 'motor-18kw', + constructeur: 'sew', + customValues: { + 'Puissance nominale (kW)': 18.5, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + }, + { + name: 'Palier vis sud', + reference: 'BRG-UCP210-03', + typeCode: 'bearing', + modelCode: 'bearing-ucp210', + constructeur: 'skf', + customValues: { + 'Référence fournisseur': 'SKF UCP210', + "Type d'étanchéité": '2RS', + }, + }, + ], + }, + { + name: 'Benna peseuse BP-5', + reference: 'BP-5000-01', + prix: '39200', + typeCode: 'weigh-hopper', + requirementLabel: 'Benna peseuse', + modelCode: 'weigh-hopper-bp5', + constructeur: 'buhler', + customValues: { + 'Capacité de pesée (kg)': 5000, + 'Précision (%)': 0.5, + 'Nombre de capteurs': 4, + }, + pieces: [ + { + name: 'Capteur pesage 1', + reference: 'LC-5000-01', + typeCode: 'load-cell', + modelCode: 'load-cell-5t', + constructeur: 'ifm', + customValues: { + 'Capacité (kg)': 5000, + 'Sensibilité (mV/V)': '2.0', + }, + }, + { + name: 'Capteur pesage 2', + reference: 'LC-5000-02', + typeCode: 'load-cell', + modelCode: 'load-cell-5t', + constructeur: 'ifm', + customValues: { + 'Capacité (kg)': 5000, + 'Sensibilité (mV/V)': '2.0', + }, + }, + { + name: 'Capteur pesage 3', + reference: 'LC-5000-03', + typeCode: 'load-cell', + modelCode: 'load-cell-5t', + constructeur: 'ifm', + customValues: { + 'Capacité (kg)': 5000, + 'Sensibilité (mV/V)': '2.0', + }, + }, + { + name: 'Capteur pesage 4', + reference: 'LC-5000-04', + typeCode: 'load-cell', + modelCode: 'load-cell-5t', + constructeur: 'ifm', + customValues: { + 'Capacité (kg)': 5000, + 'Sensibilité (mV/V)': '2.0', + }, + }, + ], + }, + { + name: 'Armoire Schneider M340', + reference: 'CTRL-M340-01', + prix: '46500', + typeCode: 'control-panel', + requirementLabel: 'Armoire de contrôle', + modelCode: 'control-panel-m340', + constructeur: 'schneider', + customValues: { + 'Automate principal': 'Schneider Modicon M340', + 'Année de mise à jour': 2023, + "Indice de protection": 'IP55', + }, + pieces: [ + { + name: 'Module PLC Siemens 1512', + reference: 'PLC-1512-01', + typeCode: 'control-module', + modelCode: 'plc-module-siemens-1512', + constructeur: 'siemens', + customValues: { + "Nombre d'E/S": 32, + 'Version firmware': 'V2.9', + }, + }, + { + name: 'Entrée vitesse ligne', + reference: 'SNS-VIT-CTRL-01', + typeCode: 'speed-sensor', + modelCode: 'sensor-speed-m12', + constructeur: 'ifm', + customValues: { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 1000, + }, + }, + ], + }, + { + name: 'Chariot télescopique Manitou', + reference: 'MLT-1040-01', + prix: '73500', + typeCode: 'telehandler', + requirementLabel: 'Chariot télescopique', + modelCode: 'telehandler-mlt1040', + constructeur: 'manitou', + customValues: { + 'Capacité de levage (t)': 4, + 'Hauteur de levage (m)': 9.6, + "Type d'attache": 'Fourches FEM', + }, + pieces: [ + { + name: 'Pompe hydraulique principale', + reference: 'HDP-PVH98-01', + typeCode: 'hydraulic-pump', + modelCode: 'hydraulic-pump-pvh98', + constructeur: 'manitou', + customValues: { + 'Débit nominal (l/min)': 160, + 'Pression max (bar)': 320, + }, + }, + { + name: 'Palier articulation flèche', + reference: 'BRG-UCP210-04', + typeCode: 'bearing', + modelCode: 'bearing-ucp210', + constructeur: 'skf', + customValues: { + 'Référence fournisseur': 'SKF UCP210', + "Type d'étanchéité": '2RS', + }, + }, + ], + }, + ]; + + for (const component of components) { + await createComponentHierarchy(machine.id, component, componentContext); + } + + console.log('📦 Ajout des pièces de réserve machine...'); + + const spareMotor = await prisma.piece.create({ + data: { + name: 'Moteur IE3 75 kW de secours', + reference: 'MTR-75-SPARE', + prix: new Prisma.Decimal('7900'), + machine: { connect: { id: machine.id } }, + typePiece: { connect: { id: context.pieceTypes['electric-motor'].id } }, + pieceModel: { connect: { id: context.pieceModels['motor-75kw'].id } }, + constructeur: { connect: { id: context.constructeurs['sew'].id } }, + typeMachinePieceRequirement: { + connect: { id: pieceRequirementMap.get('Moteurs de rechange')! }, + }, + customFieldValues: buildCustomFieldValues( + context.pieceTypes['electric-motor'].customFields, + { + 'Puissance nominale (kW)': 75, + 'Tension (V)': 400, + "Indice de protection": 'IP55', + }, + ), + }, + }); + + await prisma.piece.create({ + data: { + name: 'Capteur vitesse M12 de secours', + reference: 'SNS-VIT-SPARE-01', + prix: new Prisma.Decimal('190'), + machine: { connect: { id: machine.id } }, + typePiece: { connect: { id: context.pieceTypes['speed-sensor'].id } }, + pieceModel: { connect: { id: context.pieceModels['sensor-speed-m12'].id } }, + constructeur: { connect: { id: context.constructeurs['ifm'].id } }, + typeMachinePieceRequirement: { + connect: { id: pieceRequirementMap.get('Capteurs de vitesse de secours')! }, + }, + customFieldValues: buildCustomFieldValues( + context.pieceTypes['speed-sensor'].customFields, + { + 'Type de sortie': 'PNP 4-20 mA', + 'Plage de mesure (rpm)': 1200, + }, + ), + }, + }); + + console.log( + `✅ Machine créée avec ${components.length} composants et pièces critiques enregistrées.`, + ); + + return { + machine, + spareMotor, + }; +} + +async function main() { + try { + await clearDatabaseExceptSitesAndProfiles(); + + const [site, constructeurs] = await Promise.all([ + ensureDemoSite(), + createConstructeurs(), + ]); + + const { componentTypes, pieceTypes } = await createModelTypes(); + const [pieceModels, componentModels, typeMachine] = await Promise.all([ + createPieceModels(pieceTypes), + createComponentModels(componentTypes), + createTypeMachine(componentTypes, pieceTypes), + ]); + + await createMachineWithComponents(site.id, typeMachine, { + componentTypes, + componentModels, + pieceTypes, + pieceModels, + constructeurs, + }); + + console.log('🎉 Données de démonstration générées avec succès.'); + } catch (error) { + console.error('❌ Erreur pendant la génération des données :', error); + process.exitCode = 1; + } finally { + await prisma.$disconnect(); + } +} + +main();