Compare commits
9 Commits
c6bd763485
...
yiming1013
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a43883d08 | |||
| 87a2c35300 | |||
| 7d57e292fe | |||
| 63cab37c87 | |||
| cd1e5c2cd0 | |||
| d7b0f29296 | |||
| 79854a2618 | |||
| 93aaffd3d8 | |||
| 71490b1fac |
26
data/memo/report-view.md
Normal file
26
data/memo/report-view.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 報表系統規劃
|
||||||
|
為每場活動(法會)建立報名到舉辦過程的報表,
|
||||||
|
要涵蓋以下面向的統計分析資訊
|
||||||
|
|
||||||
|
- 以單一場法會為核心
|
||||||
|
- 時間面向: 當前狀況, 指定期間狀況
|
||||||
|
- 維度:
|
||||||
|
- 信眾: 報名數量, 金額, 收款狀態
|
||||||
|
- 牌位型態(活動品項表): 報名數量, 金額, 收款狀態
|
||||||
|
- 收款狀態: 己收/未收 統計明細
|
||||||
|
|
||||||
|
## 法會報表系統查詢規劃**
|
||||||
|
|
||||||
|
### 1. 核心基礎 VIEW
|
||||||
|
1. `vw_activity_registration_base` - 單一活動的完整報名基礎資料
|
||||||
|
2. `vw_activity_payment_detail` - 單一活動的完整收款明細資料
|
||||||
|
### 2. 統計分析 VIEW
|
||||||
|
3. `vw_activity_follower_statistics` - 按信眾統計報名情況
|
||||||
|
4. `vw_activity_item_statistics` - 按品項統計報名情況
|
||||||
|
5. `vw_activity_payment_status` - 收款狀態統計分析
|
||||||
|
### 3. 時間維度分析 VIEW
|
||||||
|
6. `vw_activity_registration_trend` - 按日期統計報名趨勢
|
||||||
|
7. `vw_activity_payment_trend` - 按收款日期統計收款趨勢
|
||||||
|
### 4. 詳細查詢 VIEW
|
||||||
|
8. `vw_activity_unpaid_detail` - 未收款明細清單
|
||||||
|
9. `vw_activity_transfer_reconciliation` - 匯款對帳明細
|
||||||
252
data/memo/report-view.sql
Normal file
252
data/memo/report-view.sql
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
drop view if exists vw_activity_registration_base;
|
||||||
|
drop view if exists vw_activity_payment_detail;
|
||||||
|
drop view if exists vw_activity_follower_statistics;
|
||||||
|
drop view if exists vw_activity_item_statistics;
|
||||||
|
drop view if exists vw_activity_payment_status;
|
||||||
|
drop view if exists vw_activity_registration_trend;
|
||||||
|
drop view if exists vw_activity_payment_trend;
|
||||||
|
drop view if exists vw_activity_unpaid_detail;
|
||||||
|
drop view if exists vw_activity_transfer_reconciliation;
|
||||||
|
GO
|
||||||
|
-- 1. 法會報名基礎資料 VIEW
|
||||||
|
CREATE VIEW vw_activity_registration_base AS
|
||||||
|
SELECT
|
||||||
|
a.num AS 活動編號,
|
||||||
|
a.subject AS 活動名稱,
|
||||||
|
a.startDate_solar AS 活動開始日期,
|
||||||
|
a.endDate_solar AS 活動結束日期,
|
||||||
|
po.order_no AS 報名單號,
|
||||||
|
po.up_time AS 報名日期,
|
||||||
|
po.keyin1 AS 報名狀態,
|
||||||
|
f.num AS 信眾編號,
|
||||||
|
f.f_number AS 信眾代號,
|
||||||
|
f.u_name AS 信眾姓名,
|
||||||
|
f.phone AS 聯絡電話,
|
||||||
|
f.identity_type AS 身分別,
|
||||||
|
f.country AS 國籍,
|
||||||
|
pod.num AS 報名明細編號,
|
||||||
|
ai.num AS 品項編號,
|
||||||
|
ai.subject AS 品項名稱,
|
||||||
|
ai.category AS 品項分類,
|
||||||
|
pod.price AS 單價,
|
||||||
|
pod.qty AS 數量,
|
||||||
|
pod.price * pod.qty AS 應繳金額,
|
||||||
|
pod.pay AS 已收金額,
|
||||||
|
(pod.price * pod.qty - ISNULL(pod.pay, 0)) AS 未收金額,
|
||||||
|
pod.pay_date AS 付款期限,
|
||||||
|
pod.start_date AS 開始日期,
|
||||||
|
pod.due_date AS 到期日期,
|
||||||
|
pod.keyin1 AS 明細狀態,
|
||||||
|
pod.demo AS 備註
|
||||||
|
FROM activity a
|
||||||
|
INNER JOIN pro_order po ON a.num = po.activity_num
|
||||||
|
INNER JOIN followers f ON po.f_num = f.num
|
||||||
|
INNER JOIN pro_order_detail pod ON po.order_no = pod.order_no
|
||||||
|
INNER JOIN actItem ai ON pod.actItem_num = ai.num;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 2. 收款明細基礎資料 VIEW
|
||||||
|
CREATE VIEW vw_activity_payment_detail AS
|
||||||
|
SELECT
|
||||||
|
a.num AS 活動編號,
|
||||||
|
a.subject AS 活動名稱,
|
||||||
|
po.order_no AS 報名單號,
|
||||||
|
f.u_name AS 信眾姓名,
|
||||||
|
pod.num AS 報名明細編號,
|
||||||
|
ai.subject AS 品項名稱,
|
||||||
|
por.num AS 收款記錄編號,
|
||||||
|
por.price AS 收款金額,
|
||||||
|
por.payment AS 付款方式,
|
||||||
|
por.pay_date AS 收款日期,
|
||||||
|
por.organization AS 收款機構,
|
||||||
|
por.bank_code AS 銀行代碼,
|
||||||
|
por.transfer_id AS 匯款記錄ID,
|
||||||
|
por.reconcile_memo AS 對帳備註,
|
||||||
|
tr.name AS 匯款人姓名,
|
||||||
|
tr.phone AS 匯款人電話,
|
||||||
|
tr.amount AS 匯款金額,
|
||||||
|
tr.check_date AS 入帳日期,
|
||||||
|
tr.status AS 匯款狀態
|
||||||
|
FROM activity a
|
||||||
|
INNER JOIN pro_order po ON a.num = po.activity_num
|
||||||
|
INNER JOIN followers f ON po.f_num = f.num
|
||||||
|
INNER JOIN pro_order_detail pod ON po.order_no = pod.order_no
|
||||||
|
INNER JOIN actItem ai ON pod.actItem_num = ai.num
|
||||||
|
LEFT JOIN pro_order_record por ON pod.num = por.detail_num
|
||||||
|
LEFT JOIN transfer_register tr ON por.transfer_id = tr.id;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 3. 信眾報名統計 VIEW
|
||||||
|
CREATE VIEW vw_activity_follower_statistics AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
信眾編號,
|
||||||
|
信眾代號,
|
||||||
|
信眾姓名,
|
||||||
|
聯絡電話,
|
||||||
|
身分別,
|
||||||
|
國籍,
|
||||||
|
COUNT(DISTINCT 報名單號) AS 報名單數,
|
||||||
|
COUNT(報名明細編號) AS 報名品項數,
|
||||||
|
SUM(應繳金額) AS 應繳總金額,
|
||||||
|
SUM(已收金額) AS 已收總金額,
|
||||||
|
SUM(未收金額) AS 未收總金額,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(未收金額) = 0 THEN '已繳清'
|
||||||
|
WHEN SUM(未收金額) = SUM(應繳金額) THEN '未繳'
|
||||||
|
ELSE '部分繳款'
|
||||||
|
END AS 繳款狀態
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
GROUP BY 活動編號, 活動名稱, 信眾編號, 信眾代號, 信眾姓名, 聯絡電話, 身分別, 國籍;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 4. 品項報名統計 VIEW
|
||||||
|
CREATE VIEW vw_activity_item_statistics AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
品項編號,
|
||||||
|
品項名稱,
|
||||||
|
品項分類,
|
||||||
|
單價,
|
||||||
|
COUNT(報名明細編號) AS 報名數量,
|
||||||
|
SUM(數量) AS 總數量,
|
||||||
|
SUM(應繳金額) AS 應繳總金額,
|
||||||
|
SUM(已收金額) AS 已收總金額,
|
||||||
|
SUM(未收金額) AS 未收總金額,
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(報名明細編號) = 0 THEN 0
|
||||||
|
ELSE AVG(已收金額)
|
||||||
|
END AS 平均已收金額,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(未收金額) = 0 THEN '已收齊'
|
||||||
|
WHEN SUM(未收金額) = SUM(應繳金額) THEN '未收款'
|
||||||
|
ELSE '部分收款'
|
||||||
|
END AS 收款狀態
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
GROUP BY 活動編號, 活動名稱, 品項編號, 品項名稱, 品項分類, 單價;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 5. 收款狀態統計 VIEW
|
||||||
|
CREATE VIEW vw_activity_payment_status AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
'總計' AS 統計項目,
|
||||||
|
COUNT(DISTINCT 報名單號) AS 報名單數,
|
||||||
|
COUNT(報名明細編號) AS 報名品項數,
|
||||||
|
SUM(應繳金額) AS 應繳總金額,
|
||||||
|
SUM(已收金額) AS 已收總金額,
|
||||||
|
SUM(未收金額) AS 未收總金額,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(應繳金額) = 0 THEN 0
|
||||||
|
ELSE ROUND(SUM(已收金額) * 100.0 / SUM(應繳金額), 2)
|
||||||
|
END AS 收款率
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
GROUP BY 活動編號, 活動名稱
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
CASE
|
||||||
|
WHEN 未收金額 = 0 THEN '已繳清'
|
||||||
|
WHEN 未收金額 = 應繳金額 THEN '未繳'
|
||||||
|
ELSE '部分繳款'
|
||||||
|
END AS 統計項目,
|
||||||
|
COUNT(DISTINCT 報名單號) AS 報名單數,
|
||||||
|
COUNT(報名明細編號) AS 報名品項數,
|
||||||
|
SUM(應繳金額) AS 應繳總金額,
|
||||||
|
SUM(已收金額) AS 已收總金額,
|
||||||
|
SUM(未收金額) AS 未收總金額,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(應繳金額) = 0 THEN 0
|
||||||
|
ELSE ROUND(SUM(已收金額) * 100.0 / SUM(應繳金額), 2)
|
||||||
|
END AS 收款率
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
GROUP BY 活動編號, 活動名稱,
|
||||||
|
CASE
|
||||||
|
WHEN 未收金額 = 0 THEN '已繳清'
|
||||||
|
WHEN 未收金額 = 應繳金額 THEN '未繳'
|
||||||
|
ELSE '部分繳款'
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 6. 報名趨勢分析 VIEW
|
||||||
|
CREATE VIEW vw_activity_registration_trend AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
CAST(報名日期 AS DATE) AS 報名日期,
|
||||||
|
COUNT(DISTINCT 報名單號) AS 當日報名單數,
|
||||||
|
COUNT(報名明細編號) AS 當日報名品項數,
|
||||||
|
SUM(應繳金額) AS 當日應繳金額,
|
||||||
|
SUM(已收金額) AS 當日已收金額,
|
||||||
|
SUM(未收金額) AS 當日未收金額
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
GROUP BY 活動編號, 活動名稱, CAST(報名日期 AS DATE);
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 7. 收款趨勢分析 VIEW
|
||||||
|
CREATE VIEW vw_activity_payment_trend AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
CAST(收款日期 AS DATE) AS 收款日期,
|
||||||
|
COUNT(收款記錄編號) AS 當日收款筆數,
|
||||||
|
SUM(收款金額) AS 當日收款金額,
|
||||||
|
COUNT(DISTINCT 報名單號) AS 當日收款單數,
|
||||||
|
CASE
|
||||||
|
WHEN COUNT(收款記錄編號) = 0 THEN 0
|
||||||
|
ELSE AVG(收款金額)
|
||||||
|
END AS 平均收款金額
|
||||||
|
FROM vw_activity_payment_detail
|
||||||
|
WHERE 收款日期 IS NOT NULL
|
||||||
|
GROUP BY 活動編號, 活動名稱, CAST(收款日期 AS DATE);
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 8. 未收款明細 VIEW
|
||||||
|
CREATE VIEW vw_activity_unpaid_detail AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
報名單號,
|
||||||
|
信眾姓名,
|
||||||
|
聯絡電話,
|
||||||
|
品項名稱,
|
||||||
|
應繳金額,
|
||||||
|
已收金額,
|
||||||
|
未收金額,
|
||||||
|
付款期限,
|
||||||
|
CASE
|
||||||
|
WHEN 付款期限 < GETDATE() THEN '已逾期'
|
||||||
|
WHEN 付款期限 <= DATEADD(DAY, 3, GETDATE()) THEN '即將到期'
|
||||||
|
ELSE '未到期'
|
||||||
|
END AS 到期狀態
|
||||||
|
FROM vw_activity_registration_base
|
||||||
|
WHERE 未收金額 > 0;
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- 9. 匯款對帳明細 VIEW
|
||||||
|
CREATE VIEW vw_activity_transfer_reconciliation AS
|
||||||
|
SELECT
|
||||||
|
活動編號,
|
||||||
|
活動名稱,
|
||||||
|
匯款記錄ID,
|
||||||
|
匯款人姓名,
|
||||||
|
匯款人電話,
|
||||||
|
匯款金額,
|
||||||
|
入帳日期,
|
||||||
|
匯款狀態,
|
||||||
|
COUNT(收款記錄編號) AS 關聯收款筆數,
|
||||||
|
SUM(收款金額) AS 已對帳金額,
|
||||||
|
CASE
|
||||||
|
WHEN SUM(收款金額) IS NULL THEN 匯款金額
|
||||||
|
ELSE 匯款金額 - SUM(收款金額)
|
||||||
|
END AS 剩餘金額
|
||||||
|
FROM vw_activity_payment_detail
|
||||||
|
WHERE 匯款記錄ID IS NOT NULL
|
||||||
|
GROUP BY 活動編號, 活動名稱, 匯款記錄ID, 匯款人姓名, 匯款人電話, 匯款金額, 入帳日期, 匯款狀態;
|
||||||
|
GO
|
||||||
484
data/memo/report.md
Normal file
484
data/memo/report.md
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
# 相關頁面
|
||||||
|
## 基本功能
|
||||||
|
admin/order/index.aspx
|
||||||
|
admin/activity/index.aspx
|
||||||
|
admin/follower/index.aspx
|
||||||
|
admin/activity/index2.aspx
|
||||||
|
admin/transfer/index.aspx
|
||||||
|
|
||||||
|
## 入帳沖帳
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\balance_reconcile_query.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\balance_reconcile.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\group_reconcile.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\index.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\personal_reconcile.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\register.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\verify_order_record_query.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\verify.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\verify1.aspx
|
||||||
|
D:\dev\ez\17168erp\git_17888\web\admin\transfer\verify2.aspx
|
||||||
|
|
||||||
|
# 資料結構
|
||||||
|
|
||||||
|
## 📊 17168ERP 系統使用的資料表架構
|
||||||
|
|
||||||
|
### 🎯 **核心業務資料表**
|
||||||
|
|
||||||
|
#### 1. **報名管理系統** (`order/index.aspx`)
|
||||||
|
**主要資料表:**
|
||||||
|
- **`pro_order`** - 報名主表
|
||||||
|
- `order_no` (單號)、`up_time` (報名日期)、`keyin1` (單據狀態)
|
||||||
|
- `f_num` (信眾編號)、`activity_num` (活動編號)、`phone` (聯絡電話)
|
||||||
|
|
||||||
|
- **`pro_order_detail`** - 報名明細表
|
||||||
|
- `order_no` (關聯主表)、`actItem_num` (活動品項)、`f_num` (報名者)
|
||||||
|
- `price` (金額)、`qty` (數量)、`pay` (已收金額)、`pay_date` (付款期限)
|
||||||
|
|
||||||
|
- **`activity`** - 活動主表
|
||||||
|
- `num`、`subject` (活動名稱)、`start_date` (開始日期)、`end_date` (結束日期)
|
||||||
|
|
||||||
|
- **`actItem`** - 活動品項表
|
||||||
|
- `num`、`subject` (品項名稱)、`category` (品項分類)
|
||||||
|
|
||||||
|
#### 2. **信眾管理系統** (`follower/index.aspx`)
|
||||||
|
**主要資料表:**
|
||||||
|
- **`followers`** - 信眾基本資料表
|
||||||
|
- `num`、`f_number` (信眾編號)、`u_name` (姓名)、`sex` (性別)
|
||||||
|
- `identity_type` (身分別)、`birthday` (生日)、`phone` (電話)
|
||||||
|
- `address` (地址)、`country` (國籍)、`refugedate` (皈依日期)
|
||||||
|
|
||||||
|
- **`countries`** - 國籍資料表
|
||||||
|
- `ID`、`name_zh` (中文名稱)、`name_en` (英文名稱)
|
||||||
|
|
||||||
|
#### 3. **活動管理系統** (`activity/index.aspx`)
|
||||||
|
**主要資料表:**
|
||||||
|
- **`activity`** - 活動主表
|
||||||
|
- `num`、`subject` (活動名稱)、`startDate_solar` (國曆開始日期)
|
||||||
|
- `startDate_lunar` (農曆開始日期)、`endDate_solar` (國曆結束日期)
|
||||||
|
- `endDate_lunar` (農曆結束日期)、`dueDate` (報名截止日期)
|
||||||
|
|
||||||
|
- **`activity_kind`** - 活動分類表
|
||||||
|
- `num`、`subject` (分類名稱)
|
||||||
|
|
||||||
|
#### 4. **匯款沖帳系統** (`transfer/index.aspx`)
|
||||||
|
**主要資料表:**
|
||||||
|
- **`transfer_register`** - 匯款登錄表
|
||||||
|
- `id`、`name` (匯款人姓名)、`phone` (電話)、`amount` (匯款金額)
|
||||||
|
- `pay_type` (付款方式)、`account_last5` (帳號後五碼)
|
||||||
|
- `proof_img` (匯款證明圖片)、`status` (狀態)
|
||||||
|
- `f_num_match` (配對信眾編號)、`check_amount` (核對金額)
|
||||||
|
|
||||||
|
- **`accounting`** - 會計帳務表
|
||||||
|
- `num`、`category` (科目分類)、`kind` (收支類型)
|
||||||
|
- `price` (金額)、`debtor` (債務人)、`activity_num` (關聯活動)
|
||||||
|
|
||||||
|
- **`pro_order_record`** - 報名收款記錄表
|
||||||
|
- `num`、`detail_num` (關聯明細)、`price` (金額)、`payment` (付款方式)
|
||||||
|
- `pay_date` (收款日期)、`transfer_id` (關聯匯款記錄)
|
||||||
|
|
||||||
|
### 🔗 **關聯關係**
|
||||||
|
|
||||||
|
#### **主要外鍵關聯:**
|
||||||
|
```
|
||||||
|
pro_order → followers (f_num)
|
||||||
|
pro_order → activity (activity_num)
|
||||||
|
pro_order_detail → pro_order (order_no)
|
||||||
|
pro_order_detail → actItem (actItem_num)
|
||||||
|
pro_order_detail → followers (f_num)
|
||||||
|
transfer_register → followers (f_num)
|
||||||
|
transfer_register → activity (activity_num)
|
||||||
|
accounting → pro_order_detail (pro_order_detail_num)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📈 **資料表統計**
|
||||||
|
|
||||||
|
| 功能模組 | 主要資料表數量 | 核心業務表 |
|
||||||
|
|---------|-------------|-----------|
|
||||||
|
| 報名管理 | 5個 | `pro_order`, `pro_order_detail` |
|
||||||
|
| 信眾管理 | 2個 | `followers`, `countries` |
|
||||||
|
| 活動管理 | 2個 | `activity`, `activity_kind` |
|
||||||
|
| 匯款沖帳 | 3個 | `transfer_register`, `accounting`, `pro_order_record` |
|
||||||
|
|
||||||
|
### 🎯 **系統特色**
|
||||||
|
|
||||||
|
1. **模組化設計**:每個功能模組都有獨立的資料表群組
|
||||||
|
2. **關聯完整**:透過外鍵建立完整的業務關聯關係
|
||||||
|
3. **狀態管理**:多個狀態表支援複雜的業務流程
|
||||||
|
4. **UUID設計**:新模組採用UUID作為主鍵,提升擴展性
|
||||||
|
5. **軟刪除機制**:使用`IsDeleted`欄位而非物理刪除
|
||||||
|
|
||||||
|
這個資料表架構支援了一個完整的宗教活動管理ERP系統,涵蓋了從信眾管理、活動報名、床位安排到財務沖帳的全流程管理。
|
||||||
|
|
||||||
|
# 入帳沖帳
|
||||||
|
```
|
||||||
|
報名者匯款 → transfer_register (status: pending)
|
||||||
|
↓
|
||||||
|
出納核對身份 → transfer_register (status: verified, f_num_match)
|
||||||
|
↓
|
||||||
|
出納核對金額 → transfer_register (status: confirmed, check_amount)
|
||||||
|
↓
|
||||||
|
會計個人沖帳 → pro_order_record + accounting
|
||||||
|
↓
|
||||||
|
會計共同沖帳 → pro_order_record + accounting (多筆)
|
||||||
|
↓
|
||||||
|
會計餘額核銷 → transfer_register (remain_amount)
|
||||||
|
↓
|
||||||
|
查詢統計報表 → 各表關聯查詢
|
||||||
|
```
|
||||||
|
|
||||||
|
# DB SCHEMA
|
||||||
|
我來為您使用 Mermaid 語法繪製 17168ERP 系統的 ER 圖,展示各個資料表之間的關聯關係。
|
||||||
|
|
||||||
|
|
||||||
|
[1 tool called]
|
||||||
|
|
||||||
|
我無法直接創建 Mermaid 圖表,但我可以為您提供完整的 Mermaid ER 圖代碼,您可以複製到支援 Mermaid 的工具中查看(如 GitHub、GitLab、或 Mermaid Live Editor)。
|
||||||
|
|
||||||
|
以下是 17168ERP 系統的 ER 圖 Mermaid 代碼:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
%% 信眾管理系統
|
||||||
|
followers {
|
||||||
|
int num PK
|
||||||
|
string f_number
|
||||||
|
string u_name
|
||||||
|
string sex
|
||||||
|
int identity_type
|
||||||
|
datetime birthday
|
||||||
|
string phone
|
||||||
|
string email
|
||||||
|
datetime refugedate
|
||||||
|
string refuge_name
|
||||||
|
string address
|
||||||
|
string demo
|
||||||
|
int leader
|
||||||
|
string socialid1
|
||||||
|
string socialid2
|
||||||
|
string tab
|
||||||
|
string contactor
|
||||||
|
string contactor_phone
|
||||||
|
string blood
|
||||||
|
string customize_data
|
||||||
|
}
|
||||||
|
|
||||||
|
countries {
|
||||||
|
int ID PK
|
||||||
|
string name_zh
|
||||||
|
string name_en
|
||||||
|
string range
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 活動管理系統
|
||||||
|
activity {
|
||||||
|
int num PK
|
||||||
|
string subject
|
||||||
|
datetime startDate_solar
|
||||||
|
datetime startDate_lunar
|
||||||
|
datetime endDate_solar
|
||||||
|
datetime endDate_lunar
|
||||||
|
datetime dueDate
|
||||||
|
int kind
|
||||||
|
string demo
|
||||||
|
string customize_data
|
||||||
|
}
|
||||||
|
|
||||||
|
activity_kind {
|
||||||
|
int num PK
|
||||||
|
string subject
|
||||||
|
string demo
|
||||||
|
}
|
||||||
|
|
||||||
|
actItem {
|
||||||
|
int num PK
|
||||||
|
int activity_num FK
|
||||||
|
string subject
|
||||||
|
int category
|
||||||
|
string demo
|
||||||
|
string customize_data
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 報名管理系統
|
||||||
|
pro_order {
|
||||||
|
string order_no PK
|
||||||
|
datetime up_time
|
||||||
|
datetime reg_time
|
||||||
|
string keyin1
|
||||||
|
int f_num FK
|
||||||
|
string phone
|
||||||
|
int activity_num FK
|
||||||
|
string address
|
||||||
|
string demo
|
||||||
|
string customize_data
|
||||||
|
int introducer FK
|
||||||
|
boolean send_receipt
|
||||||
|
string receipt_title
|
||||||
|
}
|
||||||
|
|
||||||
|
pro_order_detail {
|
||||||
|
int num PK
|
||||||
|
string order_no FK
|
||||||
|
int actItem_num FK
|
||||||
|
int f_num FK
|
||||||
|
string f_num_tablet
|
||||||
|
string address
|
||||||
|
int from_id FK
|
||||||
|
string from_id_tablet
|
||||||
|
datetime due_date
|
||||||
|
int bed_type
|
||||||
|
float price
|
||||||
|
int qty
|
||||||
|
datetime start_date
|
||||||
|
datetime extend_date
|
||||||
|
float pay
|
||||||
|
datetime pay_date
|
||||||
|
int keyin1
|
||||||
|
string demo
|
||||||
|
datetime UpdateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 匯款沖帳系統
|
||||||
|
transfer_register {
|
||||||
|
int id PK
|
||||||
|
int activity_num FK
|
||||||
|
string name
|
||||||
|
string phone
|
||||||
|
string pay_type
|
||||||
|
string account_last5
|
||||||
|
decimal amount
|
||||||
|
string pay_mode
|
||||||
|
string note
|
||||||
|
string proof_img
|
||||||
|
string status
|
||||||
|
datetime create_time
|
||||||
|
int f_num_match FK
|
||||||
|
int f_num FK
|
||||||
|
int acc_num
|
||||||
|
datetime check_date
|
||||||
|
decimal check_amount
|
||||||
|
string check_memo
|
||||||
|
string check_status
|
||||||
|
int acc_kind
|
||||||
|
int member_num
|
||||||
|
datetime verify_time
|
||||||
|
string verify_note
|
||||||
|
string draft
|
||||||
|
decimal remain_amount
|
||||||
|
int balance_act_item FK
|
||||||
|
int balance_pro_order_detail FK
|
||||||
|
}
|
||||||
|
|
||||||
|
accounting {
|
||||||
|
int num PK
|
||||||
|
datetime uptime
|
||||||
|
int category
|
||||||
|
int kind
|
||||||
|
int kind2
|
||||||
|
float price
|
||||||
|
float tax
|
||||||
|
string demo
|
||||||
|
int mem_num
|
||||||
|
string debtor
|
||||||
|
int activity_num FK
|
||||||
|
string excerpt
|
||||||
|
datetime reg_time
|
||||||
|
int pro_order_detail_num FK
|
||||||
|
}
|
||||||
|
|
||||||
|
pro_order_record {
|
||||||
|
int num PK
|
||||||
|
int detail_num FK
|
||||||
|
float price
|
||||||
|
int payment
|
||||||
|
datetime reg_time
|
||||||
|
datetime pay_date
|
||||||
|
string organization
|
||||||
|
string bank_code
|
||||||
|
int transfer_id FK
|
||||||
|
string reconcile_memo
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 區域床位管理系統
|
||||||
|
Region {
|
||||||
|
Guid Uuid PK
|
||||||
|
string Name
|
||||||
|
boolean Gender
|
||||||
|
boolean IsActive
|
||||||
|
boolean IsDeleted
|
||||||
|
}
|
||||||
|
|
||||||
|
Room {
|
||||||
|
Guid Uuid PK
|
||||||
|
string Name
|
||||||
|
boolean Gender
|
||||||
|
int BedCount
|
||||||
|
boolean IsActive
|
||||||
|
datetime CreatedAt
|
||||||
|
datetime UpdatedAt
|
||||||
|
boolean IsDeleted
|
||||||
|
Guid RegionUuid FK
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionRoomBed {
|
||||||
|
Guid Uuid PK
|
||||||
|
string Name
|
||||||
|
boolean IsActive
|
||||||
|
boolean Gender
|
||||||
|
boolean IsDeleted
|
||||||
|
Guid RoomUuid FK
|
||||||
|
string StatusCode FK
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionRoomBedStatus {
|
||||||
|
string Code PK
|
||||||
|
string Name
|
||||||
|
string Description
|
||||||
|
int Category
|
||||||
|
boolean IsDeleted
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 掛單管理系統
|
||||||
|
GuaDanOrder {
|
||||||
|
Guid Uuid PK
|
||||||
|
datetime StartDate
|
||||||
|
datetime EndDate
|
||||||
|
int CreateUser FK
|
||||||
|
datetime CreatedAt
|
||||||
|
datetime UpdatedAt
|
||||||
|
string Notes
|
||||||
|
string GuaDanOrderNo
|
||||||
|
int BookerFollowerNum FK
|
||||||
|
string BookerName
|
||||||
|
string BookerPhone
|
||||||
|
boolean IsDeleted
|
||||||
|
int ActivityNum FK
|
||||||
|
boolean IsCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
GuaDanOrderGuest {
|
||||||
|
Guid Uuid PK
|
||||||
|
string GuaDanOrderNo FK
|
||||||
|
int FollowerNum FK
|
||||||
|
boolean IsDeleted
|
||||||
|
Guid RoomUuid FK
|
||||||
|
Guid BedUuid FK
|
||||||
|
datetime CheckInAt
|
||||||
|
datetime CheckOutAt
|
||||||
|
string StatusCode FK
|
||||||
|
}
|
||||||
|
|
||||||
|
RegionAndRoomAndBedSchedule {
|
||||||
|
Guid Uuid PK
|
||||||
|
Guid TargetUuid FK
|
||||||
|
string GuaDanOrderNo
|
||||||
|
date ScheduleDate
|
||||||
|
boolean IsCancel
|
||||||
|
boolean IsDeleted
|
||||||
|
int UseType
|
||||||
|
string Title
|
||||||
|
string Description
|
||||||
|
string CreatedBy
|
||||||
|
datetime CreatedAt
|
||||||
|
Guid GuaDanOrderGuestUuid FK
|
||||||
|
}
|
||||||
|
|
||||||
|
%% 關聯關係
|
||||||
|
followers ||--o{ pro_order : "報名"
|
||||||
|
followers ||--o{ pro_order_detail : "報名明細"
|
||||||
|
followers ||--o{ transfer_register : "匯款人"
|
||||||
|
followers ||--o{ GuaDanOrderGuest : "掛單客人"
|
||||||
|
|
||||||
|
activity ||--o{ pro_order : "活動報名"
|
||||||
|
activity ||--o{ actItem : "活動品項"
|
||||||
|
activity ||--o{ transfer_register : "活動匯款"
|
||||||
|
activity ||--o{ GuaDanOrder : "掛單活動"
|
||||||
|
|
||||||
|
pro_order ||--o{ pro_order_detail : "報名明細"
|
||||||
|
pro_order ||--o{ pro_order_record : "收款記錄"
|
||||||
|
|
||||||
|
pro_order_detail ||--o{ pro_order_record : "明細收款"
|
||||||
|
pro_order_detail ||--o{ accounting : "會計分錄"
|
||||||
|
pro_order_detail ||--o{ transfer_register : "沖帳明細"
|
||||||
|
|
||||||
|
transfer_register ||--o{ pro_order_record : "匯款記錄"
|
||||||
|
transfer_register ||--o{ accounting : "會計記錄"
|
||||||
|
|
||||||
|
Region ||--o{ Room : "區域房間"
|
||||||
|
Room ||--o{ RegionRoomBed : "房間床位"
|
||||||
|
RegionRoomBedStatus ||--o{ RegionRoomBed : "床位狀態"
|
||||||
|
RegionRoomBedStatus ||--o{ GuaDanOrderGuest : "客人狀態"
|
||||||
|
|
||||||
|
GuaDanOrder ||--o{ GuaDanOrderGuest : "掛單客人"
|
||||||
|
GuaDanOrderGuest ||--o{ RegionAndRoomAndBedSchedule : "床位排程"
|
||||||
|
|
||||||
|
countries ||--o{ followers : "國籍"
|
||||||
|
activity_kind ||--o{ activity : "活動分類"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 **ER 圖說明**
|
||||||
|
|
||||||
|
### **主要實體群組:**
|
||||||
|
|
||||||
|
1. **信眾管理群組**
|
||||||
|
- `followers` (信眾基本資料)
|
||||||
|
- `countries` (國籍資料)
|
||||||
|
|
||||||
|
2. **活動管理群組**
|
||||||
|
- `activity` (活動主表)
|
||||||
|
- `activity_kind` (活動分類)
|
||||||
|
- `actItem` (活動品項)
|
||||||
|
|
||||||
|
3. **報名管理群組**
|
||||||
|
- `pro_order` (報名主表)
|
||||||
|
- `pro_order_detail` (報名明細)
|
||||||
|
|
||||||
|
4. **財務管理群組**
|
||||||
|
- `transfer_register` (匯款登錄)
|
||||||
|
- `accounting` (會計帳務)
|
||||||
|
- `pro_order_record` (收款記錄)
|
||||||
|
|
||||||
|
5. **床位管理群組**
|
||||||
|
- `Region` (區域)
|
||||||
|
- `Room` (房間)
|
||||||
|
- `RegionRoomBed` (床位)
|
||||||
|
- `RegionRoomBedStatus` (床位狀態)
|
||||||
|
|
||||||
|
6. **掛單管理群組**
|
||||||
|
- `GuaDanOrder` (掛單主表)
|
||||||
|
- `GuaDanOrderGuest` (掛單客人)
|
||||||
|
- `RegionAndRoomAndBedSchedule` (床位排程)
|
||||||
|
|
||||||
|
### **關鍵關聯關係:**
|
||||||
|
|
||||||
|
- **一對多關係**:使用 `||--o{` 表示
|
||||||
|
- **外鍵關聯**:FK 標記顯示主要的外鍵關聯
|
||||||
|
- **業務流程**:從信眾報名 → 匯款 → 沖帳 → 床位安排的完整流程
|
||||||
|
|
||||||
|
您可以将這個 Mermaid 代碼複製到 [Mermaid Live Editor](https://mermaid.live/) 或其他支援 Mermaid 的工具中查看完整的 ER 圖表。
|
||||||
|
|
||||||
|
---
|
||||||
|
# 報表系統規劃
|
||||||
|
為每場活動(法會)建立報名到舉辦過程的報表,
|
||||||
|
要涵蓋以下面向的統計分析資訊
|
||||||
|
|
||||||
|
- 以單一場法會為核心
|
||||||
|
- 時間面向: 當前狀況, 指定期間狀況
|
||||||
|
- 維度:
|
||||||
|
- 信眾: 報名數量, 金額, 收款狀態
|
||||||
|
- 牌位型態(活動品項表): 報名數量, 金額, 收款狀態
|
||||||
|
- 收款狀態: 己收/未收 統計明細
|
||||||
|
|
||||||
|
## 執行方式:
|
||||||
|
- 類似excel, 詳細資料->pivot table
|
||||||
|
- 先建立一個(或數個)最核心的sql view, 包含各項:報名資料, 收款明細
|
||||||
|
- 先以單一活動編號為固定FILTER : activity.num=59
|
||||||
|
- 再依不同面向, 建立第二級的sql view
|
||||||
|
- 再人工將以上:第一, 第二級的SQL VIEW, 以EXCEL查詢, 做資料分析/整理
|
||||||
|
- 相關英文欄名, 在VIEW中以中文別名顯示
|
||||||
|
|
||||||
|
## 相關SQL VIEW
|
||||||
|
- (查詢清單)
|
||||||
|
### (查詢)
|
||||||
|
(說明)
|
||||||
|
```sql
|
||||||
|
```
|
||||||
BIN
data/查詢範例.xlsx
Normal file
BIN
data/查詢範例.xlsx
Normal file
Binary file not shown.
33
web/App_Code/GuaDanStatusCode.cs
Normal file
33
web/App_Code/GuaDanStatusCode.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GuaDanStatusCode 的摘要描述
|
||||||
|
/// </summary>
|
||||||
|
public static class GuaDanStatusCode
|
||||||
|
{
|
||||||
|
public static class Bed
|
||||||
|
{
|
||||||
|
public const string Empty = "101"; // 空床:床位可分配
|
||||||
|
public const string Occupied = "102"; // 占用中:床位已有人使用
|
||||||
|
public const string Repair = "103"; // 維修停用:床位維修或不可使用
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Room
|
||||||
|
{
|
||||||
|
public const string Empty = "301"; // 空房:房間所有床位皆為空
|
||||||
|
public const string Partly = "302"; // 部分入住:房間有人,但仍有空床
|
||||||
|
public const string Full = "303"; // 已滿:房間所有床位皆已入住
|
||||||
|
public const string Repair = "304"; // 維修停用:房間維修或不可使用
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Guadan
|
||||||
|
{
|
||||||
|
public const string Booked = "401"; // 預訂成功:默認就是預訂成功狀態
|
||||||
|
public const string CheckedIn = "402"; // 已入住:已辦理入住
|
||||||
|
public const string CheckedOut = "403"; // 已退房
|
||||||
|
public const string Cancelled = "404"; // 已取消:取消後的狀態,不是取消的動作
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +1,38 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Web;
|
|
||||||
using System.Data;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Collections;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Web.UI;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Net.Mail;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Web;
|
|
||||||
using System.Web.UI;
|
|
||||||
using System.Web.UI.WebControls;
|
|
||||||
using System.Data.OleDb;
|
|
||||||
using Microsoft.VisualBasic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Web.Security;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Web.UI;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.Design;
|
using System.ComponentModel.Design;
|
||||||
using System.ComponentModel.Design.Serialization;
|
using System.ComponentModel.Design.Serialization;
|
||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.OleDb;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Web;
|
||||||
using System.Web.Caching;
|
using System.Web.Caching;
|
||||||
using System.Web.ModelBinding;
|
using System.Web.ModelBinding;
|
||||||
using System.Web.Routing;
|
using System.Web.Routing;
|
||||||
|
using System.Web.Security;
|
||||||
using System.Web.SessionState;
|
using System.Web.SessionState;
|
||||||
|
using System.Web.UI;
|
||||||
using System.Web.UI.Adapters;
|
using System.Web.UI.Adapters;
|
||||||
using System.Web.UI.HtmlControls;
|
using System.Web.UI.HtmlControls;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Web;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Data;
|
|
||||||
using System.Data.OleDb;
|
|
||||||
using System.Configuration;
|
|
||||||
using System.Web.UI;
|
|
||||||
using System.Web.UI.WebControls;
|
using System.Web.UI.WebControls;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Web;
|
|
||||||
using System.Web.UI;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Net;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Web.UI.WebControls;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Model.ViewModel
|
namespace Model.ViewModel
|
||||||
|
|||||||
73
web/App_Code/api/ActivityStatisticsController.cs
Normal file
73
web/App_Code/api/ActivityStatisticsController.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.Http;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ActivityStatisticsController 的摘要描述
|
||||||
|
/// </summary>
|
||||||
|
public class ActivityStatisticsController: ApiController
|
||||||
|
{
|
||||||
|
private Model.ezEntities _db = new Model.ezEntities();
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/activity/statistics/summary")]
|
||||||
|
public IHttpActionResult GetStatisticsSummary([FromUri] int? activity_num =null)
|
||||||
|
{
|
||||||
|
if (activity_num == null)
|
||||||
|
{
|
||||||
|
return BadRequest("活动Id不能为空");
|
||||||
|
}
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var pre_order_query = _db.pro_order.Where(a => a.activity_num == activity_num);
|
||||||
|
var totalApplicants = pre_order_query.Count();
|
||||||
|
var maleApplicants = pre_order_query.Where(a => a.follower.sex == "男眾").Count();
|
||||||
|
|
||||||
|
var pro_order_detail_query = _db.pro_order_detail
|
||||||
|
.Where(d => d.pro_order.activity_num == activity_num);
|
||||||
|
var result = new
|
||||||
|
{
|
||||||
|
reportDate = now.ToString("yyyy/MM/dd HH:mm:ss"),
|
||||||
|
totalApplicants = totalApplicants, //报名总人数
|
||||||
|
maleApplicants = maleApplicants, //男
|
||||||
|
femaleApplicants = totalApplicants - maleApplicants,//女
|
||||||
|
donation = new
|
||||||
|
{
|
||||||
|
total = 158000,//总功德金
|
||||||
|
received = 150000,//已收功德金
|
||||||
|
unreceived = 8000//未收功德金
|
||||||
|
},
|
||||||
|
items = new[]//功德项目,这个的功德项目要根据活动预设的功德项目来做统计,就是这个活动有那些可报名的功德项目
|
||||||
|
{
|
||||||
|
new { name = "總功德主", count = "10人" },
|
||||||
|
new { name = "利益主", count = "15人" },
|
||||||
|
new { name = "個人大牌", count = "50人" },
|
||||||
|
new { name = "供僧", count = "45人" }
|
||||||
|
},
|
||||||
|
plaques = new[]//牌位
|
||||||
|
{
|
||||||
|
new { name = "總牌位數", count = pro_order_detail_query.Count() },
|
||||||
|
new { name = "消災-大牌位", count = 101 },
|
||||||
|
new { name = "超冤-大牌位", count =10 },
|
||||||
|
new { name = "消災-個人大牌", count =10 },
|
||||||
|
new { name = "超冤-個人大牌", count = 10 },
|
||||||
|
new { name = "超薦-個人大牌", count = 10 },
|
||||||
|
new { name = "消災-個人中牌", count = 10 },
|
||||||
|
new { name = "超冤-個人中牌", count = 10 },
|
||||||
|
new { name = "超薦-個人中牌", count = 10 },
|
||||||
|
new { name = "消災-隨喜牌位", count = 10 },
|
||||||
|
new { name = "超冤-隨喜牌位", count = 10 },
|
||||||
|
new { name = "超薦-隨喜牌位", count = 10 },
|
||||||
|
new { name = "消災-常年牌位", count = 10 },
|
||||||
|
new { name = "超冤-常年牌位", count = 10 },
|
||||||
|
new { name = "超薦-常年牌位", count = 10 },
|
||||||
|
new { name = "消災-急立牌位", count = 10 },
|
||||||
|
new { name = "超薦-急立牌位", count = 10 },
|
||||||
|
new { name = "超冤-急立牌位", count = 101 },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
184
web/App_Code/api/HandleBedInUsedController.cs
Normal file
184
web/App_Code/api/HandleBedInUsedController.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.Http;
|
||||||
|
using System.Web.Http.Results;
|
||||||
|
using static GuaDanStatusCode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HandleBedInUsedController 的摘要描述
|
||||||
|
/// </summary>
|
||||||
|
public class HandleBedInUsedController : ApiController
|
||||||
|
{
|
||||||
|
private Model.ezEntities _db = new Model.ezEntities();
|
||||||
|
public HandleBedInUsedController()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// TODO: 在這裡新增建構函式邏輯
|
||||||
|
//
|
||||||
|
}
|
||||||
|
[HttpPost]
|
||||||
|
[Route("api/bed/inuse/list")]
|
||||||
|
public IHttpActionResult Get([FromBody] UuidModel uuidModel)
|
||||||
|
{
|
||||||
|
//獲取已被預約或者正在入住的床位,如果有指定就會查詢指定條件,如果沒有指定就會返回所有
|
||||||
|
var query = _db.GuaDanOrderGuest
|
||||||
|
.Where(gd => gd.StatusCode == GuaDanStatusCode.Guadan.CheckedIn
|
||||||
|
|| gd.StatusCode == GuaDanStatusCode.Guadan.Booked).ToList();
|
||||||
|
if (uuidModel.bedUuid.HasValue)
|
||||||
|
{
|
||||||
|
// 優先按床位查詢
|
||||||
|
query = query.Where(g => g.BedUuid == uuidModel.bedUuid.Value).ToList();
|
||||||
|
}
|
||||||
|
else if (uuidModel.roomUuid.HasValue)
|
||||||
|
{
|
||||||
|
// 如果沒有 bed,但有 room
|
||||||
|
query = query.Where(g => g.RoomUuid == uuidModel.roomUuid.Value).ToList();
|
||||||
|
}
|
||||||
|
else if (uuidModel.regionUuid.HasValue)
|
||||||
|
{
|
||||||
|
// 如果只有 region
|
||||||
|
//query = query.Where(g => g.Room.RegionUuid == uuidModel.regionUuid.Value);
|
||||||
|
query = query.Where(g => IsRegionOrAncestor(g.Room, uuidModel.regionUuid.Value)).ToList();
|
||||||
|
|
||||||
|
}
|
||||||
|
var data = query.Select(g => new
|
||||||
|
{
|
||||||
|
g.BedUuid,
|
||||||
|
g.RoomUuid,
|
||||||
|
g.Room.RegionUuid,
|
||||||
|
g.GuaDanOrderNo,
|
||||||
|
g.RegionRoomBed.Name,
|
||||||
|
fullName = GetFullBedName(g.BedUuid.Value),
|
||||||
|
g.followers.u_name,
|
||||||
|
guadan_during = new { g.CheckInAt, g.CheckOutAt },
|
||||||
|
status = new { g.StatusCode, g.RegionRoomBedStatus.Name }
|
||||||
|
});
|
||||||
|
return Ok(data.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("api/bed/inuse/cancel/singlebed/booking")]
|
||||||
|
public IHttpActionResult CancelSingleBedBooking([FromBody] UuidModel uuidModel)
|
||||||
|
{
|
||||||
|
if (uuidModel?.bedUuid == null)
|
||||||
|
return BadRequest("床位ID不能為空");
|
||||||
|
|
||||||
|
using (var transaction = _db.Database.BeginTransaction())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 查詢符合條件的訂單
|
||||||
|
var orders = _db.GuaDanOrderGuest
|
||||||
|
.Where(g => g.BedUuid == uuidModel.bedUuid)
|
||||||
|
.Where(g => g.StatusCode == GuaDanStatusCode.Guadan.Booked || g.StatusCode == GuaDanStatusCode.Guadan.CheckedIn)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!orders.Any())
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
// 更新狀態
|
||||||
|
foreach (var order in orders)
|
||||||
|
{
|
||||||
|
if (!StatusTransitionManager.CanTransition(order.StatusCode, GuaDanStatusCode.Guadan.Cancelled))
|
||||||
|
{
|
||||||
|
return BadRequest("當前狀態不能被取消");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
order.StatusCode = GuaDanStatusCode.Guadan.Cancelled; // 假設Cancelled是取消狀態
|
||||||
|
}
|
||||||
|
var schedules = _db.RegionAndRoomAndBedSchedule
|
||||||
|
.Where(s => s.TargetUuid == uuidModel.bedUuid)
|
||||||
|
.Where(s => s.GuaDanOrderGuest.StatusCode == GuaDanStatusCode.Guadan.Booked
|
||||||
|
|| s.GuaDanOrderGuest.StatusCode == GuaDanStatusCode.Guadan.CheckedIn)
|
||||||
|
.ToList();
|
||||||
|
foreach (var schedule in schedules)
|
||||||
|
{
|
||||||
|
schedule.IsCancel = true;
|
||||||
|
}
|
||||||
|
_db.SaveChanges();
|
||||||
|
transaction.Commit();
|
||||||
|
|
||||||
|
return Ok(new { message = "取消成功", cancelledCount = orders.Count });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
return InternalServerError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/bed/inuse/region/list")]
|
||||||
|
public IHttpActionResult GetRegionList()
|
||||||
|
{
|
||||||
|
var regions = _db.Region
|
||||||
|
.Select(r => new
|
||||||
|
{
|
||||||
|
r.Uuid,
|
||||||
|
r.Name,
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
return Ok(regions);
|
||||||
|
}
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/bed/inuse/room/list")]
|
||||||
|
public IHttpActionResult GetRoomList([FromUri] Guid? regionUuid = null)
|
||||||
|
{
|
||||||
|
var room = _db.Room.Where(r => !r.IsDeleted && r.IsActive.Value).ToList();
|
||||||
|
if (regionUuid != null)
|
||||||
|
{
|
||||||
|
room = room.Where(r => IsRegionOrAncestor(r, regionUuid.Value)).ToList();
|
||||||
|
}
|
||||||
|
var data = room.Select(r => new
|
||||||
|
{
|
||||||
|
r.Uuid,
|
||||||
|
r.Name,
|
||||||
|
fullName = r.Region.Name + "/" + r.Name,
|
||||||
|
}).ToList();
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
public string GetFullBedName(Guid bedUuid)
|
||||||
|
{
|
||||||
|
var bed = _db.RegionRoomBed.Find(bedUuid);
|
||||||
|
if (bed == null)
|
||||||
|
return "";
|
||||||
|
var name = bed.Name;
|
||||||
|
var room = bed.Room;
|
||||||
|
if (room == null)
|
||||||
|
return name;
|
||||||
|
name = room.Name + "/" + name;
|
||||||
|
|
||||||
|
var region = room?.Region;
|
||||||
|
while (region != null)
|
||||||
|
{
|
||||||
|
name = region.Name + "/" + name;
|
||||||
|
region = region.Region2; // 遞迴向上
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
bool IsRegionOrAncestor(Model.Room room, Guid regionUuid)
|
||||||
|
{
|
||||||
|
//判斷傳入的regionuuid是否是room的祖先
|
||||||
|
if (room.RegionUuid == regionUuid)
|
||||||
|
return true;
|
||||||
|
var region = room.Region;
|
||||||
|
while (region != null)
|
||||||
|
{
|
||||||
|
if (region.Uuid == regionUuid) return true;
|
||||||
|
region = region.Region2;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public class UuidModel
|
||||||
|
{
|
||||||
|
public Guid? regionUuid = null;
|
||||||
|
public Guid? roomUuid = null;
|
||||||
|
public Guid? bedUuid = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,9 +80,9 @@ public class guadanGuestQueryController: ApiController
|
|||||||
{
|
{
|
||||||
var today = DateTime.Now.Date;
|
var today = DateTime.Now.Date;
|
||||||
var data = await _db.GuaDanOrderGuest
|
var data = await _db.GuaDanOrderGuest
|
||||||
.Where(guest => guest.StatusCode == "402")
|
.Where(guest => guest.StatusCode == "402" || guest.StatusCode == "403")
|
||||||
.Where(guest => guest.RegionAndRoomAndBedSchedule
|
.Where(guest => guest.RegionAndRoomAndBedSchedule
|
||||||
.Any(s => s.ScheduleDate == date.Date && s.ScheduleDate == today) == true)
|
.Any(s => s.ScheduleDate == date.Date && s.ScheduleDate <= today) == true)
|
||||||
.Select(guest => new
|
.Select(guest => new
|
||||||
{
|
{
|
||||||
name = guest.followers.u_name,
|
name = guest.followers.u_name,
|
||||||
@@ -96,7 +96,7 @@ public class guadanGuestQueryController: ApiController
|
|||||||
public async Task<IHttpActionResult> GetBookingGuest([FromUri] DateTime date)
|
public async Task<IHttpActionResult> GetBookingGuest([FromUri] DateTime date)
|
||||||
{
|
{
|
||||||
var data = await _db.GuaDanOrderGuest
|
var data = await _db.GuaDanOrderGuest
|
||||||
.Where(guest => guest.StatusCode == "402" || guest.StatusCode == "401")
|
.Where(guest => guest.StatusCode == "402" || guest.StatusCode == "401" || guest.StatusCode == "403")
|
||||||
.Where(guest => guest.RegionAndRoomAndBedSchedule.Any(s => s.ScheduleDate == date.Date) == true)
|
.Where(guest => guest.RegionAndRoomAndBedSchedule.Any(s => s.ScheduleDate == date.Date) == true)
|
||||||
.Select(guest => new
|
.Select(guest => new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,11 +56,31 @@ public class guadanOrderController : ApiController
|
|||||||
created_at = a.CreatedAt,
|
created_at = a.CreatedAt,
|
||||||
updated_at = a.UpdatedAt,
|
updated_at = a.UpdatedAt,
|
||||||
notes = a.Notes,
|
notes = a.Notes,
|
||||||
|
activity = _db.activities
|
||||||
|
.Where(act => act.num == a.ActivityNum)
|
||||||
|
.Select(act => new
|
||||||
|
{
|
||||||
|
subject = act.subject
|
||||||
|
})
|
||||||
|
.FirstOrDefault(),
|
||||||
bookerName = a.BookerName,
|
bookerName = a.BookerName,
|
||||||
guest_count = _db.GuaDanOrderGuest
|
guest_count = _db.GuaDanOrderGuest
|
||||||
.Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo && c.IsDeleted == false)
|
.Where(c => c.GuaDanOrderNo == a.GuaDanOrderNo && c.IsDeleted == false)
|
||||||
.Where(c => c.RegionRoomBedStatus.Code != GuaDanOrderGuest.STATUS_CANCELLED)
|
.Where(c => c.RegionRoomBedStatus.Code != GuaDanOrderGuest.STATUS_CANCELLED)
|
||||||
.Count(),
|
.Count(),
|
||||||
|
statusName = _db.GuaDanOrderGuest
|
||||||
|
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
|
||||||
|
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
|
||||||
|
.All(g => g.StatusCode == "401") ? "預約" :
|
||||||
|
_db.GuaDanOrderGuest
|
||||||
|
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
|
||||||
|
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
|
||||||
|
.All(g => g.StatusCode == "403") ? "全部退房" :
|
||||||
|
_db.GuaDanOrderGuest
|
||||||
|
.Where(g => g.GuaDanOrderNo == a.GuaDanOrderNo && a.IsDeleted == false)
|
||||||
|
.Where(g => g.StatusCode != GuaDanOrderGuest.STATUS_CANCELLED)
|
||||||
|
.Any(g => g.StatusCode == "402" && a.IsCancel == false) ? "正在入住" :
|
||||||
|
"部分退房"
|
||||||
})
|
})
|
||||||
.Skip((search.page - 1) * search.pageSize)
|
.Skip((search.page - 1) * search.pageSize)
|
||||||
.Take(search.pageSize)
|
.Take(search.pageSize)
|
||||||
@@ -81,7 +101,7 @@ public class guadanOrderController : ApiController
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
{
|
||||||
return BadRequest("未找到对应订单");
|
return BadRequest("未找到對應訂單");
|
||||||
}
|
}
|
||||||
var result = new
|
var result = new
|
||||||
{
|
{
|
||||||
@@ -115,7 +135,7 @@ public class guadanOrderController : ApiController
|
|||||||
}
|
}
|
||||||
if (model.Uuid.HasValue)
|
if (model.Uuid.HasValue)
|
||||||
{
|
{
|
||||||
return BadRequest("已存在对应挂单资料");
|
return BadRequest("已存在對應掛單資料");
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -174,7 +194,7 @@ public class guadanOrderController : ApiController
|
|||||||
var order = await _db.GuaDanOrder.FindAsync(model.Uuid.Value);
|
var order = await _db.GuaDanOrder.FindAsync(model.Uuid.Value);
|
||||||
if (order == null)
|
if (order == null)
|
||||||
{
|
{
|
||||||
return BadRequest("未找到对应挂单资料");
|
return BadRequest("未找到對應掛單資料");
|
||||||
}
|
}
|
||||||
order.StartDate = model.startdate;
|
order.StartDate = model.startdate;
|
||||||
order.EndDate = model.enddate;
|
order.EndDate = model.enddate;
|
||||||
@@ -196,7 +216,7 @@ public class guadanOrderController : ApiController
|
|||||||
}
|
}
|
||||||
if (_db.GuaDanOrderGuest.Any(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo))
|
if (_db.GuaDanOrderGuest.Any(a => a.GuaDanOrderNo == guadan.GuaDanOrderNo))
|
||||||
{
|
{
|
||||||
return BadRequest($"该挂单已经存在挂单莲友,不能取消!");
|
return BadRequest($"該掛單已經存在掛單蓮友,不能取消!");
|
||||||
}
|
}
|
||||||
using (var transaction = _db.Database.BeginTransaction())
|
using (var transaction = _db.Database.BeginTransaction())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,12 +46,13 @@ public class guadanOrderGuestController : ApiController
|
|||||||
checkoutat = a.CheckOutAt.HasValue ? a.CheckOutAt.Value.ToString("yyyy-MM-dd") : null,
|
checkoutat = a.CheckOutAt.HasValue ? a.CheckOutAt.Value.ToString("yyyy-MM-dd") : null,
|
||||||
phone = null,
|
phone = null,
|
||||||
roomName = a.Room.Name,
|
roomName = a.Room.Name,
|
||||||
bedName = a.RegionRoomBed.Name,
|
bedName = GetBedString(a.RegionRoomBed),
|
||||||
orderNo = a.GuaDanOrderNo,
|
orderNo = a.GuaDanOrderNo,
|
||||||
follower = a.followers == null ? null : new FollowerDto
|
follower = a.followers == null ? null : new FollowerDto
|
||||||
{
|
{
|
||||||
num = a.followers.num,
|
num = a.followers.num,
|
||||||
u_name = a.followers.u_name
|
u_name = a.followers.u_name,
|
||||||
|
sex = a.followers.sex
|
||||||
},
|
},
|
||||||
statuscode = a.StatusCode,
|
statuscode = a.StatusCode,
|
||||||
statusName = a.RegionRoomBedStatus?.Name,
|
statusName = a.RegionRoomBedStatus?.Name,
|
||||||
@@ -59,7 +60,22 @@ public class guadanOrderGuestController : ApiController
|
|||||||
|
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
public string GetBedString(RegionRoomBed bed)
|
||||||
|
{
|
||||||
|
if (bed == null)
|
||||||
|
return "";
|
||||||
|
var room = bed.Room;
|
||||||
|
var name = room.Name + "/" + bed.Name;
|
||||||
|
var region = room.Region;
|
||||||
|
name = region.Name + "/" + name;
|
||||||
|
var parentRegion = region.Region2;
|
||||||
|
while(parentRegion != null)
|
||||||
|
{
|
||||||
|
name = parentRegion.Name + "/" + name;
|
||||||
|
parentRegion = parentRegion.Region2;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("api/guadanorderguest/create")]
|
[Route("api/guadanorderguest/create")]
|
||||||
public async Task<IHttpActionResult> create([FromBody] guadan_order_guest_dto model)
|
public async Task<IHttpActionResult> create([FromBody] guadan_order_guest_dto model)
|
||||||
@@ -650,6 +666,7 @@ public class guadanOrderGuestController : ApiController
|
|||||||
{
|
{
|
||||||
public int num { get; set; }
|
public int num { get; set; }
|
||||||
public string u_name { get; set; }
|
public string u_name { get; set; }
|
||||||
|
public string sex { get; set; }
|
||||||
}
|
}
|
||||||
public class XuZhuModel
|
public class XuZhuModel
|
||||||
{
|
{
|
||||||
|
|||||||
246
web/App_Code/api/pivot01Controller.cs
Normal file
246
web/App_Code/api/pivot01Controller.cs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web.Http;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// pivot01Controller - 法會報名統計分析 API
|
||||||
|
/// 設計理念:直接查詢 SQL VIEW,保持彈性與簡潔
|
||||||
|
/// </summary>
|
||||||
|
[ezAuthorize]
|
||||||
|
public class pivot01Controller : ApiController
|
||||||
|
{
|
||||||
|
// 連線字串
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
public pivot01Controller()
|
||||||
|
{
|
||||||
|
// 優先使用 shopConn 連線字串(純 SQL Server 連線字串)
|
||||||
|
var shopConnectionString = ConfigurationManager.ConnectionStrings["shopConn"]?.ConnectionString;
|
||||||
|
if (!string.IsNullOrEmpty(shopConnectionString))
|
||||||
|
{
|
||||||
|
// 移除不相容的 Provider 參數
|
||||||
|
_connectionString = shopConnectionString.Replace("Provider=SQLOLEDB;", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 備用方案:從 Entity Framework 連線字串中提取 SQL Server 連線字串
|
||||||
|
var efConnectionString = ConfigurationManager.ConnectionStrings["ezEntities"]?.ConnectionString;
|
||||||
|
if (!string.IsNullOrEmpty(efConnectionString))
|
||||||
|
{
|
||||||
|
// 解析 EF 連線字串,提取 provider connection string 部分
|
||||||
|
var startIndex = efConnectionString.IndexOf("provider connection string="") + "provider connection string="".Length;
|
||||||
|
var endIndex = efConnectionString.LastIndexOf(""");
|
||||||
|
if (startIndex > 0 && endIndex > startIndex)
|
||||||
|
{
|
||||||
|
_connectionString = efConnectionString.Substring(startIndex, endIndex - startIndex);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("無法解析 Entity Framework 連線字串");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("找不到可用的資料庫連線字串");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 通用 VIEW 查詢方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 執行 SQL 查詢並回傳 DataTable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql">SQL 查詢語句</param>
|
||||||
|
/// <param name="parameters">參數陣列</param>
|
||||||
|
/// <returns>查詢結果 DataTable</returns>
|
||||||
|
private DataTable ExecuteSqlQuery(string sql, SqlParameter[] parameters = null)
|
||||||
|
{
|
||||||
|
var dataTable = new DataTable();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(_connectionString))
|
||||||
|
using (var command = new SqlCommand(sql, connection))
|
||||||
|
{
|
||||||
|
// 設定逾時時間為 60 秒
|
||||||
|
command.CommandTimeout = 60;
|
||||||
|
|
||||||
|
// 加入參數
|
||||||
|
if (parameters != null && parameters.Length > 0)
|
||||||
|
{
|
||||||
|
command.Parameters.AddRange(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 開啟連線並執行查詢
|
||||||
|
connection.Open();
|
||||||
|
using (var adapter = new SqlDataAdapter(command))
|
||||||
|
{
|
||||||
|
adapter.Fill(dataTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SqlException ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"SQL 查詢錯誤: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"執行查詢時發生錯誤: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DataTable 轉換為動態物件列表(保留中文欄位名)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dt">DataTable</param>
|
||||||
|
/// <returns>Dictionary 列表</returns>
|
||||||
|
private List<Dictionary<string, object>> DataTableToDictionary(DataTable dt)
|
||||||
|
{
|
||||||
|
var list = new List<Dictionary<string, object>>();
|
||||||
|
|
||||||
|
foreach (DataRow row in dt.Rows)
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, object>();
|
||||||
|
foreach (DataColumn col in dt.Columns)
|
||||||
|
{
|
||||||
|
// 保留原始欄位名稱(包含中文)
|
||||||
|
dict[col.ColumnName] = row[col] == DBNull.Value ? null : row[col];
|
||||||
|
}
|
||||||
|
list.Add(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region API 端點
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GET /api/pivot01/activity_stats
|
||||||
|
/// 查詢法會統計(對應「法會統計」VIEW)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="year">查詢年份</param>
|
||||||
|
/// <returns>法會統計資料</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/pivot01/activity_stats")]
|
||||||
|
public IHttpActionResult GetActivityStats(int year)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 驗證年份參數
|
||||||
|
if (year < 1900 || year > 2100)
|
||||||
|
{
|
||||||
|
return BadRequest("年份參數不正確,請輸入 1900 ~ 2100 之間的年份");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立 SQL 查詢(包含當年度及無日期的法會)
|
||||||
|
string sql = @"
|
||||||
|
SELECT * FROM [法會統計]
|
||||||
|
WHERE (YEAR(開始日期) = @year OR 開始日期 IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN 開始日期 IS NULL THEN 1 ELSE 0 END,
|
||||||
|
開始日期 DESC,
|
||||||
|
結束日期 DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
// 建立參數
|
||||||
|
var parameters = new[]
|
||||||
|
{
|
||||||
|
new SqlParameter("@year", SqlDbType.Int) { Value = year }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
var dataTable = ExecuteSqlQuery(sql, parameters);
|
||||||
|
var data = DataTableToDictionary(dataTable);
|
||||||
|
|
||||||
|
// 回應結果
|
||||||
|
var result = new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
data = data,
|
||||||
|
message = "查詢成功",
|
||||||
|
rowCount = data.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var errorResponse = new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = $"查詢失敗:{ex.Message}",
|
||||||
|
error = ex.ToString()
|
||||||
|
};
|
||||||
|
return Content(System.Net.HttpStatusCode.BadRequest, errorResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GET /api/pivot01/registration_details
|
||||||
|
/// 查詢報名明細(對應「報名明細查詢」VIEW)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="activityNum">法會編號(必填)</param>
|
||||||
|
/// <returns>報名明細資料</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("api/pivot01/registration_details")]
|
||||||
|
public IHttpActionResult GetRegistrationDetails(int? activityNum = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 驗證參數
|
||||||
|
if (!activityNum.HasValue || activityNum.Value <= 0)
|
||||||
|
{
|
||||||
|
return BadRequest("請提供有效的法會編號(activityNum)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建立查詢 SQL
|
||||||
|
string sql = @"
|
||||||
|
SELECT * FROM [報名明細查詢]
|
||||||
|
WHERE 法會ID = @activityNum
|
||||||
|
ORDER BY 報名日期 DESC, 報名編號 DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
// 建立參數
|
||||||
|
var parameters = new[]
|
||||||
|
{
|
||||||
|
new SqlParameter("@activityNum", SqlDbType.Int) { Value = activityNum.Value }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 執行查詢
|
||||||
|
var dataTable = ExecuteSqlQuery(sql, parameters);
|
||||||
|
var data = DataTableToDictionary(dataTable);
|
||||||
|
|
||||||
|
// 回應結果
|
||||||
|
var result = new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
data = data,
|
||||||
|
message = "查詢成功",
|
||||||
|
rowCount = data.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var errorResponse = new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = $"查詢失敗:{ex.Message}",
|
||||||
|
error = ex.ToString()
|
||||||
|
};
|
||||||
|
return Content(System.Net.HttpStatusCode.BadRequest, errorResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
1142
web/App_Code/api/pivotController.cs
Normal file
1142
web/App_Code/api/pivotController.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Http;
|
using System.Web.Http;
|
||||||
using System.Web.Routing;
|
using System.Web.Routing;
|
||||||
@@ -39,7 +40,7 @@ public class regionController : ApiController
|
|||||||
var startDate = filter.StartDate.Date;
|
var startDate = filter.StartDate.Date;
|
||||||
var endDate = filter.EndDate.Date;
|
var endDate = filter.EndDate.Date;
|
||||||
|
|
||||||
var query = _db.Region//区域状态是否启用这里只设置了过滤了有客房的区域,是否要过滤所有
|
var query = _db.Region//區域狀態是否啟用這裡只設置了過濾了有客房的區域,是否要過濾所有
|
||||||
.Where(r => !r.IsDeleted)
|
.Where(r => !r.IsDeleted)
|
||||||
.Where(r => r.IsActive)
|
.Where(r => r.IsActive)
|
||||||
.Where(r => r.Room.Any());
|
.Where(r => r.Room.Any());
|
||||||
@@ -68,6 +69,7 @@ public class regionController : ApiController
|
|||||||
r.Uuid,
|
r.Uuid,
|
||||||
r.Name,
|
r.Name,
|
||||||
regionPath = r.Name,
|
regionPath = r.Name,
|
||||||
|
isStop = !IsRegionAvailable(r.Uuid),
|
||||||
Room = r.Room
|
Room = r.Room
|
||||||
.Where(room => filter.Gender == null || room.Gender == filter.Gender)
|
.Where(room => filter.Gender == null || room.Gender == filter.Gender)
|
||||||
.Where(room =>
|
.Where(room =>
|
||||||
@@ -149,6 +151,30 @@ public class regionController : ApiController
|
|||||||
Summary = summary,
|
Summary = summary,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public bool IsRegionAvailable(Guid regionUuid)
|
||||||
|
{
|
||||||
|
var current = _db.Region.FirstOrDefault(r => r.Uuid == regionUuid);
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
// 當前區域不可用就直接返回 false
|
||||||
|
if (!current.IsActive || current.IsDeleted)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 沒有父區域了,說明一路上都可用
|
||||||
|
if (!current.ParentUuid.HasValue)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 繼續往父區域走
|
||||||
|
current = _db.Region.FirstOrDefault(r => r.Uuid == current.ParentUuid.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 沒查到(極端情況,比如資料庫被改了)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 遞迴生成區域完整路徑
|
/// 遞迴生成區域完整路徑
|
||||||
@@ -228,25 +254,25 @@ public class regionController : ApiController
|
|||||||
{
|
{
|
||||||
var allRegions = _db.Region.ToList();
|
var allRegions = _db.Region.ToList();
|
||||||
|
|
||||||
// 根区域
|
// 根區域
|
||||||
var rootRegions = allRegions
|
var rootRegions = allRegions
|
||||||
.Where(r => r.ParentUuid == null)
|
.Where(r => r.ParentUuid == null)
|
||||||
.OrderBy(r => r.SortOrder)
|
.OrderBy(r => r.SortOrder)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 生成树并按性别过滤
|
// 生成樹並按性別過濾
|
||||||
var tree = rootRegions
|
var tree = rootRegions
|
||||||
.Select(r => BuildRegionDtoByGender(r, allRegions, request.IsMale))
|
.Select(r => BuildRegionDtoByGender(r, allRegions, request.IsMale))
|
||||||
.Where(r => r != null) // 去掉没有房间的区域
|
.Where(r => r != null) // 去掉沒有房間的區域
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return Ok(tree);
|
return Ok(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据性别过滤房间的 BuildRegionDto
|
// 根據性別過濾房間的 BuildRegionDto
|
||||||
private RegionDto BuildRegionDtoByGender(Region region, List<Region> allRegions, bool? gender)
|
private RegionDto BuildRegionDtoByGender(Region region, List<Region> allRegions, bool? gender)
|
||||||
{
|
{
|
||||||
// 过滤房间按性别
|
// 過濾房間按性別
|
||||||
var rooms = region.Room?
|
var rooms = region.Room?
|
||||||
.Where(a => !gender.HasValue || a.Gender == gender.Value)
|
.Where(a => !gender.HasValue || a.Gender == gender.Value)
|
||||||
.Select(a => new RoomDto
|
.Select(a => new RoomDto
|
||||||
@@ -268,14 +294,14 @@ public class regionController : ApiController
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 递归生成子区域
|
// 遞迴生成子區域
|
||||||
var children = allRegions
|
var children = allRegions
|
||||||
.Where(r => r.ParentUuid == region.Uuid)
|
.Where(r => r.ParentUuid == region.Uuid)
|
||||||
.Select(child => BuildRegionDtoByGender(child, allRegions, gender))
|
.Select(child => BuildRegionDtoByGender(child, allRegions, gender))
|
||||||
.Where(c => c != null) // 去掉没有房间的子区域
|
.Where(c => c != null) // 去掉沒有房間的子區域
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 如果这个区域既没有房间也没有子区域,则返回 null
|
// 如果這個區域既沒有房間也沒有子區域,則返回 null
|
||||||
if (!rooms.Any() && !children.Any())
|
if (!rooms.Any() && !children.Any())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -297,10 +323,10 @@ public class regionController : ApiController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求模型
|
// 請求模型
|
||||||
public class GenderRequest
|
public class GenderRequest
|
||||||
{
|
{
|
||||||
public bool? IsMale { get; set; } // true = 男, false = 女, null = 不过滤
|
public bool? IsMale { get; set; } // true = 男, false = 女, null = 不過濾
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RoomDto
|
public class RoomDto
|
||||||
@@ -378,6 +404,24 @@ public class regionController : ApiController
|
|||||||
{
|
{
|
||||||
return BadRequest("客房數量小於已存在的客房數量");
|
return BadRequest("客房數量小於已存在的客房數量");
|
||||||
}
|
}
|
||||||
|
if (dto.IsActive == false)
|
||||||
|
{
|
||||||
|
var regionIds = GetAllRegionIds(region.Uuid);
|
||||||
|
var hasPendingBeds = _db.RegionRoomBed
|
||||||
|
.Where(b => regionIds.Contains(b.Room.RegionUuid))
|
||||||
|
.SelectMany(b => b.GuaDanOrderGuest)
|
||||||
|
.Any(g => !g.IsDeleted &&
|
||||||
|
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 預約中
|
||||||
|
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN)); // 已入住
|
||||||
|
if (hasPendingBeds)
|
||||||
|
{
|
||||||
|
return Content(HttpStatusCode.BadRequest, new
|
||||||
|
{
|
||||||
|
code = "BED_IS_USED",
|
||||||
|
message = "該區域有床位正在掛單中,請先處理"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
region.Name = dto.Name;
|
region.Name = dto.Name;
|
||||||
region.Description = dto.Description;
|
region.Description = dto.Description;
|
||||||
region.SortOrder = dto.SortOrder;
|
region.SortOrder = dto.SortOrder;
|
||||||
@@ -416,6 +460,25 @@ public class regionController : ApiController
|
|||||||
return Ok(new { message = "刪除成功" });
|
return Ok(new { message = "刪除成功" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Guid> GetAllRegionIds(Guid regionUuid)
|
||||||
|
{
|
||||||
|
var regionIds = new List<Guid> { regionUuid };
|
||||||
|
|
||||||
|
var children = _db.Region
|
||||||
|
.Where(r => r.ParentUuid == regionUuid)
|
||||||
|
.Select(r => r.Uuid)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var childId in children)
|
||||||
|
{
|
||||||
|
regionIds.AddRange(GetAllRegionIds(childId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return regionIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 遞迴刪除子節點
|
// 遞迴刪除子節點
|
||||||
private void DeleteRegionRecursive(Region region)
|
private void DeleteRegionRecursive(Region region)
|
||||||
{
|
{
|
||||||
@@ -438,7 +501,7 @@ public class regionController : ApiController
|
|||||||
[Route("api/region/regionwithroom")]
|
[Route("api/region/regionwithroom")]
|
||||||
public IHttpActionResult GetRegionWithRoom()
|
public IHttpActionResult GetRegionWithRoom()
|
||||||
{
|
{
|
||||||
//返回有房间的region
|
//返回有房間的region
|
||||||
var data = _db.Region.Where(a => a.Room.Count() > 0)
|
var data = _db.Region.Where(a => a.Room.Count() > 0)
|
||||||
.Select(r => new
|
.Select(r => new
|
||||||
{
|
{
|
||||||
@@ -459,7 +522,7 @@ public class regionController : ApiController
|
|||||||
[Route("api/room/roomwithbed")]
|
[Route("api/room/roomwithbed")]
|
||||||
public IHttpActionResult GetRoomWithBed(Guid? RegionUuid = null)
|
public IHttpActionResult GetRoomWithBed(Guid? RegionUuid = null)
|
||||||
{
|
{
|
||||||
//获取所有有床位的房间
|
//獲取所有有床位的房間
|
||||||
var query = _db.Room
|
var query = _db.Room
|
||||||
.Select(r => new
|
.Select(r => new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.Entity;
|
using System.Data.Entity;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using System.Web.Http;
|
using System.Web.Http;
|
||||||
@@ -66,7 +67,7 @@ public class regionRoomBedController : ApiController
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
bool canUsed = !bedSchedules.Any();
|
bool canUsed = !bedSchedules.Any();
|
||||||
|
bool bedIsStop = IsBedStopped(a);
|
||||||
return new
|
return new
|
||||||
{
|
{
|
||||||
a.Uuid,
|
a.Uuid,
|
||||||
@@ -76,6 +77,7 @@ public class regionRoomBedController : ApiController
|
|||||||
a.StatusCode,
|
a.StatusCode,
|
||||||
a.RoomUuid,
|
a.RoomUuid,
|
||||||
canUsed,
|
canUsed,
|
||||||
|
bedIsStop,
|
||||||
schedule = bedSchedules
|
schedule = bedSchedules
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -83,6 +85,33 @@ public class regionRoomBedController : ApiController
|
|||||||
|
|
||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
public bool IsBedStopped(RegionRoomBed bed)
|
||||||
|
{
|
||||||
|
// 1️⃣ 床位本身不可用
|
||||||
|
if (!bed.IsActive || bed.IsDeleted)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 2️⃣ 所属房间不可用
|
||||||
|
var room = bed.Room;
|
||||||
|
if (room == null || !room.IsActive.Value || room.IsDeleted)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 3️⃣ 所属区域不可用
|
||||||
|
var region = room.Region;
|
||||||
|
while (region != null)
|
||||||
|
{
|
||||||
|
if (!region.IsActive || region.IsDeleted)
|
||||||
|
return true; // 有任意一级区域不可用就返回 true
|
||||||
|
|
||||||
|
if (!region.ParentUuid.HasValue)
|
||||||
|
break; // 到顶层了
|
||||||
|
|
||||||
|
region = _db.Region.FirstOrDefault(r => r.Uuid == region.ParentUuid.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4️⃣ 全部检查通过 → 床位可用
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("api/region/bed/create")]
|
[Route("api/region/bed/create")]
|
||||||
@@ -147,6 +176,20 @@ public class regionRoomBedController : ApiController
|
|||||||
{
|
{
|
||||||
return BadRequest("床為性別和房間性別必須一致");
|
return BadRequest("床為性別和房間性別必須一致");
|
||||||
}
|
}
|
||||||
|
if (bed.IsActive == false)
|
||||||
|
{
|
||||||
|
var hasPendingBeds = oldBed.GuaDanOrderGuest.Any(g => !g.IsDeleted &&
|
||||||
|
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 预约中
|
||||||
|
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN));
|
||||||
|
if (hasPendingBeds)
|
||||||
|
{
|
||||||
|
return Content(HttpStatusCode.BadRequest, new
|
||||||
|
{
|
||||||
|
code = "BED_IS_USED",
|
||||||
|
message = "該床位正在掛單中,請先處理"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
oldBed.StatusCode = bed.StatusCode;
|
oldBed.StatusCode = bed.StatusCode;
|
||||||
oldBed.IsActive = bed.IsActive;
|
oldBed.IsActive = bed.IsActive;
|
||||||
oldBed.Name = bed.Name;
|
oldBed.Name = bed.Name;
|
||||||
@@ -158,17 +201,37 @@ public class regionRoomBedController : ApiController
|
|||||||
}
|
}
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("api/region/bed/delete")]
|
[Route("api/region/bed/delete")]
|
||||||
public IHttpActionResult delete([FromUri] Guid uuid)
|
public IHttpActionResult Delete([FromUri] Guid uuid)
|
||||||
{
|
{
|
||||||
var bed = _db.RegionRoomBed.Find(uuid);
|
var bed = _db.RegionRoomBed.Find(uuid);
|
||||||
if (bed == null)
|
if (bed == null)
|
||||||
{
|
{
|
||||||
return BadRequest("未找到床位");
|
return BadRequest("未找到床位");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_db.RegionRoomBed.Remove(bed);
|
_db.RegionRoomBed.Remove(bed);
|
||||||
_db.SaveChanges();
|
_db.SaveChanges();
|
||||||
return Ok(new { message = "刪除成功" });
|
return Ok(new { message = "刪除成功" });
|
||||||
}
|
}
|
||||||
|
catch (System.Data.Entity.Infrastructure.DbUpdateException ex)
|
||||||
|
{
|
||||||
|
// 判斷是否為外鍵關聯錯誤
|
||||||
|
if (ex.InnerException?.InnerException is System.Data.SqlClient.SqlException sqlEx &&
|
||||||
|
(sqlEx.Number == 547)) // 547 = SQL Server 外鍵違反錯誤碼
|
||||||
|
{
|
||||||
|
return BadRequest("刪除失敗:該床位已被使用或存在關聯資料,無法刪除。");
|
||||||
|
}
|
||||||
|
|
||||||
|
return InternalServerError(ex); // 其他資料庫錯誤
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return InternalServerError(ex); // 其他未預期錯誤
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("api/region/bed/getavailablebedcountbytime")]
|
[Route("api/region/bed/getavailablebedcountbytime")]
|
||||||
@@ -192,6 +255,7 @@ public class regionRoomBedController : ApiController
|
|||||||
|
|
||||||
// 可用床位 = 所有床位 - 忙碌床位
|
// 可用床位 = 所有床位 - 忙碌床位
|
||||||
var availableBeds = _db.RegionRoomBed
|
var availableBeds = _db.RegionRoomBed
|
||||||
|
.Where(b => b.IsActive)
|
||||||
.Where(b => !busyBedUuids.Contains(b.Uuid));
|
.Where(b => !busyBedUuids.Contains(b.Uuid));
|
||||||
|
|
||||||
var result = await availableBeds
|
var result = await availableBeds
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Data.Entity;
|
using System.Data.Entity;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.ServiceModel.Channels;
|
using System.ServiceModel.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
@@ -99,6 +100,23 @@ public class regionRoomController : ApiController
|
|||||||
{
|
{
|
||||||
return BadRequest("請輸入床位數量");
|
return BadRequest("請輸入床位數量");
|
||||||
}
|
}
|
||||||
|
if (room.IsActive == false)
|
||||||
|
{
|
||||||
|
var hasPendingBeds = oldRoom.RegionRoomBed
|
||||||
|
.SelectMany(b => b.GuaDanOrderGuest)
|
||||||
|
.Any(g => !g.IsDeleted &&
|
||||||
|
(g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_BOOKED || // 预约中
|
||||||
|
g.RegionRoomBedStatus.Code == GuaDanOrderGuest.STATUS_CHECKED_IN)); // 已入住
|
||||||
|
if (hasPendingBeds)
|
||||||
|
{
|
||||||
|
return Content(HttpStatusCode.BadRequest, new
|
||||||
|
{
|
||||||
|
code = "BED_IS_USED",
|
||||||
|
message = "該房间有床位正在掛單中,請先處理"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oldRoom.Name = room.Name;
|
oldRoom.Name = room.Name;
|
||||||
oldRoom.BedCount = room.BedCount;
|
oldRoom.BedCount = room.BedCount;
|
||||||
oldRoom.Gender = room.Gender;
|
oldRoom.Gender = room.Gender;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
:server-items-length="activity_statistics.totalItems"
|
:server-items-length="activity_statistics.totalItems"
|
||||||
:items="activity_statistics.items">
|
:items="activity_statistics.items">
|
||||||
<template #item.detail_btn="{item}">
|
<template #item.detail_btn="{item}">
|
||||||
<a :href="'activity.aspx?num='+item.id" class="btn btn-outline-secondary btn-sm" target="_blank">詳細統計</a>
|
<a :href="'statistics.aspx?num='+item.id + '&activity_name=' + encodeURIComponent(item.activity_name)" class="btn btn-outline-secondary btn-sm" target="_blank">詳細統計</a>
|
||||||
</template>
|
</template>
|
||||||
<template #item.duetime="{item}">
|
<template #item.duetime="{item}">
|
||||||
{{item.startdate|timeString("YYYY/MM/DD")}}-{{item.enddate|timeString("YYYY/MM/DD")}}
|
{{item.startdate|timeString("YYYY/MM/DD")}}-{{item.enddate|timeString("YYYY/MM/DD")}}
|
||||||
|
|||||||
186
web/admin/activity/statistics/statistics.aspx
Normal file
186
web/admin/activity/statistics/statistics.aspx
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="statistics.aspx.cs" Inherits="admin_activity_statistics_statistics" %>
|
||||||
|
|
||||||
|
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
|
||||||
|
<div class="ms-5">
|
||||||
|
<span>
|
||||||
|
活動名稱: {{ activity_name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="me-5">
|
||||||
|
<button class="btn btn-primary" type="button">匯出表格資料</button>
|
||||||
|
</div>
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
|
||||||
|
<div id="content" class="container-fluid">
|
||||||
|
<div class="card shadow-sm my-2" id="sec2">
|
||||||
|
<div class="card-header py-0">
|
||||||
|
<nav class="navbar py-0">
|
||||||
|
<div class="nav nav-tabs">
|
||||||
|
<button class="nav-link active" id="sec2-tab1" data-bs-toggle="tab" data-bs-target="#sec2-page1"
|
||||||
|
type="button" role="tab" aria-controls="home" aria-selected="true">
|
||||||
|
活動統計總表</button>
|
||||||
|
<button class="nav-link" id="sec2-tab2" data-bs-toggle="tab" data-bs-target="#sec2-page2"
|
||||||
|
type="button" role="tab" aria-controls="profile" aria-selected="false">
|
||||||
|
活動統計數量明細 </button>
|
||||||
|
<button class="nav-link" id="sec2-tab3" data-bs-toggle="tab" data-bs-target="#sec2-page3"
|
||||||
|
type="button" role="tab" aria-controls="profile" aria-selected="false">
|
||||||
|
活動統計報名人明細 </button>
|
||||||
|
<button class="nav-link" id="sec2-tab4" data-bs-toggle="tab" data-bs-target="#sec2-page4"
|
||||||
|
type="button" role="tab" aria-controls="profile" aria-selected="false">
|
||||||
|
活動牌位明細</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active noedit p-4" id="sec2-page1" role="tabpanel" aria-labelledby="sec2-tab1">
|
||||||
|
<h5 class="mb-4"> {{ activity_name }}(截至 {{summaryStats?.reportDate }})</h5>
|
||||||
|
<!-- 基本資訊 + 功德金 統計 -->
|
||||||
|
<table class="table table-bordered table-sm w-auto mb-4">
|
||||||
|
<tbody>
|
||||||
|
<!-- 基本資訊 -->
|
||||||
|
<tr class="table-primary">
|
||||||
|
<th colspan="4">基本資訊</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>類別</th>
|
||||||
|
<td>報名人數</td>
|
||||||
|
<td>男眾人數</td>
|
||||||
|
<td>女眾人數</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>數量</th>
|
||||||
|
<td>{{ summaryStats?.totalApplicants }}</td>
|
||||||
|
<td>{{ summaryStats?.maleApplicants }}</td>
|
||||||
|
<td>{{ summaryStats?.femaleApplicants }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table table-bordered table-sm w-auto mb-4">
|
||||||
|
<tbody>
|
||||||
|
<!-- 功德金 -->
|
||||||
|
<tr class="table-primary">
|
||||||
|
<th colspan="4">功德金統計</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>類別</th>
|
||||||
|
<td>總功德金</td>
|
||||||
|
<td>已收</td>
|
||||||
|
<td>未收</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>金額</th>
|
||||||
|
<td>{{ summaryStats?.donation.total }}</td>
|
||||||
|
<td>{{ summaryStats?.donation.received }}</td>
|
||||||
|
<td>{{ summaryStats?.donation.unreceived }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<!-- 功德項目 -->
|
||||||
|
<table class="table table-bordered table-sm w-auto mb-4">
|
||||||
|
<tbody>
|
||||||
|
<tr class="table-primary">
|
||||||
|
<th colspan="100%">功德項目</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>功德項目</th>
|
||||||
|
<td v-for="(item, index) in summaryStats?.items" :key="'name-' + index">
|
||||||
|
{{ item.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>人數</th>
|
||||||
|
<td v-for="(item, index) in summaryStats?.items" :key="'count-' + index">
|
||||||
|
{{ item.count }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 牌位數量 -->
|
||||||
|
<table class="table table-bordered table-sm w-auto">
|
||||||
|
<tbody>
|
||||||
|
<tr class="table-primary">
|
||||||
|
<th colspan="100%">牌位數量</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>牌位名稱</th>
|
||||||
|
<td v-for="(plaque, index) in summaryStats?.plaques" :key="'plaque-name-' + index">
|
||||||
|
{{ plaque.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>數量</th>
|
||||||
|
<td v-for="(plaque, index) in summaryStats?.plaques" :key="'plaque-count-' + index">
|
||||||
|
{{ plaque.count }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="sec2-page2" role="tabpanel" aria-labelledby="sec2-tab2">
|
||||||
|
sec2-page2
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="sec2-page3" role="tabpanel" aria-labelledby="sec2-tab3">
|
||||||
|
sec2-page3
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="sec2-page4" role="tabpanel" aria-labelledby="sec2-tab4">
|
||||||
|
sec2-page4
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
|
||||||
|
<script>
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
vuetify: new Vuetify(vuetify_options),
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activity_num: '<%= Request["num"] %>',
|
||||||
|
activity_name: '<%= Request["activity_name"] %>',
|
||||||
|
summaryStats: null, //匯總統計(總覽)
|
||||||
|
quantityDetails: [], // 數量明細(分類數量)
|
||||||
|
applicantList: [], // 報名人清單
|
||||||
|
plaqueList: [], //牌位清單
|
||||||
|
loadingSummary: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchSummaryStats() {
|
||||||
|
//獲取匯總明細
|
||||||
|
this.loadingSummary = true;
|
||||||
|
|
||||||
|
axios.get(HTTP_HOST + 'api/activity/statistics/summary',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
activity_num: this.activity_num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
.then(res => {
|
||||||
|
this.summaryStats = res.data;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loadingSummary = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchSummaryStats(); // 頁面載入時就獲取
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</asp:Content>
|
||||||
|
|
||||||
14
web/admin/activity/statistics/statistics.aspx.cs
Normal file
14
web/admin/activity/statistics/statistics.aspx.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.UI;
|
||||||
|
using System.Web.UI.WebControls;
|
||||||
|
|
||||||
|
public partial class admin_activity_statistics_statistics : MyWeb.config
|
||||||
|
{
|
||||||
|
protected void Page_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,6 +94,10 @@
|
|||||||
<template v-slot:item.name="{item}">
|
<template v-slot:item.name="{item}">
|
||||||
{{item.follower?.u_name}}
|
{{item.follower?.u_name}}
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:item.sex="{item}">
|
||||||
|
{{item.follower?.sex}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #item.actions="{ item }">
|
<template #item.actions="{ item }">
|
||||||
<div>
|
<div>
|
||||||
<!-- 取消預訂 -->
|
<!-- 取消預訂 -->
|
||||||
@@ -325,13 +329,13 @@
|
|||||||
<div v-for="bed in region_modal.currentSelectBeds" :key="bed.uuid" @click="selectBed(bed)" style="padding: 8px; border: 1px solid #d9d9d9; cursor: pointer; max-height: 250px;" :style="{
|
<div v-for="bed in region_modal.currentSelectBeds" :key="bed.uuid" @click="selectBed(bed)" style="padding: 8px; border: 1px solid #d9d9d9; cursor: pointer; max-height: 250px;" :style="{
|
||||||
backgroundColor: region_modal.currentSelectBed?.uuid === bed.uuid
|
backgroundColor: region_modal.currentSelectBed?.uuid === bed.uuid
|
||||||
? '#bae7ff' // 當前選中
|
? '#bae7ff' // 當前選中
|
||||||
: (bed.canUsed ? '#f6ffed' : '#fff1f0'), // 可用綠色,不可用紅色
|
: ((bed.canUsed && bed.isActive && !bed.bedIsStop) ? '#f6ffed' : '#fff1f0'), // 可用綠色,不可用紅色
|
||||||
color: bed.canUsed ? 'black' : '#999', // 不可用時灰色文字
|
color: (bed.canUsed && bed.isActive && !bed.bedIsStop) ? 'black' : '#999', // 不可用時灰色文字
|
||||||
pointerEvents: bed.canUsed ? 'auto' : 'none' // 不可用時無法點擊
|
pointerEvents: (bed.canUsed && bed.isActive && !bed.bedIsStop) ? 'auto' : 'none' // 不可用時無法點擊
|
||||||
}">
|
}">
|
||||||
<div style="font-weight: 500;">{{ bed.name }}</div>
|
<div style="font-weight: 500;">{{ bed.name }}</div>
|
||||||
<div style="margin-top: 4px; font-size: 12px;">
|
<div style="margin-top: 4px; font-size: 12px;">
|
||||||
{{ bed.canUsed ? '可用' : '不可用' }}
|
{{ (!bed.isActive || bed.bedIsStop) ? '停用' : (bed.canUsed ? '可用' : '不可用') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -884,6 +888,10 @@
|
|||||||
text: '姓名',
|
text: '姓名',
|
||||||
value: 'name'
|
value: 'name'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '性別',
|
||||||
|
value: 'sex'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: '掛單開始時間',
|
text: '掛單開始時間',
|
||||||
value: 'checkinat'
|
value: 'checkinat'
|
||||||
@@ -892,10 +900,6 @@
|
|||||||
text: '掛單結束時間',
|
text: '掛單結束時間',
|
||||||
value: 'checkoutat'
|
value: 'checkoutat'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: '房間',
|
|
||||||
value: 'roomName'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: '床位',
|
text: '床位',
|
||||||
value: 'bedName'
|
value: 'bedName'
|
||||||
|
|||||||
@@ -51,6 +51,9 @@
|
|||||||
<template #item.created_at="{item}">
|
<template #item.created_at="{item}">
|
||||||
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
|
{{item.created_at | timeString('YYYY/MM/DD HH:mm')}}
|
||||||
</template>
|
</template>
|
||||||
|
<template #item.activity="{item}">
|
||||||
|
{{item.activity?.subject}}
|
||||||
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row class="align-baseline" wrap="false">
|
<v-row class="align-baseline" wrap="false">
|
||||||
@@ -114,7 +117,7 @@
|
|||||||
{ text: '掛單人數', value: 'guest_count' },
|
{ text: '掛單人數', value: 'guest_count' },
|
||||||
{ text: '狀態', value: 'statusName', align: 'center' },
|
{ text: '狀態', value: 'statusName', align: 'center' },
|
||||||
{ text: '建立時間', value: 'created_at', align: 'center' },
|
{ text: '建立時間', value: 'created_at', align: 'center' },
|
||||||
{ text: '備註', value: 'notes', align: 'center' },
|
{ text: '關聯活動', value: 'activity', align: 'center' },
|
||||||
{ text: '操作', value: 'actions', align: 'center' }
|
{ text: '操作', value: 'actions', align: 'center' }
|
||||||
],
|
],
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
1401
web/admin/pivot/README-pivot-01.md
Normal file
1401
web/admin/pivot/README-pivot-01.md
Normal file
File diff suppressed because it is too large
Load Diff
768
web/admin/pivot/README.md
Normal file
768
web/admin/pivot/README.md
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
# Pivot Module 執行計劃
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
建立多頁籤數據透視查詢模組,參考 transfer 模組的設計架構,使用相同的技術棧與 UI/UX 模式,實現法會報名資料的多維度分析與展示。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技術架構
|
||||||
|
|
||||||
|
### 前端技術
|
||||||
|
- **框架**: Vue.js 2.x + Vuetify 2.x(與 transfer 模組一致)
|
||||||
|
- **表格元件**: v-data-table(Vuetify 內建表格元件)
|
||||||
|
- **頁籤元件**: v-tabs / v-tab / v-tab-item(Vuetify 頁籤)
|
||||||
|
- **UI 框架**: Bootstrap 5 + Bootstrap Icons(MasterPage)
|
||||||
|
- **樣式**: 與 transfer 模組保持一致的視覺風格
|
||||||
|
|
||||||
|
### 後端 API
|
||||||
|
- **框架**: ASP.NET Web API(C#)
|
||||||
|
- **ORM**: Entity Framework + LINQ
|
||||||
|
- **控制器**: `App_Code/api/pivotController.cs`(已建立)
|
||||||
|
- **資料庫視圖**: `報名明細查詢`(SQL View)
|
||||||
|
|
||||||
|
### 資料流架構(重要)★
|
||||||
|
```
|
||||||
|
查詢流程:
|
||||||
|
┌─────────────┐
|
||||||
|
│ Tab 1 │ → 選擇法會 → API 查詢一次 →
|
||||||
|
│ 查詢條件 │ (完整資料集)
|
||||||
|
└─────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Vue Data (存於前端 this.rawData) │
|
||||||
|
│ - 完整報名明細 │
|
||||||
|
│ - 一次性載入,不重複查詢 │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌──────┬──────┬──────┬──────┬──────┐
|
||||||
|
│Tab 2 │Tab 3 │Tab 4 │Tab 5 │Tab 6 │
|
||||||
|
│明細 │信眾 │收入 │趨勢 │對比 │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ 純前端計算 / 過濾 / 分組 / 統計 │
|
||||||
|
│ 使用 computed / methods / filters │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**優點**:
|
||||||
|
1. ✅ **效能優化**: API 只查詢一次,減少伺服器負載
|
||||||
|
2. ✅ **即時響應**: 切換頁籤無延遲,使用者體驗佳
|
||||||
|
3. ✅ **離線分析**: 資料載入後可離線操作(過濾、排序、統計)
|
||||||
|
4. ✅ **減少流量**: 不重複傳輸相同資料
|
||||||
|
5. ✅ **一致性**: 所有頁籤基於同一份資料,確保一致性
|
||||||
|
|
||||||
|
**技術實現**:
|
||||||
|
- `this.rawData`: 原始完整資料(Tab 1 查詢後存入)
|
||||||
|
- `computed properties`: 各頁籤的資料來源(動態計算)
|
||||||
|
- `methods`: 過濾、分組、統計邏輯
|
||||||
|
- `watch`: 監聽過濾條件變化
|
||||||
|
|
||||||
|
### 元件規格
|
||||||
|
1. **日期選擇器**: `v-date-picker` 或 `<input type="date">`
|
||||||
|
2. **下拉選單**: `v-select`(年份、月份、法會選擇)
|
||||||
|
3. **資料表格**: `v-data-table`(分頁、排序、過濾)
|
||||||
|
4. **頁籤切換**: `v-tabs`(Tab 1~N)
|
||||||
|
5. **按鈕群組**: `v-btn`(查詢、匯出、重設)
|
||||||
|
6. **載入狀態**: `:loading="loading"`
|
||||||
|
7. **視覺標記**: Bootstrap Badge(橙、藍、綠、紫色標籤)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 頁籤設計
|
||||||
|
|
||||||
|
### Tab 1: 查詢條件設定
|
||||||
|
**功能目標**: 提供查詢條件,篩選法會並選擇目標法會
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [查詢條件] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 時間範圍: [年份 ▼] [月份 ▼] [查詢法會] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 法會清單: |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 序號 | 法會名稱 | 開始日期 | 結束日期 | 報名人數 | 操作 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 1 | 2025春季法會 | 2025-03-01 | 2025-03-15 | 120 | [選擇] | |
|
||||||
|
| | 2 | 2025夏季法會 | 2025-06-01 | 2025-06-20 | 85 | [選擇] | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查詢流程
|
||||||
|
1. 選擇年份(2020~2025)或月份(1~12)
|
||||||
|
2. 點擊「查詢法會」按鈕
|
||||||
|
3. API 回傳該期間的法會清單(`api/pivot/activity_stats`)
|
||||||
|
4. 表格呈現法會清單(法會名稱、日期、統計)
|
||||||
|
5. 點擊「選擇」按鈕,載入該法會的詳細資料
|
||||||
|
6. 自動切換到 Tab 2(詳細資料頁籤)
|
||||||
|
|
||||||
|
#### API 整合
|
||||||
|
- **端點**: `GET api/pivot/activity_stats?startDate={start}&endDate={end}`
|
||||||
|
- **回傳**: 法會清單(含報名統計)
|
||||||
|
|
||||||
|
#### 資料查詢策略
|
||||||
|
```javascript
|
||||||
|
// 選擇法會後,一次性載入完整資料
|
||||||
|
selectActivity(item) {
|
||||||
|
this.loading = true;
|
||||||
|
this.selectedActivity = item;
|
||||||
|
|
||||||
|
// 一次性查詢完整報名明細(不分頁)
|
||||||
|
axios.get('/api/pivot/registration_details', {
|
||||||
|
params: {
|
||||||
|
activityNum: item.法會ID,
|
||||||
|
pageSize: 9999 // 取得全部資料
|
||||||
|
}
|
||||||
|
}).then(response => {
|
||||||
|
// 存入原始資料(供所有頁籤使用)
|
||||||
|
this.rawData = response.data.data.list;
|
||||||
|
|
||||||
|
// 自動切換到 Tab 2
|
||||||
|
this.activeTab = 1;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 元件範例
|
||||||
|
```html
|
||||||
|
<v-select
|
||||||
|
:items="yearOptions"
|
||||||
|
v-model="selectedYear"
|
||||||
|
label="年份"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
></v-select>
|
||||||
|
<v-select
|
||||||
|
:items="monthOptions"
|
||||||
|
v-model="selectedMonth"
|
||||||
|
label="月份"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
></v-select>
|
||||||
|
<v-btn color="primary" @click="loadActivities">查詢法會</v-btn>
|
||||||
|
|
||||||
|
<v-data-table
|
||||||
|
:headers="activityHeaders"
|
||||||
|
:items="activities"
|
||||||
|
:loading="loading"
|
||||||
|
item-key="法會ID"
|
||||||
|
>
|
||||||
|
<template v-slot:item.actions="{ item }">
|
||||||
|
<v-btn small color="success" @click="selectActivity(item)">選擇</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tab 2: 報名明細資料
|
||||||
|
**功能目標**: 完整呈現該場法會的所有報名明細
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [報名明細] 法會: 2025春季法會 (2025-03-01 ~ 2025-03-15) |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 過濾: [信眾姓名] [功德類型 ▼] [狀態 ▼] [查詢] [匯出Excel] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 報名編號 | 報名日期 | 信眾姓名 | 功德名稱 | 數量 | 金額 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 20250301001 | 2025-03-01 | 張三 | 點燈 | 1 | 500 | |
|
||||||
|
| | 20250301002 | 2025-03-01 | 李四 | 安太歲 | 1 | 300 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| 第 1 頁,共 10 頁(共 200 筆) |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 功能特性
|
||||||
|
1. **欄位顯示**: 報名編號、報名日期、信眾姓名、功德名稱、數量、金額、已收、未收
|
||||||
|
2. **過濾條件**: 信眾姓名(模糊搜尋)、功德類型(下拉選單)、功德主(是/否)
|
||||||
|
3. **排序**: 可依任意欄位排序(升序/降序)
|
||||||
|
4. **分頁**: 預設每頁 50 筆,可調整(10/20/50/100)
|
||||||
|
5. **匯出**: 匯出 Excel/CSV
|
||||||
|
|
||||||
|
#### 欄位色彩標記(參考 Excel 視圖)
|
||||||
|
- **橙色(法會資料)**: 法會ID、法會名稱、開始日期、結束日期
|
||||||
|
- **藍色(信眾資料)**: 信眾編號、信眾姓名
|
||||||
|
- **綠色(功德資訊)**: 報名編號、報名日期、功德主、功德類型、功德名稱
|
||||||
|
- **紫色(計算欄位)**: 數量、金額、已收、未收
|
||||||
|
|
||||||
|
使用 Bootstrap Badge 或背景色區隔:
|
||||||
|
```html
|
||||||
|
<span class="badge bg-warning text-dark">法會</span>
|
||||||
|
<span class="badge bg-info">信眾</span>
|
||||||
|
<span class="badge bg-success">功德</span>
|
||||||
|
<span class="badge bg-secondary">計算</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 資料來源(前端計算)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// Tab 2: 報名明細(前端分頁、過濾)
|
||||||
|
filteredRegistrations() {
|
||||||
|
let data = this.rawData;
|
||||||
|
|
||||||
|
// 過濾:信眾姓名
|
||||||
|
if (this.filter.followerName) {
|
||||||
|
data = data.filter(x => x.信眾姓名.includes(this.filter.followerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 過濾:功德類型
|
||||||
|
if (this.filter.itemKind) {
|
||||||
|
data = data.filter(x => x.功德類型 === this.filter.itemKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 過濾:功德主
|
||||||
|
if (this.filter.isParent !== null) {
|
||||||
|
data = data.filter(x => x.功德主 === (this.filter.isParent ? '是' : '否'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 前端分頁
|
||||||
|
paginatedRegistrations() {
|
||||||
|
const start = (this.currentPage - 1) * this.pageSize;
|
||||||
|
const end = start + this.pageSize;
|
||||||
|
return this.filteredRegistrations.slice(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 元件範例
|
||||||
|
```html
|
||||||
|
<v-data-table
|
||||||
|
:headers="detailHeaders"
|
||||||
|
:items="registrations"
|
||||||
|
:loading="loading"
|
||||||
|
:server-items-length="totalCount"
|
||||||
|
:options.sync="options"
|
||||||
|
item-key="報名編號"
|
||||||
|
class="elevation-1"
|
||||||
|
>
|
||||||
|
<template v-slot:item.信眾姓名="{ item }">
|
||||||
|
<span class="badge bg-info me-1">信</span>{{ item.信眾姓名 }}
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.金額="{ item }">
|
||||||
|
<span class="badge bg-secondary me-1">計</span>{{ item.金額 | currency }}
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tab 3: 信眾參與分析
|
||||||
|
**功能目標**: 統計信眾的參與情況(參與次數、金額、最近參與)
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [信眾參與分析] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 過濾: [信眾編號] [參與次數 ≥] [總金額 ≥] [查詢] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 信眾編號 | 姓名 | 參與次數 | 總金額 | 最近參與日期 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | F001 | 張三 | 5 | 2500 | 2025-03-01 | |
|
||||||
|
| | F002 | 李四 | 3 | 1500 | 2025-02-15 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 功能特性
|
||||||
|
1. **統計欄位**: 參與次數、總金額、平均金額、最近參與日期
|
||||||
|
2. **過濾條件**: 信眾編號、參與次數閾值、總金額閾值
|
||||||
|
3. **排序**: 預設依參與次數降序
|
||||||
|
4. **分頁**: 預設每頁 50 筆
|
||||||
|
|
||||||
|
#### 資料來源(前端計算)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// Tab 3: 信眾參與分析(從 rawData 計算)
|
||||||
|
followerAnalysis() {
|
||||||
|
const followerMap = {};
|
||||||
|
|
||||||
|
this.rawData.forEach(item => {
|
||||||
|
const fNum = item.信眾編號;
|
||||||
|
if (!followerMap[fNum]) {
|
||||||
|
followerMap[fNum] = {
|
||||||
|
信眾編號: fNum,
|
||||||
|
姓名: item.信眾姓名,
|
||||||
|
參與次數: 0,
|
||||||
|
總金額: 0,
|
||||||
|
最近參與日期: item.報名日期
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
followerMap[fNum].參與次數++;
|
||||||
|
followerMap[fNum].總金額 += (item.金額 * item.數量);
|
||||||
|
|
||||||
|
// 更新最近參與日期
|
||||||
|
if (new Date(item.報名日期) > new Date(followerMap[fNum].最近參與日期)) {
|
||||||
|
followerMap[fNum].最近參與日期 = item.報名日期;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 轉換為陣列,並計算平均金額
|
||||||
|
return Object.values(followerMap).map(f => ({
|
||||||
|
...f,
|
||||||
|
平均金額: Math.round(f.總金額 / f.參與次數)
|
||||||
|
})).sort((a, b) => b.參與次數 - a.參與次數); // 依參與次數降序
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tab 4: 收入統計分析
|
||||||
|
**功能目標**: 依時間、法會、功德類型統計收入
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [收入統計分析] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 分組方式: ( ) 月份 ( ) 年度 (•) 法會 ( ) 功德類型 |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 分組名稱 | 報名人數 | 總金額 | 已收 | 未收 | 收款率 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 2025春季法會 | 120 | 60000 | 50000 | 10000 | 83.3% | |
|
||||||
|
| | 2025夏季法會 | 85 | 45000 | 40000 | 5000 | 88.9% | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 功能特性
|
||||||
|
1. **分組方式**: 月份、年度、法會、功德類型
|
||||||
|
2. **統計欄位**: 報名人數、總金額、已收、未收、收款率
|
||||||
|
3. **圖表呈現**: 可加入 CanvasJS 長條圖/圓餅圖(選配)
|
||||||
|
4. **匯出**: 支援 Excel/CSV
|
||||||
|
|
||||||
|
#### 資料來源(前端計算)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// Tab 4: 收入統計分析(從 rawData 計算)
|
||||||
|
incomeStats() {
|
||||||
|
const statsMap = {};
|
||||||
|
const groupBy = this.groupBy; // 'monthly', 'yearly', 'activity', 'itemKind'
|
||||||
|
|
||||||
|
this.rawData.forEach(item => {
|
||||||
|
let key;
|
||||||
|
switch(groupBy) {
|
||||||
|
case 'monthly':
|
||||||
|
key = item.報名日期.substring(0, 7); // YYYY-MM
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
key = item.報名日期.substring(0, 4); // YYYY
|
||||||
|
break;
|
||||||
|
case 'activity':
|
||||||
|
key = item.法會名稱;
|
||||||
|
break;
|
||||||
|
case 'itemKind':
|
||||||
|
key = item.功德類型;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!statsMap[key]) {
|
||||||
|
statsMap[key] = {
|
||||||
|
分組名稱: key,
|
||||||
|
報名人數: 0,
|
||||||
|
總金額: 0,
|
||||||
|
已收: 0,
|
||||||
|
未收: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
statsMap[key].報名人數++;
|
||||||
|
const amount = item.金額 * item.數量;
|
||||||
|
statsMap[key].總金額 += amount;
|
||||||
|
statsMap[key].已收 += item.已收 || 0;
|
||||||
|
statsMap[key].未收 += item.未收 || amount;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 計算收款率
|
||||||
|
return Object.values(statsMap).map(s => ({
|
||||||
|
...s,
|
||||||
|
收款率: s.總金額 > 0 ? ((s.已收 / s.總金額) * 100).toFixed(1) + '%' : '0%'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tab 5: 趨勢分析
|
||||||
|
**功能目標**: 顯示時間序列趨勢(收入、參與人數、法會數量)
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [趨勢分析] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 指標: ( ) 收入 (•) 參與人數 ( ) 法會數量 |
|
||||||
|
| 時間間隔: ( ) 月份 (•) 季度 ( ) 年度 |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| [折線圖] |
|
||||||
|
| |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 時間 | 數值 | 成長率 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 2025-01 | 120 | +10% | |
|
||||||
|
| | 2025-02 | 132 | +10% | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 功能特性
|
||||||
|
1. **指標選擇**: 收入、參與人數、法會數量
|
||||||
|
2. **時間間隔**: 月份、季度、年度
|
||||||
|
3. **成長率計算**: 較前期成長率
|
||||||
|
4. **圖表**: CanvasJS 折線圖(選配)
|
||||||
|
|
||||||
|
#### 資料來源(前端計算)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// Tab 5: 趨勢分析(從 rawData 計算)
|
||||||
|
trendAnalysis() {
|
||||||
|
const metric = this.trendMetric; // 'income', 'followers', 'count'
|
||||||
|
const interval = this.trendInterval; // 'monthly', 'quarterly', 'yearly'
|
||||||
|
const trendMap = {};
|
||||||
|
|
||||||
|
this.rawData.forEach(item => {
|
||||||
|
let key;
|
||||||
|
switch(interval) {
|
||||||
|
case 'monthly':
|
||||||
|
key = item.報名日期.substring(0, 7); // YYYY-MM
|
||||||
|
break;
|
||||||
|
case 'quarterly':
|
||||||
|
const month = parseInt(item.報名日期.substring(5, 7));
|
||||||
|
const quarter = Math.ceil(month / 3);
|
||||||
|
key = `${item.報名日期.substring(0, 4)}-Q${quarter}`;
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
key = item.報名日期.substring(0, 4); // YYYY
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trendMap[key]) {
|
||||||
|
trendMap[key] = { 時間: key, 收入: 0, 人數: 0, 次數: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
trendMap[key].收入 += (item.金額 * item.數量);
|
||||||
|
trendMap[key].人數++; // 簡化計算,實際可用 Set 去重
|
||||||
|
trendMap[key].次數++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 轉換為陣列並排序
|
||||||
|
const result = Object.values(trendMap).sort((a, b) => a.時間.localeCompare(b.時間));
|
||||||
|
|
||||||
|
// 計算成長率
|
||||||
|
return result.map((item, index) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return { ...item, 數值: item[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'], 成長率: '-' };
|
||||||
|
}
|
||||||
|
const prev = result[index - 1];
|
||||||
|
const currentValue = item[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'];
|
||||||
|
const prevValue = prev[metric === 'income' ? '收入' : metric === 'followers' ? '人數' : '次數'];
|
||||||
|
const growthRate = prevValue > 0 ? (((currentValue - prevValue) / prevValue) * 100).toFixed(1) : '0';
|
||||||
|
return { ...item, 數值: currentValue, 成長率: growthRate + '%' };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tab 6: 對比分析(選配)
|
||||||
|
**功能目標**: 不同時期、法會的對比分析
|
||||||
|
|
||||||
|
#### UI 布局
|
||||||
|
```
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
| [對比分析] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| 對比類型: (•) 法會對比 ( ) 年度對比 |
|
||||||
|
| 期間1: [2025春季法會 ▼] 期間2: [2024春季法會 ▼] |
|
||||||
|
|--------------------------------------------------------------|
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 指標 | 期間1 | 期間2 | 差異 | 差異率 | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| | 報名人數 | 120 | 100 | +20 | +20% | |
|
||||||
|
| | 總金額 | 60000 | 50000 | +10000 | +20% | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
+--------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 資料來源(前端計算)
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// Tab 6: 對比分析(從 rawData 計算)
|
||||||
|
// 注意:對比分析可能需要跨法會資料,如需要可額外查詢
|
||||||
|
comparativeAnalysis() {
|
||||||
|
// 如果只在單一法會內對比(如月份對比),可用 rawData
|
||||||
|
// 如果需要跨法會對比,建議另外查詢或在 Tab 1 時一併載入多場法會資料
|
||||||
|
const period1Data = this.rawData.filter(x => {
|
||||||
|
// 依 period1 條件過濾
|
||||||
|
return this.isPeriod1(x);
|
||||||
|
});
|
||||||
|
|
||||||
|
const period2Data = this.rawData.filter(x => {
|
||||||
|
// 依 period2 條件過濾
|
||||||
|
return this.isPeriod2(x);
|
||||||
|
});
|
||||||
|
|
||||||
|
const stats1 = this.calculateStats(period1Data);
|
||||||
|
const stats2 = this.calculateStats(period2Data);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ 指標: '報名人數', 期間1: stats1.count, 期間2: stats2.count, 差異: stats1.count - stats2.count },
|
||||||
|
{ 指標: '總金額', 期間1: stats1.amount, 期間2: stats2.amount, 差異: stats1.amount - stats2.amount },
|
||||||
|
// ... 其他指標
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 視覺設計規範
|
||||||
|
|
||||||
|
### 色彩系統(參考 transfer)
|
||||||
|
- **主色調**: Bootstrap 5 預設配色
|
||||||
|
- **成功/確認**: `bg-success` / `text-success`
|
||||||
|
- **警告**: `bg-warning` / `text-warning`
|
||||||
|
- **危險/錯誤**: `bg-danger` / `text-danger`
|
||||||
|
- **資訊**: `bg-info` / `text-info`
|
||||||
|
- **次要**: `bg-secondary` / `text-muted`
|
||||||
|
|
||||||
|
### 欄位色彩標記(對應 Excel)
|
||||||
|
```html
|
||||||
|
<!-- 橙色:法會資料 -->
|
||||||
|
<span class="badge bg-warning text-dark">法</span>
|
||||||
|
|
||||||
|
<!-- 藍色:信眾資料 -->
|
||||||
|
<span class="badge bg-info">信</span>
|
||||||
|
|
||||||
|
<!-- 綠色:功德資訊 -->
|
||||||
|
<span class="badge bg-success">功德</span>
|
||||||
|
|
||||||
|
<!-- 紫色:計算欄位 -->
|
||||||
|
<span class="badge bg-secondary">計</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表格樣式
|
||||||
|
```css
|
||||||
|
/* 與 transfer 保持一致 */
|
||||||
|
.v-data-table th {
|
||||||
|
background-color: #f5f5f5 !important;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-data-table tbody tr:hover {
|
||||||
|
background-color: #f0f8ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 欄位色彩標記 */
|
||||||
|
.field-activity { border-left: 3px solid #ffc107; } /* 橙 */
|
||||||
|
.field-follower { border-left: 3px solid #17a2b8; } /* 藍 */
|
||||||
|
.field-merit { border-left: 3px solid #28a745; } /* 綠 */
|
||||||
|
.field-calculated { border-left: 3px solid #6c757d; } /* 紫 */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 端點總覽
|
||||||
|
|
||||||
|
| 端點 | 方法 | 功能 | 對應頁籤 | 使用時機 |
|
||||||
|
|------|------|------|----------|----------|
|
||||||
|
| `/api/pivot/activity_stats` | GET | 查詢法會清單與統計 | Tab 1 | 選擇查詢條件時 |
|
||||||
|
| `/api/pivot/registration_details` | GET | 報名明細查詢(**完整資料**) | Tab 1 | **選擇法會後一次性載入** ★ |
|
||||||
|
| `/api/pivot/registration_details_export` | GET | 報名明細匯出 | Tab 2 | 匯出 Excel 時(選配) |
|
||||||
|
| `/api/pivot/excel_data_structured` | GET | Excel 數據連接 | 外部 | Power Query/Power Pivot |
|
||||||
|
|
||||||
|
**重要說明**:
|
||||||
|
- ✅ **Tab 2~6 不呼叫 API**,全部使用前端 `computed` 計算
|
||||||
|
- ✅ `registration_details` 查詢時使用 `pageSize=9999` 取得完整資料
|
||||||
|
- ✅ 原 `follower_analysis`, `income_stats`, `trend_analysis`, `comparative_analysis` 端點**保留備用**,但前端優先使用 computed
|
||||||
|
- ✅ 如資料量過大(>5000 筆),可調整策略改用分頁查詢
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 檔案結構
|
||||||
|
|
||||||
|
```
|
||||||
|
admin/pivot/
|
||||||
|
├── index.aspx # 首頁(已完成)
|
||||||
|
├── index.aspx.cs # 首頁邏輯(已完成)
|
||||||
|
├── query.aspx # 多頁籤查詢頁面(待建立)★
|
||||||
|
├── query.aspx.cs # 查詢頁面邏輯(待建立)★
|
||||||
|
└── README.md # 本文件
|
||||||
|
|
||||||
|
App_Code/api/
|
||||||
|
└── pivotController.cs # API 控制器(已完成)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 實作步驟
|
||||||
|
|
||||||
|
### Phase 1: 建立查詢頁面骨架
|
||||||
|
1. ✅ 建立 `query.aspx`(參考 transfer/verify.aspx)
|
||||||
|
2. ✅ 建立 `query.aspx.cs`(參考 transfer/verify.aspx.cs)
|
||||||
|
3. ✅ 引入 Vue.js + Vuetify
|
||||||
|
4. ✅ 建立頁籤結構(v-tabs)
|
||||||
|
|
||||||
|
### Phase 2: 實作 Tab 1(查詢條件)
|
||||||
|
1. ✅ 年份/月份選擇器
|
||||||
|
2. ✅ 查詢法會按鈕與 API 整合(`activity_stats`)
|
||||||
|
3. ✅ 法會清單表格(v-data-table)
|
||||||
|
4. ✅ 選擇法會邏輯(一次性載入完整資料)
|
||||||
|
5. ✅ `this.rawData` 資料結構設計
|
||||||
|
|
||||||
|
### Phase 3: 實作 Tab 2(報名明細)
|
||||||
|
1. ✅ 報名明細表格(前端分頁、排序、過濾)
|
||||||
|
2. ✅ `computed: filteredRegistrations` 實作
|
||||||
|
3. ✅ 欄位色彩標記(badge)
|
||||||
|
4. ✅ 匯出功能(選配)
|
||||||
|
|
||||||
|
### Phase 4: 實作 Tab 3~6(分析頁籤)
|
||||||
|
1. ✅ 信眾參與分析(Tab 3)- `computed: followerAnalysis`
|
||||||
|
2. ✅ 收入統計分析(Tab 4)- `computed: incomeStats`
|
||||||
|
3. ✅ 趨勢分析(Tab 5)- `computed: trendAnalysis`
|
||||||
|
4. ✅ 對比分析(Tab 6,選配)- `computed: comparativeAnalysis`
|
||||||
|
|
||||||
|
### Phase 5: 優化與測試
|
||||||
|
1. ✅ UI/UX 調整(與 transfer 保持一致)
|
||||||
|
2. ✅ 效能優化(分頁、快取)
|
||||||
|
3. ✅ 瀏覽器相容性測試
|
||||||
|
4. ✅ 權限控制(ezAuthorize)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 參考範例
|
||||||
|
|
||||||
|
### 1. transfer/verify.aspx
|
||||||
|
- 多階段流程設計(程序1、程序2)
|
||||||
|
- v-data-table 表格元件使用
|
||||||
|
- 信眾選擇對話框(v-dialog)
|
||||||
|
- 狀態選擇(v-select)
|
||||||
|
|
||||||
|
### 2. transfer/verify1.aspx
|
||||||
|
- 單階段流程設計
|
||||||
|
- 簡潔的查詢與確認流程
|
||||||
|
|
||||||
|
### 3. transfer/index.aspx
|
||||||
|
- 功能入口頁面設計
|
||||||
|
- Bootstrap Icons + Badge
|
||||||
|
- 三欄式布局
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事項
|
||||||
|
|
||||||
|
1. **元件一致性**: 所有元件、樣式、命名均與 transfer 模組保持一致
|
||||||
|
2. **API 規範**: 遵循 RESTful 設計,統一回傳格式
|
||||||
|
3. **權限控制**: 所有 API 加上 `[ezAuthorize]`
|
||||||
|
4. **效能考量**:
|
||||||
|
- ✅ **前端計算策略**: Tab 1 查詢一次,Tab 2~6 使用 Vue computed 計算
|
||||||
|
- ✅ **資料量控制**: 單一法會報名明細預估 <5000 筆,適合前端處理
|
||||||
|
- ⚠️ **大資料處理**: 若單場法會 >5000 筆,可改用 API 分頁查詢
|
||||||
|
- ✅ **記憶體管理**: 切換法會時清除舊資料(`this.rawData = []`)
|
||||||
|
5. **視覺區隔**: 使用 Badge、邊框色、背景色標記欄位類型
|
||||||
|
6. **使用者體驗**: 載入狀態(loading)、錯誤提示(alert)、空資料提示
|
||||||
|
7. **前端效能優化**:
|
||||||
|
- 使用 `computed` 而非 `methods`(自動快取)
|
||||||
|
- 大型陣列操作使用 `Object.freeze()` 凍結原始資料
|
||||||
|
- v-data-table 啟用虛擬滾動(`:virtual-scroll="true"`,資料量 >1000 時)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SQL View 參考
|
||||||
|
|
||||||
|
```SQL
|
||||||
|
CREATE VIEW [dbo].[報名明細查詢]
|
||||||
|
AS
|
||||||
|
SELECT dbo.activity.num AS 法會ID, dbo.activity.subject AS 法會名稱, dbo.activity.startDate_solar AS 開始日期, dbo.activity.endDate_solar AS 結束日期,
|
||||||
|
dbo.followers.f_number AS 信眾編號, dbo.followers.u_name AS 信眾姓名, dbo.pro_order.order_no AS 報名編號, dbo.pro_order.up_time AS 報名日期,
|
||||||
|
CASE WHEN parent_num IS NOT NULL THEN '是' ELSE '否' END AS 功德主, dbo.actItem_kind.kind AS 功德類型, dbo.actItem.subject AS 功德名稱,
|
||||||
|
dbo.pro_order_detail.qty AS 數量, dbo.pro_order_detail.price AS 金額, 0 AS 已收, dbo.pro_order_detail.price * dbo.pro_order_detail.qty - 0 AS 未收
|
||||||
|
FROM dbo.pro_order_detail INNER JOIN
|
||||||
|
dbo.pro_order ON dbo.pro_order_detail.order_no = dbo.pro_order.order_no INNER JOIN
|
||||||
|
dbo.actItem ON dbo.pro_order_detail.actItem_num = dbo.actItem.num INNER JOIN
|
||||||
|
dbo.activity ON dbo.pro_order.activity_num = dbo.activity.num INNER JOIN
|
||||||
|
dbo.followers ON dbo.pro_order.f_num = dbo.followers.num INNER JOIN
|
||||||
|
dbo.actItem_kind ON dbo.actItem.kind = dbo.actItem_kind.num
|
||||||
|
GO
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 資料結構範例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Vue data 結構
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 原始完整資料(Tab 1 查詢後存入,供所有頁籤使用)
|
||||||
|
rawData: [], // Array<報名明細物件>
|
||||||
|
|
||||||
|
// 選中的法會
|
||||||
|
selectedActivity: null,
|
||||||
|
|
||||||
|
// 當前頁籤索引
|
||||||
|
activeTab: 0, // 0=Tab1, 1=Tab2, 2=Tab3...
|
||||||
|
|
||||||
|
// Tab 2 過濾條件
|
||||||
|
filter: {
|
||||||
|
followerName: '',
|
||||||
|
itemKind: null,
|
||||||
|
isParent: null
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tab 4 分組方式
|
||||||
|
groupBy: 'activity', // 'monthly', 'yearly', 'activity', 'itemKind'
|
||||||
|
|
||||||
|
// Tab 5 趨勢設定
|
||||||
|
trendMetric: 'income', // 'income', 'followers', 'count'
|
||||||
|
trendInterval: 'monthly', // 'monthly', 'quarterly', 'yearly'
|
||||||
|
|
||||||
|
// 載入狀態
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Vue Computed 效能考量
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
computed: {
|
||||||
|
// 使用 Object.freeze() 凍結大型陣列,提升效能
|
||||||
|
frozenRawData() {
|
||||||
|
return Object.freeze(this.rawData);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 各頁籤 computed 基於 frozenRawData
|
||||||
|
filteredRegistrations() {
|
||||||
|
return this.frozenRawData.filter(/* 過濾邏輯 */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 執行確認
|
||||||
|
|
||||||
|
請檢視以上計劃,確認以下事項:
|
||||||
|
|
||||||
|
1. ✅ 頁籤設計(Tab 1~6)符合需求
|
||||||
|
2. ✅ 查詢流程(年/月 → 法會清單 → 明細資料)清晰
|
||||||
|
3. ✅ 視覺區隔(色彩標記、Badge)符合 Excel 概念
|
||||||
|
4. ✅ 技術架構(Vue + Vuetify + API)與 transfer 一致
|
||||||
|
5. ✅ 功能完整性(查詢、過濾、排序、匯出)
|
||||||
|
6. ✅ **資料流架構(一次查詢 + 前端計算)**合理且高效 ★
|
||||||
|
|
||||||
|
**確認後即開始實作 Phase 1(建立查詢頁面骨架)。**
|
||||||
185
web/admin/pivot/index.aspx
Normal file
185
web/admin/pivot/index.aspx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<%@ Page Title="數據透視管理" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" EnableEventValidation="false" CodeFile="index.aspx.cs" Inherits="admin_pivot_index" %>
|
||||||
|
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" runat="Server">
|
||||||
|
<link rel="stylesheet" href="../../js/_bootstrap-icons-1.8.1/bootstrap-icons.css">
|
||||||
|
<style>
|
||||||
|
.function-icon {
|
||||||
|
font-size: 2em;
|
||||||
|
line-height: 1;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
.external-link-icon {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content3" ContentPlaceHolderID="page_nav" runat="Server">
|
||||||
|
<h2 class="mb-3">數據透視管理</h2>
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
|
||||||
|
<div id="content" class="container py-4">
|
||||||
|
<div class="row">
|
||||||
|
<!-- 第一欄:報表查詢 -->
|
||||||
|
<div class="col-lg-4 mb-4">
|
||||||
|
<h5 class="text-primary mb-3">
|
||||||
|
<i class="bi bi-graph-up"></i> 報表查詢
|
||||||
|
</h5>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="activity_report.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-bar-chart text-success me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>法會報名統計</div>
|
||||||
|
<small class="text-muted">各法會報名人數與金額統計</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-primary">管理員</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="follower_report.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-people text-info me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>信眾參與分析</div>
|
||||||
|
<small class="text-muted">信眾參與法會的詳細分析</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-info">管理員</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="income_report.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-currency-dollar text-warning me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>收入統計報表</div>
|
||||||
|
<small class="text-muted">各項功德金收入統計分析</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-warning text-dark">財務</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二欄:數據分析 -->
|
||||||
|
<div class="col-lg-4 mb-4">
|
||||||
|
<h5 class="text-primary mb-3">
|
||||||
|
<i class="bi bi-pie-chart"></i> 數據分析
|
||||||
|
</h5>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="query.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-diagram-3 text-primary me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>數據透視查詢</div>
|
||||||
|
<small class="text-muted">多維度數據透視分析(完整版)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-success">NEW</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="pivot_analysis.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-diagram-3 text-secondary me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>樞紐分析(舊版)</div>
|
||||||
|
<small class="text-muted">多維度數據透視分析</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-secondary">分析師</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="trend_analysis.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-graph-up-arrow text-success me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>趨勢分析</div>
|
||||||
|
<small class="text-muted">時間序列趨勢變化分析</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-success">分析師</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="comparative_analysis.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-graph-down text-danger me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>對比分析</div>
|
||||||
|
<small class="text-muted">不同時期、法會的對比分析</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-danger">分析師</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三欄:報表管理 -->
|
||||||
|
<div class="col-lg-4 mb-4">
|
||||||
|
<h5 class="text-primary mb-3">
|
||||||
|
<i class="bi bi-file-earmark-text"></i> 報表管理
|
||||||
|
</h5>
|
||||||
|
<div class="list-group">
|
||||||
|
<a href="custom_report.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-file-plus text-info me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>自訂報表</div>
|
||||||
|
<small class="text-muted">建立自訂的報表範本</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-info">管理員</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="report_schedule.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-clock text-secondary me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>定期報表</div>
|
||||||
|
<small class="text-muted">設定定期自動產生報表</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-secondary">管理員</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="export_center.aspx" class="list-group-item list-group-item-action">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="bi bi-download text-success me-3 function-icon"></i>
|
||||||
|
<div>
|
||||||
|
<div>匯出中心</div>
|
||||||
|
<small class="text-muted">報表匯出與下載管理</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-success">使用者</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 統計資訊 -->
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<h6 class="alert-heading">
|
||||||
|
<i class="bi bi-info-circle"></i> 系統說明
|
||||||
|
</h6>
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li><strong>報表查詢</strong>:提供各種預設報表的查詢與統計功能</li>
|
||||||
|
<li><strong>數據分析</strong>:多維度的數據透視與趨勢分析工具</li>
|
||||||
|
<li><strong>報表管理</strong>:自訂報表建立、定期報表設定與匯出管理</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</asp:Content>
|
||||||
10
web/admin/pivot/index.aspx.cs
Normal file
10
web/admin/pivot/index.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Web.UI;
|
||||||
|
|
||||||
|
public partial class admin_pivot_index : MyWeb.config
|
||||||
|
{
|
||||||
|
protected void Page_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// 頁面初始化(暫無邏輯)
|
||||||
|
}
|
||||||
|
}
|
||||||
1374
web/admin/pivot/pivot-01.aspx
Normal file
1374
web/admin/pivot/pivot-01.aspx
Normal file
File diff suppressed because it is too large
Load Diff
11
web/admin/pivot/pivot-01.aspx.cs
Normal file
11
web/admin/pivot/pivot-01.aspx.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Web.UI;
|
||||||
|
|
||||||
|
public partial class admin_pivot_pivot01 : MyWeb.config
|
||||||
|
{
|
||||||
|
protected void Page_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// 頁面初始化(暫無邏輯)
|
||||||
|
// 前端直接呼叫 API
|
||||||
|
}
|
||||||
|
}
|
||||||
1128
web/admin/pivot/query.aspx
Normal file
1128
web/admin/pivot/query.aspx
Normal file
File diff suppressed because it is too large
Load Diff
10
web/admin/pivot/query.aspx.cs
Normal file
10
web/admin/pivot/query.aspx.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Web.UI;
|
||||||
|
|
||||||
|
public partial class admin_pivot_query : MyWeb.config
|
||||||
|
{
|
||||||
|
protected void Page_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// 頁面初始化(暫無邏輯)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,8 +108,9 @@
|
|||||||
|
|
||||||
</asp:Content>
|
</asp:Content>
|
||||||
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
|
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
|
||||||
<div v-for="region in regions" :key="region.uuid" class="region-block mb-4">
|
<div v-for="region in regions" :key="region.uuid" class="region-block mb-4"
|
||||||
<h2 class="region-title mb-3">{{ region.regionPath }}</h2>
|
:style="region.isStop ? { pointerEvents: 'none', opacity: '0.6', backgroundColor: '#f8d7da' } : {}">
|
||||||
|
<h2 class="region-title mb-3">{{region.isStop?(region.regionPath + '(已停用)'): region.regionPath }}</h2>
|
||||||
|
|
||||||
<div class="row g-3 justify-content-start">
|
<div class="row g-3 justify-content-start">
|
||||||
<div v-for="room in region.room" :key="room.ruid" class="col-12 col-md-6 col-lg-6">
|
<div v-for="room in region.room" :key="room.ruid" class="col-12 col-md-6 col-lg-6">
|
||||||
@@ -319,6 +320,14 @@
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.regions = res.data.regions;
|
this.regions = res.data.regions;
|
||||||
this.summary = res.data.summary; // 保存後端統計
|
this.summary = res.data.summary; // 保存後端統計
|
||||||
|
try {
|
||||||
|
this.regions.sort((a, b) => {
|
||||||
|
return Number(a.isStop) - Number(b.isStop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error('API 錯誤', err);
|
console.error('API 錯誤', err);
|
||||||
|
|||||||
217
web/admin/region/handle_bed_in_used.aspx
Normal file
217
web/admin/region/handle_bed_in_used.aspx
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<%@ Page Title="" Language="C#" MasterPageFile="~/admin/Templates/TBS5ADM001/MasterPage.master" AutoEventWireup="true" CodeFile="handle_bed_in_used.aspx.cs" Inherits="admin_region_handle_bed_in_used" %>
|
||||||
|
|
||||||
|
<asp:Content ID="Content1" ContentPlaceHolderID="page_header" Runat="Server">
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content2" ContentPlaceHolderID="page_nav" Runat="Server">
|
||||||
|
<nav>
|
||||||
|
<div>
|
||||||
|
<label style="display: inline-block;">區域:</label>
|
||||||
|
<select style="display: inline-block; min-width:150px; max-width:20%;"
|
||||||
|
class="form-select"
|
||||||
|
v-model="selectedRegionUuid"
|
||||||
|
:disabled="isFromUrl"
|
||||||
|
@change="onRegionChange"
|
||||||
|
><option :value="null">請選擇區域</option>
|
||||||
|
<option
|
||||||
|
v-for="region in regions"
|
||||||
|
:key="region.uuid"
|
||||||
|
:value="region.uuid"
|
||||||
|
>
|
||||||
|
{{ region.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<label style="display: inline-block;margin-left: 20px;">客房:</label>
|
||||||
|
<select style="display: inline-block; min-width:200px; max-width:20%;"
|
||||||
|
class="form-select"
|
||||||
|
v-model="selectedRoomUuid"
|
||||||
|
:disabled="isFromUrl"
|
||||||
|
@change="onRoomChange"
|
||||||
|
>
|
||||||
|
<option :value="null">請選擇房間</option>
|
||||||
|
<option v-for="room in rooms" :key="room.uuid" :value="room.uuid">{{room.fullName}}</option>
|
||||||
|
</select>
|
||||||
|
<label style="display: inline-block;margin-left: 20px;">床位:</label>
|
||||||
|
<select style="display: inline-block; width:100px;"
|
||||||
|
class="form-select"
|
||||||
|
v-model="selectedBedUuid"
|
||||||
|
:disabled="isFromUrl"
|
||||||
|
></select>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content3" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
|
||||||
|
<div>
|
||||||
|
<v-data-table
|
||||||
|
:items="items"
|
||||||
|
show-select
|
||||||
|
v-model:selected="selectedItems"
|
||||||
|
item-key="bedUuid"
|
||||||
|
|
||||||
|
:headers="headers">
|
||||||
|
<template #item.actions="{item}">
|
||||||
|
<button class="btn btn-outline-danger" type="button" @click="confirmAndCancelSingleBedBooking(item)">取消預約</button>
|
||||||
|
</template>
|
||||||
|
<template #item.guadan_during="{item}">
|
||||||
|
{{item.guadan_during.checkInAt|timeString('YYYY-MM-DD')}} - {{item.guadan_during.checkOutAt|timeString('YYYY-MM-DD')}}
|
||||||
|
</template>
|
||||||
|
<template #item.status="{item}">
|
||||||
|
{{item.status.name}}
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</div>
|
||||||
|
<!-- 更新修改確認彈出視窗 -->
|
||||||
|
<message-modal ref="messageModal"></message-modal>
|
||||||
|
<!-- 刪除確認彈出視窗 -->
|
||||||
|
<confirm-modal ref="confirmModal"></confirm-modal>
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content4" ContentPlaceHolderID="offCanvasRight" Runat="Server">
|
||||||
|
</asp:Content>
|
||||||
|
<asp:Content ID="Content5" ContentPlaceHolderID="footer_script" Runat="Server">
|
||||||
|
<script>
|
||||||
|
Vue.filter('timeString', function (value, myFormat) {
|
||||||
|
return value == null || value == "" ? "" : moment(value).format(myFormat || 'YYYY-MM-DD, HH:mm:ss');
|
||||||
|
});
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
vuetify: new Vuetify(vuetify_options),
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
regionUuidFromUrl: '<%= Request.QueryString["region"] %>' || null,
|
||||||
|
roomUuidFromUrl: '<%= Request.QueryString["room"] %>' || null,
|
||||||
|
bedUuidFromUrl: '<%= Request.QueryString["bed"] %>' || null,
|
||||||
|
// 用戶選擇的值
|
||||||
|
selectedRegionUuid: null,
|
||||||
|
selectedRoomUuid: null,
|
||||||
|
selectedBedUuid: null,
|
||||||
|
regions: [],
|
||||||
|
rooms: [],
|
||||||
|
bed: [],
|
||||||
|
selectedItems: [],
|
||||||
|
items: [],
|
||||||
|
headers: [
|
||||||
|
{ text: '床位名稱', value: 'fullName' },
|
||||||
|
{ text: '掛單號:', value: 'guaDanOrderNo' },
|
||||||
|
{ text: '掛單人', value: 'u_name' },
|
||||||
|
{ text: '掛單時間', value: 'guadan_during' },
|
||||||
|
{ text: '掛單狀態', value: 'status' },
|
||||||
|
{ text: '', value: 'actions' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRegionChange() {
|
||||||
|
console.log("選擇的區域 UUID:", this.selectedRegionUuid);
|
||||||
|
this.selectedRoomUuid = null;
|
||||||
|
this.selectedBedUuid = null;
|
||||||
|
this.GetInUsedBed();
|
||||||
|
this.GetRoomList();
|
||||||
|
},
|
||||||
|
onRoomChange() {
|
||||||
|
console.log("選擇的客房 UUID:", this.selectedRoomUuid);
|
||||||
|
this.GetInUsedBed();
|
||||||
|
},
|
||||||
|
async GetInUsedBed() {
|
||||||
|
//獲取已經預約或者入住的床位
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
regionUuid: this.selectedRegionUuid || null,
|
||||||
|
roomUuid: this.selectedRoomUuid || null,
|
||||||
|
bedUuid: this.selectedBedUuid || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axios.post(HTTP_HOST + "api/bed/inuse/list", payload);
|
||||||
|
|
||||||
|
// 假設返回的就是床位數組
|
||||||
|
this.items = response.data;
|
||||||
|
|
||||||
|
console.log("已獲取床位:");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("獲取床位失敗:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async confirmAndCancelSingleBedBooking(item) {
|
||||||
|
// 先彈出確認彈出視窗
|
||||||
|
this.$refs.confirmModal.open({
|
||||||
|
message: `確定要取消床位 ${item.name || ''} 的所有預約嗎?`,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
bedUuid: item.bedUuid || null
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axios.post(
|
||||||
|
HTTP_HOST + "api/bed/inuse/cancel/singlebed/booking",
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
// 刷新床位列表
|
||||||
|
this.GetInUsedBed();
|
||||||
|
|
||||||
|
// 成功提示
|
||||||
|
console.log("取消成功:", item.bedUuid);
|
||||||
|
this.$refs.messageModal.open({
|
||||||
|
title: '取消成功',
|
||||||
|
message: response?.data?.message || '取消成功!',
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("取消失敗:", error);
|
||||||
|
this.$refs.messageModal.open({
|
||||||
|
title: '取消失敗',
|
||||||
|
message: error.response?.data?.message || '取消過程中發生錯誤!',
|
||||||
|
status: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async CancelAllBedBooking() {
|
||||||
|
//取消符合條件的所有床位的所有預約
|
||||||
|
},
|
||||||
|
async GetRegionList() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
HTTP_HOST + "api/bed/inuse/region/list"
|
||||||
|
);
|
||||||
|
this.regions = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async GetRoomList() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
HTTP_HOST + "api/bed/inuse/room/list", {
|
||||||
|
params: {
|
||||||
|
regionUuid: this.selectedRegionUuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.rooms = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.selectedRegionUuid = this.regionUuidFromUrl;
|
||||||
|
this.selectedRoomUuid = this.roomUuidFromUrl;
|
||||||
|
this.selectedBedUuid = this.bedUuidFromUrl;
|
||||||
|
this.GetInUsedBed();
|
||||||
|
this.GetRegionList();
|
||||||
|
this.GetRoomList();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 判斷是否來自 URL,禁用下拉框
|
||||||
|
isFromUrl() {
|
||||||
|
return this.regionUuidFromUrl || this.roomUuidFromUrl || this.bedUuidFromUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</asp:Content>
|
||||||
14
web/admin/region/handle_bed_in_used.aspx.cs
Normal file
14
web/admin/region/handle_bed_in_used.aspx.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Web;
|
||||||
|
using System.Web.UI;
|
||||||
|
using System.Web.UI.WebControls;
|
||||||
|
|
||||||
|
public partial class admin_region_handle_bed_in_used : MyWeb.config
|
||||||
|
{
|
||||||
|
protected void Page_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,17 +15,16 @@
|
|||||||
<i class="mdi mdi-arrow-collapse-all"></i> 全部收起
|
<i class="mdi mdi-arrow-collapse-all"></i> 全部收起
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<nav v-if="form && selectedType==null">
|
<nav v-if="createRegionFlag">
|
||||||
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
|
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
|
||||||
<i class="bi bi-save me-1"></i> 儲存區域資料
|
<i class="bi bi-save me-1"></i> 儲存區域資料
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="btn-group mb-2 ps-3 pe-3" role="group" v-if="form && selectedType=='region'">
|
<nav class="btn-group mb-2 ps-3 pe-3" role="group" v-if="form && selectedType=='region' && !createRegionFlag">
|
||||||
|
|
||||||
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
|
|
||||||
<i class="bi bi-save me-1"></i> 儲存區域資料
|
|
||||||
</button>
|
|
||||||
<div v-if="selectedRegionId">
|
<div v-if="selectedRegionId">
|
||||||
|
<button class="btn btn-primary me-2" @click="saveRegion" type="button">
|
||||||
|
<i class="bi bi-save me-1"></i> 儲存區域資料1
|
||||||
|
</button>
|
||||||
<button class="btn btn-success me-2" @click="createSubRegion" type="button" >
|
<button class="btn btn-success me-2" @click="createSubRegion" type="button" >
|
||||||
<i class="mdi mdi-arrow-down-right"></i> 新增下層區域
|
<i class="mdi mdi-arrow-down-right"></i> 新增下層區域
|
||||||
</button>
|
</button>
|
||||||
@@ -252,7 +251,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">狀態</label>
|
<label class="form-label">狀態</label>
|
||||||
<select class="form-control" v-model="room_bed.newBedForm.statuscode">
|
<select class="form-control" v-model="room_bed.newBedForm.statuscode" disabled>
|
||||||
<option v-for="status in room_bed.bed_status" :value="status.code">
|
<option v-for="status in room_bed.bed_status" :value="status.code">
|
||||||
{{status.name}}
|
{{status.name}}
|
||||||
</option>
|
</option>
|
||||||
@@ -349,6 +348,23 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- 停用區域如果有床位正在掛單提示彈出視窗 -->
|
||||||
|
<div v-if="bed_is_used_modal" style="position:fixed; top:0; left:0; width:100%; height:100%;
|
||||||
|
background:rgba(0,0,0,0.5); display:flex; align-items:center; justify-content:center; z-index:9999;">
|
||||||
|
<div style="background:#fff; padding:20px; border-radius:8px; width:300px; text-align:center;">
|
||||||
|
<p style="margin-bottom:15px;">{{bed_is_used_modal_message}}</p>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button @click="closeBedInUsedModal"
|
||||||
|
class="btn btn-danger"
|
||||||
|
type="button" style="padding:6px 12px; border:none; border-radius:4px; cursor:pointer;">关闭</button>
|
||||||
|
<a :href="bed_is_used_modal_link" target="_blank" class="btn btn-primary">
|
||||||
|
前往处理
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 更新修改確認彈出視窗 -->
|
<!-- 更新修改確認彈出視窗 -->
|
||||||
<message-modal ref="messageModal"></message-modal>
|
<message-modal ref="messageModal"></message-modal>
|
||||||
<!-- 刪除確認彈出視窗 -->
|
<!-- 刪除確認彈出視窗 -->
|
||||||
@@ -477,13 +493,14 @@
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
vuetify: new Vuetify(vuetify_options),
|
vuetify: new Vuetify(vuetify_options),
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
bed_is_used_modal: false,
|
||||||
|
bed_is_used_modal_message: null,
|
||||||
|
bed_is_used_modal_link: "handle_bed_in_used.aspx", // 默认链接
|
||||||
selectedId: null, // 被選中項目ID
|
selectedId: null, // 被選中項目ID
|
||||||
selectedType: null, // 'region' 或 'room'
|
selectedType: null, // 'region' 或 'room'
|
||||||
expandAllFlag: false, // 控制全部展開
|
expandAllFlag: false, // 控制全部展開
|
||||||
@@ -499,6 +516,7 @@
|
|||||||
regionTypes: [],
|
regionTypes: [],
|
||||||
currentSelectRegion: null,
|
currentSelectRegion: null,
|
||||||
currentSelectRoom: null,
|
currentSelectRoom: null,
|
||||||
|
createRegionFlag: false,
|
||||||
form: {
|
form: {
|
||||||
uuid: null,
|
uuid: null,
|
||||||
name: '',
|
name: '',
|
||||||
@@ -546,6 +564,11 @@
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
closeBedInUsedModal() {
|
||||||
|
this.bed_is_used_modal = false;
|
||||||
|
this.bed_is_used_modal_message = null;
|
||||||
|
this.bed_is_used_modal_link = "handle_bed_in_used.aspx"; // 默认链接
|
||||||
|
},
|
||||||
expandAll() {
|
expandAll() {
|
||||||
this.expandAllFlag = true;
|
this.expandAllFlag = true;
|
||||||
this.collapseAllFlag = false;
|
this.collapseAllFlag = false;
|
||||||
@@ -613,6 +636,8 @@
|
|||||||
this.disabledParentOptions = [];
|
this.disabledParentOptions = [];
|
||||||
this.currentSelectRegion = null;
|
this.currentSelectRegion = null;
|
||||||
this.currentSelectRoom = null;
|
this.currentSelectRoom = null;
|
||||||
|
this.createRegionFlag = true;
|
||||||
|
this.selectedType = null;
|
||||||
},
|
},
|
||||||
createSubRegion() {
|
createSubRegion() {
|
||||||
if (!this.selectedRegionId) return;
|
if (!this.selectedRegionId) return;
|
||||||
@@ -634,25 +659,39 @@
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = this.form.uuid ? '/api/region/update' : '/api/region/create';
|
const url = this.form.uuid
|
||||||
|
? HTTP_HOST + 'api/region/update'
|
||||||
|
: HTTP_HOST + 'api/region/create';
|
||||||
|
|
||||||
axios.post(url, this.form)
|
axios.post(url, this.form)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
//alert('儲存成功');
|
//alert('儲存成功');
|
||||||
this.loadRegions();
|
this.loadRegions();
|
||||||
//this.newRegion();
|
//this.newRegion();
|
||||||
this.form.uuid = res.data.uuid;
|
this.form.uuid = res.data.id;
|
||||||
this.selectedRegionId = res.data.uuid;
|
this.selectedRegionId = res.data.id;
|
||||||
this.currentSelectRegion = JSON.parse(JSON.stringify(this.form));
|
this.currentSelectRegion = JSON.parse(JSON.stringify(this.form));
|
||||||
|
this.createRegionFlag = false;
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
title: "更新",
|
title: "更新",
|
||||||
message: "更新成功",
|
message: "更新成功",
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.error('更新失敗', error);
|
||||||
|
const code = error.response?.data?.code;
|
||||||
|
const message = error.response?.data?.message || error.message ||
|
||||||
|
"未知錯誤,請稍後再試";
|
||||||
|
if (code === "BED_IS_USED") {
|
||||||
|
this.bed_is_used_modal = true;
|
||||||
|
this.bed_is_used_modal_message = message
|
||||||
|
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?region=' + this.form.uuid
|
||||||
|
|
||||||
|
} else {
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
title: '更新提示',
|
message: (message)
|
||||||
message: error.response?.data?.message || "儲存失敗,請稍後再試。",
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteRegion() {
|
deleteRegion() {
|
||||||
@@ -665,7 +704,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
confirmDeleteRegion() {
|
confirmDeleteRegion() {
|
||||||
axios.post('/api/region/delete', { Uuid: this.form.uuid })
|
axios.post(HTTP_HOST + 'api/region/delete', { Uuid: this.form.uuid })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.showDeleteModal = false;
|
this.showDeleteModal = false;
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
@@ -717,7 +756,7 @@
|
|||||||
uuid: null,
|
uuid: null,
|
||||||
RoomUuid: this.currentSelectRoom.uuid,
|
RoomUuid: this.currentSelectRoom.uuid,
|
||||||
Name: '',
|
Name: '',
|
||||||
statuscode: null,
|
statuscode: "101",
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇
|
Gender: this.currentSelectRoom.gender, // 不設預設值,強制選擇
|
||||||
};
|
};
|
||||||
@@ -732,7 +771,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
var res = await axios.post('/api/region/bed/create', this.room_bed.newBedForm);
|
var res = await axios.post(HTTP_HOST + 'api/region/bed/create', this.room_bed.newBedForm);
|
||||||
this.room_bed.showBedModal = false;
|
this.room_bed.showBedModal = false;
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
title: '成功',
|
title: '成功',
|
||||||
@@ -825,13 +864,21 @@
|
|||||||
await this.loadRegions();
|
await this.loadRegions();
|
||||||
this.room_bed.bed_items = this.currentSelectRoom.beds;
|
this.room_bed.bed_items = this.currentSelectRoom.beds;
|
||||||
//this.selectRegion(this.findRegionById(this.regions, this.form.id));
|
//this.selectRegion(this.findRegionById(this.regions, this.form.id));
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.log(err)
|
console.error('更新失敗', error);
|
||||||
|
const code = error.response?.data?.code;
|
||||||
|
const message = error.response?.data?.message || error.message ||
|
||||||
|
"未知錯誤,請稍後再試";
|
||||||
|
if (code === "BED_IS_USED") {
|
||||||
|
this.bed_is_used_modal = true;
|
||||||
|
this.bed_is_used_modal_message = message;
|
||||||
|
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?bed=' + this.room_bed.newBedForm.uuid
|
||||||
|
} else {
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
title: '錯誤',
|
message: (message)
|
||||||
message: err.response?.data?.message || '更新失敗'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getBedStatus() {
|
getBedStatus() {
|
||||||
//獲取床位狀態
|
//獲取床位狀態
|
||||||
@@ -887,10 +934,19 @@
|
|||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新失敗', error);
|
console.error('更新失敗', error);
|
||||||
|
const code = error.response?.data?.code;
|
||||||
|
const message = error.response?.data?.message || error.message ||
|
||||||
|
"未知錯誤,請稍後再試";
|
||||||
|
if (code === "BED_IS_USED") {
|
||||||
|
this.bed_is_used_modal = true;
|
||||||
|
this.bed_is_used_modal_message = message;
|
||||||
|
this.bed_is_used_modal_link = this.bed_is_used_modal_link + '?room=' + this.room.room_form.uuid
|
||||||
|
} else {
|
||||||
this.$refs.messageModal.open({
|
this.$refs.messageModal.open({
|
||||||
message: (error.response?.data?.message || error.message)
|
message: (message)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
roomDelete() {
|
roomDelete() {
|
||||||
@@ -947,7 +1003,16 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
currentSelectRegion(newVal) {
|
||||||
|
if (newVal !== null) {
|
||||||
|
this.createRegionFlag = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentSelectRoom(newVal) {
|
||||||
|
if (newVal !== null) {
|
||||||
|
this.createRegionFlag = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadRegions();
|
this.loadRegions();
|
||||||
|
|||||||
Reference in New Issue
Block a user