diff --git a/APP/src/app/app.routes.ts b/APP/src/app/app.routes.ts
index e99500b..3835603 100644
--- a/APP/src/app/app.routes.ts
+++ b/APP/src/app/app.routes.ts
@@ -20,6 +20,7 @@ import { FinanceDashboardPageComponent } from './features/finance-dashboard/page
import { DisbursementPageComponent } from './features/disbursement/pages/disbursement-page/disbursement-page.component';
import { CheckRegisterPageComponent } from './features/disbursement/pages/check-register-page/check-register-page.component';
import { ChurchProfilePageComponent } from './features/disbursement/pages/church-profile-page/church-profile-page.component';
+import { Form990ReportPageComponent } from './features/finance-report/pages/form990-report-page/form990-report-page.component';
import { AttendanceCounterPageComponent } from './features/meal-attendance/pages/attendance-counter-page/attendance-counter-page.component';
import { OfferingEntryMobilePageComponent } from './features/giving/pages/offering-entry-mobile-page/offering-entry-mobile-page.component';
import { SystemLogsPageComponent } from './features/logging/pages/system-logs-page/system-logs-page.component';
@@ -206,6 +207,15 @@ export const routes: Routes = [
title: 'Church Profile', titleZh: '教會資料', section: 'Finance',
},
},
+ {
+ path: 'finance/form-990-report',
+ component: Form990ReportPageComponent,
+ canActivate: [PermissionGuard],
+ data: {
+ permission: { module: PermissionModules.Form990Report, action: 'read' },
+ title: 'Form 990 — Functional Expenses', titleZh: 'Form 990 功能性費用表', section: 'Finance',
+ },
+ },
]
},
diff --git a/APP/src/app/features/finance-report/models/form990-report.model.ts b/APP/src/app/features/finance-report/models/form990-report.model.ts
index d214d5f..2479b56 100644
--- a/APP/src/app/features/finance-report/models/form990-report.model.ts
+++ b/APP/src/app/features/finance-report/models/form990-report.model.ts
@@ -6,3 +6,22 @@ export interface Form990ExpenseLineDto {
sortOrder: number;
label?: string; // bilingual "code — name", filled by service
}
+
+export interface FunctionalExpenseRowDto {
+ lineCode: string;
+ name_en: string;
+ name_zh: string | null;
+ program: number;
+ managementGeneral: number;
+ fundraising: number;
+ total: number;
+}
+
+export interface FunctionalExpenseStatementDto {
+ rows: FunctionalExpenseRowDto[];
+ programTotal: number;
+ managementGeneralTotal: number;
+ fundraisingTotal: number;
+ grandTotal: number;
+ unmappedExpenseCount: number;
+}
diff --git a/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.html b/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.html
new file mode 100644
index 0000000..db32c77
--- /dev/null
+++ b/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+ {{ statement?.unmappedExpenseCount }} expense(s) have no Form 990 mapping — counted under line 24.
+ 尚有支出未對應 990 行,已暫計入 line 24。
+
+
+
+
+
+
+
+
+
+
+
+
+ Program: {{ statement.programTotal | currency }}
+ M&G: {{ statement.managementGeneralTotal | currency }}
+ Fundraising: {{ statement.fundraisingTotal | currency }}
+ Total: {{ statement.grandTotal | currency }}
+
+
+
+
+
+
{{ row.lineCode }} — {{ row.name_en }}
+
Program{{ row.program | currency }}
+
M&G{{ row.managementGeneral | currency }}
+
Fundraising{{ row.fundraising | currency }}
+
Total{{ row.total | currency }}
+
+
diff --git a/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.ts b/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.ts
new file mode 100644
index 0000000..4558058
--- /dev/null
+++ b/APP/src/app/features/finance-report/pages/form990-report-page/form990-report-page.component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { GridModule } from '@progress/kendo-angular-grid';
+import { DatePickerModule } from '@progress/kendo-angular-dateinputs';
+import { ButtonsModule } from '@progress/kendo-angular-buttons';
+import { Form990ReportApiService } from '../../services/form990-report-api.service';
+import { FunctionalExpenseStatementDto } from '../../models/form990-report.model';
+
+@Component({
+ selector: 'app-form990-report-page',
+ standalone: true,
+ imports: [CommonModule, FormsModule, GridModule, DatePickerModule, ButtonsModule],
+ templateUrl: './form990-report-page.component.html',
+})
+export class Form990ReportPageComponent implements OnInit {
+ from: Date = new Date(new Date().getFullYear(), 0, 1);
+ to: Date = new Date(new Date().getFullYear(), 11, 31);
+ statement: FunctionalExpenseStatementDto | null = null;
+ loading = false;
+
+ constructor(private api: Form990ReportApiService) {}
+
+ ngOnInit(): void {
+ this.load();
+ }
+
+ load(): void {
+ this.loading = true;
+ const fmt = (date: Date): string => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ };
+ this.api.getFunctionalExpenses(fmt(this.from), fmt(this.to)).subscribe({
+ next: (statement) => {
+ this.statement = statement;
+ this.loading = false;
+ },
+ error: () => {
+ this.loading = false;
+ },
+ });
+ }
+}
diff --git a/APP/src/app/features/finance-report/services/form990-report-api.service.ts b/APP/src/app/features/finance-report/services/form990-report-api.service.ts
new file mode 100644
index 0000000..200935d
--- /dev/null
+++ b/APP/src/app/features/finance-report/services/form990-report-api.service.ts
@@ -0,0 +1,24 @@
+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 { FunctionalExpenseStatementDto } from '../models/form990-report.model';
+
+@Injectable({ providedIn: 'root' })
+export class Form990ReportApiService {
+ private readonly endpoint: string;
+
+ constructor(private http: HttpClient, apiConfig: ApiConfigService) {
+ this.endpoint = apiConfig.getApiUrl('form990-report');
+ }
+
+ getFunctionalExpenses(from?: string, to?: string): Observable {
+ let params = new HttpParams();
+ if (from) { params = params.set('from', from); }
+ if (to) { params = params.set('to', to); }
+ return this.http.get(
+ `${this.endpoint}/functional-expenses`,
+ { params }
+ );
+ }
+}
diff --git a/APP/src/app/portals/user-portal/user-portal.component.ts b/APP/src/app/portals/user-portal/user-portal.component.ts
index 6e77afc..4399781 100644
--- a/APP/src/app/portals/user-portal/user-portal.component.ts
+++ b/APP/src/app/portals/user-portal/user-portal.component.ts
@@ -108,6 +108,8 @@ export class UserPortalComponent implements OnInit, OnDestroy {
permission: { module: PermissionModules.FinanceDashboard, action: 'read' } },
{ text: 'Monthly Statement', icon: fileReportIcon, path: '/user-portal/finance/monthly-statement',
permission: { module: PermissionModules.MonthlyStatements, action: 'read' } },
+ { text: 'Form 990 Report', icon: fileReportIcon, path: '/user-portal/finance/form-990-report',
+ permission: { module: PermissionModules.Form990Report, action: 'read' } },
],
},
{