# Brands — 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-87`, `BrandController.php:17`)

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

Controller symbols:

| Symbol | Signature | Return | Note |
|--------|-----------|--------|------|
| `BrandController::index` | `()` | `Content` | Two-column layout: `grid()->render()` + a `Widgets\Form` action=`settings/brand` with `name`(required)/`description`. 🟢 (`:19-40`) |
| `BrandController::edit` | `($id)` | `Content` | `form()->edit($id)`. 🟢 (`:42-48`) |
| `BrandController::grid` | `()` | `Grid` | Columns id/name/description; create/export/selector/filter/pagination disabled; row actions disable edit for key 1, disable delete for all. 🟢 (`:53-73`) |
| `BrandController::form` | `()` | `Form` | `name`(required)/`description`; drives store/update. 🟢 (`:75-81`) |

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

1. `GET settings/brand` → `index()` builds `Admin::content` with header `Nhãn hiệu` ("Brand"), a row with two columns: left `column(6, grid()->render())`, right `column(6, …)` a `Widgets\Form` whose action is `admin_base_path('settings/brand')` exposing `name` (required) + `description`, wrapped in a green `Box(admin.new)`. 🟢 (`:19-40`)
2. Submitting the inline form → `POST settings/brand` → `ModelForm::store()` validates via `form()` (`name` required) and persists a `Brand`, then redirects back. 🟢 (`:75-81`)
3. `GET settings/brand/{id}/edit` → `edit()` renders `form()->edit($id)` (header `Nhãn hiệu`). 🟢 (`:42-48`)
4. Submitting the edit → `PUT settings/brand/{id}` → `ModelForm::update()` persists the change. 🟢 (`:75-81`)
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. 🟢 (`:53-72`)

## Alternative Flows

- **Default brand (id 1):** the edit action is removed from that row; it can never be edited or deleted through the UI. 🟢 (`:66-68`)
- **Validation failure:** missing `name` → framework validation error, redirect back with errors. 🟢 (`:78`)
- **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()`. 🟢 (`:17`)
- **`Encore\Admin` grid/form/widgets/layout** (`Grid`, `Form`, `Widgets\Form`, `Widgets\Box`, `Content`, `Row`, `Column`) — the whole UI. 🟢 (`:5-13`)
- **`Brand` model** — `products()` hasMany via `products.brand_id`; the reason brands are undeletable. 🟢 (`Brand.php:9-12`)
- **`products-catalog`** — consumes brands in the product create/edit brand dropdown (`products.brand_id` required FK). 🟢

## 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 | 🟢 (`:17`) |
| Inline create widget in a two-column screen instead of a separate create page | `index()` right column + `disableCreateButton()` | 🟢 (`:28-37,59`) |
| Brands are permanent reference data — no UI delete on any row | `$actions->disableDelete()` unconditionally | 🟢 (`:69`) |
| A single protected default brand (id 1), edit-locked | `in_array($actions->getKey(),[1]) → disableEdit()` | 🟢 (`:66-68`) |
| Unpaginated/unfiltered grid (small catalogue assumption) | `disableFilter()`, `disablePagination()` | 🟡 (`:62-63`) |

## Internal State

None beyond the persisted `brands` rows (`id`, `name`, `description`, timestamps). The controller holds no request-spanning state. 🟢 (`BrandController.php`)

## Observability

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

## Risks and Gaps

- 🟡 **Default-brand lock is id-based.** The edit lock keys off literal id `1`; if the default brand is ever re-seeded with a different id, the lock silently protects the wrong row. (`:66`)
- 🟡 **Unpaginated grid.** `disablePagination()` renders all brands at once; fine for a short list, but unbounded as the catalogue grows. (`:63`)
- 🟡 **No per-record authorization.** Any authenticated admin can create/edit brands. (ADR-0009)
- 🔴 **No observability** on brand mutations (see above).
