feat(giving): frontend models + API services

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-28 16:57:15 -07:00
parent b5a15dd9f2
commit 4a2b142061
4 changed files with 223 additions and 0 deletions
@@ -0,0 +1,119 @@
export type PaymentMethod = 'Cash' | 'Check' | 'Zelle' | 'PayPal' | 'Other';
export type SessionStatus = 'Draft' | 'Submitted' | 'Reconciled';
export interface PagedResult<T> {
items: T[];
totalCount: number;
page: number;
pageSize: number;
totalPages: number;
}
// ── Giving categories ─────────────────────────────────────────────
export interface GivingCategoryDto {
id: number;
name_en: string;
name_zh: string | null;
description_en: string | null;
description_zh: string | null;
isActive: boolean;
sortOrder: number;
}
export interface CreateGivingCategoryRequest {
name_en: string;
name_zh: string | null;
description_en: string | null;
description_zh: string | null;
sortOrder: number;
}
export interface UpdateGivingCategoryRequest extends CreateGivingCategoryRequest {
isActive: boolean;
}
// ── Single giving ─────────────────────────────────────────────────
export interface GivingListItemDto {
id: number;
memberId: number | null;
memberName: string | null;
givingCategoryId: number;
categoryName: string;
amount: number;
paymentMethod: PaymentMethod;
givingDate: string; // yyyy-MM-dd
isAnonymous: boolean;
offeringSessionId: number | null;
}
export interface CreateGivingRequest {
memberId: number | null;
givingCategoryId: number;
amount: number;
paymentMethod: PaymentMethod;
checkNumber: string | null;
zelleReferenceCode: string | null;
payPalTransactionId: string | null;
givingDate: string; // yyyy-MM-dd
isAnonymous: boolean;
notes: string | null;
}
export type UpdateGivingRequest = CreateGivingRequest;
// ── Offering session (batch) ──────────────────────────────────────
export interface OfferingGivingLineRequest {
memberId: number | null;
givingCategoryId: number;
amount: number;
paymentMethod: PaymentMethod;
checkNumber: string | null;
zelleReferenceCode: string | null;
payPalTransactionId: string | null;
isAnonymous: boolean;
notes: string | null;
}
export interface CreateOfferingSessionRequest {
sessionDate: string; // yyyy-MM-dd
cashTotal: number;
checkTotal: number;
notes: string | null;
givings: OfferingGivingLineRequest[];
}
export interface OfferingGivingLineDto {
id: number;
memberId: number | null;
memberName: string | null;
givingCategoryId: number;
categoryName: string;
amount: number;
paymentMethod: PaymentMethod;
checkNumber: string | null;
zelleReferenceCode: string | null;
payPalTransactionId: string | null;
isAnonymous: boolean;
notes: string | null;
}
export interface OfferingSessionDto {
id: number;
sessionDate: string;
status: SessionStatus;
cashTotal: number;
checkTotal: number;
systemTotal: number;
difference: number;
notes: string | null;
givings: OfferingGivingLineDto[];
}
export interface OfferingSessionListItemDto {
id: number;
sessionDate: string;
status: SessionStatus;
cashTotal: number;
checkTotal: number;
systemTotal: number;
difference: number;
lineCount: number;
}
/** A row held in the client-side batch buffer before submit. */
export interface OfferingBufferLine extends OfferingGivingLineRequest {
memberName: string | null; // for display only
categoryName: string; // for display only
}
@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiConfigService } from '../../../core/services/api-config.service';
import {
GivingListItemDto, CreateGivingRequest, UpdateGivingRequest, PagedResult,
} from '../models/giving.model';
export interface GivingQuery {
page?: number; pageSize?: number; search?: string;
categoryId?: number; from?: string; to?: string;
}
@Injectable({ providedIn: 'root' })
export class GivingApiService {
private readonly endpoint: string;
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
this.endpoint = apiConfig.getApiUrl('givings');
}
getPaged(q: GivingQuery = {}): Observable<PagedResult<GivingListItemDto>> {
let p = new HttpParams().set('page', q.page ?? 1).set('pageSize', q.pageSize ?? 20);
if (q.search) p = p.set('search', q.search);
if (q.categoryId != null) p = p.set('categoryId', q.categoryId);
if (q.from) p = p.set('from', q.from);
if (q.to) p = p.set('to', q.to);
return this.http.get<PagedResult<GivingListItemDto>>(this.endpoint, { params: p });
}
create(request: CreateGivingRequest): Observable<{ id: number }> {
return this.http.post<{ id: number }>(this.endpoint, request);
}
update(id: number, request: UpdateGivingRequest): Observable<void> {
return this.http.put<void>(`${this.endpoint}/${id}`, request);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.endpoint}/${id}`);
}
}
@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiConfigService } from '../../../core/services/api-config.service';
import {
GivingCategoryDto, CreateGivingCategoryRequest, UpdateGivingCategoryRequest,
} from '../models/giving.model';
@Injectable({ providedIn: 'root' })
export class GivingCategoryApiService {
private readonly endpoint: string;
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
this.endpoint = apiConfig.getApiUrl('giving-categories');
}
getAll(includeInactive = false): Observable<GivingCategoryDto[]> {
const params = new HttpParams().set('includeInactive', includeInactive);
return this.http.get<GivingCategoryDto[]>(this.endpoint, { params });
}
create(request: CreateGivingCategoryRequest): Observable<{ id: number }> {
return this.http.post<{ id: number }>(this.endpoint, request);
}
update(id: number, request: UpdateGivingCategoryRequest): Observable<void> {
return this.http.put<void>(`${this.endpoint}/${id}`, request);
}
deactivate(id: number): Observable<void> {
return this.http.delete<void>(`${this.endpoint}/${id}`);
}
}
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ApiConfigService } from '../../../core/services/api-config.service';
import {
OfferingSessionDto, OfferingSessionListItemDto,
CreateOfferingSessionRequest, PagedResult,
} from '../models/giving.model';
@Injectable({ providedIn: 'root' })
export class OfferingSessionApiService {
private readonly endpoint: string;
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
this.endpoint = apiConfig.getApiUrl('offering-sessions');
}
getPaged(page = 1, pageSize = 20): Observable<PagedResult<OfferingSessionListItemDto>> {
const params = new HttpParams().set('page', page).set('pageSize', pageSize);
return this.http.get<PagedResult<OfferingSessionListItemDto>>(this.endpoint, { params });
}
getById(id: number): Observable<OfferingSessionDto> {
return this.http.get<OfferingSessionDto>(`${this.endpoint}/${id}`);
}
checkDate(date: string): Observable<{ exists: boolean }> {
const params = new HttpParams().set('date', date);
return this.http.get<{ exists: boolean }>(`${this.endpoint}/check-date`, { params });
}
create(request: CreateOfferingSessionRequest): Observable<{ id: number }> {
return this.http.post<{ id: number }>(this.endpoint, request);
}
reopen(id: number): Observable<void> {
return this.http.post<void>(`${this.endpoint}/${id}/reopen`, {});
}
replace(id: number, request: CreateOfferingSessionRequest): Observable<void> {
return this.http.put<void>(`${this.endpoint}/${id}`, request);
}
}