33800292aa
- apps/api/Dockerfile: build NestJS, run prisma migrate deploy on start - apps/web/Dockerfile + nginx.conf: build Vite, serve static, proxy /api -> api - docker-compose.coolify.yml: full prod stack (postgres, redis, minio, keycloak, api, web) - .dockerignore / .gitignore / .gitattributes Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
739 lines
38 KiB
TypeScript
739 lines
38 KiB
TypeScript
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());
|