# Document Templates — admin-managed forms (feature)

A from-scratch subsystem (2026-06-14): admins upload `.docx` templates with Jinja
`{{ placeholders }}`; applicants fill a form **generated from each template's fields** and download a
server-rendered PDF. Independent of the hardcoded *sáng kiến* form pipeline (which stays as-is).

## Data + storage
- Table **`document_templates`** (migration `015_document_templates.sql`, model `DocumentTemplate`):
  `id, name, description, storage_key, original_filename, content_sha256, fields JSONB ([{key,label,type}]),
  is_active, created_by, created_at, updated_at`.
- MinIO bucket **`initiative-templates`** (`S3Settings.s3_bucket_templates`, has a default; created by
  `ensure_buckets_exist`). Server-side only — no browser CORS / presign needed.

## Backend — `be0/src/template_routes.py` (mounted `/api/v1/templates`)
- `POST /templates` (admin) — multipart `.docx` → extract `{{placeholders}}` → MinIO + row.
- `GET /templates` (authed; admin sees inactive too) · `GET /{id}` · `GET /{id}/file` (admin, raw docx).
- `PUT /{id}` (admin) metadata · `DELETE /{id}` (admin) soft-delete, `?hard=true` removes row + object.
- `POST /{id}/render` (authed) — fill with `values` → DOCX (docxtpl) or PDF (reuses
  `src.be01.docx_to_pdf.convert_docx_bytes_to_pdf`, LibreOffice).
- **Placeholder extraction:** docxtpl `get_undeclared_template_variables()` with a regex-on-stripped-XML
  fallback — DOCX often splits `{{ }}` across `<w:r>` runs, so stripping tags first recovers them.
- Auth mirrors the extracted admin routers (`decode_access_token_user_id`/`decode_bearer_token`; admin = `"admin"` in JWT roles).

## Frontend
- **`@ump/shared/lib/templateApi.ts`** — `DocumentTemplate`/`TemplateField` types + list/get/create
  (FormData)/update/delete/render (`postArrayBuffer`)/downloadFile + `saveArrayBufferAs` / `arrayBufferToObjectUrl`.
- **frontend_admin** `pages/TemplatesPage.tsx` (+ `layouts/AdminLayout.tsx` shell; route `/templates`,
  admin-gated) — upload / list / edit meta / activate-deactivate / download / hard-delete (alert-dialog). Its first real feature.
- **frontend_user** `pages/TemplatesFillPage.tsx` (route `/dashboard/templates`, sidebar « Mẫu tài liệu »)
  — pick a template → form generated from its fields → server PDF → iframe preview + download.

## Verified
Backend **e2e live** against the dev stack: create → extract fields → render (filled values) → delete.
`@ump/shared` + frontend_user + frontend_admin `tsc --noEmit` + `vite build` all clean. Commits
`c6d003c` (BE) + `4f1cb3e` (FE).

## Deferred (v1 limitations)
- **No file-replace endpoint** — delete + re-create to change a template's `.docx` (metadata edit works).
- **All fields render as single-line text** — no type/date/number/long-text per field.
- **No audit-log entries** on template create/update/delete (the rest of the app audits admin mutations).
