feat(client): 完成 P3 业务页联动闭环(D6/D8/D10/D11)

- 待办:列表/详情 + approve/reject/return/transfer(D6)
- 报销:列表/详情 + create/submit/withdraw/pay(D8)
- 票据:列表/上传/OCR轮询/绑定/预览(D10)
- 统一:Api types + 错误映射 + react-query缓存失效(D11)
这个提交包含在:
2026-02-08 23:08:41 +08:00
父节点 65e7b37ebd
当前提交 7a6f5573a6
共修改 16 个文件,包含 1762 行新增和 31 行删除
+152
查看文件
@@ -0,0 +1,152 @@
import { apiRequestData } from "./http";
export type ReimbursementStatus =
| "draft"
| "reviewing"
| "approved"
| "paid"
| "rejected"
| "cancelled";
export type ReimbursementListItem = {
id: string;
formNumber: string | null;
status: ReimbursementStatus;
reimbursementDate: string | null;
totalAmount: string | null;
description: string | null;
payment: {
paymentVoucherNo: string | null;
paymentDate: string | null;
paymentChannel: string | null;
paidAt: string | null;
paidBy: string | null;
};
owner: {
id: string;
employeeNo: string;
name: string;
} | null;
submitter: {
id: string;
employeeNo: string;
name: string;
} | null;
createdAt: string | null;
updatedAt: string | null;
};
export type ReimbursementListResponse = {
page: number;
pageSize: number;
total: number;
scope: "own" | "all";
items: ReimbursementListItem[];
};
export type ReimbursementDetail = {
id: string;
formNumber: string | null;
submitterId: string;
ownerId: string;
departmentId: string;
status: ReimbursementStatus;
reimbursementDate: string | null;
totalAmount: string | null;
description: string | null;
owner: {
id: string;
employee_no: string;
name: string;
} | null;
submitter: {
id: string;
employee_no: string;
name: string;
} | null;
invoices: Array<{
id: string;
status: string;
invoiceCode: string | null;
invoiceNumber: string | null;
totalAmount: string | null;
}>;
pendingWorkflow: {
instanceId: string;
stepId: string;
task: {
id: string;
step_name: string;
assignee_id: string | null;
assignee_role: string | null;
} | null;
} | null;
};
export type ReimbursementListQuery = {
page?: number;
pageSize?: number;
status?: ReimbursementStatus;
scope?: "own" | "all";
};
export type ReimbursementPayPayload = {
paymentVoucherNo?: string;
paymentDate?: string;
paymentChannel?: "bank" | "alipay" | "wechat" | "cash" | "other";
};
function toQueryString(query: Record<string, string | number | undefined>) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (value == null || value === "") {
continue;
}
params.set(key, String(value));
}
const encoded = params.toString();
return encoded ? `?${encoded}` : "";
}
export async function fetchReimbursements(query: ReimbursementListQuery = {}) {
return apiRequestData<ReimbursementListResponse>(
`/api/reimbursements${toQueryString(query as Record<string, string | number | undefined>)}`,
);
}
export async function fetchReimbursementDetail(reimbursementId: string) {
return apiRequestData<ReimbursementDetail>(`/api/reimbursements/${reimbursementId}`);
}
export async function createReimbursement(payload: {
reimbursementDate?: string;
description?: string;
} = {}) {
return apiRequestData<{ id: string }>(`/api/reimbursements`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function submitReimbursement(reimbursementId: string) {
return apiRequestData<Record<string, unknown>>(`/api/reimbursements/${reimbursementId}/submit`, {
method: "POST",
body: JSON.stringify({}),
});
}
export async function withdrawReimbursement(reimbursementId: string, comment?: string) {
return apiRequestData<Record<string, unknown>>(`/api/reimbursements/${reimbursementId}/withdraw`, {
method: "POST",
body: JSON.stringify({ comment }),
});
}
export async function payReimbursement(reimbursementId: string, payload: ReimbursementPayPayload = {}) {
return apiRequestData<Record<string, unknown>>(`/api/reimbursements/${reimbursementId}/pay`, {
method: "POST",
body: JSON.stringify(payload),
});
}