# Gifts Crud Specification

## Purpose

`gifts-crud` is the back-office catalogue editor for loyalty-reward gifts, exposing a server-rendered HTML CRUD at `settings/gifts` for authenticated administrators. It manages the gift master data — name, image, points redemption cost, per-customer limit, stock quantity, usage counter, and active flag — that the loyalty redemption flow depends on.

## Requirements

### Requirement: Authenticated Admin Session Required

The system SHALL redirect any request to `settings/gifts` (any verb or path under the resource) to `auth/login` with HTTP 302 when the caller has no authenticated admin session. (Implemented in `routes/web.php:24-28,86-91`)

#### Scenario: Anonymous access to the gift list

- **GIVEN** a caller with no authenticated admin session
- **WHEN** they send `GET settings/gifts`
- **THEN** the system responds with HTTP 302 redirecting to `auth/login`

#### Scenario: Anonymous access to an edit form

- **GIVEN** a caller with no authenticated admin session
- **WHEN** they send `GET settings/gifts/{id}/edit`
- **THEN** the system responds with HTTP 302 redirecting to `auth/login`

---

### Requirement: Gift Listing Screen

The system SHALL respond to `GET settings/gifts` from an authenticated administrator with HTTP 200, rendering a page that contains both the gifts grid and the inline new-gift form. (Implemented in `app/Http/Controllers/GiftController.php:19-41`)

#### Scenario: Listing screen renders grid and inline form

- **GIVEN** an authenticated administrator
- **WHEN** they send `GET settings/gifts`
- **THEN** the system responds with HTTP 200 containing the gifts grid and the inline new-gift form

---

### Requirement: Gift Grid Column Set

The system SHALL render each gift row in the grid with the following columns: id (sortable), name, image thumbnail, points, limit, a combined used/quantity stock display, and an active status label. The grid SHALL NOT display controls for creating gifts, exporting, selecting rows, filtering, or paginating. (Implemented in `app/Http/Controllers/GiftController.php:51-74`)

#### Scenario: Expected columns appear in the grid

- **GIVEN** at least one gift exists
- **WHEN** an authenticated administrator loads `GET settings/gifts`
- **THEN** each row shows id, name, image thumbnail, points, limit, the used/quantity stock display, and an active status label, and no create, export, filter, or pagination controls are present

---

### Requirement: Every Gift Row Is Editable and Deletable

The system SHALL make edit and delete actions available on every gift row without restriction. (Implemented in `app/Http/Controllers/GiftController.php:72`)

#### Scenario: Edit and delete present on all rows

- **GIVEN** multiple gifts exist in the catalogue
- **WHEN** an authenticated administrator views the grid
- **THEN** every row offers both an edit action and a delete action

---

### Requirement: Gift Name Is Required

The system SHALL reject a create or edit submission where the `name` field is absent or blank, redirecting back with validation errors and making no change to stored data. (Implemented in `app/Http/Controllers/GiftController.php:79`)

#### Scenario: Create without name fails

- **GIVEN** an authenticated administrator
- **WHEN** they submit `POST settings/gifts` without a name
- **THEN** the system redirects back with a validation error and no gift is created

#### Scenario: Edit removing name fails

- **GIVEN** an existing gift
- **WHEN** an authenticated administrator submits `PUT settings/gifts/{id}` with name blank
- **THEN** the system redirects back with a validation error and the gift is not updated

#### Scenario: Create with valid name succeeds

- **GIVEN** an authenticated administrator
- **WHEN** they submit `POST settings/gifts` with a non-blank name
- **THEN** the gift is persisted and the system responds with HTTP 302 back to the list

---

### Requirement: Blank Numeric Fields Stored as Zero

The system SHALL store `0` for `points`, `limit`, and `quantity` when those fields are submitted blank on create or edit. (Implemented in `app/Http/Controllers/GiftController.php:99-102`)

#### Scenario: Blank numerics on create default to zero

- **GIVEN** an authenticated administrator
- **WHEN** they submit `POST settings/gifts` with a valid name and `points`, `limit`, and `quantity` left blank
- **THEN** the created gift has `points = 0`, `limit = 0`, and `quantity = 0`

#### Scenario: Blank numerics on edit default to zero

- **GIVEN** an existing gift
- **WHEN** an authenticated administrator submits `PUT settings/gifts/{id}` leaving `points`, `limit`, and `quantity` blank
- **THEN** those fields are stored as `0`

---

### Requirement: New Gift Used Counter Initialised to Zero

The system SHALL set `used = 0` when persisting a new gift, regardless of any submitted value. (Implemented in `app/Http/Controllers/GiftController.php:99-104`)

#### Scenario: used is zero after creation

- **GIVEN** an authenticated administrator
- **WHEN** they submit `POST settings/gifts` with a valid name
- **THEN** the created gift has `used = 0`

---

### Requirement: Used Counter Preserved on Edit

The system SHALL NOT alter the `used` counter when a gift is updated via `PUT settings/gifts/{id}`. (Implemented in `app/Http/Controllers/GiftController.php:99-104`)

#### Scenario: Editing a gift leaves used unchanged

- **GIVEN** an existing gift with `used = 5`
- **WHEN** an authenticated administrator submits `PUT settings/gifts/{id}` updating any editable field
- **THEN** the gift's `used` value remains `5`

---

### Requirement: Quantity Cannot Be Lowered Below Used on Edit

The system SHALL reject an edit submission where `quantity` is less than the current `used` value, redirecting back with the error message "Số lượng không được bé hơn tổng đã dùng" and leaving the gift unchanged. This validation SHALL NOT apply on create. (Implemented in `app/Http/Controllers/GiftController.php:84-92`)

#### Scenario: Quantity below used is rejected

- **GIVEN** an existing gift with `used = 5`
- **WHEN** an authenticated administrator submits `PUT settings/gifts/{id}` with `quantity = 3`
- **THEN** the system redirects back with the error "Số lượng không được bé hơn tổng đã dùng" and the gift is not updated

#### Scenario: Quantity equal to used is accepted

- **GIVEN** an existing gift with `used = 5`
- **WHEN** an authenticated administrator submits `PUT settings/gifts/{id}` with `quantity = 5`
- **THEN** the update is persisted successfully

#### Scenario: No floor applied on create

- **GIVEN** an authenticated administrator
- **WHEN** they submit `POST settings/gifts` with a valid name and any non-negative `quantity`
- **THEN** no used-based floor validation is applied and the gift is created

---

### Requirement: Image Upload Validated for Size and Type

The system SHALL reject an image upload that exceeds 1024 KB or whose MIME type is not one of `jpeg`, `png`, `jpg`, `gif`, `svg`, `webp`, redirecting back with a validation error and storing no file. (Implemented in `app/Http/Controllers/GiftController.php:80-81`)

#### Scenario: Oversized image is rejected

- **GIVEN** an authenticated administrator
- **WHEN** they submit a gift form with an image file exceeding 1024 KB
- **THEN** the system redirects back with a validation error and no image is stored

#### Scenario: Disallowed file type is rejected

- **GIVEN** an authenticated administrator
- **WHEN** they submit a gift form with a file whose MIME type is not in `jpeg, png, jpg, gif, svg, webp`
- **THEN** the system redirects back with a validation error and no file is stored

---

### Requirement: Uploaded Image Normalised to 300×300

The system SHALL crop and resize every successfully uploaded gift image to a 300×300-pixel square after the gift record is saved. (Implemented in `app/Http/Controllers/GiftController.php:106-111`)

#### Scenario: Valid image is resized after save

- **GIVEN** an authenticated administrator submitting a gift with a valid image (≤1024 KB, accepted MIME type)
- **WHEN** the gift is saved
- **THEN** the stored image file is 300×300 pixels

---

### Requirement: Missing Image Falls Back to Placeholder

The system SHALL return the `noimage.png` placeholder asset URL when a gift has no image stored. (Implemented in `app/Models/Gift.php:21-23`)

#### Scenario: Gift without image returns placeholder

- **GIVEN** a gift with no image
- **WHEN** the image field of that gift is read
- **THEN** the returned value is the URL of the `images/noimage.png` placeholder asset

---

### Requirement: quantity_available Exposed as Computed Attribute

The system SHALL expose `quantity_available` as `quantity − used` on every gift record. This value SHALL NOT be stored; it is derived on read. (Implemented in `app/Models/Gift.php:18,25-27`)

#### Scenario: quantity_available reflects current stock

- **GIVEN** a gift with `quantity = 10` and `used = 3`
- **WHEN** the gift record is read
- **THEN** `quantity_available` equals `7`

---

### Requirement: Gift Deletion Is a Soft Delete

The system SHALL soft-delete a gift on `DELETE settings/gifts/{id}`, causing the row to leave the grid while retaining all associated redemption records. (Implemented in `app/Models/Gift.php:10-14`)

#### Scenario: Deleted gift is removed from the grid

- **GIVEN** an existing gift visible in the grid
- **WHEN** an authenticated administrator triggers delete on that row
- **THEN** the gift no longer appears in the grid

#### Scenario: Redemption history survives deletion

- **GIVEN** a gift that has been redeemed by at least one customer
- **WHEN** an authenticated administrator deletes that gift
- **THEN** the associated redemption records in the `customer_gift` pivot are retained

---

### Requirement: Unknown Gift ID Returns 404

The system SHALL return HTTP 404 when `GET settings/gifts/{id}/edit`, `PUT settings/gifts/{id}`, or `DELETE settings/gifts/{id}` is called with an id that does not correspond to any gift. (Implemented in `app/Http/Controllers/GiftController.php:43-49`)

#### Scenario: Edit form with unknown id returns 404

- **GIVEN** an authenticated administrator
- **WHEN** they send `GET settings/gifts/{id}/edit` for a non-existent id
- **THEN** the system responds with HTTP 404

---

### Requirement: Inline Create Form Defaults active to Enabled

The system SHALL default the `active` field to `1` (enabled) when a new gift is submitted via the inline create form without an explicit active value. (Implemented in `app/Http/Controllers/GiftController.php:36`)

#### Scenario: Inline create without explicit active value creates an enabled gift

- **GIVEN** an authenticated administrator on the gifts listing screen
- **WHEN** they submit the inline create form with a valid name and without explicitly setting the active field
- **THEN** the created gift has `active = 1`
