# Spec Impact Matrix — TinyPOS (`tnx-pos`)

> Produced by the Reversa **Architect** (phase: interpretation) · doc_level: `complete`
> Generated on 2026-09-18

**Confidence scale:** 🟢 CONFIRMED · 🟡 INFERRED · 🔴 GAP

This matrix records **change blast-radius**: which components/artifacts a given part of TinyPOS impacts. Use it when scoping a change to see what else must be re-verified. Rows are ordered by centrality (most-shared assets first). It feeds `/reversa-forward` planning and `/reversa-migrate` sequencing.

Legend: **✎ writes / owns** · **→ calls / depends on** · **⟂ read-only consumer**.

---

## 1. Shared assets → dependents (highest coupling first)

| Shared asset (owner) | Directly impacts | Kind | Conf. |
|----------------------|------------------|:----:|:-----:|
| **`Product::getPriceByCustomerType`** (Product) — pricing rule | `ProductController::getPriceByCustomerType` (`get-price` endpoint), `OrderController::store`/`update` totalling, POS Cart Client (live re-pricing) | → | 🟢 |
| **`CustomerDebt::record` / `voidForOrder`** (CustomerDebt) — A/R ledger | `OrderController::store`/`update` (`pos_debt`), `OrderController::destroy` (`debt_void`), `CustomerController::storeDebt`/`storeRepayment`, `DebtController` (⟂), `customers.debt_total` | ✎→ | 🟢 |
| **`customers.debt_total`** (Customer) — running balance | `CustomerDebt::record` (✎), `DebtController::index` (⟂ filter `>0`), `CustomerController::debt`/`statis` (⟂), POS/order debt guards | ✎⟂ | 🟢 |
| **`orders.status` + guards (`is_editable`, `debt_locked`, `points_awarded_at`)** (Order) | `OrderController::update`/`destroy`, POS Cart Client (disables debt input), points & debt lifecycles (`state-machines.md`) | → | 🟢 |
| **`order_product` pivot** (Order↔Product) | POS submit, `OrderController` totalling, print/detail/history views, dashboard top-products, `summaryLogging` | ✎⟂ | 🟢 |
| **`Order::summaryLogging`** (Order) — nightly rollup | Scheduler/Cron (✎ writes `customer_order_summary`), `CustomerController::statis` (⟂), category ids 1/8 special-casing | ✎ | 🟢 |
| **`Customer::checkGiftAvailable` + `gifts.used`/`quantity`** | `CustomerController::redeemRewardPoints`, `customer_gift` pivot, `GiftController` stock rules (`min:used`) | →✎ | 🟢 |
| **`units` reference data** (name string + FK) | `products.unit` (by name ✎), `product_units.unit_id` (FK), `order_product.unit_id` (FK), receipt/detail/history views (`Unit::find`) | ⟂ | 🟡 |
| **`categories` (parent_id, ids 1/8)** | `ProductController` dropdown (child-only), `products.category_id` (required), `summaryLogging`/`statis` special-casing | ⟂ | 🟢 |
| **`brands`** | `products.brand_id` (required), never deletable | ⟂ | 🟢 |
| **`admin` auth guard / Encore\Admin session** | **Every** application route (single `['web','admin']` group) | → | 🟢 |
| **`Setting::get`** (Setting) | `ProductController::index` (`near_expiry_days`) — extensible config seam | ⟂ | 🟡 |
| **`orders.discount_id` FK → `discounts`** (Discount) | `Order::discount()` relation only — no controller sets it; checkout discounts flow through `discount_amount` instead. Kept intentionally for future use (GAP-O2, resolved 2026-09-23) — do not drop on migration/rewrite. | ⟂ | 🟢 |

---

## 2. Component → impacted components (functional)

| Component | Writes / owns | Calls / depends on | Read-only consumers of its output |
|-----------|---------------|--------------------|-----------------------------------|
| **PosController** | injected cart JS, unit payload | Product, ProductUnit, Order (draft preload), Customer | POS Cart Client |
| **POS Cart Client** | (client cart state) | `get-price`, `pos/scan`, `customers/scan`, `orders/scan`, POST `/orders` | — |
| **OrderController** | `orders`, `order_product` | `Product::getPriceByCustomerType`, `CustomerDebt::record`/`voidForOrder`, `Customer` points, `Order` guards | print view, dashboard, `customers/{}/orders`, `summaryLogging` |
| **CustomerController** | `customers`, `customer_gift`, `customer_debts` (via record) | `CustomerDebt`, `Gift`, `CustomerOrderSummary`, `Customer::checkGiftAvailable` | DebtController, POS customer picker |
| **ProductController** | `products`, `product_units`, image files | `Product`, `syncProductUnits`, `Setting`, `Category`(child), `Brand`, `Unit` | POS (`get-price`), Order totalling, dashboard |
| **DebtController** | — (read-only) | `customers.debt_total` | (view only) |
| **DashboardController** | — | `orders`, `order_product`, `products`, `customers` (counts, raw SQL) | (view only) |
| **Brand/Category/Unit/GiftController** | reference tables (via Encore\Admin `ModelForm`) | `Brand`/`Category`/`Unit`/`Gift` models | products, orders, loyalty |
| **Scheduler / Cron** | `customer_order_summary` | `Order::summaryLogging` | `CustomerController::statis` |

---

## 3. Change-scenario blast-radius (worked examples)

| If you change… | You must re-verify… | Why | Conf. |
|----------------|---------------------|-----|:-----:|
| **The pricing rule** (`getPriceByCustomerType`) | `get-price` endpoint, POS live re-pricing, `store`/`update` totalling (**both** — duplicated), captured `order_product.price` semantics | Single rule, three call sites; totalling logic is duplicated (debt #1) | 🟢 |
| **Order totalling** (`store` or `update`) | The *other* of store/update (drift), earned-point award, `pos_debt` posting, print view | ~60 duplicated lines across store/update | 🟢 |
| **Debt ledger** (`CustomerDebt::record`) | POS debt post, manual debt, repayment ceiling, `debt_void` on delete, `debt_total`, `/debts`, `balance_after` audit | All A/R funnels through one method | 🟢 |
| **`customer_order_summary` shape / `summaryLogging`** | `CustomerController::statis` unpacking, milk/medicine (ids 1/8) buckets, nightly cron | Denormalised read model; freshness = last run | 🟢 |
| **Units** (rename or model change) | `products.unit` string (desync risk), `product_units`, `order_product.unit_id`, receipt/detail/history labels | Base-unit-by-name vs conversion-by-id split (GAP-U2) | 🟡 |
| **Category `parent_id` / dropdown rule** | Product create/edit dropdown, `category_id` required validation, stats special-casing | Only child categories assignable; UI never sets `parent_id` (GAP-C1) | 🔴 |
| **Add an authorization tier** (e.g. cashier vs manager) | The single `['web','admin']` route group, **every** controller, seed RBAC wiring | No policies/gates/permission middleware exist today (PERM-1/PERM-3) — starts from zero | 🟡 |
| **Product image handling** | `store`/`update` upload + the broken `app/public2` cleanup path | Cleanup targets wrong root; orphans accumulate (GAP-P1) | 🔴 |
| **Order deletion policy** | `reversePointsForOrder`, `voidForOrder`, hard-delete cascade, retained ledger trail | Hard-delete + retained `debt_void` accepted (GAP-O1) | 🟢 |

---

## 4. Traceability pointers

- **Structural detail:** `code-analysis.md`, `data-dictionary.md`, `flowcharts/<module>.md`.
- **Domain rules & gaps:** `domain.md` (rules 1–21, GAP-C1/C2/U1/U2/O2/P1).
- **Lifecycles:** `state-machines.md` (order, points, debt, product, gift).
- **Access control:** `permissions.md` (PERM-1/2/3).
- **Decisions:** `adrs/0001`–`0009`.
- **Diagrams:** `c4-context.md`, `c4-containers.md`, `c4-components.md`, `erd-complete.md`.

> A spec-to-code / code-to-spec traceability matrix (per generated spec ⇄ source file ⇄ test) is produced by the **Writer** (`geracao` phase) as the Code/Spec Matrix; this Architect matrix is the component-level precursor.
</content>
