Skip to content

P-11 · Finalize, close, archive

SOP: Payroll_Processing.md §6 / Step 7.0 (S14 → S16)Actors: Payroll Executive (PE) for Finalize and Close. The Archive transition is automatic via the monthly retention-purge job. Pre-state: Cycle in APPROVED. Post-state: Cycle transitions APPROVED → FINALIZED → CLOSED (manual). Eventual CLOSED → ARCHIVED (cron).

0. Prerequisites

  • Client has approved (P-10, happy path).

1. Steps

1.1 Capture post-payroll evidence (optional but recommended)

Per SOP §6 / Step 7.0, attach evidence files before finalizing:

  • Bank disbursement confirmation
  • CPF submission acknowledgement
  • IR21 acknowledgement (if any foreign leavers)

Upload via the cycle's Files tab (fileKind = EVIDENCE). They are referenced from the audit log but do not block the transition.

1.2 Finalize

On /dashboard/cycles/<id>, click Finalize.

http
POST /ops/cycles/<id>/finalize
Authorization: Bearer <staff-jwt>

Cycle transitions APPROVED → FINALIZED. Audit event cycle.finalized. From this state, no further submission, export, or approval is allowed.

1.3 Close

Click Close.

http
POST /ops/cycles/<id>/close
Authorization: Bearer <staff-jwt>

Cycle transitions FINALIZED → CLOSED. Audit event cycle.closed. The cycle now appears in archived listings on /dashboard/cycles?status=CLOSED.

1.4 Archive (automatic)

The retention-purge job runs at 0 3 1 * * UTC (03:00 on the 1st of each month). It transitions every CLOSED cycle older than the configured retention window (default: keep CLOSED for 12 months, then ARCHIVED). ARCHIVED cycles surface only under explicit filter.

This is fully automatic — do not block manual testing on it.

2. Verification

Database

sql
SELECT status, finalized_at, closed_at, archived_at FROM payroll_cycles WHERE id = '<cycleId>';

Audit log (/dashboard/cycles/<id>/audit)

The full lifecycle is visible end-to-end:

cycle.created
cycle.requested
cycle.intake_started
file.finalized × N
ocr.completed × N
submission.submitted
cycle.submitted
validation.run.completed
issue.* (any resolutions / overrides)
cycle.ready_for_internal_review
cycle.exported
approval_report.generated
approval.requested
approval.responded
cycle.approved
cycle.finalized
cycle.closed

Mailpit

No new outbound emails fire on Finalize / Close. (A future enhancement could email a finalization summary; not implemented today.)

3. Negative & edge cases

  • Finalize on a non-APPROVED cycle → 400 with the actual current status.
  • Close on a non-FINALIZED cycle → 400.
  • Archive a non-CLOSED cycle manually → no manual archive endpoint; the only path is the cron.
  • Re-open a closed cycle — there is no re-open API. Instead, open a brand-new cycle for the same month? No — the unique constraint (client_id, cycle_month) blocks that. For correction-after-close, you handle it in the next month's cycle as a RETRO_ADJUSTMENT change type (handled via the next cycle's intake).

Done

The cycle has run end-to-end.

Internal use only — BreezyCorp