feat: basic POS & invoicing #26

Merged
ghost merged 1 commits from feat/basic-pos-invoicing into main 2026-03-17 20:02:04 +00:00
ghost commented 2026-03-17 20:01:00 +00:00 (Migrated from github.com)

Summary

Implements groombook/groombook#5 — Basic POS & Invoicing.

  • Database: New invoices and invoice_line_items tables with migration 0002_invoices.sql. Invoice statuses: draft, pending, paid, void. Payment methods: cash, card, check, other.
  • API: Full invoicing endpoints — create manually or one-click from appointment, list with filters, get with line items, update status/payment/tip/notes.
  • Frontend: Invoices page with status filter, create-from-appointment modal, detail modal with tip entry, payment method selection, Mark as Paid and Void actions.

Key design decisions

  • Invoice from appointment auto-populates service name and price; returns 409 if invoice already exists for that appointment.
  • Tip is editable until payment; tax is set at creation time (0% default — business configures via API).
  • Voided invoices are immutable (no edits allowed after voiding).
  • paidAt is auto-set to now() when status changes to paid.
  • Amounts stored as integer cents throughout — no floating point.

Test plan

  • TypeScript, lint, and vitest all pass (verified locally)
  • Create invoice from a completed appointment — check pre-populated line item
  • 409 returned if trying to create second invoice for same appointment
  • Mark invoice as paid — confirm paidAt is set and form becomes read-only
  • Void an invoice — confirm edits are blocked afterward
  • Status filter on invoices list works (draft/pending/paid/void/all)

Closes groombook/groombook#5

🤖 Generated with Claude Code

## Summary Implements groombook/groombook#5 — Basic POS & Invoicing. - **Database**: New `invoices` and `invoice_line_items` tables with migration `0002_invoices.sql`. Invoice statuses: `draft`, `pending`, `paid`, `void`. Payment methods: `cash`, `card`, `check`, `other`. - **API**: Full invoicing endpoints — create manually or one-click from appointment, list with filters, get with line items, update status/payment/tip/notes. - **Frontend**: Invoices page with status filter, create-from-appointment modal, detail modal with tip entry, payment method selection, Mark as Paid and Void actions. ## Key design decisions - Invoice from appointment auto-populates service name and price; returns 409 if invoice already exists for that appointment. - Tip is editable until payment; tax is set at creation time (0% default — business configures via API). - Voided invoices are immutable (no edits allowed after voiding). - `paidAt` is auto-set to now() when status changes to `paid`. - Amounts stored as integer cents throughout — no floating point. ## Test plan - [ ] TypeScript, lint, and vitest all pass (verified locally) - [ ] Create invoice from a completed appointment — check pre-populated line item - [ ] 409 returned if trying to create second invoice for same appointment - [ ] Mark invoice as paid — confirm `paidAt` is set and form becomes read-only - [ ] Void an invoice — confirm edits are blocked afterward - [ ] Status filter on invoices list works (draft/pending/paid/void/all) Closes groombook/groombook#5 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This repo is archived. You cannot comment on pull requests.