# Addon request types: planned-trip scoping (frontend guide)

This document describes API and data changes for **trip-scoped add-on types**: types that are only available on **specific planned trips**, in addition to **global** types (available everywhere).

All routes below require authentication (`Authorization: Bearer …`, Sanctum), same as the rest of your admin API. Base path prefix is `/api` (adjust if your app uses a different prefix).

---

## 1. What changed (summary)

| Area | Change |
|------|--------|
| **Type model** | Each type has `is_global` (boolean). Global types apply to any context; non-global types only apply to planned trips linked in a pivot table. |
| **New relation on type** | Responses include `planned_trips` (array of `{ id, itinerary_code, pivot? }`) for types that are scoped to trips. Empty for global-only types. |
| **List types (`GET …/types`)** | **Breaking:** without query params, the API returns **only global** types. You must pass `planned_trip_id` or `list_all=1` when you need more. |
| **New endpoint** | `GET /planned-trips/{planned_trip}/addon-request-types` — convenience alias for listing types valid on that trip. |
| **Create / update type** | Optional `is_global` and `planned_trip_ids[]`. Non-global types **must** include at least one planned trip id. |
| **Create addon request** | If the chosen `type_id` is non-global, the client **must** send `planned_trip_id` and it must be one of the type’s linked trips (enforced server-side). |

---

## 2. Concepts

- **Global type (`is_global: true`)**  
  Appears in generic type lists (subject to `list_all` / filters). Can be used on any booking when creating an addon request (same rules as before for `planned_trip_id` on the request).

- **Trip-scoped type (`is_global: false`)**  
  Only appears when listing types **for** a planned trip that is linked to that type. Creating an addon request with this type requires `planned_trip_id` to match an allowed trip.

- **Sharing one special type across several trips**  
  Set `is_global: false` and put **multiple** ids in `planned_trip_ids` when creating or updating the type.

---

## 3. Listing types

### 3.1 `GET /api/addon-requests/types`

**Query parameters**

| Param | Type | Effect |
|--------|------|--------|
| *(none)* | — | **Only global types** (`is_global === true`). Trip-scoped types are **hidden**. |
| `planned_trip_id` | number | Global types **plus** types scoped to this planned trip. |
| `list_all` | `1` / `true` | **All** types (global and scoped). Use for full admin catalog screens. |
| `category_id` | number | Optional filter; works with any of the above. |

**Response shape (unchanged envelope)**

```json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "category_id": 1,
      "name": "…",
      "is_global": true,
      "planned_trips": [],
      "category": { "id": 1, "name": "…", "name_en": "…", "department_code": "…" }
    },
    {
      "id": 99,
      "is_global": false,
      "planned_trips": [
        { "id": 12, "itinerary_code": "JP20260301", "pivot": { "addon_request_type_id": 99, "planned_trip_id": 12 } }
      ]
    }
  ],
  "meta": { "pricing_types": [ … ] }
}
```

**Frontend migration note**

- If you previously called `GET /api/addon-requests/types` with **no query** and expected **every** type, add **`?list_all=1`** on admin “manage all types” pages.
- For **dropdowns when creating an addon request** for a booking on planned trip `X`, call either:
  - `GET /api/addon-requests/types?planned_trip_id=X`, or
  - `GET /api/planned-trips/X/addon-request-types` (see below).

### 3.2 `GET /api/planned-trips/{planned_trip}/addon-request-types`

Same response as `GET /api/addon-requests/types?planned_trip_id={planned_trip}`.

Use this on the **planned trip edit** screen so the URL encodes context (no need to remember the query param name).

Supports the same optional `category_id` (and any other query you forward — internally it merges `planned_trip_id`).

---

## 4. Showing one type

### `GET /api/addon-requests/types/{type}`

Loads `category` and **`planned_trips`** (`id`, `itinerary_code`). Use this to show “applies to trips: …” when editing a scoped type.

---

## 5. Creating a type

### `POST /api/addon-requests/types`

**Body (existing fields unchanged)**  
`category_id`, `name`, `pricing_type` (`free` | `fixed_price` | `quotation_required`), etc., as before.

**New / important fields**

| Field | Type | Default | Notes |
|--------|------|---------|--------|
| `is_global` | boolean | `true` if omitted | Set `false` for trip-only types. |
| `planned_trip_ids` | `number[]` | — | **Required when `is_global` is false:** at least one valid `planned_trips.id`. Replaces pivot when not global. |

**Global type (default)**

```json
{
  "category_id": 1,
  "name": "Extra baggage",
  "pricing_type": "fixed_price",
  "cost_currency": "TWD"
}
```

**Trip-scoped type (e.g. created from planned trip X = `12`)**

```json
{
  "category_id": 1,
  "name": "Special dinner – March tour",
  "pricing_type": "fixed_price",
  "cost_currency": "TWD",
  "is_global": false,
  "planned_trip_ids": [12]
}
```

**422 example (non-global without trips)**

```json
{
  "success": false,
  "message": "非全域類型時必須指定至少一個計劃團",
  "errors": {
    "planned_trip_ids": ["請選擇適用的計劃團"]
  }
}
```

---

## 6. Updating a type

### `PUT /api/addon-requests/types/{type}`

You may send `is_global` and/or `planned_trip_ids`.

- If you set **`is_global: true`**, pivot links are **cleared**.
- If the type stays or becomes **non-global**, there must be at least one planned trip:
  - either send **`planned_trip_ids`**, or
  - omit it only if the type **already** has trips linked (server keeps existing links).

Pivot is synced when the request includes `is_global` or `planned_trip_ids`.

---

## 7. Deleting a type

### `DELETE /api/addon-requests/types/{type}`

Unchanged rules: cannot delete if any addon **request** uses this type. Pivot rows are cleared before delete.

---

## 8. Creating an addon **request** (booking flow)

### `POST /api/addon-requests` (or your existing create endpoint)

When `type_id` refers to a **non-global** type:

1. Send **`planned_trip_id`** and it must be one of the type’s `planned_trips`.
2. If `planned_trip_id` is missing, validation error on `type_id` (Chinese message: must specify planned trip).
3. If `planned_trip_id` is wrong for that type, validation error (type not applicable to selected trip).

**Global types** behave as before: `planned_trip_id` remains optional on the addon request (unless your product rules say otherwise).

---

## 9. Recommended UI flows

### Planned trip edit — “special add-ons for this tour”

1. List: `GET /api/planned-trips/{id}/addon-request-types` (or `GET …/types?planned_trip_id={id}`).
2. Create: `POST /api/addon-requests/types` with `is_global: false`, `planned_trip_ids: [id]` (and optional extra ids if the same offering applies to multiple departures).

### Admin — full type catalog

- `GET /api/addon-requests/types?list_all=1`

### Sales / booking — pick type for a booking on trip X

- `GET /api/addon-requests/types?planned_trip_id=X`  
  Show only options returned; do not offer trip-scoped types from another trip.

---

## 10. Database / ops (for reference)

- Column: `addon_request_types.is_global` (default `true` for existing rows).
- Table: `addon_request_type_planned_trip` (`addon_request_type_id`, `planned_trip_id`, unique pair).

Run migrations on each environment: `php artisan migrate`.

---

## 11. Quick checklist for frontend

- [ ] Replace bare `GET …/types` with `?list_all=1` where you need the full catalog.
- [ ] Pass `planned_trip_id` (or use `planned-trips/{id}/addon-request-types`) for trip-aware pickers.
- [ ] For “create special type on this trip”, POST with `is_global: false` and `planned_trip_ids: [currentTripId]`.
- [ ] When creating addon requests, if user picks a scoped type, always send matching `planned_trip_id`.

---

*Document version: matches backend implementation with `AddonRequestTypeController`, `CreateAddonRequestRequest`, and migration `2026_03_27_000001_add_trip_scoping_to_addon_request_types`.*
