import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { console.log('🌱 Seeding reference data...'); // ── Disability Grades (grade de dizabilitate MD) ───────────────── await prisma.disabilityGrade.createMany({ data: [ { code: 'GRAD_I', name: 'Grad I (sever)' }, { code: 'GRAD_II', name: 'Grad II (accentuat)' }, { code: 'GRAD_III', name: 'Grad III (mediu)' }, ], skipDuplicates: true, }); console.log(' ✓ DisabilityGrade (3)'); // ── Tax Exemptions (scutiri Codul Fiscal RM) ───────────────────── await prisma.taxExemption.createMany({ data: [ { code: 'PE', description: 'Scutire personală (art. 33 CF)' }, { code: 'PI', description: 'Scutire personală majorată (art. 33 alin. 2 CF)' }, { code: 'SO', description: 'Scutire pentru soț/soție (art. 34 CF)' }, { code: 'MP1', description: 'Scutire pentru 1 copil minor (art. 35 CF)' }, { code: 'MP2', description: 'Scutire pentru 2 copii minori (art. 35 CF)' }, { code: 'MP3', description: 'Scutire pentru 3+ copii minori (art. 35 CF)' }, { code: 'INVALID', description: 'Scutire persoană cu dizabilitate (art. 33 alin. 2 lit. a CF)' }, ], skipDuplicates: true, }); console.log(' ✓ TaxExemption (7)'); // ── Work Schedules ─────────────────────────────────────────────── await prisma.workSchedule.createMany({ data: [ { name: '5/2 — 8h/zi', daysWork: 5, daysRest: 2, hoursPerDay: 8 }, { name: '5/2 — 7h/zi', daysWork: 5, daysRest: 2, hoursPerDay: 7 }, { name: 'Gărzi 24h (1/3)', daysWork: 1, daysRest: 3, hoursPerDay: 24 }, { name: 'Gărzi 12h zi (1/1)', daysWork: 1, daysRest: 1, hoursPerDay: 12 }, { name: 'Gărzi 12h noapte (1/1)',daysWork: 1, daysRest: 1, hoursPerDay: 12 }, { name: '7/7 — 12h/zi', daysWork: 7, daysRest: 7, hoursPerDay: 12 }, { name: 'Rotație 2/2 — 12h', daysWork: 2, daysRest: 2, hoursPerDay: 12 }, ], skipDuplicates: true, }); console.log(' ✓ WorkSchedule (7)'); // ── Departments — Medpark International Hospital ───────────────── // Level 0: hospital root const root = await prisma.department.upsert({ where: { code: 'MEDPARK' }, update: {}, create: { name: 'Medpark International Hospital', code: 'MEDPARK' }, }); // Helper to upsert a department const dept = async (name: string, code: string, parentId?: string) => prisma.department.upsert({ where: { code }, update: {}, create: { name, code, parentId: parentId ?? null }, }); // Administration const admin = await dept('Administrare', 'ADMIN', root.id); await dept('Resurse Umane', 'HR', admin.id); await dept('Financiar-Contabil', 'FIN', admin.id); await dept('Juridic', 'JUR', admin.id); await dept('IT', 'IT', admin.id); await dept('Achiziții', 'ACHIZ', admin.id); // Medical divisions const med = await dept('Bloc Medical', 'MED', root.id); const terapie = await dept('Terapie și Medicină Internă', 'TERAP', med.id); await dept('Cardiologie', 'CARDIO', terapie.id); await dept('Gastroenterologie', 'GASTRO', terapie.id); await dept('Endocrinologie', 'ENDO', terapie.id); await dept('Neurologie', 'NEURO', terapie.id); await dept('Pneumologie', 'PNEUMO', terapie.id); await dept('Reumatologie', 'REUMA', terapie.id); const chir = await dept('Chirurgie', 'CHIR', med.id); await dept('Chirurgie Generală', 'CHIR_GEN', chir.id); await dept('Chirurgie Vasculară', 'CHIR_VAS', chir.id); await dept('Ortopedie și Traumatologie','ORTOPED', chir.id); await dept('Urologie', 'UROL', chir.id); await dept('ORL', 'ORL', chir.id); await dept('Oftalmologie', 'OFTALMO', chir.id); const ped = await dept('Pediatrie', 'PED', med.id); await dept('Pediatrie Generală', 'PED_GEN', ped.id); await dept('Neonatologie', 'NEONAT', ped.id); const obst = await dept('Obstetrică-Ginecologie', 'OBG', med.id); await dept('Obstetrică', 'OBSTET', obst.id); await dept('Ginecologie', 'GINECO', obst.id); await dept('Oncologie', 'ONCOL', med.id); await dept('Hemodializă', 'HEMODIAL', med.id); await dept('Psihiatrie', 'PSIHIAT', med.id); await dept('Dermatologie', 'DERMA', med.id); await dept('Medicină Sportivă și Reabilitare', 'REAB', med.id); // Diagnostics const diag = await dept('Diagnostic', 'DIAG', root.id); await dept('Laborator Clinic', 'LAB', diag.id); await dept('Imagistică Medicală (CT/RMN/Rx)', 'IMAG', diag.id); await dept('Endoscopie', 'ENDOSC', diag.id); await dept('Ecografie', 'ECO', diag.id); await dept('Cardiologie Funcțională (ECG/Holter)', 'ECG', diag.id); // Support const suport = await dept('Servicii Suport', 'SUPORT', root.id); await dept('Urgențe (UPU)', 'UPU', suport.id); await dept('Anestezie și Terapie Intensivă (ATI)', 'ATI', suport.id); await dept('Bloc Operator', 'BLOC_OP', suport.id); await dept('Sterilizare', 'STERIL', suport.id); await dept('Farmacie', 'FARMACIE', suport.id); await dept('Nutriție și Dietetică', 'NUTRIT', suport.id); await dept('Serviciu Social', 'SOC', suport.id); await dept('Curățenie și Dezinfecție', 'CURATENIE',suport.id); await dept('Securitate', 'SECUR', suport.id); await dept('Tehnică Medicală', 'TEH_MED', suport.id); // Ambulatory const ambul = await dept('Centru Ambulator', 'AMBUL', root.id); await dept('Medicină de Familie', 'MED_FAM', ambul.id); await dept('Consultații Specializate', 'CONSULT', ambul.id); await dept('Fizioterapie', 'FIZIOTER', ambul.id); const deptCount = await prisma.department.count(); console.log(` ✓ Department (${deptCount})`); // ── Anexa Templates — minimal seed ───────────────────────── const heading = (text: string, level = 2) => ({ type: 'heading', attrs: { level, textAlign: 'center' }, content: [{ type: 'text', text }], }); const para = (content: object[], textAlign: string = 'left') => ({ type: 'paragraph', attrs: { textAlign }, content, }); const txt = (text: string, marks?: { type: string }[]) => marks ? { type: 'text', text, marks } : { type: 'text', text }; const chip = (key: string, label: string) => ({ type: 'variableChip', attrs: { key, label }, }); const cell = (content: object[]) => ({ type: 'tableCell', content }); const row = (cells: object[]) => ({ type: 'tableRow', content: cells }); const headerRow = (labels: string[]) => row(labels.map((l) => cell([para([txt(l, [{ type: 'bold' }])], 'center')]))); // ── Anexa 3: Fișa de solicitare ───────────────────────────────── const anexa3 = { type: 'doc', content: [ heading('FIȘA DE SOLICITARE A EXAMENULUI MEDICAL'), para([txt('Unitatea economică: '), chip('company.name', 'Denumirea unității')]), para([txt('IDNO: '), chip('company.idno', 'IDNO'), txt(' Adresa: '), chip('company.address', 'Adresa')]), para([txt('Tipul examenului: '), chip('tipExamen', 'Tipul examenului')]), para([txt('Departament: '), chip('department.name', 'Departament'), txt(' Carta de risc: '), chip('riskCard.name', 'Carta de risc')]), para([txt('Data: '), chip('document.date', 'Data documentului'), txt(' Nr.: '), chip('document.number', 'Număr')]), para([txt('Lista angajaților:', [{ type: 'bold' }])]), { type: 'table', attrs: { repeatRows: true }, content: [ headerRow(['Nr.', 'Nume Prenume', 'IDNP', 'Anul nașterii', 'Ocupația', 'Tipul examenului']), row([ cell([para([chip('row.index', 'Nr.')])]), cell([para([chip('row.employeeName', 'Nume Prenume')])]), cell([para([chip('row.idnp', 'IDNP')])]), cell([para([chip('row.birthYear', 'Anul nașterii')])]), cell([para([chip('row.occupation', 'Ocupația')])]), cell([para([chip('row.tipExamen', 'Tipul examenului')])]), ]), ], }, ], }; // ── Anexa 4: Fișa de evaluare a riscurilor profesionale (NU-10-MS-2026) ── const cb = (key: string, label: string) => [chip(`a4.cb.${key}`, '☐'), txt(' ' + label)]; const factorTable = (rowsKey: string, cols: string[], rowChips: string[]) => ({ type: 'table', attrs: { repeatRows: true, rowsKey }, content: [ headerRow(cols), row(rowChips.map((k) => cell([para([chip(k, '—')])]))), ], }); const anexa4 = { type: 'doc', content: [ // ── Antet ── para([txt('Unitatea economică/instituția: '), chip('a4.unitatea', 'Denumirea unității')]), para([txt('Adresa, telefon, fax, e-mail: '), chip('a4.adresa', 'Adresa')]), para([txt('Filiala: '), chip('a4.filiala', '—'), txt(' Adresa filialei: '), chip('a4.adresaFiliala', '—'), txt(' CAEM (primele 2 cifre): '), chip('a4.caem2', '—')]), heading('FIȘA de evaluare a riscurilor profesionale', 2), para([txt('Ocupația (subgrupa majoră CORM): '), chip('a4.cormSubgrupa', '—')]), para([txt('Direcția/secția/sectorul: '), chip('a4.directiaSectia', '—')]), para([txt('Numărul locului de muncă: '), chip('a4.numarLoc', '—'), txt(' CAEM (nivel diviziune): '), chip('a4.caemDiviziune', '—')]), para([txt('Numărul de lucrători care pot activa la acest loc de muncă: '), chip('a4.numarLucratori', '—'), txt(' Clasa condițiilor de muncă: '), chip('a4.clasa', '—')]), // ── Descrierea activității ── heading('Descrierea activității', 3), para([txt('Lucrul în echipă: '), ...cb('echipa', 'da'), txt(' Nr. ore/zi: '), chip('a4.val.oreZi', '—'), txt(' Nr. schimburi: '), chip('a4.val.schimburi', '—')]), para([...cb('schimbNoapte', 'schimb de noapte'), txt(' '), ...cb('pauzeOrganizate', 'pauze organizate')]), para([txt('Riscuri: '), ...cb('riscInfectare', 'infectare'), txt(' '), ...cb('riscElectrocutare', 'electrocutare'), txt(' '), ...cb('riscTensiuneInalta', 'tensiune înaltă'), txt(' '), ...cb('riscInecare', 'înecare'), txt(' '), ...cb('riscAsfixiere', 'asfixiere')]), para([...cb('riscStrivire', 'strivire'), txt(' '), ...cb('riscTaiere', 'tăiere'), txt(' '), ...cb('riscIntepare', 'înțepare'), txt(' '), ...cb('riscLovire', 'lovire'), txt(' '), ...cb('riscMuscatura', 'mușcătură'), txt(' '), ...cb('riscMicrotraumatisme', 'microtraumatisme repetate')]), para([txt('Conduce mașina instituției: '), ...cb('conduceMasina', 'da'), txt(' categorie: '), chip('a4.val.conduceMasinaCategorie', '—'), txt(' '), ...cb('conduceUtilajeIntrauzinal', 'conduce utilaje numai intrauzinal')]), // ── Spațiul de lucru ── heading('Descrierea spațiului de lucru', 3), para([txt('Dimensiunile încăperii: L '), chip('a4.val.spatiuL', '—'), txt(' l '), chip('a4.val.spatiul', '—'), txt(' H '), chip('a4.val.spatiuH', '—'), txt(' m')]), para([txt('Suprafața de lucru: '), ...cb('suprafataVerticala', 'verticală'), txt(' '), ...cb('suprafataOrizontala', 'orizontală'), txt(' '), ...cb('suprafataOblica', 'oblică')]), para([txt('Muncă: '), ...cb('muncaIzolare', 'în condiții de izolare'), txt(' '), ...cb('muncaInaltime', 'la înălțime'), txt(' '), ...cb('muncaInMiscare', 'în mișcare')]), // ── Efort fizic ── heading('Efort fizic', 3), para([txt('Poziție preponderent: '), ...cb('pozitieOrtostatica', 'ortostatică'), txt(' '), ...cb('pozitieAsezat', 'așezat'), txt(' '), ...cb('pozitieAplecata', 'aplecată'), txt(' '), ...cb('pozitieMixta', 'mixtă'), txt(' '), ...cb('pozitieFortata', 'forțată/nefiziologică')]), para([txt('Suprasolicitări musculo-articulare (coloană): '), ...cb('coloanaCervicala', 'cervicală'), txt(' '), ...cb('coloanaToracala', 'toracală'), txt(' '), ...cb('coloanaLombara', 'lombară')]), para([txt('Manipulare manuală a maselor: '), ...cb('manipulareRidicare', 'ridicare'), txt(' '), ...cb('manipulareCoborare', 'coborâre'), txt(' '), ...cb('manipulareImpingere', 'împingere'), txt(' '), ...cb('manipulareTragere', 'tragere'), txt(' '), ...cb('manipularePurtare', 'purtare'), txt(' '), ...cb('manipulareDeplasare', 'deplasare')]), para([txt('Greutate maximă manipulată manual: '), chip('a4.val.greutateMaxima', '—')]), para([txt('Suprasolicitări: '), ...cb('suprasolicitariVizuale', 'vizuale'), txt(' '), ...cb('suprasolicitariAuditive', 'auditive'), txt(' '), ...cb('suprasolicitariNeuropsihice', 'neuropsihosenzoriale')]), // ── Factori de risc cu tabel ── heading('AGENȚI CHIMICI', 3), para([...cb('chimici_da', 'da'), txt(' '), ...cb('chimici_nu', 'nu'), txt(' (se atașează Fișa cu date de securitate, în limba română)')]), factorTable('chimici', ['Agentul chimic', 'CAS', 'EINECS', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.cas', 'row.einecs', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), heading('PULBERI', 3), para([...cb('pulberi_da', 'da'), txt(' '), ...cb('pulberi_nu', 'nu')]), factorTable('pulberi', ['Pulberi', 'CAS', 'EINECS', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.cas', 'row.einecs', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), heading('AGENȚI BIOLOGICI', 3), para([...cb('biologici_da', 'da'), txt(' '), ...cb('biologici_nu', 'nu')]), factorTable('biologici', ['Agent biologic', 'Clasificare', 'Note'], ['row.denumire', 'row.clasificare', 'row.caracteristici']), heading('ZGOMOT PROFESIONAL', 3), para([...cb('zgomot_da', 'da'), txt(' '), ...cb('zgomot_nu', 'nu')]), factorTable('zgomot', ['Tipul', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), heading('VIBRAȚII MECANICE', 3), para([...cb('vibratii_da', 'da'), txt(' '), ...cb('vibratii_nu', 'nu')]), factorTable('vibratii', ['Tipul', 'Zona afectată', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.zona', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), // ── Microclimat (descriptiv) ── heading('MICROCLIMAT', 3), para([...cb('microclimatInterior', 'lucrări interior'), txt(' '), ...cb('microclimatExterior', 'lucru exterior/sub cerul liber')]), para([txt('Radiații calorice (perioada rece): '), ...cb('radiatiiCaloriceRece', 'da'), txt(' Radiații calorice (perioada caldă): '), ...cb('radiatiiCaloriceCalda', 'da')]), // ── Radiații ionizante ── heading('RADIAȚII IONIZANTE', 3), para([...cb('radiatii_da', 'da'), txt(' '), ...cb('radiatii_nu', 'nu'), txt(' Grupa: '), chip('a4.rad.grupa', '—')]), para([txt('Aparatură folosită: '), chip('a4.rad.aparatura', '—'), txt(' Surse: '), chip('a4.rad.surse', '—')]), para([txt('Tip de expunere: '), chip('a4.rad.tipExpunere', '—'), txt(' Măsuri de protecție: '), chip('a4.rad.masuriProtectie', '—')]), // ── Câmp electromagnetic ── heading('CÂMP ELECTROMAGNETIC', 3), para([...cb('campEM_da', 'da'), txt(' '), ...cb('campEM_nu', 'nu')]), factorTable('campEM', ['Tipul', 'Zona afectată', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.zona', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), // ── Iluminat ── heading('ILUMINAT', 3), para([...cb('iluminatSuficient', 'suficient'), txt(' '), ...cb('iluminatInsuficient', 'insuficient'), txt(' '), ...cb('iluminatNatural', 'natural'), txt(' '), ...cb('iluminatArtificial', 'artificial'), txt(' '), ...cb('iluminatMixt', 'mixt')]), // ── Radiații optice artificiale ── heading('RADIAȚII OPTICE ARTIFICIALE', 3), para([...cb('optice_da', 'da'), txt(' '), ...cb('optice_nu', 'nu')]), factorTable('optice', ['Tipul', 'Zona afectată', 'Timp expunere', 'VEP', 'VLEP obligatorie', 'Caracteristici'], ['row.denumire', 'row.zona', 'row.timp', 'row.vep', 'row.vlep', 'row.caracteristici']), // ── Subsol ── heading('Protecție și dotări', 3), para([txt('Mijloace de protecție colectivă: '), chip('a4.protectieColectiva', '—')]), para([txt('Mijloace de protecție individuală: '), chip('a4.protectieIndividuala', '—')]), para([txt('Echipament de lucru: '), chip('a4.echipament', '—')]), para([txt('Anexe igienico-sanitare: '), ...cb('anexe.vestiar', 'vestiar'), txt(' '), ...cb('anexe.chiuveta', 'chiuvetă'), txt(' '), ...cb('anexe.wc', 'WC'), txt(' '), ...cb('anexe.dus', 'duș'), txt(' '), ...cb('anexe.salaMese', 'sală de mese'), txt(' '), ...cb('anexe.recreere', 'spațiu de recreere')]), para([txt('Observații: '), chip('a4.observatii', '—')]), para([txt('Data completării: '), chip('document.date', 'Data')]), para([txt('Angajatorul (nume, prenume, semnătura): ____________________')]), para([txt('Instrucțiuni de completare: răspuns afirmativ [☑]; răspuns negativ [☐].', [{ type: 'italic' }])]), ], }; // ── Anexa 4B: Supliment radiații ionizante ─────────────────────── const anexa4b = { type: 'doc', content: [ heading('SUPLIMENT — EXPUNERE LA RADIAȚII IONIZANTE'), para([txt('Unitatea economică: '), chip('company.name', 'Denumirea unității')]), para([txt('Data: '), chip('document.date', 'Data documentului')]), para([txt('Personal expus radiațiilor ionizante:', [{ type: 'bold' }])]), { type: 'table', attrs: { repeatRows: true }, content: [ headerRow(['Nr.', 'Nume Prenume', 'IDNP', 'Data intrării', 'Perioada anterioară', 'Ani', 'Doza ext. (mSv)', 'Doza int. (mSv)', 'Total (mSv)']), row([ cell([para([chip('row.index', 'Nr.')])]), cell([para([chip('row.employeeName', 'Nume Prenume')])]), cell([para([chip('row.idnp', 'IDNP')])]), cell([para([chip('row.entryDate', 'Data intrării')])]), cell([para([chip('row.priorPeriod', 'Perioada anterioară')])]), cell([para([chip('row.priorYears', 'Ani')])]), cell([para([chip('row.externalMsv', 'Doza ext.')])]), cell([para([chip('row.internalMsv', 'Doza int.')])]), cell([para([chip('row.totalMsv', 'Total')])]), ]), ], }, ], }; // ── Anexa 6: Verdict medic de familie (per-employee) ───────────── const anexa6 = { type: 'doc', content: [ heading('FIȘĂ DE APTITUDINE — VERDICTUL MEDICULUI DE FAMILIE'), para([txt('Angajat: '), chip('employee.fullName', 'Nume Prenume')]), para([txt('IDNP: '), chip('employee.idnp', 'IDNP'), txt(' Data nașterii: '), chip('employee.birthDate', 'Data nașterii')]), para([txt('Ocupația: '), chip('employee.occupation', 'Ocupația'), txt(' Departament: '), chip('employee.department', 'Departament')]), para([txt('Tipul examenului: '), chip('tipExamen', 'Tipul examenului')]), para([txt('Data examinării: '), chip('document.date', 'Data')]), para([txt('Verdict:', [{ type: 'bold' }])]), para([chip('verdict.checkbox.apt', '☐'), txt(' Apt')]), para([chip('verdict.checkbox.apt_perioada_adaptare', '☐'), txt(' Apt în perioada de adaptare')]), para([chip('verdict.checkbox.apt_conditionat', '☐'), txt(' Apt condiționat')]), para([chip('verdict.checkbox.inapt_temporar', '☐'), txt(' Inapt temporar')]), para([chip('verdict.checkbox.inapt', '☐'), txt(' Inapt')]), para([txt('Recomandări: '), chip('verdict.recomandari', 'Recomandări')]), para([txt(' ')]), para([txt('Semnătura medicului de familie: ____________________')]), ], }; const SYS = '00000000-0000-0000-0000-000000000000'; const templates: Array<{ type: 'ANEXA_3' | 'ANEXA_4' | 'ANEXA_4B' | 'ANEXA_6'; name: string; doc: object }> = [ { type: 'ANEXA_3', name: 'Fișa de solicitare a examenului medical', doc: anexa3 }, { type: 'ANEXA_4', name: 'Fișa de evaluare a locului de muncă', doc: anexa4 }, { type: 'ANEXA_4B', name: 'Supliment radiații ionizante', doc: anexa4b }, { type: 'ANEXA_6', name: 'Verdict medic de familie', doc: anexa6 }, ]; for (const t of templates) { await prisma.anexaTemplate.upsert({ where: { type: t.type }, update: { name: t.name, contentJson: t.doc as never }, create: { type: t.type, name: t.name, contentJson: t.doc as never, updatedById: SYS }, }); } console.log(' ✓ AnexaTemplate (4)'); // ── Inventory items (depozit Vestimentație + Echipament) ───────── const inventory = [ { sku: 'UN-CHIR-S-AL', name: 'Uniformă chirurgie S albastru', type: 'uniforma' as const, size: 'S', color: 'albastru', stockQty: 50 }, { sku: 'UN-CHIR-M-AL', name: 'Uniformă chirurgie M albastru', type: 'uniforma' as const, size: 'M', color: 'albastru', stockQty: 50 }, { sku: 'UN-CHIR-L-AL', name: 'Uniformă chirurgie L albastru', type: 'uniforma' as const, size: 'L', color: 'albastru', stockQty: 50 }, { sku: 'UN-ATI-M-VE', name: 'Uniformă ATI M verde', type: 'uniforma' as const, size: 'M', color: 'verde', stockQty: 30 }, { sku: 'HA-MED-M-AL', name: 'Halat medical M alb', type: 'halat' as const, size: 'M', color: 'alb', stockQty: 50 }, { sku: 'HA-MED-L-AL', name: 'Halat medical L alb', type: 'halat' as const, size: 'L', color: 'alb', stockQty: 50 }, { sku: 'HA-LAB-M-AL', name: 'Halat laborator M alb', type: 'halat' as const, size: 'M', color: 'alb', stockQty: 30 }, { sku: 'HA-LAB-L-AL', name: 'Halat laborator L alb', type: 'halat' as const, size: 'L', color: 'alb', stockQty: 30 }, { sku: 'CI-38-AL', name: 'Ciupici 38-40 albi', type: 'ciupici' as const, size: '38-40', color: 'alb', stockQty: 80 }, { sku: 'CI-41-AL', name: 'Ciupici 41-43 albi', type: 'ciupici' as const, size: '41-43', color: 'alb', stockQty: 80 }, { sku: 'CI-44-AL', name: 'Ciupici 44-46 albi', type: 'ciupici' as const, size: '44-46', color: 'alb', stockQty: 80 }, { sku: 'VE-S-TE', name: 'Vestă S teal', type: 'vesta' as const, size: 'S', color: 'teal', stockQty: 20 }, { sku: 'VE-M-TE', name: 'Vestă M teal', type: 'vesta' as const, size: 'M', color: 'teal', stockQty: 20 }, { sku: 'AT-SAMS-A15', name: 'Samsung Galaxy A15', type: 'aparat_telefon' as const, stockQty: 15 }, { sku: 'AT-IPHONE-SE', name: 'iPhone SE 2022', type: 'aparat_telefon' as const, stockQty: 10 }, ]; for (const item of inventory) { await prisma.inventoryItem.upsert({ where: { sku: item.sku }, update: {}, create: item, }); } console.log(` ✓ InventoryItem (${inventory.length})`); // ── Demo data pentru prezentare ────────────────────────────── console.log('\n🎭 Seeding demo data...'); // Risk cards const chirExposures = [ { tip: 'AGENT_CHIMIC' as const, denumire: 'Glutaraldehidă (dezinfectant)', cas: '111-30-8', einecs: '203-856-5', timpExpunere: '2 h/zi', vep: '0,03 ppm', vlep: '0,1 ppm', caracteristici: 'iritant respirator' }, { tip: 'AGENT_BIOLOGIC' as const, denumire: 'Virusuri hematogene (HBV, HCV, HIV)', clasificare: 'grupa 3', caracteristici: 'risc de infectare prin înțepare/tăiere' }, ]; const chirHeader = { filiala: 'Sediul central', caemPrimeleDouaCifre: '86', cormSubgrupaMajora: 'Personal medical — secție chirurgie', directiaSectiaSectorul: 'Bloc Medical / Chirurgie Generală', numarulLoculuiDeMunca: 'CH-01', caemDiviziune: '86.10', clasaConditiilorDeMunca: '3.2', numarLucratoriPosibili: 12, evaluareDetalii: { echipa: true, oreZi: '8', schimburi: '2', schimbNoapte: true, pauzeOrganizate: true, riscInfectare: true, riscTaiere: true, riscIntepare: true, pozitieOrtostatica: true, manipulareRidicare: true, suprasolicitariVizuale: true, }, anexeIgienicoSanitare: { vestiar: true, chiuveta: true, wc: true, dus: true, salaMese: true }, mijloaceProtectieIndividuala: 'Mănuși, mască, halat steril', echipamentLucru: 'Uniformă chirurgicală', }; const rcChir = await prisma.workplaceRiskCard.upsert({ where: { name: 'Secție chirurgie generală' }, update: { ...chirHeader, exposures: { deleteMany: {}, create: chirExposures } }, create: { name: 'Secție chirurgie generală', ...chirHeader, exposures: { create: chirExposures } }, }); const imagExposures = [ { tip: 'CAMP_ELECTROMAGNETIC' as const, denumire: 'Câmp electromagnetic RMN', zonaAfectata: 'corp întreg', timpExpunere: '4 h/zi', vep: '—', vlep: 'conform NU-10', caracteristici: 'câmp magnetic static intens' }, ]; const imagHeader = { filiala: 'Sediul central', caemPrimeleDouaCifre: '86', cormSubgrupaMajora: 'Personal imagistică medicală', directiaSectiaSectorul: 'Diagnostic / Imagistică Medicală', numarulLoculuiDeMunca: 'IMG-01', caemDiviziune: '86.90', clasaConditiilorDeMunca: '3.3', numarLucratoriPosibili: 8, radiatiiIonizante: true, radiatiiGrupa: 'A', radiatiiSurse: 'închise', radiatiiTipExpunere: 'X externă', radiatiiAparatura: 'CT, aparat Rx', radiatiiMasuriProtectie: 'șorț cu plumb, ecran de protecție, dozimetru individual', evaluareDetalii: { echipa: true, oreZi: '7', schimburi: '2', riscElectrocutare: true, pozitieAsezat: true, suprasolicitariVizuale: true, }, anexeIgienicoSanitare: { vestiar: true, chiuveta: true, wc: true }, mijloaceProtectieIndividuala: 'Șorț cu plumb, ochelari, dozimetru', }; const rcImag = await prisma.workplaceRiskCard.upsert({ where: { name: 'Radiologie și imagistică' }, update: { ...imagHeader, exposures: { deleteMany: {}, create: imagExposures } }, create: { name: 'Radiologie și imagistică', ...imagHeader, exposures: { create: imagExposures } }, }); console.log(' ✓ WorkplaceRiskCard demo (2) — cu antet Anexa 4 + factori'); // Lookup departments & inventory items const chirGenDept = await prisma.department.findUnique({ where: { code: 'CHIR_GEN' } }); const imagDept = await prisma.department.findUnique({ where: { code: 'IMAG' } }); const uniformaS = await prisma.inventoryItem.findUnique({ where: { sku: 'UN-CHIR-S-AL' } }); const halatM = await prisma.inventoryItem.findUnique({ where: { sku: 'HA-MED-M-AL' } }); // 4 demo employees (IDNPs pre-validated cu algoritmul de sumă de control MD) const emp1 = await prisma.employee.upsert({ where: { idnp: '1985061500016' }, update: {}, create: { idnp: '1985061500016', nume: 'Popescu', prenume: 'Alexandru', sex: 'M', dataNasterii: new Date('1985-06-15'), domiciliu: 'mun. Chișinău, str. Ștefan cel Mare 1', telefonPersonal: '+37369100001', status: 'activ', }, }); const emp2 = await prisma.employee.upsert({ where: { idnp: '1990032200017' }, update: {}, create: { idnp: '1990032200017', nume: 'Ionescu', prenume: 'Maria', sex: 'F', dataNasterii: new Date('1990-03-22'), domiciliu: 'mun. Chișinău, str. Mihai Viteazul 5', telefonPersonal: '+37369100002', status: 'activ', }, }); const emp3 = await prisma.employee.upsert({ where: { idnp: '1978110800016' }, update: {}, create: { idnp: '1978110800016', nume: 'Rusu', prenume: 'Viorel', sex: 'M', dataNasterii: new Date('1978-11-08'), domiciliu: 'mun. Chișinău, str. Alba Iulia 12', telefonPersonal: '+37369100003', status: 'activ', }, }); const emp4 = await prisma.employee.upsert({ where: { idnp: '2001091400010' }, update: {}, create: { idnp: '2001091400010', nume: 'Cojocaru', prenume: 'Elena', sex: 'F', dataNasterii: new Date('2001-09-14'), domiciliu: 'mun. Chișinău, str. Trandafirilor 3', telefonPersonal: '+37369100004', status: 'activ', }, }); console.log(' ✓ Employee demo (4)'); // Employment contracts if (chirGenDept) { await prisma.employmentContract.upsert({ where: { nrCim: 'CIM-DEMO-001' }, update: {}, create: { nrCim: 'CIM-DEMO-001', employeeId: emp1.id, categorie: 'principal', tipCim: 'de_baza', perioada: 'nedeterminata', dataSemnarii: new Date('2020-01-10'), dataAngajarii: new Date('2020-01-15'), departmentId: chirGenDept.id, functiaOrganigrama: 'Chirurg', salarizareDetails: { tip: 'fix', salariu: 18000, zileConcediu: 28 }, }, }); await prisma.employmentContract.upsert({ where: { nrCim: 'CIM-DEMO-002' }, update: {}, create: { nrCim: 'CIM-DEMO-002', employeeId: emp2.id, categorie: 'principal', tipCim: 'de_baza', perioada: 'nedeterminata', dataSemnarii: new Date('2021-03-01'), dataAngajarii: new Date('2021-03-05'), departmentId: chirGenDept.id, functiaOrganigrama: 'Asistentă medicală', salarizareDetails: { tip: 'fix', salariu: 10000, zileConcediu: 28 }, }, }); } if (imagDept) { await prisma.employmentContract.upsert({ where: { nrCim: 'CIM-DEMO-003' }, update: {}, create: { nrCim: 'CIM-DEMO-003', employeeId: emp3.id, categorie: 'principal', tipCim: 'de_baza', perioada: 'nedeterminata', dataSemnarii: new Date('2018-06-01'), dataAngajarii: new Date('2018-06-10'), departmentId: imagDept.id, functiaOrganigrama: 'Radiolog', salarizareDetails: { tip: 'fix', salariu: 20000, zileConcediu: 35 }, }, }); await prisma.employmentContract.upsert({ where: { nrCim: 'CIM-DEMO-004' }, update: {}, create: { nrCim: 'CIM-DEMO-004', employeeId: emp4.id, categorie: 'principal', tipCim: 'de_baza', perioada: 'nedeterminata', dataSemnarii: new Date('2023-09-01'), dataAngajarii: new Date('2023-09-15'), departmentId: imagDept.id, functiaOrganigrama: 'Asistentă radiologie', salarizareDetails: { tip: 'fix', salariu: 9500, zileConcediu: 28 }, }, }); } console.log(' ✓ EmploymentContract demo (4)'); // Medical profiles: // emp1 — chirurgie, niciodată examinat // emp2 — chirurgie, examinat acum 15 luni (expirat) // emp3 — radiologie, examinat acum 11 luni + radiații (expiră curând) // emp4 — radiologie, niciodată examinat + radiații await prisma.employeeMedicalProfile.upsert({ where: { employeeId: emp1.id }, update: {}, create: { employeeId: emp1.id, workplaceRiskCardId: rcChir.id, expusRadiatiiIonizante: false }, }); await prisma.employeeMedicalProfile.upsert({ where: { employeeId: emp2.id }, update: {}, create: { employeeId: emp2.id, workplaceRiskCardId: rcChir.id, dataUltimControlMedical: new Date('2025-02-14'), expusRadiatiiIonizante: false, }, }); const emp3Radiatii = { workplaceRiskCardId: rcImag.id, dataUltimControlMedical: new Date('2025-06-14'), expusRadiatiiIonizante: true, dataIntrarii: new Date('2019-02-01'), expunereAnterioaraPerioda: '2015–2018', expunereAnterioaraAni: 3, dozaCumulataExternaMsv: 4.2500, dozaCumulataInternaMsv: 0.8000, }; const emp3Supra = [ { fel: 'EXCEPTIONALA' as const, tipExpunere: 'X externă', data: new Date('2023-05-12'), dozaMsv: 2.5000 }, { fel: 'ACCIDENTALA' as const, tipExpunere: 'gamma externă', data: new Date('2024-09-03'), dozaMsv: 1.2000 }, ]; await prisma.employeeMedicalProfile.upsert({ where: { employeeId: emp3.id }, update: { ...emp3Radiatii, overexposures: { deleteMany: {}, create: emp3Supra } }, create: { employeeId: emp3.id, ...emp3Radiatii, overexposures: { create: emp3Supra } }, }); await prisma.employeeMedicalProfile.upsert({ where: { employeeId: emp4.id }, update: {}, create: { employeeId: emp4.id, workplaceRiskCardId: rcImag.id, expusRadiatiiIonizante: true }, }); console.log(' ✓ EmployeeMedicalProfile demo (4)'); // Pending checkups for inbox (verdict = null) // emp1 — la_angajare, acum 5 zile (depășit → roșu) // emp2 — periodic, peste 3 zile // emp3 — la_reluarea_activitatii, mâine const day = (offsetDays: number) => { const d = new Date('2026-05-14'); d.setDate(d.getDate() + offsetDays); return d; }; for (const [empId, tip, offset] of [ [emp1.id, 'la_angajare', -5], [emp2.id, 'periodic', 3], [emp3.id, 'la_reluarea_activitatii', 1], ] as [string, string, number][]) { const exists = await prisma.medicalCheckup.findFirst({ where: { employeeId: empId, verdict: null } }); if (!exists) { await prisma.medicalCheckup.create({ data: { employeeId: empId, tip: tip as never, dataPlanificata: day(offset) }, }); } } console.log(' ✓ MedicalCheckup demo — pending inbox (3)'); // ── Evaluation campaigns demo (modulul de evaluare nursing) ────── // Campania A — Chirurgie Generală, IN_PROGRESS: // emp2 (Ionescu Maria) — formular complet, scoruri bune + 1 criteriu EXPERT // → categorie calculată "superioara", ÎNCĂ NEAPROBATĂ // (nursing_director o poate aproba — demo aprobare) // emp1 (Popescu Alexandru) — formular parțial (în lucru) → "fara" if (chirGenDept) { const campMonth = new Date('2026-05-01'); let camp = await prisma.evaluationCampaign.findFirst({ where: { departmentId: chirGenDept.id, month: campMonth }, }); if (!camp) { camp = await prisma.evaluationCampaign.create({ data: { name: 'Evaluare anuală nursing — Chirurgie Generală 2026', departmentId: chirGenDept.id, month: campMonth, status: 'in_progress', }, }); } await prisma.evaluationForm.upsert({ where: { campaignId_employeeId: { campaignId: camp.id, employeeId: emp2.id } }, update: {}, create: { campaignId: camp.id, employeeId: emp2.id, abilitatiClinice: 'bine', judecataClinica: 'bine', manopere: 'bine', gestionareaSarcinilor: 'mediu', constiintaProfesionala: 'bine', atitudineaPacienti: 'bine', atitudineaColegi: 'bine', atitudineaPersonalNonMed: 'mediu', utilizareSmartphone: 'bine', respectareaProgramului: 'bine', respectareaDressCode: 'bine', testJci: { score: 18, max_score: 20, percent: 90, completed_at: '2026-05-10', source: 'academy_ocean', external_id: 'AO-DEMO-001' }, completareaDocMed: true, perfectioneazaCunostinte: true, membruComitetCalitate: true, functieDeMonitor: false, inlocuiesteSuperiorul: false, categorieCalculata: 'superioara', }, }); await prisma.evaluationForm.upsert({ where: { campaignId_employeeId: { campaignId: camp.id, employeeId: emp1.id } }, update: {}, create: { campaignId: camp.id, employeeId: emp1.id, abilitatiClinice: 'bine', judecataClinica: 'mediu', manopere: 'mediu', categorieCalculata: 'fara', }, }); console.log(' ✓ EvaluationCampaign demo — Chirurgie (in_progress, 2 formulare)'); } // Campania B — Imagistică, CLOSED (istoric read-only): // emp3 (Rusu Viorel) — formular finalizat și aprobat → "cat_I" if (imagDept) { const campMonth = new Date('2025-11-01'); let camp = await prisma.evaluationCampaign.findFirst({ where: { departmentId: imagDept.id, month: campMonth }, }); if (!camp) { camp = await prisma.evaluationCampaign.create({ data: { name: 'Evaluare anuală nursing — Imagistică 2025', departmentId: imagDept.id, month: campMonth, status: 'closed', }, }); } await prisma.evaluationForm.upsert({ where: { campaignId_employeeId: { campaignId: camp.id, employeeId: emp3.id } }, update: {}, create: { campaignId: camp.id, employeeId: emp3.id, abilitatiClinice: 'bine', judecataClinica: 'bine', manopere: 'bine', gestionareaSarcinilor: 'bine', constiintaProfesionala: 'bine', atitudineaPacienti: 'mediu', atitudineaColegi: 'bine', atitudineaPersonalNonMed: 'bine', utilizareSmartphone: 'bine', respectareaProgramului: 'bine', respectareaDressCode: 'mediu', completareaDocMed: true, perfectioneazaCunostinte: true, membruComitetCalitate: false, functieDeMonitor: false, inlocuiesteSuperiorul: false, categorieCalculata: 'cat_I', categorieAprobata: 'cat_I', observatii: 'Performanță constantă, recomandat pentru categoria I.', completedAt: new Date('2025-11-20'), }, }); console.log(' ✓ EvaluationCampaign demo — Imagistică (closed, 1 formular aprobat)'); } // Benefit cu vestimentație pentru emp1 if (uniformaS && halatM) { await prisma.benefit.upsert({ where: { employeeId: emp1.id }, update: {}, create: { employeeId: emp1.id, uniformaId: uniformaS.id, halatId: halatM.id, ticheteMasa: true, valoareTichet: 65, alimentatiePersonal: false, abonamentTel: 150, }, }); console.log(' ✓ Benefit demo (1) — Popescu Alexandru: uniformă + halat'); } console.log('\n✅ Seed complete.'); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());