CRM · Receipt verification queue

Moderation workspace for customer-uploaded purchase receipts: review the image and OCR’d data, validate (which also redeems the entitlement), reject with a reason, un-reject, and navigate the queue with previous/next.

AreaCRM
Routescrm.receipts · crm.receipts.index
Centre modulestickets
Staff levelsroot · admin · manager · crmmanager · crmoperator · weboperator
Modelscampaign · commerce · receipt
UI labeltitle.header.crm.receipts / menu.navbar.receipt.show
Coverage5 use cases · 4 happy · 7 failure

Known issues (1)

How it works

The richest state machine in the app. Seven receipt states (draft, pending, not-redeemable, redeemable, not-yet-redeemable, redeemed, rejected) each with a colour and a tooltip; eight reject reasons (duplicated, illegible, modified, other centre, out of period, out of store, over max amount, other) and seven automatic pending reasons explaining why the system could not decide (no code, no store, over 250 amount, not this centre, out of date, no amount, invalid date). isValidating/isSavingReject/isUnrejecting guard concurrent mutations, and previousReceipt/nextReceipt with isFirstSelectedReceipt/isLastSelectedReceipt make high-volume moderation keyboard-fast. canSeeModerator reveals who moderated a receipt. reprocessPendingReceipts re-runs automatic processing over the pending backlog.

Use cases

Why someone opens this (5)

  • A moderator working through the overnight backlog of uploaded receipts.
  • Rejecting a duplicate receipt with an auditable reason the customer can be told.
  • Reversing a wrong rejection (unreject re-runs validation).
  • Re-processing the pending queue after fixing a store’s configuration or campaign dates.
  • Finding out which moderator handled a disputed receipt.

Path map

flowchart LR
    F(["Receipt verification queue"]):::feat
    F --> H1["Receipt validated"]:::ok
    F --> H2["Receipt rejected with a reason"]:::ok
    F --> H3["Rejection reversed"]:::ok
    F --> H4["Pending backlog reprocessed"]:::ok
    F -.-> X1["Validated but not redeemable"]:::bad
    F -.-> X2["Validation request fails"]:::bad
    F -.-> X3["Reject without a reason"]:::bad
    F -.-> X4["Unknown reject reason"]:::bad
    F -.-> X5["Reject save fails"]:::bad
    F -.-> X6["Embedded API errors"]:::bad
    F -.-> X7["Reprocess not permitted"]:::bad
    classDef feat fill:#157068,stroke:#157068,color:#ffffff,font-weight:bold
    classDef ok fill:#e3f1ea,stroke:#0d6b42,color:#0d4a2e
    classDef bad fill:#f7e6e2,stroke:#9a2d1c,color:#6e1f13

Happy paths

Receipt validated

  1. Moderator opens the queue filtered to pending and selects a receipt.
  2. Reviews the image against the parsed amount, date, code and store.
  3. Clicks validate; isValidating blocks double submission.
  4. The state moves to a validated state, validatedAt is stamped and the entitlement is redeemed.

Receipt rejected with a reason

  1. Moderator picks a reject reason (required) and saves.
  2. State becomes rejected, validatedAt is stamped and the reason is stored for the customer-facing explanation.

Rejection reversed

  1. Moderator opens a rejected receipt and clicks un-reject.
  2. unreject() delegates straight to validate(), so the receipt is re-validated in one step.

Pending backlog reprocessed

  1. Moderator triggers reprocess on the pending set.
  2. crm.receipts.reprocess-receipts-success confirms the job was accepted.

Failure paths

QA checklist

Tick these off while regression-testing this feature. The boxes are plain Markdown, so progress is visible in git diffs.

  • ✅ Receipt validated
  • ✅ Receipt rejected with a reason
  • ✅ Rejection reversed
  • ✅ Pending backlog reprocessed
  • ⛔ Validated but not redeemable
  • ⛔ Validation request fails
  • ⛔ Reject without a reason
  • ⛔ Unknown reject reason
  • ⛔ Reject save fails
  • ⛔ Embedded API errors
  • ⛔ Reprocess not permitted

Implementation


Generated from doc/feature-inventory.json · id crm-receipt-moderation · commit 276788ca6