Design-first · Laravel 13 · PostgreSQL 18 · React 19 Wiki GitHub
POS

POS — Overview and Scope

What this is

A point-of-sale system for a single business operating multiple locations. It serves both retail (scan a barcode, pay, leave) and food service (open a tab against a table, add items over an hour, split the bill) from one core model.

The central idea

Retail and restaurant POS look like different products, but they are the same product at different speeds.

Both are: open an order, attach lines to it, take money, close it.

We model one order lifecycle and let the UI compress or expand it. We do not build two systems behind a shared login. Where the domains genuinely diverge, we add an optional concept rather than a parallel hierarchy:

Need Retail Food service Our model
Sellable thing SKU with barcode Menu item product_variant
"Blue, size L" Distinct stocked SKU Variant (own barcode, own stock)
"Extra shot, no onions" Priced adjustment Modifier (not stocked)
Where the order lives Nowhere, it's instant Table 12 Optional order.table_ref
Who owns the order The register The server order.opened_by

Variants and modifiers are the distinction people most often get wrong, so to be explicit: a variant is a thing you count in a stockroom; a modifier is a thing you say to the person making it. "Large" is a variant of a t-shirt and a modifier of a latte, and that is correct — it depends on whether you keep a shelf of them.

Principles

  1. Money is integers. Amounts are bigint in the currency's minor unit (cents). No floats anywhere, in any layer, ever. See 01-architecture.md.
  2. Financial records are append-only. A closed order is immutable. A refund is a new record, never a mutation of the original. A payment is never edited. If you want to know what happened, the rows tell you.
  3. Stock is a ledger, not a number. We record movements and derive levels. "Why is my count wrong?" must always be answerable.
  4. The server owns the truth. v1 is online-only (see below), so there is exactly one authority for every ID, price, and total. The client never computes a total it then sends us.
  5. Never double-charge. Every mutating request carries an idempotency key. A retry on a flaky network must be a no-op, not a second payment.
  6. Prices are decided at order time and then frozen. A line item stores the price it was sold at. Changing a product's price tomorrow must not rewrite yesterday's receipts.

v1 decisions

These were chosen deliberately; the reasoning matters as much as the choice.

Non-goals for v1

Named explicitly so they don't creep in:

Glossary

Terms are used in this exact sense throughout the docs and the code.