feat(client): 报销明细编辑器与费用类型下拉(F6)

- 报销详情新增明细列表与新增/编辑/删除入口
- 接入费用类型只读接口并保持总金额自动刷新
- 补 data-testid 以便 E2E/回归
这个提交包含在:
2026-02-09 17:54:59 +08:00
父节点 2bb804bc94
当前提交 a4f38792f1
共修改 3 个文件,包含 435 行新增和 0 行删除
+31
查看文件
@@ -0,0 +1,31 @@
import { apiRequestData } from "./http";
export type ExpenseType = {
id: string;
code: string;
name: string;
description: string | null;
category: string;
sortOrder: number;
accountCodeDefault: string | null;
isSystemDefault: boolean;
enabled: boolean;
createdAt: string | null;
updatedAt: string | null;
};
export type ExpenseTypeListResponse = {
enabled: boolean | null;
items: ExpenseType[];
};
export async function fetchExpenseTypes(query: { enabled?: boolean } = {}) {
const params = new URLSearchParams();
if (query.enabled != null) {
params.set("enabled", query.enabled ? "true" : "false");
}
const qs = params.toString();
return apiRequestData<ExpenseTypeListResponse>(`/api/expense-types${qs ? `?${qs}` : ""}`);
}
+75
查看文件
@@ -8,6 +8,21 @@ export type ReimbursementStatus =
| "rejected"
| "cancelled";
export type ReimbursementItem = {
id: string;
expenseTypeId: string;
expenseDate: string | null;
amount: string;
description: string | null;
expenseType: {
id: string;
code: string;
name: string;
category: string;
enabled: boolean;
} | null;
};
export type ReimbursementListItem = {
id: string;
formNumber: string | null;
@@ -81,6 +96,7 @@ export type ReimbursementDetail = {
assignee_role: string | null;
} | null;
} | null;
items: ReimbursementItem[];
};
export type ReimbursementListQuery = {
@@ -96,6 +112,29 @@ export type ReimbursementPayPayload = {
paymentChannel?: "bank" | "alipay" | "wechat" | "cash" | "other";
};
export type ReimbursementItemPayload = {
expenseTypeId: string;
expenseDate: string;
amount: string;
description?: string;
};
export type ReimbursementItemMutationResponse = {
item: {
id: string;
expenseTypeId: string;
expenseDate: string;
amount: string;
description: string | null;
};
totalAmount: string;
};
export type ReimbursementItemDeleteResponse = {
outcome: "deleted";
totalAmount: string;
};
function toQueryString(query: Record<string, string | number | undefined>) {
const params = new URLSearchParams();
@@ -150,3 +189,39 @@ export async function payReimbursement(reimbursementId: string, payload: Reimbur
body: JSON.stringify(payload),
});
}
export async function createReimbursementItem(
reimbursementId: string,
payload: ReimbursementItemPayload,
) {
return apiRequestData<ReimbursementItemMutationResponse>(
`/api/reimbursements/${reimbursementId}/items`,
{
method: "POST",
body: JSON.stringify(payload),
},
);
}
export async function updateReimbursementItem(
reimbursementId: string,
itemId: string,
payload: Partial<ReimbursementItemPayload>,
) {
return apiRequestData<ReimbursementItemMutationResponse>(
`/api/reimbursements/${reimbursementId}/items/${itemId}`,
{
method: "PATCH",
body: JSON.stringify(payload),
},
);
}
export async function deleteReimbursementItem(reimbursementId: string, itemId: string) {
return apiRequestData<ReimbursementItemDeleteResponse>(
`/api/reimbursements/${reimbursementId}/items/${itemId}`,
{
method: "DELETE",
},
);
}