feat(client): 完成 P3 业务页联动闭环(D6/D8/D10/D11)
- 待办:列表/详情 + approve/reject/return/transfer(D6) - 报销:列表/详情 + create/submit/withdraw/pay(D8) - 票据:列表/上传/OCR轮询/绑定/预览(D10) - 统一:Api types + 错误映射 + react-query缓存失效(D11)
这个提交包含在:
@@ -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),
|
||||
});
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户