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>
830 lines
28 KiB
Plaintext
830 lines
28 KiB
Plaintext
// HRM Medpark — Prisma Schema
|
|
// Phase 1: Employee Master Data + Department + AuditLog
|
|
// Phase 2 stubs: EmploymentContract
|
|
// Phase 4 stubs: EvaluationCampaign, EvaluationForm
|
|
// Phase 5 stubs: WorkplaceRiskCard, EmployeeMedicalProfile, MedicalCheckup
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// ENUMS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
enum Sex {
|
|
F
|
|
M
|
|
}
|
|
|
|
enum MaritalStatus {
|
|
casatorit
|
|
necasatorit
|
|
divortat
|
|
vaduv
|
|
}
|
|
|
|
enum EmployeeStatus {
|
|
activ
|
|
concediat
|
|
suspendat
|
|
}
|
|
|
|
enum DocumentType {
|
|
buletin_de_identitate
|
|
pasaport
|
|
}
|
|
|
|
enum FamilyMemberType {
|
|
contact_principal
|
|
sot
|
|
sotie
|
|
mama
|
|
tata
|
|
copil
|
|
}
|
|
|
|
enum StudyType {
|
|
superioare
|
|
medii_de_specialitate
|
|
secundare_tehnice
|
|
medii
|
|
}
|
|
|
|
enum StudyLevel {
|
|
de_baza
|
|
postuniversitar
|
|
}
|
|
|
|
enum PostUniversityType {
|
|
masterat
|
|
rezidentiat
|
|
secundariat
|
|
altele
|
|
}
|
|
|
|
enum DiplomaStatus {
|
|
confirmata
|
|
neconfirmata
|
|
}
|
|
|
|
enum QualificationCategory {
|
|
fara
|
|
cat_II
|
|
cat_I
|
|
superioara
|
|
}
|
|
|
|
enum ScientificTitle {
|
|
doctor
|
|
doctor_habilitat
|
|
}
|
|
|
|
enum TrainingType {
|
|
orientare
|
|
intern
|
|
extern_RM
|
|
extern_international
|
|
}
|
|
|
|
enum DisciplinarySanctionType {
|
|
avertisment
|
|
mustrare
|
|
mustrare_aspra
|
|
}
|
|
|
|
// Phase 2
|
|
enum ContractPeriod {
|
|
determinata
|
|
nedeterminata
|
|
replasare_temporara
|
|
}
|
|
|
|
enum ContractCategory {
|
|
principal
|
|
secundar
|
|
}
|
|
|
|
enum ContractType {
|
|
de_baza
|
|
cumul
|
|
}
|
|
|
|
enum SalaryType {
|
|
fix
|
|
pe_ore
|
|
in_acord
|
|
}
|
|
|
|
// Phase 4
|
|
enum CampaignStatus {
|
|
draft
|
|
scheduled
|
|
in_progress
|
|
closed
|
|
}
|
|
|
|
enum EvaluationScore {
|
|
slab
|
|
mediu
|
|
bine
|
|
}
|
|
|
|
enum ProposedCategory {
|
|
fara
|
|
cat_II
|
|
cat_I
|
|
superioara
|
|
}
|
|
|
|
// Phase 5
|
|
enum MedicalCheckupType {
|
|
la_angajare
|
|
periodic
|
|
la_reluarea_activitatii
|
|
la_incetarea_expunerii
|
|
suplimentar
|
|
}
|
|
|
|
enum MedicalVerdict {
|
|
apt
|
|
apt_perioada_adaptare
|
|
apt_conditionat
|
|
inapt_temporar
|
|
inapt
|
|
}
|
|
|
|
enum AnexaType {
|
|
ANEXA_3
|
|
ANEXA_4
|
|
ANEXA_4A
|
|
ANEXA_4B
|
|
ANEXA_6
|
|
}
|
|
|
|
// Tipuri de factori cu tabel de expunere în Anexa 4 (NU-10-MS-2026)
|
|
enum RiskExposureType {
|
|
AGENT_CHIMIC
|
|
PULBERI
|
|
AGENT_BIOLOGIC
|
|
ZGOMOT
|
|
VIBRATII
|
|
CAMP_ELECTROMAGNETIC
|
|
RADIATII_OPTICE
|
|
}
|
|
|
|
// Tipul supraexpunerii la radiații ionizante (Anexa 4B)
|
|
enum OverexposureKind {
|
|
EXCEPTIONALA
|
|
ACCIDENTALA
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// СПРАВОЧНИКИ
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model DisabilityGrade {
|
|
id String @id @default(uuid())
|
|
code String @unique
|
|
name String
|
|
employees Employee[]
|
|
|
|
@@map("disability_grades")
|
|
}
|
|
|
|
model TaxExemption {
|
|
id String @id @default(uuid())
|
|
code String @unique
|
|
description String
|
|
familyMembers FamilyMember[]
|
|
|
|
@@map("tax_exemptions")
|
|
}
|
|
|
|
model WorkSchedule {
|
|
id String @id @default(uuid())
|
|
name String @unique // "5/2 8h", "7/7 12h"
|
|
daysWork Int
|
|
daysRest Int
|
|
hoursPerDay Int
|
|
contracts EmploymentContract[]
|
|
|
|
@@map("work_schedules")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// DEPARTMENT — иерархия (adjacency list)
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Department {
|
|
id String @id @default(uuid())
|
|
name String
|
|
code String? @unique
|
|
parentId String?
|
|
parent Department? @relation("DeptTree", fields: [parentId], references: [id])
|
|
children Department[] @relation("DeptTree")
|
|
|
|
contracts EmploymentContract[]
|
|
campaigns EvaluationCampaign[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([parentId])
|
|
@@map("departments")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// EMPLOYEE — ядро системы
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Employee {
|
|
id String @id @default(uuid())
|
|
// IDNP — 13 цифр, алгоритм контрольной суммы валидируется на app-уровне
|
|
idnp String @unique @db.VarChar(13)
|
|
|
|
// A. Личная информация
|
|
nume String
|
|
prenume String
|
|
patronimic String?
|
|
numeAnterior String?
|
|
dataNasterii DateTime @db.Date
|
|
domiciliu String
|
|
adresaReala String?
|
|
telefonPersonal String
|
|
telefonServiciu String?
|
|
emailPersonal String?
|
|
emailCorporativ String?
|
|
sex Sex
|
|
codCpas String?
|
|
stareCivila MaritalStatus?
|
|
|
|
// Научное/университетское звание (уровень Employee, не Qualification)
|
|
titluStiintific ScientificTitle?
|
|
titluUniversitar String?
|
|
|
|
status EmployeeStatus @default(activ)
|
|
|
|
gradDizabilitateId String?
|
|
gradDizabilitate DisabilityGrade? @relation(fields: [gradDizabilitateId], references: [id])
|
|
|
|
// Кто рекомендовал (самоссылка)
|
|
// Бизнес-правило: нельзя выбрать супруга текущего сотрудника — проверка на service-уровне
|
|
recomandareInternaId String?
|
|
recomandareInterna Employee? @relation("Recomandari", fields: [recomandareInternaId], references: [id])
|
|
recomandat Employee[] @relation("Recomandari")
|
|
|
|
// Связанные сущности
|
|
identityDocuments IdentityDocument[]
|
|
familyMembers FamilyMember[]
|
|
educations Education[]
|
|
qualifications Qualification[]
|
|
trainings Training[]
|
|
disciplinarySanctions DisciplinarySanction[]
|
|
contracts EmploymentContract[]
|
|
benefit Benefit?
|
|
evaluationForms EvaluationForm[]
|
|
medicalProfile EmployeeMedicalProfile?
|
|
medicalCheckups MedicalCheckup[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([idnp])
|
|
@@index([nume, prenume])
|
|
@@index([status])
|
|
@@index([dataNasterii])
|
|
@@map("employees")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// B. IDENTITY DOCUMENT
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model IdentityDocument {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
tipAct DocumentType
|
|
seria String?
|
|
nr String
|
|
dataEmiterii DateTime @db.Date
|
|
autoritateEmitenta String
|
|
dataExpirarii DateTime @db.Date
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Cron-задача за 30 дней до dataExpirarii → HR Inbox
|
|
@@index([employeeId])
|
|
@@index([dataExpirarii])
|
|
@@map("identity_documents")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// C. FAMILY MEMBERS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model FamilyMember {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
tip FamilyMemberType
|
|
numePrenume String
|
|
dataNasterii DateTime? @db.Date
|
|
idnp String? @db.VarChar(13)
|
|
telefon String? // обязателен для contact_principal — проверка на service-уровне
|
|
|
|
// Скидки FISC (только для copil)
|
|
tipScutireId String?
|
|
tipScutire TaxExemption? @relation(fields: [tipScutireId], references: [id])
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([employeeId])
|
|
@@map("family_members")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// D. EDUCATION
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Education {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
tipStudii StudyType
|
|
institutia String
|
|
specialitatea String
|
|
dataAbsolvirii DateTime? @db.Date
|
|
nrSeriaDiploma String?
|
|
dataEmiterii DateTime? @db.Date
|
|
nrInregistrare String?
|
|
confirmare DiplomaStatus?
|
|
nivel StudyLevel?
|
|
tipPostuniversitar PostUniversityType?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([employeeId])
|
|
@@map("educations")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// E. QUALIFICATIONS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Qualification {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
categorie QualificationCategory
|
|
dataObtinerii DateTime? @db.Date
|
|
dataUltimeiConfirmari DateTime? @db.Date
|
|
dataExpirarii DateTime? @db.Date
|
|
specialitate String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Cron-задача за 90/30/7 дней до dataExpirarii → HR + manager
|
|
@@index([employeeId])
|
|
@@index([dataExpirarii])
|
|
@@map("qualifications")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// F. TRAINING
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Training {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
denumire String
|
|
inceput DateTime @db.Date
|
|
sfirsit DateTime? @db.Date
|
|
tip TrainingType
|
|
tara String?
|
|
nrOre Int?
|
|
organizatia String?
|
|
certificat Boolean @default(false)
|
|
cost Decimal? @db.Decimal(10, 2)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([employeeId])
|
|
@@map("trainings")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// G. DISCIPLINARY SANCTIONS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model DisciplinarySanction {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
tip DisciplinarySanctionType
|
|
dataAplicarii DateTime @db.Date
|
|
// auto-calc: dataAplicarii + 6 months — вычисляется на service-уровне при создании
|
|
dataExpirarii DateTime @db.Date
|
|
// set true cron-ом после dataExpirarii; до этого — активна при расчёте performance
|
|
isStinsa Boolean @default(false)
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([employeeId])
|
|
@@index([dataExpirarii])
|
|
@@map("disciplinary_sanctions")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// H. BENEFITS
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model Benefit {
|
|
id String @id @default(uuid())
|
|
employeeId String @unique
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
|
|
uniformaId String?
|
|
uniforma InventoryItem? @relation("BenefitUniforma", fields: [uniformaId], references: [id])
|
|
halatId String?
|
|
halat InventoryItem? @relation("BenefitHalat", fields: [halatId], references: [id])
|
|
ciupiciId String?
|
|
ciupici InventoryItem? @relation("BenefitCiupici", fields: [ciupiciId], references: [id])
|
|
vestaId String?
|
|
vesta InventoryItem? @relation("BenefitVesta", fields: [vestaId], references: [id])
|
|
|
|
ticheteMasa Boolean @default(false)
|
|
valoareTichet Decimal? @db.Decimal(10, 2)
|
|
alimentatiePersonal Boolean @default(false)
|
|
abonamentTel Decimal? @db.Decimal(10, 2)
|
|
aparatTelefonId String?
|
|
aparatTelefon InventoryItem? @relation("BenefitAparatTel", fields: [aparatTelefonId], references: [id])
|
|
cardCompanie String?
|
|
automobilServiciu String?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("benefits")
|
|
}
|
|
|
|
enum InventoryItemType {
|
|
uniforma
|
|
halat
|
|
ciupici
|
|
vesta
|
|
aparat_telefon
|
|
alte
|
|
}
|
|
|
|
model InventoryItem {
|
|
id String @id @default(uuid())
|
|
sku String @unique
|
|
name String
|
|
type InventoryItemType
|
|
size String?
|
|
color String?
|
|
pricePerUnit Decimal? @db.Decimal(10, 2)
|
|
stockQty Int @default(0)
|
|
active Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
uniformaBenefits Benefit[] @relation("BenefitUniforma")
|
|
halatBenefits Benefit[] @relation("BenefitHalat")
|
|
ciupiciBenefits Benefit[] @relation("BenefitCiupici")
|
|
vestaBenefits Benefit[] @relation("BenefitVesta")
|
|
aparatTelBenefits Benefit[] @relation("BenefitAparatTel")
|
|
|
|
@@index([type, active])
|
|
@@map("inventory_items")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// PHASE 2 STUB: EMPLOYMENT CONTRACT
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model EmploymentContract {
|
|
id String @id @default(uuid())
|
|
nrCim String @unique
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id])
|
|
categorie ContractCategory
|
|
dataSemnarii DateTime @db.Date
|
|
dataAngajarii DateTime @db.Date
|
|
dataDemisiei DateTime? @db.Date
|
|
perioada ContractPeriod
|
|
dataTerminarii DateTime? @db.Date
|
|
functiaClasificator String? // CORM код
|
|
codFunctie String?
|
|
functiaOrganigrama String?
|
|
tipCim ContractType
|
|
departmentId String
|
|
department Department @relation(fields: [departmentId], references: [id])
|
|
regimMunca String?
|
|
tipSalarizare SalaryType?
|
|
// Условные поля salariu_fix / pe_ore / in_acord хранятся как JSONB
|
|
salarizareDetails Json?
|
|
clausaAditionala Json?
|
|
workScheduleId String?
|
|
workSchedule WorkSchedule? @relation(fields: [workScheduleId], references: [id])
|
|
|
|
categoriiServicii CimServiceCategory[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Бизнес-правило: zile_concediu = MAX среди всех CIM сотрудника — проверка на service-уровне
|
|
@@index([employeeId])
|
|
@@index([departmentId])
|
|
@@index([dataDemisiei])
|
|
@@map("employment_contracts")
|
|
}
|
|
|
|
model CimServiceCategory {
|
|
id String @id @default(uuid())
|
|
contractId String
|
|
contract EmploymentContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
|
|
categorieId String
|
|
tipRemunerare String // 'tarif' | 'procent'
|
|
sumaNeta Decimal? @db.Decimal(10, 2)
|
|
procent Decimal? @db.Decimal(5, 2)
|
|
|
|
@@index([contractId])
|
|
@@map("cim_service_categories")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// PHASE 4 STUB: PERFORMANCE EVALUATION
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model EvaluationCampaign {
|
|
id String @id @default(uuid())
|
|
name String
|
|
departmentId String
|
|
department Department @relation(fields: [departmentId], references: [id])
|
|
month DateTime @db.Date // первый день месяца кампании
|
|
status CampaignStatus @default(draft)
|
|
forms EvaluationForm[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([departmentId])
|
|
@@index([month])
|
|
@@map("evaluation_campaigns")
|
|
}
|
|
|
|
model EvaluationForm {
|
|
id String @id @default(uuid())
|
|
campaignId String
|
|
campaign EvaluationCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id])
|
|
|
|
// A. Competente clinice (slab/mediu/bine)
|
|
abilitatiClinice EvaluationScore?
|
|
judecataClinica EvaluationScore?
|
|
manopere EvaluationScore?
|
|
gestionareaSarcinilor EvaluationScore?
|
|
|
|
// B. Comunicare si empatie
|
|
constiintaProfesionala EvaluationScore?
|
|
atitudineaPacienti EvaluationScore?
|
|
atitudineaColegi EvaluationScore?
|
|
atitudineaPersonalNonMed EvaluationScore?
|
|
|
|
// C. Disciplina
|
|
utilizareSmartphone EvaluationScore?
|
|
respectareaProgramului EvaluationScore?
|
|
respectareaDressCode EvaluationScore?
|
|
|
|
// D. Documentatie si complianta
|
|
testJci Json? // { score, max_score, percent, completed_at, source, external_id }
|
|
completareaDocMed Boolean?
|
|
perfectioneazaCunostinte Boolean?
|
|
|
|
// E. Candidat EXPERT (Da/Nu)
|
|
membruComitetCalitate Boolean?
|
|
functieDeMonitor Boolean?
|
|
inlocuiesteSuperiorul Boolean?
|
|
|
|
// F. Verdict final
|
|
categorieCalculata ProposedCategory?
|
|
categorieAprobata ProposedCategory? // override de nursing_director
|
|
observatii String?
|
|
|
|
completedAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([campaignId, employeeId])
|
|
@@index([campaignId])
|
|
@@index([employeeId])
|
|
@@map("evaluation_forms")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// PHASE 5 STUB: MEDICAL CONTROL
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model WorkplaceRiskCard {
|
|
id String @id @default(uuid())
|
|
name String @unique // "Medic profil chirurgical cu gărzi de noapte"
|
|
riskFactors Json? // legacy: { chimici, fizici, biologici, ergonomici, psihosociali }
|
|
profiles EmployeeMedicalProfile[]
|
|
|
|
// ── Anexa 4 — antet (Fișa de evaluare a riscurilor profesionale) ──
|
|
filiala String?
|
|
adresaFiliala String?
|
|
telefonFiliala String?
|
|
caemPrimeleDouaCifre String?
|
|
cormSubgrupaMajora String?
|
|
directiaSectiaSectorul String?
|
|
numarulLoculuiDeMunca String?
|
|
caemDiviziune String?
|
|
clasaConditiilorDeMunca String?
|
|
numarLucratoriPosibili Int?
|
|
// STANDARD (Anexa 4) | DISTANTA_DIGITAL (Anexa 4A — muncă la distanță/platforme digitale)
|
|
tipFisa String @default("STANDARD")
|
|
|
|
// ── Anexa 4 — bloc descriptiv (checkbox-uri / descrieri) ──
|
|
evaluareDetalii Json?
|
|
|
|
// ── Anexa 4 — radiații ionizante (per loc de muncă) ──
|
|
radiatiiIonizante Boolean @default(false)
|
|
radiatiiGrupa String? // A | B
|
|
radiatiiAparatura String?
|
|
radiatiiSurse String? // inchise | deschise
|
|
radiatiiTipExpunere String? // X externă | gamma externă | internă | externă și internă
|
|
radiatiiMasuriProtectie String?
|
|
|
|
// ── Anexa 4 — subsol ──
|
|
mijloaceProtectieColectiva String?
|
|
mijloaceProtectieIndividuala String?
|
|
echipamentLucru String?
|
|
observatii String?
|
|
anexeIgienicoSanitare Json? // { vestiar, chiuveta, wc, dus, salaMese, recreere }
|
|
|
|
exposures WorkplaceRiskExposure[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("workplace_risk_cards")
|
|
}
|
|
|
|
// Rând din tabelele factoriale ale Anexei 4
|
|
model WorkplaceRiskExposure {
|
|
id String @id @default(uuid())
|
|
cardId String
|
|
card WorkplaceRiskCard @relation(fields: [cardId], references: [id], onDelete: Cascade)
|
|
tip RiskExposureType
|
|
denumire String
|
|
cas String? // doar chimic / pulberi
|
|
einecs String? // doar chimic / pulberi
|
|
clasificare String? // doar agent biologic
|
|
zonaAfectata String? // vibrații / câmp EM / radiații optice
|
|
timpExpunere String?
|
|
vep String? // valoarea de expunere profesională
|
|
vlep String? // valoarea-limită de expunere profesională obligatorie
|
|
caracteristici String?
|
|
procesVerbal String?
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([cardId])
|
|
@@map("workplace_risk_exposures")
|
|
}
|
|
|
|
model EmployeeMedicalProfile {
|
|
id String @id @default(uuid())
|
|
employeeId String @unique
|
|
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
|
|
ocupatieCorm String?
|
|
workplaceRiskCardId String?
|
|
workplaceRiskCard WorkplaceRiskCard? @relation(fields: [workplaceRiskCardId], references: [id])
|
|
dataUltimControlMedical DateTime? @db.Date
|
|
|
|
// Câmpuri radiații ionizante
|
|
expusRadiatiiIonizante Boolean @default(false)
|
|
dataIntrarii DateTime? @db.Date
|
|
expunereAnterioaraPerioda String? // se completează o singură dată la angajare
|
|
expunereAnterioaraAni Int?
|
|
dozaCumulataExternaMsv Decimal? @db.Decimal(10, 4)
|
|
dozaCumulataInternaMsv Decimal? @db.Decimal(10, 4)
|
|
// dozaTotalaMsv = externa + interna — câmp calculat, nu stocat
|
|
|
|
// Supraexpuneri excepționale/accidentale (Anexa 4B)
|
|
overexposures RadiationOverexposure[]
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("employee_medical_profiles")
|
|
}
|
|
|
|
// Supraexpunere la radiații ionizante — rând din Anexa 4B (per lucrător)
|
|
model RadiationOverexposure {
|
|
id String @id @default(uuid())
|
|
medicalProfileId String
|
|
medicalProfile EmployeeMedicalProfile @relation(fields: [medicalProfileId], references: [id], onDelete: Cascade)
|
|
fel OverexposureKind // EXCEPTIONALA | ACCIDENTALA
|
|
tipExpunere String? // X externă | gamma externă | internă | externă și internă
|
|
data DateTime? @db.Date
|
|
dozaMsv Decimal? @db.Decimal(10, 4)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([medicalProfileId])
|
|
@@map("radiation_overexposures")
|
|
}
|
|
|
|
model MedicalCheckup {
|
|
id String @id @default(uuid())
|
|
employeeId String
|
|
employee Employee @relation(fields: [employeeId], references: [id])
|
|
tip MedicalCheckupType
|
|
dataPlanificata DateTime @db.Date
|
|
dataEfectuata DateTime? @db.Date
|
|
verdict MedicalVerdict?
|
|
recomandari String?
|
|
valabilPanaLa DateTime? @db.Date
|
|
semnatDe String?
|
|
// Ссылки на S3-документы: [{ name, url, type }]
|
|
documenteGenerate Json?
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([employeeId])
|
|
@@index([dataPlanificata])
|
|
@@map("medical_checkups")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// AUDIT LOG — append-only, 5 ani retentie
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model AuditLog {
|
|
id BigInt @id @default(autoincrement())
|
|
ts DateTime @default(now())
|
|
userId String
|
|
userRole String @db.VarChar(50)
|
|
ip String? @db.VarChar(45) // IPv4 или IPv6
|
|
action String @db.VarChar(20) // READ | CREATE | UPDATE | DELETE | EXPORT
|
|
entity String @db.VarChar(50)
|
|
entityId String @db.VarChar(50)
|
|
field String? @db.VarChar(100)
|
|
// PII-значения шифруются на app-уровне (pgcrypto / KMS) перед записью
|
|
oldValue String?
|
|
newValue String?
|
|
reason String? // обязателен для READ медицинских данных (GDPR)
|
|
|
|
@@index([userId])
|
|
@@index([entity, entityId])
|
|
@@index([ts])
|
|
@@map("audit_logs")
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// ANEXA TEMPLATE EDITOR
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
model AnexaTemplate {
|
|
id String @id @default(uuid())
|
|
type AnexaType @unique
|
|
name String
|
|
contentJson Json
|
|
updatedById String
|
|
updatedAt DateTime @updatedAt
|
|
versions AnexaTemplateVersion[]
|
|
|
|
@@map("anexa_templates")
|
|
}
|
|
|
|
model AnexaTemplateVersion {
|
|
id String @id @default(uuid())
|
|
templateId String
|
|
template AnexaTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
|
|
contentJson Json
|
|
savedById String
|
|
savedAt DateTime @default(now())
|
|
label String?
|
|
|
|
@@index([templateId])
|
|
@@map("anexa_template_versions")
|
|
}
|