239 行
7.7 KiB
TypeScript
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 }) },
|
|
);
|
|
}
|