feat(1099): add 1099 recipient picker to expense form

Add optional payeeId to CreateExpenseRequest + ExpenseListItemDto
frontend models. In the expense form dialog: inject Payee1099ApiService,
load active payees on init, add payeeId to the form state, pre-populate
it from expense.payeeId in edit mode, and include it in the emitSave
payload. Render a "1099 Recipient / 1099 收款人" Kendo DropdownList
(textField=legalName, valueField=id, [valuePrimitive]="true",
md:col-span-2) inside the vendor-mode ng-container below Vendor Name
and Check #.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-06-25 17:50:25 -07:00
parent d8e6f3ed61
commit fb95bf0048
2 changed files with 16 additions and 1 deletions
@@ -158,7 +158,7 @@
</div>
<!-- Vendor mode: vendor name + check number -->
<!-- Vendor mode: vendor name + check number + optional 1099 recipient -->
<ng-container *ngIf="mode === 'vendor'">
<label class="flex flex-col gap-1">Vendor Name
<kendo-textbox [(ngModel)]="form.vendorName" placeholder="Payee / vendor name"></kendo-textbox>
@@ -166,6 +166,12 @@
<label class="flex flex-col gap-1">Check #
<kendo-textbox [(ngModel)]="form.checkNumber" placeholder="Check number (optional)"></kendo-textbox>
</label>
<label class="flex flex-col gap-1 md:col-span-2">1099 Recipient / 1099 收款人 <span class="text-gray-400 font-normal">(optional)</span>
<kendo-dropdownlist [data]="payees" textField="legalName" valueField="id" [valuePrimitive]="true"
[defaultItem]="{ id: null, legalName: ' none ' }"
[(ngModel)]="form.payeeId">
</kendo-dropdownlist>
</label>
</ng-container>
<!-- Reimbursement mode: receipt file input -->
@@ -15,6 +15,8 @@ import { ExpenseSnapshotDto, CreateExpenseSnapshotRequest } from '../../models/e
import { ExpenseAiService } from '../../services/expense-ai.service';
import { MemberApiService } from '../../../members/services/member-api.service';
import { MemberListItemDto, memberDisplayName } from '../../../members/models/member.model';
import { Payee1099ApiService } from '../../../payee1099/services/payee1099-api.service';
import { Payee1099ListItem } from '../../../payee1099/models/payee1099.model';
import {
MinistryDto, ExpenseCategoryGroupDto, ExpenseSubCategoryDto, ExpenseType, CreateExpenseRequest,
ExpenseDto, FunctionalClass, ExpenseAiSuggestion,
@@ -66,6 +68,8 @@ export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
ministries: MinistryDto[] = [];
groups: ExpenseCategoryGroupDto[] = [];
payees: Payee1099ListItem[] = [];
/** Saved snapshots (vendor mode only) for the "Load from snapshot" picker. */
snapshots: ExpenseSnapshotDto[] = [];
/** Picker binding; reset to null after each apply so the same snapshot can be re-picked. */
@@ -95,6 +99,7 @@ export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
vendorName: '',
checkNumber: '',
memberId: null as number | null,
payeeId: null as number | null,
expenseDate: new Date(),
};
/** At least one line always; "+ Add line" appends, each line is independently removable down to one. */
@@ -133,11 +138,13 @@ export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
private expenseApi: ExpenseApiService,
private snapshotApi: ExpenseSnapshotApiService,
private aiApi: ExpenseAiService,
private payeeApi: Payee1099ApiService,
private sanitizer: DomSanitizer,
) {}
ngOnInit(): void {
this.ministryApi.getAll().subscribe(m => (this.ministries = m));
this.payeeApi.getAll(false).subscribe(list => (this.payees = list));
if (this.showSnapshotTools) this.loadSnapshots();
this.catApi.getAll(false).subscribe(groups => {
this.groups = groups;
@@ -169,6 +176,7 @@ export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
vendorName: expense.vendorName ?? '',
checkNumber: expense.checkNumber ?? '',
memberId: expense.memberId,
payeeId: expense.payeeId ?? null,
expenseDate: new Date(year, month - 1, day),
};
this.lines = (expense.lines ?? []).map(l => ({
@@ -424,6 +432,7 @@ export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
checkNumber: this.mode === 'vendor' ? (this.form.checkNumber || null) : null,
expenseDate,
notes: null,
payeeId: this.form.payeeId,
};
// The request and receipt are snapshotted here, so resetting the form right
// after emitting is safe even though the parent saves asynchronously.