Part 5 of 7 · Receipt organizer series ~5 min read

How a receipt reaches the books

A confident receipt files itself — one clean row in the expense sheet, the image tucked into this month’s folder, done. The interesting case is the unsure one sitting in the bookkeeper’s review queue. There’s a card with the receipt image, the fields the system read, the proposed category, and three buttons. This post walks through the three things the bookkeeper can do — approve, correct, reject — and how the expense sheet, the stored image, and the audit trail all stay in sync.

Key takeaways

  • Confident receipts file straight to the expense sheet with no human touch.
  • Three actions on a review item: approve (file as-is), correct (fix, then file), reject (set aside).
  • Each action updates the expense sheet via the Sheets API and writes an audit row.
  • A correction can teach a new vendor hint, so the next similar receipt files on its own.
  • Every filed record links back to its original image — the proof is one click away.

Three actions on a review item

Three actions on a review item A diagram showing one input on the left flowing through a small interactive review card, then branching into three action paths. Far left: a "Review card in Slack" box showing a typical review item — the receipt image, the read fields (vendor, date, total, tax), and the proposed category — with three button placeholders below: Approve, Correct, Reject. The bookkeeper taps one button. The middle column shows the three branches. Branch one, Approve: the read fields and proposed category were right; a Function URL Lambda writes the clean row to the expense sheet via the Sheets API, files the image in this month's folder, marks the receipt filed in the ro-receipts table, and writes an approved event to the audit trail. Branch two, Correct: opens a small modal pre-filled with the read fields and the proposed category so the bookkeeper can fix whatever's wrong — a smudged total, the date, the category — then save; the Function URL Lambda writes the corrected row to the sheet, files the image, and offers to remember a vendor-to-category correction as a new hint so the next similar receipt files on its own. Branch three, Reject: the item isn't a valid expense — a personal purchase, a duplicate that slipped through, a junk image; the Lambda moves the receipt to a rejected folder with a one-line note and writes a rejected event; nothing reaches the expense sheet. The right side shows the convergence: every action writes a row to the ro-audit DynamoDB table with timestamp, receipt id, action, by-user, and a before-and-after snapshot. A note at the bottom: every filed record links back to its image — the proof of any expense is always one click away. Review card in Slack image, fields, category [Approve] [Correct] [Reject] Action 1 Approve • Fields and category right • Sheets API writes the row • Image filed by month • Marked filed Action 2 Correct • Modal: fix field or category • Saves the corrected row + offers a vendor hint Action 3 Reject • Not a valid expense — personal, junk, dup • Moved to rejected — nothing files Audit trail DynamoDB ro-audit timestamp · receipt_id action · by-user before / after Every filed record links back to its image — the proof of any expense is one click away.
Fig 5. Three actions per review item, three different effects. Approve files as-is. Correct fixes a field or category, then files, and can teach a vendor hint. Reject sets it aside. Every action writes to the audit trail.

Action 1: approve (the most common)

The bookkeeper opens the queue first thing. The top card is a fuel receipt the system read at 88% on the total — just under the threshold — but the image is perfectly clear and “$83.40” is right there. She taps Approve.

The button submits to a Function URL Lambda. Three things happen, in order. First, the Sheets API writes one row to the expense sheet: date, vendor, total, tax, category, who submitted it, and a link to the stored image. Second, the receipt’s image is moved into the folder for this month — 2026-05/ — so the original is filed where the bookkeeper expects it. Third, an action: approved row is written to ro-audit with the user, the timestamp, and the fields as filed. The card updates in place to “Filed by Maria,” and it’s gone from the queue.

Approving is the path for the receipts the system read correctly but wasn’t quite sure enough to file on its own. It’s a glance and a tap — seconds, not minutes.

Action 2: correct (fix, then file)

Some review items genuinely have something wrong. The total was misread — “$8?.40” should be “$83.40.” The date didn’t parse. The model guessed “software” for a vendor that’s really office supplies. Correct opens a small modal pre-filled with everything the system read, so the bookkeeper isn’t retyping — she just fixes the one field that’s wrong and hits Save.

On save, the Function URL Lambda writes the corrected row to the expense sheet, files the image, and writes an action: corrected audit row that records both the original read value and the corrected one. If the correction was a category change for a known vendor, the modal offers a checkbox: “Always file [vendor] as [category]?” Tick it and a new vendor hint is added to the rules doc, so the next receipt from that vendor sails through Gate 1 and never reaches a human again. This is how the queue shrinks over time — every correction is a chance to teach the system one more thing it won’t have to ask about again.

Action 3: reject (set it aside)

Sometimes the right answer is “this shouldn’t be in the books at all.” A staff member forwarded a personal purchase by mistake. A duplicate slipped past the automatic check because the photo was cropped differently. The image is junk — a thumb over the lens. Reject moves the receipt to a rejected folder with a one-line reason and writes an action: rejected audit row. Nothing reaches the expense sheet.

Rejecting isn’t deleting. The image and the record stay in the rejected folder, so if a staff member asks “what happened to that receipt I sent?” there’s an answer — “rejected as a personal purchase by Maria on the 12th” — rather than a mystery. The monthly summary in Part 6 reports the count of rejects so nothing disappears quietly.

Every action is logged, every action is reversible

The ro-audit table records every approve, correct, and reject with the user who did it, the timestamp, and a snapshot of the record before and after. If a wrong total gets entered during a correction (fat-fingered a digit), a small “undo last action” admin command reads the previous-state snapshot and restores the row — and the undo is itself an audit row, so the trail stays clean. Because every filed record links back to its original image, an auditor or an accountant who questions a line next year can open the proof in one click.

This reversibility and this paper trail are the whole point at tax time. The books aren’t just a list of numbers somebody typed; every number traces back to a receipt, a person, and a moment — and the unclear ones were all confirmed by a human before they counted.

Next post: the cost breakdown. The whole pipeline above runs in coffee-money territory at SMB volume; Part 6 explains exactly where the dollars go and why Textract is the only line that grows with the number of receipts.

All posts