Phase 1: Line Messaging API channel with webhook binding (individual +
group), manual send-now, history, and binding/group admin UI. Scheduled
sends and event triggers deferred to phases 2-3; IMessageChannel seam
left for future PWA/WeChat channels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final-review findings:
- ExpenseCategoriesController was finance-only at the class level, but the member
self-service reimbursement form reads the category list to populate its dropdown,
so members got 403 and could not submit. Open GET to any authenticated user;
keep group/subcategory writes finance-only (mirrors MinistriesController).
Verified live with a member-role account: reads 200, writes 403, self-submit 200.
- MonthlyStatementService Update/Finalize now use FirstOrDefaultAsync for
convention consistency with the rest of the service layer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live verification revealed the JWT carries the user id in the 'sub' claim
(NameClaimType=sub, MapInboundClaims=false), so ClaimTypes.NameIdentifier is
null at runtime. This caused ExpensesController.GetMine/GetById to throw
NullReferenceException (500) on the '!.Value', and made the services fall back
to 'system' — silently defeating the self-ownership guard. Resolve via
NameIdentifier (unit tests) then 'sub' (real tokens). Adds a regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Also fix kendo-grid [total] binding in expenses-page template by
switching to GridDataResult object form ({ data, total }) on [data].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 16 — MonthlyStatementPageComponent with Kendo Grid list
(year filter), create/edit dialog (server-computed totals preview), and
finalize action that locks the statement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The receipt <a href target=_blank> was an unauthenticated browser navigation
that the API's [Authorize] rejects with 401. Replace with a HttpClient blob
download (downloadReceipt) so the auth interceptor attaches the JWT, opened
via an object URL. Also fix the delete button: confirm() must run inside the
component method (matching givings-page), not as a template expression where
confirm is not a component member.
Standalone Angular component (Kendo Grid + ExpenseFormDialog) that lets
any logged-in user list, create, submit, and delete their own draft
reimbursements, with optional receipt upload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>