文件
hyb-oa/client/src/api/paymentBatches.ts
T

239 行
7.7 KiB
TypeScript

import { apiRequestBlob, apiRequestData, type ApiBlobResponse } from "./http";
import { buildAttachmentFileUrl, type AttachmentGovernanceFields } from "../lib/attachmentGovernance";
import type { RiskFlag } from "./feeControl";
export type PaymentBatchEntityType = "reimbursement" | "travel_order";
export type PaymentBatchStatus = "draft" | "reviewed" | "executed" | "failed" | "cancelled";
export type PaymentChannel = "bank" | "alipay" | "wechat" | "cash" | "other";
export type PaymentBatchItem = {
id: string;
entityId: string;
entityCode: string;
entityTitle: string;
amount: string;
currencyCode: string;
paymentVoucherNoOverride: string | null;
paymentDateOverride: string | null;
paymentChannelOverride: PaymentChannel | null;
payeeName: string | null;
bankName: string | null;
bankAccountMasked: string | null;
status: string;
failureCode: string | null;
failureMessage: string | null;
createdAt: string | null;
openRiskFlags: RiskFlag[];
};
export type PaymentBatch = {
id: string;
batchNo: string;
entityType: PaymentBatchEntityType;
name: string;
status: PaymentBatchStatus;
defaultPaymentDate: string | null;
defaultPaymentChannel: PaymentChannel | null;
voucherPrefix: string | null;
totalAmount: string;
highRisk: boolean;
reviewRequirement: {
thresholdAmount: string;
requiredReviewCount: number;
completedReviewCount: number;
nextStep: "first_review" | "ready_for_execute";
makerCheckerRequired: boolean;
reviewerPermission: string;
};
reviewComment: string | null;
secondaryReviewComment: string | null;
executionSummaryJson: Record<string, unknown> | null;
receiptAttachmentCount?: number;
paymentAccount: {
accountName: string;
bankName: string;
bankInstitutionCode: string;
accountNumberMasked: string;
} | null;
paymentOrderExportCount: number;
paymentOrderLastExportedAt: string | null;
paymentOrderLastExportedBy: string | null;
canExportPaymentOrder: boolean;
paymentOrderExportBlockReason: string | null;
paymentOrderExportBlockers: Array<{ code: string; entityCode?: string }>;
createdAt: string | null;
createdBy: string | null;
reviewedAt: string | null;
reviewedBy: string | null;
secondaryReviewedAt: string | null;
secondaryReviewedBy: string | null;
executedAt: string | null;
executedBy: string | null;
cancelledAt: string | null;
cancelledBy: string | null;
cancelledReason: string | null;
items: PaymentBatchItem[];
};
export type PaymentBatchCandidate = {
entityId: string;
entityType: "reimbursement";
businessNo: string;
employee: {
id: string;
employeeNo: string;
name: string;
};
amount: string;
currencyCode: "CNY";
businessDate: string | null;
summary: string | null;
};
export type PaymentBatchCandidatesResponse = {
items: PaymentBatchCandidate[];
page: number;
pageSize: number;
total: number;
};
export type PaymentBatchReceiptAttachment = {
id: string;
originalName: string;
mimeType: string;
fileSize: number;
fileSha256: string;
} & AttachmentGovernanceFields & {
uploadedBy: {
id: string;
employeeNo: string;
name: string;
} | null;
createdAt: string | null;
};
export type PaymentBatchListResponse = { items: PaymentBatch[] };
export async function fetchPaymentBatches(query: { entityType?: PaymentBatchEntityType; status?: PaymentBatchStatus } = {}) {
const params = new URLSearchParams();
if (query.entityType) params.set("entityType", query.entityType);
if (query.status) params.set("status", query.status);
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiRequestData<PaymentBatchListResponse>(`/api/payment-batches${suffix}`);
}
export async function fetchPaymentBatchDetail(batchId: string) {
return apiRequestData<{ item: PaymentBatch }>(`/api/payment-batches/${batchId}`);
}
export async function fetchPaymentBatchCandidates(query: { q?: string; page?: number; pageSize?: number } = {}) {
const params = new URLSearchParams();
if (query.q?.trim()) params.set("q", query.q.trim());
if (query.page) params.set("page", String(query.page));
if (query.pageSize) params.set("pageSize", String(query.pageSize));
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiRequestData<PaymentBatchCandidatesResponse>(`/api/payment-batches/candidates${suffix}`);
}
export async function createPaymentBatch(payload: {
entityType: PaymentBatchEntityType;
name?: string;
entityIds: string[];
defaultPaymentDate?: string;
defaultPaymentChannel?: PaymentChannel;
voucherPrefix?: string;
}) {
return apiRequestData<{ item: PaymentBatch }>("/api/payment-batches", {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function updatePaymentBatchItem(batchId: string, itemId: string, payload: {
paymentVoucherNoOverride?: string | null;
paymentDateOverride?: string | null;
}) {
return apiRequestData<{ item: Pick<PaymentBatchItem, "id" | "paymentVoucherNoOverride" | "paymentDateOverride"> }>(
`/api/payment-batches/${batchId}/items/${itemId}`,
{
method: "PATCH",
body: JSON.stringify(payload),
},
);
}
export async function reviewPaymentBatch(batchId: string, payload: { comment?: string; resolveRiskFlagIds?: string[] } = {}) {
return apiRequestData<{ item: PaymentBatch }>(`/api/payment-batches/${batchId}/review`, {
method: "POST",
body: JSON.stringify(payload),
});
}
export async function executePaymentBatch(batchId: string) {
return apiRequestData<{ item: PaymentBatch }>(`/api/payment-batches/${batchId}/execute`, {
method: "POST",
body: JSON.stringify({}),
});
}
export async function cancelPaymentBatch(batchId: string, reason: string) {
return apiRequestData<{ item: PaymentBatch }>(`/api/payment-batches/${batchId}/cancel`, {
method: "POST",
body: JSON.stringify({ reason }),
});
}
export async function downloadPaymentOrder(batchId: string, reason: string): Promise<ApiBlobResponse> {
return apiRequestBlob(`/api/payment-batches/${batchId}/payment-order`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ reason }),
});
}
export async function revealPaymentBatchPayeeBank(batchId: string, itemId: string, reason: string) {
return apiRequestData<{ bankAccount: string }>(
`/api/payment-batches/${batchId}/items/${itemId}/payee-bank/reveal`,
{ method: "POST", body: JSON.stringify({ reason }) },
);
}
export async function fetchPaymentBatchReceiptAttachments(batchId: string) {
return apiRequestData<{ items: PaymentBatchReceiptAttachment[] }>(
`/api/payment-batches/${batchId}/receipt-attachments`,
);
}
export async function uploadPaymentBatchReceiptAttachments(batchId: string, files: File[]) {
const formData = new FormData();
for (const file of files) {
formData.append("files", file);
}
return apiRequestData<{ items: PaymentBatchReceiptAttachment[] }>(
`/api/payment-batches/${batchId}/receipt-attachments`,
{
method: "POST",
body: formData,
},
);
}
export function paymentBatchReceiptAttachmentFileUrl(batchId: string, attachmentId: string) {
return `/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/file`;
}
export async function downloadPaymentBatchReceiptAttachment(
batchId: string,
attachmentId: string,
reason?: string,
): Promise<ApiBlobResponse> {
return apiRequestBlob(buildAttachmentFileUrl(paymentBatchReceiptAttachmentFileUrl(batchId, attachmentId), reason));
}
export async function voidPaymentBatchReceiptAttachment(batchId: string, attachmentId: string, reason: string) {
return apiRequestData<{ attachmentId: string; outcome: "voided"; voidedAt: string }>(
`/api/payment-batches/${batchId}/receipt-attachments/${attachmentId}/void`,
{ method: "POST", body: JSON.stringify({ reason }) },
);
}