Skip to content

FS-07 · Export publication-ready DOCX

SOP: SOP_Financial_Statements.md §6 / Step 7.0Actors: FS Reviewer (fs-reviewer@spade.local), Senior Accountant, or Platform Admin — EXPORT_FS_DOCUMENT permission. Pre-state: Engagement at NOTES_COMPLETE. Latest FsGeneratedStatement.isBalanced = true. Every mandatory disclosure rule answered. Post-state: A .docx artifact persisted to S3; signed URL returned. Engagement remains at NOTES_COMPLETE (export does not advance status).

0. Prerequisites

  • Engagement at NOTES_COMPLETE (see FS-06).
  • The caller holds EXPORT_FS_DOCUMENT. FS_PREPARER does not hold this action — by design (SoD).

1. Steps

1.1 Trigger export

http
POST /ops/financial-statements/engagements/<engagementId>/exports/word
Authorization: Bearer <fs-reviewer-jwt>

(Or click Export Word on the engagement detail page.)

The handler:

  1. Asserts EXPORT_FS_DOCUMENT.
  2. Asserts engagement is at NOTES_COMPLETE.
  3. Enqueues export-statements-docx with {engagementId, generatedStatementVersion?, requestedBy}.
  4. Returns 202 Accepted with a polling URL.

1.2 Worker — export-statements-docx

  1. Reads the latest FsGeneratedStatement for the engagement (or the specified versionNo).
  2. Reads every FsNote.
  3. Asserts:
    • isBalanced = true (or, with reviewer-override flag, emits a banner warning on the cover)
    • Every mandatory disclosure rule has every required prompt answered
    • Every mandatory policy text in the notes matches the constitution verbatim (assertion against mandatory_policies/*.md)
  4. Renders DOCX using @breezycorp/financial-statements/output/word:
    • Cover page (company name, title, year-end date, UEN)
    • Running header (company left, year-end right, thin bottom border)
    • Centred 8 pt page numbers
    • Directors Report (with financial-results table injected at placeholder position)
    • SOFP (4-col: Description / Note / CY / PY)
    • SPLOCI (2-col)
    • SOCE (zero-movement columns auto-omitted)
    • SOCF (Operating → Investing → Financing)
    • Notes auto-numbered (1. General Information, 2. Basis, …)
  5. Persists to S3 at financial-statements/<clientId>/<engagementId>/<filename>-v<n>.docx.
  6. Inserts a File row with fileKind = FS_DOCX_EXPORT.
  7. Returns the signed URL.
  8. Emits AuditEvent fs.export.generated with {filename, fileId, versionNo}.

2. Verification

S3

bash
docker exec breezycorp-monorepo-minio-1 mc ls local/breezycorp/financial-statements/<clientId>/<engagementId>/
# expect a .docx file with a timestamp suffix

Database

sql
SELECT id, original_name, storage_key, size_bytes
  FROM files
  WHERE client_id = '<clientId>' AND file_kind = 'FS_DOCX_EXPORT'
  ORDER BY created_at DESC LIMIT 5;

Audit log

Event typeNotes
fs.export.requestedAPI hit
fs.export.generatedBackground success; payload includes filename + signed URL + version

Manual inspection

Download the DOCX. Open in Word. Confirm:

  • Cover page shows the right company name, year-end, UEN
  • Running header shows the company name on the left and the year-end on the right
  • The Financial Results table appears inside the Directors Report at the right position
  • Notes are auto-numbered starting from 1
  • The income-tax policy paragraph in the policies note matches constitution/mandatory_policies/income_tax.md verbatim

3. Negative & edge cases

  • Caller without EXPORT_FS_DOCUMENT403 Forbidden. FS_PREPARER is rejected.
  • Engagement not balanced — handler refuses with 422 Unprocessable Entity and reason: "engagement_not_balanced". Regenerate statements after fixing the mapping.
  • Mandatory disclosure missing — handler refuses with reason: "mandatory_disclosure_missing". Open Interrogation.
  • Mandatory policy text mismatch — handler refuses with reason: "mandatory_policy_mismatch". Regenerate notes (the verbatim assertion is on the constitution side; a paraphrased Claude response can land in the notes but won't pass the export gate).
  • Re-export — allowed; each export produces a new .docx with a new versionNo. All versions are retained for audit.
  • Export from NOTES_ERROR — refused; the engagement must be at NOTES_COMPLETE.

Next

Proceed to FS-08 · Client portal sign-off (optional).

Internal use only — BreezyCorp