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>
227 lines
14 KiB
TypeScript
227 lines
14 KiB
TypeScript
/**
|
|
* Generează BOLĂVANKE (stub) .docx pentru Anexele 3/4/4A/4B/6 cu TOATE placeholder-ele
|
|
* docxtemplater din `templates/docx/README.md`. Formatarea o ajustați apoi în Word.
|
|
*
|
|
* Rulare: pnpm --filter api exec ts-node scripts/generate-docx-stubs.ts
|
|
*/
|
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import {
|
|
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
|
HeadingLevel, WidthType, BorderStyle,
|
|
} from 'docx';
|
|
|
|
const OUT = join(__dirname, '..', 'templates', 'docx');
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
// ── helpers ──
|
|
const T = (text: string, bold = false) => new TextRun({ text, bold });
|
|
const ph = (name: string) => new TextRun({ text: `{${name}}`, bold: true, color: '0B6E70' });
|
|
const P = (...children: TextRun[]) => new Paragraph({ children });
|
|
const H = (text: string, level: (typeof HeadingLevel)[keyof typeof HeadingLevel] = HeadingLevel.HEADING_2) =>
|
|
new Paragraph({ heading: level, children: [T(text, true)] });
|
|
const empty = () => new Paragraph({ children: [] });
|
|
// "Label {ph}"
|
|
const line = (label: string, name: string) => P(T(label + ' '), ph(name));
|
|
// checkbox: "{cbX} Label"
|
|
const cb = (name: string, label: string) => [ph(name), T(' ' + label + ' ')];
|
|
const cbLine = (...pairs: [string, string][]) =>
|
|
P(...pairs.flatMap(([n, l]) => cb(n, l)));
|
|
|
|
const BORDER = {
|
|
top: { style: BorderStyle.SINGLE, size: 1, color: '999999' },
|
|
bottom: { style: BorderStyle.SINGLE, size: 1, color: '999999' },
|
|
left: { style: BorderStyle.SINGLE, size: 1, color: '999999' },
|
|
right: { style: BorderStyle.SINGLE, size: 1, color: '999999' },
|
|
};
|
|
const cell = (children: Paragraph[]) => new TableCell({ children, borders: BORDER });
|
|
const headerRow = (labels: string[]) =>
|
|
new TableRow({ children: labels.map((l) => cell([P(T(l, true))])) });
|
|
|
|
/**
|
|
* Tabel repetabil: rândul-șablon repetă pentru fiecare element din `loop`.
|
|
* `{#loop}` în prima celulă, `{/loop}` în ultima.
|
|
*/
|
|
function loopTable(loop: string, headers: string[], rowFields: string[]): Table {
|
|
const tplCells = rowFields.map((f, i) => {
|
|
const runs: TextRun[] = [];
|
|
if (i === 0) runs.push(new TextRun({ text: `{#${loop}}`, bold: true, color: 'B11116' }));
|
|
runs.push(ph(f));
|
|
if (i === rowFields.length - 1) runs.push(new TextRun({ text: `{/${loop}}`, bold: true, color: 'B11116' }));
|
|
return cell([P(...runs)]);
|
|
});
|
|
return new Table({
|
|
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
rows: [headerRow(headers), new TableRow({ children: tplCells })],
|
|
});
|
|
}
|
|
|
|
function save(name: string, children: (Paragraph | Table)[]) {
|
|
const doc = new Document({ sections: [{ children }] });
|
|
return Packer.toBuffer(doc).then((buf) => {
|
|
writeFileSync(join(OUT, name), buf);
|
|
console.log(' ✓', name, `(${buf.length} bytes)`);
|
|
});
|
|
}
|
|
|
|
// ════════════════════════ ANEXA 3 ════════════════════════
|
|
const anexa3: (Paragraph | Table)[] = [
|
|
H('FIȘA de solicitare a examenului medical'),
|
|
line('Unitatea economică/instituția:', 'unitatea'),
|
|
P(T('IDNO: '), ph('idno'), T(' Adresa: '), ph('adresa')),
|
|
P(T('Telefon: '), ph('telefon'), T(' Fax: '), ph('fax'), T(' E-mail: '), ph('email')),
|
|
P(T('Filiala: '), ph('filiala'), T(' Adresa filialei: '), ph('adresaFiliala'), T(' Telefon: '), ph('telefonFiliala')),
|
|
empty(),
|
|
loopTable('angajati',
|
|
['Nr.', 'Numele și prenumele', 'Anul nașterii', 'IDNP', 'Tipul examenului', 'Ocupația (CORM)', 'CAEM', 'Nr. loc muncă', 'Factorul de risc'],
|
|
['nr', 'numePrenume', 'anNastere', 'idnp', 'tipExamen', 'ocupatieCorm', 'caem', 'numarLoc', 'factorRisc']),
|
|
empty(),
|
|
line('Data completării:', 'dataCompletarii'),
|
|
P(T('Solicitant: '), ph('solicitant'), T(' Funcția: '), ph('functia')),
|
|
P(T('Semnătura: ____________________')),
|
|
];
|
|
|
|
// ════════════════════════ ANEXA 4 ════════════════════════
|
|
const anexa4Header: (Paragraph | Table)[] = [
|
|
line('Unitatea economică/instituția:', 'unitatea'),
|
|
P(T('Adresa: '), ph('adresa')),
|
|
P(T('Filiala: '), ph('filiala'), T(' Adresa filialei: '), ph('adresaFiliala'), T(' CAEM (2 cifre): '), ph('caem2')),
|
|
H('FIȘA de evaluare a riscurilor profesionale'),
|
|
line('Ocupația (subgrupa majoră CORM):', 'cormSubgrupa'),
|
|
line('Direcția/secția/sectorul:', 'directiaSectia'),
|
|
P(T('Numărul locului de muncă: '), ph('numarLoc'), T(' CAEM (diviziune): '), ph('caemDiviziune')),
|
|
P(T('Nr. lucrători care pot activa: '), ph('numarLucratori'), T(' Clasa condițiilor de muncă: '), ph('clasa')),
|
|
];
|
|
const anexa4Descriptiv: (Paragraph | Table)[] = [
|
|
H('Descrierea activității', HeadingLevel.HEADING_3),
|
|
P(T('Lucru în echipă '), ph('cbEchipa'), T(' Nr. ore/zi: '), ph('oreZi'), T(' Nr. schimburi: '), ph('schimburi')),
|
|
cbLine(['cbSchimbNoapte', 'schimb de noapte'], ['cbPauze', 'pauze organizate']),
|
|
P(T('Riscuri:')),
|
|
cbLine(['cbInfectare', 'infectare'], ['cbElectrocutare', 'electrocutare'], ['cbTensiuneInalta', 'tensiune înaltă'], ['cbInecare', 'înecare'], ['cbAsfixiere', 'asfixiere']),
|
|
cbLine(['cbStrivire', 'strivire'], ['cbTaiere', 'tăiere'], ['cbIntepare', 'înțepare'], ['cbLovire', 'lovire'], ['cbMuscatura', 'mușcătură'], ['cbMicrotraumatisme', 'microtraumatisme']),
|
|
P(T('Conduce mașina '), ph('cbConduceMasina'), T(' categorie: '), ph('categorieConducere'), T(' '), ph('cbUtilajeIntrauzinal'), T(' utilaje intrauzinal')),
|
|
H('Descrierea spațiului de lucru', HeadingLevel.HEADING_3),
|
|
P(T('Dimensiuni: L '), ph('spatiuL'), T(' l '), ph('spatiul'), T(' H '), ph('spatiuH'), T(' m')),
|
|
cbLine(['cbSuprafVerticala', 'suprafață verticală'], ['cbSuprafOrizontala', 'orizontală'], ['cbSuprafOblica', 'oblică']),
|
|
cbLine(['cbMuncaIzolare', 'în izolare'], ['cbMuncaInaltime', 'la înălțime'], ['cbMuncaMiscare', 'în mișcare']),
|
|
H('Efort fizic', HeadingLevel.HEADING_3),
|
|
P(T('Poziție: ')),
|
|
cbLine(['cbPozitieOrtostatica', 'ortostatică'], ['cbPozitieAsezat', 'așezat'], ['cbPozitieAplecata', 'aplecată'], ['cbPozitieMixta', 'mixtă'], ['cbPozitieFortata', 'forțată']),
|
|
P(T('Suprasolicitări coloană: ')),
|
|
cbLine(['cbColoanaCervicala', 'cervicală'], ['cbColoanaToracala', 'toracală'], ['cbColoanaLombara', 'lombară']),
|
|
P(T('Manipulare manuală: ')),
|
|
cbLine(['cbManipRidicare', 'ridicare'], ['cbManipCoborare', 'coborâre'], ['cbManipImpingere', 'împingere'], ['cbManipTragere', 'tragere'], ['cbManipPurtare', 'purtare'], ['cbManipDeplasare', 'deplasare']),
|
|
P(T('Greutate maximă manipulată: '), ph('greutateMaxima')),
|
|
cbLine(['cbVizuale', 'suprasolicitări vizuale'], ['cbAuditive', 'auditive'], ['cbNeuropsihice', 'neuropsihice']),
|
|
];
|
|
const factorTables: (Paragraph | Table)[] = [
|
|
H('AGENȚI CHIMICI', HeadingLevel.HEADING_3),
|
|
loopTable('chimici', ['Agentul chimic', 'CAS', 'EINECS', 'Timp', 'VEP', 'VLEP', 'Caracteristici'],
|
|
['denumire', 'cas', 'einecs', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
H('PULBERI', HeadingLevel.HEADING_3),
|
|
loopTable('pulberi', ['Pulberi', 'CAS', 'EINECS', 'Timp', 'VEP', 'VLEP', 'Caracteristici'],
|
|
['denumire', 'cas', 'einecs', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
H('AGENȚI BIOLOGICI', HeadingLevel.HEADING_3),
|
|
loopTable('biologici', ['Agent biologic', 'Clasificare', 'Note'], ['denumire', 'clasificare', 'note']),
|
|
H('ZGOMOT PROFESIONAL', HeadingLevel.HEADING_3),
|
|
loopTable('zgomot', ['Tipul', 'Timp', 'VEP', 'VLEP', 'Caracteristici'], ['denumire', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
H('VIBRAȚII MECANICE', HeadingLevel.HEADING_3),
|
|
loopTable('vibratii', ['Tipul', 'Zona', 'Timp', 'VEP', 'VLEP', 'Caracteristici'], ['denumire', 'zona', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
H('CÂMP ELECTROMAGNETIC', HeadingLevel.HEADING_3),
|
|
loopTable('campEM', ['Tipul', 'Zona', 'Timp', 'VEP', 'VLEP', 'Caracteristici'], ['denumire', 'zona', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
H('RADIAȚII OPTICE ARTIFICIALE', HeadingLevel.HEADING_3),
|
|
loopTable('optice', ['Tipul', 'Zona', 'Timp', 'VEP', 'VLEP', 'Caracteristici'], ['denumire', 'zona', 'timp', 'vep', 'vlep', 'caracteristici']),
|
|
];
|
|
const anexa4Footer: (Paragraph | Table)[] = [
|
|
H('MICROCLIMAT / RADIAȚII / ILUMINAT', HeadingLevel.HEADING_3),
|
|
cbLine(['cbMicroclimatInterior', 'interior'], ['cbMicroclimatExterior', 'exterior'], ['cbCaloriceRece', 'rad. calorice (rece)'], ['cbCaloriceCalda', 'rad. calorice (caldă)']),
|
|
P(T('Radiații ionizante '), ph('cbRadiatii'), T(' Grupa: '), ph('radGrupa'), T(' Surse: '), ph('radSurse')),
|
|
P(T('Tip expunere: '), ph('radTipExpunere'), T(' Aparatură: '), ph('radAparatura'), T(' Măsuri: '), ph('radMasuri')),
|
|
cbLine(['cbIluminatSuficient', 'iluminat suficient'], ['cbIluminatInsuficient', 'insuficient'], ['cbIluminatNatural', 'natural'], ['cbIluminatArtificial', 'artificial'], ['cbIluminatMixt', 'mixt']),
|
|
H('Protecție și dotări', HeadingLevel.HEADING_3),
|
|
line('Mijloace de protecție colectivă:', 'protectieColectiva'),
|
|
line('Mijloace de protecție individuală:', 'protectieIndividuala'),
|
|
line('Echipament de lucru:', 'echipament'),
|
|
P(T('Anexe igienico-sanitare: ')),
|
|
cbLine(['cbVestiar', 'vestiar'], ['cbChiuveta', 'chiuvetă'], ['cbWc', 'WC'], ['cbDus', 'duș'], ['cbSalaMese', 'sală de mese'], ['cbRecreere', 'recreere']),
|
|
line('Observații:', 'observatii'),
|
|
line('Data completării:', 'dataCompletarii'),
|
|
P(T('Angajatorul (nume, prenume, semnătura): ____________________')),
|
|
P(new TextRun({ text: 'Instrucțiuni: răspuns afirmativ [☑]; răspuns negativ [☐].', italics: true })),
|
|
];
|
|
|
|
// ════════════════════════ ANEXA 4A ════════════════════════
|
|
const anexa4a: (Paragraph | Table)[] = [
|
|
line('Unitatea economică/instituția:', 'unitatea'),
|
|
P(T('Adresa: '), ph('adresa'), T(' Filiala: '), ph('filiala'), T(' CAEM (2 cifre): '), ph('caem2')),
|
|
H('FIȘA de evaluare — muncă la distanță / platforme digitale'),
|
|
line('Ocupația (subgrupa majoră CORM):', 'cormSubgrupa'),
|
|
line('Direcția/secția/sectorul:', 'directiaSectia'),
|
|
P(T('Numărul locului de muncă: '), ph('numarLoc'), T(' CAEM (diviziune): '), ph('caemDiviziune'), T(' Clasa: '), ph('clasa')),
|
|
H('Descrierea activității', HeadingLevel.HEADING_3),
|
|
P(T('Lucru în echipă '), ph('cbEchipa'), T(' Nr. ore/zi: '), ph('oreZi'), T(' Nr. schimburi: '), ph('schimburi')),
|
|
cbLine(['cbSchimbNoapte', 'schimb de noapte'], ['cbPauze', 'pauze organizate'], ['cbLucruMonitor', 'lucru la monitor'], ['cbPlatformeDigitale', 'platforme digitale']),
|
|
P(T('Conduce mașina '), ph('cbConduceMasina'), T(' categorie: '), ph('categorieConducere')),
|
|
line('Operațiuni executate:', 'operatiuni'),
|
|
P(T('Deplasări pe teren '), ph('cbDeplasari'), T(' '), ph('deplasariDescriere')),
|
|
H('Efort fizic', HeadingLevel.HEADING_3),
|
|
P(T('Manipulare manuală: ')),
|
|
cbLine(['cbManipRidicare', 'ridicare'], ['cbManipCoborare', 'coborâre'], ['cbManipImpingere', 'împingere'], ['cbManipTragere', 'tragere'], ['cbManipPurtare', 'purtare'], ['cbManipDeplasare', 'deplasare']),
|
|
P(T('Greutate maximă: '), ph('greutateMaxima')),
|
|
cbLine(['cbVizuale', 'vizuale'], ['cbAuditive', 'auditive'], ['cbNeuropsihice', 'neuropsihice']),
|
|
line('Alte riscuri:', 'alteRiscuri'),
|
|
line('Data completării:', 'dataCompletarii'),
|
|
P(T('Angajatorul (nume, prenume, semnătura): ____________________')),
|
|
];
|
|
|
|
// ════════════════════════ ANEXA 4B ════════════════════════
|
|
const anexa4b: (Paragraph | Table)[] = [
|
|
line('Unitatea economică/instituția:', 'unitatea'),
|
|
P(T('Adresa: '), ph('adresa'), T(' Telefon: '), ph('telefon'), T(' Fax: '), ph('fax'), T(' E-mail: '), ph('email')),
|
|
P(T('Filiala: '), ph('filiala'), T(' Adresa filialei: '), ph('adresaFiliala'), T(' CAEM (2 cifre): '), ph('caem2')),
|
|
H('SUPLIMENT la Fișa de evaluare a riscurilor profesionale'),
|
|
line('Ocupația (subgrupa majoră CORM):', 'cormSubgrupa'),
|
|
line('Direcția/secția/sectorul:', 'directiaSectia'),
|
|
P(T('Numărul locului de muncă: '), ph('numarLoc'), T(' CAEM (diviziune): '), ph('caemDiviziune')),
|
|
P(T('Numele, prenumele lucrătorului: '), ph('numePrenume'), T(' IDNP: '), ph('idnp')),
|
|
P(T('RADIAȚII IONIZANTE: '), ph('cbRadiatii')),
|
|
line('Data intrării în mediul cu expunere:', 'dataIntrarii'),
|
|
P(T('Expunere anterioară — perioada: '), ph('expAnterioaraPerioada'), T(' ani: '), ph('expAnterioaraAni')),
|
|
P(T('Doză externă (mSv): '), ph('dozaExterna'), T(' Doză internă (mSv): '), ph('dozaInterna'), T(' Doză totală (mSv): '), ph('dozaTotala')),
|
|
H('Supraexpuneri excepționale', HeadingLevel.HEADING_3),
|
|
loopTable('supraexpExceptionale', ['Tip de expunere', 'Data', 'Doză (mSv)'], ['tipExpunere', 'data', 'doza']),
|
|
H('Supraexpuneri accidentale', HeadingLevel.HEADING_3),
|
|
loopTable('supraexpAccidentale', ['Tip de expunere', 'Data', 'Doză (mSv)'], ['tipExpunere', 'data', 'doza']),
|
|
line('Data completării:', 'dataCompletarii'),
|
|
P(T('Angajatorul (nume, prenume, semnătura): ____________________')),
|
|
];
|
|
|
|
// ════════════════════════ ANEXA 6 ════════════════════════
|
|
const anexa6: (Paragraph | Table)[] = [
|
|
H('FIȘĂ DE APTITUDINE ÎN MUNCĂ'),
|
|
line('Unitatea:', 'unitatea'),
|
|
P(T('Angajat: '), ph('numePrenume'), T(' IDNP: '), ph('idnp'), T(' Anul nașterii: '), ph('anNastere')),
|
|
P(T('Ocupația: '), ph('ocupatieCorm'), T(' Departament: '), ph('departament')),
|
|
P(T('Tipul examenului: '), ph('tipExamen'), T(' Data: '), ph('dataCompletarii')),
|
|
H('Verdict', HeadingLevel.HEADING_3),
|
|
P(ph('cbApt'), T(' Apt')),
|
|
P(ph('cbAptAdaptare'), T(' Apt în perioada de adaptare')),
|
|
P(ph('cbAptConditionat'), T(' Apt condiționat')),
|
|
P(ph('cbInaptTemporar'), T(' Inapt temporar')),
|
|
P(ph('cbInapt'), T(' Inapt')),
|
|
line('Recomandări:', 'recomandari'),
|
|
line('Valabil până la:', 'valabilPanaLa'),
|
|
P(T('Semnătura medicului: '), ph('semnatDe')),
|
|
];
|
|
|
|
async function main() {
|
|
console.log('📄 Generez bolăvanke .docx în', OUT);
|
|
await save('anexa-3.docx', anexa3);
|
|
await save('anexa-4.docx', [...anexa4Header, ...anexa4Descriptiv, ...factorTables, ...anexa4Footer]);
|
|
await save('anexa-4a.docx', anexa4a);
|
|
await save('anexa-4b.docx', anexa4b);
|
|
await save('anexa-6.docx', anexa6);
|
|
console.log('✅ Gata. Editați formatarea în Word — placeholder-ele rămân ca {nume}.');
|
|
}
|
|
main().catch((e) => { console.error(e); process.exit(1); });
|