# Open Gaps — TinyPOS (`tnx-pos`)

> Produced by the Reversa **Reviewer** (phase: review) · doc_level: `complete`
> Generated 2026-09-22.

Gaps that remain **unanswered** after the independent review. Items needing a stakeholder decision are cross-linked to `questions.md`; the rest are implementation gaps or documentation-quality issues the Reviewer can carry without a human decision. Grouped by severity to help triage.

---

## 🔴 Critical — likely defects or reimplementation-blocking

| Gap | Where | Status |
|-----|-------|--------|
| ~~`gift-received` orders by an ambiguous `id`...~~ | `customers-loyalty` (`CustomerController.php:349`) | ✅ Verified NOT a bug (2026-09-23) — retested against the real MariaDB 10.11.13 DB, no error. See `questions.md#question-5`. |
| ~~Dashboard month chart drops every 6th day (windows do not tile)~~ | `dashboard` (`DashboardController.php:91`) | ✅ Fixed 2026-09-22 — confirmed a bug, `<` → `<=` closes the gap. See `questions.md#question-1`. |
| ~~`Discount` model + `discounts` table + live FK marked fully "unused"~~ | `traceability/code-spec-matrix.md`, `spec-impact-matrix.md` | ✅ Decided 2026-09-23 — kept intentionally for future use; matrices requalified. See `questions.md#question-10`. |
| ~~Pricing endpoint collapses all failures to a raw `400`...~~ | `products-pricing` (`ProductController.php:277-298`) | ✅ Fixed 2026-09-22 — per-kind status codes (422/404), bad customer id degrades to retail. See `questions.md#question-4`. |

## 🟡 Moderate — behavior/robustness, decision or fix pending

| Gap | Where | Status |
|-----|-------|--------|
| Dashboard month chart: for a non-6-multiple month length (31-day or 28/29-day months), the loop's last window can spill a few days into the next calendar month (e.g. October's last bucket reaches into early November), attributing those orders to the wrong month's chart. | `dashboard` (`DashboardController.php:85-98`) | New finding (surfaced verifying the `question-1` fix); pre-existing in both the pre- and post-fix code, deferred — no question raised yet. |
| ~~`store`/`update` are not wrapped in `DB::transaction`; `update` also mutates pivots before the debt guard runs~~ | `orders-crud` (`OrderController.php:135-160,301-337`) | ✅ Fixed 2026-09-25 — transaction added, guard now runs before mutation. See `questions.md#question-11`. |
| ~~Finalising a parked draft (`create_now_mode`) re-prices at current catalog prices~~ | `orders-crud` (`:225-242,278`) | ✅ Confirmed intentional 2026-09-25. See `questions.md#question-12`. |
| `orders-print` has no status guard (draft prints as a receipt) — confirmed intentional. Live points on a historical reprint — deferred (needs a migration + relabeling to fix properly). | `orders-print` (`pos-print.blade.php:48`) | See `questions.md#question-13`. |
| ~~`orders-note` has no editability/status/24h guard~~ | `orders-note` (`OrderController.php:342-356`) | ✅ Confirmed intentional 2026-09-25 — notes are free metadata. See `questions.md#question-14`. |
| Empty/blank `q` on the three scan endpoints returns an arbitrary first-10 instead of `[]`. | `pos-scan`, `customers-scan`, `gifts-scan` | ⚪ Decided 2026-09-23 — keep as-is, no change. See `questions.md#question-6`. |
| ~~`gifts-scan` default feed includes inactive/out-of-stock gifts...~~ | `gifts-scan` (`GiftController.php:118-121`) | ✅ Verified not a practical risk (2026-09-23) — the only consumer already passes `active=1` and disables out-of-stock options client-side. See `questions.md#question-7`. |
| ~~A `null` `customer_order_summary` row renders identically to a genuinely-zero customer~~ | `customers-statistics` (`CustomerController.php:224-234`) | ✅ Verified NOT a gap (2026-09-23) — the page already shows "Chưa cập nhật" for a null summary. Residual, narrower point: a stale-but-existing summary (silently-failed refresh) isn't flagged. See `questions.md#question-8`. |
| ~~Categories `parent_id` is never settable via the UI~~ | `categories` (`CategoryController.php`) | ✅ Confirmed intentional (2026-09-23) — seed/DB-managed taxonomy, consistent with categories being undeletable. See `questions.md#question-9`. |
| ~~`gifts-crud` `used`-on-edit preservation is not provable from code~~ | `gifts-crud` (`GiftController.php:103`) | ✅ Verified 2026-09-25 via a real HTTP edit test — `used` is preserved. See `questions.md#question-16`. |
| On gift **create**, a negative `quantity` is accepted — no `min:0` rule and the `?: 0` coercion only touches falsy values, so e.g. `-5` persists. Not documented in any of the four files. | `gifts-crud` (`GiftController.php:102`, form rules `:84-92`) | New finding; needs a `min:0` rule or documentation. |
| `customers-crud` `tasks.md:14` asserts an `email` unique index, but no validator checks email uniqueness — a duplicate-email create would surface as an unhandled 500 `QueryException`, not a validation error. | `customers-crud` (`CustomerController.php:81,153`) | New finding; confirm the index and add a `unique` rule or remove the claim. |
| ~~No login rate-limiting/lockout; failed logins unlogged~~ | `auth` (`AuthController.php:39-60`) | ✅ Decided 2026-09-25 — not needed. See `questions.md#question-15`. |
| ~~POS client duplicates server-side debt rules with no single source of truth~~ | `pos-terminal` / `orders-crud` | ✅ Decided 2026-09-25 — server is authoritative; duplication kept but cross-referenced with comments. See `questions.md#question-17`. |
| Product image cleanup targets `app/public2` (wrong path); old images never deleted (failure swallowed). | `products-catalog` (`ProductController.php:201`) | → `questions.md#question-3`. |
| `store`+`syncProductUnits` run without a wrapping transaction (delete-all-then-insert); a mid-sync failure can drop a product's units. | `products-catalog` (`ProductController.php:212-283`) | Reclassified 🟢→🟡; tracked as T-13. |
| Pervasive **no observability** across nearly every unit (money paths, scan hot paths, print, migrate). This is a cross-cutting implementation gap, not a per-unit stakeholder question. | all units | Carry as a cross-cutting NFR for the reimplementation; no decision needed. |

## ⚪ Cosmetic — documentation quality (Reviewer can fix without a decision)

| Gap | Where | Status |
|-----|-------|--------|
| ~~Systemic language-policy inconsistency: 18/23 unit `requirements.md` files wrote Acceptance-Criteria Gherkin scenarios fully in Portuguese~~ | 18/23 units | ✅ Fixed 2026-09-25 — full scenario text (not just keywords) translated to English across all affected files. |
| ~~Line-reference drift (~2–6 lines low)~~ | `customers-debt-actions`, `customers-loyalty`, `orders-scan`, `pos-terminal`, `pos-scan`, `dashboard`, `products-catalog` | ✅ Fixed 2026-09-25 — re-synced all citations against current line numbers, verified by direct source reads (not offset guessing) for the high-traffic units; `pos-terminal`'s `buildUnitsPayload` citation now agrees with `pos-scan`'s. A handful of `pos-terminal`'s deep JS-behavior-line citations (within the ~630-line injected script) may still be off by ~3-4 lines — low priority, still in the right neighbourhood. |
| ~~`artisan-migrate` uses an undefined `⚪` marker and `requirements.md` wasn't reconciled to the accepted-risk decision~~ | `artisan-migrate` | ✅ Fixed 2026-09-25 — `requirements.md` now reads 🟢 (was 🔴); `⚪` replaced with the standard `🟢` marker in `design.md`/`contracts.md`/`user-stories/maintenance.md`. |
| `store` "Thêm thất bại" failure branch is effectively dead (`Product::create()` returns a model or throws, so `if ($product)` is always truthy); presented as a real alternative flow. | `products-catalog` (`ProductController.php:121-126`) | Note as practically-unreachable. |
| Category overview states ids 1/8 are "singled out 🟢" flatly while the business rule correctly qualifies id-8 as a code-marked placeholder (🟢/🟡). Prefer the qualified form. | `categories/requirements.md:14` vs `:31` | Minor wording. |

---

## Notes on refuted flags (no gap — do not re-open)

Several previously-flagged 🔴/🟡 items were **verified against current code and refuted** (already fixed on 2026-09-17/09-21 or never real). They are recorded here so they are not re-raised:

- `check-gift` 500 on missing/invalid `gift_id` — a null-guard exists (`CustomerController.php:368-370`); fixed.
- `gift-received` search on a non-existent `code` column — searches `name` only; fixed.
- `orders-crud` `index` ungrouped `orWhere` ignoring the status filter — now a grouped closure (`:32-38`); fixed.
- `orders-print` unknown id → null-deref/500 — uses `findOrFail` → clean 404 (`:396`).
- `customers-scan` soft-deleted leak via ungrouped `orWhere` — Eloquent's `SoftDeletingScope` auto-nests the OR group (verified against vendored `Builder`); not a leak.
- `customers-crud` search leak — fixed with an explicit closure (`:39-41`).
- `debts` search — correctly grouped (`DebtController.php:20-24`).
- Loyalty and debt redemption/ledger races — correctly handled with `lockForUpdate` + under-lock re-check.
- `dashboard` day windows (Sáng/Trưa/Chiều) — the Carbon-mutation concatenation is genuinely correct (verified).
- `orders` status enum — the live schema is `('draft','done')` after `2022_05_10_192107_change_status_col_in_orders_table`; the stale 2019 `('waiting','done')` migration is not the live state.
- `customers-purchase-history` `debt_locked` N+1 — the HTML blade never reads the appended attribute, so it does not materialize on that path.
- `customers-loyalty` `gift-received` ambiguous `ORDER BY id` — retested 2026-09-23 against the real MariaDB 10.11.13 database via `php artisan tinker`; `paginate(30)` runs the exact production SQL and returns correct rows with no error.
