---
# System prepended metadata

title: Zhsports API

---

# Zhsports API

---
## 更新紀錄
- 2024/02/16
    - [取得場地資訊(for預約)](#取得場地資訊(for預約)) 新增了paid,occupied_name兩個欄位
    - 新增場地類別相關 [場地類別](#場地類別)
- 2024/02/19
    - [建立使用者資料](#建立使用者資料), [修改使用者基本資料](#修改使用者基本資料) 多了緊急聯絡人欄位
    - [場地關閉維護](#場地關閉維護), [場地價格維護](#場地價格維護) 多一個欄位==until==可以指定結束日期
- 2024/02/21
    - 修改[取得場地列表](#取得場地列表), 新增[修改場地資料](#修改場地資料), [上傳場地圖片](#上傳場地圖片)
- 2024/02/25
    - 修改[透過場地類別取得場地資訊(for預約)](#透過場地類別取得場地資訊(for預約)) and [取得場地資訊(for預約)](#取得場地資訊(for預約)) 的已經預約資料會多一個==reservation==
    - 新增[商品價格試算](#商品價格試算)目前只支援場地預約
- 2024/03/03
    - 新增[透過場地類別取得場地價格設定列表](#透過場地類別取得場地價格設定列表),[透過場地類別設定場地預設價格](#透過場地類別設定場地預設價格),[透過場地類別新增場地價格](#透過場地類別新增場地價格),[透過場地類別刪除場地價格](#透過場地類別刪除場地價格),[透過場地類別取得場地關閉設定列表](#透過場地類別取得場地關閉設定列表),[透過場地類別新增場地關閉](#透過場地類別新增場地關閉),[透過場地類別刪除場地關閉](#透過場地類別刪除場地關閉)共七支API
    - 修改[透過場地類別修改場地價格](#透過場地類別修改場地價格)
- 2024/03/05
    - 修改[預約](#預約)，多一個==occupied_name==會自動建立資料
- 2024/04/02
    - 新增[其他功能](#其他功能)
- 2024/04/12
    - 新增[卡片相關功能](#卡片相關功能)
- 2024/04/22
    - 新增[批次場地預約](#預約)
- 2024/04/23
    - 新增[搜尋預約記錄(透過場地類別)](#搜尋預約記錄(透過場地類別))
- 2024/04/25
    - 新增[重新預約(限定已取消且時段都還沒過期的)](#重新預約(限定已取消且時段都還沒過期的))
- 2024/05/22
    - 新增[固定電話驗證碼](#固定電話驗證碼)
- 2024/05/23
    - 新增[修改固定驗證碼](#修改固定驗證碼)
- 2024/06/05
    - 新增[課程](#課程)
    - 修改[商品價格試算](#商品價格試算)新增支援課程報名結帳試算
- 2024/06/11
    - 修改[課程](#課程)新增[刪除課程(還未有人報名時)](#刪除課程(還未有人報名時)),[修改課程(還未有人報名時)](#修改課程(還未有人報名時)),[刪除單堂(還未有人報名時)](#刪除單堂(還未有人報名時)),[取得選課資料(for繳費)](#取得選課資料(for繳費))
- 2024/07/01
    - 新增[發票入庫(兩種做法)](#發票入庫(兩種做法))
- 2024/07/15
    - 修改[拿敬老卡列表(沿用舊系統的員工卡列表加上參數)](#拿敬老卡列表(沿用舊系統的員工卡列表加上參數))加了月卡相關的回應
- 2024/07/18
    - 新增[透過QRCode課程報到](#透過QRCode課程報到)
- 2024/07/22
    - 新增[補票查詢](#補票查詢),[補票](#補票),[列印信用卡簽單](#列印信用卡簽單)
- 2024/08/01
    - 修改[取得選課資料(for繳費or點名)](#取得選課資料(for繳費or點名))新增回應的欄位==invoice==
    - 修改[取得場地資訊(for預約)](#取得場地資訊(for預約))新增回應的資料==order_info==
- 2024/08/09
    - 修改[拿敬老卡列表(沿用舊系統的員工卡列表加上參數)](#拿敬老卡列表(沿用舊系統的員工卡列表加上參數))新增type參數==app_monthly==
- 2024/08/16
    - 新增[交易紀錄查詢](#交易紀錄查詢),[交易紀錄查詢(單筆)](#交易紀錄查詢(單筆)),[交易紀錄作廢/折讓](#交易紀錄作廢/折讓)
- 2024/08/20
    - 新增[統計(會員人數)](#統計(會員人數))
- 2024/08/26
    - 修改[預約](#預約)加入了==備註==
- 2024/08/27
    - 修改[交易紀錄查詢](#交易紀錄查詢)，新增參數==by==
- 2024/09/10
    - 新增[Kiosk相關](#Kiosk相關), [結帳](#結帳), [取得結帳紀錄](#取得結帳紀錄)
- 2024/09/11
    - 修改[取得場地資訊(for預約)](#取得場地資訊(for預約))新增回應的資料==order_info中的vat==
- 2024/09/16
    - 新增[結帳(POS版)](#結帳(POS版)), [取得結帳紀錄(POS版)](#取得結帳紀錄(POS版))
- 2024/09/19
    - 新增[課程欄位解說](#課程欄位解說)的數個新欄位：==enable_returning_student_discount, previous_course, returning_student_threshold, returning_student_after_discount_unit_price, returning_student_after_discount_total_price==
- 2024/09/24
    - 修改[商品價格試算](#商品價格試算)新增支援==課程舊生優惠==
- 2024/09/26
    - 新增[確認有無符合舊生優惠](#確認有無符合舊生優惠)
- 2024/10/11
    - 新增[取得Kiosk印表機狀態(建議每30秒call一次)](#取得Kiosk印表機狀態建議每30秒call一次)
- 2024/10/21
    - 修改[取得結帳紀錄](#取得結帳紀錄)
    - 修改[取得結帳紀錄(POS版)](#取得結帳紀錄(POS版))
- 2024/10/23
    - 新增[取得Kiosk設定及狀態(POS及KIOSK用)](#取得Kiosk設定及狀態(POS及KIOSK用))
    - 新增[設定Kiosk(POS用)](#設定Kiosk(POS用))
- 2024/10/26
    - 新增KIOSK的api[Heartbeat](#Heartbeat)
    - 修改[取得Kiosk設定及狀態(POS及KIOSK用)](#取得Kiosk設定及狀態(POS及KIOSK用))，回傳多一個欄位==app_alive==
- 2024/10/27
    - 修改[取得Kiosk設定及狀態(POS及KIOSK用)](#取得Kiosk設定及狀態(POS及KIOSK用))及[設定Kiosk(POS用)](#設定Kiosk(POS用))，多一個欄位==enable==
- 2024/11/13
    - 新增[查詢場地預約紀錄](#查詢場地預約紀錄)
- 2025/01/03
    - 新增[建立APP通知任務](#建立APP通知任務)
    - 新增[取得APP通知任務列表](#取得APP通知任務列表)
    - 新增[修改APP通知任務](#修改APP通知任務正在執行中的無法修改date-time)
    - 新增[刪除/取消APP通知任務](#刪除取消APP通知任務會將執行到一半的任務取消)
- 2025/01/13
    - 新增[發票相關特別操作](#發票相關特別操作)
- 2025/02/26
    - 修改[交易紀錄查詢](#交易紀錄查詢)，多一個欄位：==transaction_id==
- 2025/03/07
    - 新增[建立APP的操作](#建立APP的操作)
    - 新增[修改APP的操作](#修改APP的操作)
    - 新增[刪除APP的操作](#刪除APP的操作)
    - 新增[取得APP的操作列表](#取得APP的操作列表)
- 2025/03/14
    - 新增[建立月卡的操作](#建立月卡的操作)
    - 新增[執行月卡的操作](#執行月卡的操作)
    - 新增[刪除月卡的操作](#刪除月卡的操作)
    - 新增[取得月卡的操作列表](#取得月卡的操作列表)
- 2025/04/14
    - 修改[建立月卡的操作](#建立月卡的操作)，多一個欄位：==specific==
- 2025/04/29
    - 修改[建立APP的操作](#建立APP的操作)，多了兩個欄位：==comment==, ==reason==
- 2025/05/08
    - 修改[查詢場地預約紀錄](#查詢場地預約紀錄)，多一個欄位：==canceled_at==
    - 新增[建立報表類別](#建立報表類別)
    - 新增[修改報表類別](#修改報表類別)
    - 新增[刪除報表類別](#刪除報表類別)
    - 新增[取得報表類別列表](#取得報表類別列表)
- 2025/05/29
    - 修改[建立報表類別](#建立報表類別) [修改報表類別](#修改報表類別) [取得報表類別列表](#取得報表類別列表)  多一個欄位：==childrn==
- 2025/06/11
    - 修改[建立報表類別](#建立報表類別) [修改報表類別](#修改報表類別) [取得報表類別列表](#取得報表類別列表) 多一個欄位:==code==
- 2025/07/29
    - 修改[取得使用者列表](#取得使用者列表)，修改成支援分頁排序及搜尋
- 2025/08/19
    - 新增[補印](#補印)
---

```
Test Url: http://192.168.5.206:8964
Product Url: http://192.168.5.200:8964
```

## 注意事項
- 他們會需要編輯自己的商品id, 所以零售商品/購課他們都會有自己編的id
- 除了登入以外所有的Request都會需要Authorization Header: zhs [token]

## 登入員工
```
POST /staff
```

- Body
```jsonld=
{
    "account": "richard", 
    "password": "richard"
} // or curtis/curtis allen/allen
```

- Response
```
[token] // 將這個token放到之後所有的Request中的Header: Authorization=zhs [token] 
```

## 取得登入員工帳號的資訊
```
GET /me
```

- Response
```jsonld=
{
    "expired_at": 1707143598, // [unix timstamp] 這個token過期的時間
    "functions": [
        {
            "desc": "建立員工帳號",
            "method": "POST",
            "path": "/staff",
            "type": "api"
        },
        {
            "desc": "結帳",
            "method": "POST",
            "path": "/checkout",
            "type": "api"
        },
        {
            "desc": "編輯使用者帳號",
            "method": "PUT",
            "path": "/user/:id",
            "type": "api"
        },
        {
            "desc": "取得角色列表",
            "method": "GET",
            "path": "/roles",
            "type": "api"
        },
        {
            "desc": "取得使用者清單",
            "method": "GET",
            "path": "/users",
            "type": "api"
        },
        {
            "desc": "建立使用者帳號",
            "method": "POST",
            "path": "/user",
            "type": "api"
        },
        {
            "desc": "建立角色",
            "method": "POST",
            "path": "/role",
            "type": "api"
        },
        {
            "desc": "取得員工帳號列表",
            "method": "GET",
            "path": "/staffs",
            "type": "api"
        },
        {
            "desc": "編輯零售商品",
            "method": "PUT",
            "path": "/retail/:id",
            "type": "api"
        },
        {
            "desc": "建立權限功能",
            "method": "POST",
            "path": "/function",
            "type": "api"
        },
        {
            "desc": "刪除權限功能",
            "method": "DELETE",
            "path": "/function/:id",
            "type": "api"
        },
        {
            "desc": "取得後台統計數字",
            "method": "GET",
            "path": "/statistics",
            "type": "api"
        },
        {
            "desc": "刪除員工帳號",
            "method": "DELETE",
            "path": "/staff/:id",
            "type": "api"
        },
        {
            "desc": "編輯員工帳號",
            "method": "PUT",
            "path": "/staff/:id",
            "type": "api"
        },
        {
            "desc": "取得零售商品列表",
            "method": "GET",
            "path": "/retails",
            "type": "api"
        },
        {
            "desc": "建立零售商品",
            "method": "POST",
            "path": "/retail",
            "type": "api"
        },
        {
            "desc": "取得權限列表",
            "method": "GET",
            "path": "/functions",
            "type": "api"
        },
        {
            "desc": "編輯角色權限",
            "method": "PUT",
            "path": "/role/:id",
            "type": "api"
        }
    ],
    "id": "e6a590ea-8eba-4b99-9ec1-0ab321d1de88",
    "name": "richard",
    "role": "super"
}
```

---
# 會員系統

## 建立使用者資料
```
POST /user
```

- Body
```jsonld=
{
    "phone": "+886972957297", // 這個是主key之後app登入也是用這個務必要用+886開頭
    "profile": {
        "name": "Richard",
        "email": "test@gmail.com",
        "gender": "男", // 男/女/不指定
        "birthday": "1984/01/01",
        "emergencyContactName": "tester",
        "emergencyContactPhone": "0212345678"
    }
}
```

- Response
```jsonld=
{
    "banned": false,
    "id": "pos_bfc40179-6e27-4ce5-97a0-e78fecd5e221",
    "inserted_at": "2024-02-05T02:40:06",
    "latest_sync": 1707100806,
    "phone": "+886972957297",
    "profile": {
        "birthday": "1984/01/01",
        "email": "test@gmail.com",
        "gender": "男",
        "name": "Richard",
        "emergencyContactName": "tester",
        "emergencyContactPhone": "0212345678"
    },
    "updated_at": "2024-02-05T02:40:06",
    "headshot": "http://zhs.toolset.biz:8964/user/headshot/pos_bfc40179-6e27-4ce5-97a0-e78fecd5e221"
}
```

## 上傳使用者頭像
```
POST /user/[user id]/headshot
```

- Body [form-data]
```
headshot: [頭像檔案.png/.jpg]
```

- Response
```
ok
```

## 取得使用者列表
```
GET /users
```

- Query Parameters
    - search_key: phone(預設) or name or id
    - term: 關鍵字,電話(可以是09/9/+886/886/其他號碼開頭)
    - size: 每頁的筆數
    - page: 第幾頁
    - dir: asc or desc排序方向(目前寫死以電話排序)
    

- Response(無參數時)
```jsonld=
[
    {
        "banned": false,
        "id": "pos_bfc40179-6e27-4ce5-97a0-e78fecd5e221",
        "headshot": [頭像url],
        "inserted_at": "2024-02-05T02:40:06",
        "latest_sync": 1707100806,
        "phone": "+886972957297",
        "profile": {
            "birthday": "1984/01/01",
            "email": "test@gmail.com",
            "gender": "男",
            "name": "Richard",
            "emergencyContactName": "tester",
            "emergencyContactPhone": "0212345678"
        },
        "updated_at": "2024-02-05T02:40:06"
    }
]
```

- Response(有參數時)
```jsonld=
{
    "count": 17144, // 總筆數
    "dir": "desc", // 排序方向
    "list": [
        {
            "banned": false,
            "headshot": "https://zhs.mraytec.com/user/headshot/pos_83500963-89cb-417b-8c7c-23dd6f741c86?t=43224",
            "id": "pos_83500963-89cb-417b-8c7c-23dd6f741c86",
            "inserted_at": "2024-09-17T02:18:21",
            "latest_sync": 1726539501,
            "phone": "+886999999999",
            "profile": {
                "name": "維修中"
            },
            "updated_at": "2024-09-17T02:18:21"
        },
        ...
    ],
    "max_page": 858, // 總頁數
    "page": 1, // 目前是第幾頁
    "size": 20 // 每頁的筆數
}
```

## 搜尋使用者
```
GET /users/search
```

- Query Parameters
    - term: 會員電話關鍵字(可以是09/9/+886/886/其他號碼開頭)

- Response
```jsonld=
[
    {
        "banned": false,
        "id": "pos_bfc40179-6e27-4ce5-97a0-e78fecd5e221",
        "inserted_at": "2024-02-05T02:40:06",
        "latest_sync": 1707100806,
        "phone": "+886972957297",
        "profile": {
            "birthday": "1984/01/01",
            "email": "test@gmail.com",
            "gender": "男",
            "name": "Richard",
            "emergencyContactName": "tester",
            "emergencyContactPhone": "0212345678"
        },
        "updated_at": "2024-02-05T02:40:06"
    }
]
```

## 修改使用者基本資料
```
PUT /user/[user_id]
```

- Body
```jsonld=
{
    "name": "Richard123",
    "email": "test123@gmail.com",
    "gender": "男",
    "birthday": "1984/02/01",
    "emergencyContactName": "tester",
    "emergencyContactPhone": "0212345678"
}
```

- Response
```
ok
```

---
# 其他功能

## 開門
```
PUT /gate/pass
```

- Body
```jsonld=
{
    "position": "pool", // "pool" 泳池 "fitness" 健身房
    "direction": "enter" // "enter" 進去 "exit" 出來
}
```

- Response
```
ok
```

## 取得人流
```
GET /state/:position
```

- position: "pool" 泳池 "fitness" 健身房

- Response
```jsonld=
{
    "value": 0 // 人數
}
```

## 統計(會員人數)
```
GET /statistics/users
```

- Response
```jsonld=
{
    "app": 5088, // APP註冊的
    "pos": 1630 // POS建的
}
```

## 取得Kiosk印表機狀態(建議每30秒call一次)
```
GET /machine/notify
```

- Response
```jsonld=
{
    "alert": false, // 當為true時將下面message的訊息show出來
    "message": "所有印表機狀態正常"
}
```

## 取得Kiosk設定及狀態(POS及KIOSK用)
```
GET /machine/kiosks
```

- Response (POS的)
```jsonld=
{
    "kiosks": [
        {
            "alive": false, // 機台狀態
            "comment": "", // 備註
            "course_attend": false, // 啟用課程報到
            "fitness_compansate": false, // 啟用健身房補票
            "fitness_ticket": false, // 啟用健身房全票
            "ip": "192.168.17.41", // ip
            "location": "unknown", // 位置
            "name": "櫃台售票機", // 名稱
            "pool_ticket": false, // 啟用泳池全票
            "printer": false, // 印表機狀態
            "app_alive": false, // Kiosk APP狀態
            "enable": true // 啟用？true/false
        },
        {
            "alive": false,
            "comment": "",
            "course_attend": false,
            "fitness_compansate": false,
            "fitness_ticket": false,
            "ip": "192.168.17.43",
            "location": "unknown",
            "name": "Kiosk3",
            "pool_ticket": false,
            "printer": false,
            "app_alive": false,
            "enable": true
        },
        {
            "alive": false,
            "comment": "",
            "course_attend": false,
            "fitness_compansate": false,
            "fitness_ticket": false,
            "ip": "192.168.17.42",
            "location": "大廳",
            "name": "櫃台售票機2",
            "pool_ticket": false,
            "printer": false,
            "app_alive": false,
            "enable": true
        }
    ]
}
```

- Response (KIOSK的會給的資料是根據送request的ip)
```jsonld=
{
    "alive": false,
    "comment": "",
    "course_attend": false,
    "fitness_compansate": false,
    "fitness_ticket": false,
    "ip": "192.168.17.42",
    "location": "大廳",
    "name": "櫃台售票機2",
    "pool_ticket": false,
    "printer": false,
    "app_alive": false,
    "enable": true
}
```

## 設定Kiosk(POS用)
```
PUT /machine/kiosks/[ip]
```

- Body
```jsonld=
{
    "name": "櫃台售票機2", // 名稱
    "location": "大廳", // 位置
    "fitness_ticket": false, // 啟用健身房全票
    "pool_ticket": false, // 啟用泳池全票
    "fitness_compansate": false, // 啟用健身房補票
    "course_attend": false, // 啟用課程報到
    "enable": false, // 啟用？true/false
    "comment": "" // 備註
}
```

- Response
```
ok
```

## 補印

### 小白單
```
PUT /print/course_detail
```
- Body
```jsonld=
{
    "order_id": "xxxxxxxxx", // 訂單id
}
```

### 報到QRCode
```
PUT /print/course_attendance
```
- Body
```jsonld=
{
    "phone": "+886912345678",
    "course": "course id"
}
```

- Response
```
ok
```

---
# APP相關

## 建立APP通知任務
```
POST /notifications
```

- Body
```jsonld=
{
    "course": "課程id",
    "phone": "+886xxxxxxxxx", // 使用者電話 phone跟student必須要有一個
    "student": "xxxxxxxxx" // 學生id phone跟student必須要有一個
}
```

- Response
```
ok
```

## 取得APP通知任務列表
```
GET /notifications
```

- Response
```jsonld=
[
    {
        "id": "935aa73d-94d2-4134-bfb4-f9f4fff54a84",
        "status": "未執行", // 目前狀態
        "date": "2025/01/03",
        "time": "12:25",
        "progress": null, // 目前進度 會以(已發送/總數)顯示如：10/999 還沒開始會是null
        "title": "test title",
        "comment": "test",
        "content": "test content",
        "inserted_at": "2025/01/03 12:17",
        "updated_at": "2025/01/03 12:17"
    }
]
```

## 修改APP通知任務(正在執行中的無法修改date, time)
```
PUT /notifications/[notification id]
```

- Body
```jsonld=
{
    "date": "2025/01/03", // 日期 ＊無法比目前早
    "time": "12:30",      // 時間
    "title": "test title", // 標題
    "content": "test content", // 內容
    "comment": "test" // comment
}
```

- Response
```
ok
```

## 刪除/取消APP通知任務(會將執行到一半的任務取消)
```
DELETE /notifications/[notification id]
```

- Response
```
ok
```

## 建立APP的操作
```
POST /app_operations
```

- Body
```jsonld=
// 建立app公告
{
    "comment": "xxoo",
    "reason": "ooxx",
    "type": "announce", // 公告必定是announce
    "start": "2025-03-06 00:00:00", // 開始
    "until": "2025-03-08 23:00:00", // 結束
    "announce_title": "重大公告", // 公告標題
    "announce_content": "系統將於2025年3月6日進行維護，預計維護時間為2025年3月6日00:00:00至2025年3月8日23:00:00。維護期間，系統將無法正常使用，請您提前做好準備。" // 公告內容
}
// 建立app的售票,場地,課程這三項服務的關閉
{
    "comment": "xxoo",
    "reason": "ooxx",
    "type": "service", // 服務必定是service
    "start": "2025-03-06 00:00:00", // 開始
    "until": "2025-03-08 23:00:00", // 結束
    "ticket": false, // 售票false是關閉預設是開啟
    "reservation": false, // 場地預約
    "course": false // 課程報名
}
```

- Response
```jsonld=
// 公告
{
    "announce_content": "系統將於2025年3月6日進行維護，預計維護時間為2025年3月6日00:00:00至2025年3月8日23:00:00。維護期間，系統將無法正常使用，請您提前做好準備。",
    "announce_title": "重大公告",
    "comment": "xxoo",
    "reason": "ooxx",
    "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
    "enabled": true, // 這個設定的開關true是開false是關
    "id": "a32bb1f4-bf64-4037-9aa0-7527664089be",
    "inserted_at": "2025/03/07 10:57:44",
    "start": "2025/03/06 00:00:00",
    "type": "announce",
    "until": "2025/03/08 23:00:00",
    "updated_at": "2025/03/07 10:57:44",
    "updated_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4"
}
// 服務
{
    "enabled": true, // 這個設定的開關true是開false是關
    "id": "23958b65-90dc-4904-8c5d-a1fc5a25f33f",
    "start": "2025/03/06 00:00:00",
    "type": "service",
    "until": "2025/03/06 23:00:00",
    "comment": "xxoo",
    "reason": "ooxx",
    "ticket": false,
    "inserted_at": "2025/03/07 11:42:21",
    "updated_at": "2025/03/07 11:42:21",
    "course": false,
    "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
    "updated_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
    "reservation": false
}
```

## 修改APP的操作
```
PUT /app_operations/[app operation id]
```

- Body
```jsonld=
// 公告
{
    "announce_title": "test123"
}
// 服務
{
    "ticket": true,
    "reservation": true,
    "course": true
}
```

- Response
```
同建立
```

## 刪除APP的操作
```
DELETE /app_operations/[app operation id]
```

- Response
```
ok
```

## 取得APP的操作列表
```
GET /app_operations
```

- Response
```jsonld=
{
    "count": 2,
    "rows": [
        {
            "enabled": true,
            "id": "a32bb1f4-bf64-4037-9aa0-7527664089be",
            "start": "2025/03/06 00:00:00",
            "type": "announce",
            "until": "2025/03/08 23:00:00",
            "comment": "xxoo",
            "reason": "ooxx",
            "inserted_at": "2025/03/07 10:57:44",
            "updated_at": "2025/03/07 11:00:58",
            "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
            "updated_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
            "announce_content": "系統將於2025年3月6日進行維護，預計維護時間為2025年3月6日00:00:00至2025年3月8日23:00:00。維護期間，系統將無法正常使用，請您提前做好準備。",
            "announce_title": "test123"
        },
        {
            "enabled": true,
            "id": "23958b65-90dc-4904-8c5d-a1fc5a25f33f",
            "start": "2025/03/06 00:00:00",
            "type": "service",
            "until": "2025/03/06 23:00:00",
            "comment": "ooxx",
            "reason": "xxoo",
            "ticket": false,
            "inserted_at": "2025/03/07 11:42:21",
            "updated_at": "2025/03/07 11:42:21",
            "course": false,
            "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
            "updated_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
            "reservation": false
        }
    ]
}
```

---

---
# Kiosk相關

## Tokens
- Kiosk1: k_125dddf4-4a11-4ac3-a092-e57fa15331ce
- Kiosk2: k_acd1854a-915e-4e8e-a79f-c15c537c9a56
- Kiosk3: k_ff8066f1-1f9e-4f57-b5f7-abff8620e744

## HTTP Header
```
Authorization: Kiosk [token]
```

## 結帳
```
PUT /creditcardsettlement
```

- Body
```jsonld=
{
    "token": "[token]",       // 請用[員工登入]拿到的token
    "data": {                 // 隨便要放什麼都可以
        "test1": "value1",
        "test2": "value2"
    }
}
```

- Response
```
ok
```

## 取得結帳紀錄
```
GET /creditcardsettlement
```

- Query Parameters
    - token: 請用[員工登入]拿到的token，必填
    - filter: today/week/month/date 可以用這三個參數不給的時候預設today，其他就代表從今天往後7天/30天
    - date: yyyy/MM/dd 只有當filter為date時才會生效
    
- Response: 
```jsonld=
{
    "count": 1,
    "datas": [
        {
            "data": { // 結帳時用存入的資料
                "test1": "value1",
                "test2": "value2"
            },
            "date": "2024/09/10", // 結帳的日期
            "time": "14:35:10", // 結帳的時間
            "from": "127.0.0.1", // 哪一臺結的帳
            "by": "richard" // 哪個帳號結的帳
        }
    ]
}
```

## Heartbeat
```
PUT /machine/kiosks/heartbeat
```
    
- Response: 
```
ok
```

---

---
# 一般POS功能

## 上架零售商品
```
POST /retail
```

- Body
```jsonld=
{
    "id": "R001",
    "name": "毛巾(小)",
    "desc": "小條毛巾",
    "price": 300
}
```

- Response
```jsonld=
{
    "comment": null,
    "created_by": "6bf4f54c-9e69-40a8-87f9-0e2ab6b2b1fa",
    "desc": "小條毛巾",
    "hide": null,
    "id": "R001",
    "inserted_at": "2024-02-05T05:00:25",
    "name": "毛巾(小)",
    "price": 300,
    "updated_at": "2024-02-05T05:00:25"
}
```

## 取得零售商品列表
```
GET /retails?all=false
```

- Query Parameters
    - all: false就不會包括hide=true的
    
- Response
```jsonld=
[
    {
        "comment": null,
        "created_by": "6bf4f54c-9e69-40a8-87f9-0e2ab6b2b1fa",
        "desc": "小條毛巾",
        "hide": false,
        "id": "R001",
        "inserted_at": "2024-02-05T05:00:25",
        "name": "毛巾(小)",
        "price": 300,
        "updated_at": "2024-02-05T05:00:25"
    }
]
```

## 使用id搜尋零售商品
```
GET /retails/search
```

- Query Parameters
    - key: 商品id的內容 ex: R
    
- Response
```jsonld=
[
    {
        "comment": null,
        "created_by": "6bf4f54c-9e69-40a8-87f9-0e2ab6b2b1fa",
        "desc": "小條毛巾",
        "hide": false,
        "id": "R001",
        "inserted_at": "2024-02-05T05:00:25",
        "name": "毛巾(小)",
        "price": 300,
        "updated_at": "2024-02-05T05:00:25"
    }
]
```

## 修改零售商品資料
```
PUT /retail/[retail id]
```

- Body
```jsonld=
{
    "price": 1000,
    "hide": true,
    "comment": "test"
}
```

- Response
```
ok
```

## 商品價格試算
```
PUT /checkout/calculate
```

- Body
```jsonld=
{
    "items": [
        // 以下是當場地預約試算價格時要傳
        {
            "type": "reservation",
            "field": "3樓羽球場A",
            "date": "2024/02/26",
            "time": "12:00",
            "duration": 150
        },
        // 以下是當課程報名試算價格時要傳
        {
            "type": "course",
            "takes": [...] // 報名id array
        }
    ]
}
```

- Response
```jsonld=
{
    "items": [
        {
            "date": "2024/02/26",
            "duration": 150,
            "field": "3樓羽球場A",
            "price": 1250, // 各個品項的價格
            "time": "12:00",
            "type": "reservation"
        },
        {
            "type": "course",
            "takes": [...], // 報名id array
            "price": 100,
            "match_returning_student_discount": true/false, // 有無符合舊生優惠
            "origin_price": 200 // 原價,當match_returning_student_discount==true時才會有
        }
    ],
    "total": 2250 // 總金額
}
```

## 結帳
```
POST /checkout
```

- Body
```jsonld=
{
    "items": [
        {
            "type": "retail",
            "item": "R001",
            "price": 300,
            "count": 2,
            "cost": 550
        },
        {
            "type": "retail",
            "item": "R001",
            "price": 300,
            "count": 2,
            "cost": 600
        }
        
    ]
}
```

- Response
```
ok
```

## 發票入庫(兩種做法)
```
POST /invoices
```

- Body [form-data] (上傳CSV)
```
file: [.csv] // 一定要是財政部的csv
```

- Body (手動)
```jsonld=
{
    "month": "113/09~113/10", // 發票的月份
    "word_track": "CC", // 發票頭兩碼
    "begin": "00000000", // 發票起
    "finish": "00000100" // 發票迄
}
```

- Response
```
ok
```

## 補票查詢
```
GET /pass/[掃到的內容]
```

- Response
```jsonld=
{
    "overtime": 60, // 超時的秒數,
    "enter_at": "2024/07/22 05:19", // 進場的時間
    "name": "ooxx", //票券名稱
    "compensate": 50, // 試算出的補票金額
    "after_compensate": "2024/07/22 07:19" // 補票後要什麼時候之前離開
}
```

## 補票
```
POST /sales
```

- Body (欄位與結帳的一模一樣，但是多了一個欄位)
```jsonld=
{
    "amount": 50,
    "carrier_num": "/14ZREPR",
    "carrier_type": 0,
    "creditcard": "",
    "items": [
        {
            "count": 1,
            "item": 4,
            "price": 50,
            "type": 0,
            "ticket": "ooxx" // 讀卡機讀取到的內容，必填不然不會自動補票，只會產生一筆交易
        }
    ],
    "love_code": "",
    "paymentType": 0,
    "print_detail": true,
    
}
```

## 列印信用卡簽單
```
PUT /print/creditcardtx
```

- Body
```jsonld=
卡機給的所有資料的json
```

- Response
```
ok
```

---

## 交易紀錄查詢
```
GET /orders
```

- Query Parameters (date預設當日,start必須與end一起使用不然沒有效果,date與start/end一起用時只會生效date,==用by的時候其他條件都會忽略==)
    - date: 指定日期(yyyy/MM/dd)
    - start: 開始日期(yyyy/MM/dd)
    - end: 結束日期日期(yyyy/MM/dd)
    - ==by: 訂單擁有者(+886開頭會員，目前不支援pos機)==

- Response
```jsonld
{
    "count": 123, //筆數
    "rows": [
        {
            "id": 2024080100001, // 訂單編號
            "fid": "20240801000030", // 訂單假編號(APP上顯示)
            "status": "已付款", // 狀態直接顯示就好
            "date": "20240801", // 日期
            "time": "00:00:30", // 時間
            "source": "APP", // 訂單來源
            "pay_type": "APP線上刷卡", // 付款方式
            "invoice": "DN02586742", // 發票號碼
            "by": "+886970363575", // 訂單擁有者+886開頭是會員 pos機會用uuid
            "cost": 300, // 總金額
            "transaction_id": "txxxxxxx" // 金流平台上的交易編號
        }
        ...
    ]
}
```

## 交易紀錄查詢(單筆)
```
GET /orders/[訂單編號]
```

- Response
```jsonld
{
    "id": 2024080100001, // 訂單編號
    "fid": "20240801000030", // 訂單假編號(APP上顯示)
    "status": "已付款", // 狀態直接顯示就好
    "date": "20240801", // 日期
    "time": "00:00:30", // 時間
    "source": "APP", // 訂單來源
    "pay_type": "APP線上刷卡", // 付款方式
    "invoice": "DN02586742", // 發票號碼
    "by": "+886970363575", // 訂單擁有者+886開頭是會員 pos機會用uuid
    "cost": 300, // 總金額
    "items": [
        {
            "name": "3樓羽球場E", // 品名
            "count": 1, // 數量
            "price": 300, // 單價
            "cost": 300, // 總價
        }
    ]
}
```

## 交易紀錄作廢/折讓
```
DELETE /orders/[訂單編號]
```

- Response
```jsonld
ok or 下載折讓單
```

## 結帳(POS版)
```
PUT /creditcardsettlement
```

- Body
```jsonld=
{
    "token": "[Kiosk的token]", // 不放時下面的api from會是call api的機器ip, 有給的話就會是token
    "data": {                 // 隨便要放什麼都可以
        "test1": "value1",
        "test2": "value2"
    }
}
```

- Response
```
ok
```

## 取得結帳紀錄(POS版)
```
GET /creditcardsettlement
```

- Query Parameters
    - filter: today/week/month/date 可以用這三個參數不給的時候預設today，其他就代表從今天往後7天/30天
    - date: yyyy/MM/dd 只有當filter為date時才有用
    
- Response: 
```jsonld=
{
    "count": 1,
    "datas": [
        {
            "data": { // 結帳時用存入的資料
                "test1": "value1",
                "test2": "value2"
            },
            "date": "2024/09/10", // 結帳的日期
            "time": "14:35:10", // 結帳的時間
            "from": "127.0.0.1", // 哪一臺結的帳
            "by": "richard" // 哪個帳號結的帳
        }
    ]
}
```

## 發票相關特別操作
```
PUT /invoices/[invoice_number]/[method]
```

- methods
    - ==cancel_creditnote== 取消折讓
    - ==force_cancel== 直接作廢(超過一天以上也可以)
    - ==undo_cancel== 取消作廢
    
*==取消的操作不會回復場地預約跟課程報名==

- Response
```
ok
```

# 場地預約

## 取得場租類型列表
```
GET /reservation_types
```

- Response
```jsonld=
[
    {
        "id": "handicapped",
        "name": "身心障礙",
        "color": "#d20382" // 區塊背景顏色
    },
    {
        "id": "normal",
        "name": "臨租長租",
        "color": "#b37a1f"
    },
    {
        "id": "event",
        "name": "活動包場",
        "color": "#7d00f0"
    },
    {
        "id": "classroom",
        "name": "課程使用",
        "color": "#77c137"
    }
]
```

## 取得場地列表
```
GET /fields
```

### 欄位解說
* id: 場地的id
* disabled: 這個場地無法預約(也不會顯示在app)
* image: 場地的圖片網址
* profile: app上面顯示的訊息有可能為null(目前先用introduction)

- Response
```jsonld=
[
    {
        "disabled": false,
        "id": "3樓羽球場A",
        "profile": null,
        "image": [url]
    },
    {
        "disabled": false,
        "id": "3樓羽球場B",
        "profile": {
            "introduction": "ooxx"
        },
        "image": [url]
    },
    ...
]
```

## 上傳場地圖片
```
POST /field/[field id]/image
```

- Body [form-data]
```
image: [.png/.jpg]
```

- Response
```
ok
```

## 修改場地資料
```
PUT /field/[field_id]
```

- Body
```jsonld=
{
    "introduction": "介紹..."
}
```

- Response
```
ok
```

## 場地價格維護

### 注意事項
- 當時間有重疊時會選擇較高金額(不包含預設)

### 欄位解說
- date: 開始的日期格式必須要是yyyy/MM/dd
- repeat_type: `base在新增的時候不會用上`
```jsonld
[
    {
        "id": "base",
        "name": "預設"
    },
    {
        "id": "no_repeat",
        "name": "不重複"
    },
    {
        "id": "daily",
        "name": "每日"
    },
    {
        "id": "weekly",
        "name": "每週"
    },
    {
        "id": "monthly",
        "name": "每月"
    },
    {
        "id": "yearly",
        "name": "每年"
    }
]
```
- until: 結束日期，有給repeat_type才生效，不給就是不會結束，格式與date一樣==會計算到當天結束==
- price: 金額
- start_time: 開始時間格式請用hh:mm(24小時制)，可以不給代表整天
- end_time: 有給start_time才需要給，格式一樣，一定要大於start_time
- comment: 備註, 加強可讀性

### 取得場地價格設定列表
```
GET /field/[場地id]/price
```

- Response
```jsonld=
[
    {
        "id": "233a6aba-e8c9-48da-b59e-7533307f4394",
        "date": "1980/01/01",
        "comment": "預設價格",
        "inserted_at": "2024-02-06T07:34:33",
        "updated_at": "2024-02-06T08:37:27",
        "end_time": "23:59",
        "price": 1000,
        "repeat_type": "base",
        "start_time": "00:00"
    },
    {
        "id": "f6a5e476-2721-463a-bd36-568a3dc8d240",
        "date": "2024/01/06",
        "comment": "每個週六的06:00~20:00價格提高到3000直到2024/02/28",
        "inserted_at": "2024-02-06T08:44:49",
        "updated_at": "2024-02-06T08:44:49",
        "end_time": "20:00",
        "price": 3000,
        "repeat_type": "weekly",
        "until": "2024/02/28",
        "start_time": "06:00"
    }
]
```

### 設定場地預設價格
```
PUT /field/[場地id]/price/base
```

- Body
```jsonld=
{
    "price": 1000
}
```

- Response
```
ok
```

### 新增場地價格
```
POST /field/[場地id]/price
```

- Body
```jsonld=
{
    "date": "2024/01/06",
    "repeat_type": "weekly",
    "until": "2024/02/28",
    "price": 3000,
    "start_time": "06:00",
    "end_time": "20:00",
    "comment": "每個週六的06:00~20:00價格提高到3000"
}
```

- Response
```
ok
```

### 刪除場地價格
```
DELETE /field/[場地id]/price/[場地價格id]
```

- Response
```
ok
```

## 場地關閉維護

### 欄位解說
- date: 開始的日期格式必須要是yyyy/MM/dd
- repeat_type:
```jsonld
[
    {
        "id": "no_repeat",
        "name": "不重複"
    },
    {
        "id": "daily",
        "name": "每日"
    },
    {
        "id": "weekly",
        "name": "每週"
    },
    {
        "id": "monthly",
        "name": "每月"
    },
    {
        "id": "yearly",
        "name": "每年"
    }
]
```
- until: 結束日期，有給repeat_type才生效，不給就是不會結束，格式與date一樣==會計算到當天結束==
- start_time: 開始時間格式請用hh:mm(24小時制)，可以不給代表整天
- end_time: 有給start_time才需要給，格式一樣，一定要大於start_time
- comment: 備註, 加強可讀性

### 取得場地關閉設定列表
```
GET /field/[場地id]/close
```

- Response
```jsonld=
[
    {
        "id": "f6a5e476-2721-463a-bd36-568a3dc8d240",
        "date": "2024/01/06",
        "comment": "每個週六的06:00~20:00休息直到2024/02/28",
        "inserted_at": "2024-02-06T08:44:49",
        "updated_at": "2024-02-06T08:44:49",
        "end_time": "20:00",
        "repeat_type": "weekly",
        "until": "2024/02/28",
        "start_time": "06:00"
    }
]
```

### 新增場地關閉
```
POST /field/[場地id]/close
```

- Body
```jsonld=
{
    "date": "2024/01/06",
    "repeat_type": "weekly",
    "until": "2024/02/28",
    "start_time": "06:00",
    "end_time": "20:00",
    "comment": "每個週六的06:00~20:00休息直到2024/02/28"
}
```

- Response
```
ok
```

### 刪除場地關閉
```
DELETE /field/[場地id]/close/[場地關閉id]
```

- Response
```
ok
```

## 場地類別

### 取得場地類別列表
```
GET /field/categories
```

- Response
```jsonld=
[
    {
        "id": "羽球",
        "fields": null
    }
]
```

### 新增場地類別
```
POST /field/category
```

- Body
```jsonld=
{
    "id": "羽球"
}
```

- Response
```
ok
```

### 修改場地類別包含的場地
```
PUT /field/category/[類別id]
```

- Body
```jsonld=
{
    "fields": ["3樓羽球場A","3樓羽球場B"]
}
```

- Response
```
ok
```

### 刪除場地類別
```
DELETE /field/category/[類別id]
```

- Response
```
ok
```

### 透過場地類別取得場地資訊(for預約)
```
GET /field/category/[類別id]
```

- Query Parameters
    - mode: half 時段模式(不給的話是1小時一個時段，給half是半小時一個時段)
    - date: 指定日期

- Response
```jsonld=
[
    {
        "field": "[場地id]",
        "blocks": [
            ...// 結構跟 [取得場地資訊(for預約)] 一樣
        ]
    },
    ...
]
```

### 透過場地類別取得場地價格設定列表
```
GET /field/category/[類別id]/price
```

- Response
```jsonld=
[
    {
        "id": "233a6aba-e8c9-48da-b59e-7533307f4394",
        "date": "1980/01/01",
        "comment": "預設價格",
        "inserted_at": "2024-02-06T07:34:33",
        "updated_at": "2024-02-06T08:37:27",
        "end_time": "23:59",
        "price": 1000,
        "repeat_type": "base",
        "start_time": "00:00"
    },
    {
        "id": "f6a5e476-2721-463a-bd36-568a3dc8d240",
        "date": "2024/01/06",
        "comment": "每個週六的06:00~20:00價格提高到3000直到2024/02/28",
        "inserted_at": "2024-02-06T08:44:49",
        "updated_at": "2024-02-06T08:44:49",
        "end_time": "20:00",
        "price": 3000,
        "repeat_type": "weekly",
        "until": "2024/02/28",
        "start_time": "06:00"
    }
]
```

### 透過場地類別修改場地價格
```
PUT /field/category/[類別id]/price/:fp_id
```

==fp_id是base時是設定預設價格其他就用上面的API拿到的id==

- Body
```jsonld=
{
    "price": 1000
}
```

- Response
```
ok
```

### 透過場地類別新增場地價格
```
POST /field/category/[類別id]/price
```

- Body
```jsonld=
{
    "date": "2024/01/06",
    "repeat_type": "weekly",
    "until": "2024/02/28",
    "price": 3000,
    "start_time": "06:00",
    "end_time": "20:00",
    "comment": "每個週六的06:00~20:00價格提高到3000"
}
```

- Response
```
ok
```

### 透過場地類別刪除場地價格
```
DELETE /field/category/[類別id]/price/[場地價格id]
```

- Response
```
ok
```


### 透過場地類別取得場地關閉設定列表
```
GET /field/category/[類別id]/close
```

- Response
```jsonld=
[
    {
        "id": "f6a5e476-2721-463a-bd36-568a3dc8d240",
        "date": "2024/01/06",
        "comment": "每個週六的06:00~20:00休息直到2024/02/28",
        "inserted_at": "2024-02-06T08:44:49",
        "updated_at": "2024-02-06T08:44:49",
        "end_time": "20:00",
        "repeat_type": "weekly",
        "until": "2024/02/28",
        "start_time": "06:00"
    }
]
```

### 透過場地類別新增場地關閉
```
POST /field/category/[類別id]/close
```

- Body
```jsonld=
{
    "date": "2024/01/06",
    "repeat_type": "weekly",
    "until": "2024/02/28",
    "start_time": "06:00",
    "end_time": "20:00",
    "comment": "每個週六的06:00~20:00休息直到2024/02/28"
}
```

- Response
```
ok
```

### 透過場地類別刪除場地關閉
```
DELETE /field/category/[類別id]/close/[場地關閉id]
```

- Response
```
ok
```

## 場地預約相關

### 注意事項
- 系統這裡會控制可以預約的時間(`目前是3天`)
- 每個時段目前預設1個小時(有參數可以切換成半小時)

### 取得場地資訊(for預約)
```
GET /field/[場地id]
```

- Query Parameters
    - mode: half 時段模式(不給的話是1小時一個時段，給half是半小時一個時段)
    - date: 指定日期

- Response
```jsonld=
[
    {
        "date": "2024/02/07", // 日期
        "blocks": [ // 時段列表
            {
                "available": false, // 可預約與否
                "comment": "超時", // 無法預約的原因
                "price": 1500, // 價格
                "start_time": "00:00", // 時段開始時間
                "end_time": "00:59" // 時段結束時間
            },
            {
                "available": false,
                "comment": "整修",
                "price": 1500,
                "start_time": "01:00",
                "end_time": "01:59"
            },
            {
                "available": false,
                "comment": "超時",
                "price": 1500,
                "start_time": "02:00",
                "end_time": "02:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "03:00",
                "end_time": "03:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "04:00",
                "end_time": "04:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "05:00",
                "end_time": "05:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "06:00",
                "end_time": "06:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "07:00",
                "end_time": "07:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "08:00",
                "end_time": "08:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "09:00",
                "end_time": "09:59"
            },
            {
                "field": "3樓羽球場A", // 已預約的場地id
                "available": false,
                "price": 1500,
                "start_time": "10:00",
                "end_time": "10:59",
                "occupied_by": "pos", // 從哪預約 pos/phone/app
                "occupied_id": "+886972957297", // 預約會員電話(務必要用+886開頭)
                "occupied_name": "Richard", // 預約會員姓名
                "bgcolor": "#b37a1f", // 區塊背景顏色
                "border_color": "#000000", // 區塊邊框顏色
                "paid": true, // 已付款？ true/false
                "reservation_type": "normal", // 對應到上面的reservation_types api
                "reservation_id": "1ed3fe67-18d5-4be3-b1e8-4774dea55047", // 預約的id可用來之後的操作,
                "reservation": {
                    ... // 格式請參考[搜尋預約記錄]
                },
                "order_info": { // 訂單相關內容
                    "order": 2024080100001, // 訂單編號
                    "invoice": "AA00000001", // 發票號碼，有開才會有
                    "created_at": "2024/08/01 07:00:00", // 預約時間，預約產生的時間
                    "paid_at": "2024/08/01 07:02:00", // 繳費時間，成功繳費的時間
                    "vat": "ooxx" // 統編,null的時候代表沒有或者已作廢折讓
                }
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "11:00",
                "end_time": "11:59"
            },
            {
                "available": true,
                "price": 1500,
                "start_time": "12:00",
                "end_time": "12:59"
            },
            ...
        ]
    },
    ...
]
```

### 預約
```
POST /reservation
```

- 注意事項
    - ==批次預約是提供一個方便的介面，實際上是根據參數展開成數個時段！==

- Body
```jsonld=
{
    "type": "normal", // 對應上面的reservation_types
    "occupied_by": "pos", // 從哪預約 pos/phone/app (現場/電話/APP)
    "occupied_id": "+886972957297", // 預約會員電話(務必要用+886開頭)
    "occupied_name": "tester", // 要是沒有撈到會員資料時給這個欄位會自動建立使用者
    "cost": 1000, //.實收金額 這個金額會在結帳時帶入
    "comment": "備註", // 備註
    "items": [
        {
            "field": "3樓羽球場A", // 場地id
            "date": "2024/02/07", // 預約日期
            "time": "10:00", // 預約時段
            "duration": 60 // 預約的長度(單位是分鐘 預設是60 半小時模式是30)
        },
        {
            "type": "expand", // 目前需要批次預約才會要用這個 其他情況下照上面的
            "field": "3樓羽球場A",
            "repeat_type": "weekly", // 與設定價格一樣的用法 不支援no_repeat！
            "repeat_range": 1, // 指定重複的間距 ex: repeat_type=weekly repeat_range=1就是每個禮拜,2就是每兩個禮拜
            "date": "2024/01/06", // 開始時間
            "until": "2024/02/28", // 結束日期
            "time": "10:00", // 預約時段
            "duration": 60 // 預約的長度(單位是分鐘 預設是60 半小時模式是30)
        }
    ]
}
```

- Response
    - 200: ok
    - 400: 與已預約場地衝突

### 搜尋預約記錄
```
GET /reservation/search
```

- Query Parameters
    - phone: 會員電話(一定要是+886開頭的完整電話，可以透過上面的會員搜尋功能，拿到完整的電話號碼)
    - paid: true/false 要包含已付款/未付款 (預設是false)
    
- Response
```jsonld=
[
    {
        "id": "1ed3fe67-18d5-4be3-b1e8-4774dea55047",
        "status": "pending", // 狀態 pending/paid/cancel 待繳費/已繳費/已取消
        "type": "normal", // 對應上面的reservation_types
        "items": [
            {
                "id": "ecff976d-2ae5-4ddc-8f53-541331900ed7",
                "date": "2024/02/07", // 預約日期
                "time": "10:00", // 時段
                "field_id": "3樓羽球場A", // 場地
                "duration": 60, // 時長
                "end_time": 1707274800, // 結束時間
                "reservation_id": "1ed3fe67-18d5-4be3-b1e8-4774dea55047"
            }
        ],
        "deadline": 1707270600, // 最後繳費時間
        "comment": null,
        "occupied_by": "pos", // 預約方式 pos/phone/app
        "occupied_id": "+886972957297", // 會員電話
        "cost": 1000, // 應繳金額
        "ext": null,
        "inserted_at": "2024-02-06T17:48:51",
        "updated_at": "2024-02-06T17:48:51"
    },
    {
        "id": "78860f55-401f-4370-8215-25922ded2d2c",
        "status": "pending",
        "type": "normal",
        "items": [
            {
                "id": "98131b49-196a-49a7-be6e-67d5bbee8a29",
                "date": "2024/02/07",
                "time": "12:00",
                "field_id": "3樓羽球場A",
                "duration": 60,
                "end_time": 1707282000,
                "reservation_id": "78860f55-401f-4370-8215-25922ded2d2c"
            }
        ],
        "deadline": 1707277779,
        "comment": null,
        "occupied_by": "pos",
        "occupied_id": "+886972957297",
        "cost": 1000,
        "ext": null,
        "inserted_at": "2024-02-07T03:39:39",
        "updated_at": "2024-02-07T03:39:39"
    }
]
```

### 搜尋預約記錄(透過場地類別)
```
GET /field/category/[類別id]/reservation/search
```

- Response
```jsonld=
[
    {
        "id": "1ed3fe67-18d5-4be3-b1e8-4774dea55047",
        "status": "pending", // 狀態 pending/paid/cancel 待繳費/已繳費/已取消
        "type": "normal", // 對應上面的reservation_types
        "items": [
            {
                "id": "ecff976d-2ae5-4ddc-8f53-541331900ed7",
                "date": "2024/02/07", // 預約日期
                "time": "10:00", // 時段
                "field_id": "3樓羽球場A", // 場地
                "duration": 60, // 時長
                "end_time": 1707274800, // 結束時間
                "reservation_id": "1ed3fe67-18d5-4be3-b1e8-4774dea55047"
            }
        ],
        "deadline": 1707270600, // 最後繳費時間
        "comment": null,
        "occupied_by": "pos", // 預約方式 pos/phone/app
        "occupied_id": "+886972957297", // 會員電話
        "cost": 1000, // 應繳金額
        "ext": null,
        "inserted_at": "2024-02-06T17:48:51",
        "updated_at": "2024-02-06T17:48:51"
    },
    {
        "id": "78860f55-401f-4370-8215-25922ded2d2c",
        "status": "pending",
        "type": "normal",
        "items": [
            {
                "id": "98131b49-196a-49a7-be6e-67d5bbee8a29",
                "date": "2024/02/07",
                "time": "12:00",
                "field_id": "3樓羽球場A",
                "duration": 60,
                "end_time": 1707282000,
                "reservation_id": "78860f55-401f-4370-8215-25922ded2d2c"
            }
        ],
        "deadline": 1707277779,
        "comment": null,
        "occupied_by": "pos",
        "occupied_id": "+886972957297",
        "cost": 1000,
        "ext": null,
        "inserted_at": "2024-02-07T03:39:39",
        "updated_at": "2024-02-07T03:39:39"
    }
]
```

### 重新預約(限定已取消且時段都還沒過期的)
```
PUT /reservation/[預約id]/redo
```

- Response
```
ok
```

### 取消預約(標記取消，不會刪除紀錄)
```
PUT /reservation/[預約id]/cancel
```

- Body
```jsonld=
{
    "reason": "沒有理由"
}
```

- Response
```
ok
```

### 刪除預約紀錄(慎用)
```
DELETE /reservation/[預約id]
```

- Response
```
ok
```

### 查詢場地預約紀錄
```
GET /field/[場地id]/reservations
```

- Query Parameters ==用了start_date/end_date就不用date格式都是yyyy/MM/dd==
    - date: 指定日期
    - start_date: 開始日期 ==需與end_date一起否則忽略直接使用當天==
    - end_date: 結束日期

- Response
```jsonld=
{
    "count": 11, // 共有幾筆
    "rows": [
        {
            "created_at": "2024/11/09 05:50:51", // 使用者預約日期及時間
            "order_id": "", // 訂單編號
            "invoice_number": "", // 發票號碼
            "status": "未付款", // 交易狀態
            "cancel_reason": "", // 取消原因
            "cost": 300, // 金額
            "range": "2024/11/11 06:00~06:59", // 場地被預約日期及時間
            "created_from": "pos", // 結帳櫃檯
            "created_by": "+886910814601", // 結帳人員
            "cancel_from": "", // 取消櫃檯
            "cancel_by": "", // 取消人員
            "canceled_at": "" // 取消時間
        },
        ...
    ]
}
```

---

## 卡片相關功能

### 建立敬老卡(沿用舊系統的建立員工卡加上參數)
```
POST /card2/[掃RFID拿到的卡號]
```

- Body
```jsonld=
{
    "type": "elder",
    "name": "ooxx", // 姓名
    "profile": {   // 裡面可以放資料如：警急聯絡人,電話等...key可以自己取
        ...
    }
}
```

- Response
```
ok
```

### 拿敬老卡列表(沿用舊系統的員工卡列表加上參數)
```
GET /card2
```

- Query Parameters
-- type: elder/employee/monthly/app_monthly (elder: 敬老卡, employee: 員工卡, monthly: 月卡, app_monthly: APP月卡)

- Response
```jsonld=
{
    "count": 1,
    "rows": [
        {
            "id": "[卡號]",
            "sn": "[卡號]",
            "type": "elder",
            "name": "ooxx",
            "profile": {
                ...
            }
        },
        ...
    ],
	"monthly_valids": { // 當type == "monthly" 的時候會出現，代表可以用的月卡數量
        "fitness": 33,
        "pool": 94
    }
}
```

### 修改敬老卡(沿用舊系統的建立員工卡加上參數)
```
PUT /card2/[掃RFID拿到的卡號]
```

- Body
```jsonld=
{
    "type": "elder",
    "name": "ooxx", // 姓名
    "profile": {   // 裡面可以放資料如：警急聯絡人,電話等...key可以自己取
        ...
    }
}
```

- Response
```
ok
```

### 建立月卡的操作
```
POST /monthly_operations
```

- Body
```jsonld=
{
    "specific": "all", // default: "all" 指定的場地：all 全部 pool 游泳池 fitness 健身房
    "date": "2025-03-20", // 指定什麼時候到期後的月卡
    "extend_days": 1, // 延長幾天
    "comment": "因為xxx所以xxx" // 備註
}
```

- Response
```jsonld=
{
    "id": "bd28d94b-3bcd-4d12-a7fc-cb9e6a93a5a6",
    "specific": "all", // 指定的場地
    "status": "初始化", // 狀態
    "date": "2025/03/20",
    "comment": "因為xxx所以xxx",
    "extend_days": 1,
    "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
    "executed_at": "",
    "inserted_at": "2025/03/14 15:32:32",
    "updated_at": "2025/03/14 15:32:32"
}
```

### 執行月卡的操作(已刪除)
```
PUT /monthly_operations/[monthly operation id]
```

- Response
```jsonld=
{
    "id": "bd28d94b-3bcd-4d12-a7fc-cb9e6a93a5a6",
    "specific": "all",
    "status": "已完成",
    "date": "2025/03/20",
    "comment": "因為xxx所以xxx",
    "extend_days": 1,
    "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
    "executed_at": "2025/03/14 15:35:07", // 執行時間
    "inserted_at": "2025/03/14 15:32:32",
    "updated_at": "2025/03/14 15:35:07"
}
```

### 刪除月卡的操作
```
DELETE /monthly_operations/[monthly operation id]
```

- Response
```
ok
```

### 取得月卡的操作列表
```
GET /monthly_operations
```

- Response
```jsonld=
{
    "count": 1,
    "rows": [
        {
            "id": "bd28d94b-3bcd-4d12-a7fc-cb9e6a93a5a6",
            "specific": "all",
            "status": "已完成",
            "date": "2025/03/20",
            "comment": "因為xxx所以xxx",
            "inserted_at": "2025/03/14 15:32:32",
            "updated_at": "2025/03/14 15:35:07",
            "created_by": "722543ea-bdcc-48c4-aa9e-baf4e0f2fac4",
            "executed_at": "2025/03/14 15:35:07",
            "extend_days": 1
        }
    ]
}
```

### 建立報表類別
```
POST /report_categories
```

- Body
```jsonld=
{
    "code": "TestA", // code
    "name": "NameA", // 名稱
    "comment": "NameA", // 備註
    "weight": 3, // 權重與排序有關(越大越前面)
    "products": [ 1 ], // product id 的 array
    "children": [],
    "parent_id": "1" // 上一層類別的id(不給就是第一層)
}
```

- Response
```jsonld=
{
    "id": "ooxxxxxx",
    "code": "TestA",
    "name": "NameA",
    "comment": "NameA",
    "weight": 3,
    "products": [
        1
    ],
    "children": [],
    "parent_id": "ooxxxxx"
}
```

### 修改報表類別
```
PUT /report_categories/[report category id]
```

- Body
```jsonld=
{
    "code": "testB",
    "name": "NameA_Update",
    "comment": "NameA_Update",
    "weight": 0,
    "products": [ 2 ],
    "children": [],
    "parent_id": "2"
}
```

- Response
```
ok
```

### 刪除報表類別
```
DELETE /report_categories/[report category id]
```

- Response
```
ok
```

### 取得報表類別列表
```
GET /report_categories
```

- Response
```jsonld=
{
    "count": 2,
    "rows": [
        {
            "id": "ooxx",
            "code": "TestA",
            "name": "NameA",
            "comment": "NameA",
            "products": [
                {
                    "id": 1,
                    "name": "體適能-全票",
                    "price": 50
                }
            ],
            "children": [],
            "parent_id": null,
            "weight": 3
        },
        {
            "id": "xxoo",
            "code": "TestB",
            "name": "NameB",
            "comment": "NameB",
            "products": [
                {
                    "id": 1,
                    "name": "體適能-全票",
                    "price": 50
                }
            ],
            "children": [],
            "parent_id": "ooxx",
            "weight": 0
        }
    ]
}
```

---
---

## 固定電話驗證碼

### 建立固定驗證碼
```
POST /phonecodes
```

- Body
```jsonld=
{
    "phone": "+886912345678",
    "code": "123456" // 固定六位數字
}
```

- Response
```jsonld=
{
    "phone": "+886912345678",
    "code": "123456"
}
```

### 拿驗證碼列表
```
GET /phonecodes
```

- Response
```jsonld=
{
    "count": 1,
    "rows": [
        {
            "phone": "+886912345678",
            "code": "123456"
        },
        ...
    ]
}
```

### 刪除驗證碼
```
DELETE /phonecodes/[電話號碼(+886開頭)]
```

- Response
```
ok
```

### 修改固定驗證碼
```
PUT /phonecodes/[電話號碼(+886開頭)]
```

- Body
```jsonld=
{
    "code": "123456" // 固定六位數字
}
```

- Response
```
ok
```
---

---

## 課程

### 課程欄位解說
* id: string 課程的id(客戶自己編)
* name: string 課程名稱
* type: string 課程類別的id
* teacher: string 老師的id
* classroom_type: string 教室類型("classroom", "field")
* classroom: string 當classroom_type=="classroom"時用教室id，當classroom_type=="field"時用場地id
* description: string 課程說明
* picture1: string url 照片1
* picture2: string url 照片2
* picture3: string url 照片3
* hide: true/false 封存？
* enable: true/false 啟動？
* app: true/false 開放app報名？
* begin: string yyyy/MM/dd 開始報名日期
* app_begin: string yyyy/MM/dd APP開始報名日期
* deadline: string yyyy/MM/dd 截止報名日期
* app_deadline: string yyyy/MM/dd APP截止報名日期
* open_threshold: integer 開課人數
* full: integer 滿班人數
* app_limit: integer 開放APP報名人數
* checkin_before: integer 開課前幾分鐘可報到
* checkin_after: integer 開課後幾分鐘可報到
* unit_price: integer 單堂價格
* total_price: integer 課程總價
* taste_price: integer 單次體驗價
* status_name: string 用在ui上的課程狀態
* enable_returning_student_discount: true/false 啟用舊生優惠？
* previous_course: string 關聯課程的id
* returning_student_threshold: integer 關聯課程需報名付款的堂數
* returning_student_after_discount_unit_price: integer 折扣後單堂價格
* returning_student_after_discount_total_price: integer 折扣後總價
* schedules: array 單堂資料(欄位下列)
* open_months: 開課年月

### 課程單堂欄位解說
* id: string 單堂的id(系統自動產生)
* date: string 日期yyyy/MM/dd
* start: string 時間HH:mm
* duration: integer 時長(分鐘)
* comment: string 該堂說明

### 新增課程
```
POST /courses
```

- Body

```jsonld=
{
    ..., //使用課程欄位解說所有欄位
    "schedules": [
        ..., // 使用課程單堂欄位解說所有欄位
    ]
}
```

- Response
```jsonld=
{
    ... //使用上面所有欄位
}
```

### 取得課程列表
```
GET /courses
```

- Response
```jsonld=
{
    "count": 1,
    "rows": [
        ... //使用上面課程所有欄位
    ]
}
```

### 選課
```
POST /courses/[課程id]/takes
```

- Body

```jsonld=
{
    "user": "[user的電話號碼+886開頭]",
    "student": "[student id]", // student/user必須要選一個填
    "schedules": [...] // 放schedule的id
}
```

- Response
```json
ok
```

### 取得選課資料(for繳費or點名)
```
GET /courses/[課程id]/takes
```

- Response

```jsonld=
{
    "count": 1,
    "rows": [
        {
          "id":"49055e94-6291-4e85-b39a-96158bddfb00", // 報名id
          "course":"C001",  // 課程id
          "paid": false,   // 是否繳費
          "invoice": "AA00000001",   // 發票號碼, 有繳費才會有
          "attend":false,  // 已報到
          "isAbsent":false,
          "isLeave":false,
          "isShift":false,
          "isTakeOff":false,
          "schedule":"fffd2f91-b492-4d74-8499-02c6ec7f2bfa", // 課程單堂id
          "student":"b9907b0a-7b54-48b6-933a-bc63cbe84915", // 報名學生id
          "user":null, // 報名使用者電話
          "name": "ooxx", //報名者名稱
          "comment":null // 備註
        },
        ...
    ]
}
```

### 取消選課(尚未繳費時用)
```
DELETE /courses/takes/[選課id]
```

- Response
```jsonld
ok
```

### 課程報名結帳(其他參數與一般結帳及場地預約結帳一樣只列出不同之處)
```
POST /sales
```

- Body
```jsonld=
{
    "items": [
        {
            "type": "course",
            "price": 金額(會照這個金額開發票),
            "items": [...] // 報名id array
        }
    ]
}
```

### 刪除課程(還未有人報名時)
```
DELETE /courses/[課程id]
```

- Response
```jsonld
ok
```

### 修改課程(還未有人報名時)
```
PUT /courses/[課程id]
```

- Body

```jsonld=
{
    ..., //使用課程欄位解說所有欄位，有帶的就會修改
    "schedules": [ // 如有修改單堂資料請將所有課堂的資料都帶進來,如沒有修改請勿使用這個key(也不要用空array)
        {
            ..., // 使用課程單堂欄位解說所有欄位(id除外)
        }
    ]
}
```

- Response
```jsonld
ok
```

### 刪除單堂(還未有人報名時)
```
DELETE /courses/schedules/[單堂id]
```

- Response
```jsonld
ok
```

### 取得選課資料(for繳費)
```
GET /courses/takes
```

- Query Parameters ==下面兩個參數選一個就好==
    - phone: 會員電話(+886開頭)
    - student: student id

- Response

```jsonld=
{
    "count": 3,
    "rows": [
        {
            "id": "49055e94-6291-4e85-b39a-96158bddfb00", // 報名id用來帶到繳費api
            "name": "student",
            "comment": null,
            "schedule": { // 報名的課堂資料
                "id": "fffd2f91-b492-4d74-8499-02c6ec7f2bfa",
                "start": "10:00",
                "date": "2024/06/04",
                "until": "11:00",
                "comment": "1234",
                "course": "C001",
                "isTakeOff": false,
                "isShift": false,
                "duration": 60,
                "weekday": "二"
            },
            "course": { // 報名的課程資料
                "picture3": "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=19026",
                "app_begin": "2024/06/04",
                "checkin_after": 15,
                "open_threshold": 10,
                "picture2": "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=13680",
                "type": "37776f6f-4729-41fb-b606-a6fdebd2b091",
                "classroom_type": "classroom",
                "app_intro": {
                    "schedule_count": 3,
                    "schedule_range": "2024/06/04 ~ 2024/06/08",
                    "take_range": "2024/06/04 ~ 2024/07/04",
                    "takes_count": 2,
                    "teacher": "teacher"
                },
                "unit_price": 100,
                "deadline": "2024/07/04",
                "begin": "2024/06/04",
                "pictures": [
                    "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=78238",
                    "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=13680",
                    "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=19026"
                ],
                "name": "測試課程",
                "app_limit": 8,
                "app_enable": true,
                "classroom": "a9f2f48a-f825-4787-a78a-6c70e0112952",
                "taste_price": 200,
                "full": 10,
                "type_name": "class_c",
                "courseType": "37776f6f-4729-41fb-b606-a6fdebd2b091",
                "enable": true,
                "classroom_name": "舞蹈教室",
                "status_name": "未知",
                "total_price": 5000,
                "description": "測試課程AAABBBCCCDDD",
                "app_status": "報名進行中",
                "id": "C001",
                "app_deadline": "2024/07/04",
                "schedules": [
                    {
                        "id": "fffd2f91-b492-4d74-8499-02c6ec7f2bfa",
                        "start": "10:00",
                        "date": "2024/06/04",
                        "until": "11:00",
                        "comment": "1234",
                        "course": "C001",
                        "isTakeOff": false,
                        "isShift": false,
                        "duration": 60,
                        "weekday": "二"
                    },
                    {
                        "id": "7283fa51-7910-47c9-85e7-3c73547c5218",
                        "start": "10:00",
                        "date": "2024/06/06",
                        "until": "11:00",
                        "comment": null,
                        "course": "C001",
                        "isTakeOff": false,
                        "isShift": false,
                        "duration": 60,
                        "weekday": "四"
                    },
                    {
                        "id": "5dc3b8c8-cd13-4bf3-892d-e7091033bf53",
                        "start": "10:00",
                        "date": "2024/06/08",
                        "until": "11:00",
                        "comment": null,
                        "course": "C001",
                        "isTakeOff": false,
                        "isShift": false,
                        "duration": 60,
                        "weekday": "六"
                    }
                ],
                "hide": false,
                "app": true,
                "checkin_before": 15,
                "picture1": "https://cosmic-jaybird-regularly.ngrok-free.app/files/image/712d17dc6d95935019f226f54e676ce177551b190ce9ff75d5a06e28dbe970cb?t=78238",
                "teacher": "a82fbfed-7f8f-44c9-84d4-9e9965d6058a"
            },
            "attend": false,
            "isTakeOff": false,
            "isShift": false,
            "isLeave": false,
            "isAbsent": false,
            "paid": false // 是否已繳費
        },
        ...
    ]
}
```

### 透過QRCode課程報到
```
PUT /courses/take
```

- Body
```jsonld=
{
    "code": "xxoo" // QRCode的內容
}
```

- Response

```jsonld=
{
    "status": "報到成功", // 報到結果
    "time": "2024/07/18 17:03:28",  // 報到的時間
    "schedule": "2024/07/18 17:00", // 報到的課堂時間
    "course": "測試課程" // 課程名稱
}
```

### 確認有無符合舊生優惠
```
PUT /courses/[course_id]/discount
```

- Body
```jsonld=
{
    "ref_id": "xxoo" // 學生id or 會員id
}
```

- Response

```jsonld=
{
    "match_returning_student_discount": true/false // 有/無符合
}
```
---