# Brands — Requirements

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

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

## Overview

`brands` is the back-office reference-data editor for product brands (`resource settings/brand`, `BrandController`). It is a thin **Encore\Admin** scaffolded CRUD: a two-column screen with a read-only grid on the left and an inline "new brand" widget form on the right, plus an edit form. A brand is little more than a `name` + optional `description` that products reference via the required `products.brand_id` foreign key. 🟢 (`routes/web.php:87`, `BrandController.php`)

Only `index`, `edit`, `grid`, and `form` are overridden in the controller; `store`, `update`, and `destroy` come from the `Encore\Admin\Controllers\ModelForm` trait and are driven by `form()`. 🟢 (`BrandController.php:17,75-81`)

## Responsibilities

- Render a two-column management screen: left = brand grid, right = an inline create form posting to `settings/brand`. 🟢 (`:19-40`)
- List brands in a grid with columns `id` (sortable), `name`, `description`, and no create button / export / row selector / filter / pagination. 🟢 (`:53-72`)
- Gate row actions: **disable delete on every row**, and **disable edit on the row with key `1`** (the mandatory default brand). 🟢 (`:65-70`)
- Provide an edit form (`name` required, `description`) for non-locked brands. 🟢 (`:42-48,75-81`)
- Persist create/update via the `ModelForm` trait using `form()`'s field set (`name` required, `description`). 🟢 (`:17,75-81`)

## Business Rules

- **A brand is `name` (required) + `description` (optional).** Both the inline widget form and `form()` expose exactly these two fields. 🟢 (`:32-33,78-79`)
- **Brand id 1 is the mandatory default brand.** Its edit action is disabled (`in_array($actions->getKey(), [1])` → `disableEdit()`); combined with the app-wide delete lock, id 1 is permanent and immutable through the UI. 🟢 (`:66-68`)
- **No brand is UI-deletable.** `disableDelete()` runs for every row, and there is no create button (`disableCreateButton()`); creation happens only through the inline widget form. 🟢 (`:59,69`)
- **Brands are referenced by products.** `Brand::products()` is `hasMany(Product)` via `products.brand_id`, a required FK — every product belongs to a brand, which is why brands cannot be deleted. 🟢 (`Brand.php:9-12`; `brands` flowchart)
- **No custom algorithm or lifecycle hook.** The controller adds no `saving`/`saved` hooks, no validation beyond `name` required; all persistence is the framework default. 🟢 (`BrandController.php`)
- **Grid is unpaginated / unfiltered.** Export, row selector, filter, and pagination are all disabled — the brand list is expected to be short. 🟡 (`:60-63`)

## Functional Requirements

| ID | Requirement | Priority | Acceptance criterion |
|----|-------------|----------|----------------------|
| RF-01 | List brands at `GET settings/brand` in a two-column screen (grid + inline create form) | Must | The page shows the id/name/description grid and a "new brand" box with `name`/`description` fields. 🟢 |
| RF-02 | Create a brand via the inline form (`POST settings/brand`) | Must | Submitting `name` (required) + `description` persists a new brand and returns to the list. 🟢 |
| RF-03 | Edit a brand's `name`/`description` (`GET settings/brand/{id}/edit`, `PUT settings/brand/{id}`) | Must | Editing a non-locked brand updates its fields; `name` is required. 🟢 |
| RF-04 | Lock the default brand (id 1) from editing | Should | The edit action is absent on the id-1 row. 🟢 |
| RF-05 | Disable deletion for all brands | Should | No delete action is offered on any row. 🟢 |
| RF-06 | Require an authenticated admin session | Must | Anonymous request → `302` to `auth/login`. 🟢 (`routes/web.php:24-28`) |

## Non-Functional Requirements

| Type | Inferred requirement | Evidence in code | Confidence |
|------|----------------------|------------------|------------|
| Security | Admin authentication required (admin route group middleware `['web','admin']`) | `routes/web.php:24-28,86-92` | 🟢 |
| Integrity | Brands are undeletable by design because `products.brand_id` is a required FK (deleting a brand would orphan products) | `Brand.php:9-12`; `BrandController.php:69` | 🟢 |
| Usability | Unpaginated single-screen grid — assumes a small brand catalogue | `BrandController.php:60-63` | 🟡 |
| Observability | None — framework CRUD emits no domain log/metric | `BrandController.php` (absence) | 🔴 |

> Inferred from code. Validate with the operations team.

## Acceptance Criteria

```gherkin
Given an authenticated administrator
When he accesses GET settings/brand
Then he receives HTTP 200 with the brand grid (id/name/description) and the inline "new brand" form

Given the inline new-brand form
When he submits a filled name and description
Then a new brand is persisted and the list is re-rendered

Given the inline new-brand form
When he submits without name
Then required validation fails and the brand is not created

Given the default brand of id 1
When the grid is rendered
Then the edit action is absent from that row and no row offers delete

Given a request with no authenticated admin session
When settings/brand is accessed
Then it receives HTTP 302 redirecting to auth/login
```

## Priority (MoSCoW)

| Requirement | MoSCoW | Justification |
|-------------|--------|---------------|
| List + create brands (RF-01, RF-02) | Must | Brands are required master data for every product |
| Edit brand (RF-03) | Must | Correcting brand names/descriptions |
| Admin authentication (RF-06) | Must | Enforced by the route group |
| Lock default brand id 1 (RF-04) | Should | Protects the mandatory fallback brand |
| Disable deletion (RF-05) | Should | Prevents orphaning products via the required FK |

> Priority inferred from brands being mandatory reference data referenced by a required product FK.

## Code Traceability

| File | Function / Class | Coverage |
|------|------------------|----------|
| `app/Http/Controllers/BrandController.php:19-40` | `BrandController::index` (two-column grid + inline form) | 🟢 |
| `app/Http/Controllers/BrandController.php:42-48` | `BrandController::edit` | 🟢 |
| `app/Http/Controllers/BrandController.php:53-73` | `BrandController::grid` (columns, disabled features, row-action locks) | 🟢 |
| `app/Http/Controllers/BrandController.php:75-81` | `BrandController::form` (drives ModelForm store/update) | 🟢 |
| `app/Models/Brand.php:9-12` | `Brand::products()` hasMany via `products.brand_id` | 🟢 |
| `routes/web.php:87` | `resource settings/brand` | 🟢 |
