business Case study
Repair & After-Sales Desk
A watch that stays on the bench for a week is a customer relationship with money in it. The repair desk is the part of the store where the paper trail used to be a drawer.
The business problem
The retailer's repair and after-sales work ran on paper and memory: a technician wrote the fault on a slip, the price was agreed on the phone, the watch sat in whichever drawer was free, and the customer came back to whoever was on shift. Nothing connected the file to the customer's account, so a person who had bought three watches online and left one for service was two strangers to the system.
The owner described the idea as raw and asked for a first phase that fixed the axes that would otherwise be re-argued mid-build: what currency staff type and what the database stores, who may see and change a file, where the customer approves a price, and whether repair revenue counts toward the loyalty club. Those four decisions shaped everything below.
What I delivered
- A technician PWA for repair files: intake with mandatory fault, running-state and storage-drawer fields, a quote, the customer's approval, payments, and a PIN-gated hand-over — with a three-column desk layout on wide screens and a phone layout on the bench.
- Intake that opens a customer account for the phone number through the same creation path the store uses, so the customer can log in by one-time code, see a clean invoice, and approve the quote from a repairs menu in their own panel — not by replying to a text.
- A closing flow where every payment attempt from an open sheet carries one idempotency key minted when the sheet opened, a settlement is refused when nothing is owed or when it exceeds the balance, and delivering an already-delivered file is a replay that writes nothing twice.
- Two prices tracked independently — the sum of the lines and the total the customer was quoted by text — with a warning strip when they differ and a one-tap re-quote, and a closed list of two storage drawers instead of free text.
- Three pricing books that answer different questions: declared base prices by tier, brand and exception; a learned price history written when a job reaches ready; and battery fitment by reference. Where history and declaration both exist, history wins; where neither does, the answer is 'announced after inspection'.
- A management dashboard and a spreadsheet export that are admin-only, exclude provisional files from every count, and store amounts as numbers so the owner's own sums work; repairs feeding the loyalty club at their own rate through the one reward service, never by writing the ledger directly.
Technical approach
- Staff enter and see Toman while the database stores Rial, because the in-person orders table already stored Rial and two units in one schema is how balances go wrong. The conversion lives at the input and display boundary only.
- Every technician sees and edits every file, including its prices, because a colleague hands the watch over when the intake technician is off; the dashboard and the export stay admin-only. Access follows who physically does the work.
- The closing flow was rebuilt after two real files settled at large negative balances: six identical settlements in forty seconds — pay, wrong PIN, refused, tap again — on shop wifi where the app retries dropped requests. Anything money-shaped now needs an idempotency key per sheet, not per tap, and the server refuses a settlement that nothing justifies.
- The hand-over sheet answers every gate before the tap. The old sheet hid the PIN override in a collapsed panel with a second, unannounced gate behind it; a technician failed three times typing a reason too short for the server. Now the sheet shows the choices — has the PIN or not, pays now or not — each revealing its own reason box that counts the characters still owed, and nothing is sent until all gates pass. The PIN is only demanded when it was actually sent to the customer.
- Price history is recorded when a job reaches ready, never on delivery, because delivery would count the same job twice; free lines, keyless lines and quantities are ignored so the book stays a book of prices, not of invoices.
Result and evidence
The module runs on the bench of a watch and jewellery retailer, with the four owner decisions unchanged since scoping. The invariants are what I verified rather than a speed figure: a payment key is minted once per open sheet and a seen key returns 'replayed'; a delivered file delivered again writes no second timeline row and fires no second event; provisional files are absent from every count; the export's amounts sum in a spreadsheet without formulas. The closing-flow rebuild came out of two real bad settlements, and the gate sheet out of a technician's three failed taps — both are in the timeline, and neither has recurred.
Commercial value
A service desk is where a retailer either keeps or loses the customer after the sale. When the repair file lives in the same account as the purchases, the customer approves a price from their phone instead of a call, the hand-over cannot happen twice, and the owner can read the month from a spreadsheet that adds up, after-sales stops being a cost centre nobody can see and becomes a second reason to come back — and it now earns club points like a purchase does.
Readable implementation brief
implementation_brief {
project: "Repair & After-Sales Desk"
status: running on a retailer's bench
intake: fault + running_state + drawer (mandatory) -> provisional file
identity: phone opens the customer's account -> OTP login -> approve quote in panel
money: staff see Toman, storage is Rial, conversion at the boundary only
payments: one idem key per open sheet; settle refused if nothing owed or over balance
handover: PIN only if it was sent; over-quote answered by any technician with a reason
prices: declared base -> learned history (written at ready) -> battery fitment
rule: history beats declaration; absent = "announced after inspection"
reporting: dashboard + numeric export, admin-only, provisional files excluded
loyalty: repairs earn points through the reward service, never the ledger directly
showcase: github.com/shiny-a2/a2-crm-operations-system
}What this project shows
The part I would want a reviewer to read is the closing flow: two bad settlements became an idempotency key with a defined scope, a refusal rule on the server, and a hand-over sheet that cannot be submitted half-answered. That is the shape of most operational bugs I fix — not a missing feature, but a gate that lived in the wrong place.
The four scoping decisions are the other thing. Fixing the money unit, the access model, the approval surface and the loyalty seam before writing code meant the module did not have to be re-argued mid-build, and when the owner later connected repairs to the club, the seam was already the only door.