Files
ROLAC/docs/superpowers/specs/2026-06-24-offering-session-attendance-design.md
2026-06-24 11:35:34 -07:00

69 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Offering Session — 顯示與修改主日參加人數
Date: 2026-06-24
## Goal
在 finance/offering-session 的 **Recent Sessions** 表格中,每筆 session 顯示該日的「主日參加人數」總數,並新增一個 Action 可修改該日的參加人數。
## Confirmed Decisions
1. **資料來源** — 沿用既有 `MealAttendance`。主日參加人數 = 該日 `AdultCount + YouthCount + KidCount`。修改 Action 改的是同一筆 `MealAttendance` 紀錄。
2. **顯示** — 後端 joinDTO 加 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`
- **Optionalplan 階段決定)**:若 `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。
- Frontenddialog 總數計算(成人+青年+兒童)與存檔後就地更新該列。(前端測試環境較脆弱,採最小範圍 inline-template 測試。)