# Debts — Contracts

> Produced by the Reversa **Writer** (phase: generation) · doc_level: `complete`
> Generated on 2026-09-21

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

External HTTP contract exposed by the `debts` unit — the single route `GET /debts` (`DebtController::index`, route name `debts.index`) under the admin group (`['web','admin']`, empty admin prefix). It is a read-only **HTML** receivables overview page; it requires an authenticated admin session; an unauthenticated request gets `302 → auth/login`. The controller performs **no writes** — the mutations reachable from this page are owned by `customers-debt-actions`. 🟢 (`routes/web.php:67`, `DebtController.php:10-29`)

---

## GET `/debts` — accounts-receivable overview 🟢 (`:10-29`)

- **Auth:** required; anonymous → `302 auth/login`. 🟢 (`routes/web.php:23-27`)
- **Request (query string):**

  | Field | Type | Required | Notes |
  |-------|------|----------|-------|
  | `q` | string | ❌ | Search token. When truthy, filters to debtors whose `phone LIKE %q%` OR `fullname LIKE %q%`, grouped so the OR stays ANDed with `debt_total > 0`. `%`/`_` act as LIKE wildcards (parameter-bound, no injection). 🟢 (`:20-24`) |
  | `page` | integer | ❌ | Standard Laravel paginator page (`paginate(30)`). Out-of-range pages render the empty-state row. 🟢 (`:26`) |

- **Response — `200 text/html`** rendering `pages.debts` with:

  | View variable | Type | Meaning |
  |---------------|------|---------|
  | `customers` | `LengthAwarePaginator<Customer>` | Customers with `debt_total > 0`, ordered `debt_total` desc, 30/page, optionally `q`-filtered. Each row exposes `fullname`, `phone`, `debt_total` (float, shown as `number_format(…,1) ₫`), and `id` (row links to `customers.debt`). 🟢 (`:17-28`) |

- **Status codes:** `200` (page rendered, possibly empty) · `302 → auth/login` (unauthenticated). No `404` — a missing/empty result is a valid empty page, not an error. No JSON variant; this route is not content-negotiated. 🟢 (`:10-29`)
- **CSRF:** not applicable to the `GET` itself; the page's embedded modal forms carry `csrf_field()` and submit to the `customers-debt-actions` POST routes. 🟢 (`debts.blade.php`)

---

## Consumed contracts (owned by other units)

`debts` reads the `customers` table through the `Customer` model and, in the browser, calls two `customers-debt-actions` write routes plus the `customers-scan` lookup. The controller itself calls no other unit's HTTP endpoint and no external service. 🟢

| Reads / calls | Owner unit | Purpose |
|---------------|------------|---------|
| `customers` (via `Customer::where('debt_total','>',0)`) | `customers-crud` | the debtor list and their live balances |
| `GET /customers/scan` (select2 pickers, min 3 chars) | `customers-scan` | resolve a customer to act on inside each modal |
| `POST /customers/{id}/repayments` (Thu nợ modal) | `customers-debt-actions` | record a repayment against the chosen customer |
| `POST /customers/{id}/debts` (Ghi nợ tay modal) | `customers-debt-actions` | record a manual debt against the chosen customer |
| `GET /customers/{id}/debt` (per-row link) | `customers-debt-actions` | open the customer's full debt ledger |

---

## Producer/consumer relationships

| This unit is… | Counterparty | Contract |
|---------------|--------------|----------|
| **Consumer** | `customers-crud` | reads `Customer`/`debt_total`; the list is a projection over customer master data. 🟢 |
| **Consumer** | `customers-scan` | the modal pickers autocomplete customers via `/customers/scan`. 🟢 |
| **Consumer** | `customers-debt-actions` | delegates every write (repayment, manual debt) and the ledger drill-down to that unit's routes; owns none of them. 🟢 |

---

## Cross-cutting contract notes

- **Method surface:** a single `GET`; no other verbs on `/debts`. 🟢 (`routes/web.php:67`)
- **Content type:** `text/html` (server-rendered Blade), not JSON — contrast the sibling `customers-scan` autocomplete. 🟢 (`:28`)
- **Read-only:** `DebtController::index` never mutates state; all state changes originate from the browser submitting to `customers-debt-actions`. 🟢 (`:10-29`)
- **Live vs. summary:** balances are read live off `customers.debt_total` (maintained transactionally by `CustomerDebt::record`), not the nightly `customer_order_summary` behind `customers-statistics`. 🟢 (`:17`; cross-ref `customers-debt-actions`, `customers-statistics`)
- **Search grouping:** the `q` closure is grouped, so `debt_total > 0` stays ANDed with the phone/name OR — correct, unlike the ungrouped `orWhere` bugs in `customers-crud`/`customers-scan`. 🟢 (`:20-24`)
- **Picker breadth:** the modal picker is not restricted to debtors; a repayment for a non-debtor is rejected by `storeRepayment` downstream (over-balance throw), not here. 🟡 (`debts.blade.php`; `customers-debt-actions`)
- **Authorization:** authentication only; any admin may view every customer's balance and act on it (no per-record scope). 🟡 (ADR-0009)
- **No observability:** the page emits no telemetry. 🔴 (`:10-29`, absence)
