feat(giving): add sundayAttendanceCount model field and attendance setCounts API
ci-cd-vm / ci-cd (push) Successful in 2m21s

This commit is contained in:
Chris Chen
2026-06-24 11:26:16 -07:00
parent 28eba8a3ea
commit b0e2e112fc
20 changed files with 653 additions and 5 deletions
@@ -0,0 +1,68 @@
# 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 測試。)