# Units — Technical Design

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

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

## Interface

Standard Laravel resource under the admin group + `settings` prefix (`['web','admin']`, empty admin prefix). The controller overrides `index`/`edit`; `store`/`update`/`destroy`/`show`/`create` are the `ModelForm` trait defaults driven by `form()`. 🟢 (`routes/web.php:86-89`, `UnitController.php:18`)

| Method | Path | Input | Output | Status codes |
|--------|------|-------|--------|--------------|
| GET | `settings/units` | — | `text/html` (grid + inline create form) | 200, 302 (unauthenticated) |
| POST | `settings/units` | `name` (required), `description` | `302` back to the list | 302, 422 (validation) |
| GET | `settings/units/{id}/edit` | path `id` | `text/html` (edit form) | 200, 404 |
| PUT/PATCH | `settings/units/{id}` | `name` (required), `description` | `302` back to the list | 302, 404, 422 |

Controller symbols:

| Symbol | Signature | Return | Note |
|--------|-----------|--------|------|
| `UnitController::index` | `()` | `Content` | Two-column layout: `grid()->render()` + a `Widgets\Form` action=`settings/units` with `name`(required)/`description`. 🟢 (`:20-41`) |
| `UnitController::edit` | `($id)` | `Content` | `form()->edit($id)`. 🟢 (`:43-49`) |
| `UnitController::grid` | `()` | `Grid` | Columns id/name/description; create/export/selector/filter/pagination disabled; row actions disable edit for key 1, disable delete for all. 🟢 (`:54-74`) |
| `UnitController::form` | `()` | `Form` | `name`(required)/`description`; drives store/update. 🟢 (`:76-82`) |

## Main Flow 🟢 (`UnitController.php`)

1. `GET settings/units` → `index()` builds `Admin::content` with header `Đơn vị` ("Unit"), a row with two columns: left `column(6, grid()->render())`, right `column(6, …)` a `Widgets\Form` whose action is `admin_base_path('settings/units')` exposing `name` (required) + `description`, wrapped in a green `Box(admin.new)`. 🟢 (`:20-41`)
2. Submitting the inline form → `POST settings/units` → `ModelForm::store()` validates via `form()` (`name` required) and persists a `Unit`, then redirects back. 🟢 (`:76-82`)
3. `GET settings/units/{id}/edit` → `edit()` renders `form()->edit($id)` (header `Đơn vị`). 🟢 (`:43-49`)
4. Submitting the edit → `PUT settings/units/{id}` → `ModelForm::update()` persists the change. 🟢 (`:76-82`)
5. `grid()` renders id(sortable)/name/description with create button, export, row selector, filter, and pagination all disabled; the row-action closure disables `edit` when the row key is `1` and disables `delete` on every row. 🟢 (`:54-72`)

## Alternative Flows

- **Default unit (id 1):** the edit action is removed from that row; it can never be edited or deleted through the UI. 🟢 (`:67-69`)
- **Validation failure:** missing `name` → framework validation error, redirect back with errors. 🟢 (`:79`)
- **Unknown id on edit/update:** framework `findOrFail` → `404`. 🟡 (`ModelForm` default)
- **Unauthenticated:** admin group middleware → `302 auth/login`. 🟢 (`routes/web.php:24-28`)

## Dependencies

- **`Encore\Admin\Controllers\ModelForm` trait** — supplies `store`/`update`/`destroy`/`create`/`show`, driven by `form()`. 🟢 (`:18`)
- **`Encore\Admin` grid/form/widgets/layout** (`Grid`, `Form`, `Widgets\Form`, `Widgets\Box`, `Content`, `Row`, `Column`) — the whole UI. 🟢 (`:6-14`)
- **`Unit` model** — bare Eloquent model, no relations/casts/fillable. 🟢 (`Unit.php`)
- **Consumers:** `products-catalog` (base-unit dropdown `products.unit` keyed by name, and the conversion-units table `product_units.unit_id`), `orders-print`/`orders-crud` (order-detail + receipt line labels via `Unit::find(pivot.unit_id)->name`), `customers-purchase-history` (customer-order line labels). 🟢 (`units` flowchart:24-40)

## Identified Design Decisions

| Decision | Evidence in code | Confidence |
|----------|------------------|------------|
| Framework-scaffolded CRUD (Encore\Admin `ModelForm`) rather than a hand-written controller | `use ModelForm;` + only index/edit/grid/form overridden | 🟢 (`:18`) |
| Inline create widget in a two-column screen instead of a separate create page | `index()` right column + `disableCreateButton()` | 🟢 (`:29-38,60`) |
| Units are permanent reference data — no UI delete on any row | `$actions->disableDelete()` unconditionally | 🟢 (`:70`) |
| A single protected default unit (id 1), edit-locked (active, unlike `categories`) | `in_array($actions->getKey(),[1]) → disableEdit()` | 🟢 (`:67-69`) |
| Unpaginated/unfiltered grid (small catalogue assumption) | `disableFilter()`, `disablePagination()` | 🟡 (`:63-64`) |
| Base unit stored as a **name string** on `products.unit` while conversions reference `units.id` — two different keying schemes for the same table | `units` flowchart:28-32 | 🟡 |

## Internal State

None beyond the persisted `units` rows (`id`, `name`, `description`, nullable `category_id`, timestamps). The controller holds no request-spanning state. 🟢 (`UnitController.php`; `create_units_table` migration)

## Observability

None. The scaffolded CRUD emits no domain-level log/metric/trace; only framework-default behaviour. 🔴 (`UnitController.php`, absence)

## Risks and Gaps

- 🟡 **Dead `units.category_id` column.** The table carries a nullable `category_id` that no code reads or writes — an apparently unused column. Confirm before relying on it. (`create_units_table` migration:20; `units` flowchart:39)
- 🟡 **Base-unit-by-name vs conversion-by-id inconsistency.** `products.unit` stores a unit *name string* while `product_units.unit_id` stores a unit *id* — renaming a unit does not update products that captured the old name string. (`units` flowchart:28-32)
- 🟡 **Default-unit lock is id-based.** The edit lock keys off literal id `1`; a re-seed with a different id silently protects the wrong row. (`:67`)
- 🟡 **Unpaginated grid.** `disablePagination()` renders all units at once; fine for a short list, unbounded as it grows. (`:64`)
- 🟡 **No per-record authorization.** Any authenticated admin can create/edit units. (ADR-0009)
- 🔴 **No observability** on unit mutations (see above).
