# Gifts Scan — Contracts

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

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

External HTTP contract exposed by the `gifts-scan` unit — the JSON lookup `GET settings/gifts/scan` (`GiftController::scan`) under the admin group (`['web','admin']`) with the `settings` prefix. It is a read-only **JSON** endpoint (not HTML), requires an authenticated admin session, and is declared before `resource settings/gifts` so it is not shadowed by the resource `show` route. 🟢 (`routes/web.php:90-91`, `GiftController.php:115-126`)

---

## Endpoint `GET settings/gifts/scan` 🟢

| Aspect | Value |
|--------|-------|
| Method / Path | `GET settings/gifts/scan` |
| Auth | Required; anonymous → `302 auth/login`. 🟢 (`routes/web.php:24-28`) |
| Query params | `q` (string, optional — name substring), `active` (0/1, optional — status filter applied only when present) 🟢 (`:117-121`) |
| Success | `200` `application/json` `{ "data": [ …Gift ] }` (≤10 items; `[]` when no match) 🟢 (`:122-125`) |
| Errors | No explicit error branch; invalid input is ignored (no validation) 🟡 |

### Request

| Param | Type | Required | Notes |
|-------|------|----------|-------|
| `q` | string | ❌ | Substring matched as `name LIKE %q%`; no validation/default. Empty/missing → `LIKE '%%'` (returns first 10). 🔴 (`:117-118`) |
| `active` | 0 / 1 | ❌ | Applied only when the param is **present** (`request()->has('active')`); value passed straight to `where('active', …)`. 🟢/🟡 (`:119-121`) |

### Response body 🟢

```json
{
  "data": [
    {
      "id": 12,
      "name": "Voucher 50k",
      "image": "https://…/storage/…/gift.png",
      "points": 100.0,
      "limit": 0,
      "quantity": 50,
      "used": 7,
      "active": 1,
      "quantity_available": 43,
      "created_at": "…",
      "updated_at": "…",
      "deleted_at": null
    }
  ]
}
```

- Each element is the full serialized `Gift`: all columns + appended `quantity_available` (`quantity − used`) + the `image` accessor URL (or `noimage.png` placeholder). 🟢 (`Gift.php:16-27`)
- `data` is always an array (empty `[]` when nothing matches); the response is always `200`. 🟢 (`:122-125`)
- Envelope shape is `{data:[…]}` — **different** from `customers-scan` (bare array) and `pos-scan` (`{is_barcode, data}` union). Consumers must read `.data`. 🟡

---

## Consumed contracts (owned by other units)

`gifts-scan` reads only the `gifts` table through Eloquent; it calls no other unit's HTTP endpoint and no external service. 🟢

| Reads | Owner unit | Purpose |
|-------|------------|---------|
| `gifts` rows (`name`, `active`, `quantity`/`used` → `quantity_available`, `image`) | `gifts-crud` | candidate gifts for the redemption picker |

---

## Producer/consumer relationships

| This unit is… | Counterparty | Contract |
|---------------|--------------|----------|
| **Producer** | loyalty redemption **picker** (`customers-statistics` / redeem screens) | supplies `{data:[…]}` gift candidates as the operator types; the authoritative availability/redeemability gate runs later in `customers-loyalty`. 🟢 (`gifts` flowchart:39-40) |
| **Consumer** | `gifts-crud` | reads the catalogue rows that unit maintains (name/active/stock/image). 🟢 |

---

## Cross-cutting contract notes

- **Read-only JSON:** no writes; the endpoint never mutates state. 🟢
- **Name-only search:** no code/id path (gifts have no code column). 🟢 (`:118`)
- **Availability not enforced at the endpoint level, but mitigated by the only consumer.** The endpoint itself does not filter by `quantity_available` and treats `active` as opt-in. ✅ Verified 2026-09-23: the redeem-points picker (`customer-statis.blade.php:328,338`) always calls with `?active=1` and visually disables (`pointer-events: none`) any option with `quantity_available <= 0`. The authoritative gate remains `checkGiftAvailable` at redemption. 🟢
- **Soft-deleted excluded:** by the `Gift` default scope. 🟢 (`Gift.php:10-14`)
- **Authorization:** authentication only; any admin may query gifts. 🟡 (ADR-0009)
- **No observability:** the lookup emits no telemetry. 🔴 (`GiftController.php:115-126`, absence)
