# Brands — Implementation Tasks

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

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

## Prerequisites

- [ ] `Encore\Admin` installed with the `ModelForm` trait, grid/form/widgets/layout components. 🟢 (`BrandController.php:5-17`)
- [ ] `Brand` model with `products()` hasMany via `products.brand_id`. 🟢 (`Brand.php:9-12`)
- [ ] `brands` table (`id`, `name`, `description`, timestamps) present. 🟢 (`data-dictionary.md` brands schema)
- [ ] A seeded default brand at id 1. 🟢 (`BrandController.php:66`)
- [ ] Admin route group `['web','admin']` + `settings` prefix. 🟢 (`routes/web.php:86-87`)

## Tasks

> Each task references the legacy file the behaviour was extracted from.

- [ ] T-01, Register `resource settings/brand` → `BrandController` inside the `settings` prefix group.
  - Legacy origin: `routes/web.php:87`
  - Done when: the seven resource routes resolve for an authenticated admin; anonymous → `302` to `auth/login`.
  - Confidence: 🟢

- [ ] T-02, Implement `index()`: two-column `Admin::content` (header `Nhãn hiệu`) — left `grid()->render()`, right an inline `Widgets\Form` (action `settings/brand`) with `name`(required)/`description` in a green `Box`.
  - Legacy origin: `BrandController.php:19-40`
  - Done when: the page shows the grid and the inline create form.
  - Confidence: 🟢

- [ ] T-03, Implement `grid()`: columns id(sortable)/name/description; disable create button, export, row selector, filter, pagination.
  - Legacy origin: `BrandController.php:53-64`
  - Done when: the grid shows the three columns with none of the disabled controls.
  - Confidence: 🟢

- [ ] T-04, Implement the row-action lock: `disableDelete()` on every row; `disableEdit()` when the row key is `1`.
  - Legacy origin: `BrandController.php:65-70`
  - Done when: no row offers delete; the id-1 row offers no edit.
  - Confidence: 🟢

- [ ] T-05, Implement `edit($id)` (`form()->edit($id)`) and `form()` (`name` required / `description`) so `ModelForm` store/update/edit work.
  - Legacy origin: `BrandController.php:42-48,75-81`
  - Done when: creating and editing brands persists `name`/`description`, with `name` required.
  - Confidence: 🟢

- [ ] T-06, (Improvement, not in legacy) Make the default-brand lock resilient — key the edit lock off a seeded flag/slug rather than the literal id `1`.
  - Legacy origin: `BrandController.php:66`
  - Done when: the default brand stays protected regardless of its id.
  - Confidence: 🟡

- [ ] T-07, (Improvement, not in legacy) Add observability on brand create/update.
  - Legacy origin: `BrandController.php` (absence)
  - Done when: brand mutations emit a structured log or metric.
  - Confidence: 🔴

## Test Tasks

- [ ] TT-01, List: `GET settings/brand` returns the grid + inline create form (see `requirements.md`, Acceptance Criteria).
- [ ] TT-02, Create happy path: posting `name`+`description` persists a brand.
- [ ] TT-03, Create validation: posting without `name` fails and creates nothing.
- [ ] TT-04, Edit: updating a non-locked brand's `name`/`description` persists.
- [ ] TT-05, Locks: the id-1 row offers no edit; no row offers delete.
- [ ] TT-06, Auth: anonymous request → `302` to `auth/login`.

## Data Migration Tasks (if applicable)

- [ ] TM-01, Ensure the `brands` table and its seeded default (id 1) exist before products reference `brand_id`.
  - Legacy origin: `data-dictionary.md` brands schema; `Brand.php`

## Suggested Order

1. T-01 → T-02 (route + index screen).
2. T-03 → T-05 (grid, locks, form/edit).
3. T-06 → T-07 (lock resilience, observability) as improvements.

## Pending Gaps (🔴)

- **No observability (T-07):** brand mutations emit no signal.
- **Id-based default lock (T-06):** confirm/replace the literal-id-1 edit lock with a durable marker.
