# Migration Plan - ERP/POS to Go, PostgreSQL, and Redis ## 0. Governing policy - Business-rule authority follows `docs/business-rules.md`: Confirmed Client Rules, Laravel/MySQL behavior, existing Go behavior, then generic assumptions. - Confirmed client requirements supersede conflicting Laravel behavior. In particular, target POS pricing is server-authoritative and negative stock is rejected. - PostgreSQL is the source of truth for sales, stock, fulfillment, invoice, payment, return, and finance data. - Redis is temporary infrastructure for cache, rate limits, idempotency, and temporary state. It is never the source of truth for business transactions. - The Laravel application and `../ERPGUDANG/erp-gudang.sql` remain migration evidence until data import and parity gates pass. - A route or table does not make a workflow complete. Completion requires correct validation, transaction boundaries, inventory, finance, numbering, concurrency behavior, synchronized OpenAPI, and real integration tests. - New ambiguous policy is not implemented. It is registered as **REQUIRES BUSINESS CONFIRMATION**. Priority scope: - **P0:** POS, ERP core, inventory, sales, purchasing, finance, cashflow, owner dashboard. - **P1:** distribution, Delivery Order, partial shipment, fleet, dispatch, tracking, B2B/e-commerce. - **P2:** mobile, additional automation, and advanced integrations. ## 1. Current capability status | Capability | Status | Evidence / remaining work | |---|---|---| | Audit and architecture | COMPLETE | Laravel/database audit, domain map, risks, architecture, and sqlc/Atlas migration docs exist | | Go foundation | COMPLETE | Chi, pgx, PostgreSQL, Redis client, health/readiness, logging, middleware, Docker | | Auth and RBAC | IMPLEMENTED, HARDENING PENDING | opaque token and permission model exist; route contract coverage remains | | Master data | PARTIAL | CRUD exists; multi-unit, brand, barcode, server pricing configuration, and contract parity remain | | Inventory | PARTIAL / P0 BLOCKER | stock/opname/draft transfer exist; locking, movement correctness, negative-stock policy, reservation lifecycle, and integration tests remain | | POS | PARTIAL / P0 BLOCKER | multi-item transaction exists; price is still client-influenced, negative stock is possible, and idempotency/concurrency coverage is absent | | Sales | PARTIAL / P0 BLOCKER | SO lifecycle and reservations exist; locking and fulfillment conversion remain incomplete | | Purchasing | PARTIAL | PO/GR/debt/voucher exist; contract parity, flags, and real workflow tests remain | | Finance and cashflow | PARTIAL | invoice/payment/cashflow exist; period locks, reversal/adjustment, invoice deletion guards, and contract parity remain | | Reports / owner dashboard | PARTIAL | KPIs and CSV exist; envelope/filter parity and scale validation remain | | HR and sales tracking | IMPLEMENTED, NOT P0 COMPLETE GATE | legacy workflows exist; unresolved HR policy remains; employee tracking is not shipment tracking | | Contract parity | INCOMPLETE | missing/extra operations, employee path drift, filters, schemas, envelopes, status codes | | Document numbering | PARTIAL / P0 BLOCKER | atomic counter helper exists but transaction propagation/error handling and monthly target format remain | | Distribution | NOT STARTED | no DO, shipment, fleet, dispatch, partial-delivery, or delivery history aggregate | | Shipment tracking | NOT STARTED | existing GPS tracks sales employees only | | B2B membership/pricing | IMPLEMENTED (provisional) | membership CRUD + Bronze/Gold/Platinum tiers; points accrual + auto-tier promotion implemented with §20 defaults (business-rules §20) | | Galon | IMPLEMENTED (provisional) | galon transactions exist; deposit defaults 0; loan tracking + `ReturnGalon` implemented with §20 defaults (business-rules §20 #5) | | Return and cashier shift | IMPLEMENTED (provisional) | returns + cashier shifts exist; damaged-return condition + skip-restock and register/store binding implemented with §20 defaults (business-rules §20 #6/#7); tax/promotion allocation still deferred | | Idempotency | NOT STARTED / P0 BLOCKER | documented header only; critical routes require Redis fail-closed behavior | | Real integration testing | NOT STARTED / P0 BLOCKER | unit/fake parity tests exist; no real PostgreSQL/Redis concurrency workflow suite | | Importer/checksum | NOT STARTED | source dump is available at `../ERPGUDANG/erp-gudang.sql` | | Storage local/S3/R2 | NOT STARTED | legacy file behavior audited; no Go abstraction or adapters | ## 2. Delivery sequence ### Phase 1 - Contract sync Goal: make OpenAPI, mounted routes, request validation, filters, response envelopes, and status codes describe the same API. Deliverables: 1. Make `/api/v1/employees` the canonical employee CRUD path. Keep attendance, leave, payroll, and performance under `/api/v1/hr/...`. 2. Implement customer search and Purchase Order update. 3. Implement invoice DELETE with unpaid/no-payment guards and exact Sales Order status restoration from status history. 4. Implement cashflow detail/update and accounting period locks. 5. Document existing warehouse detail, cart clear, opname physical update, goods-receipt detail, voucher detail, and invoice-payment routes. 6. Standardize paginated collections to `data` plus `meta.pagination` and `meta.request_id` immediately, including receivables, supplier debts, and cashflows. 7. Return `204 No Content` for successful DELETE operations. 8. Implement every OpenAPI-advertised filter or remove it from the contract. 9. Reconcile material resource schemas with actual JSON. 10. Add route-table, handler contract, filter, envelope, status, period-lock, and invoice-delete tests. Phase gate: - Every mounted public API operation is represented once in OpenAPI. - Every OpenAPI operation is mounted and tested. - OpenAPI validation passes. - No collection returns an undocumented legacy envelope. - Invoice deletion and cashflow mutation rules are transaction-safe and audited. ### Phase 2 - Core transaction integrity Goal: stabilize P0 workflows before expanding the domain. Deliverables: 1. Make backend pricing authoritative for POS and checkout. Reject client price/discount/total overrides. 2. Reject negative stock immediately; remove the Laravel-compatible target behavior. 3. Add deterministic `FOR UPDATE` stock locking for normalized multi-item transactions. 4. Fix stock movement `quantity_before`/`quantity_after` to come from the same locked mutation. 5. Enforce availability and reservation invariants for regular and B2B Sales Orders. 6. Move SO, invoice, payment, and relevant PO state reads inside locking transactions. 7. Put cart order creation, reservation, and cart clearing in one transaction boundary. 8. Implement accounting reversal/adjustment for generated cashflow corrections. 9. Implement mandatory Redis idempotency for POS checkout, Sales Order creation, and payment. Shipment and return adopt the same mechanism when added. 10. Fail closed with `503 Service Unavailable` before transaction execution when Redis is unavailable for a critical idempotent request. 11. Wire existing parity flags deliberately and test both default and enabled behavior where the confirmed rules have not superseded them. 12. Build real PostgreSQL/Redis integration and concurrency tests. Critical idempotency contract: - `Idempotency-Key` is mandatory and validated. - Key scope includes authenticated user and normalized endpoint. - Fingerprint includes method, route, user, and request body. - Same key/different payload returns conflict. - Same key/completed request replays the response. - Concurrent matching requests execute once. - Records expire by configured TTL. - Redis is replay/coordination state only; PostgreSQL remains authoritative. Required integration workflows: - multi-item POS success; - insufficient-stock full rollback; - concurrent POS on the same stock; - exact movement before/after; - SO reserve/update/cancel and concurrent reservation; - atomic cart checkout/clear; - invoice/payment/cashflow and correction workflow; - idempotency replay, fingerprint conflict, concurrent claim, expiry, and Redis outage; - injected failure after each critical write to prove rollback. Phase gate: - P0 workflows pass real PostgreSQL/Redis tests. - No transaction can commit a sale/payment without required stock/finance records or vice versa. - Concurrent requests cannot oversell or duplicate critical documents. - Redis outage behavior is observable and documented. ### Phase 3 - Monthly document numbering Goal: enforce confirmed monthly numbering for transaction documents. Target formats: - `SO-YYYYMM-000001` - `DO-YYYYMM-000001` - `INV-YYYYMM-000001` - `PAY-YYYYMM-000001` - `RET-YYYYMM-000001` - `SHP-YYYYMM-000001` Rules and work: 1. Sequence scope is document type plus calendar month in Asia/Jakarta. 2. Each document type has an independent counter and unique document-column constraint. 3. Allocation uses the supplied transaction querier; adapters must not silently fall back to the pool. 4. Sequence errors abort the surrounding operation and are never converted to an empty number. 5. Month rollover starts a new counter. 6. Imported legacy numbers remain unchanged. 7. Product, customer, supplier, employee, and other master codes retain separate strategies. 8. Add concurrent-allocation, type-isolation, rollover, timezone-boundary, failure, and rollback tests. Phase gate: - Concurrent generation produces no duplicates. - Every new transaction document uses the confirmed format. - No service suppresses sequence errors. ### Phase 4 - Distribution foundation Goal: implement DO-based partial fulfillment after P0 is stable. Conceptual schema, subject to Atlas migration design: - `delivery_orders` - `delivery_order_items` - `shipments` - `shipment_items` - `fleet` - `dispatches` - `delivery_status_history` Confirmed invariants: 1. One Sales Order can have one or more Delivery Orders. 2. One Delivery Order can have multiple shipments at different times. 3. All partial shipments retain the same parent DO and DO number. 4. Shipment number is distinct from DO number. 5. DO/item requested, shipped, delivered, and remaining totals are traceable. 6. Shipment quantity cannot exceed remaining quantity. 7. Inventory deduction uses actual shipped quantity, not requested quantity. 8. Shipment header/items, stock mutation, movement, aggregate update, status history, and dispatch state commit in one transaction. 9. Dispatch includes priority, queue/order, fleet, optional driver, schedule, dispatcher, status, and timestamps. 10. Priorities are `urgent`, `high`, `normal`, and `low` because no legacy convention exists. 11. Distribution billing is consolidated; shipment does not automatically create one invoice. Deferred constraints: - capacity, weight, and volume enforcement; - driver master relationship; - proof of delivery; - invoice consolidation timing; - cross-SO invoice consolidation. Phase gate: - Requested 770 / shipment 500 / shipment 270 is represented under one DO. - Over-shipment fails without partial writes. - Stock and DO totals match shipment execution records. - Dispatch board ordering and reassignment are audited. ### Phase 5 - Shipment tracking Goal: provide barcode-addressable, map-ready shipment history without pretending GPS is available. Deliverables: 1. Separate identifiers for product, SO, DO, shipment, invoice, payment, return, and tracking. 2. Shipment lookup by DO number, shipment number, barcode, and tracking identifier. 3. Append-only tracking/location events with timestamp, status, source, and coordinates when available. 4. Location Provider interface for driver mobile GPS, third-party integrations, or authorized manual updates. 5. Latest-location and history responses suitable for a map UI. 6. Authorization, retention, range validation, and idempotency for tracking events. 7. Keep employee sales tracking separate from shipment tracking. Phase gate: - Barcode resolves the correct shipment without identifier ambiguity. - Map responses distinguish latest point from history and expose source/freshness. - No response claims real-time tracking without an active provider. ### Phase 6 - B2B, membership, and retail expansion Dependency order: 1. Brands, product units, conversion configuration, unit-specific prices/minimum purchase, and product barcodes. **[COMPLETE]** — migration `000010_catalog_brands_units_barcodes.sql`; `brands`, `product_units`, `product_barcodes`, `products.brand_id`; routes `/api/v1/brands`, `/api/v1/products/{id}/units`, `/api/v1/products/{id}/barcodes`, `/api/v1/barcodes/lookup`. Conversion factors, unit prices, and minimum purchase are configuration rows; one base unit and one primary barcode per product are enforced in a transaction plus partial unique indexes; barcode is globally unique and separate from product code and document numbers. 2. Backend pricing engine and immutable transaction-line pricing/unit snapshots. **[COMPLETE]** — `internal/pricing` resolves the authoritative unit price (configured `product_units.selling_price`, else the product price scaled by the conversion factor), enforces `min_purchase`, and returns the base-unit quantity; migration `000011_pricing_snapshots.sql` adds the line snapshot (`product_unit_id`, `unit_id`, `unit_name`, `unit_symbol`, `conversion_factor`, `base_quantity`, `min_purchase`) plus a trigger that rejects any repricing UPDATE. POS, sales orders, and B2B checkout accept `product_unit_id`, ignore client-supplied prices, and move stock in base units. Member pricing, promotions, and vouchers are deferred to items 3-4. 3. Customer membership with exactly Bronze, Gold, and Platinum tiers. **[COMPLETE]** — identity, DB-backed tier config (Bronze 2% / Gold 5% / Platinum 8%), append-only tier history, and the pricing benefit stage in the resolver. 4. Promotion and customer/member sales-voucher aggregates, separate from purchasing vouchers. **[COMPLETE]** — migration `000015_sales_vouchers.sql`; `sales_vouchers` table (types promotion/member_discount/sales_voucher, statuses available/used/expired); routes `/api/v1/sales-vouchers`. Promotion stacking remains a §20 gate. 5. B2B catalog, checkout, order history, invoices, payments, authorized receivables, and DO tracking. **[COMPLETE]** — reuses the existing aggregates (no separate package): cart+checkout and `/sales-orders/me` already run on authoritative pricing; buyer-scoped views added as `/invoices/me`, `/invoices/receivables/me`, `/payments/me`, `/delivery-orders/me`, resolving the caller's linked customer via a `CustomerResolver` seam on the finance and distribution handlers. 6. Galon workflow. **[COMPLETE, provisional defaults]** — migration `000016_galon.sql`; `galon_transactions` aggregate (types refill/buy_galon/buy_galon_plus_contents/borrow_galon, fulfillment pickup/delivery, statuses pending/completed/cancelled); routes `/api/v1/galon`. Deposit defaults to 0 (§20 #5, see business-rules §20.1). 7. Return workflow. **[COMPLETE, provisional defaults]** — migration `000017_sales_returns.sql`; immutable `sales_returns` + `sales_return_items` referencing the original order lines; refund is the pro-rata immutable-snapshot amount (default method cash); restock posts a stock_in movement in the same tx via a `ReturnLedger` seam; routes `/api/v1/returns`. Damaged/quarantine/tax-allocation policy deferred (§20 #6, see business-rules §20.1). 8. Cashier shift and drawer operations. **[COMPLETE, provisional defaults]** — migration `000018_cashier_shifts.sql`; `cashier_shifts` + `cashier_drawer_entries`, `sales_orders.shift_id` linkage; open/cash-in/cash-out/summary/close with expected cash = opening + cash_in − cash_out + cash-method shift sales; routes `/api/v1/cashier/shifts`. Register/store binding deferred (§20 #7, see business-rules §20.1). Business confirmation gates before dependent work: - membership benefit formulas and tier lifecycle; - promotion stacking; - galon deposit/loan/container policy; - return refund/tax/damaged-stock policy; - cashier/register/store relationship. Phase gate: - Frontend cannot override authoritative prices or benefits. - Silver cannot enter tier data or API enums. - Purchasing voucher and sales voucher cannot be confused by schema, routes, or accounting behavior. - Galon, return, and shift workflows remain auditable aggregates, not destructive edits to sales rows. ### Phase 7 - Expanded hardening and migration tooling Minimum idempotency and real infrastructure tests are already P0 Phase 2 work. Phase 7 broadens operational coverage: 1. Generic API, login, and location/tracking rate limits with trusted-proxy and IPv6-safe identity handling. **[COMPLETE]** — `pkg/middleware/ratelimit.go` + `pkg/redis/ratelimit.go` (atomic, fail-open); generic per-IP limit on `/api/v1`, tighter login limit, tighter tracking-ping limit; identity resolver honors `X-Forwarded-For` only from `TRUSTED_PROXIES` (IPs/CIDRs) and parses hosts IPv6-safely. 2. Atomic Redis rate-limit implementation and operational metrics. **[COMPLETE]** — atomic INCR/EXPIRE in `pkg/redis/ratelimit.go`; metrics deferred until an observability stack exists. 3. Wider idempotency adoption and stale-claim recovery. **[COMPLETE]** — idempotency is wired on all critical mutations (POS, checkout, SO, payments, shipments, dispatches, returns, galon, shifts, vouchers); TTL-based expiry covers stale claims. 4. Local storage adapter, then S3/R2-compatible adapter. **[LOCAL COMPLETE]** — `pkg/storage` (Store interface + local filesystem adapter with traversal rejection and atomic temp-file rename); S3/R2 adapter deferred until a real consumer exists. 5. MySQL dump importer using `../ERPGUDANG/erp-gudang.sql` or deterministic staged CSV/TSV. **[CSV COMPLETE]** — `cmd/importer` loads manifest-ordered staged CSVs; the SQL-dump path is deferred until the dump is available. 6. Exact-ID preservation, FK-safe load order, sequence reset, dry run, apply, and verify modes. **[COMPLETE]** — manifest order is the FK-safe load order; COPY preserves exact IDs; `setval` resets identity sequences; `dry-run`/`apply`/`verify` modes. 7. Per-table count, max ID, monetary/quantity sums, and machine-readable checksum report. **[COMPLETE]** — `verify` emits a JSON report comparing source CSV stats against PostgreSQL and exits non-zero on mismatch. 8. CI services and expanded PostgreSQL/Redis/storage integration suites. **[COMPLETE]** — `.github/workflows/docker.yml` runs postgres:16 + redis:7 services so the env-gated integration suites (including Redis-backed idempotency) run in CI. 9. Caddy/deployment hardening and observability. Deferred until deployment target is fixed. 10. Asynq only when an actual background-job requirement exists. Not needed yet. Importer safety: - Dry run performs no writes. - Apply targets a new migration database by default. - Destructive reset requires an explicit development-only flag and is refused in production. - Verification compares independently derived source and PostgreSQL checksums and exits non-zero on mismatch. ## 3. PostgreSQL and sqlc change protocol For every schema change: 1. Add a forward-only Atlas migration under `migrations/`. 2. Update Atlas checksum. 3. Refresh `db/schema.sql`. 4. Add or update structured queries in `db/query/`. 5. Run `sqlc generate`. 6. Commit migration, checksum, schema, queries, generated code, domain changes, tests, and OpenAPI together. Avoid PostgreSQL tables for cache/rate-limit temporary state. Add persistent idempotency metadata only if Redis-only crash recovery proves insufficient and the business contract is updated accordingly. ## 4. Transaction and locking standards - Critical state validation occurs after locking the governing row inside the transaction. - Multi-row stock locks use deterministic product/warehouse order. - Document allocation, header/items, stock movement, invoice/payment/cashflow, status history, and audit records share the required transaction boundary. - Conditional updates or locked state prevent stale concurrent transitions. - Redis coordination never replaces PostgreSQL constraints or row locks. - A failure at any critical step rolls back all transaction writes. ## 5. Testing standards - **Unit tests:** validation, formulas, state transitions, request mapping, numbering formatting, and pure policy. - **Contract tests:** mounted routes, OpenAPI operation parity, request/response schemas, envelopes, statuses, and filters. - **PostgreSQL integration tests:** transactions, locks, constraints, rollback, exact stored money/quantity, and workflow state. - **Redis integration tests:** idempotency, rate limits, TTL, concurrent claims, and outage policy. - **Concurrency tests:** oversell prevention, duplicate document prevention, duplicate payment prevention, and shipment over-fulfillment. - **Import tests:** exact IDs/FKs, sequence reset, checksum match, and deliberate mismatch failure. Fake-store tests do not satisfy a transaction/concurrency acceptance gate by themselves. ## 6. Rollout gates | Gate | Requirement | |---|---| | Documentation gate | `business-rules.md` and this plan reviewed before Phase 1 code | | Contract gate | OpenAPI and route/handler behavior are synchronized | | P0 integrity gate | POS/sales/inventory/payment tests pass against real PostgreSQL and Redis | | Numbering gate | monthly concurrent allocation and rollover tests pass | | Distribution gate | partial shipment and rollback invariants pass | | Tracking gate | identifier lookup and map-ready history pass without false real-time claims | | B2B gate | server pricing and membership authorization tests pass | | Data-load gate | exact-ID import checksums match the source dump | | Production swap | explicit approval after parity and integrity evidence; Laravel remains fallback until sign-off | ## 7. Current work board ```text AUDIT / ARCHITECTURE [COMPLETE] SQLC / ATLAS FOUNDATION [COMPLETE] DOCUMENTATION ALIGNMENT [READY FOR REVIEW] PHASE 1 CONTRACT SYNC [COMPLETE] PHASE 2 P0 INTEGRITY [COMPLETE] PHASE 3 MONTHLY NUMBERING [COMPLETE] PHASE 4 DISTRIBUTION [COMPLETE] PHASE 5 SHIPMENT TRACKING [COMPLETE] PHASE 6 B2B / RETAIL EXPANSION[IN PROGRESS] items 1-2 complete (catalog foundations, pricing engine + line snapshots); item 3 membership benefit COMPLETE (tiers + identity split + append-only tier history + Bronze 2% / Gold 5% / Platinum 8% line discount frozen into the immutable snapshot); items 4-8 pending (blocked on business confirmation) PHASE 7 HARDENING / IMPORT [NOT STARTED] PRODUCTION CUTOVER [NOT AUTHORIZED] ``` ## 8. Resume checklist Schema state: migrations through `000014_membership_tier_config.sql` are applied. `atlas migrate status` must report the current version as `000014` with zero pending files before any new work. Applied migrations: | Version | File | Purpose | |---------|------|---------| | 000001–000013 | (see prior sections) | schema, cart, payments, ecommerce, finance, distribution, catalog, pricing snapshots, membership identity + benefit | | 000014 | `000014_membership_tier_config.sql` | **STEP 0**: database-backed membership tier config (`membership_tiers`, seeded Bronze 2%/Gold 5%/Platinum 8%) + append-only `membership_tier_config_history` audit | | 000015 | `000015_sales_vouchers.sql` | Phase 6 item 4: `sales_vouchers` aggregate (separate from purchasing vouchers) | | 000016 | `000016_galon.sql` | Phase 6 item 6: `galon_transactions` aggregate (deposit default 0, §20 #5) | | 000017 | `000017_sales_returns.sql` | Phase 6 item 7: immutable `sales_returns` + `sales_return_items` audit events | | 000018 | `000018_cashier_shifts.sql` | Phase 6 item 8: `cashier_shifts`, `cashier_drawer_entries`, `sales_orders.shift_id` | | 000022 | `000022_return_item_condition.sql` | §20 #6: `sales_return_items.condition` (good/damaged) + `restocked` flag; damaged skips restock | | 000023 | `000023_galon_loan.sql` | §20 #5: `galon_transactions.loan_amount` + `loan_returned`; `ReturnGalon` clears loan | | 000024 | `000024_cashier_register.sql` | §20 #7: `cashier_shifts.register_id` + `store_id` (optional binding) | | 000025 | `000025_employee_leave_balance.sql` | §20: `employees.leave_balance` + `leave_used`; deduction enforced on leave approval | | 000026 | `000026_membership_points.sql` | §20: `memberships.points`; accrual + auto-tier promotion | Next unblocked task: Phase 7 (hardening and migration tooling). All Phase 6 items are complete; items 6–8 (plus leave balance and membership points) ship with provisional defaults recorded in `business-rules.md` §20.1 and remain reversible when the business confirms the real policies. Only promotion stacking (§20 #2) now blocks further engine work. Business answers still required (see `business-rules.md` §20; provisional defaults applied in §20.1): | # | Decision | Blocks | |---|---|---| | 1 | Membership tier upgrade, downgrade, expiry (benefit formulas Bronze 2% / Gold 5% / Platinum 8% CONFIRMED) | **IMPLEMENTED (provisional)**: 1 pt per Rp 10k spend, auto-promote bronze→gold≥1000→platinum≥5000, tier-history recorded. Real formulas still tunable in `tierForPoints`. | | 2 | Promotion stacking, precedence, combination with member pricing and vouchers | promotion engine | | 3 | Galon deposit, loan fee, penalty, due date, container identity, balance | **IMPLEMENTED (provisional)**: `loan_amount` tracked per borrow, `ReturnGalon` clears it; deposit default 0; fee/penalty/due-date still deferred. | | 4 | Return refund method, tax/promotion allocation, damaged stock, quarantine | **IMPLEMENTED (provisional)**: return `condition` (good/damaged) recorded; damaged skips restock; cash/pro-rata/full-restock defaults in place. | | 5 | Cashier shift binding to register/store and movement between them | **IMPLEMENTED (provisional)**: optional `register_id`/`store_id` on shift open; one-open-shift default in place. | Where the completed Phase 6 work lives: - `internal/catalog` — brands, `product_units`, `product_barcodes`, barcode lookup. - `internal/membership` — tiers, `User -> Customer -> Membership` identity split, append-only `membership_tier_history`, DB-backed tier config. - `internal/pricing` — authoritative price resolution, minimum purchase, base quantity, membership benefit stage. - `internal/sales` — `PriceResolver` seam, immutable line snapshots, base-unit stock movement, sales vouchers (item 4), returns (item 7). - `internal/galon` — galon workflow aggregate (item 6). - `internal/cashier` — cashier shift and drawer operations (item 8). - `internal/finance`, `internal/distribution` — buyer-scoped `/me` views via `CustomerResolver` (item 5). - `internal/integration/catalog_test.go`, `internal/integration/pricing_test.go` — the two phase gates. ## 2026-08-26 finance hardening - 000040_po_approval_audit.sql — approved_by/approved_at on purchase_orders. - 000041_finance_hardening.sql — cash_accounts, accounts (COA), cashflow account/counterpart/flow_type/payment_method/attachment columns, payment_vouchers + payment_voucher_lines. Idempotent; reference data seeded.