# Gifts (CRUD) — 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. 🟢 (`GiftController.php:6-17`)
- [ ] `Intervention\Image` available via the `\Image` facade. 🟢 (`GiftController.php:108`)
- [ ] Local `storage/app/public` disk configured and symlinked/served via `storage_url`. 🟢 (`GiftController.php:108`; `Gift.php:22`)
- [ ] `Gift` model with `SoftDeletes`, `$fillable`, `quantity_available` accessor, image accessor, `active` scope. 🟢 (`Gift.php`)
- [ ] `gifts` table (`id`, `name`, `image`, `points` decimal(8,1), `limit`, `quantity`, `active`, `used`, timestamps, `deleted_at`). 🟢 (`create_gifts_table` + `add_total_used_to_gifts_table` migrations)
- [ ] Admin route group `['web','admin']` + `settings` prefix. 🟢 (`routes/web.php:86-91`)

## Tasks

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

- [ ] T-01, Register `resource settings/gifts` → `GiftController` inside the `settings` prefix group (after the `settings/gifts/scan` route so it is not shadowed).
  - Legacy origin: `routes/web.php:90-91`
  - 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 `Quà tặng`) — left `grid()->render()`, right an inline `Widgets\Form` (action `settings/gifts`) with name/image(file)/points/limit/quantity/active(switch default 1) in a green `Box`.
  - Legacy origin: `GiftController.php:19-41`
  - Done when: the page shows the grid and the inline create form.
  - Confidence: 🟢

- [ ] T-03, Implement `grid()`: columns id(sortable)/name/image-thumbnail/points/limit/computed `used/quantity`/active-label; disable create button, export, row selector, filter, pagination; **empty** row-action closure so edit + delete remain on every row.
  - Legacy origin: `GiftController.php:51-74`
  - Done when: the grid renders all columns; every row offers edit and delete.
  - Confidence: 🟢

- [ ] T-04, Implement `form()` fields: `name`(required), `image`(file, `max:1024|mimes:jpeg,png,jpg,gif,svg,webp`, `dir('/')`), `points`/`limit` (number, `nullable|numeric`, `min(0)`), `quantity` (number, `nullable|numeric`), read-only `used` html display, `active` switch.
  - Legacy origin: `GiftController.php:76-97`
  - Done when: create/edit persist the fields with the stated validation.
  - Confidence: 🟢

- [ ] T-05, Implement the dynamic `quantity` rule: append `|min:{used}` only when the model has an id, with message "Số lượng không được bé hơn tổng đã dùng".
  - Legacy origin: `GiftController.php:84-92`
  - Done when: editing with `quantity < used` fails with that message; create has no such floor.
  - Confidence: 🟢

- [ ] T-06, Implement the `saving` hook: `points/limit/quantity ?: 0`; `used = id ? used : 0`.
  - Legacy origin: `GiftController.php:99-104`
  - Done when: blank numerics persist as 0 and a new gift starts at `used = 0`.
  - Confidence: 🟢

- [ ] T-07, Implement the `saved` hook: when an image was uploaded, `Image::make(storage_path('app/public/'.original))->fit(300,300)->save()`.
  - Legacy origin: `GiftController.php:106-111`
  - Done when: a saved gift's image is cropped/resized to a 300×300 square.
  - Confidence: 🟢

- [ ] T-08, Implement `edit($id)` (`form()->edit($id)`) so `ModelForm` edit/update work with the read-only `used` display.
  - Legacy origin: `GiftController.php:43-49,93-95`
  - Done when: the edit screen shows current values and the disabled `used` field.
  - Confidence: 🟢

- [ ] T-09, Confirm soft delete: `DELETE settings/gifts/{id}` sets `deleted_at`, hides the row, and preserves `customer_gift` redemption history.
  - Legacy origin: `Gift.php:10-14`
  - Done when: a deleted gift leaves the grid while its past redemptions remain.
  - Confidence: 🟢

- [ ] T-10, (Improvement, not in legacy) Add observability on gift create/update/delete and the image-resize step.
  - Legacy origin: `GiftController.php` (absence)
  - Done when: gift mutations emit a structured log or metric.
  - Confidence: 🔴

- [ ] T-11, (Improvement, not in legacy) Reconcile the inline create widget field types/defaults with `form()` so create and edit present consistently.
  - Legacy origin: `GiftController.php:31-36` vs `:76-97`
  - Done when: the two surfaces agree on field types and defaults.
  - Confidence: 🟡

## Test Tasks

- [ ] TT-01, List: `GET settings/gifts` returns the grid + inline create form (see `requirements.md`, Acceptance Criteria).
- [ ] TT-02, Create happy path: a valid gift with blank points/limit/quantity persists with those = 0 and used = 0.
- [ ] TT-03, Edit quantity floor: saving `quantity < used` fails with the localized message.
- [ ] TT-04, Image: a valid upload is resized to 300×300; a >1 MB or non-image upload is rejected.
- [ ] TT-05, Delete: deleting a gift soft-deletes it (row gone) while redemption history remains.
- [ ] TT-06, Locks: every row offers edit and delete (no locked rows).
- [ ] TT-07, Auth: anonymous request → `302` to `auth/login`.

## Data Migration Tasks (if applicable)

- [ ] TM-01, Ensure `gifts` (with `used` column) and `customer_gift` exist before enabling redemption.
  - Legacy origin: `create_gifts_table`, `add_total_used_to_gifts_table`, `create_customer_gift_table` migrations

## Suggested Order

1. T-01 → T-03 (route + index screen + grid).
2. T-04 → T-08 (form fields, quantity floor, hooks, edit).
3. T-09 (soft delete), then T-10 → T-11 (observability, widget/form reconciliation).

## Pending Gaps (🔴)

- **No observability (T-10):** gift mutations and image resizing emit no signal.
