# Checklists module — backend (NestJS, drop-in)

Replaces your existing `src/checklists-module/` folder. Everything your draft
left as TODO is implemented: multi-email notify with branded emails, client
user resolution, real-time notifications on every step, client-portal
endpoints, upload/download streaming, review results, completion detection.

## Install steps

0. **One new dependency** (the checklist PDF):
   `npm i pdfkit` and `npm i -D @types/pdfkit`

1. **Copy the folder** over `src/checklists-module/` (controller, new
   client controller, module, services, entities).

2. **Database** — run `migration.sql` against `scheme_dbs` once
   (4 new nullable columns on `audit_checklists`). If dev runs with
   `synchronize: true`, TypeORM adds them itself.

3. **Notification types** — append the 4 enum members from
   `NOTIFICATION-TYPES.txt` to
   `src/notifications/enums/notification-type.enum.ts`.

4. **Client portal guard** (one import) — `client-checklists.controller.ts`
   imports `ClientPortalAuthGuard` from
   `../client-portal/guards/client-portal-auth.guard`.
   Point it at whatever guard already protects `GET /client-portal/audits`.
   If your client JWT payload doesn't carry `company_id` under one of
   `company_id / companyId / company.id`, adjust `clientCtx()` at the top of
   that file (single function, clearly marked).

5. **company_users query** — `ChecklistsService.resolveClientUserIds()` runs
   `SELECT user_id FROM company_users WHERE company_id = ?`.
   If your bridge table/columns differ, change that one query. On failure it
   logs a warning and continues (emails still go out).

6. **Mail** — `sendEmail()` duck-types your MailsService: it calls
   `sendMail({ to, subject, html, attachments })` if present, else
   `send(...)`. `attachments` is nodemailer-style
   (`[{ filename, content: Buffer, contentType }]`), so if your MailsService
   forwards the object to a nodemailer transporter, the checklist PDF
   attachment works with zero changes. Otherwise align that one function.

7. **Env (optional)**
   - `CLIENT_PORTAL_URL` (or `FRONTEND_URL`) — base for the email CTA link
     `…/client/dashboard/audits/:rowId`
   - `CHECKLIST_ADMIN_NOTIFY_EMAIL` — cc an admin when a client submits
   - `CHECKLIST_DOCS_ROOT` — storage root (unchanged default)

## Endpoints

Staff (JwtAuthGuard) — everything the old controller had, plus:
| Method | Path | Purpose |
|---|---|---|
| GET | `/audits/:rowId/checklists` | grouped: one checklist per standard, full items |
| DELETE | `/checklists/:id` | discard a draft (refused after notify/uploads) |
| POST | `/checklists/:id/notify-client` | body `{ emails[], message?, due_date? }` → email + real-time |
| POST | `/checklists/:id/notify-review` | ONE batched result (rejections or all-approved); `notify-rejections` kept as alias |
| GET | `/checklists/items/:itemId/document` | stream an uploaded file |
| GET | `/checklists/:id/pdf` | the checklist itself as a PDF (same file attached to the client email) |

Client portal (ClientPortalAuthGuard, `/client-portal` prefix):
| Method | Path | Purpose |
|---|---|---|
| GET | `/client-portal/audits/:rowId/checklists` | only checklists that were sent (notified) |
| POST | `/client-portal/checklists/items/:itemId/upload` | multipart `file`, 10 MB |
| POST | `/client-portal/checklists/:id/submit` | allowed when every item uploaded → auditor email + real-time |
| GET | `/client-portal/checklists/items/:itemId/document` | own-company download |
| GET | `/client-portal/checklists/:id/pdf` | view the checklist PDF on the portal |

## Real-time
All in-app pings go through your existing `NotificationsService.send()` —
so they reach the same socket gateway / bell you already run:
- `CHECKLIST_READY` → client portal users, on notify
- `CHECKLIST_DOC_UPLOADED` → lead auditor, on every client upload
- `CHECKLIST_CLIENT_SUBMITTED` → lead auditor, on client submit
- `CHECKLIST_REVIEW_RESULT` → client portal users, on review result

## Storage
`checklist-storage.service.ts` now also accepts DOC/DOCX/XLS/XLSX
(PDF/PNG/JPG/WEBP unchanged, 10 MB limit unchanged).
