69 lines
4.1 KiB
Markdown
69 lines
4.1 KiB
Markdown
# Offering Session — 顯示與修改主日參加人數
|
||
|
||
Date: 2026-06-24
|
||
|
||
## Goal
|
||
|
||
在 finance/offering-session 的 **Recent Sessions** 表格中,每筆 session 顯示該日的「主日參加人數」總數,並新增一個 Action 可修改該日的參加人數。
|
||
|
||
## Confirmed Decisions
|
||
|
||
1. **資料來源** — 沿用既有 `MealAttendance`。主日參加人數 = 該日 `AdultCount + YouthCount + KidCount`。修改 Action 改的是同一筆 `MealAttendance` 紀錄。
|
||
2. **顯示** — 後端 join,DTO 加 nullable 欄位;該日無紀錄時顯示 `—`。
|
||
3. **編輯介面** — Kendo Dialog 分別編輯三個分類(成人/青年/兒童),總數即時計算。
|
||
4. **Action 擺放** — 依既有慣例,Date 欄位可點擊觸發 View;View 與「修改主日人數」都進右鍵 context menu(沿用 expense-categories 範式)。
|
||
|
||
## Key Constraint
|
||
|
||
`AttendanceHub.SetCount` 只作用在 `ServiceDay`(本週日),無法改任意日期。因此編輯過去某場 session 的日期,**必須走新的 REST 端點**,不可用 SignalR。
|
||
|
||
## Backend Changes
|
||
|
||
### 1. DTO
|
||
`API/ROLAC.API/DTOs/Giving/OfferingSessionListItemDto.cs`
|
||
- 新增 `public int? SundayAttendanceCount { get; set; }`(nullable:該日無 attendance row 為 null)。
|
||
|
||
### 2. OfferingSessionService.GetPaged
|
||
- 取得當頁 sessions 後,以這些 `SessionDate` 一次查 `MealAttendance`(單一 set-based 查詢,依日期分組求和),把總數填入各 DTO 的 `SundayAttendanceCount`;無紀錄者留 null。
|
||
- 不可對每筆 session 各發一次查詢(避免 N+1)。
|
||
|
||
### 3. IMealAttendanceService + MealAttendanceService
|
||
- 新增 `Task<AttendanceCountsDto> SetCountsAsync(DateOnly date, int adult, int youth, int kid)`。
|
||
- 沿用既有 clamp-at-zero 語意,一次寫三欄並回傳 `AttendanceCountsDto`;該日無 row 則建立(沿用 `GetOrCreateAsync` 的建立邏輯)。
|
||
|
||
### 4. MealAttendanceController
|
||
- 新增 `PUT /api/meal-attendance/{date}`,`[Authorize]`(與既有 `GetRange` 一致),body `{ adult, youth, kid }` → 回傳 `AttendanceCountsDto`。
|
||
- **Optional(plan 階段決定)**:若 `date == ServiceDay`,順手透過 `AttendanceHub` 廣播 `ReceiveCounts`,讓正在跑的即時計數器同步。預設先不做,避免增加耦合。
|
||
|
||
## Frontend Changes
|
||
|
||
### 1. Model
|
||
`APP/.../giving/models/giving.model.ts`
|
||
- `OfferingSessionListItemDto` 加 `sundayAttendanceCount?: number | null`。
|
||
|
||
### 2. Attendance API service
|
||
`APP/.../meal-attendance/services/meal-attendance-api.service.ts`
|
||
- 新增 `setCounts(date: string, counts: { adult: number; youth: number; kid: number }): Observable<AttendanceCounts>` → 呼叫 `PUT /api/meal-attendance/{date}`。
|
||
|
||
### 3. offering-session-page component
|
||
`APP/.../giving/pages/offering-session-page/offering-session-page.component.{ts,html}`
|
||
- imports 加 `ContextMenuModule`、`DialogsModule`、`InputsModule`(NumericTextBox)。
|
||
- Recent Sessions grid:
|
||
- 在 "Lines" 後新增欄 `Attendance · 主日人數`,顯示 `s.sundayAttendanceCount ?? '—'`。
|
||
- 移除原本獨立的 "View" 按鈕欄;改為 `(cellClick)`:`event.type === 'contextmenu'` → 開 context menu;否則 `openView`。Date 欄加可點擊樣式(`clickable-rows`)。
|
||
- `kendo-contextmenu`,items:`View`、`修改主日人數`。
|
||
- 修改主日人數 Dialog:
|
||
- 開啟時以 `mealAttendanceApi.getRange(date, date)` 取該日 breakdown 預填(無紀錄則三欄為 0)。
|
||
- 三個 `kendo-numerictextbox`(成人/青年/兒童,min 0),即時顯示總數。
|
||
- Save → `setCounts(date, …)`;成功後就地把該列 `sundayAttendanceCount` 更新為三者之和。Cancel 關閉。
|
||
|
||
## Permissions
|
||
PUT 端點用 `[Authorize]`,與既有 `GetRange` 一致。
|
||
|
||
## Out of Scope
|
||
- Recent Sessions grid 目前尚無手機卡片版;本次只新增欄位與 action,不一併重構 mobile 版面(可另開 task)。
|
||
|
||
## Testing
|
||
- Backend:`SetCountsAsync` 對負值 clamp 為 0、該日無 row 時建立;`GetPaged` 正確帶入 attendance 總數且無 N+1。
|
||
- Frontend:dialog 總數計算(成人+青年+兒童)與存檔後就地更新該列。(前端測試環境較脆弱,採最小範圍 inline-template 測試。)
|