Skip to content

P-03 · Client intake — Zero-change fast path

SOP: Payroll_Processing.md §6 / Step 2.0 (S1 → S4) — one-click variation per 2026-04-22 client requirementActors: Client Submitter (CR) — magic-link authenticated. Pre-state: Cycle in REQUESTED. Submitter has the data-request email. Post-state: Cycle transitions REQUESTED → INTAKE_IN_PROGRESS → SUBMITTED with a zero-item submission and a server-synthesized declaration marker.

This is the happy-path shortcut for clients with no payroll deltas this month. The magic-link click is treated as consent — no monthly declaration form is required.

0. Prerequisites

  • P-01 has run for the target client + month.
  • Submitter has the data-request email open in mailpit.

1. Steps

There are two entry points; both funnel into the same zero-touch submit.

Entry A — one-click from email

Click No changes this month in the mailpit email. Browser lands on http://localhost:3000/portal/<jwt>?choice=no.

Entry B — in-portal decision card

If the client opened the portal first (or clicked Yes then reconsidered), the in-portal decision card offers a No changes this month button that auto-submits without entering the wizard.

Server behaviour (both entries)

  1. Portal auto-POSTs to /portal/cycles/<token>/submit with no monthlyDeclaration field:

    json
    {
      "attestationText": "Zero-change confirmation via one-click \"No changes\" flow."
    }
  2. Server detects the fast-path (declaration absent and zero items in draft) and:

    • Skips validateDeclaration (declaration is not required for zero-touch confirmations).

    • Skips the synchronous BLOCKING validation gate (ATTENDANCE_REQUIRED et al. are no-ops with no items to validate).

    • Skips the completeness gauge (0% is the correct state for a no-changes cycle).

    • Auto-creates an empty DRAFT submission and transitions it to SUBMITTED.

    • Persists a server-synthesized marker in submissions.monthly_declaration:

      json
      {
        "noChangesFromPriorMonth": true,
        "signedAt": "<iso timestamp>",
        "source": "NO_CHANGES_FAST_PATH"
      }
  3. Cycle transitions REQUESTED → INTAKE_IN_PROGRESS → SUBMITTED. The intermediate INTAKE_IN_PROGRESS is logged but the user never sees it.

  4. Portal renders a dedicated zero-change success card — green tick, "You're all set — we've recorded zero payroll changes for {month}", plus a single secondary "Changed my mind — open the portal" button (visible only while the cycle is still SUBMITTED).

2. Verification

Database

sql
SELECT
  s.version_no,
  s.submitted_at,
  s.monthly_declaration ->> 'source'                AS source,
  s.monthly_declaration ->> 'noChangesFromPriorMonth' AS no_changes,
  (SELECT COUNT(*) FROM submission_items WHERE submission_id = s.id) AS item_count
FROM submissions s
WHERE s.cycle_id = '<cycleId>'
ORDER BY s.version_no DESC LIMIT 1;
-- source           = 'NO_CHANGES_FAST_PATH'
-- no_changes       = 'true'
-- item_count       = 0

Audit log

Exactly one submission.submitted event — not two — even if the user reloads ?choice=no. The backend idempotency guard in apps/api/src/routes/portal/index.ts short-circuits when the cycle is already past intake.

Mailpit

No outbound email is sent at this step.

3. Negative & edge cases

  • Reload ?choice=no after success → portal renders the same success card. API returns 200 OK with newStatus = SUBMITTED. No second submit happens.
  • Items already drafted (item count > 0) + missing declaration → backend rejects with DECLARATION_INCOMPLETE. The fast-path is strictly scoped to zero-item submissions.
  • Legacy compatibility — clients / integrations that still send a full monthlyDeclaration with noChangesFromPriorMonth: true continue to work unchanged. The declaration is validated and persisted verbatim. The fast-path kicks in only when the body omits the declaration altogether.
  • "Changed my mind" — clicking the secondary button reopens the wizard, but the cycle stays at SUBMITTED until the wizard re-submits. To convert a zero-change submission into a real one, the client withdraws via the in-portal Withdraw submission action; that transitions back to INTAKE_IN_PROGRESS and clears the NO_CHANGES_FAST_PATH marker.

Next

The cycle is in SUBMITTED. Validation runs automatically as the first step of P-07 · Generate export. Auto-approval requests fire at the same step (per the 2026-04-22 client requirement).

Internal use only — BreezyCorp