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>
163 lines
11 KiB
Markdown
163 lines
11 KiB
Markdown
# Șabloane DOCX — Control medical (docxtemplater)
|
||
|
||
Сюда кладутся **`.docx`-шаблоны** (вы их делаете в Word, вставляя плейсхолдеры). Движок генерации
|
||
загружает нужный файл, подставляет данные и отдаёт готовый документ.
|
||
|
||
- **Где лежат:** `apps/api/templates/docx/`
|
||
- **Движок:** `docxtemplater` + `pizzip` (уже в зависимостях).
|
||
- **Синтаксис плейсхолдеров:** фигурные скобки `{...}`.
|
||
|
||
## Имена файлов (фиксированные — по ним движок находит шаблон)
|
||
|
||
| Файл | Источник (регламент) | На что генерируется |
|
||
|------|----------------------|---------------------|
|
||
| `anexa-3.docx` | Anexa 3 — Fișa de solicitare | 1 на группу (карту риска) |
|
||
| `anexa-4.docx` | Anexa 4 — Fișa de evaluare a riscurilor | 1 на карту риска (`tipFisa = STANDARD`) |
|
||
| `anexa-4a.docx` | Anexa 4A — muncă la distanță/digital | 1 на карту риска (`tipFisa = DISTANTA_DIGITAL`) |
|
||
| `anexa-4b.docx` | Anexa 4B — supliment radiații | 1 на каждого облучаемого сотрудника |
|
||
| `anexa-6.docx` | Anexa 6 — Fișa de aptitudine | 1 на каждого сотрудника (вердикт врача) |
|
||
|
||
## Синтаксис docxtemplater (шпаргалка для Word)
|
||
|
||
| Нужно | Пишете в шаблоне |
|
||
|------|------------------|
|
||
| Простое значение | `{numePrenume}` |
|
||
| Таблица/список (повтор строки) | в строке таблицы: `{#chimici}` … `{denumire}` `{vlep}` … `{/chimici}` |
|
||
| Чек-бокс | `{cbEchipa}` — движок подставит `☑` или `☐` |
|
||
| Условный блок | `{#esteRadiatii}…текст…{/esteRadiatii}` (показать, если true); `{^esteRadiatii}…{/esteRadiatii}` (если false) |
|
||
|
||
> ⚠️ Циклы в таблицах: `{#name}` ставится в **первую ячейку строки-шаблона**, `{/name}` — в **последнюю ячейку той же строки**. Эта строка повторится для каждого элемента массива.
|
||
|
||
> ⚠️ Не оставляйте «висячих» `{` без пары — docxtemplater упадёт. Один плейсхолдер не должен быть разорван форматированием Word (выделите и наберите его одним стилем).
|
||
|
||
---
|
||
|
||
# Контракт данных по приложениям
|
||
|
||
Ниже — **точная форма `data`**, которую движок передаёт в каждый шаблон, и откуда берётся каждое значение.
|
||
Вставляйте в `.docx` плейсхолдеры именно с этими именами.
|
||
|
||
## Общее (есть во всех)
|
||
| Плейсхолдер | Значение | Источник |
|
||
|---|---|---|
|
||
| `{unitatea}` | Medpark International Hospital | константа |
|
||
| `{idno}` | 1003600035476 | константа |
|
||
| `{adresa}` | str. Nicolae Testemițanu 29, Chișinău | константа |
|
||
| `{dataCompletarii}` | дата генерации (DD.MM.YYYY) | now() |
|
||
|
||
## Контекст генерации batch
|
||
Эти поля заполняются в modal-е «Inițiere control medical» и используются в Anexa 3 / 4 / 4A / 4B:
|
||
`{telefon}` `{fax}` `{email}` `{solicitant}` `{functia}`.
|
||
|
||
`{telefonFiliala}` берётся из `WorkplaceRiskCard.telefonFiliala`.
|
||
|
||
---
|
||
|
||
## `anexa-3.docx` — Fișa de solicitare
|
||
**Уровень:** группа сотрудников (по карте риска).
|
||
|
||
Шапка: `{unitatea}` `{idno}` `{adresa}` `{telefon}` `{fax}` `{email}` `{filiala}` `{adresaFiliala}` `{telefonFiliala}`
|
||
Подвал: `{dataCompletarii}` `{solicitant}` `{functia}`
|
||
|
||
**Таблица сотрудников** — строку оборачиваете `{#angajati}` … `{/angajati}`:
|
||
| Плейсхолдер (в строке) | Значение | Источник |
|
||
|---|---|---|
|
||
| `{nr}` | порядковый № | счётчик |
|
||
| `{numePrenume}` | Фамилия Имя | Employee.nume/prenume |
|
||
| `{anNastere}` | год рождения | Employee.dataNasterii |
|
||
| `{idnp}` | IDNP | Employee.idnp |
|
||
| `{tipExamen}` | тип контроля (текст) | Batch.tip |
|
||
| `{ocupatieCorm}` | occupația CORM | EmployeeMedicalProfile.ocupatieCorm |
|
||
| `{caem}` | CAEM (diviziune) | WorkplaceRiskCard.caemDiviziune |
|
||
| `{numarLoc}` | № locului de muncă | WorkplaceRiskCard.numarulLoculuiDeMunca |
|
||
| `{factorRisc}` | факторы (через запятую) | WorkplaceRiskCard.exposures[].denumire |
|
||
|
||
---
|
||
|
||
## `anexa-4.docx` — Fișa de evaluare a riscurilor profesionale
|
||
**Уровень:** карта риска (`WorkplaceRiskCard`).
|
||
|
||
**Шапка:** `{unitatea}` `{adresa}` `{telefon}` `{fax}` `{email}` `{filiala}` `{adresaFiliala}` `{telefonFiliala}` `{caem2}` `{cormSubgrupa}` `{directiaSectia}` `{numarLoc}` `{caemDiviziune}` `{numarLucratori}` `{clasa}`
|
||
|
||
**Чек-боксы описательного блока** (`☑`/`☐`) — источник `WorkplaceRiskCard.evaluareDetalii`:
|
||
`{cbEchipa}` `{cbSchimbNoapte}` `{cbPauze}`
|
||
`{cbInfectare}` `{cbElectrocutare}` `{cbTensiuneInalta}` `{cbInecare}` `{cbAsfixiere}` `{cbStrivire}` `{cbTaiere}` `{cbIntepare}` `{cbLovire}` `{cbMuscatura}` `{cbMicrotraumatisme}`
|
||
`{cbConduceMasina}` `{categorieConducere}` `{cbUtilajeIntrauzinal}`
|
||
`{spatiuL}` `{spatiul}` `{spatiuH}` `{cbSuprafVerticala}` `{cbSuprafOrizontala}` `{cbSuprafOblica}` `{cbMuncaIzolare}` `{cbMuncaInaltime}` `{cbMuncaMiscare}`
|
||
`{cbPozitieOrtostatica}` `{cbPozitieAsezat}` `{cbPozitieAplecata}` `{cbPozitieMixta}` `{cbPozitieFortata}`
|
||
`{cbColoanaCervicala}` `{cbColoanaToracala}` `{cbColoanaLombara}`
|
||
`{cbManipRidicare}` `{cbManipCoborare}` `{cbManipImpingere}` `{cbManipTragere}` `{cbManipPurtare}` `{cbManipDeplasare}` `{greutateMaxima}`
|
||
`{cbVizuale}` `{cbAuditive}` `{cbNeuropsihice}`
|
||
Microclimat/iluminat: `{cbMicroclimatInterior}` `{cbMicroclimatExterior}` `{cbCaloriceRece}` `{cbCaloriceCalda}` `{cbIluminatSuficient}` `{cbIluminatInsuficient}` `{cbIluminatNatural}` `{cbIluminatArtificial}` `{cbIluminatMixt}`
|
||
|
||
**Факторные таблицы** — каждая = свой цикл; источник `WorkplaceRiskCard.exposures` (сгруппированы по `tip`):
|
||
Секции также имеют чек-боксы наличия данных: `{cbChimici}`/`{cbChimiciNu}`, `{cbPulberi}`/`{cbPulberiNu}`, `{cbBiologici}`/`{cbBiologiciNu}`, `{cbZgomot}`/`{cbZgomotNu}`, `{cbVibratii}`/`{cbVibratiiNu}`, `{cbCampEM}`/`{cbCampEMNu}`, `{cbOptice}`/`{cbOpticeNu}`.
|
||
| Цикл | Поля строки |
|
||
|---|---|
|
||
| `{#chimici}…{/chimici}` | `{denumire}` `{cas}` `{einecs}` `{timp}` `{vep}` `{vlep}` `{caracteristici}` |
|
||
| `{#pulberi}…{/pulberi}` | те же |
|
||
| `{#biologici}…{/biologici}` | `{denumire}` `{clasificare}` `{note}` |
|
||
| `{#zgomot}…{/zgomot}` | `{denumire}` `{timp}` `{vep}` `{vlep}` `{caracteristici}` |
|
||
| `{#vibratii}…{/vibratii}` | `{denumire}` `{zona}` `{timp}` `{vep}` `{vlep}` `{caracteristici}` |
|
||
| `{#campEM}…{/campEM}` | `{denumire}` `{zona}` `{timp}` `{vep}` `{vlep}` `{caracteristici}` |
|
||
| `{#optice}…{/optice}` | `{denumire}` `{zona}` `{timp}` `{vep}` `{vlep}` `{caracteristici}` |
|
||
|
||
**Радиация ионизирующая** (источник `WorkplaceRiskCard.radiatii*`):
|
||
`{cbRadiatii}` `{cbRadiatiiNu}` `{radGrupa}` `{radSurse}` `{radTipExpunere}` `{radAparatura}` `{radMasuri}`
|
||
|
||
**Подвал:** `{protectieColectiva}` `{protectieIndividuala}` `{echipament}` `{cbVestiar}` `{cbChiuveta}` `{cbWc}` `{cbDus}` `{cbSalaMese}` `{cbRecreere}` `{observatii}`
|
||
|
||
---
|
||
|
||
## `anexa-4a.docx` — muncă la distanță / platforme digitale
|
||
**Уровень:** карта риска (`tipFisa = DISTANTA_DIGITAL`). **Без факторных таблиц.**
|
||
|
||
Шапка: `{unitatea}` `{adresa}` `{telefon}` `{fax}` `{email}` `{filiala}` `{adresaFiliala}` `{telefonFiliala}` `{caem2}` `{cormSubgrupa}` `{directiaSectia}` `{numarLoc}` `{caemDiviziune}` `{clasa}`
|
||
Тело: `{cbEchipa}` `{oreZi}` `{schimburi}` `{cbSchimbNoapte}` `{cbPauze}` **`{cbLucruMonitor}`** **`{cbPlatformeDigitale}`**
|
||
`{cbConduceMasina}` `{categorieConducere}` `{operatiuni}`
|
||
`{cbDeplasari}` `{deplasariDescriere}`
|
||
`{cbManipRidicare}…{cbManipDeplasare}` `{greutateMaxima}`
|
||
`{cbVizuale}` `{cbAuditive}` `{cbNeuropsihice}` `{alteRiscuri}`
|
||
Подвал: `{dataCompletarii}`
|
||
|
||
---
|
||
|
||
## `anexa-4b.docx` — Supliment radiații ionizante
|
||
**Уровень:** один облучаемый сотрудник. Источник: `EmployeeMedicalProfile` + `overexposures`.
|
||
|
||
Шапка: `{unitatea}` `{adresa}` `{telefon}` `{fax}` `{email}` `{filiala}` `{adresaFiliala}` `{telefonFiliala}` `{caem2}`
|
||
Контекст места: `{cormSubgrupa}` `{directiaSectia}` `{numarLoc}` `{caemDiviziune}`
|
||
Сотрудник: `{numePrenume}` `{idnp}`
|
||
|
||
| Плейсхолдер | Значение | Источник |
|
||
|---|---|---|
|
||
| `{cbRadiatii}` | ☑/☐ | profile.expusRadiatiiIonizante |
|
||
| `{dataIntrarii}` | дата | profile.dataIntrarii |
|
||
| `{expAnterioaraPerioada}` | период | profile.expunereAnterioaraPerioda |
|
||
| `{expAnterioaraAni}` | лет | profile.expunereAnterioaraAni |
|
||
| `{dozaExterna}` | mSv | profile.dozaCumulataExternaMsv |
|
||
| `{dozaInterna}` | mSv | profile.dozaCumulataInternaMsv |
|
||
| `{dozaTotala}` | mSv (ext+int) | вычисляется |
|
||
|
||
Циклы supraexpuneri (источник `overexposures`, разделены по `fel`):
|
||
- `{#supraexpExceptionale}` `{tipExpunere}` `{data}` `{doza}` `{/supraexpExceptionale}`
|
||
- `{#supraexpAccidentale}` `{tipExpunere}` `{data}` `{doza}` `{/supraexpAccidentale}`
|
||
|
||
Подвал: `{dataCompletarii}`
|
||
|
||
---
|
||
|
||
## `anexa-6.docx` — Fișa de aptitudine
|
||
**Уровень:** один сотрудник. Источник: `MedicalCheckup` (вердикт врача).
|
||
|
||
Шапка/сотрудник: `{unitatea}` `{adresa}` `{numePrenume}` `{idnp}` `{anNastere}` `{ocupatieCorm}` `{departament}` `{caemDiviziune}` `{numarLoc}` `{factorRisc}` `{tipExamen}` `{dataCompletarii}`
|
||
Вердикт (чек-боксы): `{cbApt}` `{cbAptAdaptare}` `{cbAptConditionat}` `{cbInaptTemporar}` `{cbInapt}`
|
||
`{recomandari}` `{valabilPanaLa}` `{semnatDe}`.
|
||
`{valabilPanaLa}` и `{semnatDe}` заполняет medic_familie при finalizarea controlului.
|
||
|
||
---
|
||
|
||
## Статус движка
|
||
`DocxTemplateService.render(type, data)` уже подключён. Если файл `apps/api/templates/docx/anexa-*.docx`
|
||
существует, bulk-generation использует официальный `.docx`-шаблон через `docxtemplater`; старый `docx`+tiptap-JSON renderer остаётся fallback-ом.
|