# audit-report-builder (dynamic Form 04-2a audit report)

Generates the Stage 1 and Stage 2 / Surveillance / Recertification audit report from CRM data, in the same layout as the Word reports. It sits next to the existing `audit-report` module and does not change it.

## 1. Install

1. Copy this folder to `src/audit-report-builder/`.
2. Packages (most are already in the project): `npm i handlebars puppeteer pdf-lib class-validator class-transformer`
3. `app.module.ts`: add `AuditReportBuilderModule` to `imports`.
4. If entities are listed by hand in the `scheme_dbs` TypeORM config, add the 7 entities from `entities/`. If it uses a glob, nothing to do.
5. Run `migrations/001_create_audit_report_tables.sql` on scheme_dbs (skip if synchronize is on in dev).
6. `nest-cli.json` must copy the template files into `dist`:
   `"compilerOptions": { "assets": [ { "include": "audit-report-builder/templates/**/*", "outDir": "dist" } ] }`
   (or set `AUDIT_REPORT_TEMPLATE_DIR` to the folder on the server). If the folder is not found the service also looks in `dist/` and `src/` under the working directory.
7. Server fonts: install Carlito (`apt install fonts-crosextra-carlito`) so Calibri text prints the same as in Word.
8. Seed the built-in clause lists once (needs the `standards.id` of each standard):
   `POST /audit-reports/clause-library/seed/iso9001   { "standard_id": 4 }`  (4 = ISO 9001:2015, Form 04-2a)
   `POST /audit-reports/clause-library/seed/iso14001  { "standard_id": 33 }` (33 = ISO 14001:2026, Form 04-2b)
   `POST /audit-reports/clause-library/seed/iso45001  { "standard_id": 6 }`  (6 = ISO 45001:2018, Form 04-2a OH&S)
   `POST /audit-reports/clause-library/seed/ims       { "standard_id": 1 }`  (1 = "ISO 9001, 14001 & 45001", used by the combined report)
   or run `migrations/005_iso14001_templates_and_clauses.sql`, which also adds the four `report_templates` rows for ISO 14001.

## 2. Environment

| Variable | Default | Use |
|---|---|---|
| AUDIT_REPORT_ROOT | `/var/www/scheme_certiifcation/backend/storage/auditReport` | existing setting of the report storage; approved PDFs go here |
| AUDIT_REPORT_TEMPLATE_DIR | `dist/.../templates` | template + image folder |
| AUDIT_REPORT_APPROVER_ROLES | `scheme` | roles (besides admin / superadmin) that may approve, scoped to their own scheme |
| AUDIT_REPORT_SUPER_ADMIN_IDS | `1,8,29` | user ids that may approve for every scheme (same list as the QAQC approvals module) |
| AUDIT_REPORT_NOTIFY_EMAIL | empty | extra address(es) told when a report is submitted |
| AUDIT_REPORT_PDF_AUTHOR | Quality Registrar Systems | PDF author / creator metadata |
| FRONTEND_URL | empty | used for the link in notification emails |

A user can also be made an approver with the permission action `report-approve` (the SQL adds it under module 54); it is still limited to the user's own scheme.

## 3. How it works

1. `POST /audit-reports { audit_schedule_row_id, standard_id }` creates the report (or returns the existing one). It copies company, certificate, team and attendance data from the CRM, creates one finding row per clause from `clause_library`, and fills the text sections with the default wording from the Word file. The organization brief is carried over from the last approved report of the same company and standard.
2. The auditor fills the clause grid (C / NC / OFI / NA + evidence), the text sections, attendance, change detail, conclusion.
3. `Raise NC` on a clause row calls the existing `PreviousNcService.createNew` and links the new NC entry back to the row.
4. `Preview` (HTML) and `PDF` render the same template. Drafts carry a DRAFT watermark.
5. `Submit` checks the report is complete. The scheme department then approves or returns it.
6. `Approve` freezes the data (`snapshot_json`), creates the final PDF, stores it and writes a row in `audit_report_versions`. From then on the report prints from the snapshot, so later CRM edits cannot change it. `Reopen` (approver only) sends it back for correction and raises the version; the old PDF is kept.

CRM values live in `crm_json`; anything the auditor types over them lives in `edits_json`. `POST /:id/refresh` re-reads the CRM and never touches the auditor's text.

## 4. Endpoints (staff, JWT)

```
GET    /audit-reports                       list (own reports, or all for view-all users)
GET    /audit-reports/by-audit/:rowId       reports of one audit (for the tracker)
GET    /audit-reports/context/:rowId        stage guess, booked standards, existing reports
GET    /audit-reports/clause-library/:standardId?scope=readiness|full
POST   /audit-reports/clause-library/seed/:key      key = iso9001 | iso14001 | iso45001 | ims (built-in sets, see services/clause-library.service.ts SEED_SETS)
POST   /audit-reports/clause-library/import         any standard, rows in the body (see samples/iso14001-flow-example)
POST   /audit-reports/clause-library/clone          copy one standard's rows to another
POST   /audit-reports                       create
GET    /audit-reports/:id                   editor model
GET    /audit-reports/:id/preview           HTML
GET    /audit-reports/:id/pdf               final PDF if approved, otherwise a DRAFT PDF
PATCH  /audit-reports/:id/header            { fields, conclusion, recommendation, next_audit_date, extras }
PUT    /audit-reports/:id/clauses           bulk save  { items: [{ id, status, evidence_text, docs_reviewed }] }
PUT    /audit-reports/:id/clauses/:clauseId
POST   /audit-reports/:id/clauses/:clauseId/raise-nc   { nc_type, statement?, corrective_action? }
PUT    /audit-reports/:id/sections/:key     { body }
PUT    /audit-reports/:id/attendance        { attendees: [...] }
POST   /audit-reports/:id/refresh
POST   /audit-reports/:id/submit | approve | lock
POST   /audit-reports/:id/return | reopen   { reason }
```

Client portal: `GET /client-portal/audits/:auditRequestId/reports` and `.../reports/:reportId/pdf` (approved or locked reports of the client's own audits only).

`extras` keys read by the template: `declarations` (["d1",..]), `attachments` (["a1",..]), `mark_use` ("m1".."m3"), `mark_use_nc_no`, `change_detail` ({ man_days_adequate: "yes", scope_change: { value: "none" | "yes", note } }), `improvement` ("modification" | "none").
Section keys: objective, criteria, org_brief, audit_brief, prev_nc, effectiveness, improvement_comments.

## 5. Where each report field comes from (checked against scheme_dbs)

| Report field | Source |
|---|---|
| Name, contact person, mobile / telephone, e-mail | `companies` (+ `audit_requests.auditee_email`) |
| Address | `companies.address` + `city` + `countries.name` |
| Certificate no., NACE/EAC code, scope | `new_certificate` (company + standard, active first), scope falls back to `audit_requests.scope_of_work`, then `companies.scope_of_work` |
| Accreditation (ASCB / Others) | `audit_schedule_rows.accreditation`, then request, then company |
| Lead auditor / auditors | `audit_schedule_rows.lead_auditor_id`, `audit_row_co_auditors` -> `users.firstName lastName` |
| Audit date | `audit_schedules.schedule_date` |
| Attendance sheet | `audit_requests.auditees` |
| Clause list and requirement text | `clause_library` (copied into the report when it is created) |
| NC / observation tables | `nc__ncs` + `ncr_entries` where `audit_id` = audit row id |
| Documents the client uploaded | `audit_checklist_items` (approved) shown in the editor to tick into "documents reviewed" |
| Employees, man-days, technical expert, multisite | not in the CRM, typed by the auditor |

Approval writes the PDF with `AuditReportStorageService` and sets `stg1_audit_report` (Stage 1) or `stg2_audit_report` (other stages) on the audit row, plus `report_qaqc_approved = 1`. If that column already held a manually uploaded file, it is replaced by the generated PDF (the old file stays on disk).

Known limits:
- The NC section lists every NC entry of the audit row. If one audit row covers two standards, both reports show the same NC list, and both write to the same `stg*_audit_report` column (the last approved one wins).
- `createNew` may send its own NC notifications; they behave as they do for a normal NC.
- Clause lists exist for ISO 9001:2015 (standard 4), ISO 14001:2026 (standard 33), ISO 45001:2018 (standard 6) and the IMS set (standard 1, used by the combined report of a 9001/14001/45001 audit). A standard without its own rows borrows another booked standard's list (wrong clauses) — import or seed its rows first.
- Stage 2 effectiveness elements (sections 10 to 13) start with the generic wording from the Word file in the evidence box; the auditor edits it.

## 6. Testing

See `POSTMAN_GUIDE.md` (step by step) and `DynamicAuditReport.postman_collection.json`. `CHANGES.md` lists what was corrected after checking the database dump. `samples/` has two PDFs produced from your data by the test run (printed with a different engine than Chromium, so spacing differs slightly from the real output).

## 7. Per-standard wording (ISO 9001 vs ISO 14001 vs others)

The two Word forms (04-2a for ISO 9001, 04-2b for ISO 14001) share one layout. Everything that differs in wording but is not a clause row lives in `report.standards.ts`:

| What | Where it comes from |
|---|---|
| Clause rows, Stage 1 readiness list, sections 10–13 rows and their pre-drafted "C" wording | `clause_library` rows of the report's `standard_id` (seed files `seed/iso9001.seed.ts`, `seed/iso14001.seed.ts`) |
| Document control block (Form No., Issue, Rev, Date) | `report_templates` row resolved for the standard + stage (`template_id` on the report) |
| Colours and cover picture (blue Form 04-2a / green Form 04-2b: `templates/cover.jpg` vs `templates/cover-14001.jpg`) | `theme` of the same profile (`ReportTheme`); the `.hbs` files read it as CSS variables, the Word service as `this.T` |
| Finding-category definitions, objective / criteria text, audit-brief lines, default "auditor's overall conclusion", section 10/11/12/13/15 titles and intros, interview line, attachment tick list (ISO 14001 adds the "mandatory documented information" block) | `profileFor(standard_name)` in `report.standards.ts` — ISO 9001, ISO 14001, ISO 45001, IMS (two or more of 9001/14001/45001 covered), or the generic profile |

The profile is chosen from the standard name saved on the report (`crm_json.standard_name`), so an approved snapshot keeps the wording it was approved with; snapshots made before this change have no `labels` and fall back to the profile of their standard at print time. A combined report that includes ISO 9001 keeps the Form 04-2a wording. HTML preview (`templates/*.hbs`), the Word file and the PDF (Word converted by LibreOffice) all read the same `vm.labels`.

To add another standard: seed/import its clause rows, add its `report_templates` rows (can point at the shared `form-04-1-stage1.hbs` / `form-04-2-stage2.hbs`), and add a profile entry in `report.standards.ts` if its wording differs from the generic one.
