feat(expense): add frontend models + API services
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
export type ExpenseType = 'VendorPayment' | 'StaffReimbursement';
|
||||
export type ExpenseStatus = 'Draft' | 'PendingApproval' | 'Approved' | 'Paid' | 'Rejected';
|
||||
|
||||
export interface PagedResult<T> {
|
||||
items: T[]; totalCount: number; page: number; pageSize: number; totalPages: number;
|
||||
}
|
||||
|
||||
export interface MinistryDto { id: number; name_en: string; name_zh: string | null; sortOrder: number; isActive: boolean; }
|
||||
|
||||
export interface ExpenseSubCategoryDto { id: number; groupId: number; name_en: string; name_zh: string | null; sortOrder: number; isActive: boolean; }
|
||||
export interface ExpenseCategoryGroupDto { id: number; name_en: string; name_zh: string | null; sortOrder: number; isActive: boolean; subCategories: ExpenseSubCategoryDto[]; }
|
||||
export interface CreateExpenseGroupRequest { name_en: string; name_zh: string | null; sortOrder: number; }
|
||||
export interface UpdateExpenseGroupRequest extends CreateExpenseGroupRequest { isActive: boolean; }
|
||||
export interface CreateExpenseSubCategoryRequest { groupId: number; name_en: string; name_zh: string | null; sortOrder: number; }
|
||||
export interface UpdateExpenseSubCategoryRequest extends CreateExpenseSubCategoryRequest { isActive: boolean; }
|
||||
|
||||
export interface ExpenseListItemDto {
|
||||
id: number; type: ExpenseType; status: ExpenseStatus; amount: number; description: string;
|
||||
ministryId: number; ministryName: string; categoryGroupId: number; categoryGroupName: string;
|
||||
subCategoryId: number; subCategoryName: string; vendorName: string | null;
|
||||
memberId: number | null; memberName: string | null; expenseDate: string; hasReceipt: boolean;
|
||||
}
|
||||
export interface ExpenseDto extends ExpenseListItemDto {
|
||||
checkNumber: string | null; notes: string | null; reviewNotes: string | null;
|
||||
submittedBy: string | null; submittedAt: string | null; reviewedAt: string | null; paidAt: string | null;
|
||||
}
|
||||
export interface CreateExpenseRequest {
|
||||
type: ExpenseType; ministryId: number; categoryGroupId: number; subCategoryId: number;
|
||||
amount: number; description: string; vendorName: string | null; memberId: number | null;
|
||||
checkNumber: string | null; expenseDate: string; notes: string | null;
|
||||
}
|
||||
export type UpdateExpenseRequest = CreateExpenseRequest;
|
||||
export interface RejectExpenseRequest { reviewNotes: string | null; }
|
||||
export interface PayExpenseRequest { checkNumber: string | null; paidAt: string | null; }
|
||||
|
||||
export interface MonthlyStatementDto {
|
||||
id: number; year: number; month: number; openingBalance: number; totalGiving: number;
|
||||
totalOtherIncome: number; totalExpenses: number; calculatedClosingBalance: number;
|
||||
bankStatementBalance: number; difference: number; notes: string | null; isFinalized: boolean;
|
||||
}
|
||||
export interface CreateMonthlyStatementRequest {
|
||||
year: number; month: number; openingBalance: number; totalOtherIncome: number; bankStatementBalance: number; notes: string | null;
|
||||
}
|
||||
export interface UpdateMonthlyStatementRequest {
|
||||
openingBalance: number; totalOtherIncome: number; bankStatementBalance: number; notes: string | null;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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 {
|
||||
PagedResult, ExpenseListItemDto, ExpenseDto, CreateExpenseRequest, UpdateExpenseRequest,
|
||||
RejectExpenseRequest, PayExpenseRequest,
|
||||
} from '../models/expense.model';
|
||||
|
||||
export interface ExpenseQuery {
|
||||
page?: number; pageSize?: number; search?: string; ministryId?: number;
|
||||
categoryGroupId?: number; status?: string; from?: string; to?: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ExpenseApiService {
|
||||
private readonly endpoint: string;
|
||||
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
|
||||
this.endpoint = apiConfig.getApiUrl('expenses');
|
||||
}
|
||||
private toParams(q: Record<string, unknown>): HttpParams {
|
||||
let p = new HttpParams();
|
||||
for (const [k, v] of Object.entries(q)) if (v !== undefined && v !== null && v !== '') p = p.set(k, String(v));
|
||||
return p;
|
||||
}
|
||||
getPaged(q: ExpenseQuery): Observable<PagedResult<ExpenseListItemDto>> {
|
||||
return this.http.get<PagedResult<ExpenseListItemDto>>(this.endpoint, { params: this.toParams(q as Record<string, unknown>) });
|
||||
}
|
||||
getMine(status?: string, page = 1, pageSize = 50): Observable<PagedResult<ExpenseListItemDto>> {
|
||||
return this.http.get<PagedResult<ExpenseListItemDto>>(`${this.endpoint}/mine`, { params: this.toParams({ status, page, pageSize }) });
|
||||
}
|
||||
getById(id: number): Observable<ExpenseDto> { return this.http.get<ExpenseDto>(`${this.endpoint}/${id}`); }
|
||||
create(r: CreateExpenseRequest): Observable<{ id: number }> { return this.http.post<{ id: number }>(this.endpoint, r); }
|
||||
update(id: number, r: UpdateExpenseRequest): Observable<void> { return this.http.put<void>(`${this.endpoint}/${id}`, r); }
|
||||
delete(id: number): Observable<void> { return this.http.delete<void>(`${this.endpoint}/${id}`); }
|
||||
submit(id: number): Observable<void> { return this.http.post<void>(`${this.endpoint}/${id}/submit`, {}); }
|
||||
approve(id: number): Observable<void> { return this.http.post<void>(`${this.endpoint}/${id}/approve`, {}); }
|
||||
reject(id: number, r: RejectExpenseRequest): Observable<void> { return this.http.post<void>(`${this.endpoint}/${id}/reject`, r); }
|
||||
pay(id: number, r: PayExpenseRequest): Observable<void> { return this.http.post<void>(`${this.endpoint}/${id}/pay`, r); }
|
||||
uploadReceipt(id: number, file: File): Observable<void> {
|
||||
const form = new FormData(); form.append('file', file);
|
||||
return this.http.post<void>(`${this.endpoint}/${id}/receipt`, form);
|
||||
}
|
||||
receiptUrl(id: number): string { return `${this.endpoint}/${id}/receipt`; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 {
|
||||
ExpenseCategoryGroupDto, CreateExpenseGroupRequest, UpdateExpenseGroupRequest,
|
||||
CreateExpenseSubCategoryRequest, UpdateExpenseSubCategoryRequest,
|
||||
} from '../models/expense.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ExpenseCategoryApiService {
|
||||
private readonly endpoint: string;
|
||||
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
|
||||
this.endpoint = apiConfig.getApiUrl('expense-categories');
|
||||
}
|
||||
getAll(includeInactive = false): Observable<ExpenseCategoryGroupDto[]> {
|
||||
return this.http.get<ExpenseCategoryGroupDto[]>(this.endpoint, { params: new HttpParams().set('includeInactive', includeInactive) });
|
||||
}
|
||||
createGroup(r: CreateExpenseGroupRequest): Observable<{ id: number }> { return this.http.post<{ id: number }>(`${this.endpoint}/groups`, r); }
|
||||
updateGroup(id: number, r: UpdateExpenseGroupRequest): Observable<void> { return this.http.put<void>(`${this.endpoint}/groups/${id}`, r); }
|
||||
deactivateGroup(id: number): Observable<void> { return this.http.delete<void>(`${this.endpoint}/groups/${id}`); }
|
||||
createSub(r: CreateExpenseSubCategoryRequest): Observable<{ id: number }> { return this.http.post<{ id: number }>(`${this.endpoint}/subcategories`, r); }
|
||||
updateSub(id: number, r: UpdateExpenseSubCategoryRequest): Observable<void> { return this.http.put<void>(`${this.endpoint}/subcategories/${id}`, r); }
|
||||
deactivateSub(id: number): Observable<void> { return this.http.delete<void>(`${this.endpoint}/subcategories/${id}`); }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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 { MinistryDto } from '../models/expense.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MinistryApiService {
|
||||
private readonly endpoint: string;
|
||||
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
|
||||
this.endpoint = apiConfig.getApiUrl('ministries');
|
||||
}
|
||||
getAll(includeInactive = false): Observable<MinistryDto[]> {
|
||||
return this.http.get<MinistryDto[]>(this.endpoint, { params: new HttpParams().set('includeInactive', includeInactive) });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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 { MonthlyStatementDto, CreateMonthlyStatementRequest, UpdateMonthlyStatementRequest } from '../models/expense.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MonthlyStatementApiService {
|
||||
private readonly endpoint: string;
|
||||
constructor(private http: HttpClient, apiConfig: ApiConfigService) {
|
||||
this.endpoint = apiConfig.getApiUrl('monthly-statements');
|
||||
}
|
||||
getAll(year?: number): Observable<MonthlyStatementDto[]> {
|
||||
let p = new HttpParams(); if (year) p = p.set('year', year);
|
||||
return this.http.get<MonthlyStatementDto[]>(this.endpoint, { params: p });
|
||||
}
|
||||
getById(id: number): Observable<MonthlyStatementDto> { return this.http.get<MonthlyStatementDto>(`${this.endpoint}/${id}`); }
|
||||
create(r: CreateMonthlyStatementRequest): Observable<{ id: number }> { return this.http.post<{ id: number }>(this.endpoint, r); }
|
||||
update(id: number, r: UpdateMonthlyStatementRequest): Observable<void> { return this.http.put<void>(`${this.endpoint}/${id}`, r); }
|
||||
finalize(id: number): Observable<void> { return this.http.post<void>(`${this.endpoint}/${id}/finalize`, {}); }
|
||||
}
|
||||
Reference in New Issue
Block a user