Skip to content

P-05 · Ask the client (issue resolution loop) ​

SOP: Payroll_Processing.md §7 — Exception handling / re-request loopActors: Payroll Executive (PE) — executive@spade.local. A payroll sender (or approver) on the receiving side. Pre-state: An open, client-askable WorkflowIssue on the month — or, for a reminder, a month that is waiting on the client. Post-state: The wording staff approved is sent word for word; WorkflowIssue.clientAskedAt is set; the client's reply shows under the issue. While the month is ACTION_REQUIRED, the hourly sweep follows up on day 3 / 5 / 7 until the client answers or writes on that thread.

The old request-info route (POST /ops/payroll/cycles/:cycleId/request-info), its payroll.intake-reminder job and the portal-link "action needed" email are gone. Staff now write to the client in two ways, both draft-then-send from the month's Work page (/dashboard/work/<id>):

  • Ask client — a question about one check, or Ask about N together for several checks in one numbered message.
  • Send reminder — the stage's reminder, from the Client reminder section.

0. Prerequisites ​

  • A month with at least one open issue the client can answer (internal issues have no Ask client).
  • For a reminder: the month is REQUESTED, INTAKE_IN_PROGRESS, REVISION_REQUESTED, or ACTION_REQUIRED with a question still waiting.
  • At least one payroll sender who can be reached: an email address (and a payroll mailbox set in Settings › Integrations), or a chat they have written on.

1. Steps ​

1.1 Ask about one check ​

Click Ask client on the check. The console reads the draft:

http
GET /ops/issues/<issueId>/ask-client/draft

It returns { body, aboutDocument? } — aboutDocument names the file the check is about, and whether the question will quote the client's own message. The dialog Ask the client shows the body and a To: line. Edit, then Send to client:

http
POST /ops/issues/<issueId>/ask-client
Content-Type: application/json

{ "body": "<the wording staff approved>" }

The route records who approved which words (audit), checks that someone can be reached, and enqueues payroll.issue-client-ask. It returns 202 { queued: true }.

1.2 Ask about several checks together ​

Ask about N together (2–6 checks not yet asked):

http
GET  /ops/cycles/<cycleId>/ask-client/draft?issueIds=a,b,c
POST /ops/cycles/<cycleId>/ask-client     { "issueIds": [...], "body": "..." }

The message is numbered; a reply "1 yes, 2 no" is put on each check by its number. Wording that has lost a number is refused before anything is queued.

1.3 Where the question goes (worker) ​

payroll.issue-client-ask plans recipients as a QUESTION (payroll-recipient.ts):

  1. About something the client sent: back on the thread it came on, to its sender.
  2. Otherwise: to whichever payroll sender wrote to us most recently, on that thread.
  3. Nobody has written: by email to the first payroll sender who can be reached.

A WhatsApp thread is used only within 24 hours of the client's last message on it. On send the worker sets WorkflowIssue.clientAskedAt and records a client_questions row (kind = CHECK) with the thread it went on.

A low-confidence field (OCR_LOW_CONFIDENCE) is asked the same way: there is no automatic "send a clearer copy" any more.

1.4 The client replies ​

The reply appears under the check. It does not close the check — staff clear it (fix + Re-check, Resolve…, or a Payroll Lead override; see P-04).

1.5 Follow-up while ACTION_REQUIRED ​

The hourly check-pending-submissions sweep (from 10:00 in the company's own time) counts day 3 / 5 / 7 from the newest ask still waiting and sends "Just a reminder — we're still waiting on the items we asked you for so we can finish your {Month YYYY} payroll." — only on the thread(s) the question went on. An ask stops counting as waiting once its question is answered or withdrawn, or the client has written anything on that thread since. With automatic messages off (the platform's MSG_AUTO_REPLY_ENABLED=false, or the company's switch on Settings → Payroll) it is held and staff get a REMINDER_DELIVERY_FAILED task instead. See P-12.

1.6 Send reminder (staff) ​

In the Client reminder section, Send a reminder now / Review and send reminder:

http
GET  /ops/cycles/<cycleId>/client-reminder/draft     → { body, kind }   (409 when not waiting on the client)
POST /ops/cycles/<cycleId>/client-reminder           { "body": "..." } → 202

The POST writes payroll.client_reminder_approved and enqueues payroll.client-reminder-send. A staff-sent reminder is prompted, so it goes with automatic messages off; it goes where the automatic one would: about asked questions, on the thread(s) they were asked on (ACTION_REQUIRED); after a revision request, to the approver who asked (REVISION_REQUESTED); at intake, to each payroll sender by email on their thread for the month (chat only for someone with no email). Delivered, it clears the REMINDER_DELIVERY_FAILED task.

2. Verification ​

Database ​

sql
SELECT id, issue_code, status, client_asked_at
  FROM workflow_issues WHERE cycle_id = '<cycleId>' AND client_asked_at IS NOT NULL;

SELECT kind, subject_key, conversation_id, status, asked_at
  FROM client_questions WHERE cycle_id = '<cycleId>' ORDER BY asked_at DESC;

API ​

GET /ops/cycles/<cycleId>/client-notices — every month-level message the client was told (requests, reminders, "we have everything", "done"), newest first, with the words each person was sent; links are left out.

Audit sequence ​

payroll.issue_client_ask_sent      (one per ask that went)
payroll.client_reminder_approved   (staff reminder: who approved which words)
payroll.silence_reminder_sent      (the reminder that went, on its thread)
payroll.client_notice_sent         (on the cycle, for the reminder: who it reached and the words)

3. Negative & edge cases ​

  • Issue not open → 400 "Issue is not open."
  • Internal issue → 400 "This issue is internal to the payroll team and cannot be put to the client."
  • No wording → 400 Nothing was sent: no message was given. Open "Ask client", read the draft, and send it from there.
  • Nobody reachable → 409 "Nobody to ask: … Add their email or link their chat on the company's People tab."
  • Ask together with fewer than 2 or more than 6 checks → 400.
  • Reminder when the month is not waiting on the client → 409 "This cycle is not waiting on the client, so there is no reminder to send."
  • A person with an email and no payroll mailbox set → not sent and not moved to chat; staff task: "… is emailed (…), but no payroll mailbox is set up to send from (Settings › Integrations)".

Next ​

Once the checks are cleared and validation passes, proceed to P-06 · Internal review → P-07 · Generate export.

Internal use only — BreezyCorp