# Pet Hotel API 文件

## 概述

本文檔詳細說明 Pet Hotel 功能的 API 端點規格，包含商家設定管理、房型管理、房間管理、住宿單表單管理、價格規則和排班管理等核心功能。

## 🔗 API 端點列表

### 目錄
1. [寵物旅館設定管理 API](#寵物旅館設定管理-api)
2. [房型管理 API](#房型管理-api)
3. [房間管理 API](#房間管理-api)
4. [價格規則管理 API](#價格規則管理-api)
5. [住宿排班管理 API](#住宿排班管理-api)
6. [預約查詢 API](#預約查詢-api)
7. [住宿訂單管理 API](#住宿訂單管理-api)
   - [取得訂單容器內的住宿訂單列表](#取得訂單容器內的住宿訂單列表)
   - [取得訂單短網址](#取得訂單短網址)
   - [取得住宿訂單列表](#取得住宿訂單列表)
   - [匯出住宿訂單 CSV](#匯出住宿訂單-csv)
   - [計算住宿訂單價格](#計算住宿訂單價格)
   - [更新住宿訂單](#更新住宿訂單)
   - [建立或更新住宿訂單表單](#建立或更新住宿訂單表單)
   - [取得住宿訂單表單（包含寵物資訊和住宿單題目）](#取得住宿訂單表單包含寵物資訊和住宿單題目)
   - [處理訂單操作](#處理訂單操作)
   - [建立分期付款](#建立分期付款)
   - [住宿訂單寵物服務記錄管理 API](#住宿訂單寵物服務記錄管理-api)
     - [取得住宿訂單寵物的服務記錄列表](#取得住宿訂單寵物的服務記錄列表)
     - [取得寵物的歷史記錄列表（包含住宿記錄、寵物備註和服務記錄）](#取得寵物的歷史記錄列表包含住宿記錄寵物備註和服務記錄)
     - [建立住宿訂單寵物的服務記錄](#建立住宿訂單寵物的服務記錄)
     - [更新住宿訂單寵物的服務記錄](#更新住宿訂單寵物的服務記錄)
     - [刪除住宿訂單寵物的服務記錄](#刪除住宿訂單寵物的服務記錄)
8. [會員通知 API](#會員通知-api)
   - [發送會員通知](#發送會員通知)
9. [輔助 API](#輔助-api)
10. [住宿單表單管理 API](#住宿單表單管理-api)

### 寵物旅館設定管理 API

#### 取得寵物旅館設定
```http
POST /api/admin/merchant/setting
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `type` | string | ✅ | 設定類型 | `"hotel"` |

**type 可選值**:
| 值 | 說明 |
|----|------|
| `"hotel"` | 寵物住宿設定 |
| `"cosmetic"` | 美容服務設定 |
| `"daycare"` | 日托服務設定 |
| `"homestay"` | 民宿設定 |
| `"prepaid"` | 預付設定 |
| `"deposit"` | 訂金設定 |
| `"level"` | 等級設定 |
| `"goods"` | 商品設定 |

**請求範例**:
```json
{
    "type": "hotel"
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": {
        "isEnabled": 1,
        "title": "寵物住宿",
        "allowNewCustomer": 1,
        "addEnabled": 1,
        "bannerImages": [
            {
                "path": "/storage/merchants/example/banner1.jpg",
                "sort": 0
            }
        ],
        "bannerText": "歡迎來到我們的寵物旅館",
        "description": "提供舒適安全的住宿環境，讓您的寵物有家的感覺",
        "checkinNote": "入住時請攜帶寵物疫苗證明文件",
        "terms": "1. 入住前需完成健康檢查\n2. 禁止攜帶違禁品\n3. 遵守旅館規定",
        "showCheckoutDate": 1,
        "productSetting": [
            {
                "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
                "isEnable": 1
            },
            {
                "productId": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "isEnable": 0
            }
        ]
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `isEnabled` | integer | 是否啟用 (0:停用, 1:啟用) |
| `title` | string | 功能標題 |
| `allowNewCustomer` | integer | 是否允許新客戶預訂 (0:否, 1:是) |
| `addEnabled` | integer | 是否啟用新增功能 (0:否, 1:是) |
| `bannerImages` | array | 橫幅圖片陣列 |
| `bannerText` | string | 橫幅文字 |
| `description` | string | 住宿介紹 |
| `checkinNote` | string | 入住須知 |
| `terms` | string | 契約內容/服務條款 |
| `showCheckoutDate` | integer | 是否以隔日退房日作為區間迄日顯示 (0:否＝迄日為最後一晚／共 N 天, 1:是＝迄日為 check_out+1／共 N 晚；預設 1)。影響 email／自訂通知 `{入住時間}`；前台自行依此開關調整 UI |
| `productSetting` | array | 分店開放設定, 分店名稱等資訊請使用 `/api/admin/product/search` 分店列表 API 取得|

#### 儲存寵物旅館設定
```http
POST /api/admin/merchant/setting/store
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `type` | string | ✅ | 設定類型 | `"hotel"` |
| `payload` | object | ✅ | 設定內容物件 | 見下方 payload 物件說明 |

**payload 物件結構** (所有欄位皆為選填，未提供的欄位將使用預設值):

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `isEnabled` | integer | ❌ | 是否啟用 | `1` |
| `title` | string | ❌ | 功能標題 | `"寵物住宿"` |
| `allowNewCustomer` | integer | ❌ | 是否允許新客戶預訂 | `1` |
| `addEnabled` | integer | ❌ | 是否啟用新增功能 | `1` |
| `bannerImages` | array | ❌ | 橫幅圖片陣列 | `[{"path": "/storage/...", "sort": 0}]` |
| `bannerText` | string | ❌ | 橫幅文字 | `"歡迎光臨"` |
| `description` | string | ❌ | 住宿介紹 | `"舒適的住宿環境"` |
| `checkinNote` | string | ❌ | 入住須知 | `"請攜帶證明文件"` |
| `terms` | string | ❌ | 契約內容/服務條款 | `"服務條款內容..."` |
| `showCheckoutDate` | integer | ❌ | 是否顯示隔日退房迄日（0/1，預設 1） | `1` |
| `productSetting` | array | ❌ | 分店設定 | `[{"productId": "...", "isEnable": 1}]` |

**請求範例**:
```json
{
    "type": "hotel",
    "payload": {
        "isEnabled": 1,
        "title": "寵物住宿",
        "allowNewCustomer": 1,
        "addEnabled": 1,
        "bannerImages": [
            {
                "path": "/storage/merchants/example/banner1.jpg",
                "sort": 0
            },
            {
                "path": "/storage/merchants/example/banner2.jpg",
                "sort": 1
            }
        ],
        "bannerText": "給您的毛孩最舒適的住宿體驗",
        "description": "我們提供24小時專業照護，確保您的寵物在住宿期間安全舒適。設施包含獨立空調房間、監控系統、遊戲區等。",
        "checkinNote": "1. 請於入住前提供寵物疫苗證明\n2. 建議攜帶寵物平時使用的玩具或毯子\n3. 入住時間為每日下午2點後",
        "terms": "一、服務條款\n1. 入住前需完成健康檢查\n2. 寵物需已完成基本疫苗接種\n\n二、取消政策\n1. 入住前7天取消可全額退費\n2. 入住前3-7天取消退還50%費用\n3. 入住前3天內取消恕不退費",
        "showCheckoutDate": 1,
        "productSetting": [
            {
                "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
                "isEnable": 1
            },
            {
                "productId": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "isEnable": 0
            }
        ]
    }
}
```

**回應範例**:
```json
{
    "status": "success",
    "message": "設定已成功儲存"
}
```

### 房型管理 API

#### 新增/編輯房型
```http
POST /api/admin/hotel/room/type/store
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | integer | ❌ | 房型ID (編輯時必填，新增時為null) | `1` |
| `productId` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得  | `"uuid-product-id"` |
| `title` | string | ✅ | 房型名稱 | `"豪華單人房"` |
| `enTitle` | string | ❌ | 英文房型名稱 | `"Deluxe Single"` |
| `subTitle` | string | ❌ | 副標題 | `"適合小型犬"` |
| `maxPets` | integer | ✅ | 最大寵物數量 | `1` |
| `code` | string | ✅ | 房型代碼 | `"DS"` |
| `startNumber` | integer | ✅ | 起始房號 | `101` |
| `quantity` | integer | ✅ | 房間數量 | `10` |
| `state` | integer | ❌ | 狀態 (1:啟用, 0:停用, -2:刪除) | `1` |
| `description` | string | ❌ | 房型描述 | `"寬敞舒適的單人房"` |
| `images` | array | ❌ | 房型圖片陣列 | 見下方 images 物件說明 |
| `features` | array | ✅ | 房型特色陣列 | `["冷暖空調", "獨立衛浴"]` |
| `prices` | array | ✅ | 價格與分類設定（依寵物分類設定不同價格），請使用 `/api/admin/service/category/search` API 取得所有分類 | 見下方 prices 物件說明 |
| `startDate` | string | ❌ | 開放日期 (YYYY-MM-DD) | `"2025-01-01"` |
| `endDate` | string | ❌ | 結束日期 (YYYY-MM-DD) | `"2025-12-31"` |
| `closedDates` | array | ❌ | 休息日期陣列 | `["2025-02-10", "2025-02-11"]` |

**images 物件結構**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `path` | string | ✅ | 圖片路徑 | `"/storage/merchants/example/room-type-1.jpg"` |
| `sort` | integer | ❌ | 圖片排序（數字越小越前面，預設為 0） | `0` |

**prices 物件結構**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `sizeId` | integer | ✅ | 寵物體型分類 ID（服務類別 ID） | `1` |
| `price` | number | ❌ | 實際售價 | `1000` |
| `basePrice` | number | ❌ | 原價（劃線價） | `1200` |
| `extraPrice` | number | ❌ | 加購第二隻寵物的實際售價 | `500` |
| `extraBasePrice` | number | ❌ | 加購第二隻寵物的原價（劃線價） | `700` |
| `state` | integer | ❌ | 狀態 (1:啟用, 0:停用) | `1` |

**注意**: 在回應中，`prices` 陣列中的每個物件會額外包含 `categoryTitle` 欄位，對應 `sizeId` 的服務分類標題（寵物體型名稱）。

**參數說明**:
- `sizeId`: 必須是商家已建立的寵物分類 ID（對應寵物體型，如小型犬、中型犬、大型犬等）
- `price` / `basePrice`: 單一寵物的住宿價格，`price` 為實際售價，`basePrice` 可用於顯示原價（劃線價）
- `extraPrice` / `extraBasePrice`: 當房間住多隻寵物時，第二隻起的加價設定

**請求範例**:
```json
{
    "id": null,
    "productId": "uuid-product-id",
    "title": "豪華單人房",
    "enTitle": "Deluxe Single",
    "subTitle": "適合小型犬",
    "maxPets": 1,
    "code": "DS",
    "startNumber": 101,
    "quantity": 10,
    "state": 1,
    "description": "寬敞舒適的單人房，配備冷暖空調",
    "images": [
        {
            "path": "/storage/merchants/example/room-type-1.jpg",
            "sort": 0
        },
        {
            "path": "/storage/merchants/example/room-type-2.jpg",
            "sort": 1
        }
    ],
    "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
    "prices": [
        {
            "sizeId": 1,
            "price": 1000,
            "basePrice": 1000,
            "extraPrice": 500,
            "extraBasePrice": 500,
            "state": 1
        },
        {
            "sizeId": 2,
            "price": 1500,
            "basePrice": 1500,
            "extraPrice": 700,
            "extraBasePrice": 700,
            "state": 1
        }
    ],
    "startDate": "2025-01-01",
    "endDate": "2025-12-31",
    "closedDates": ["2025-02-10", "2025-02-11"]
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": {
        "id": 1,
        "title": "豪華單人房",
        "enTitle": "Deluxe Single",
        "subTitle": "適合小型犬",
        "maxPets": 1,
        "code": "DS",
        "startNumber": 101,
        "quantity": 10,
        "state": 1,
        "description": "寬敞舒適的單人房，配備冷暖空調",
        "images": [
            {
                "path": "/storage/merchants/example/room-type-1.jpg",
                "sort": 0
            },
            {
                "path": "/storage/merchants/example/room-type-2.jpg",
                "sort": 1
            }
        ],
        "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
        "prices": [
            {
                "id": 1,
                "roomTypeId": 1,
                "sizeId": 1,
                "categoryTitle": "小型犬",
                "price": 1000,
                "basePrice": 1000,
                "extraPrice": 500,
                "extraBasePrice": 500,
                "state": 1
            },
            {
                "id": 2,
                "roomTypeId": 1,
                "sizeId": 2,
                "categoryTitle": "中型犬",
                "price": 1500,
                "basePrice": 1500,
                "extraPrice": 700,
                "extraBasePrice": 700,
                "state": 1
            }
        ],
        "startDate": "2025-01-01",
        "endDate": "2025-12-31",
        "closedDates": ["2025-02-10", "2025-02-11"]
    }
}
```

#### 取得分店房型列表
```http
GET /api/admin/hotel/{product_id}/room/type
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得 | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_SEARCH_SUCCESS",
    "message": "Hotel room type admin search success",
    "data": [
        {
            "id": 1,
            "merchant_id": "uuid-merchant-id",
            "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
            "title": "豪華單人房",
            "enTitle": "Deluxe Single",
            "subTitle": "適合小型犬",
            "maxPets": 1,
            "code": "DS",
            "startNumber": 101,
            "state": 1,
            "description": "寬敞舒適的單人房，配備冷暖空調",
            "images": [
                {
                    "path": "/storage/merchants/example/room-type-1.jpg",
                    "sort": 0
                },
                {
                    "path": "/storage/merchants/example/room-type-2.jpg",
                    "sort": 1
                }
            ],
            "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
            "priceStartAt": 1000,
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z"
        },
        {
            "id": 2,
            "merchant_id": "uuid-merchant-id",
            "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
            "title": "豪華雙人房",
            "enTitle": "Deluxe Double",
            "subTitle": "適合中型犬",
            "maxPets": 2,
            "code": "DD",
            "startNumber": 201,
            "state": 1,
            "description": "寬敞舒適的雙人房，配備冷暖空調",
            "images": [
                {
                    "path": "/storage/merchants/example/room-type-3.jpg",
                    "sort": 0
                }
            ],
            "features": ["冷暖空調", "獨立衛浴", "24小時監控", "遊戲區"],
            "priceStartAt": 1500,
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z"
        }
    ]
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `status` | string | 回應狀態碼，成功時為 `HOTEL_ROOM_TYPE_ADMIN_SEARCH_SUCCESS` |
| `message` | string | 回應訊息 |
| `data` | array | 房型列表陣列 |
| `data[].id` | integer | 房型ID |
| `data[].merchant_id` | string | 商家ID |
| `data[].product_id` | string | 分店ID |
| `data[].title` | string | 房型名稱 |
| `data[].enTitle` | string\|null | 英文房型名稱 |
| `data[].subTitle` | string\|null | 副標題 |
| `data[].maxPets` | integer | 最大寵物數量 |
| `data[].code` | string | 房型代碼 |
| `data[].startNumber` | integer | 起始房號 |
| `data[].state` | integer | 狀態 (1:啟用, 0:停用, -2:刪除) |
| `data[].description` | string\|null | 房型描述 |
| `data[].images` | array | 房型圖片陣列 |
| `data[].images[].path` | string | 圖片路徑 |
| `data[].images[].sort` | integer | 圖片排序 |
| `data[].features` | array | 房型特色陣列 |
| `data[].priceStartAt` | number\|null | 房型起始價格（所有啟用價格中的最低價） |

**錯誤回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_SEARCH_FAIL",
    "message": "Hotel room type admin search fail",
    "data": null
}
```

---

#### 房型管理 API（RESTful）

以下為 RESTful 風格的房型 API，分店ID（`product_id`）由 URL 路徑傳入，支援分頁列表、部分更新（PATCH），與上述舊版 API 可並存。

**中介層**: `hasMerchant`, `expired`, `admin`

##### 建立房型

```http
POST /api/admin/hotel/{product_id}/room-type
Content-Type: application/json
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `title` | string | ✅ | 房型名稱 | `"豪華單人房"` |
| `enTitle` | string | ❌ | 英文房型名稱 | `"Deluxe Single"` |
| `subTitle` | string | ❌ | 副標題 | `"適合小型犬"` |
| `maxPets` | integer | ✅ | 最大寵物數量 | `1` |
| `code` | string | ✅ | 房型代碼 | `"DS"` |
| `startNumber` | integer | ✅ | 起始房號 | `101` |
| `quantity` | integer | ✅ | 房間數量 | `10` |
| `state` | integer | ❌ | 狀態 (1:啟用, 0:停用, -2:刪除)，預設 1 | `1` |
| `description` | string | ❌ | 房型描述 | `"寬敞舒適的單人房"` |
| `images` | array | ❌ | 房型圖片陣列 | `[{"path": "/storage/...", "sort": 0}]` |
| `features` | array | ✅ | 房型特色陣列 | `["冷暖空調", "獨立衛浴"]` |
| `prices` | array | ✅ | 價格與分類設定 | 見 [新增/編輯房型](#新增編輯房型) 之 prices 說明 |
| `startDate` | string | ❌ | 開放開始日 (YYYY-MM-DD) | `"2025-01-01"` |
| `endDate` | string | ❌ | 開放結束日 (YYYY-MM-DD) | `"2025-12-31"` |
| `closedDates` | array | ❌ | 休息日期陣列 | `["2025-02-10", "2025-02-11"]` |

**請求範例**:
```json
{
    "title": "豪華單人房",
    "enTitle": "Deluxe Single",
    "subTitle": "適合小型犬",
    "maxPets": 1,
    "code": "DS",
    "startNumber": 101,
    "quantity": 10,
    "state": 1,
    "description": "寬敞舒適的單人房",
    "images": [
        {"path": "/storage/merchants/example/room-type-1.jpg", "sort": 0}
    ],
    "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
    "prices": [
        {"sizeId": 1, "price": 1000, "basePrice": 1000, "extraPrice": 500, "extraBasePrice": 500, "state": 1},
        {"sizeId": 2, "price": 1500, "basePrice": 1500, "extraPrice": 700, "extraBasePrice": 700, "state": 1}
    ]
}
```

**回應範例** (201 Created):
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_CREATE_SUCCESS",
    "code": 201,
    "message": "Hotel room type admin create success",
    "data": {
        "id": 1,
        "merchant_id": "uuid-merchant-id",
        "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "title": "豪華單人房",
        "enTitle": "Deluxe Single",
        "subTitle": "適合小型犬",
        "maxPets": 1,
        "code": "DS",
        "startNumber": 101,
        "quantity": 10,
        "state": 1,
        "description": "寬敞舒適的單人房",
        "images": [{"path": "/storage/merchants/example/room-type-1.jpg", "sort": 0}],
        "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
        "categories": ["小型犬", "中型犬"],
        "prices": [
            {"sizeId": 1, "categoryTitle": "小型犬", "price": 1000, "basePrice": 1000, "extraPrice": 500, "extraBasePrice": 500, "state": 1},
            {"sizeId": 2, "categoryTitle": "中型犬", "price": 1500, "basePrice": 1500, "extraPrice": 700, "extraBasePrice": 700, "state": 1}
        ],
        "priceStartAt": 1000,
        "created_at": "2026-03-19T10:00:00+08:00",
        "updated_at": "2026-03-19T10:00:00+08:00"
    }
}
```

##### 取得房型列表（分頁）

```http
GET /api/admin/hotel/{product_id}/room-types
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**請求參數（Query String）**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `page` | integer | ❌ | 頁碼（預設：1） | `1` |
| `perPage` | integer | ❌ | 每頁筆數（預設：15，最大：100） | `15` |
| `state` | integer | ❌ | 狀態篩選 (0, 1, -2) | `1` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room-types?page=1&perPage=15&state=1" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_INDEX_SUCCESS",
    "code": 200,
    "message": "Hotel room type admin index success",
    "data": {
        "items": [
            {
                "id": 1,
                "merchant_id": "uuid-merchant-id",
                "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
                "title": "豪華單人房",
                "enTitle": "Deluxe Single",
                "subTitle": "適合小型犬",
                "maxPets": 1,
                "code": "DS",
                "startNumber": 101,
                "quantity": 10,
                "state": 1,
                "description": "寬敞舒適的單人房",
                "images": [{"path": "/storage/merchants/example/room-type-1.jpg", "sort": 0}],
                "features": ["冷暖空調", "獨立衛浴"],
                "categories": ["小型犬", "中型犬"],
                "priceStartAt": 1000,
                "created_at": "2026-03-19T10:00:00+08:00",
                "updated_at": "2026-03-19T10:00:00+08:00"
            }
        ],
        "pagination": {
            "page": 1,
            "perPage": 15,
            "total": 5,
            "lastPage": 1
        }
    }
}
```

##### 取得房型選項（供下拉選單使用）

```http
GET /api/admin/hotel/{product_id}/room-types/options
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**請求參數（Query String）**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `state` | integer | ❌ | 狀態篩選 (0, 1, -2)，未傳則預設僅回傳啟用（state=1） | `1` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room-types/options" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel room type admin options success",
    "data": [
        {
            "value": 1,
            "label": "豪華單人房",
            "categories": ["小型犬", "中型犬"]
        },
        {
            "value": 2,
            "label": "經濟雙人房",
            "categories": ["小型犬"]
        }
    ]
}
```

**回應欄位說明（data 陣列內每個物件）**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `value` | integer | 房型ID |
| `label` | string | 房型名稱 |
| `categories` | array | 該房型對應的服務分類名稱陣列（來自價格規則的寵物體型分類） |

##### 取得單筆房型

```http
GET /api/admin/hotel/{product_id}/room-type/{roomTypeId}
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `roomTypeId` | integer | ✅ | 房型ID | `1` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room-type/1" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_GET_ITEM_SUCCESS",
    "code": 200,
    "message": "Hotel room type admin get item success",
    "data": {
        "id": 1,
        "merchant_id": "uuid-merchant-id",
        "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "title": "豪華單人房",
        "enTitle": "Deluxe Single",
        "subTitle": "適合小型犬",
        "maxPets": 1,
        "code": "DS",
        "startNumber": 101,
        "quantity": 10,
        "state": 1,
        "description": "寬敞舒適的單人房",
        "images": [{"path": "/storage/merchants/example/room-type-1.jpg", "sort": 0}],
        "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
        "categories": ["小型犬", "中型犬"],
        "prices": [
            {"sizeId": 1, "categoryTitle": "小型犬", "price": 1000, "basePrice": 1000, "extraPrice": 500, "extraBasePrice": 500, "state": 1},
            {"sizeId": 2, "categoryTitle": "中型犬", "price": 1500, "basePrice": 1500, "extraPrice": 700, "extraBasePrice": 700, "state": 1}
        ],
        "priceStartAt": 1000,
        "created_at": "2026-03-19T10:00:00+08:00",
        "updated_at": "2026-03-19T10:00:00+08:00"
    }
}
```

**回應欄位說明**：`categories` 為該房型支援的寵物體型分類名稱陣列（從 prices 的 `sizeId` 對應 ServiceCategory 取得，去重後回傳）。

**錯誤回應** (404):
```json
{
    "status": "HOTEL_ROOM_TYPE_NOT_FOUND",
    "code": 404,
    "message": "Hotel room type not found",
    "data": null
}
```

##### 部分更新房型

```http
PATCH /api/admin/hotel/{product_id}/room-type/{roomTypeId}
Content-Type: application/json
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `roomTypeId` | integer | ✅ | 房型ID | `1` |

**請求參數**（可部分更新，傳送欲變更欄位即可）:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `state` | integer | ❌ | 狀態 (0, 1, -2) | `1` |
| `title` | string | ❌ | 房型名稱 | `"豪華單人房"` |
| `enTitle` | string | ❌ | 英文房型名稱 | `"Deluxe Single"` |
| `subTitle` | string | ❌ | 副標題 | `"適合小型犬"` |
| `maxPets` | integer | ❌ | 最大寵物數量 | `1` |
| `code` | string | ❌ | 房型代碼 | `"DS"` |
| `startNumber` | integer | ❌ | 起始房號 | `101` |
| `description` | string | ❌ | 房型描述 | `"寬敞舒適的單人房"` |
| `images` | array | ❌ | 房型圖片陣列 | `[{"path": "/storage/...", "sort": 0}]` |
| `features` | array | ❌ | 房型特色陣列 | `["冷暖空調", "獨立衛浴"]` |

**請求範例**（僅變更狀態）:
```json
{
    "state": 1
}
```

**使用說明**:
- 支援部分更新，可只傳送欲變更的欄位
- 可單獨更新 `state`（例如啟用/停用房型），無需傳送其他欄位
- `prices`、`quantity` 不支援 PATCH，完整編輯請使用舊版 `POST /api/admin/hotel/room/type/store`

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_UPDATE_SUCCESS",
    "code": 200,
    "message": "Hotel room type admin update success",
    "data": {
        "id": 1,
        "merchant_id": "uuid-merchant-id",
        "productId": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "title": "豪華單人房",
        "enTitle": "Deluxe Single",
        "subTitle": "適合小型犬",
        "maxPets": 1,
        "code": "DS",
        "startNumber": 101,
        "quantity": 10,
        "state": 1,
        "description": "寬敞舒適的單人房",
        "images": [],
        "features": ["冷暖空調", "獨立衛浴"],
        "categories": ["小型犬", "中型犬"],
        "prices": [...],
        "priceStartAt": 1000,
        "created_at": "2026-03-19T10:00:00+08:00",
        "updated_at": "2026-03-19T10:30:00+08:00"
    }
}
```

##### 刪除房型

```http
DELETE /api/admin/hotel/{product_id}/room-type/{roomTypeId}
Authorization: Bearer {token}
```

**說明**: 軟刪除，將房型的 `state` 設為 `-2`，記錄不會從資料庫移除。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `roomTypeId` | integer | ✅ | 房型ID | `1` |

**請求範例**:
```bash
curl -X DELETE "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room-type/1" \
  -H "Authorization: Bearer your_token_here"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_DELETE_SUCCESS",
    "code": 200,
    "message": "Hotel room type admin delete success",
    "data": null
}
```

##### 取得房型不開放日期

```http
GET /api/admin/hotel/{product_id}/room-type/{room_type_id}/closedDates
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `room_type_id` | integer | ✅ | 房型ID | `1` |

**請求參數（Query String）**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `startDate` | string | ✅ | 查詢開始日 (YYYY-MM-DD) | `"2026-01-01"` |
| `endDate` | string | ✅ | 查詢結束日 (YYYY-MM-DD) | `"2026-02-01"` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room-type/1/closedDates?startDate=2026-01-01&endDate=2026-02-01" \
  -H "Authorization: Bearer your_token_here"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_GET_CLOSED_DATES_SUCCESS",
    "code": 200,
    "message": "Hotel room type admin get closed dates success",
    "data": {
        "roomTypeId": 1,
        "title": "豪華單人房",
        "state": 1,
        "closedDates": ["2026-01-02"]
    }
}
```

**說明**: 不開放的日期是指 `hotels_room_types_schedules` 表中 `state` 不等於 `1` 的記錄。

---

#### 取得房型詳細資料（舊版）
```http
GET /api/admin/hotel/{product_id}/room/type/{id}
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得 | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `id` | integer | ✅ | 房型ID | `1` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/1" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_GET_ITEM_SUCCESS",
    "message": "Hotel room type admin get item success",
    "data": {
        "id": 1,
        "merchant_id": 123,
        "product_id": "uuid-product-id",
        "title": "豪華單人房",
        "enTitle": "Deluxe Single",
        "subTitle": "適合小型犬",
        "maxPets": 1,
        "code": "DS",
        "startNumber": 101,
        "quantity": 10,
        "state": 1,
        "description": "寬敞舒適的單人房，配備冷暖空調",
        "images": [
            {
                "path": "/storage/merchants/example/room-type-1.jpg",
                "sort": 0
            },
            {
                "path": "/storage/merchants/example/room-type-2.jpg",
                "sort": 1
            }
        ],
        "features": ["冷暖空調", "獨立衛浴", "24小時監控"],
        "priceStartAt": 1000,
        "created_at": "2025-01-01T00:00:00.000000Z",
        "updated_at": "2025-01-01T00:00:00.000000Z",
        "prices": [
            {
                "id": 1,
                "roomTypeId": 1,
                "sizeId": 1,
                "categoryTitle": "小型犬",
                "price": 1000,
                "basePrice": 1200,
                "extraPrice": 500,
                "extraBasePrice": 700,
                "state": 1,
                "created_at": "2025-01-01T00:00:00.000000Z",
                "updated_at": "2025-01-01T00:00:00.000000Z"
            },
            {
                "id": 2,
                "roomTypeId": 1,
                "sizeId": 2,
                "categoryTitle": "中型犬",
                "price": 1500,
                "basePrice": 1800,
                "extraPrice": 700,
                "extraBasePrice": 900,
                "state": 1,
                "created_at": "2025-01-01T00:00:00.000000Z",
                "updated_at": "2025-01-01T00:00:00.000000Z"
            }
        ],
        "rooms": [
            {
                "id": 1,
                "product_id": "uuid-product-id",
                "roomTypeId": 1,
                "code": "DS",
                "number": 101,
                "state": 1,
                "created_at": "2025-01-01T00:00:00.000000Z",
                "updated_at": "2025-01-01T00:00:00.000000Z"
            },
            {
                "id": 2,
                "product_id": "uuid-product-id",
                "roomTypeId": 1,
                "code": "DS",
                "number": 102,
                "state": 1,
                "created_at": "2025-01-01T00:00:00.000000Z",
                "updated_at": "2025-01-01T00:00:00.000000Z"
            }
        ]
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 房型ID |
| `merchant_id` | integer | 商家ID |
| `product_id` | string | 分店ID |
| `title` | string | 房型名稱 |
| `enTitle` | string\|null | 英文房型名稱 |
| `subTitle` | string\|null | 副標題 |
| `maxPets` | integer | 最大寵物數量 |
| `code` | string | 房型代碼 |
| `startNumber` | integer | 起始房號 |
| `quantity` | integer | 房間數量 |
| `state` | integer | 狀態 (1:啟用, 0:停用, -2:刪除) |
| `description` | string\|null | 房型描述 |
| `images` | array | 房型圖片陣列 |
| `images[].path` | string | 圖片路徑 |
| `images[].sort` | integer | 圖片排序 |
| `features` | array | 房型特色陣列 |
| `priceStartAt` | number\|null | 房型起始價格（所有啟用價格中的最低價） |
| `prices` | array | 該房型的所有價格設定 |
| `prices[].id` | integer | 價格ID |
| `prices[].sizeId` | integer | 寵物體型分類ID |
| `prices[].categoryTitle` | string | 寵物體型分類標題（對應 sizeId 的服務分類標題） |
| `prices[].price` | number | 實際售價 |
| `prices[].basePrice` | number | 原價（劃線價） |
| `prices[].extraPrice` | number | 加購第二隻寵物的實際售價 |
| `prices[].extraBasePrice` | number | 加購第二隻寵物的原價（劃線價） |
| `prices[].state` | integer | 狀態 (1:啟用, 0:停用) |
| `rooms` | array | 該房型下的所有房間 |
| `rooms[].id` | integer | 房間ID |
| `rooms[].number` | integer | 房號 |
| `rooms[].code` | string | 房間代碼 |
| `rooms[].state` | integer | 狀態 |

**錯誤回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_ITEM_NOT_EXIST",
    "message": "Hotel room type admin item not exist",
    "data": null
}
```

#### 是否開放房型預約
```http
POST /api/admin/hotel/room/type/state
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `ids` | array | ✅ | 房型ID陣列（可設定單個或多個） | `[1]` 或 `[1, 2, 3]` |
| `state` | integer | ✅ | 狀態值 | `0` (關) 或 `1` (開) |

**state 可選值**:

| 值 | 說明 |
|----|------|
| `0` | 停用 |
| `1` | 啟用 |
| `-2` | 刪除 |

**請求範例**:
```json
{
    "ids": [1],
    "state": 0
}
```

**回應範例**:
```json
{
    "status": "success",
    "message": "房型狀態已更新"
}
```

#### 刪除房型
```http
DELETE /api/admin/hotel/{product_id}/room/type/{id}
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得 | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `id` | integer | ✅ | 房型ID | `1` |

**說明**:
- 此 API 會將房型的 `state` 設為 `-2`（軟刪除），不會實際刪除資料庫中的記錄
- 刪除前會驗證房型是否屬於指定的分店和商家

**請求範例**:
```bash
curl -X DELETE "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/1" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**成功回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_DELETE_SUCCESS",
    "message": "Hotel room type admin delete success",
    "data": null
}
```

**失敗回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_DELETE_FAIL",
    "message": "Hotel room type admin delete fail",
    "data": null
}
```

**錯誤情況**:
- 房型不存在
- 房型不屬於指定的分店
- 房型不屬於當前商家

#### 取得房型不開放日期
```http
GET /api/admin/hotel/{product_id}/room/type/{room_type_id}/closedDates
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得 | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `room_type_id` | integer | ✅ | 房型ID | `1` |

**Query String 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `startDate` | string | ✅ | 開始日期 (格式: Y-m-d) | `"2026-01-01"` |
| `endDate` | string | ✅ | 結束日期 (格式: Y-m-d) | `"2026-02-01"` |

**說明**:
- 此 API 用於查詢指定日期區間內，房型不開放的日期列表
- 不開放的日期是指 `hotels_room_types_schedules` 表中 `state` 不等於 `1` 的記錄
- 返回的日期格式為 `Y-m-d` (例如: `"2026-01-02"`)

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/1/closedDates?startDate=2026-01-01&endDate=2026-02-01" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**成功回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_GET_ITEM_SUCCESS",
    "message": "Hotel room type admin get item success",
    "data": {
        "closedDates": ["2026-01-02"],
        "title": "精品房",
        "state": 1
    }
}
```

**失敗回應範例**:
```json
{
    "status": "HOTEL_ROOM_TYPE_ADMIN_GET_ITEM_FAIL",
    "message": "Hotel room type admin get item fail",
    "data": null
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `closedDates` | array | 不開放日期陣列，格式為 `Y-m-d` |
| `closedDates[]` | string | 不開放日期 (格式: Y-m-d) |
| `title` | string | 房型名稱 |
| `state` | integer | 房型狀態 (1:啟用, 0:停用, -2:刪除) |

**錯誤情況**:
- 缺少 `startDate` 或 `endDate` 參數
- 房型不存在
- 房型不屬於指定的分店
- 房型不屬於當前商家

### 房間管理 API

#### 搜尋房型（房間列表）
```http
POST /api/admin/hotel/room/search
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `productId` | string | ❌ | 分店ID，提供時僅回傳該分店的房間；不提供時回傳所有符合條件的房間 | `"uuid-product-id"` |
| `title` | string | ❌ | 房型標題關鍵字 | - |
| `state` | integer | ❌ | 狀態 (0=停用, 1=啟用, -2=刪除) | `1` |
| `limit` | integer | ❌ | 每頁筆數 | - |
| `order_by` | string | ❌ | 排序欄位 | - |
| `order` | string | ❌ | 排序方向 (`asc` / `desc`) | - |
| `paginate` | integer | ❌ | 是否分頁 (0 / 1) | - |

**請求範例**:
```json
{
    "productId": "56f62486-eb81-435a-be63-04d519f20d5f"
}
```

**說明**: 依 `productId` 篩選時，僅回傳該分店底下的房間列表；未帶 `productId` 時依其他條件（如 `title`、`state`）搜尋。

---

#### 查詢現有房型及房況
```http
POST /api/admin/hotel/room/availability
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `productId` | string | ✅ | 分店ID | `"uuid-product-id"` |
| `startDate` | string | ✅ | 指定開始日期 (YYYY-MM-DD) | `"2025-01-01"` |
| `endDate` | string | ✅ | 指定結束日期 (YYYY-MM-DD) | `"2025-01-07"` |

**請求範例**:
```json
{
    "productId": "uuid-product-id",
    "startDate": "2025-01-01",
    "endDate": "2025-01-07"
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": [
        {
            "id": 1,
            "name": "小捲尾精品房",
            "maxPets": 3,
            "categories": ["貓", "狗"],
            "images": [
                {
                    "path": "/storage/merchants/example/room-type-1.jpg",
                    "sort": 0
                },
                {
                    "path": "/storage/merchants/example/room-type-2.jpg",
                    "sort": 1
                }
            ],
            "days": [
                {
                    "date": "2025-05-05",
                    "available": true,
                    "priceStartAt": 1500
                },
                {
                    "date": "2025-05-06",
                    "available": true,
                    "priceStartAt": 1500
                },
                {
                    "date": "2025-05-07",
                    "available": false,
                    "priceStartAt": 1500
                }
            ],
            "rooms": [
                {
                    "id": 1,
                    "name": "小捲尾精品房01",
                    "days": [
                        {
                            "date": "2025-05-05",
                            "available": true,
                            "priceStartAt": 1500
                        },
                        {
                            "date": "2025-05-06",
                            "available": true,
                            "priceStartAt": 1500
                        },
                        {
                            "date": "2025-05-07",
                            "available": false,
                            "priceStartAt": 1500
                        }
                    ]
                },
                {
                    "id": 2,
                    "name": "小捲尾精品房02",
                    "days": [
                        {
                            "date": "2025-05-05",
                            "available": true,
                            "priceStartAt": 1500
                        },
                        {
                            "date": "2025-05-06",
                            "available": false,
                            "priceStartAt": 1500
                        },
                        {
                            "date": "2025-05-07",
                            "available": true,
                            "priceStartAt": 1500
                        }
                    ]
                }
            ]
        },
        {
            "id": 2,
            "name": "標準雙人房",
            "maxPets": 2,
            "categories": ["狗"],
            "images": [
                {
                    "path": "/storage/merchants/example/room-type-3.jpg",
                    "sort": 0
                }
            ],
            "days": [
                {
                    "date": "2025-05-05",
                    "available": true,
                    "priceStartAt": 2000
                },
                {
                    "date": "2025-05-06",
                    "available": true,
                    "priceStartAt": 2000
                },
                {
                    "date": "2025-05-07",
                    "available": true,
                    "priceStartAt": 2000
                }
            ],
            "rooms": [
                {
                    "id": 3,
                    "name": "標準雙人房01",
                    "days": [
                        {
                            "date": "2025-05-05",
                            "available": true,
                            "priceStartAt": 2000
                        },
                        {
                            "date": "2025-05-06",
                            "available": true,
                            "priceStartAt": 2000
                        },
                        {
                            "date": "2025-05-07",
                            "available": true,
                            "priceStartAt": 2000
                        }
                    ]
                }
            ]
        }
    ]
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 房型ID |
| `name` | string | 房型名稱 |
| `maxPets` | integer | 該房型最大寵物數量 |
| `categories` | array | 寵物類別陣列（從價格設定中提取，例如：["貓", "狗"]） |
| `images` | array | 房型圖片陣列 |
| `images[].path` | string | 圖片路徑 |
| `images[].sort` | integer | 圖片排序（數字越小越前面） |
| `days` | array | 房型層級的日期可用性資訊（包含查詢日期範圍內的所有日期） |
| `days[].date` | string | 日期 (YYYY-MM-DD) |
| `days[].available` | boolean | 該日期是否有任何房間開放 (true: 有房間開放, false: 無房間開放) |
| `days[].priceStartAt` | integer | 該日期的起始價格（整數） |
| `rooms` | array | 該房型下的所有房間 |
| `rooms[].id` | integer | 房間ID |
| `rooms[].name` | string | 房間名稱（房型名稱 + 兩位數房號，例如："小捲尾精品房01"） |
| `rooms[].days` | array | 該房間在查詢日期範圍內的排班資訊（包含查詢日期範圍內的所有日期） |
| `rooms[].days[].date` | string | 日期 (YYYY-MM-DD) |
| `rooms[].days[].available` | boolean | 該房間在該日期是否開放 (true: 開放, false: 休息) |
| `rooms[].days[].priceStartAt` | integer | 該日期的起始價格（整數） |

**注意事項**:
- `days` 陣列會包含從 `startDate` 到 `endDate` 之間的所有日期（包含開始和結束日期）
- 即使某個日期沒有排班記錄，也會在 `days` 陣列中出現，`available` 為 `false`
- `priceStartAt` 為整數類型，小數會四捨五入
- 如果價格為 `null`，`priceStartAt` 會返回 `0`

#### 取得房間不開放日期
```http
GET /api/admin/hotel/{product_id}/room/{room_id}/closedDates
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID，使用寵物住宿設定中的 `productSetting[].productId`，分店列表請使用 `/api/admin/product/search` 分店列表 API 取得 | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `room_id` | integer | ✅ | 房間ID | `1` |

**Query String 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `startDate` | string | ✅ | 開始日期 (格式: Y-m-d) | `"2026-01-01"` |
| `endDate` | string | ✅ | 結束日期 (格式: Y-m-d) | `"2026-02-01"` |

**說明**:
- 此 API 用於查詢指定日期區間內，房間不開放的日期列表
- 不開放的日期是指 `hotels_rooms_schedules` 表中 `state` 不等於 `1` 的記錄
- 返回的日期格式為 `Y-m-d` (例如: `"2026-01-02"`)
- 房間名稱格式為：房型名稱 + 房間號碼（補零到2位數），例如："精品房001"

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/1/closedDates?startDate=2026-01-01&endDate=2026-02-01" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**成功回應範例**:
```json
{
    "status": "HOTEL_ROOM_ADMIN_GET_CLOSED_DATES_SUCCESS",
    "message": "Hotel room admin get closed dates success",
    "data": {
        "closedDates": ["2026-01-02"],
        "title": "精品房001",
        "state": 1
    }
}
```

**失敗回應範例**:
```json
{
    "status": "HOTEL_ROOM_ADMIN_GET_CLOSED_DATES_FAIL",
    "message": "Hotel room admin get closed dates fail",
    "data": null
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `closedDates` | array | 不開放日期陣列，格式為 `Y-m-d` |
| `closedDates[]` | string | 不開放日期 (格式: Y-m-d) |
| `title` | string | 房間名稱（房型名稱 + 房間號碼，例如："精品房001"） |
| `state` | integer | 房間狀態 (1:啟用, 0:停用, -2:刪除) |

**錯誤情況**:
- 缺少 `startDate` 或 `endDate` 參數
- 日期格式錯誤
- 日期區間超過 90 天
- 房間不存在
- 房間不屬於指定的分店
- 房間不屬於當前商家

### 價格規則管理 API

#### 新增/編輯價格規則
```http
POST /api/admin/hotel/price/rule/store
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | integer | ❌ | 規則ID (編輯時必填，新增時為null) | `1` |
| `productId` | string | ✅ | 分店ID | `"uuid-product-id"` |
| `roomTypeIds` | array | ✅ | 適用房型ID陣列 | `[1, 2, 3]` |
| `title` | string | ✅ | 規則名稱 | `"春節加價"` |
| `type` | string | ✅ | 規則類型 | `"stay"\|"period"` |
| `state` | integer | ❌ | 狀態 (1:啟用, 0:停用, -2:刪除) | `1` |
| `valueType` | string | ✅ | 數值類型 | `"%"` |
| `operator` | string | ✅ | 操作符 | `"+"` |
| `value` | number | ✅ | 數值 (百分比或固定金額) | `20` |
| `minNight` | integer | ❌ | 最少住宿天數（當 `type="stay"` 時使用） | `3` |
| `validFrom` | string | ✅* | 開始日（`type="period"` 時必填，YYYY-MM-DD） | `"2026-01-10"` |
| `validUntil` | string | ✅* | 到期日（`type="period"` 時必填，YYYY-MM-DD） | `"2026-01-31"` |

\* 當 `type="period"` 時，`validFrom` 與 `validUntil` 為必填。

**參數說明**:

**type 規則類型**:
| 值 | 說明 |
|----|------|
| `"stay"` | 連續住宿變價 |
| `"period"` | 日期區間變價 |

**valueType 數值類型**:
| 值 | 說明 | 範例 |
|----|------|------|
| `"%"` | 百分比調價 | +20% 表示加價20% |
| `"fixed"` | 固定金額調價 | +500 表示加價500元 |

**operator 操作符**:
| 值 | 說明 |
|----|------|
| `"+"` | 加價 |
| `"-"` | 減價 |

**請求範例**:
```json
{
    "id": null,
    "productId": "uuid-product-id",
    "roomTypeIds": [1, 2, 3],
    "title": "連續住3天",
    "type": "stay",
    "state": 1,
    "valueType": "%",
    "operator": "+",
    "value": 20,
    "minNight": 3
}
```

#### 搜尋價格規則
```http
POST /api/admin/hotel/price/rule/search
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `productId` | string | ✅ | 分店ID | `"uuid-product-id"` |

**請求範例**:
```json
{
    "productId": "uuid-product-id"
}
```

**回應範例**:
```json
{
    "code": 200,
    "message": "Hotel price rule admin search success",
    "status": "HOTEL_PRICE_RULE_ADMIN_SEARCH_SUCCESS",
    "data": {
        "items": [
            {
                "id": 1,
                "merchantId": 123,
                "productId": "uuid-product-id",
                "title": "連續住3天",
                "type": "stay",
                "state": 1,
                "valueType": "%",
                "operator": "+",
                "value": 20,
                "minNight": 3,
                "validFrom": null,
                "validUntil": null,
                "createdAt": "2025-01-01T00:00:00.000000Z",
                "updatedAt": "2025-01-01T00:00:00.000000Z",
                "roomTypes": [
                    {
                        "id": 1,
                        "title": "豪華單人房",
                        "code": "DS"
                    },
                    {
                        "id": 2,
                        "title": "標準雙人房",
                        "code": "STD"
                    }
                ]
            },
            {
                "id": 2,
                "merchantId": 123,
                "productId": "uuid-product-id",
                "title": "春節加價",
                "type": "period",
                "state": 1,
                "valueType": "%",
                "operator": "+",
                "value": 30,
                "minNight": null,
                "validFrom": "2025-02-09",
                "validUntil": "2025-02-17",
                "createdAt": "2025-01-01T00:00:00.000000Z",
                "updatedAt": "2025-01-01T00:00:00.000000Z",
                "roomTypes": [
                    {
                        "id": 1,
                        "title": "豪華單人房",
                        "code": "DS"
                    }
                ]
            }
        ],
        "records": 2
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `items` | array | 價格規則列表 |
| `items[].id` | integer | 規則ID |
| `items[].merchantId` | string | 商家ID |
| `items[].productId` | string | 分店ID |
| `items[].title` | string | 規則名稱 |
| `items[].type` | string | 規則類型 (`"stay"`: 連續住宿變價, `"period"`: 日期區間變價) |
| `items[].state` | integer | 狀態 (1:啟用, 0:停用, -2:刪除) |
| `items[].valueType` | string | 數值類型 (`"%": 百分比調價, `"fixed"`: 固定金額調價) |
| `items[].operator` | string | 操作符 (`"+": 加價, `"-": 減價) |
| `items[].value` | number | 數值（百分比或固定金額） |
| `items[].minNight` | integer\|null | 最少住宿天數（當 `type="stay"` 時有值） |
| `items[].validFrom` | string\|null | 開始日（當 `type="period"` 時有值，YYYY-MM-DD） |
| `items[].validUntil` | string\|null | 到期日（當 `type="period"` 時有值，YYYY-MM-DD） |
| `items[].isExpired` | boolean | 是否已過期（`type="stay"` 一律為 false；`type="period"` 時，超過 validUntil 為 true，未超過為 false） |
| `items[].roomTypes` | array | 適用的房型列表 |
| `items[].roomTypes[].id` | integer | 房型ID |
| `items[].roomTypes[].title` | string | 房型名稱 |
| `items[].roomTypes[].code` | string | 房型代碼 |
| `items[].createdAt` | string | 建立時間 (ISO 8601) |
| `items[].updatedAt` | string | 更新時間 (ISO 8601) |
| `records` | integer | 總筆數 |

#### 刪除價格規則
```http
POST /api/admin/hotel/price/rule/remove
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `ids` | array | ✅ | 規則ID  | `1` |

**請求範例**:
```json
{
    "ids": [1]
}
```

**回應範例**:
```json
{
    "code": 200,
    "message": "Hotel price rule admin delete success",
    "status": "HOTEL_PRICE_RULE_ADMIN_DELETE_SUCCESS",
    "data": null
}
```

### 價格規則管理 API（RESTful）

以下為 RESTful 風格的價格規則 API，分店ID（`product_id`）由 URL 路徑傳入，與上述舊版 API 可並存。

**中介層**: `hasMerchant`, `expired`, `admin`

#### 新增價格規則

```http
POST /api/admin/hotel/{product_id}/price-rule
Content-Type: application/json
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"550e8400-e29b-41d4-a716-446655440000"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `roomTypeIds` | array | ✅ | 適用房型ID陣列 | `[1, 2, 3]` |
| `title` | string | ✅ | 規則名稱 | `"連續住3天享折扣"` |
| `type` | string | ✅ | 規則類型 | `"stay"` \| `"period"` |
| `state` | integer | ❌ | 狀態 (1:啟用, 0:停用, -2:刪除)，預設 1 | `1` |
| `valueType` | string | ✅ | 數值類型 | `"%"` \| `"fixed"` |
| `operator` | string | ✅ | 操作符 | `"+"` \| `"-"` |
| `value` | number | ✅ | 數值 (百分比或固定金額) | `15` |
| `minNight` | integer | ❌ | 最少住宿天數（`type="stay"` 時使用） | `3` |
| `validFrom` | string | ✅* | 開始日（`type="period"` 時必填，YYYY-MM-DD） | `"2026-01-10"` |
| `validUntil` | string | ✅* | 到期日（`type="period"` 時必填，YYYY-MM-DD） | `"2026-01-31"` |

\* 當 `type="period"` 時，`validFrom` 與 `validUntil` 為必填。

**請求範例**（type=stay 連續住宿）:
```json
{
    "roomTypeIds": [1, 2, 3],
    "title": "連續住3天享折扣",
    "type": "stay",
    "state": 1,
    "valueType": "%",
    "operator": "-",
    "value": 15,
    "minNight": 3
}
```

**請求範例**（type=period 日期區間）:
```json
{
    "roomTypeIds": [1, 2, 3],
    "title": "春節加價",
    "type": "period",
    "state": 1,
    "valueType": "fixed",
    "operator": "+",
    "value": 200,
    "validFrom": "2026-01-10",
    "validUntil": "2026-01-31"
}
```

**回應範例** (200 OK):
```json
{
    "status": "HOTEL_PRICE_RULE_ADMIN_CREATE_SUCCESS",
    "code": 200,
    "message": "Hotel price rule admin create success",
    "data": {
        "id": 1,
        "merchantId": "merchant-uuid",
        "productId": "550e8400-e29b-41d4-a716-446655440000",
        "title": "連續住3天享折扣",
        "type": "stay",
        "state": 1,
        "valueType": "%",
        "operator": "-",
        "value": 15,
        "minNight": 3,
        "validFrom": null,
        "validUntil": null,
        "isExpired": false,
        "createdAt": "2026-03-18T10:00:00+08:00",
        "updatedAt": "2026-03-18T10:00:00+08:00",
        "roomTypes": [
            {"id": 1, "title": "豪華單人房", "code": "DS"},
            {"id": 2, "title": "標準雙人房", "code": "STD"}
        ]
    }
}
```

#### 取得價格規則列表

```http
GET /api/admin/hotel/{product_id}/price-rules
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"550e8400-e29b-41d4-a716-446655440000"` |

**請求參數（Query String）**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `page` | integer | ❌ | 頁碼（預設：1） | `1` |
| `perPage` | integer | ❌ | 每頁筆數（預設：15，最大：100） | `15` |
| `state` | integer | ❌ | 狀態篩選 (0, 1, -2) | `1` |
| `type` | string | ❌ | 類型篩選 | `"stay"` \| `"period"` |
| `operator` | string | ❌ | 操作符篩選 | `"+"`（加價）\| `"-"`（折扣） |
| `isExpired` | boolean | ❌ | 是否過期篩選。`true`：僅已過期；`false`：僅未過期（除 type=period 且 validUntil\<今日 外皆視為未過期） | `false` |

**請求範例**:
```http
GET /api/admin/hotel/550e8400-e29b-41d4-a716-446655440000/price-rules?page=1&perPage=15&state=1&operator=-&isExpired=false
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "HOTEL_PRICE_RULE_ADMIN_INDEX_SUCCESS",
    "code": 200,
    "message": "Hotel price rule admin index success",
    "data": {
        "items": [
            {
                "id": 1,
                "merchantId": "merchant-uuid",
                "productId": "550e8400-e29b-41d4-a716-446655440000",
                "title": "連續住3天享折扣",
                "type": "stay",
                "state": 1,
                "valueType": "%",
                "operator": "-",
                "value": 15,
                "minNight": 3,
                "validFrom": null,
                "validUntil": null,
                "isExpired": false,
                "createdAt": "2026-03-18T10:00:00+08:00",
                "updatedAt": "2026-03-18T10:00:00+08:00",
                "roomTypes": [
                    {"id": 1, "title": "豪華單人房", "code": "DS"}
                ]
            }
        ],
        "pagination": {
            "page": 1,
            "perPage": 15,
            "total": 5,
            "lastPage": 1
        }
    }
}
```

#### 取得單筆價格規則

```http
GET /api/admin/hotel/{product_id}/price-rule/{priceRuleID}
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"550e8400-e29b-41d4-a716-446655440000"` |
| `priceRuleID` | integer | ✅ | 價格規則ID | `1` |

**回應範例**:
```json
{
    "status": "HOTEL_PRICE_RULE_ADMIN_GET_ITEM_SUCCESS",
    "code": 200,
    "message": "Hotel price rule admin get item success",
    "data": {
        "id": 1,
        "merchantId": "merchant-uuid",
        "productId": "550e8400-e29b-41d4-a716-446655440000",
        "title": "連續住3天享折扣",
        "type": "stay",
        "state": 1,
        "valueType": "%",
        "operator": "-",
        "value": 15,
        "minNight": 3,
        "validFrom": null,
        "validUntil": null,
        "isExpired": false,
        "createdAt": "2026-03-18T10:00:00+08:00",
        "updatedAt": "2026-03-18T10:00:00+08:00",
        "roomTypes": [
            {"id": 1, "title": "豪華單人房", "code": "DS"}
        ]
    }
}
```

**錯誤回應** (404):
```json
{
    "status": "HOTEL_PRICE_RULE_NOT_FOUND",
    "code": 404,
    "message": "Hotel price rule not found",
    "data": null
}
```

#### 修改價格規則（部分更新）

```http
PATCH /api/admin/hotel/{product_id}/price-rule/{priceRuleID}
Content-Type: application/json
Authorization: Bearer {token}
```

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"550e8400-e29b-41d4-a716-446655440000"` |
| `priceRuleID` | integer | ✅ | 價格規則ID | `1` |

**請求參數**（可部分更新，傳送欲變更欄位即可）:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `state` | integer | ❌ | 狀態 (0, 1, -2) | `1` |
| `title` | string | ❌ | 規則名稱 | `"春節加價"` |
| `roomTypeIds` | array | ❌ | 適用房型ID陣列 | `[1, 2, 3]` |
| `type` | string | ❌ | 規則類型 | `"stay"` \| `"period"` |
| `valueType` | string | ❌ | 數值類型 | `"%"` \| `"fixed"` |
| `operator` | string | ❌ | 操作符 | `"+"` \| `"-"` |
| `value` | number | ❌ | 數值 | `20` |
| `minNight` | integer | ❌ | 最少住宿天數 | `3` |
| `validFrom` | string | ❌ | 開始日（`type="period"` 時使用，YYYY-MM-DD） | `"2026-01-10"` |
| `validUntil` | string | ❌ | 到期日（`type="period"` 時使用，YYYY-MM-DD） | `"2026-01-31"` |

**請求範例**（僅變更狀態）:
```json
{
    "state": 1
}
```

**使用說明**:
- 支援部分更新，可只傳送欲變更的欄位
- 可單獨更新 `state`（例如啟用/停用規則），無需傳送其他欄位

**回應範例**:
```json
{
    "status": "HOTEL_PRICE_RULE_ADMIN_UPDATE_SUCCESS",
    "code": 200,
    "message": "Hotel price rule admin update success",
    "data": {
        "id": 1,
        "merchantId": "merchant-uuid",
        "productId": "550e8400-e29b-41d4-a716-446655440000",
        "title": "連續住3天享折扣",
        "type": "stay",
        "state": 1,
        "valueType": "%",
        "operator": "-",
        "value": 15,
        "minNight": 3,
        "validFrom": null,
        "validUntil": null,
        "isExpired": false,
        "createdAt": "2026-03-18T10:00:00+08:00",
        "updatedAt": "2026-03-18T10:00:00+08:00",
        "roomTypes": [...]
    }
}
```

#### 刪除價格規則

```http
DELETE /api/admin/hotel/{product_id}/price-rule/{priceRuleID}
Authorization: Bearer {token}
```

**說明**: 軟刪除，將規則的 `state` 設為 `-2`，記錄不會從資料庫移除。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"550e8400-e29b-41d4-a716-446655440000"` |
| `priceRuleID` | integer | ✅ | 價格規則ID | `1` |

**回應範例**:
```json
{
    "status": "HOTEL_PRICE_RULE_ADMIN_DELETE_SUCCESS",
    "code": 200,
    "message": "Hotel price rule admin delete success",
    "data": null
}
```

**錯誤回應** (404):
```json
{
    "status": "HOTEL_PRICE_RULE_NOT_FOUND",
    "code": 404,
    "message": "Hotel price rule not found",
    "data": null
}
```

### 住宿排班管理 API

#### 房況管理
```http
POST /api/admin/hotel/room/schedule/store
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `roomTypeId` | integer | ❌ | 房型ID (與roomIds二選一) | `1` |
| `roomIds` | array | ❌ | 房間ID陣列 (與roomTypeId二選一) | `[1, 2, 3]` |
| `startDate` | string | ✅ | 開始日期 (YYYY-MM-DD) | `"2025-01-01"` |
| `endDate` | string | ✅ | 結束日期 (YYYY-MM-DD) | `"2025-01-31"` |
| `closedDates` | array | ❌ | 休息日期陣列 | `["2025-01-15", "2025-01-16"]` |

**參數說明**:
- `roomTypeId` 與 `roomIds` 二選一必填
- 使用 `roomTypeId` 時，會對該房型下的所有房間設定排班
- 使用 `roomIds` 時，僅對指定的房間設定排班
- `closedDates` 為在排班期間內的休息日期

**請求範例**:
```json
{
    "roomTypeId": 1,
    "startDate": "2025-01-01",
    "endDate": "2025-01-31",
    "closedDates": ["2025-01-15", "2025-01-16"]
}
```

**回應範例**:
```json
{
    "code": 200,
    "message": "Hotel room Schedule admin update success",
    "status": "HOTEL_ROOM_SCHEDULE_ADMIN_UPDATE_SUCCESS",
    "data": {
        "items": [
            {
                "id": 1,
                "product_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "roomTypeId": 1,
                "code": "STD",
                "number": 1,
                "state": 1,
                "created_by": 1,
                "updated_by": null,
                "created_at": "2025-10-26T15:39:06.000000Z",
                "updated_at": "2025-10-26T15:39:06.000000Z",
                "deleted_at": null
            },
            {
                "id": 2,
                "product_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "roomTypeId": 1,
                "code": "STD",
                "number": 2,
                "state": 1,
                "created_by": 1,
                "updated_by": null,
                "created_at": "2025-10-26T15:39:06.000000Z",
                "updated_at": "2025-10-26T15:39:06.000000Z",
                "deleted_at": null
            }
        ],
        "records": 2
    }
}
```

### 預約查詢 API

#### 取得預約資料
```http
GET /api/admin/hotel/{product_id}/bookings?startDate=2026-01-01&endDate=2026-01-07
Authorization: Bearer {token}
```

**說明**: 取得指定分店在指定日期區間內的所有預約資料，包含房型、房間、每日可用性和預約狀態。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |

**查詢參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `startDate` | string | ✅ | 開始日期 (YYYY-MM-DD) | `"2026-01-01"` |
| `endDate` | string | ✅ | 結束日期 (YYYY-MM-DD) | `"2026-01-07"` |

**參數驗證規則**:
- `startDate` 和 `endDate` 必須為有效的日期格式 (YYYY-MM-DD)
- `startDate` 不得大於 `endDate`
- `endDate` 不得小於 `startDate`
- 日期區間不得超過 90 天

**請求範例**:
```http
GET /api/admin/hotel/f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95/bookings?startDate=2026-01-01&endDate=2026-01-07
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "HOTEL_BOOKING_ADMIN_GET_BOOKINGS_SUCCESS",
    "code": 200,
    "message": "Hotel booking admin get bookings success",
    "data": [
        {
            "id": "1",
            "title": "小捲尾精品房",
            "image": "https://example.com/images/room-type-1.jpg",
            "defaultPrices": [500, 800, 1000],
            "accommodates": 3,
            "categories": [
                {
                    "id": 1,
                    "title": "小型犬(3-5kg）",
                    "tags": ["狗"]
                },
                {
                    "id": 2,
                    "title": "小型貓",
                    "tags": ["貓"]
                }
            ],
            "rooms": [
                {
                    "roomId": "101",
                    "roomTitle": "小捲尾精品房 01",
                    "availabilityByDate": [
                        {
                            "date": "2026-01-01",
                            "priceStartAt": 500,
                            "status": "BOOKING",
                            "booking": {
                                "containerUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                                "status": "PAID",
                                "guests": ["小白", "小黑"]
                            }
                        },
                        {
                            "date": "2026-01-02",
                            "priceStartAt": 500,
                            "status": "BOOKING",
                            "booking": {
                                "containerUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                                "status": "PAID",
                                "guests": ["小白", "小黑"]
                            }
                        },
                        {
                            "date": "2026-01-03",
                            "priceStartAt": 500,
                            "status": "OPEN",
                            "booking": null
                        },
                        {
                            "date": "2026-01-04",
                            "priceStartAt": 500,
                            "status": "RESERVE",
                            "booking": null
                        },
                        {
                            "date": "2026-01-05",
                            "priceStartAt": 500,
                            "status": "CLOSE",
                            "booking": null
                        },
                        {
                            "date": "2026-01-06",
                            "priceStartAt": 500,
                            "status": "OPEN",
                            "booking": null
                        },
                        {
                            "date": "2026-01-07",
                            "priceStartAt": 500,
                            "status": "OPEN",
                            "booking": null
                        }
                    ]
                },
                {
                    "roomId": "102",
                    "roomTitle": "小捲尾精品房 02",
                    "availabilityByDate": [
                        {
                            "date": "2026-01-01",
                            "priceStartAt": 500,
                            "status": "OPEN",
                            "booking": null
                        }
                        // ... 其他日期
                    ]
                }
            ]
        },
        {
            "id": "2",
            "title": "豪華套房",
            "image": "https://example.com/images/room-type-2.jpg",
            "defaultPrices": [1000, 1500],
            "accommodates": 5,
            "categories": [
                {
                    "id": 3,
                    "title": "大型犬(15-20kg）",
                    "tags": ["狗"]
                }
            ],
            "rooms": [
                // ... 房間資料
            ]
        }
    ]
}
```

**回應欄位說明**:

**房型層級 (`data[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string | 房型ID |
| `title` | string | 房型名稱 |
| `image` | string | 房型圖片URL（取第一個圖片的 path） |
| `defaultPrices` | array | 預設價格列表（升序排列） |
| `accommodates` | integer | 可住寵物上限 |
| `categories` | array | 寵物類別列表（物件陣列） |
| `rooms` | array | 房間列表 |

**寵物類別 (`categories[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 分類ID |
| `title` | string | 分類標題 |
| `tags` | array | 父分類標題陣列（例如：`["狗"]`） |

**房間層級 (`rooms[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `roomId` | string | 房間ID |
| `roomTitle` | string | 房間標題（格式：房型名稱 + 房間號碼，號碼補零到2位數） |
| `availabilityByDate` | array | 每日可用性資料 |

**每日可用性 (`availabilityByDate[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `date` | string | 日期 (YYYY-MM-DD) |
| `priceStartAt` | integer | 該日期的起始價格 |
| `status` | string | 狀態（見下方狀態說明） |
| `booking` | object\|null | 預約資料（如果有預約） |

**狀態 (`status`) 說明**:

| 值 | 說明 |
|----|------|
| `OPEN` | 開放預約 |
| `CLOSE` | 不開放（房型停用、房間排程關閉） |
| `BOOKING` | 已預約（狀態為 PAID 或 CHECK_IN） |
| `RESERVE` | 保留中（狀態為 PENDING） |

**預約資料 (`booking`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `containerUuid` | string (UUID) | 訂單容器UUID（可用於查詢訂單詳情） |
| `status` | string | 訂單狀態（PAID, CHECK_IN, PENDING 等） |
| `guests` | array | 客人名稱列表（同一天、同一房間、同一訂單的所有客人名稱，從 `guest_names` 欄位取得） |

**使用說明**:
- 此 API 用於日曆視圖顯示，可查看指定日期範圍內所有房型和房間的預約狀態
- `containerUuid` 為訂單容器的 UUID，可用於查詢訂單詳情
- `guests` 陣列包含該日期、該房間、該訂單的所有客人名稱（從 `hotel_bookings` 表的 `guest_names` 欄位取得）
- 如果一個房間在同一天有多個訂單，只會顯示第一個訂單的資訊
- `priceStartAt` 目前為房型的基本價格，未來可能會加入價格規則計算

**錯誤回應範例**:
```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "startDate": ["開始日期為必填欄位。"],
        "endDate": ["日期區間不得超過90天。"]
    }
}
```

### 住宿訂單管理 API

#### 取得訂單容器內的住宿訂單列表

```http
GET /api/admin/hotel/{product_id}/order/{container_uuid}
Authorization: Bearer {token}
```

**說明**: 取得指定訂單容器內所有住宿訂單（`hotel_orders`）及其詳細的預約明細（`bookings`）、調整項目（`adjustments`、`extraGuestAdjustments`、`customAdjustments`）、訂單異動歷史（`histories`）、會員資訊（`member`）、付款資訊（`payment`）和發票資訊（`invoice`）。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器UUID | `"52373aac-a07d-49e6-909f-3fbd52413f7f"` |

**參數驗證規則**:
- `product_id` 必須為有效的 UUID 格式
- `container_uuid` 必須為有效的 UUID 格式

**請求範例**:
```http
GET /api/admin/hotel/f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95/order/52373aac-a07d-49e6-909f-3fbd52413f7f
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order admin get orders by container success",
    "data": {
        "orders": [
            {
                "uuid": "2232a629-716b-43ff-851a-d95b40151ba9",
                "displayId": "IPTEJ15H001ABCDE",
                "merchantId": "80cdbbf1-435d-4daa-9888-d51b73c045c7",
                "productId": "169a1c29-bb0b-428a-899a-7d6bedc9ac23",
                "status": "CONFIRMED",
                "checkinDate": "2025-01-20",
                "checkoutDate": "2025-01-22",
                "startDate": "2025-01-20",
                "endDate": "2025-01-21",
                "nights": 2,
                "note": "特殊需求：需要餵藥",
                "bookings": [
                    {
                        "id": "a3da4601-4b60-4b49-af48-2f65929b1427",
                        "roomTypeId": 10,
                        "title": "豪華單人房",
                        "roomId": 10,
                        "roomNumber": 101,
                        "date": "2025-01-20",
                        "price": 1500.00,
                        "qty": 1,
                        "note": null,
                        "roomTypeCategories": ["大型犬", "小型犬"]
                    },
                    {
                        "id": "601e6bf1-da34-4c37-b605-ce959f58877e",
                        "roomTypeId": 10,
                        "title": "豪華單人房",
                        "roomId": 10,
                        "roomNumber": 101,
                        "date": "2025-01-20",
                        "price": 500.00,
                        "qty": 1,
                        "note": "寵物加床費",
                        "roomTypeCategories": ["大型犬", "小型犬"]
                    },
                    {
                        "id": "b2c3d4e5-f6a7-8901-2345-6789abcdef01",
                        "roomTypeId": 10,
                        "title": "豪華單人房",
                        "roomId": 10,
                        "roomNumber": 101,
                        "date": "2025-01-21",
                        "price": 1500.00,
                        "qty": 1,
                        "note": null,
                        "roomTypeCategories": ["大型犬", "小型犬"]
                    },
                    {
                        "id": "c3d4e5f6-a7b8-9012-3456-789abcdef012",
                        "roomTypeId": 10,
                        "title": "豪華單人房",
                        "roomId": 10,
                        "roomNumber": 101,
                        "date": "2025-01-21",
                        "price": 500.00,
                        "qty": 1,
                        "note": "寵物加床費",
                        "roomTypeCategories": ["大型犬", "小型犬"]
                    }
                ],
                "adjustments": [
                    {
                        "id": 1,
                        "type": "discount",
                        "title": "連續住宿折扣",
                        "amount": -200.00,
                        "note": "連續住宿折扣"
                    }
                ],
                "extraGuestAdjustments": [
                    {
                        "id": 2,
                        "bookingUuid": "a1b2c3d4-...",
                        "bookingDate": "2025-01-20",
                        "roomTypeTitle": "豪華單人房",
                        "price": 500,
                        "count": 1
                    }
                ],
                "customAdjustments": [
                    {
                        "id": 3,
                        "type": "add",
                        "title": "手動加價",
                        "amount": 300.00,
                        "note": "特殊情況加價"
                    },
                    {
                        "id": 4,
                        "type": "discount",
                        "title": "手動減價",
                        "amount": -100.00,
                        "note": "優惠調整"
                    }
                ],
                "guests": [
                    {
                        "id": 59886,
                        "name": "球球",
                        "categoryId": 5,
                        "category": "大型犬",
                        "isEvaluationRequired": true,
                        "imgUrl": "https://..."
                    },
                    {
                        "id": 59887,
                        "name": "小黑",
                        "categoryId": 3,
                        "category": "小型犬",
                        "isEvaluationRequired": true,
                        "imgUrl": "https://..."
                    }
                ],
                "histories": [
                    {
                        "id": 123,
                        "action": "CANCEL",
                        "status": "CANCEL",
                        "reason": "客戶要求取消",
                        "log": {
                            "status": {
                                "original": "PAID",
                                "updated": "CANCEL"
                            }
                        },
                        "createdAt": "2025-01-15 12:34:56",
                        "createdBy": "張三"
                    },
                    {
                        "id": 122,
                        "action": "REJECT",
                        "status": "CANCEL",
                        "reason": "房間已滿",
                        "log": {
                            "status": {
                                "original": "UNPAID",
                                "updated": "CANCEL"
                            }
                        },
                        "createdAt": "2025-01-15 11:00:00",
                        "createdBy": "李四"
                    }
                ],
                "createdAt": "2025-01-15 10:00:00",
                "updatedAt": "2025-01-15 10:30:00"
            }
        ],
        "member": {
            "id": "550e8400-e29b-41d4-a716-446655440000",
            "phoneCode": "886",
            "phoneNumber": "912345678",
            "phone": "886-912345678",
            "email": "member@example.com",
            "fullName": "會員姓名",
            "gender": "性別",
            "taxType": "B2C",
            "taxId": "統一編號",
            "carrierType": "載具類型",
            "carrierNum": "載具號碼",
            "loveCode": "愛心碼",
            "level": {
                "id": "level-uuid-1234",
                "title": "VIP 會員"
            },
            "levelExpiredAt": "2025-12-31 23:59:59",
            "remainingPoints": 1200,
            "tags": [
                {
                    "id": 1,
                    "title": "重要客戶"
                },
                {
                    "id": 2,
                    "title": "常客"
                }
            ],
            "lineId": "U1234567890abcdef"
        },
        "payment": {
            "paymentMethod": "Credit",
            "paymentStatus": "PAID",
            "paymentExpiredDate": "2025-01-20 23:59:59",
            "paidAt": "2025-01-15 10:30:00",
            "detail": {
                "type": "Credit",
                "card4No": "1234",
                "tradeNo": "交易序號",
                "amt": 1000,
                "tradeDate": "2025-01-15 10:30:00",
                "canRefund": 1,
                "canPartialRefund": 0,
                "backBalance": 1000
            }
        },
        "invoice": {
            "taxType": "B2C",
            "taxId": "統一編號",
            "carrierType": "載具類型",
            "carrierNum": "載具號碼",
            "loveCode": "愛心碼",
            "invoiceNumber": "發票號碼",
            "randomNum": "隨機碼",
            "createTime": "2025-01-15 10:30:00",
            "status": "發票狀態",
            "totalAmt": "總金額"
        }
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `orders` | array | 訂單列表 |
| `member` | object\|null | 會員資訊（可為 null） |
| `payment` | object | 付款資訊 |
| `invoice` | object\|null | 發票資訊（可為 null，若無發票則從會員資訊取得預設值） |

**訂單基本資訊 (`orders[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `uuid` | string (UUID) | 住宿訂單ID |
| `displayId` | string | 顯示用訂單編號（自動產生，格式：IP + 商家別名 + 月份 + 日期 + H + 序號 + 隨機字串） |
| `merchantId` | string (UUID) | 商家ID |
| `productId` | string (UUID) | 分店ID |
| `status` | string | 訂單狀態（維持原始大小寫，如 "CONFIRMED", "PAID" 等） |
| `checkinDate` | string | 入住日期 (Y-m-d 格式) |
| `checkoutDate` | string | 退房日期 (Y-m-d 格式) |
| `startDate` | string\|null | 預約開始日期（從 bookings 的第一天取得，Y-m-d 格式，可為 null） |
| `endDate` | string\|null | 預約結束日期（從 bookings 的最後一天取得，Y-m-d 格式，可為 null） |
| `nights` | integer | 住宿天數 |
| `note` | string\|null | 備註（可為 null） |
| `createdAt` | string | 建立時間 (Y-m-d H:i:s 格式) |
| `updatedAt` | string | 更新時間 (Y-m-d H:i:s 格式) |
| `bookings` | array | 預約明細列表 |
| `guests` | array | 該訂單的寵物資訊列表（從該訂單的 guest_ids 取得） |
| `adjustments` | array | 系統自動調整項目（連續住宿折扣等，不含加床費） |
| `extraGuestAdjustments` | array | 加床費（額外客人）調整項目，與計算 API 格式一致 |
| `customAdjustments` | array | 手動調整項目 |
| `histories` | array | 訂單異動歷史記錄列表（按建立時間降序排列） |
| `signatures` | object | 簽名路徑（如 CHECK_IN、CHECK_OUT） |
| `containerId` | string (UUID)\|null | 訂單容器 UUID（可為 null） |

**客人資訊 (`orders[].guests[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 寵物ID |
| `name` | string | 寵物名稱 |
| `category` | object\|null | 分類物件（完整的分類資訊，可為 null） |
| `isEvaluationRequired` | boolean | 是否需要評估（需評估為 true，不需評估為 false） |
| `imgUrl` | string\|null | 寵物圖片網址（可為 null） |

**分類物件 (`orders[].guests[].category`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 分類ID |
| `title` | string | 分類名稱 |
| `tags` | array | 所有父級分類的 title 陣列（從根節點到當前節點的所有父級） |

**預約明細 (`bookings[]`)**:

按 `date` 升序、`created_at` 升序排序。

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 預約ID |
| `roomTypeId` | integer | 房型ID |
| `title` | string\|null | 房型名稱 |
| `roomId` | integer | 房間ID |
| `roomNumber` | integer\|null | 房間號碼 |
| `date` | string | 預約日期 (Y-m-d 格式) |
| `price` | float | 價格 |
| `qty` | integer | 數量（固定為 1） |
| `note` | string\|null | 備註（可為 null） |
| `roomTypeCategories` | array | 房型支援的寵物類別名稱列表（從房型價格設定中的 sizeId 取得 ServiceCategory 的 title，字串陣列） |

**調整項目 (`adjustments[]`)**:

按 `created_at` 升序排序。包含系統自動計算的調整項目（如連續住宿折扣），**不含加床費**；加床費請見 `extraGuestAdjustments`。

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 調整項目ID |
| `type` | string | 類型：`"discount"`（折扣）、`"add"`（加購）、`"minus"`（減價） |
| `title` | string | 標題 |
| `amount` | float | 金額 |
| `note` | string\|null | 備註（可為 null） |

**加床費調整項目 (`extraGuestAdjustments[]`)**:

與「計算訂單價格」API 的加床費格式一致，每筆一列（房型+日期）。

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 調整項目ID |
| `bookingUuid` | string\|null | 預約 UUID（可為 null） |
| `bookingDate` | string\|null | 預約日期 (Y-m-d，可為 null) |
| `roomTypeTitle` | string\|null | 房型名稱 |
| `price` | integer | 該筆總價 |
| `count` | integer | 該筆額外客人數量 |

**自訂調整項目 (`customAdjustments[]`)**:

按 `created_at` 升序排序。包含手動調整的項目（管理員手動加價或減價）。

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 調整項目ID |
| `type` | string | 類型：`"discount"`（折扣）、`"add"`（加購）、`"subtract"`（減價） |
| `title` | string | 標題 |
| `amount` | float | 金額 |
| `note` | string\|null | 備註（可為 null） |

**調整項目類型說明**:

| 類型 | 說明 | 金額特性 |
|------|------|---------|
| `discount` | 折扣項目 | 負數（如 -200.00） |
| `add` | 加購項目 | 正數（如 500.00） |
| `subtract` | 減價項目 | 負數（如 -300.00） |

**訂單異動歷史 (`histories[]`)**:

按 `created_at` 降序排序（最新的在前）。記錄訂單的所有異動操作（如取消、拒絕等）。

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 歷史記錄ID |
| `action` | string | 操作類型：`"CONFIRM"`（確認）、`"CANCEL"`（取消）、`"REJECT"`（拒絕）、`"CHECK_IN"`（入住）、`"NO_SHOW"`（未到）、`"CHECK_OUT"`（退房） |
| `status` | string | 記錄時的訂單狀態（如 `"CANCEL"`） |
| `reason` | string\|null | 操作原因（可為 null） |
| `log` | object | 異動記錄 JSON 物件 |
| `createdAt` | string | 建立時間（格式：`Y-m-d H:i:s`，根據商家時區） |
| `createdBy` | string | 建立者名稱（若無則為 `"系統建置"`） |

**異動記錄 (`histories[].log`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `status` | object | 狀態異動記錄 |
| `status.original` | string\|null | 原始狀態（如 `"PAID"`, `"UNPAID"`，可為 null） |
| `status.updated` | string | 更新後的狀態（如 `"CANCEL"`） |

**會員資訊 (`member`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 會員ID |
| `phoneCode` | string\|null | 電話區碼 |
| `phoneNumber` | string\|null | 電話號碼 |
| `phone` | string | 完整電話號碼（格式：區碼-號碼） |
| `email` | string\|null | 電子郵件 |
| `fullName` | string\|null | 姓名 |
| `gender` | string\|null | 性別 |
| `taxType` | string\|null | 發票類型（B2C, B2B, DONATE） |
| `taxId` | string\|null | 統一編號 |
| `carrierType` | string\|null | 載具類型 |
| `carrierNum` | string\|null | 載具號碼 |
| `loveCode` | string\|null | 愛心碼 |
| `level` | object\|null | 會員等級資訊（可為 null） |
| `levelExpiredAt` | string\|null | 會員等級效期 (Y-m-d H:i:s 格式，可為 null) |
| `remainingPoints` | integer | 剩餘點數（儲值金餘額，reducePoint + reduceBonus） |
| `tags` | array | 會員標籤列表 |
| `lineId` | string\|null | LINE ID（可為 null） |
| `extraInfos` | object\|null | 額外資訊（可為 null） |

**會員等級資訊 (`member.level`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 等級ID |
| `title` | string | 等級名稱 |

**會員標籤 (`member.tags[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 標籤ID |
| `title` | string | 標籤名稱 |

**付款資訊 (`payment`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `paymentMethod` | string\|null | 付款方式（Credit, Vacc, StoreCredit, Atm 等） |
| `paymentStatus` | string | 付款狀態（PAID, UNPAID） |
| `paymentExpiredDate` | string\|null | 付款到期時間 (Y-m-d H:i:s 格式，可為 null) |
| `paidAt` | string\|null | 付款時間 (Y-m-d H:i:s 格式，可為 null) |
| `detail` | object\|null | 付款詳細資訊（根據付款方式不同而異，可為 null） |

**付款詳細資訊 (`payment.detail`)**:

根據付款方式不同，`detail` 的結構會有所不同：

**信用卡付款 (`type: "Credit"`)**:
| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `type` | string | 固定為 "Credit" |
| `card4No` | string\|null | 卡片後四碼 |
| `tradeNo` | string\|null | 交易序號 |
| `amt` | integer\|null | 交易金額 |
| `tradeDate` | string\|null | 交易日期 |
| `canRefund` | integer | 是否可退款 (0:否, 1:是, 2:處理中) |
| `canPartialRefund` | integer | 是否可部分退款 (0:否, 1:是) |
| `backBalance` | integer | 可退餘額 |

**虛擬帳號付款 (`type: "Vacc"`)**:
| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `type` | string | 固定為 "Vacc" |
| `bank` | string\|null | 銀行名稱 |
| `bankType` | string\|null | 銀行類型代碼 |
| `bankCode` | string\|null | 銀行代碼 |
| `vaccNo` | string\|null | 虛擬帳號 |

**現場刷卡/線下匯款 (`type: "StoreCredit"` 或 `"Atm"`)**:
| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `type` | string | "StoreCredit" 或 "Atm" |
| `payerAccount5Code` | string\|null | 帳號後五碼 |
| `card4No` | string\|null | 卡片後四碼 |
| `amt` | integer\|null | 交易金額 |

**發票資訊 (`invoice`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `taxType` | string\|null | 發票類型（B2C, B2B, DONATE） |
| `taxId` | string\|null | 統一編號 |
| `carrierType` | string\|null | 載具類型 |
| `carrierNum` | string\|null | 載具號碼 |
| `loveCode` | string\|null | 愛心碼 |
| `invoiceNumber` | string\|null | 發票號碼（若無發票則為 null） |
| `randomNum` | string\|null | 隨機碼（若無發票則為 null） |
| `createTime` | string\|null | 建立時間 (Y-m-d H:i:s 格式，若無發票則為 null) |
| `status` | string\|null | 發票狀態（若無發票則為 null） |
| `totalAmt` | string\|null | 總金額（若無發票則為 null） |

**使用說明**:
- 此 API 用於查詢訂單容器內的所有住宿訂單及其詳細資訊
- 如果查無訂單容器，會返回 `{"orders": [], "member": null, "guests": [], "payment": null, "invoice": null}`
- `guests` 陣列是從所有訂單的 `guest_ids` 欄位收集並去重後的寵物資料
- `bookings` 陣列包含該訂單所有日期的預約明細，按日期排序
- `adjustments` 陣列包含該訂單的系統自動計算調整項目（如連續住宿折扣），不含加床費，按建立時間排序
- `extraGuestAdjustments` 陣列包含該訂單的加床費（額外客人），每筆含 roomTypeTitle、bookingDate、price（總價）、count
- `customAdjustments` 陣列包含該訂單的手動調整項目（管理員手動加價或減價），按建立時間排序
- `status` 維持原始大小寫，不會轉換為小寫

**錯誤回應範例**:
```json
{
    "status": "error",
    "code": 404,
    "message": "找不到指定的訂單容器或住宿訂單"
}
```

#### 取得訂單短網址

```http
GET /api/admin/order/{id}/shortUrl
Authorization: Bearer {token}
```

**說明**: 取得指定訂單的短網址，用於分享訂單連結。支援三種訂單類型：

- **一般訂單（Order）**：`{id}` 為訂單 displayId（非 36 字元）
- **住宿訂單（HotelOrder）**：`{id}` 為訂單 UUID（36 字元），優先以 HotelOrder 解析
- **預付訂單（PrepaidOrder）**：`{id}` 為訂單 UUID（36 字元），當找不到對應 HotelOrder 時以 PrepaidOrder 解析

訂單狀態須為 UNPAID、PAID、CHECKIN／CHECK_IN、CHECKEDIN、NOSHOW／NO_SHOW、COMPLETE／CHECK_OUT（住宿訂單另支援 PENDING）；已取消訂單無法取得短網址。

**中介層:** `hasMerchant`, `expired`, `admin`
- 驗證商家管理員身份
- 權限需具備 `getLink`
- 產生訂單分享連結

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | string | ✅ | 訂單識別：一般訂單為 displayId；住宿／預付訂單為 UUID（36 字元） | `"IPTEJ15H001ABCDE"`（一般）<br>`"550e8400-e29b-41d4-a716-446655440000"`（住宿／預付） |

**請求範例**（一般訂單）:
```http
GET /api/admin/order/IPTEJ15H001ABCDE/shortUrl
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**請求範例**（住宿／預付訂單，以 UUID）:
```http
GET /api/admin/order/550e8400-e29b-41d4-a716-446655440000/shortUrl
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "ORDER_ADMIN_GET_SHORT_URL_SUCCESS",
    "code": 200,
    "message": "ORDER_ADMIN_GET_SHORT_URL_SUCCESS",
    "data": "https://dsth.example.com/ABC12x"
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `data` | string | 訂單短網址，可轉址至訂單詳情頁 |

**錯誤回應**（訂單已取消）:
```json
{
    "status": "error",
    "code": 400,
    "message": "Order has been canceled"
}
```

**錯誤回應**（訂單狀態不支援）:
```json
{
    "status": "error",
    "code": 400,
    "message": "Unsupported order state"
}
```

**錯誤回應**（訂單不存在）:
```json
{
    "status": "error",
    "code": 404,
    "message": "OrderAdminItemNotExist"
}
```

#### 取得住宿訂單列表

```http
GET /api/admin/hotel/order
Authorization: Bearer {token}
```

**說明**: 取得商戶裡所有子訂單是 `hotel_orders` 的 container 資料列表，支援分頁和多種篩選條件。

**Query String 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `memberName` | string | ❌ | 會員姓名（模糊搜尋） | `"張三"` |
| `memberPhone` | string | ❌ | 會員手機號碼（模糊搜尋） | `"0912345678"` |
| `orderDisplayId` | string | ❌ | 訂單編號（模糊搜尋） | `"IPFIJ14P001VIFXU"` |
| `orderStatus` | string | ❌ | 訂單狀態 | `"PENDING"`, `"PAID"`, `"UNPAID"`, `"CHECK_IN"`, `"NO_SHOW"`, `"CHECK_OUT"` |
| `paymentMethod` | string | ❌ | 付款方式 | `"Free"`, `"Cash"`, `"Credit"`, `"Prepaid"`, `"Vacc"`, `"StoreCredit"`, `"Atm"`, `"Deposit"`, `"Installment"`, `"StoreLinePay"`, `"Chailease"` |
| `startDate` | string | ❌ | 訂單建立開始日期 (Y-m-d) | `"2026-01-01"` |
| `endDate` | string | ❌ | 訂單建立結束日期 (Y-m-d) | `"2026-01-31"` |
| `page` | integer | ❌ | 頁碼（預設 1） | `1` |
| `perPage` | integer | ❌ | 每頁筆數（預設 15，最大 100） | `15` |

**orderStatus 可選值**:

| 值 | 說明 |
|----|------|
| `"PENDING"` | 待處理（保留訂單） |
| `"UNPAID"` | 未付款 |
| `"PAID"` | 已付款 |
| `"CHECK_IN"` | 已入住 |
| `"NO_SHOW"` | 未到（No Show） |
| `"CHECK_OUT"` | 已退房 |

**paymentMethod 可選值**:

| 值 | 說明 |
|----|------|
| `"Free"` | 免費 |
| `"Cash"` | 現金 |
| `"Credit"` | 信用卡 |
| `"Prepaid"` | 預付方案折抵 |
| `"Vacc"` | 虛擬帳戶 |
| `"StoreCredit"` | 現場刷卡 |
| `"Atm"` | 線下匯款 |
| `"Deposit"` | 儲值金抵扣 |
| `"Installment"` | 分期付款 |
| `"StoreLinePay"` | 現場 LINE Pay |
| `"Chailease"` | 中租零卡分期 |

**參數驗證規則**:
- `startDate` 必須為有效的日期格式 (Y-m-d)
- `endDate` 必須為有效的日期格式 (Y-m-d)，且不得小於 `startDate`
- `page` 必須為正整數（最小 1）
- `perPage` 必須為正整數（最小 1，最大 100）
- `orderStatus` 必須為有效的狀態值
- `paymentMethod` 必須為有效的付款方式值

**請求範例**:
```http
GET /api/admin/hotel/order?memberName=張三&orderStatus=PAID&startDate=2026-01-01&endDate=2026-01-31&page=1&perPage=15
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order containers retrieved successfully",
    "data": {
        "items": [
            {
                "containerId": "52373aac-a07d-49e6-909f-3fbd52413f7f",
                "displayId": "IPFIJ14P001VIFXU",
                "paymentMethod": "Credit",
                "paymentStatus": "PAID",
                "memberName": "張三",
                "memberPhone": "886-912345678",
                "productId": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "bookings": [
                    {
                        "date": "2026-01-01",
                        "title": "房型A"
                    },
                    {
                        "date": "2026-01-02",
                        "title": "房型A"
                    },
                    {
                        "date": "2026-01-03",
                        "title": "房型B"
                    }
                ],
                "total": 5000.00
            },
            {
                "containerId": "62373aac-a07d-49e6-909f-3fbd52413f8f",
                "displayId": "IPFIJ14P002VIFXU",
                "paymentMethod": "Cash",
                "paymentStatus": "UNPAID",
                "memberName": "李四",
                "memberPhone": "886-987654321",
                "productId": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "bookings": [
                    {
                        "date": "2026-01-05",
                        "title": "房型C"
                    }
                ],
                "total": 2000.00
            }
        ],
        "pagination": {
            "page": 1,
            "perPage": 15,
            "total": 100,
            "lastPage": 7
        }
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `items` | array | 訂單容器列表 |
| `pagination` | object | 分頁資訊 |

**訂單容器資料 (`data.items[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `containerId` | string (UUID) | 訂單容器ID |
| `displayId` | string | 訂單編號 |
| `paymentMethod` | string | 付款方式（可為空字串） |
| `paymentStatus` | string | 付款狀態（如 `PAID`、`UNPAID`，可為空字串） |
| `memberName` | string | 會員姓名（可為空字串） |
| `memberPhone` | string | 會員電話（可為空字串） |
| `productId` | string (UUID) | 分店ID（可為空字串） |
| `bookings` | array | 預約日期與房型列表 |
| `total` | float | 總金額（四捨五入至小數點後兩位） |

**預約資訊 (`data.items[].bookings[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `date` | string | 預約日期 (Y-m-d 格式) |
| `title` | string | 房型名稱（可為空字串） |

**分頁資訊 (`pagination`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `page` | integer | 當前頁碼 |
| `perPage` | integer | 每頁筆數 |
| `total` | integer | 總筆數 |
| `lastPage` | integer | 最後一頁頁碼 |

**使用說明**:
- 此 API 用於查詢商戶內所有包含住宿訂單的訂單容器
- 只會返回 `type` 為 `"hotel"` 且 `is_temp` 為 `0` 的訂單容器
- 支援多種篩選條件，可以組合使用
- 預設每頁 15 筆，可透過 `perPage` 參數調整（最大 100）
- 預設依建立時間降序排序（最新的在前）
- `bookings` 陣列包含該容器內所有住宿訂單的預約明細，按日期排序
- `total` 為該容器內所有住宿訂單的總金額加總

**錯誤回應範例**:
```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "endDate": ["結束日期不得小於開始日期。"],
        "perPage": ["每頁筆數不得超過 100。"]
    }
}
```

#### 匯出住宿訂單 CSV

```http
GET /api/admin/hotel/order/export
Authorization: Bearer {token}
```

**說明**: 匯出商戶裡所有子訂單是 `hotel_orders` 的 container 資料為 CSV 檔案，支援與列表 API 相同的篩選條件。

**Query String 參數**:

與「取得住宿訂單列表」API 相同的參數，但不包含 `page` 和 `perPage`（匯出所有符合條件的資料）。

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `memberName` | string | ❌ | 會員姓名（模糊搜尋） | `"張三"` |
| `memberPhone` | string | ❌ | 會員手機號碼（模糊搜尋） | `"0912345678"` |
| `orderDisplayId` | string | ❌ | 訂單編號（模糊搜尋） | `"IPFIJ14P001VIFXU"` |
| `orderStatus` | string | ❌ | 訂單狀態 | `"PENDING"`, `"PAID"`, `"UNPAID"`, `"CHECK_IN"`, `"NO_SHOW"`, `"CHECK_OUT"` |
| `paymentMethod` | string | ❌ | 付款方式 | `"Free"`, `"Cash"`, `"Credit"`, `"Prepaid"`, `"Vacc"`, `"StoreCredit"`, `"Atm"`, `"Deposit"`, `"Installment"`, `"StoreLinePay"`, `"Chailease"` |
| `startDate` | string | ❌ | 訂單建立開始日期 (Y-m-d) | `"2026-01-01"` |
| `endDate` | string | ❌ | 訂單建立結束日期 (Y-m-d) | `"2026-01-31"` |

**請求範例**:
```http
GET /api/admin/hotel/order/export?memberName=張三&orderStatus=PAID&startDate=2026-01-01&endDate=2026-01-31
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order containers exported successfully",
    "data": {
        "fileName": "2026-01-15 住宿訂單匯出.csv",
        "csvData": [
            {
                "containerId": "訂單容器ID",
                "displayId": "訂單編號",
                "paymentMethod": "付款方式",
                "memberName": "會員姓名",
                "memberPhone": "會員電話",
                "productId": "分店ID",
                "bookings": "預約日期與房型",
                "total": "總金額",
                "createdAt": "訂單成立時間"
            },
            {
                "containerId": "52373aac-a07d-49e6-909f-3fbd52413f7f",
                "displayId": "IPFIJ14P001VIFXU",
                "paymentMethod": "Credit",
                "memberName": "張三",
                "memberPhone": "886-912345678",
                "productId": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "bookings": "2026-01-01 房型A, 2026-01-02 房型A, 2026-01-03 房型B",
                "total": "$5,000.00",
                "createdAt": "2026/01/15 10:30"
            }
        ]
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `fileName` | string | CSV 檔案名稱（格式：YYYY-MM-DD 住宿訂單匯出.csv） |
| `csvData` | array | CSV 資料陣列（第一筆為表頭，其餘為資料列） |

**CSV 資料格式**:

| 欄位名稱 | 說明 |
|---------|------|
| `containerId` | 訂單容器ID |
| `displayId` | 訂單編號 |
| `paymentMethod` | 付款方式 |
| `memberName` | 會員姓名 |
| `memberPhone` | 會員電話 |
| `productId` | 分店ID |
| `bookings` | 預約日期與房型（多筆以逗號分隔，格式：YYYY-MM-DD 房型名稱） |
| `total` | 總金額（格式：$X,XXX.XX） |
| `createdAt` | 訂單成立時間（格式：YYYY/MM/DD HH:mm，使用商家時區） |

**使用說明**:
- 此 API 用於匯出符合篩選條件的所有住宿訂單資料
- 支援與列表 API 相同的篩選條件
- 匯出的資料不分頁，包含所有符合條件的記錄
- `bookings` 欄位會將所有預約日期與房型以逗號分隔的方式合併顯示
- `total` 欄位會格式化為貨幣格式（包含 $ 符號和千分位）
- `createdAt` 欄位使用商家設定的時區顯示

**錯誤回應範例**:
```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "endDate": ["結束日期不得小於開始日期。"]
    }
}
```

#### 計算住宿訂單價格

```http
POST /api/admin/hotel/{product_id}/order/calculate
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 計算住宿訂單的價格，包含基礎價格、額外客人費用和連續住宿折扣。相同的請求內容會快取 5 分鐘，避免重複計算。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `bookings` | array | ✅ | 預約資料陣列 | 見下方說明 |

**bookings 陣列項目**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `date` | string | ✅ | 預約日期 (YYYY-MM-DD) | `"2026-01-01"` |
| `roomTypeId` | integer | ✅ | 房型ID | `10` |
| `roomId` | integer | ✅ | 房間ID | `101` |
| `guestCount` | integer | ❌ | 客人數量（預設為 1） | `2` |

**參數驗證規則**:
- `product_id` 必須為有效的 UUID 格式
- `bookings` 必須為陣列，且至少包含一筆預約資料
- 每筆 `bookings` 項目必須包含 `date`、`roomTypeId`、`roomId`
- `date` 必須為有效的日期格式 (YYYY-MM-DD)
- `guestCount` 必須為正整數（預設為 1）

**請求範例**:
```http
POST /api/admin/hotel/f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95/order/calculate
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

{
    "bookings": [
        {
            "date": "2026-01-01",
            "roomTypeId": 10,
            "roomId": 101,
            "guestCount": 2
        },
        {
            "date": "2026-01-02",
            "roomTypeId": 10,
            "roomId": 101,
            "guestCount": 2
        },
        {
            "date": "2026-01-03",
            "roomTypeId": 11,
            "roomId": 102,
            "guestCount": 2
        }
    ]
}
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order admin calculate success",
    "data": {
        "bookings": [
            {
                "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "date": "2026-01-01",
                "roomTypeId": "10",
                "title": "豪華單人房",
                "roomId": "101",
                "roomNumber": 101,
                "price": 1000
            },
            {
                "uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
                "date": "2026-01-02",
                "roomTypeId": "10",
                "title": "豪華單人房",
                "roomId": "101",
                "roomNumber": 101,
                "price": 1000
            },
            {
                "uuid": "c3d4e5f6-a7b8-9012-cdef-123456789012",
                "date": "2026-01-03",
                "roomTypeId": "11",
                "title": "豪華雙人房",
                "roomId": "102",
                "roomNumber": 102,
                "price": 1200
            }
        ],
        "extraGuestFee": [
            {
                "bookingUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "bookingDate": "2026-01-01",
                "roomTypeTitle": "豪華單人房",
                "price": 500,
                "count": 1
            },
            {
                "bookingUuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
                "bookingDate": "2026-01-02",
                "roomTypeTitle": "豪華單人房",
                "price": 500,
                "count": 1
            },
            {
                "bookingUuid": "c3d4e5f6-a7b8-9012-cdef-123456789012",
                "bookingDate": "2026-01-03",
                "roomTypeTitle": "豪華雙人房",
                "price": 600,
                "count": 1
            }
        ],
        "priceRuleAdjustment": [
            {
                "ruleId": "1",
                "title": "連續住宿3日以上",
                "price": -150,
                "count": 3
            }
        ]
    }
}
```

**回應欄位說明**:

`data` 的屬性皆為 camelCase。

**bookings 陣列**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `uuid` | string (UUID) | 預約 UUID（使用 UUID v5，相同輸入會產生相同 UUID） |
| `date` | string | 預約日期 (YYYY-MM-DD) |
| `roomTypeId` | string | 房型ID |
| `title` | string | 房型名稱 |
| `roomId` | string | 房間ID |
| `roomNumber` | integer | 房間號碼 |
| `price` | integer | 該日期的價格（房型基本價格 + type=period 規則加價，已四捨五入到整數） |

**extraGuestFee 陣列**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `bookingUuid` | string (UUID) | 對應的預約 UUID |
| `bookingDate` | string | 預約日期 (YYYY-MM-DD) |
| `roomTypeTitle` | string | 房型名稱 |
| `price` | integer | 額外客人總價（單價 × count，該筆 booking 的額外客人費用，已四捨五入到整數） |
| `count` | integer | 額外客人數量（總客人數 - 1） |

**priceRuleAdjustment 陣列**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `ruleId` | string | 價格規則ID |
| `title` | string | 規則名稱 |
| `price` | integer | 調整金額（折扣為負數，加價為正數，已四捨五入到整數） |
| `count` | integer | 適用的住宿天數（type=stay 為總住宿天數，type=period 為該規則適用的夜數） |

**priceRuleAdjustment 彙總規則**（多條規則時）:
- **多條加價**：全部條列
- **多條減價**：僅選數值最大的一條（折扣金額最高者）
- **同時有加價與減價**：分別列出（全部加價 + 最佳減價一條）

**計算邏輯說明**:

1. **基礎價格計算**:
   - 每個 booking 的價格從房型的基本價格（`priceStartAt`）取得
   - 若該日期落在 type=period 規則的 `valid_from`～`valid_until` 區間內，則加上該規則的加價／減價
   - 同房晚若有多條 type=period 規則符合，將累加所有調整

2. **額外客人費用計算**:
   - 當 `guestCount > 1` 時，會計算額外客人費用
   - 額外客人數量 = `guestCount - 1`
   - 額外客人單價從房型的 `extraPrice` 取得
   - 每個 booking 的 `extraGuestFee` 項目：`price` = 單價 × count（該筆的總額外客人費用）

3. **日期區間變價計算**（type=period）:
   - 對每個房晚，查詢 `valid_from <= 日期 <= valid_until` 且適用該房型的規則
   - 根據規則的 `valueType`（'%' 或 'fixed'）和 `operator`（'+' 或 '-'）計算當夜加價／減價
   - 加價／減價直接套用於該房晚的基礎價格
   - **範例**：活動 2026-01-10～31 加價 200 元，預訂 01-08～01-12，房型 500 元/夜 → 01-08、01-09 各 500；01-10、01-11、01-12 各 700；總價 3,100

4. **連續住宿折扣計算**（type=stay）:
   - 統計每個房型在 bookings 中的住宿天數，取**住宿天數最多**的房型作為規則比對對象
   - 僅查詢適用於該房型且 `type = 'stay'` 的價格規則
   - 檢查規則的 `minNight` 條件（總住宿天數必須 >= `minNight`）
   - 計算總價 = 所有 booking 價格（已含 type=period 調整）+ 所有額外客人費用
   - 從適用規則中**只套用折扣最高**（金額最負）的一條，不重複計算
   - 根據規則的 `valueType`（'%' 或 'fixed'）和 `operator`（'+' 或 '-'）計算折扣
   - 折扣金額四捨五入到整數；`count` 為**總住宿天數**

5. **快取機制**:
   - 相同的請求內容（product_id, merchant_id, bookings）會快取 5 分鐘
   - 快取 key 基於請求內容的 MD5 hash
   - 可避免重複計算，提升效能

6. **UUID 生成**:
   - 使用 UUID v5（deterministic UUID）
   - 基於 `product_id`、`date`、`roomTypeId`、`roomId` 生成
   - 相同的輸入會產生相同的 UUID

**使用說明**:
- 此 API 用於在建立訂單前計算價格
- 可以用於前端顯示預估價格
- 相同的請求會快取 5 分鐘，避免重複計算
- 如果房型或房間不存在，該筆 booking 會被忽略
- 如果沒有額外客人（`guestCount <= 1`），`extraGuestFee` 陣列中不會包含該筆 booking
- 若無適用的價格規則（type=stay 或 type=period），`priceRuleAdjustment` 陣列為空

**錯誤回應範例**:
```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "bookings": ["預約資料為必填欄位。"],
        "bookings.0.date": ["日期格式錯誤，必須為 YYYY-MM-DD 格式。"]
    }
}
```

#### 更新住宿訂單

```http
PATCH /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 更新指定住宿訂單的狀態或備註。可以只更新狀態、只更新備註，或同時更新兩者。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器UUID | `"13ba86b9-522b-430e-9b8d-2dd214663e87"` |
| `order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `status` | string | ❌ | 訂單狀態 | `"PENDING"` |
| `note` | string\|null | ❌ | 訂單備註（可為 null） | `"需要餵藥"` |

**status 可選值**:

| 值 | 說明 |
|----|------|
| `"PENDING"` | 待處理（保留訂單） |
| `"CONFIRMED"` | 已確認 |
| `"UNPAID"` | 未付款 |
| `"PAID"` | 已付款 |
| `"CHECK_IN"` | 已入住 |
| `"NO_SHOW"` | 未到（No Show） |
| `"CHECK_OUT"` | 已退房 |
| `"CANCEL"` | 已取消 |

**請求範例（更新狀態和備註）**:
```json
{
    "status": "CHECK_IN",
    "note": "房間靠窗"
}
```

**請求範例（只更新備註）**:
```json
{
    "note": "房間靠窗"
}
```

**請求範例（只更新狀態）**:
```json
{
    "status": "PAID"
}
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order updated successfully",
    "data": {
        "orders": [
            {
                "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                "status": "CHECK_IN",
                "note": "房間靠窗",
                "forms": {
                    "CHECK_IN": [...]
                },
                ...
            }
        ],
        "member": {...},
        "payment": {...},
        "invoice": {...},
        "reservedAt": null
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `orders` | array | 訂單容器內的所有住宿訂單列表 |
| `member` | object\|null | 會員資料 |
| `payment` | object\|null | 付款資料 |
| `invoice` | object\|null | 發票資料 |
| `reservedAt` | string\|null | 保留時間（格式：YYYY-MM-DD HH:mm:ss） |

**使用說明**:

1. **部分更新**:
   - 可以只更新 `status` 或只更新 `note`，或同時更新兩者
   - 如果請求中沒有包含某個欄位，該欄位將不會被更新

2. **狀態變更處理**:
   - 當狀態變更為 `PENDING` 時，系統會刷新保留時間
   - 當從 `PENDING` 變更為其他狀態時，系統會清除保留時間
   - 狀態變更時，相關的 `HotelBooking` 狀態也會同步更新

3. **回應格式**:
   - 返回完整的訂單容器資料，包含該容器內的所有訂單
   - 訂單資料包含完整的關聯資訊（bookings、adjustments、forms 等）

**錯誤回應範例**:

**訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "HotelOrder not found"
}
```

**無效的狀態**:
```json
{
    "status": "error",
    "code": 400,
    "message": "Invalid order status: INVALID_STATUS"
}
```

#### 建立或更新住宿訂單表單

```http
POST /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/form
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 為指定的住宿訂單建立或更新表單資料。系統會自動建立表單內容的快照（snapshot），並儲存使用者填寫的答案（answers）。如果該訂單已存在相同類型的表單，則會更新現有表單。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器UUID | `"3b85a3fb-5cbf-4e78-846c-7cd4dcb04260"` |
| `order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `data.type` | string | ✅ | 表單類型 | `"CHECK_IN"` |
| `data.forms` | array | ✅ | 表單答案陣列（至少需一個） | 見下方說明 |
| `data.otherNote` | string | ❌ | 其他備註 | `"這是其他備註"` |
| `data.medicalNote` | string | ❌ | 醫療資訊備註 | `"需要特別注意的醫療資訊"` |
| `data.signaturePath` | string | ❌ | 簽名圖 path；後端會透過 CreateOrderSignature 建立 `order_signatures` 記錄，並將回傳的 id 存入 `hotel_order_forms.signature_id` | `"/uploads/signatures/xxx.png"` |

**type 可選值**:

| 值 | 說明 |
|----|------|
| `"CHECK_IN"` | 入住表單（目前僅支援此類型） |

**forms 陣列項目**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | integer | ✅ | 住宿問題ID（對應 `boardings_forms` 表的 `id`） | `1` |
| `title` | string | ❌ | 住宿題目（可選，用於前端顯示） | `"寵物健康狀況"` |
| `type` | string | ✅ | 表單類型 | `"single"`, `"multiple"`, `"text"` |
| `options` | array\|null | ❌ | 選項列表（當 `type` 為 `"single"` 或 `"multiple"` 時提供） | `["OP1", "OP2"]` |
| `answer` | string\|array | ✅ | 填寫答案 | 見下方說明 |

**type 表單類型說明**:

| 值 | 說明 | answer 類型 |
|----|------|------------|
| `"single"` | 單選 | `string`（單一選項值） |
| `"multiple"` | 多選 | `array`（選項值陣列） |
| `"text"` | 文字輸入 | `string`（自由文字） |

**請求範例**:

```json
{
    "data": {
        "type": "CHECK_IN",
        "forms": [
            {
                "id": 1,
                "title": "寵物健康狀況",
                "type": "single",
                "options": ["健康", "需注意", "生病中"],
                "answer": "健康"
            },
            {
                "id": 2,
                "title": "特殊需求",
                "type": "multiple",
                "options": ["需要散步", "需要餵藥", "需要特殊飲食"],
                "answer": ["需要散步", "需要餵藥"]
            },
            {
                "id": 3,
                "title": "其他備註",
                "type": "text",
                "options": null,
                "answer": "這是測試備註"
            }
        ],
        "otherNote": "這是其他備註",
        "medicalNote": "需要特別注意的醫療資訊",
        "signaturePath": "/uploads/signatures/xxx.png"
    }
}
```

**回應範例**:

```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order form created successfully",
    "data": {
        "orderId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "forms": {
            "CHECK_IN": [
                {
                    "id": 1,
                    "type": "single",
                    "options": ["健康", "需注意", "生病中"],
                    "answer": "健康"
                },
                {
                    "id": 2,
                    "type": "multiple",
                    "options": ["需要散步", "需要餵藥", "需要特殊飲食"],
                    "answer": ["需要散步", "需要餵藥"]
                },
                {
                    "id": 3,
                    "type": "text",
                    "options": null,
                    "answer": "這是測試備註"
                }
            ]
        },
        "otherNote": "這是其他備註",
        "medicalNote": "需要特別注意的醫療資訊",
        "signaturePath": "/uploads/signatures/xxx.png"
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `orderId` | string (UUID) | 住宿訂單ID |
| `forms` | object | 表單答案物件（格式：`type: answers`） |
| `otherNote` | string\|null | 其他備註 |
| `medicalNote` | string\|null | 醫療資訊備註 |
| `signaturePath` | string\|null | 簽名圖 path |

**表單答案物件 (`forms`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `CHECK_IN` | array | CHECK_IN 類型的表單答案陣列（key 為表單類型，value 為答案陣列） |

**表單答案 (`forms[CHECK_IN][]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 住宿問題ID |
| `type` | string | 表單類型（`single`, `multiple`, `text`） |
| `options` | array\|null | 選項列表（`text` 類型為 `null`） |
| `answer` | string\|array | 填寫答案（`single` 和 `text` 為字串，`multiple` 為陣列） |

**使用說明**:

1. **Snapshot 機制**:
   - 系統會自動建立表單內容的快照（snapshot），包含建立表單時所有啟用的住宿單定義
   - 快照儲存在 `hotel_order_forms` 表的 `snapshot` 欄位（JSON 格式）
   - 即使後續住宿單定義被修改或刪除，快照仍保留建立時的原始內容

2. **Answers 儲存**:
   - 使用者填寫的答案儲存在 `answers` 欄位（JSON 格式）
   - 每個答案包含 `id`（問題ID）、`type`（表單類型）、`options`（選項列表）、`answer`（答案）

3. **更新機制**:
   - 如果該訂單已存在相同類型的表單（例如 `CHECK_IN`），則會更新現有表單
   - 更新時會重新建立 snapshot（取得最新的啟用住宿單定義）和更新 answers
   - 返回格式為 `type: answers`，包含該訂單的所有表單（不只剛建立/更新的）

4. **回應格式**:
   - 返回格式為 `type: answers` 的物件格式
   - 一個訂單可能有多個表單類型（如 `CHECK_IN`、`CHECK_OUT` 等）
   - 每個表單類型對應一個答案陣列

5. **驗證規則**:
   - `data.type` 必須為 `"CHECK_IN"`（目前僅支援此類型）
   - `data.forms` 必須為陣列，且至少需包含一個表單
   - 每個表單的 `id`、`type`、`answer` 為必填欄位
   - `type` 必須為 `"single"`、`"multiple"` 或 `"text"`

6. **資料關聯**:
   - 表單會關聯到指定的住宿訂單（`hotel_order_id`）
   - 系統會驗證訂單是否存在，且屬於指定的分店和訂單容器

**錯誤回應範例**:

**訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "HotelOrder not found"
}
```

**無效的表單類型**:
```json
{
    "status": "error",
    "code": 400,
    "message": "Invalid form type. Only CHECK_IN is supported."
}
```

**驗證失敗**:
```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "data.type": ["表單類型為必填欄位。"],
        "data.forms": ["表單答案陣列為必填欄位，且至少需包含一個表單。"],
        "data.forms.0.id": ["住宿問題ID為必填欄位。"],
        "data.forms.0.type": ["表單類型必須為 single、multiple 或 text。"],
        "data.forms.0.answer": ["答案為必填欄位。"]
    }
}
```

#### 取得住宿訂單表單（包含寵物資訊和住宿單題目）

```http
GET /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/form
Authorization: Bearer {token}
```

**說明**: 取得指定住宿訂單的完整表單資料，包含訂單內所有寵物相關資訊和所有住宿單題目。此 API 會返回寵物的完整資訊（包含 `questions` 和 `petQuestions`）以及所有住宿單題目；每個題目包含 `isRequired`（是否必填）、若有填過答案則包含 `answer`。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器UUID | `"3b85a3fb-5cbf-4e78-846c-7cd4dcb04260"` |
| `order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |

**請求範例**:

```http
GET /api/admin/hotel/f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95/order/3b85a3fb-5cbf-4e78-846c-7cd4dcb04260/a1b2c3d4-e5f6-7890-abcd-ef1234567890/form
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:

```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order form with details retrieved successfully",
    "data": {
        "orderId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "guests": [
            {
                "id": "pet-uuid-123",
                "avatar": "https://example.com/storage/xxx.png",
                "name": "小白",
                "gender": "female",
                "breed": "比熊",
                "birthday": "2024-09-12",
                "phone": "",
                "isEvaluationRequired": false,
                "hasServiced": true,
                "category": {
                    "id": 1,
                    "title": "迷你犬",
                    "tags": ["狗"]
                },
                "questions": {
                    "personality": [
                        "粘人"
                    ],
                    "healthy": [
                        null,
                        {
                            "title": "無，狀態良好",
                            "name": "none",
                            "text": null
                        }
                    ],
                    "behavior": [
                        {
                            "title": "分離焦慮症",
                            "name": "SAD",
                            "text": "ee"
                        },
                        {
                            "title": "攻擊過家人",
                            "name": "attack-familly",
                            "text": "ee"
                        }
                    ],
                    "vaccine": 2,
                    "pregnant": [],
                    "livinCondition": [],
                    "healthStatus": [],
                    "skinCondition": [],
                    "historyOfSurgery": [],
                    "skinType": [],
                    "sunProtectionStatus": [],
                    "sunProtectionHabits": [],
                    "makeupRemoverType": [],
                    "skinCareProductsUsed": [],
                    "recentMedicalTreatment": [],
                    "skinProblemYouWantToSolve": []
                },
                "petQuestions": {
                    "ligation": true,
                    "register": true,
                    "chipNumber": "晶片號碼",
                    "fleaMedicineDate": "點除蚤藥日",
                    "vetName": "獸醫院名稱",
                    "vetPhone": "獸醫院電話",
                    "vetAddress": "獸醫院地址"
                }
            }
        ],
        "forms": {
            "CHECK_IN": [
                {
                    "id": 1,
                    "title": "題目1",
                    "type": "single",
                    "options": ["OP1", "OP2"],
                    "isRequired": true,
                    "answer": "OP1"
                },
                {
                    "id": 2,
                    "title": "題目2",
                    "type": "multiple",
                    "options": ["OP1", "OP2"],
                    "isRequired": false,
                    "answer": ["OP1", "OP2"]
                },
                {
                    "id": 3,
                    "title": "題目3",
                    "type": "text",
                    "options": null,
                    "isRequired": false,
                    "answer": "xxxxxxx"
                }
            ]
        },
        "otherNote": "這是其他備註",
        "medicalNote": "需要特別注意的醫療資訊",
        "signaturePath": "/uploads/signatures/xxx.png",
        "terms": "住宿同意條款內容（來自 merchant.hotelSetting.terms）"
    }
}
```

**回應範例**（沒有表單時，從住宿單定義取得題目）:

```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order form with details retrieved successfully",
    "data": {
        "orderId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "guests": [
            {
                "id": "pet-uuid-123",
                "avatar": "https://example.com/storage/xxx.png",
                "name": "小白",
                "gender": "female",
                "breed": "比熊",
                "birthday": "2024-09-12",
                "phone": "",
                "isEvaluationRequired": false,
                "hasServiced": true,
                "category": {
                    "id": 1,
                    "title": "迷你犬",
                    "tags": ["狗"]
                },
                "questions": {},
                "petQuestions": {}
            }
        ],
        "forms": {
            "CHECK_IN": [
                {
                    "id": 1,
                    "title": "題目1",
                    "type": "single",
                    "options": ["OP1", "OP2"],
                    "isRequired": true,
                    "answer": null
                },
                {
                    "id": 2,
                    "title": "題目2",
                    "type": "multiple",
                    "options": ["OP1", "OP2"],
                    "isRequired": false,
                    "answer": null
                }
            ]
        },
        "otherNote": null,
        "medicalNote": null,
        "signaturePath": null,
        "terms": null
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `orderId` | string (UUID) | 子訂單ID |
| `guests` | array | 寵物資訊陣列 |
| `forms` | object | 住宿單題目物件（格式：`type: questions`） |
| `otherNote` | string\|null | 其他備註 |
| `medicalNote` | string\|null | 醫療資訊備註 |
| `signaturePath` | string\|null | 簽名圖 path |
| `terms` | string\|null | 住宿條款內容（來自商家設定 `merchant.hotelSetting.terms`，未設定時為 `null`） |

**寵物資訊 (`guests[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string | 寵物ID |
| `avatar` | string\|null | 寵物頭像 URL |
| `name` | string | 寵物名稱 |
| `gender` | string\|null | 性別（`male`, `female`） |
| `breed` | string\|null | 品種 |
| `birthday` | string\|null | 生日（格式：`Y-m-d`） |
| `phone` | string | 電話（通常為空字串） |
| `isEvaluationRequired` | boolean | 是否需要評估（需評估為 true，不需評估為 false） |
| `hasServiced` | boolean | 是否已服務過 |
| `category` | object\|null | 寵物分類資訊 |
| `questions` | object | 寵物問題回答（結構化資料） |
| `petQuestions` | object | 寵物相關問題（結紮、登記、晶片等） |

**寵物分類 (`guests[].category`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 分類ID |
| `title` | string | 分類標題 |
| `tags` | array | 父分類標題陣列（例如：`["狗"]`） |

**寵物問題 (`guests[].questions`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `personality` | array | 個性相關問題 |
| `healthy` | array | 健康相關問題 |
| `behavior` | array | 行為相關問題 |
| `vaccine` | integer\|null | 疫苗相關 |
| `pregnant` | array | 懷孕相關 |
| `livinCondition` | array | 居住條件 |
| `healthStatus` | array | 健康狀態 |
| `skinCondition` | array | 皮膚狀況 |
| `historyOfSurgery` | array | 手術歷史 |
| `skinType` | array | 皮膚類型 |
| `sunProtectionStatus` | array | 防曬狀態 |
| `sunProtectionHabits` | array | 防曬習慣 |
| `makeupRemoverType` | array | 卸妝類型 |
| `skinCareProductsUsed` | array | 使用的護膚產品 |
| `recentMedicalTreatment` | array | 近期醫療治療 |
| `skinProblemYouWantToSolve` | array | 想解決的皮膚問題 |

**寵物相關問題 (`guests[].petQuestions`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `ligation` | boolean | 是否結紮 |
| `register` | boolean | 是否登記 |
| `chipNumber` | string\|null | 晶片號碼 |
| `fleaMedicineDate` | string\|null | 點除蚤藥日期 |
| `vetName` | string\|null | 獸醫院名稱 |
| `vetPhone` | string\|null | 獸醫院電話 |
| `vetAddress` | string\|null | 獸醫院地址 |

**住宿單題目物件 (`forms`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `CHECK_IN` | array | CHECK_IN 類型的住宿單題目陣列 |

**住宿單題目 (`forms[CHECK_IN][]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 住宿問題ID |
| `title` | string | 住宿題目 |
| `type` | string | 表單類型（`single`, `multiple`, `text`） |
| `options` | array\|null | 選項列表（`text` 類型為 `null`） |
| `isRequired` | boolean | 是否必填 |
| `answer` | string\|array\|null | 填寫答案（`single` 和 `text` 為字串，`multiple` 為陣列，未填寫為 `null`） |

**使用說明**:

1. **寵物資訊**:
   - 返回訂單內所有寵物的完整資訊
   - 包含寵物的基本資訊、分類、問題回答和寵物相關問題
   - `avatar` 會自動轉換為完整的 URL

2. **住宿單題目**:
   - 如果訂單已有表單，從表單的 `snapshot` 取得題目定義，並合併已填寫的答案
   - 如果訂單沒有表單，從現有的住宿單定義（`BoardingForm`）取得所有啟用的題目，但 `answer` 為 `null`
   - 題目按表單類型（如 `CHECK_IN`）分組

3. **答案處理**:
   - 如果題目已填寫過答案，`answer` 欄位會包含答案內容
   - 如果題目未填寫，`answer` 為 `null`
   - `single` 類型的答案為字串
   - `multiple` 類型的答案為陣列
   - `text` 類型的答案為字串

4. **住宿條款 (`terms`)**:
   - 來自商家設定的 `merchant.hotelSetting.terms`
   - 供前端顯示住宿同意條款（例如表單送出前勾選用）
   - 商家未設定時為 `null`

**錯誤回應範例**:

**訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "HotelOrder not found"
}
```

#### 處理訂單操作

```http
POST /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/action
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 處理住宿訂單的確認（CONFIRM）、取消（CANCEL）、拒絕（REJECT）、入住（CHECK_IN）、未到（NO_SHOW）或退房（CHECK_OUT）操作。每次操作會寫入 `hotel_order_histories`，並**連動更新該訂單下所有 `hotel_booking` 的狀態**與 `hotel_order` 一致。**取消與退款分離**：CANCEL 不觸發退款；已付款子單退款請使用 v2 refund API。拒絕會發送通知給用戶。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器 UUID | `"13ba86b9-522b-430e-9b8d-2dd214663e87"` |
| `order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `action` | string | ✅ | 操作類型 | `"CONFIRM"`, `"CANCEL"`, `"REJECT"`, `"CHECK_IN"`, `"NO_SHOW"` 或 `"CHECK_OUT"` |
| `reason` | string | ❌ | 操作原因（最多 1000 字元，CONFIRM/CHECK_IN/NO_SHOW/CHECK_OUT 時可選） | `"客戶要求取消"` |
| `signaturePath` | string | ❌ | 退房簽名圖路徑（僅 CHECK_OUT 時使用；有傳入則透過 `order_signatures` 存入） | `"/storage/signatures/xxx.png"` |

**action 可選值**:

| 值 | 說明 |
|----|------|
| `"CONFIRM"` | 確認接單（將訂單狀態改為 `UNPAID`） |
| `"CANCEL"` | 取消訂單（**不觸發退款**；退款請用 v2 refund API） |
| `"REJECT"` | 拒絕訂單（會發送通知給用戶） |
| `"CHECK_IN"` | 入住（僅限 PAID 訂單，將狀態改為 `CHECK_IN`） |
| `"NO_SHOW"` | 未到（僅限 PAID 訂單，將狀態改為 `NO_SHOW`） |
| `"CHECK_OUT"` | 退房（僅限 CHECK_IN 訂單，將狀態改為 `CHECK_OUT`） |

**請求範例（確認訂單）**:

```json
{
    "action": "CONFIRM",
    "reason": "確認接單備註"
}
```

**請求範例（取消訂單）**:

```json
{
    "action": "CANCEL",
    "reason": "客戶要求取消"
}
```

**請求範例（拒絕訂單）**:

```json
{
    "action": "REJECT",
    "reason": "房間已滿"
}
```

**請求範例（入住）**:

```json
{
    "action": "CHECK_IN",
    "reason": "入住備註"
}
```

**請求範例（未到）**:

```json
{
    "action": "NO_SHOW",
    "reason": "客人未到"
}
```

**請求範例（退房）**:

```json
{
    "action": "CHECK_OUT",
    "reason": "退房備註"
}
```

**請求範例（退房＋簽名）**:

```json
{
    "action": "CHECK_OUT",
    "reason": "退房備註",
    "signaturePath": "/storage/signatures/xxx.png"
}
```

**回應範例**:

```json
{
    "status": "HOTEL_ORDER_ACTION_PROCESSED_SUCCESSFULLY",
    "code": 200,
    "message": "Hotel order action processed successfully",
    "data": {
        "order": {
            "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "displayId": "HO-20260117-001",
            "status": "CANCEL",
            "updatedAt": "2026-01-17 12:34:56"
        },
        "histories": [
            {
                "id": 123,
                "action": "CANCEL",
                "status": "CANCEL",
                "reason": "客戶要求取消",
                "log": {
                    "status": {
                        "original": "PAID",
                        "updated": "CANCEL"
                    }
                },
                "createdAt": "2026-01-17 12:34:56",
                "createdBy": "張三"
            }
        ]
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `order` | object | 訂單基本資訊 |
| `histories` | array | 訂單歷史記錄列表（按建立時間降序排列） |

**order 物件**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 訂單ID |
| `displayId` | string | 顯示用訂單編號 |
| `status` | string | 訂單狀態（CONFIRM 後會變為 `UNPAID`，CANCEL/REJECT 後會變為 `CANCEL`，CHECK_IN 後會變為 `CHECK_IN`，NO_SHOW 後會變為 `NO_SHOW`，CHECK_OUT 後會變為 `CHECK_OUT`） |
| `updatedAt` | string\|null | 最後更新時間（格式：YYYY-MM-DD HH:mm:ss） |

**histories 陣列中的物件**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 歷史記錄ID |
| `action` | string | 操作類型（`CONFIRM`、`CANCEL`、`REJECT`、`CHECK_IN`、`NO_SHOW` 或 `CHECK_OUT`） |
| `status` | string | 記錄時的訂單狀態 |
| `reason` | string | 操作原因 |
| `log` | object | 異動記錄（JSON 格式） |
| `log.status.original` | string\|null | 原始狀態 |
| `log.status.updated` | string | 更新後的狀態 |
| `createdAt` | string | 建立時間（格式：YYYY-MM-DD HH:mm:ss，根據商家時區） |
| `createdBy` | string | 建立者名稱（或 "系統建置"） |

**各 action 實際處理邏輯**:

| action | 前置條件 | 處理邏輯 |
|--------|----------|----------|
| **CANCEL** | 無 | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **CANCEL**（**不觸發退款**）。若已是 CANCEL 則不寫 history、不更新。 |
| **REJECT** | 無 | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **CANCEL** → 發送拒絕通知（email/sms）。若已是 CANCEL 則不寫 history、不更新。 |
| **CONFIRM** | 訂單不得為 CANCEL | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **UNPAID** → 若原為 PENDING 則清除 container 的 `reserved_at`。 |
| **CHECK_IN** | 僅限 PAID（或已是 CHECK_IN） | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **CHECK_IN**。 |
| **NO_SHOW** | 僅限 PAID | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **NO_SHOW**（不觸發退款）。 |
| **CHECK_OUT** | 僅限 CHECK_IN（或已是 CHECK_OUT） | 寫入 history → `hotel_order` 與該訂單下所有 `hotel_booking` 狀態改為 **CHECK_OUT**。 |

**使用說明**:

1. **連動 hotel_booking**：上述各 action 執行後，該 `hotel_order` 底下的所有 `hotel_booking` 狀態會一併更新為與訂單相同狀態。
2. **狀態與 history**：若訂單已是目標狀態，CANCEL/REJECT 不再寫 history；CHECK_IN/CHECK_OUT/NO_SHOW 仍會寫入一筆 history。
3. **歷史記錄**：回應中的 `histories` 為該訂單在 `hotel_order_histories` 的記錄，按建立時間降序排列。

#### 住宿子單退款（v2）

```http
POST /api/v2/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/refund
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 對**單一** `hotel_order` 子單執行**全額退款**（金額固定為該子單 `total`）。與 CANCEL **獨立**（可先退後取消、只退不取消）。每子單僅可退款一次。金流紀錄寫入 `orders_refunds_histories`（`order_id` = 容器 `displayId`）；子單歸屬寫入 `hotel_order_histories`（`action` = `REFUND`）。

**路徑參數**: 同「處理訂單操作」。

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `refundDate` | string | ✅ | 退款日期 `Y-m-d` | `"2026-07-07"` |
| `paymentMethod` | string | ✅ | 退款方式（須與容器付款方式一致） | `"Cash"` |
| `note` | string | ❌ | 備註 | `"客戶要求退款"` |
| `bonus` | integer | ❌ | 儲值金退款 bonus | `0` |

**請求範例**:

```json
{
    "refundDate": "2026-07-07",
    "paymentMethod": "Cash",
    "note": "客戶要求退款"
}
```

**前置條件**:

- 容器 `paymentStatus` 須為 `PAID`
- 該子單尚未有 `hotel_order_histories.action = REFUND` 紀錄
- 子單 `total` ≤ 容器可退餘額 `backBalance`

**錯誤回應範例**（尚未付款）:

```json
{
    "status": "error",
    "code": 422,
    "message": "Order is not paid"
}
```

**錯誤回應範例**（子單已退款）:

```json
{
    "status": "error",
    "code": 422,
    "message": "Hotel order has already been refunded"
}
```

**錯誤回應範例**:

**訂單不存在**:

```json
{
    "status": "error",
    "code": 500,
    "message": "HotelOrder not found"
}
```

**無效的操作類型**:

```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "action": ["操作類型必須為 CONFIRM、CANCEL、REJECT、CHECK_IN、NO_SHOW 或 CHECK_OUT。"]
    }
}
```

**缺少必填欄位**:

```json
{
    "status": "error",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "action": ["操作類型為必填欄位。"]
    }
}
```

#### 建立分期付款

```http
POST /api/admin/hotel/{product_id}/order/{container_uuid}/installment
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 為指定的住宿訂單容器建立分期付款方案。此 API 會根據提供的分期金額陣列，建立多筆分期付款記錄，並將訂單容器的付款方式更新為 `Installment`。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95"` |
| `container_uuid` | string (UUID) | ✅ | 訂單容器UUID | `"52373aac-a07d-49e6-909f-3fbd52413f7f"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `paymentMethod` | string | ❌ | 付款方式（若未提供會自動設為 `"Installment"`） | `"Installment"` |
| `installments` | array | ✅ | 分期付款金額陣列，每個元素代表一期的金額 | `[1000, 1000, 1000]` |
| `installments[*]` | number | ✅ | 每期付款金額（必須為數字，最多兩位小數，最小值為 0） | `1000` 或 `1000.5` 或 `1000.99` |

**參數驗證規則**:
- `product_id` 必須為有效的 UUID 格式
- `container_uuid` 必須為有效的 UUID 格式
- `paymentMethod` 必須為 `"Installment"`（若未提供會自動設定）
- `installments` 必須為陣列，且至少包含一筆分期付款
- 每筆分期金額必須：
  - 為數字
  - 大於等於 0
  - 格式正確（最多兩位小數）
- **分期總額必須等於訂單總額**（此驗證在後端執行）

**請求範例**:
```http
POST /api/admin/hotel/f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95/order/52373aac-a07d-49e6-909f-3fbd52413f7f/installment
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

```json
{
    "paymentMethod": "Installment",
    "installments": [1000, 1000, 1000]
}
```

**簡化請求範例**（`paymentMethod` 可省略）:
```json
{
    "installments": [1000, 1000, 1000]
}
```

**回應範例**:
```json
{
    "status": "HOTEL_ORDER_INSTALLMENT_CREATED_SUCCESSFULLY",
    "code": 200,
    "message": "Hotel order installment created successfully",
    "data": {
        "container": {
            "uuid": "52373aac-a07d-49e6-909f-3fbd52413f7f",
            "displayId": "ORD-20260126-001",
            "paymentMethod": "Installment",
            "paymentStatus": "UNPAID",
            "total": 3000
        },
        "installments": [
            {
                "id": 1,
                "no": 1,
                "amount": 1000,
                "paymentStatus": "UNPAID",
                "paymentMethod": null,
                "dueDate": null,
                "note": null,
                "paidAt": null
            },
            {
                "id": 2,
                "no": 2,
                "amount": 1000,
                "paymentStatus": "UNPAID",
                "paymentMethod": null,
                "dueDate": null,
                "note": null,
                "paidAt": null
            },
            {
                "id": 3,
                "no": 3,
                "amount": 1000,
                "paymentStatus": "UNPAID",
                "paymentMethod": null,
                "dueDate": null,
                "note": null,
                "paidAt": null
            }
        ]
    }
}
```

**回應欄位說明**:

**`data.container` 欄位**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `uuid` | string | 訂單容器的 UUID |
| `displayId` | string | 訂單顯示 ID（如 "ORD-20260126-001"） |
| `paymentMethod` | string | 付款方式（已更新為 "Installment"） |
| `paymentStatus` | string | 付款狀態（"UNPAID" 或 "PAID"） |
| `total` | integer | 訂單總額 |

**`data.installments[]` 欄位**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 分期付款記錄 ID |
| `no` | integer | 期數（從 1 開始） |
| `amount` | integer | 該期金額 |
| `paymentStatus` | string | 付款狀態（"UNPAID" 或 "PAID"） |
| `paymentMethod` | string \| null | 該期的付款方式（可能為 null） |
| `dueDate` | string \| null | 到期日（格式：`Y-m-d H:i:s`，使用商家時區，可能為 null） |
| `note` | string \| null | 備註（可能為 null） |
| `paidAt` | string \| null | 付款時間（格式：`Y-m-d H:i:s`，僅在 `paymentStatus` 為 "PAID" 時有值，否則為 null） |

**使用說明**:

1. **分期總額驗證**:
   - 系統會自動計算訂單容器內所有 `hotel_orders` 的總額
   - 提供的分期金額總和必須等於訂單總額，否則會回傳錯誤

2. **付款方式**:
   - `paymentMethod` 參數可省略，系統會自動設為 `"Installment"`
   - 建立分期付款後，訂單容器的 `paymentMethod` 會自動更新為 `"Installment"`

3. **分期付款記錄**:
   - 每筆分期付款會建立一筆 `OrderInstallment` 記錄
   - 初始狀態為 `"UNPAID"`
   - 期數（`no`）會自動從 1 開始遞增

4. **日期時間格式**:
   - 所有日期時間欄位使用商家時區（`merchant->timezone`），預設為 `Asia/Taipei`
   - 格式為 `Y-m-d H:i:s`

**錯誤回應範例**:

**驗證錯誤**:
```json
{
    "status": "VALIDATION_ERROR",
    "code": 422,
    "message": "驗證失敗",
    "errors": {
        "installments": [
            "分期付款明細為必填"
        ],
        "installments.0": [
            "每筆分期金額格式錯誤（最多兩位小數）"
        ]
    }
}
```

**訂單容器不存在**:
```json
{
    "status": "error",
    "code": 500,
    "message": "OrderContainer not found"
}
```

**分期總額不等於訂單總額**:
```json
{
    "status": "error",
    "code": 500,
    "message": "Installments total must equal order total"
}
```

**付款方式錯誤**:
```json
{
    "status": "error",
    "code": 500,
    "message": "Payment method must be Installment"
}
```

**注意事項**:

1. 此 API 僅適用於 `type` 為 `"hotel"` 的訂單容器
2. 訂單總額是從該容器內所有 `hotel_orders` 的 `total` 欄位計算而來
3. 建立分期付款後，無法直接透過此 API 修改，需要使用其他 API 來更新分期付款狀態
4. 每筆分期付款的 `dueDate` 初始為 `null`，可後續透過其他 API 設定

#### 住宿訂單寵物服務記錄管理 API

本節說明住宿訂單寵物的服務記錄（`type = hotel`）的 CRUD API，用於記錄寵物在住宿期間的服務情況、注意事項等資訊。

##### 取得住宿訂單寵物的服務記錄列表

```http
GET /api/admin/hotel/order/{hotel_order_id}/pet/{pet_id}/comments
Authorization: Bearer {token}
```

**說明**: 取得指定住宿訂單和寵物的服務記錄列表（`type = hotel`），支援分頁查詢。記錄依 `created_at desc` 排序（最新的在前）。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 限制只能查看自己商家的記錄
- 🔍 依住宿訂單和寵物篩選服務記錄

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `hotel_order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |
| `pet_id` | integer | ✅ | 寵物ID | `12` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `page` | integer | ❌ | 頁碼（預設：1） | `1` |
| `perPage` | integer | ❌ | 每頁筆數（預設：15，最大：100） | `15` |

**參數驗證規則**:
- `hotel_order_id` 必須為有效的 UUID 格式
- `pet_id` 必須為有效的整數
- `page` 必須為正整數（最小 1）
- `perPage` 必須為正整數（最小 1，最大 100）

**請求範例**:
```http
GET /api/admin/hotel/order/a1b2c3d4-e5f6-7890-abcd-ef1234567890/pet/12/comments?page=1&perPage=15
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order pet comments retrieved successfully",
    "data": {
        "items": [
            {
                "id": "comment-uuid-1",
                "orderNo": "IPFIJ14P001VIFXU",
                "memberId": "member-uuid",
                "petId": 12,
                "type": "hotel",
                "content": "寵物今天食慾良好，活動力正常。",
                "images": [
                    "https://example.com/image1.jpg"
                ],
                "questions": {
                    "健康狀況": "良好"
                },
                "external_files": [
                    "https://example.com/file1.pdf"
                ],
                "creator": {
                    "id": 1,
                    "avatar": "https://example.com/staff.jpg",
                    "name": "服務人員小王",
                    "email": "staff@example.com"
                },
                "createdAt": "2026-01-15T10:30:00+08:00",
                "updatedAt": "2026-01-15T10:30:00+08:00"
            },
            {
                "id": "comment-uuid-2",
                "orderNo": "IPFIJ14P001VIFXU",
                "memberId": "member-uuid",
                "petId": 12,
                "type": "hotel",
                "content": "入住時寵物有些緊張，已安撫。",
                "images": [],
                "questions": {},
                "external_files": [],
                "creator": {
                    "id": 1,
                    "avatar": null,
                    "name": "服務人員小王",
                    "email": "staff@example.com"
                },
                "createdAt": "2026-01-14T14:20:00+08:00",
                "updatedAt": "2026-01-14T14:20:00+08:00"
            }
        ],
        "pagination": {
            "page": 1,
            "perPage": 15,
            "total": 2,
            "lastPage": 1
        }
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `items` | array | 服務記錄列表 |
| `pagination` | object | 分頁資訊 |

**服務記錄資料 (`data.items[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 服務記錄ID |
| `orderNo` | string | 訂單編號（`hotel_orders.display_id`） |
| `memberId` | string (UUID) \| null | 會員ID |
| `petId` | integer | 寵物ID |
| `type` | string | 記錄類型（固定為 `"hotel"`） |
| `content` | string \| null | 服務記錄內容 |
| `images` | array | 圖片URL陣列 |
| `questions` | object | 問答資料物件 |
| `external_files` | array | 外部檔案URL陣列 |
| `creator` | object | 建立者資訊 |
| `creator.id` | integer | 建立者使用者ID |
| `creator.avatar` | string \| null | 建立者頭像URL |
| `creator.name` | string | 建立者姓名 |
| `creator.email` | string \| null | 建立者Email |
| `createdAt` | string | 建立時間（商家時區，ISO 8601 格式） |
| `updatedAt` | string | 更新時間（商家時區，ISO 8601 格式） |

**分頁資訊 (`data.pagination`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `page` | integer | 當前頁碼 |
| `perPage` | integer | 每頁筆數 |
| `total` | integer | 總筆數 |
| `lastPage` | integer | 最後一頁頁碼 |

**錯誤回應範例**:

**住宿訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Hotel order not found"
}
```

**寵物不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Pet not found"
}
```

##### 取得寵物的歷史記錄列表（包含住宿記錄、寵物備註和服務記錄）

```http
GET /api/admin/hotel/pet/{pet_id}/history-comments
Authorization: Bearer {token}
```

**說明**: 取得指定寵物的歷史記錄列表，包含住宿記錄（`type = hotel`）、寵物備註（`type = pet`）和服務記錄（`type = service`），支援分頁查詢。記錄依 `created_at desc` 排序（最新的在前）。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 限制只能查看自己商家的記錄
- 🔍 依寵物篩選歷史記錄（包含住宿記錄、寵物備註和服務記錄）

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `pet_id` | integer | ✅ | 寵物ID | `12` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `page` | integer | ❌ | 頁碼（預設：1） | `1` |
| `perPage` | integer | ❌ | 每頁筆數（預設：15，最大：100） | `15` |

**參數驗證規則**:
- `pet_id` 必須為有效的整數
- `page` 必須為正整數（最小 1）
- `perPage` 必須為正整數（最小 1，最大 100）

**請求範例**:
```http
GET /api/admin/hotel/pet/12/history-comments?page=1&perPage=15
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Pet history comments retrieved successfully",
    "data": {
        "items": [
            {
                "id": "comment-uuid-1",
                "orderNo": "IPFIJ14P001VIFXU",
                "memberId": "member-uuid",
                "petId": 12,
                "type": "pet",
                "content": "寵物最近食慾良好，體重穩定。",
                "images": [],
                "questions": {
                    "健康狀況": "良好"
                },
                "external_files": [],
                "creator": {
                    "id": 1,
                    "avatar": "https://example.com/staff.jpg",
                    "name": "服務人員小王",
                    "email": "staff@example.com"
                },
                "createdAt": "2026-01-16T10:30:00+08:00",
                "updatedAt": "2026-01-16T10:30:00+08:00"
            },
            {
                "id": "comment-uuid-2",
                "orderNo": "IPFIJ14P001VIFXU",
                "memberId": "member-uuid",
                "petId": 12,
                "type": "hotel",
                "content": "住宿期間寵物適應良好，活動力正常。",
                "images": [
                    "https://example.com/image1.jpg"
                ],
                "questions": {
                    "健康狀況": "良好",
                    "飲食情況": "正常"
                },
                "external_files": [],
                "creator": {
                    "id": 1,
                    "avatar": null,
                    "name": "服務人員小王",
                    "email": "staff@example.com"
                },
                "createdAt": "2026-01-15T14:20:00+08:00",
                "updatedAt": "2026-01-15T14:20:00+08:00"
            },
            {
                "id": "comment-uuid-3",
                "orderNo": "SVC-20260110-001",
                "memberId": "member-uuid",
                "petId": 12,
                "type": "service",
                "content": "美容服務：此寵物毛量多，服務時間需加長。",
                "images": [],
                "questions": {
                    "服務類型": "美容",
                    "注意事項": "毛量多"
                },
                "external_files": [],
                "creator": {
                    "id": 3,
                    "avatar": "https://example.com/groomer.jpg",
                    "name": "美容師小張",
                    "email": "groomer@example.com"
                },
                "createdAt": "2026-01-14T11:00:00+08:00",
                "updatedAt": "2026-01-14T11:00:00+08:00"
            },
            {
                "id": "comment-uuid-4",
                "orderNo": null,
                "memberId": "member-uuid",
                "petId": 12,
                "type": "pet",
                "content": "定期健康檢查記錄",
                "images": [],
                "questions": {},
                "external_files": [
                    "https://example.com/health-report.pdf"
                ],
                "creator": {
                    "id": 2,
                    "avatar": "https://example.com/vet.jpg",
                    "name": "獸醫師小李",
                    "email": "vet@example.com"
                },
                "createdAt": "2026-01-10T09:00:00+08:00",
                "updatedAt": "2026-01-10T09:00:00+08:00"
            }
        ],
        "pagination": {
            "page": 1,
            "perPage": 15,
            "total": 4,
            "lastPage": 1
        }
    }
}
```

**回應欄位說明**:

**頂層結構**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `items` | array | 歷史記錄列表 |
| `pagination` | object | 分頁資訊 |

**歷史記錄資料 (`data.items[]`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 記錄ID |
| `orderNo` | string \| null | 訂單編號（`hotel_orders.display_id` 或服務訂單編號，`type = hotel` 或 `type = service` 時可能有值） |
| `memberId` | string (UUID) \| null | 會員ID |
| `petId` | integer | 寵物ID |
| `type` | string | 記錄類型（`"pet"`、`"hotel"` 或 `"service"`） |
| `content` | string \| null | 記錄內容 |
| `images` | array | 圖片URL陣列 |
| `questions` | object | 問答資料物件 |
| `external_files` | array | 外部檔案URL陣列 |
| `creator` | object | 建立者資訊 |
| `creator.id` | integer | 建立者使用者ID |
| `creator.avatar` | string \| null | 建立者頭像URL |
| `creator.name` | string | 建立者姓名 |
| `creator.email` | string \| null | 建立者Email |
| `createdAt` | string | 建立時間（商家時區，ISO 8601 格式） |
| `updatedAt` | string | 更新時間（商家時區，ISO 8601 格式） |

**分頁資訊 (`data.pagination`)**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `page` | integer | 當前頁碼 |
| `perPage` | integer | 每頁筆數 |
| `total` | integer | 總筆數 |
| `lastPage` | integer | 最後一頁頁碼 |

**注意事項**:
- 此 API 會同時查詢 `type = hotel`（住宿記錄）和 `type = pet`（寵物備註）兩種記錄
- `type = hotel` 的記錄會有 `orderNo` 欄位（對應住宿訂單的 `display_id`）
- `type = pet` 的記錄 `orderNo` 為 `null`
- 記錄依 `created_at desc` 排序，最新的記錄在前

**錯誤回應範例**:

**寵物不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Pet not found"
}
```

##### 建立住宿訂單寵物的服務記錄

```http
POST /api/admin/hotel/order/{hotel_order_id}/pet/{pet_id}/comment
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 為指定的住宿訂單和寵物建立新的服務記錄（`type = hotel`）。記錄會自動關聯到該訂單的 `display_id` 和寵物。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 限制只能為自己商家的訂單建立記錄
- ✏️ 驗證訂單和寵物是否存在

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `hotel_order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |
| `pet_id` | integer | ✅ | 寵物ID | `12` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `content` | string | ❌ | 服務記錄內容 | `"寵物今天食慾良好，活動力正常。"` |
| `images` | array | ❌ | 圖片URL陣列 | `["https://example.com/image1.jpg"]` |
| `questions` | object | ❌ | 問答資料物件 | `{"健康狀況": "良好"}` |
| `external_files` | array | ❌ | 外部檔案URL陣列 | `["https://example.com/file1.pdf"]` |

**參數驗證規則**:
- `hotel_order_id` 必須為有效的 UUID 格式
- `pet_id` 必須為有效的整數
- `images` 必須為陣列
- `questions` 必須為物件
- `external_files` 必須為陣列

**請求範例**:
```http
POST /api/admin/hotel/order/a1b2c3d4-e5f6-7890-abcd-ef1234567890/pet/12/comment
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

```json
{
    "content": "寵物今天食慾良好，活動力正常。",
    "images": [
        "https://example.com/image1.jpg"
    ],
    "questions": {
        "健康狀況": "良好",
        "飲食情況": "正常"
    },
    "external_files": [
        "https://example.com/file1.pdf"
    ]
}
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order pet comment created successfully",
    "data": {
        "id": "comment-uuid",
        "orderNo": "IPFIJ14P001VIFXU",
        "memberId": "member-uuid",
        "petId": 12,
        "type": "hotel",
        "content": "寵物今天食慾良好，活動力正常。",
        "images": [
            "https://example.com/image1.jpg"
        ],
        "questions": {
            "健康狀況": "良好",
            "飲食情況": "正常"
        },
        "external_files": [
            "https://example.com/file1.pdf"
        ],
        "creator": {
            "id": 1,
            "avatar": "https://example.com/staff.jpg",
            "name": "服務人員小王",
            "email": "staff@example.com"
        },
        "createdAt": "2026-01-15T10:30:00+08:00",
        "updatedAt": "2026-01-15T10:30:00+08:00"
    }
}
```

**回應欄位說明**: 同「取得住宿訂單寵物的服務記錄列表」的 `items[]` 欄位說明。

**錯誤回應範例**:

**住宿訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Hotel order not found"
}
```

**寵物不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Pet not found"
}
```

##### 更新住宿訂單寵物的服務記錄

```http
PATCH /api/admin/hotel/order/{hotel_order_id}/pet/{pet_id}/comment/{comment_uuid}
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 更新指定住宿訂單和寵物的服務記錄。支援部分更新，只傳入需要更新的欄位即可。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 限制只能更新自己商家的記錄
- ✏️ 驗證訂單、寵物和記錄是否存在

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `hotel_order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |
| `pet_id` | integer | ✅ | 寵物ID | `12` |
| `comment_uuid` | string (UUID) | ✅ | 服務記錄UUID | `"comment-uuid"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `content` | string | ❌ | 服務記錄內容 | `"更新後的內容"` |
| `images` | array | ❌ | 圖片URL陣列 | `["https://example.com/image2.jpg"]` |
| `questions` | object | ❌ | 問答資料物件 | `{"健康狀況": "需注意"}` |
| `external_files` | array | ❌ | 外部檔案URL陣列 | `["https://example.com/file2.pdf"]` |

**參數驗證規則**: 同「建立住宿訂單寵物的服務記錄」。

**請求範例**:
```http
PATCH /api/admin/hotel/order/a1b2c3d4-e5f6-7890-abcd-ef1234567890/pet/12/comment/comment-uuid
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

```json
{
    "content": "更新後的服務記錄內容",
    "images": [
        "https://example.com/image2.jpg"
    ],
    "questions": {
        "健康狀況": "需注意"
    }
}
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order pet comment updated successfully",
    "data": {
        "id": "comment-uuid",
        "orderNo": "IPFIJ14P001VIFXU",
        "memberId": "member-uuid",
        "petId": 12,
        "type": "hotel",
        "content": "更新後的服務記錄內容",
        "images": [
            "https://example.com/image2.jpg"
        ],
        "questions": {
            "健康狀況": "需注意"
        },
        "external_files": [
            "https://example.com/file1.pdf"
        ],
        "creator": {
            "id": 1,
            "avatar": "https://example.com/staff.jpg",
            "name": "服務人員小王",
            "email": "staff@example.com"
        },
        "createdAt": "2026-01-15T10:30:00+08:00",
        "updatedAt": "2026-01-15T11:00:00+08:00"
    }
}
```

**回應欄位說明**: 同「取得住宿訂單寵物的服務記錄列表」的 `items[]` 欄位說明。

**錯誤回應範例**:

**服務記錄不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Comment not found"
}
```

**住宿訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Hotel order not found"
}
```

**寵物不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Pet not found"
}
```

##### 刪除住宿訂單寵物的服務記錄

```http
DELETE /api/admin/hotel/order/{hotel_order_id}/pet/{pet_id}/comment/{comment_uuid}
Authorization: Bearer {token}
```

**說明**: 刪除指定住宿訂單和寵物的服務記錄。使用 soft delete，記錄不會真正從資料庫移除，但不會出現在正常查詢結果中。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 限制只能刪除自己商家的記錄
- 🗑️ 驗證訂單、寵物和記錄是否存在

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `hotel_order_id` | string (UUID) | ✅ | 住宿訂單ID | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |
| `pet_id` | integer | ✅ | 寵物ID | `12` |
| `comment_uuid` | string (UUID) | ✅ | 服務記錄UUID | `"comment-uuid"` |

**請求範例**:
```http
DELETE /api/admin/hotel/order/a1b2c3d4-e5f6-7890-abcd-ef1234567890/pet/12/comment/comment-uuid
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
```

**回應範例**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Hotel order pet comment deleted successfully",
    "data": null
}
```

**錯誤回應範例**:

**服務記錄不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Comment not found"
}
```

**住宿訂單不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Hotel order not found"
}
```

**寵物不存在**:
```json
{
    "status": "error",
    "code": 404,
    "message": "Pet not found"
}
```

**使用說明**:

1. **記錄類型**: 所有記錄的 `type` 欄位固定為 `"hotel"`，`order_id` 欄位存入 `hotel_orders.display_id`
2. **排序**: 查詢列表時，記錄依 `created_at desc` 排序（最新的在前）
3. **分頁**: 查詢列表支援分頁，預設每頁 15 筆，最多 100 筆
4. **刪除**: 使用 soft delete，刪除後的記錄不會出現在正常查詢結果中，但資料仍保留在資料庫中
5. **權限**: 所有操作都限制在當前商家範圍內，無法跨商家操作

### 會員通知 API

本節說明商家後台發送自訂通知給會員的 API。

#### 發送會員通知

```http
POST /api/admin/merchant/member/notification/send
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 依指定模式（會員、訂單、預付訂單、住宿訂單）發送自訂通知給會員，可選擇以 Email、簡訊或兩者同時發送。請求會以非同步 Job 處理，成功時立即回傳成功狀態。

**中介層**: `hasMerchant`, `expired`, `admin`
- 驗證商家管理員身份
- 檢查商家訂閱狀態
- 驗證管理員權限（需具備 `sendNotification` 權限）

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `mode` | string | ✅ | 發送模式 | `"byMember"`, `"byOrder"`, `"byPrepaidOrder"`, `"byHotel"` |
| `memberIds` | array | ❌ | 會員 ID 陣列（mode 為 `byMember` 時使用；不傳或空陣列時依下方搜尋條件篩選會員） | `["uuid-1", "uuid-2"]` |
| `orderIds` | array | 條件必填 | 訂單 ID 陣列（mode 為 `byOrder` 或 `byPrepaidOrder` 時必填） | `[1, 2, 3]` |
| `hotelOrderId` | string | 條件必填 | 住宿訂單 ID（mode 為 `byHotel` 時必填） | `"uuid-hotel-order"` |
| `templateContent` | string | ✅ | 通知內容（支援 HTML） | `"<p>親愛的會員您好...</p>"` |
| `router` | string | ✅ | 發送管道 | `"email"`, `"sms"`, `"all"` |
| `subject` | string | 條件必填 | 信件主旨（router 為 `email` 或 `all` 時必填） | `"重要通知"` |
| `sendWhoType` | string | 條件必填 | 訂單通知對象（mode 為 `byOrder` 時必填） | `"onlyUser"`, `"onlyBuyer"`, `"all"` |

**mode 可選值**:

| 值 | 說明 |
|----|------|
| `byMember` | 依會員發送：可指定 `memberIds`，或省略/空陣列並搭配下方搜尋參數篩選會員 |
| `byOrder` | 依一般訂單發送：必填 `orderIds`，並以 `sendWhoType` 決定發給訂單使用者或購買者或全部 |
| `byPrepaidOrder` | 依預付訂單發送：必填 `orderIds`，僅發給訂單使用者 |
| `byHotel` | 依住宿訂單發送：必填 `hotelOrderId`，發給該住宿訂單所屬會員 |

**router 可選值**:

| 值 | 說明 |
|----|------|
| `email` | 僅 Email |
| `sms` | 僅簡訊 |
| `all` | Email + 簡訊 |

**sendWhoType 可選值**（僅 mode 為 `byOrder` 時使用）:

| 值 | 說明 |
|----|------|
| `onlyUser` | 僅訂單使用者（會員） |
| `onlyBuyer` | 僅購買者 |
| `all` | 使用者與購買者皆發送 |

**mode 為 `byMember` 且未傳 `memberIds`（或為空陣列）時，可額外傳入以下搜尋參數，篩選要發送通知的會員**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `tagIds` | array | ❌ | 會員標籤 ID 陣列 | `[1, 2]` |
| `memberLevelIds` | array | ❌ | 會員等級 ID 陣列 | `["uuid-1"]` |
| `block` | boolean | ❌ | 是否封鎖 | `false` |
| `lineNotifyStatus` | boolean | ❌ | LINE 通知狀態 | `true` |
| `gender` | string | ❌ | 性別 | `"male"` |
| `birthdayMonth` | integer | ❌ | 生日月份 (1–12) | `6` |
| `monthsWithoutOrder` | integer | ❌ | 超過 N 個月無訂單 | `3` |
| `startDate` | string | ❌ | 註冊區間起 (Y-m) | `"2024-01"` |
| `endDate` | string | ❌ | 註冊區間迄 (Y-m) | `"2024-12"` |
| `singleAmount.type` | string | ❌ | 單筆消費條件類型 | `"greater"`, `"less"`, `"between"` |
| `singleAmount.value` | integer | ❌ | 單筆消費比對值 | `1000` |
| `singleAmount.min` | integer | ❌ | 單筆消費區間下限（type 為 between 時） | `500` |
| `singleAmount.max` | integer | ❌ | 單筆消費區間上限（type 為 between 時） | `5000` |
| `totalAmount.type` | string | ❌ | 累計消費條件類型 | `"greater"`, `"less"`, `"between"` |
| `totalAmount.value` | integer | ❌ | 累計消費比對值 | `5000` |
| `totalAmount.min` | integer | ❌ | 累計消費區間下限 | `1000` |
| `totalAmount.max` | integer | ❌ | 累計消費區間上限 | `20000` |
| `serviceIds` | array | ❌ | 服務 ID 陣列 | `[1, 2]` |
| `prepaidGroupOrderingIds` | array | ❌ | 預付方案 ID 陣列 | `["uuid-1"]` |
| `page` | integer | ❌ | 分頁頁碼（此情境下後端會強制不分頁，取得全部符合條件會員） | `1` |
| `limit` | integer | ❌ | 每頁筆數 | `50` |

**替換字規則（templateContent / subject）**:

主旨（`subject`）與通知內容（`templateContent`）支援以下佔位符，發送時會依收件人與訂單／會員資料替換為實際內容。

**有訂單時**（mode 為 `byOrder` 或 `byPrepaidOrder`）可用：

| 佔位符 | 說明 |
|--------|------|
| `{姓名}` | 訂單姓名，無則為會員姓名 |
| `{店家名稱}` | 商家名稱 |
| `{服務類型}` | 訂單產品標題（productTitle） |
| `{服務項目}` | 訂單第一筆明細標題 |
| `{預約時間}` | 訂單預約時間（商家時區，Y/m/d H:i） |
| `{人數}` | 訂單人數（groupSize） |
| `{店家電話}` | 商家電話 |
| `{店家地址}` | 商家完整地址 |
| `{店家網址}` | 商家網址（https://{alias}.{domain}） |
| `{訂單短網址}` | 訂單短連結 |

**住宿訂單時**（mode 為 `byHotel`）可用：

| 佔位符 | 說明 |
|--------|------|
| `{姓名}` | 會員姓名 |
| `{店家名稱}` | 商家名稱 |
| `{入住時間}` | 入住日期 - 退房日期（check_in_date - check_out_date，Y/m/d 格式） |
| `{預約項目}` | 寵物住宿 |
| `{店家電話}` | 商家電話 |
| `{店家地址}` | 商家完整地址 |
| `{店家網址}` | 商家網址（https://{alias}.{domain}） |

**無訂單時**（mode 為 `byMember`）可用：

| 佔位符 | 說明 |
|--------|------|
| `{姓名}` | 會員姓名 |
| `{店家名稱}` | 商家名稱 |
| `{店家電話}` | 商家電話 |
| `{店家地址}` | 商家完整地址 |
| `{店家網址}` | 商家網址（https://{alias}.{domain}） |

**請求範例（依會員 ID 發送 Email，使用替換字）**:
```json
{
    "mode": "byMember",
    "memberIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
    "templateContent": "<p>親愛的 {姓名}，您好！</p><p>{店家名稱} 有最新優惠活動，歡迎至 {店家網址} 查看。</p>",
    "router": "email",
    "subject": "{店家名稱} 寵物旅館優惠通知"
}
```

**請求範例（依訂單發送 Email + 簡訊）**:
```json
{
    "mode": "byOrder",
    "orderIds": [101, 102],
    "templateContent": "<p>您的訂單 #101 即將入住，請準時報到。</p>",
    "router": "all",
    "subject": "入住提醒",
    "sendWhoType": "onlyBuyer"
}
```

**請求範例（依搜尋條件發送給篩選後的會員）**:
```json
{
    "mode": "byMember",
    "memberIds": [],
    "templateContent": "<p>感謝您長期支持，專屬優惠碼：THANKYOU2024</p>",
    "router": "email",
    "subject": "老客戶專屬優惠",
    "tagIds": [1, 2],
    "memberLevelIds": ["level-uuid-vip"]
}
```

**請求範例（依住宿訂單發送）**:
```json
{
    "mode": "byHotel",
    "hotelOrderId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "templateContent": "{入住時間}{預約項目}{姓名} 測試文字",
    "router": "email",
    "subject": "{店家名稱} 入住提醒"
}
```

**回應範例（成功）**:
```json
{
    "status": "success",
    "code": 200,
    "message": "Custom send notification success",
    "data": {
        "dispatched": true
    }
}
```

**注意事項**:
- 實際發送為非同步執行，API 成功回傳僅表示任務已排入佇列。
- `templateContent` 與 `subject` 可使用上表佔位符，發送前會自動替換；未列出的佔位符將維持原樣。
- `templateContent` 可包含 HTML，Email 會以 HTML 寄出。
- 簡訊發送依商家簡訊閘道設定與會員手機號碼是否有效而定。
- 僅能發送給當前商家底下之會員或訂單，無法跨商家。

### 輔助 API

本節介紹在使用寵物旅館功能時，需要使用的相關輔助 API。

#### 取得寵物分類列表
```http
POST /api/admin/service/category/search
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 取得商家的寵物分類列表，用於設定房型價格時的 `sizeId` 參數。

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `type` | string | ❌ | 服務類型，用於篩選分類 | `"cosmetic"`, `"daycare"`, `"goods"` |
| `onlyLeaf` | boolean | ❌ | 是否只取得葉節點（最底層分類） | `true` |
| `title` | string | ❌ | 搜尋標題關鍵字 | `"小型犬"` |
| `state` | integer | ❌ | 狀態篩選 (0:停用, 1:啟用) | `1` |
| `paginate` | integer | ❌ | 分頁設定（0:不分頁） | `0` |

**type 可選值**:

| 值 | 說明 |
|----|------|
| `"cosmetic"` | 美容服務 |
| `"daycare"` | 日托服務 |
| `"goods"` | 商品服務 |

**請求範例**:
```json
{
    "type": "cosmetic",
    "onlyLeaf": true,
    "state": 1,
    "paginate": 0
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": [
        {
            "id": 1,
            "parentId": null,
            "title": "小型犬",
            "alias": "small-dog",
            "state": 1,
            "type": "cosmetic",
            "level": 1,
            "categoryTitle": "小型犬",
            "params": null,
            "evaluationPolicy": "ONCE",
            "children": [
                {
                    "id": 11,
                    "parentId": 1,
                    "title": "吉娃娃",
                    "alias": "chihuahua",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "小型犬 > 吉娃娃",
                    "params": null,
                    "evaluationPolicy": "ONCE",
                    "children": []
                },
                {
                    "id": 12,
                    "parentId": 1,
                    "title": "約克夏",
                    "alias": "yorkshire",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "小型犬 > 約克夏",
                    "params": null,
                    "evaluationPolicy": "ONCE",
                    "children": []
                }
            ]
        },
        {
            "id": 2,
            "parentId": null,
            "title": "中型犬",
            "alias": "medium-dog",
            "state": 1,
            "type": "cosmetic",
            "level": 1,
            "categoryTitle": "中型犬",
            "params": null,
            "children": [
                {
                    "id": 21,
                    "parentId": 2,
                    "title": "柯基",
                    "alias": "corgi",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "中型犬 > 柯基",
                    "params": null,
                    "children": []
                },
                {
                    "id": 22,
                    "parentId": 2,
                    "title": "邊境牧羊犬",
                    "alias": "border-collie",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "中型犬 > 邊境牧羊犬",
                    "params": null,
                    "children": []
                }
            ]
        },
        {
            "id": 3,
            "parentId": null,
            "title": "大型犬",
            "alias": "large-dog",
            "state": 1,
            "type": "cosmetic",
            "level": 1,
            "categoryTitle": "大型犬",
            "params": null,
            "children": [
                {
                    "id": 31,
                    "parentId": 3,
                    "title": "拉布拉多",
                    "alias": "labrador",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "大型犬 > 拉布拉多",
                    "params": null,
                    "children": []
                },
                {
                    "id": 32,
                    "parentId": 3,
                    "title": "黃金獵犬",
                    "alias": "golden-retriever",
                    "state": 1,
                    "type": "cosmetic",
                    "level": 2,
                    "categoryTitle": "大型犬 > 黃金獵犬",
                    "params": null,
                    "children": []
                }
            ]
        }
    ]
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 分類ID（此 ID 用於房型價格設定的 `sizeId`） |
| `parentId` | integer\|null | 父分類ID |
| `title` | string | 分類名稱 |
| `alias` | string\|null | 分類別名 |
| `state` | integer | 狀態 (1:啟用, 0:停用) |
| `type` | string | 服務類型 |
| `level` | integer | 分類層級（從根節點開始計算，1 為第一層） |
| `categoryTitle` | string | 完整分類路徑標題 |
| `params` | string\|null | 分類參數（JSON 字串） |
| `evaluationPolicy` | string | 評估方式：`"ONCE"`（需評估一次）、`"NONE"`（無需評估）、`"ALWAYS"`（每次評估） |
| `children` | array | 子分類陣列（如果有） |

#### 取得訂單容器付款資訊

```http
GET /api/admin/merchant/order/{container_uuid}/payment-info
Authorization: Bearer {token}
```

**說明**: 依照訂單容器的 `type`（`booking`、`deposit`、`hotel`）回傳對應的付款資訊，包括可用付款方式、結帳總金額與會員可用儲值金。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 驗證商家管理員身份
- 💳 取得指定訂單容器的付款資訊

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `container_uuid` | string (uuid) | ✅ | 訂單容器 UUID |

**請求範例**:

```http
GET /api/admin/merchant/order/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/payment-info
Authorization: Bearer {token}
```

**成功回應範例**:

```json
{
  "status": "MERCHANT_ADMIN_GET_ORDER_PAYMENT_INFO_SUCCESS",
  "code": 200,
  "message": "Merchant admin get order payment info success",
  "data": {
    "paymentMethods": ["Cash", "Atm", "Credit", "Vacc"],
    "total": 1200,
    "deposit": {
      "point": 1000,
      "bonus": 200,
      "total": 1200
    },
    "invoiceSetting": true,
    "vaccBanks": [
      { "code": "004", "name": "臺灣銀行", "type": "BOT" },
      { "code": "008", "name": "華南銀行", "type": "HNCB" },
      { "code": "812", "name": "台新銀行", "type": "Taishin" }
    ]
  }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `paymentMethods` | array | 可用的付款方式陣列（已依商家設定與容器類型過濾） |
| `total` | integer | 本次結帳總金額（未含已付款紀錄） |
| `deposit` | object | 會員儲值金資訊 |
| `deposit.point` | integer | 儲值金點數餘額 |
| `deposit.bonus` | integer | 儲值金贈點/紅利餘額 |
| `deposit.total` | integer | `point + bonus` 的總額 |
| `invoiceSetting` | boolean | 商家電子發票開立設定（`true`: 已開通電子發票、`false`: 未開通） |
| `vaccBanks` | array | Vacc 虛擬帳戶可選銀行列表（僅當 `paymentMethods` 含 `Vacc` 時有值，供前端顯示銀行下拉選單；`code` 為銀行代碼、`name` 為銀行名稱、`type` 為 Newebpay 銀行類型） |

**錯誤情境**:

| 狀態碼 | status | 說明 |
|--------|--------|------|
| 404 | `ORDER_CONTAINER_ADMIN_ITEM_NOT_EXIST` | 指定的訂單容器不存在或不屬於當前商家 |
| 400 | `ORDER_CONTAINER_ADMIN_UNSUPPORTED_TYPE` | 該訂單容器的 `type` 不在支援範圍內（僅支援 `booking`、`deposit`、`hotel`） |

#### 更新訂單容器付款方式

```http
PATCH /api/admin/merchant/order/{container_uuid}/payment-method
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 更新訂單容器的付款方式，並依付款方式建立相關付款明細。付款金額一律以 container 計算的未付款總額為準，不接受 `total` 參數。可帶入 `paymentStatus` 為 `PAID` 或 `UNPAID` 決定是否標記為付款完成；未傳時預設為未付款。對於 `hotel` 類型的容器，會同步更新所有 `UNPAID` 狀態的 `HotelOrder` 付款方式並記錄變更歷史。

**中介層**: `hasMerchant`, `expired`, `admin`
- 🏢 驗證商家管理員身份
- 💳 更新訂單容器的付款方式與狀態

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 |
|---------|------|------|------|
| `container_uuid` | string (uuid) | ✅ | 訂單容器 UUID |

**請求參數**:

**必填欄位**:

| 參數名稱 | 類型 | 說明 | 範例值 |
|---------|------|------|--------|
| `paymentMethod` | string | 付款方式 | `"Cash"`, `"Credit"`, `"Atm"`, `"StoreCredit"`, `"Vacc"`, `"Deposit"`, `"Installment"`, `"StoreLinePay"`, `"Chailease"` |

**付款相關欄位**（根據付款方式決定是否必填）:

| 參數名稱 | 類型 | 必填條件 | 說明 | 範例值 |
|---------|------|---------|------|--------|
| `paymentStatus` | string | ❌ | 是否標記為付款完成：`"PAID"` = 已付款，`"UNPAID"` = 未付款；未傳則預設為未付款 | `"PAID"`, `"UNPAID"` |
| `Card4No` | string | ✅ `Atm` / `StoreCredit` | 卡號後四碼 | `"1234"` |
| `PayerAccount5Code` | string | ✅ `Atm` / `StoreCredit` | 付款帳號後五碼 | `"56789"` |
| `bankType` | string | ✅ `Vacc` | 銀行類型（可選值：`"BOT"`, `"HNCB"`, `"Taishin"`） | `"BOT"` |
| `email` | string | ✅ `Vacc` | 電子郵件（`Vacc` 必填，其他付款方式可選用於更新訂單聯絡資訊） | `"user@example.com"` |
| `installments` | array | ✅ `Installment` | 分期付款金額陣列 | `[1000, 1000, 1000]` |
| `installments[*]` | integer | ✅ `Installment` | 每期付款金額 | `1000` |

**訂單資訊欄位**（可選，所有付款方式都可使用）:

| 參數名稱 | 類型 | 說明 | 範例值 |
|---------|------|------|--------|
| `phoneCode` | string | 電話國碼（格式：`+886`） | `"+886"` |
| `phoneNumber` | string | 電話號碼 | `"912345678"` |
| `fullName` | string | 姓名 | `"王小明"` |
| `gender` | string | 性別（`male` 或 `female`） | `"male"` |
| `note` | string | 備註 | `"請於下午送達"` |
| `city` | string | 城市（地址資訊，會合併到 `shippingInfo`） | `"台北市"` |
| `district` | string | 區域（地址資訊，會合併到 `shippingInfo`） | `"信義區"` |
| `address` | string | 詳細地址（地址資訊，會合併到 `shippingInfo`） | `"信義路五段7號"` |

**付款方式說明**:

| 付款方式 | 說明 | 必填欄位 | 可選欄位 |
|---------|------|---------|---------|
| `Cash` | 現金付款 | 無 | — |
| `Credit` | 信用卡付款 | 無 | — |
| `Atm` | ATM 轉帳 | `Card4No`, `PayerAccount5Code` | — |
| `StoreCredit` | 現場刷卡 | `Card4No`, `PayerAccount5Code` | — |
| `Vacc` | 虛擬帳戶付款 | `bankType`, `email` | — |
| `Deposit` | 儲值金抵扣 | 無 | — |
| `Installment` | 分期付款 | `installments` | — |
| `StoreLinePay` | 現場 LINE Pay | 無 | — |
| `Chailease` | 中租零卡分期 | 無 | — |

**付款方式欄位詳細說明**:

- **Cash（現金付款）**
  - 必填：無
  - 範例：`{ "paymentMethod": "Cash" }`

- **Credit（信用卡付款）**
  - 必填：無
  - 範例：`{ "paymentMethod": "Credit" }`

- **Atm（ATM 轉帳）**
  - 必填：`Card4No`, `PayerAccount5Code`
  - 範例：`{ "paymentMethod": "Atm", "Card4No": "1234", "PayerAccount5Code": "56789" }`

- **StoreCredit（現場刷卡）**
  - 必填：`Card4No`, `PayerAccount5Code`
  - 範例：`{ "paymentMethod": "StoreCredit", "Card4No": "1234", "PayerAccount5Code": "56789" }`

- **Vacc（虛擬帳戶付款）**
  - 必填：`bankType`, `email`
  - `bankType` 可選值：`"BOT"`, `"HNCB"`, `"Taishin"`
  - 範例：`{ "paymentMethod": "Vacc", "bankType": "BOT", "email": "user@example.com" }`

- **Deposit（儲值金抵扣）**
  - 必填：無
  - 範例：`{ "paymentMethod": "Deposit" }`

- **Installment（分期付款）**
  - 必填：`installments`（陣列，至少一筆）
  - `installments` 格式：`[1000, 1000, 1000]`（每期金額）
  - 範例：`{ "paymentMethod": "Installment", "installments": [1000, 1000, 1000] }`

- **StoreLinePay（現場 LINE Pay）**
  - 必填：無
  - 範例：`{ "paymentMethod": "StoreLinePay" }`

- **Chailease（中租零卡分期）**
  - 必填：無
  - 範例：`{ "paymentMethod": "Chailease" }`

**通用可選欄位**（所有付款方式都可使用）：

以下欄位可與任何付款方式一起使用，用於更新訂單資訊：

| 欄位名稱 | 類型 | 說明 | 範例值 |
|---------|------|------|--------|
| `phoneCode` | string | 電話國碼（格式：`+886`） | `"+886"` |
| `phoneNumber` | string | 電話號碼 | `"912345678"` |
| `fullName` | string | 姓名 | `"王小明"` |
| `gender` | string | 性別（`male` 或 `female`） | `"male"` |
| `email` | string | 電子郵件 | `"user@example.com"` |
| `note` | string | 備註 | `"請於下午送達"` |
| `city` | string | 城市（地址資訊，會合併到 `shippingInfo`） | `"台北市"` |
| `district` | string | 區域（地址資訊，會合併到 `shippingInfo`） | `"信義區"` |
| `address` | string | 詳細地址（地址資訊，會合併到 `shippingInfo`） | `"信義路五段7號"` |

**發票資訊**（可選，包在 `invoice` 物件中；**僅在帶入已付款狀態**（`paymentStatus` 為 `"PAID"`）時才會使用此流程：建立/更新發票資訊並用於開立發票；未帶入已付款或未傳 `invoice` 則不處理）:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `invoice` | object | ❌ | 發票資訊物件；僅當 `paymentStatus` 為 `"PAID"` 時會使用 | — |
| `invoice.taxType` | string | ❌ | 發票類型：`"B2C"` 個人、`"B2B"` 公司、`"DONATE"` 捐贈 | `"B2C"` |
| `invoice.taxId` | string | ✅ 當 `invoice.taxType` 為 `"B2B"` | 統一編號 | `"12345678"` |
| `invoice.carrierType` / `invoice.CarrierType` | string/number | ❌ | 載具類型：`0` 手機條碼、`1` 自然人、`2` ezPay 電子發票載具；支援 camelCase 或 PascalCase | `"0"` |
| `invoice.carrierNum` / `invoice.CarrierNum` | string | ✅ 當 carrierType 為 `0` 或 `1`；❌ 當 carrierType 為 `2` | 載具號碼；carrierType=2 時非必填，會自動以會員 `phone` 覆寫；支援 camelCase 或 PascalCase | `"/ABCD1234"` |
| `invoice.loveCode` / `invoice.LoveCode` | string | ✅ 當 `invoice.taxType` 為 `"DONATE"` | 捐贈碼（3～7 碼數字）；支援 camelCase 或 PascalCase | `"168001"` |
| `invoice.saveTaxInfo` | integer | ❌ | 是否將發票資訊寫回會員預設；`1` 寫回、`0` 不寫回；未傳預設為 `0` | `0`, `1` |

**行為說明**:

1. **狀態變更**:
   - 當容器狀態為 `UNPAID` 時，更新付款方式會自動將狀態變更為 `PAID`，並設定 `paid_at` 時間
   - 當容器狀態為 `PAID` 時，只更新付款方式，狀態保持不變

2. **子訂單同步**:
   - **booking 類型**: 同步更新所有未付款子訂單（`orders`）的付款方式與狀態
   - **hotel 類型**: 
     - 當狀態從 `UNPAID` 變為 `PAID` 時：使用 `PaidHotelOrder` Action 將所有 `UNPAID` 的 `HotelOrder` 更新為 `PAID`
     - 當只更新付款方式時：使用 `UpdateHotelOrderPaymentMethod` Action 記錄付款方式變更到 `HotelOrderHistory`
   - **deposit 類型**: 主要透過儲值金扣款與 `DepositHistory` 記錄，無需同步子訂單

3. **付款明細**:
   - 根據不同的付款方式，會建立對應的付款明細記錄（如 `OrderOfflinePaymentDetail`、`OrderVaccPaymentDetail`、`OrderInstallment` 等）

4. **發票**:
   - **僅在帶入已付款狀態**（`paymentStatus` 為 `"PAID"`）時才會走發票流程：若同時帶入 `invoice` 物件，會先建立或更新訂單容器的發票資訊，再依商家設定觸發開立發票（非儲值金/分期之訂單）。未帶入已付款狀態時，`invoice` 不會被使用；已付款但未帶入 `invoice` 時，開立發票會使用既有發票紀錄或會員預設。
   - `invoice.carrierType=2`（ezPay 電子發票載具填手機）時，`carrierNum` 非必填，系統會無視輸入值並自動以會員 `phone` 覆寫。
   - 發票欄位支援 camelCase（`carrierType`, `carrierNum`, `loveCode`）與 PascalCase（`CarrierType`, `CarrierNum`, `LoveCode`）輸入，內部統一使用 camelCase。

**請求範例（現金付款）**:

```http
PATCH /api/admin/merchant/order/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/payment-method
Content-Type: application/json
Authorization: Bearer {token}
```

```json
{
  "paymentMethod": "Cash"
}
```

**請求範例（現金付款並更新聯絡資訊）**:

```json
{
  "paymentMethod": "Cash",
  "phoneCode": "+886",
  "phoneNumber": "912345678",
  "fullName": "王小明",
  "gender": "male",
  "email": "user@example.com",
  "note": "請於下午送達",
  "city": "台北市",
  "district": "信義區",
  "address": "信義路五段7號"
}
```

**請求範例（現金付款、標記已付款並帶入發票資訊）**:

```json
{
  "paymentMethod": "Cash",
  "paymentStatus": "PAID",
  "invoice": {
    "taxType": "B2C",
    "CarrierType": "0",
    "CarrierNum": "/ABCD1234",
    "saveTaxInfo": 0
  }
}
```

**請求範例（現場刷卡）**:

```json
{
  "paymentMethod": "StoreCredit",
  "Card4No": "1234",
  "PayerAccount5Code": "56789"
}
```

**請求範例（現場刷卡並更新聯絡資訊）**:

```json
{
  "paymentMethod": "StoreCredit",
  "Card4No": "1234",
  "PayerAccount5Code": "56789",
  "fullName": "李小花",
  "phoneCode": "+886",
  "phoneNumber": "987654321",
  "email": "customer@example.com"
}
```

**請求範例（虛擬帳戶付款）**:

```json
{
  "paymentMethod": "Vacc",
  "bankType": "BOT",
  "email": "user@example.com"
}
```

**請求範例（分期付款）**:

```json
{
  "paymentMethod": "Installment",
  "installments": [1000, 1000, 1000]
}
```

**請求範例（信用卡付款）**:

```json
{
  "paymentMethod": "Credit"
}
```

**請求範例（ATM 轉帳）**:

```json
{
  "paymentMethod": "Atm",
  "Card4No": "1234",
  "PayerAccount5Code": "56789"
}
```

**請求範例（ATM 轉帳並更新地址）**:

```json
{
  "paymentMethod": "Atm",
  "Card4No": "5678",
  "PayerAccount5Code": "12345",
  "city": "新北市",
  "district": "板橋區",
  "address": "文化路一段188巷"
}
```

**請求範例（儲值金抵扣）**:

```json
{
  "paymentMethod": "Deposit"
}
```

**請求範例（現場 LINE Pay）**:

```json
{
  "paymentMethod": "StoreLinePay"
}
```

**請求範例（中租零卡分期）**:

```json
{
  "paymentMethod": "Chailease"
}
```

**成功回應範例**:

```json
{
  "status": "MERCHANT_ADMIN_PATCH_ORDER_PAYMENT_METHOD_SUCCESS",
  "code": 200,
  "message": "Merchant admin patch order payment method success",
  "data": {
    "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "displayId": "ORD-20240101-001",
    "type": "hotel",
    "productId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "paymentMethod": "Credit",
    "paymentStatus": "PAID",
    "paidAt": "2024-01-01 12:00:00",
    "total": 2000,
    "offlineDetail": {
      "paymentMethod": "Credit",
      "amt": 2000,
      "card4No": null,
      "payerAccount5Code": null
    }
  }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `uuid` | string | 訂單容器 UUID |
| `displayId` | string | 訂單容器顯示編號 |
| `type` | string | 容器類型（`booking`、`deposit`、`hotel`） |
| `productId` | string\|null | 子訂單的 product_id（UUID，取自第一筆子訂單） |
| `paymentMethod` | string | 更新後的付款方式 |
| `paymentStatus` | string | 更新後的付款狀態（`UNPAID` 或 `PAID`） |
| `paidAt` | string\|null | 付款時間（格式：YYYY-MM-DD HH:mm:ss） |
| `total` | integer | 付款總金額 |
| `offlineDetail` | object\|null | 線下付款明細（`StoreCredit` / `Atm` 時會有） |
| `offlineDetail.paymentMethod` | string | 付款方式 |
| `offlineDetail.amt` | integer | 付款金額 |
| `offlineDetail.card4No` | string\|null | 卡號後四碼 |
| `offlineDetail.payerAccount5Code` | string\|null | 付款帳號後五碼 |

**錯誤情境**:

| 狀態碼 | status | 說明 |
|--------|--------|------|
| 404 | `ORDER_CONTAINER_ADMIN_ITEM_NOT_EXIST` | 指定的訂單容器不存在或不屬於當前商家 |
| 400 | 驗證錯誤 | 請求參數不符合驗證規則（如付款方式無效、必填欄位缺失等） |

**使用說明**:

1. **付款方式選擇**:
   - 付款方式必須在商家設定的可用付款方式中
   - 不同付款方式需要提供對應的必填欄位

2. **狀態處理**:
   - 當容器狀態為 `UNPAID` 時，更新付款方式會自動變更為 `PAID`
   - 當容器狀態為 `PAID` 時，只更新付款方式，狀態保持不變

3. **子訂單同步**:
   - 系統會自動同步更新相關子訂單的付款方式與狀態
   - 對於 `hotel` 類型，會記錄付款方式變更歷史到 `HotelOrderHistory`

#### 取得寵物服務備註 (serviceComments)

```http
GET /api/admin/merchant/member/pet/{pet_id}/service-comments
Authorization: Bearer {token}
```

**說明**: 取得指定寵物的服務備註列表（`type = service`），可依訂單與狀態篩選，搭配住宿訂單或美容/日托服務使用。

**中介層:** `hasMerchant`, `expired`, `admin`
- 🏢 限制只能查看自己商家的備註
- 🔍 依寵物、訂單篩選服務備註

**路徑參數:**
- `pet_id` (integer, required): 寵物 ID

**請求參數:**

```json
{
  "page": "integer|nullable",
  "limit": "integer|nullable",
  "title": "string|nullable",
  "state": "integer|nullable|in:0,1,-2"
}
```

**回應範例:**

```json
{
  "status": "success",
  "code": 200,
  "message": "Comment admin search success",
  "data": {
    "items": [
      {
        "id": "comment-uuid",
        "orderNo": "20240115001-01",
        "memberId": "member-uuid",
        "petId": 12,
        "type": "service",
        "content": "此寵物毛量多，服務時間需加長。",
        "images": [],
        "questions": {},
        "creator": {
          "id": "user-uuid",
          "avatar": "https://example.com/staff.jpg",
          "name": "美容師小王",
          "email": "staff@example.com"
        },
        "createdAt": "2024-01-15T10:30:00Z",
        "updatedAt": "2024-01-15T11:00:00Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "last_page": 1,
      "per_page": 20,
      "total": 1
    }
  }
}
```

#### 搜尋寵物備註 (petComments)

```http
GET /api/admin/merchant/member/pet/{pet_id}/pet-comments
Authorization: Bearer {token}
```

**說明**: 取得指定寵物的通用備註列表（`type = pet`），可於建立/查看住宿訂單時快速確認寵物習性與注意事項。

**中介層:** `hasMerchant`, `expired`, `admin`
- 🏢 限制只能查看自己商家的備註
- 🔍 依寵物快速取得備註

**路徑參數:**
- `pet_id` (integer, required): 寵物 ID

**請求參數:**

```json
{
  "page": "integer|nullable",
  "limit": "integer|nullable",
  "title": "string|nullable",
  "state": "integer|nullable|in:0,1,-2"
}
```

**回應範例:**

```json
{
  "status": "success",
  "code": 200,
  "message": "Comment admin search success",
  "data": {
    "items": [
      {
        "id": "comment-uuid",
        "orderNo": null,
        "memberId": "member-uuid",
        "petId": 12,
        "type": "pet",
        "content": "怕吹風機，建議先安撫再開始。",
        "images": [],
        "questions": {},
        "creator": {
          "id": "user-uuid",
          "avatar": null,
          "name": "管理員",
          "email": "admin@example.com"
        },
        "createdAt": "2024-01-10T09:30:00Z",
        "updatedAt": "2024-01-10T09:30:00Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "last_page": 1,
      "per_page": 20,
      "total": 1
    }
  }
}
```

#### 新增服務分類

```http
POST /api/admin/service/category
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 新增服務分類（美容分類等）。使用 Action 架構開發。

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `title` | string | ✅ | 分類名稱 | `"小型犬"` |
| `parentId` | integer | ❌ | 父分類ID（用於建立子分類） | `1` |
| `alias` | string | ❌ | 分類別名 | `"small-dog"` |
| `type` | string | ❌ | 服務類型（預設為 `"cosmetic"`） | `"cosmetic"`, `"daycare"`, `"goods"` |
| `state` | integer | ✅ | 狀態 (0:停用, 1:啟用, -2:刪除) | `1` |
| `introtext` | string | ❌ | 簡介 | `"小型犬分類"` |
| `description` | string | ❌ | 詳細描述 | `"適合體型較小的犬種"` |
| `params` | string | ❌ | 分類參數（JSON 字串） | `"{}"` |
| `evaluationPolicy` | string | ❌ | 評估方式（預設為 `"ONCE"`） | `"ONCE"`, `"NONE"`, `"ALWAYS"` |

**evaluationPolicy 可選值**:

| 值 | 說明 |
|----|------|
| `"ONCE"` | 需評估一次 |
| `"NONE"` | 無需評估 |
| `"ALWAYS"` | 每次評估 |

**請求範例**:

```json
{
    "title": "小型犬",
    "alias": "small-dog",
    "type": "cosmetic",
    "state": 1,
    "evaluationPolicy": "ONCE"
}
```

**回應範例**:

```json
{
    "status": "SERVICE_CATEGORY_ADMIN_CREATE_SUCCESS",
    "code": 200,
    "message": "Service category created successfully",
    "data": {
        "id": 1,
        "parentId": null,
        "title": "小型犬",
        "alias": "small-dog",
        "state": 1,
        "type": "cosmetic",
        "level": 1,
        "categoryTitle": "小型犬",
        "introtext": null,
        "description": null,
        "params": null,
        "evaluationPolicy": "ONCE"
    }
}
```

#### 更新服務分類

```http
PATCH /api/admin/service/category/{category_id}
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 更新服務分類。使用 Action 架構開發。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `category_id` | integer | ✅ | 分類ID | `1` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `title` | string | ❌ | 分類名稱 | `"小型犬"` |
| `parentId` | integer | ❌ | 父分類ID（用於變更分類層級） | `1` |
| `alias` | string | ❌ | 分類別名 | `"small-dog"` |
| `type` | string | ❌ | 服務類型 | `"cosmetic"` |
| `state` | integer | ❌ | 狀態 (0:停用, 1:啟用, -2:刪除) | `1` |
| `introtext` | string | ❌ | 簡介 | `"小型犬分類"` |
| `description` | string | ❌ | 詳細描述 | `"適合體型較小的犬種"` |
| `params` | string | ❌ | 分類參數（JSON 字串） | `"{}"` |
| `evaluationPolicy` | string | ❌ | 評估方式 | `"NONE"` |

**請求範例**:

```json
{
    "title": "小型犬（更新）",
    "evaluationPolicy": "NONE"
}
```

**回應範例**:

```json
{
    "status": "SERVICE_CATEGORY_ADMIN_UPDATE_SUCCESS",
    "code": 200,
    "message": "Service category updated successfully",
    "data": {
        "id": 1,
        "parentId": null,
        "title": "小型犬（更新）",
        "alias": "small-dog",
        "state": 1,
        "type": "cosmetic",
        "level": 1,
        "categoryTitle": "小型犬（更新）",
        "introtext": null,
        "description": null,
        "params": null,
        "evaluationPolicy": "NONE"
    }
}
```

#### 建立/更新服務分類（舊版 API）

```http
POST /api/admin/service/category/store
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 建立或更新服務分類（舊版 API，同時支援新增和更新）。如果提供 `id` 則為更新，否則為新增。

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | integer | ❌ | 分類ID（提供則為更新，否則為新增） | `1` |
| `title` | string | ✅ | 分類名稱 | `"小型犬"` |
| `parentId` | integer | ❌ | 父分類ID（用於建立子分類） | `1` |
| `alias` | string | ❌ | 分類別名 | `"small-dog"` |
| `type` | string | ❌ | 服務類型（預設為 `"cosmetic"`） | `"cosmetic"`, `"daycare"`, `"goods"` |
| `state` | integer | ✅ | 狀態 (0:停用, 1:啟用, -2:刪除) | `1` |
| `introtext` | string | ❌ | 簡介 | `"小型犬分類"` |
| `description` | string | ❌ | 詳細描述 | `"適合體型較小的犬種"` |
| `params` | string | ❌ | 分類參數（JSON 字串） | `"{}"` |
| `evaluationPolicy` | string | ❌ | 評估方式（預設為 `"ONCE"`） | `"ONCE"`, `"NONE"`, `"ALWAYS"` |

**evaluationPolicy 可選值**:

| 值 | 說明 |
|----|------|
| `"ONCE"` | 需評估一次 |
| `"NONE"` | 無需評估 |
| `"ALWAYS"` | 每次評估 |

**請求範例（新增分類）**:

```json
{
    "title": "小型犬",
    "alias": "small-dog",
    "type": "cosmetic",
    "state": 1,
    "evaluationPolicy": "ONCE"
}
```

**請求範例（更新分類）**:

```json
{
    "id": 1,
    "title": "小型犬（更新）",
    "evaluationPolicy": "NONE"
}
```

**回應範例**:

```json
{
    "status": "SERVICE_CATEGORY_ADMIN_STORE_SUCCESS",
    "code": 200,
    "message": "Service category admin store success",
    "data": {
        "id": 1,
        "parentId": null,
        "title": "小型犬",
        "alias": "small-dog",
        "state": 1,
        "type": "cosmetic",
        "level": 1,
        "categoryTitle": "小型犬",
        "introtext": null,
        "description": null,
        "params": null,
        "evaluationPolicy": "ONCE"
    }
}
```

**注意事項**:
- 此 API 為舊版 API，建議使用新的 Action 架構 API：
  - `POST /api/admin/service/category` - 新增服務分類
  - `PATCH /api/admin/service/category/{category_id}` - 更新服務分類
  - `DELETE /api/admin/service/category/{category_id}` - 刪除服務分類
- 如果 `evaluationPolicy` 為 `null` 或未傳入，預設為 `"ONCE"`

#### 刪除服務分類

```http
DELETE /api/admin/service/category/{category_id}
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 刪除服務分類（軟刪除）。使用 Action 架構開發。此 API 會將分類的 `state` 設為 `-1`，分類仍存在於資料庫中但不會在正常查詢中顯示。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `category_id` | integer | ✅ | 分類ID | `1` |

**請求參數**: 無（僅需路徑參數）

**請求範例**:

```bash
curl -X DELETE "https://api.example.com/api/admin/service/category/1" \
  -H "Authorization: Bearer {token}"
```

**回應範例**:

```json
{
    "status": "SERVICE_CATEGORY_ADMIN_DELETE_SUCCESS",
    "code": 200,
    "message": "Service category admin delete success",
    "data": null
}
```

**錯誤回應範例（分類不存在）**:

```json
{
    "status": "error",
    "code": 404,
    "message": "No query results for model [DaydreamLab\\Dddream\\Models\\Service\\ServiceCategory]",
    "data": null
}
```

**錯誤回應範例（無權限刪除其他商家的分類）**:

```json
{
    "status": "error",
    "code": 404,
    "message": "No query results for model [DaydreamLab\\Dddream\\Models\\Service\\ServiceCategory]",
    "data": null
}
```

**注意事項**:
- 此 API 使用軟刪除方式，分類的 `state` 會被設為 `-1`
- 刪除後分類仍存在於資料庫中，但不會在正常查詢中顯示
- 只能刪除屬於當前商家的分類，嘗試刪除其他商家的分類會返回 404 錯誤
- 刪除操作會自動更新 `updated_by` 欄位為當前使用者 ID

#### 取得分店資料
```http
GET /api/admin/merchant/product/{product_id}
Authorization: Bearer {token}
```

**說明**: 依分店 ID 取得單一分店（Product）的詳細資料，用於取得分店基本資訊、聯絡方式、地址等。分店必須屬於當前登入商家。

**路徑參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string (UUID) | ✅ | 分店ID | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**回應範例**:
```json
{
    "status": "PRODUCT_RETRIEVED_SUCCESSFULLY",
    "code": 200,
    "message": "Product retrieved successfully",
    "data": {
        "id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "title": "台北總店",
        "name": "台北總店",
        "alias": "taipei-main",
        "state": 1,
        "storeType": "store",
        "type": "service",
        "introimage": "/storage/merchants/example/intro.jpg",
        "image": "/storage/merchants/example/image.jpg",
        "images": [],
        "website": "https://example.com",
        "phoneCode": "886",
        "phone": "02-1234-5678",
        "lineId": null,
        "lineFriendUrl": null,
        "fbFanpageId": null,
        "fbFanpageUrl": null,
        "atmAccountName": null,
        "atmBank": null,
        "atmAccount": null,
        "introtext": "介紹文字",
        "description": "分店描述",
        "notice": "注意事項",
        "video": null,
        "link": null,
        "lat": 25.0330,
        "lng": 121.5654,
        "locationName": "台北總店",
        "locationCountry": "台灣",
        "locationCity": "台北市",
        "locationDistrict": "信義區",
        "locationAddress": "信義路五段7號",
        "fullAddress": "台北市信義區信義路五段7號",
        "minBookingSize": null,
        "maxBookingSize": null,
        "allowOverlap": 0,
        "hidden": 0,
        "step": 3,
        "ordering": 1,
        "merchant_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string (UUID) | 分店ID |
| `title` | string | 分店名稱 |
| `name` | string | 分店名稱（同 title） |
| `alias` | string\|null | 分店別名 |
| `state` | integer | 狀態 (0:未發布, 1:已發布, 2:編輯中) |
| `storeType` | string | 店面類型 (`"store"`: 實體店面, `"home"`: 到府服務) |
| `type` | string | 分店類型 |
| `introimage` | string\|null | 介紹圖片路徑 |
| `image` | string\|null | 主要圖片路徑 |
| `images` | array | 圖片陣列 |
| `website` | string\|null | 網站 |
| `phoneCode` | string\|null | 電話國碼 |
| `phone` | string\|null | 電話 |
| `lineId` | string\|null | LINE ID |
| `lineFriendUrl` | string\|null | LINE 加友連結 |
| `fbFanpageId` | string\|null | Facebook 粉絲頁 ID |
| `fbFanpageUrl` | string\|null | Facebook 粉絲頁 URL |
| `atmAccountName` | string\|null | ATM 戶名 |
| `atmBank` | string\|null | ATM 銀行 |
| `atmAccount` | string\|null | ATM 帳號 |
| `introtext` | string\|null | 簡介文字 |
| `description` | string\|null | 描述 |
| `notice` | string\|null | 注意事項 |
| `video` | string\|null | 影片連結 |
| `link` | string\|null | 連結 |
| `lat` | float\|null | 緯度 |
| `lng` | float\|null | 經度 |
| `locationName` | string\|null | 地點名稱 |
| `locationCountry` | string\|null | 國家 |
| `locationCity` | string\|null | 城市 |
| `locationDistrict` | string\|null | 區域 |
| `locationAddress` | string\|null | 地址 |
| `fullAddress` | string\|null | 完整地址 |
| `minBookingSize` | integer\|null | 最小預約人數 |
| `maxBookingSize` | integer\|null | 最大預約人數 |
| `allowOverlap` | integer | 是否允許時段重疊 (0:否, 1:是) |
| `hidden` | integer | 是否隱藏 (0:否, 1:是) |
| `step` | integer | 當前啟用步驟 |
| `ordering` | integer | 排序 |
| `merchant_id` | string | 商家ID |

**錯誤回應**（分店不存在或非當前商家）:
- HTTP 404 或由系統回傳對應錯誤狀態

**注意事項**:
- `product_id` 必須為有效的 UUID 格式
- 僅能取得屬於當前登入商家旗下的分店

#### 取得分店列表
```http
POST /api/admin/product/search
Content-Type: application/json
Authorization: Bearer {token}
```

**說明**: 取得商家的分店列表，用於設定寵物旅館時指定開放的分店，以及新增房型時指定所屬分店。

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `state` | integer | ❌ | 狀態篩選 (0:未發布, 1:已發布, 2:編輯中) | `""` |
| `type` | string | ❌ | 分店類型篩選 | `"service"` |
| `hidden` | integer | ❌ | 是否隱藏 (0:否, 1:是) | `0` |
| `title` | string | ❌ | 搜尋名稱關鍵字 | `"台北店"` |
| `paginate` | integer | ❌ | 是否分頁 (0:不分頁) | `0` |
| `limit` | integer | ❌ | 每頁筆數（當 `paginate=1` 時有效，未指定則使用預設值） | `15` |
| `page` | integer | ❌ | 頁碼（當 `paginate=1` 時有效，預設為 1） | `1` |

**type 可選值**:

| 值 | 說明 |
|----|------|
| `"activity"` | 活動 |
| `"course"` | 課程 |
| `"service"` | 服務（通常用於住宿分店） |
| `"reservation"` | 預約 |
| `"guide"` | 導覽 |

**請求範例**:
```json
{
    "state": "",
    "paginate": 0,
    "limit": 15,
    "page": 1
}
```

**不分頁範例**:
```json
{
    "type": "service",
    "state": "",
    "paginate": 0
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": {
        "items": [
            {
                "id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
                "name": "台北總店",
                "alias": "taipei-main",
                "storeType": "store",
                "state": 1,
                "introimage": "/storage/merchants/example/intro.jpg",
                "image": "/storage/merchants/example/image.jpg",
                "type": "service",
                "activeStep": 3,
                "activeOrders": 5,
                "hidden": 0,
                "phone": "02-1234-5678",
                "fullAddress": "台北市信義區信義路五段7號"
            },
            {
                "id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "name": "新北分店",
                "alias": "newtaipei-branch",
                "storeType": "store",
                "state": 1,
                "introimage": null,
                "image": "/storage/merchants/example/image2.jpg",
                "type": "service",
                "activeStep": 2,
                "activeOrders": 2,
                "hidden": 0,
                "phone": "02-9876-5432",
                "fullAddress": "新北市板橋區中山路一段123號"
            }
        ],
        "pagination": {
            "current_page": 1,
            "per_page": 15,
            "total": 2,
            "last_page": 1
        },
        "records": 2
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `items` | array | 分店列表陣列 |
| `items[].id` | string (UUID) | 分店ID（此 ID 用於設定中的 `productSetting[].productId` 和房型設定的 `productId`） |
| `items[].name` | string | 分店名稱 |
| `items[].alias` | string\|null | 分店別名 |
| `items[].storeType` | string | 店面類型 (`"store"`: 實體店面, `"home"`: 到府服務) |
| `items[].state` | integer | 狀態 (0:未發布, 1:已發布, 2:編輯中) |
| `items[].introimage` | string\|null | 介紹圖片路徑 |
| `items[].image` | string\|null | 主要圖片路徑 |
| `items[].type` | string | 分店類型 |
| `items[].activeStep` | integer | 當前啟用的步驟（用於設定流程） |
| `items[].activeOrders` | integer | 進行中的訂單數量 |
| `items[].hidden` | integer | 是否隱藏 (0:否, 1:是) |
| `items[].phone` | string\|null | 電話 |
| `items[].fullAddress` | string\|null | 完整地址 |
| `pagination` | object | 分頁資訊 |
| `pagination.current_page` | integer | 當前頁碼 |
| `pagination.per_page` | integer | 每頁筆數 |
| `pagination.total` | integer | 總筆數 |
| `pagination.last_page` | integer | 最後一頁頁碼 |
| `records` | integer | 本頁筆數 |

#### 房型設備管理

房型設備（Room Type Feature）用於定義房型可提供的設備或服務，例如 Wi-Fi、空調、電視等。這些設備資訊可以顯示在房型詳情頁面，幫助顧客了解房型的特色。

##### 新增/更新房型設備
```http
POST /api/admin/hotel/{product_id}/room/type/feature
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | string | ❌ | 設備ID（UUID），提供時為更新，不提供時為新增 | `"7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50"` |
| `name` | string | ✅ | 設備名稱 | `"Wi-Fi"` |
| `icon` | string | ❌ | 圖示名稱或路徑 | `"wifi"` |
| `state` | integer | ❌ | 狀態 (0:停用, 1:啟用, -2:刪除)，預設為 1 | `1` |
| `ordering` | integer | ❌ | 排序順序 | `1` |

**state 可選值**:

| 值 | 說明 |
|----|------|
| `0` | 停用 |
| `1` | 啟用 |
| `-2` | 刪除 |

**請求範例（新增）**:
```bash
curl -X POST "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/feature" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Wi-Fi",
    "icon": "wifi",
    "state": 1,
    "ordering": 1
}'
```

**請求範例（更新）**:
```bash
curl -X POST "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/feature" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50",
    "name": "Wi-Fi（已更新）",
    "icon": "wifi-updated",
    "state": 1,
    "ordering": 2
}'
```

**回應範例**:
```json
{
    "code": 200,
    "status": "HOTEL_ROOM_TYPE_FEATURE_CREATE_SUCCESS",
    "message": "Hotel room type feature create success",
    "data": {
        "id": "7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50",
        "merchant_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
        "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "name": "Wi-Fi",
        "icon": "wifi"
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string | 設備ID（UUID） |
| `merchant_id` | string | 商家ID（UUID） |
| `product_id` | string | 分店ID（UUID） |
| `name` | string | 設備名稱 |
| `icon` | string\|null | 圖示名稱或路徑 |

##### 取得房型設備列表
```http
GET /api/admin/hotel/{product_id}/room/type/feature
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |

**查詢參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `name` | string | ❌ | 設備名稱關鍵字（模糊搜尋） | `"Wi-Fi"` |
| `state` | integer | ❌ | 狀態過濾 (0:停用, 1:啟用, -2:刪除) | `1` |
| `page` | integer | ❌ | 頁碼（用於分頁） | `1` |
| `limit` | integer | ❌ | 每頁筆數（用於分頁），預設為 15 | `15` |
| `order_by` | string | ❌ | 排序欄位，預設為 `ordering` | `"ordering"` |
| `order` | string | ❌ | 排序方向（asc 或 desc），預設為 `asc` | `"asc"` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/feature?name=Wi-Fi&state=1" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例（無分頁）**:
```json
{
    "code": 200,
    "status": "HOTEL_ROOM_TYPE_FEATURE_GET_ITEM_SUCCESS",
    "message": "Hotel room type feature get item success",
    "data": [
        {
            "id": "7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50",
            "merchant_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
            "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
            "name": "Wi-Fi",
            "icon": "wifi"
        },
        {
            "id": "8e9d2dfd-39e4-5063-cf26-4bf5c5f68e61",
            "merchant_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
            "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
            "name": "空調",
            "icon": "air-conditioner"
        }
    ]
}
```

**回應範例（有分頁）**:
```json
{
    "code": 200,
    "status": "HOTEL_ROOM_TYPE_FEATURE_GET_ITEM_SUCCESS",
    "message": "Hotel room type feature get item success",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": "7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50",
                "merchant_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
                "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
                "name": "Wi-Fi",
                "icon": "wifi"
            }
        ],
        "first_page_url": "https://api.example.com/api/admin/hotel/room/type/feature?page=1",
        "from": 1,
        "last_page": 1,
        "last_page_url": "https://api.example.com/api/admin/hotel/room/type/feature?page=1",
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "active": false
            },
            {
                "url": "https://api.example.com/api/admin/hotel/room/type/feature?page=1",
                "label": "1",
                "active": true
            },
            {
                "url": null,
                "label": "Next &raquo;",
                "active": false
            }
        ],
        "next_page_url": null,
        "path": "https://api.example.com/api/admin/hotel/room/type/feature",
        "per_page": 15,
        "prev_page_url": null,
        "to": 1,
        "total": 1
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string | 設備ID（UUID） |
| `merchant_id` | string | 商家ID（UUID） |
| `product_id` | string | 分店ID（UUID） |
| `name` | string | 設備名稱 |
| `icon` | string\|null | 圖示名稱或路徑 |

**使用說明**:
- 當提供 `productId` 時，只會返回該分店的設備
- 當提供 `name` 時，會進行模糊搜尋（使用 `LIKE` 查詢）
- 當提供 `state` 時，會過濾特定狀態的設備
- 當提供 `page` 或 `limit` 時，會返回分頁結果
- 預設按 `ordering` 欄位升序排序

##### 取得單筆房型設備
```http
GET /api/admin/hotel/{product_id}/room/type/feature/{id}
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `id` | string | ✅ | 設備ID（UUID） | `"7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50"` |

**請求範例**:
```bash
curl -X GET "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/feature/7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例（成功）**:
```json
{
    "code": 200,
    "status": "HOTEL_ROOM_TYPE_FEATURE_GET_ITEM_SUCCESS",
    "message": "Hotel room type feature get item success",
    "data": {
        "id": "7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50",
        "merchant_id": "f2cba0e8-a21b-4bd8-8cbb-02f3cfc45d95",
        "product_id": "98a635d4-f0a2-423c-8d97-8bd328f6b9a3",
        "name": "Wi-Fi",
        "icon": "wifi"
    }
}
```

**回應範例（找不到）**:
```json
{
    "code": 500,
    "status": "HOTEL_ROOM_TYPE_FEATURE_GET_ITEM_FAIL",
    "message": "Hotel room type feature get item fail",
    "data": null
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | string | 設備ID（UUID） |
| `merchant_id` | string | 商家ID（UUID） |
| `product_id` | string | 分店ID（UUID） |
| `name` | string | 設備名稱 |
| `icon` | string\|null | 圖示名稱或路徑 |

##### 刪除房型設備
```http
DELETE /api/admin/hotel/{product_id}/room/type/feature/{id}
Content-Type: application/json
Authorization: Bearer {token}
```

**URL 參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `product_id` | string | ✅ | 分店ID（UUID） | `"98a635d4-f0a2-423c-8d97-8bd328f6b9a3"` |
| `id` | string | ✅ | 設備ID（UUID） | `"7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50"` |

**請求範例**:
```bash
curl -X DELETE "https://api.example.com/api/admin/hotel/98a635d4-f0a2-423c-8d97-8bd328f6b9a3/room/type/feature/7d8c1cfc-28d3-4952-bf15-3ae4b4e57d50" \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"
```

**回應範例（成功）**:
```json
{
    "code": 200,
    "status": "HOTEL_ROOM_TYPE_FEATURE_DELETE_SUCCESS",
    "message": "Hotel room type feature delete success",
    "data": null
}
```

**回應範例（失敗）**:
```json
{
    "code": 500,
    "status": "HOTEL_ROOM_TYPE_FEATURE_DELETE_FAIL",
    "message": "Hotel room type feature delete fail",
    "data": null
}
```

**使用說明**:
- 刪除操作會從資料庫中永久移除該設備記錄
- 如果設備不存在或刪除失敗，會返回 500 錯誤狀態
- 刪除後無法復原，請謹慎操作

### 住宿單表單管理 API

住宿單表單用於在顧客預訂住宿時收集額外資訊，例如寵物健康狀況、特殊需求等。支援單選、多選和文字輸入三種表單類型。

#### 新增/編輯住宿單表單
```http
POST /api/admin/product/boarding/forms/store
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

| 參數名稱 | 類型 | 必填 | 說明 | 範例值 |
|---------|------|------|------|--------|
| `id` | integer\|null | ❌ | 住宿單表單 ID（更新時必填，新增時為 null） | `1` 或 `null` |
| `title` | string | ✅ | 表單標題 | `"寵物健康狀況"` |
| `type` | string | ✅ | 表單類型 | `"single"`, `"multiple"`, `"text"` |
| `isFirstOnly` | boolean | ✅ | 是否僅首次顯示（僅在第一次預訂時顯示此表單） | `true` 或 `false` |
| `isRequired` | boolean | ✅ | 是否必填 | `true` 或 `false` |
| `state` | integer\|null | ❌ | 狀態 (0:停用, 1:啟用, -2:刪除) | `1` |
| `options` | array\|null | ⚠️ | 選項列表（當 `type` 為 `"single"` 或 `"multiple"` 時必填，至少需一個選項） | `["選項1", "選項2"]` |
| `options.*` | string | ✅ | 選項內容（字串） | `"健康"` |

**type 表單類型說明**:

| 值 | 說明 | 使用場景 |
|----|------|----------|
| `"single"` | 單選 | 只能選擇一個選項，例如：寵物健康狀況（健康/需注意/生病） |
| `"multiple"` | 多選 | 可以選擇多個選項，例如：特殊需求（需要散步/需要餵藥/需要特殊飲食） |
| `"text"` | 文字輸入 | 自由文字輸入，例如：其他備註說明 |

**state 狀態值說明**:

| 值 | 說明 |
|----|------|
| `0` | 停用（表單不顯示） |
| `1` | 啟用（表單正常顯示） |
| `-2` | 刪除（軟刪除） |

**請求範例 - 單選表單**:
```json
{
    "id": null,
    "title": "寵物健康狀況",
    "type": "single",
    "isFirstOnly": true,
    "isRequired": true,
    "state": 1,
    "options": [
        "健康",
        "需注意",
        "生病中"
    ]
}
```

**請求範例 - 多選表單**:
```json
{
    "id": null,
    "title": "特殊需求",
    "type": "multiple",
    "isFirstOnly": false,
    "isRequired": false,
    "state": 1,
    "options": [
        "需要散步",
        "需要餵藥",
        "需要特殊飲食",
        "需要額外照護"
    ]
}
```

**請求範例 - 文字輸入表單**:
```json
{
    "id": null,
    "title": "其他備註",
    "type": "text",
    "isFirstOnly": false,
    "isRequired": false,
    "state": 1,
    "options": null
}
```

**請求範例 - 更新表單**:
```json
{
    "id": 1,
    "title": "寵物健康狀況（已更新）",
    "type": "single",
    "isFirstOnly": true,
    "isRequired": true,
    "state": 1,
    "options": [
        "健康",
        "需注意",
        "生病中",
        "恢復中"
    ]
}
```

**回應範例**:
```json
{
    "status": "success",
    "data": {
        "id": 1,
        "merchant_id": "550770e0-d9fe-481d-a118-f973dfeadbcf",
        "title": "寵物健康狀況",
        "type": "single",
        "isFirstOnly": true,
        "isRequired": true,
        "state": 1,
        "options": [
            "健康",
            "需注意",
            "生病中"
        ],
        "created_at": "2025-01-01T00:00:00.000000Z",
        "updated_at": "2025-01-01T00:00:00.000000Z"
    }
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `id` | integer | 住宿單表單 ID |
| `merchant_id` | string | 商家 ID（UUID） |
| `title` | string | 表單標題 |
| `type` | string | 表單類型 (`"single"`, `"multiple"`, `"text"`) |
| `isFirstOnly` | boolean | 是否僅首次顯示 |
| `isRequired` | boolean | 是否必填 |
| `state` | integer | 狀態 (0:停用, 1:啟用, -2:刪除) |
| `options` | array\|null | 選項列表（當 `type` 為 `"text"` 時為 `null`） |
| `created_at` | string | 建立時間 (ISO 8601) |
| `updated_at` | string | 更新時間 (ISO 8601) |

#### 搜尋住宿單表單列表
```http
POST /api/admin/product/boarding/forms/search
Content-Type: application/json
Authorization: Bearer {token}
```

**請求參數**:

無需傳遞參數，會自動根據當前商家的 `merchant_id` 查詢所有住宿單表單。

**請求範例**:
```json
{}
```

**回應範例**:
```json
{
    "status": "success",
    "data": [
        {
            "id": 1,
            "merchant_id": "550770e0-d9fe-481d-a118-f973dfeadbcf",
            "title": "寵物健康狀況",
            "type": "single",
            "isFirstOnly": true,
            "isRequired": true,
            "state": 1,
            "options": [
                "健康",
                "需注意",
                "生病中"
            ],
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z"
        },
        {
            "id": 2,
            "merchant_id": "550770e0-d9fe-481d-a118-f973dfeadbcf",
            "title": "特殊需求",
            "type": "multiple",
            "isFirstOnly": false,
            "isRequired": false,
            "state": 1,
            "options": [
                "需要散步",
                "需要餵藥",
                "需要特殊飲食"
            ],
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z"
        },
        {
            "id": 3,
            "merchant_id": "550770e0-d9fe-481d-a118-f973dfeadbcf",
            "title": "其他備註",
            "type": "text",
            "isFirstOnly": false,
            "isRequired": false,
            "state": 1,
            "options": null,
            "created_at": "2025-01-01T00:00:00.000000Z",
            "updated_at": "2025-01-01T00:00:00.000000Z"
        }
    ]
}
```

**回應欄位說明**:

| 欄位名稱 | 類型 | 說明 |
|---------|------|------|
| `data` | array | 該商家的所有住宿單表單列表 |
| `data[].id` | integer | 住宿單表單 ID |
| `data[].merchant_id` | string | 商家 ID（UUID） |
| `data[].title` | string | 表單標題 |
| `data[].type` | string | 表單類型 (`"single"`, `"multiple"`, `"text"`) |
| `data[].isFirstOnly` | boolean | 是否僅首次顯示 |
| `data[].isRequired` | boolean | 是否必填 |
| `data[].state` | integer | 狀態 (0:停用, 1:啟用, -2:刪除) |
| `data[].options` | array\|null | 選項列表（當 `type` 為 `"text"` 時為 `null`） |
| `data[].created_at` | string | 建立時間 (ISO 8601) |
| `data[].updated_at` | string | 更新時間 (ISO 8601) |

**使用說明**:
- 表單會依照建立順序顯示在預訂流程中
- 當 `isFirstOnly` 為 `true` 時，該表單僅在會員第一次預訂住宿時顯示
- 當 `isRequired` 為 `true` 時，顧客必須填寫該表單才能完成預訂
- 表單的 `state` 為 `0`（停用）時，表單不會顯示在預訂流程中

## 🚨 錯誤處理

### 統一錯誤格式

所有 API 都會回傳統一的錯誤格式：

```json
{
    "status": "error",
    "message": "錯誤訊息",
    "errors": {
        "field_name": ["具體錯誤描述"]
    }
}
```

### 錯誤代碼

> 💡 **詳細的回應碼說明**：查看 [Hotel Constants 文件](../constants/hotel-constants.md) 了解所有 API 回應碼與訊息的完整定義。

## 🔐 認證與權限

### 認證方式
所有 API 都需要透過 Bearer Token 進行認證：

```http
Authorization: Bearer {your_access_token}
```

### 權限要求
- 需要管理員權限
- 需要商戶權限 (`hasMerchant` 中介層)
- 需要檢查商戶是否過期 (`expired` 中介層)

### 中介層
所有 API 都使用以下中介層：
- `hasMerchant`: 檢查商戶權限
- `expired`: 檢查商戶是否過期
- `admin`: 檢查管理員權限

---

**最後更新**: 2026年1月26日  
**分支**: `ipetbooking/feat/pet-hotel`  
**異動內容**: 
- 新增商家設定管理 API (取得/儲存寵物旅館設定)
- 房況查詢 API 新增 `priceStartAt` 欄位（房型起始價格）
- 新增取得分店房型列表 API (`GET /api/admin/hotel/{product_id}/room/type`)
- 新增取得房型詳細資料 API (`GET /api/admin/hotel/room/type/{id}`)
- 新增設定房型狀態 API (`POST /api/admin/hotel/room/type/state`)
- 新增取得房型不開放日期 API (`GET /api/admin/hotel/{product_id}/room/type/{room_type_id}/closedDates`)
- 新增住宿單表單管理 API (`POST /api/admin/product/boarding/forms/store`, `POST /api/admin/product/boarding/forms/search`)
- 新增輔助 API 說明（取得寵物分類列表、取得分店列表）
- 新增取得商家付款方式 API (`GET /api/admin/merchant/payment-methods`)
- 新增房型設備管理 API (`POST /api/admin/hotel/room/type/feature`, `GET /api/admin/hotel/room/type/feature`, `GET /api/admin/hotel/room/type/feature/{id}`, `DELETE /api/admin/hotel/room/type/feature/{id}`)
- 新增預約查詢 API (`GET /api/admin/hotel/{product_id}/bookings`)
- 新增住宿訂單管理 API (`GET /api/admin/hotel/{product_id}/order/{container_uuid}`)
- 新增取得住宿訂單列表 API (`GET /api/admin/hotel/order`)
- 新增匯出住宿訂單 CSV API (`GET /api/admin/hotel/order/export`)
- 新增更新住宿訂單 API（支援更新 `status` 和 `note`）(`PATCH /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}`)
- 新增建立或更新住宿訂單表單 API (`POST /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/form`)
- 新增取得住宿訂單表單 API（包含寵物資訊和住宿單題目）(`GET /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/form`)
- 新增處理訂單操作 API（CONFIRM/CANCEL/REJECT/CHECK_IN/NO_SHOW/CHECK_OUT）(`POST /api/admin/hotel/{product_id}/order/{container_uuid}/{order_id}/action`)
- 新增建立分期付款 API(`POST /api/admin/hotel/{product_id}/order/{container_uuid}/installment`)
