diff --git a/APP/src/app/features/expense/models/expense.model.ts b/APP/src/app/features/expense/models/expense.model.ts
index 0a9ce2c..7c785d3 100644
--- a/APP/src/app/features/expense/models/expense.model.ts
+++ b/APP/src/app/features/expense/models/expense.model.ts
@@ -10,9 +10,9 @@ export interface MinistryDto { id: number; name_en: string; name_zh: string | nu
export interface ExpenseSubCategoryDto { id: number; groupId: number; name_en: string; name_zh: string | null; sortOrder: number; isActive: boolean; label?: string; form990LineId: number | null; form990LineCode: string | null; }
export interface ExpenseCategoryGroupDto { id: number; name_en: string; name_zh: string | null; sortOrder: number; isActive: boolean; subCategories: ExpenseSubCategoryDto[]; label?: string; form990LineId: number | null; form990LineCode: string | null; }
-export interface CreateExpenseGroupRequest { name_en: string; name_zh: string | null; sortOrder: number; }
+export interface CreateExpenseGroupRequest { name_en: string; name_zh: string | null; sortOrder: number; form990LineId: number | null; }
export interface UpdateExpenseGroupRequest extends CreateExpenseGroupRequest { isActive: boolean; }
-export interface CreateExpenseSubCategoryRequest { groupId: number; name_en: string; name_zh: string | null; sortOrder: number; }
+export interface CreateExpenseSubCategoryRequest { groupId: number; name_en: string; name_zh: string | null; sortOrder: number; form990LineId: number | null; }
export interface UpdateExpenseSubCategoryRequest extends CreateExpenseSubCategoryRequest { isActive: boolean; }
export interface ExpenseListItemDto {
diff --git a/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.html b/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.html
index 7c7ff7b..90be8fa 100644
--- a/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.html
+++ b/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.html
@@ -61,6 +61,15 @@
Sort order
+
@@ -89,6 +98,15 @@
Sort order
+
diff --git a/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.ts b/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.ts
index 994fc51..3a395f7 100644
--- a/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.ts
+++ b/APP/src/app/features/expense/pages/expense-categories-page/expense-categories-page.component.ts
@@ -5,14 +5,16 @@ import { GridModule, CellClickEvent, RowClassArgs } from '@progress/kendo-angula
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { DialogsModule } from '@progress/kendo-angular-dialog';
import { InputsModule } from '@progress/kendo-angular-inputs';
+import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { ContextMenuModule, ContextMenuComponent, ContextMenuSelectEvent } from '@progress/kendo-angular-menu';
import { ExpenseCategoryApiService } from '../../services/expense-category-api.service';
import { ExpenseCategoryGroupDto, ExpenseSubCategoryDto } from '../../models/expense.model';
+import { Form990ExpenseLineDto } from '../../../finance-report/models/form990-report.model';
@Component({
selector: 'app-expense-categories-page',
standalone: true,
- imports: [CommonModule, FormsModule, GridModule, ButtonsModule, DialogsModule, InputsModule, ContextMenuModule],
+ imports: [CommonModule, FormsModule, GridModule, ButtonsModule, DialogsModule, InputsModule, DropDownsModule, ContextMenuModule],
templateUrl: './expense-categories-page.component.html',
styleUrls: ['./expense-categories-page.component.scss'],
})
@@ -20,6 +22,7 @@ export class ExpenseCategoriesPageComponent implements OnInit {
groups: ExpenseCategoryGroupDto[] = [];
selectedGroup: ExpenseCategoryGroupDto | null = null;
loading = false;
+ form990Lines: Form990ExpenseLineDto[] = [];
@ViewChild('groupMenu') groupMenu!: ContextMenuComponent;
@ViewChild('subMenu') subMenu!: ContextMenuComponent;
@@ -30,15 +33,18 @@ export class ExpenseCategoriesPageComponent implements OnInit {
groupDialogOpen = false;
editingGroupId: number | null = null;
- groupForm = { name_en: '', name_zh: '', sortOrder: 0, isActive: true };
+ groupForm = { name_en: '', name_zh: '', sortOrder: 0, isActive: true, form990LineId: null as number | null };
subDialogOpen = false;
editingSubId: number | null = null;
- subForm = { name_en: '', name_zh: '', sortOrder: 0, isActive: true };
+ subForm = { name_en: '', name_zh: '', sortOrder: 0, isActive: true, form990LineId: null as number | null };
constructor(private api: ExpenseCategoryApiService) {}
- ngOnInit(): void { this.load(); }
+ ngOnInit(): void {
+ this.load();
+ this.api.getForm990Lines().subscribe(lines => { this.form990Lines = lines; });
+ }
load(): void {
this.loading = true;
@@ -101,16 +107,16 @@ export class ExpenseCategoriesPageComponent implements OnInit {
openNewGroup(): void {
this.editingGroupId = null;
- this.groupForm = { name_en: '', name_zh: '', sortOrder: this.groups.length + 1, isActive: true };
+ this.groupForm = { name_en: '', name_zh: '', sortOrder: this.groups.length + 1, isActive: true, form990LineId: null };
this.groupDialogOpen = true;
}
openEditGroup(g: ExpenseCategoryGroupDto): void {
this.editingGroupId = g.id;
- this.groupForm = { name_en: g.name_en, name_zh: g.name_zh ?? '', sortOrder: g.sortOrder, isActive: g.isActive };
+ this.groupForm = { name_en: g.name_en, name_zh: g.name_zh ?? '', sortOrder: g.sortOrder, isActive: g.isActive, form990LineId: g.form990LineId };
this.groupDialogOpen = true;
}
saveGroup(): void {
- const body = { name_en: this.groupForm.name_en, name_zh: this.groupForm.name_zh || null, sortOrder: this.groupForm.sortOrder };
+ const body = { name_en: this.groupForm.name_en, name_zh: this.groupForm.name_zh || null, sortOrder: this.groupForm.sortOrder, form990LineId: this.groupForm.form990LineId };
const done = () => { this.groupDialogOpen = false; this.load(); };
if (this.editingGroupId == null) this.api.createGroup(body).subscribe(done);
else this.api.updateGroup(this.editingGroupId, { ...body, isActive: this.groupForm.isActive }).subscribe(done);
@@ -123,17 +129,17 @@ export class ExpenseCategoriesPageComponent implements OnInit {
openNewSub(): void {
if (!this.selectedGroup) return;
this.editingSubId = null;
- this.subForm = { name_en: '', name_zh: '', sortOrder: this.subCategories.length + 1, isActive: true };
+ this.subForm = { name_en: '', name_zh: '', sortOrder: this.subCategories.length + 1, isActive: true, form990LineId: null };
this.subDialogOpen = true;
}
openEditSub(s: ExpenseSubCategoryDto): void {
this.editingSubId = s.id;
- this.subForm = { name_en: s.name_en, name_zh: s.name_zh ?? '', sortOrder: s.sortOrder, isActive: s.isActive };
+ this.subForm = { name_en: s.name_en, name_zh: s.name_zh ?? '', sortOrder: s.sortOrder, isActive: s.isActive, form990LineId: s.form990LineId };
this.subDialogOpen = true;
}
saveSub(): void {
if (!this.selectedGroup) return;
- const body = { groupId: this.selectedGroup.id, name_en: this.subForm.name_en, name_zh: this.subForm.name_zh || null, sortOrder: this.subForm.sortOrder };
+ const body = { groupId: this.selectedGroup.id, name_en: this.subForm.name_en, name_zh: this.subForm.name_zh || null, sortOrder: this.subForm.sortOrder, form990LineId: this.subForm.form990LineId };
const done = () => { this.subDialogOpen = false; this.load(); };
if (this.editingSubId == null) this.api.createSub(body).subscribe(done);
else this.api.updateSub(this.editingSubId, { ...body, isActive: this.subForm.isActive }).subscribe(done);
diff --git a/APP/src/app/features/expense/services/expense-category-api.service.ts b/APP/src/app/features/expense/services/expense-category-api.service.ts
index d204878..4052f57 100644
--- a/APP/src/app/features/expense/services/expense-category-api.service.ts
+++ b/APP/src/app/features/expense/services/expense-category-api.service.ts
@@ -7,11 +7,12 @@ import {
ExpenseCategoryGroupDto, CreateExpenseGroupRequest, UpdateExpenseGroupRequest,
CreateExpenseSubCategoryRequest, UpdateExpenseSubCategoryRequest,
} from '../models/expense.model';
+import { Form990ExpenseLineDto } from '../../finance-report/models/form990-report.model';
@Injectable({ providedIn: 'root' })
export class ExpenseCategoryApiService {
private readonly endpoint: string;
- constructor(private http: HttpClient, apiConfig: ApiConfigService) {
+ constructor(private http: HttpClient, private apiConfig: ApiConfigService) {
this.endpoint = apiConfig.getApiUrl('expense-categories');
}
getAll(includeInactive = false): Observable {
@@ -29,4 +30,8 @@ export class ExpenseCategoryApiService {
createSub(r: CreateExpenseSubCategoryRequest): Observable<{ id: number }> { return this.http.post<{ id: number }>(`${this.endpoint}/subcategories`, r); }
updateSub(id: number, r: UpdateExpenseSubCategoryRequest): Observable { return this.http.put(`${this.endpoint}/subcategories/${id}`, r); }
deactivateSub(id: number): Observable { return this.http.delete(`${this.endpoint}/subcategories/${id}`); }
+ getForm990Lines(): Observable {
+ return this.http.get(this.apiConfig.getApiUrl('form990-report') + '/lines')
+ .pipe(map(rows => rows.map(r => ({ ...r, label: `${r.lineCode} — ${r.name_en}${r.name_zh ? ' / ' + r.name_zh : ''}` }))));
+ }
}
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
new file mode 100644
index 0000000..d214d5f
--- /dev/null
+++ b/APP/src/app/features/finance-report/models/form990-report.model.ts
@@ -0,0 +1,8 @@
+export interface Form990ExpenseLineDto {
+ id: number;
+ lineCode: string;
+ name_en: string;
+ name_zh: string | null;
+ sortOrder: number;
+ label?: string; // bilingual "code — name", filled by service
+}