R111/A7-A10 统一报销双端发票附件体验
这个提交包含在:
@@ -62,9 +62,12 @@ export type InvoicePoolListResponse = {
|
||||
};
|
||||
|
||||
export type UploadInvoiceResponse = {
|
||||
invoicePoolId: string;
|
||||
invoicePoolId: string | null;
|
||||
status: InvoicePoolStatus;
|
||||
fileSha256: string;
|
||||
uploadAction?: "create" | "reuse_existing" | "blocked";
|
||||
unavailableReason?: string | null;
|
||||
unavailableMessage?: string | null;
|
||||
};
|
||||
|
||||
export type InvoicePoolDetail = {
|
||||
@@ -136,6 +139,31 @@ export type BindableInvoiceListResponse = {
|
||||
items: BindableInvoiceSummary[];
|
||||
};
|
||||
|
||||
export type InvoiceStatusItem = {
|
||||
id: string;
|
||||
status: InvoicePoolStatus;
|
||||
reason: string;
|
||||
message: string;
|
||||
invoiceCode: string | null;
|
||||
invoiceNumber: string | null;
|
||||
invoiceDate: string | null;
|
||||
sellerName: string | null;
|
||||
totalAmount: string | null;
|
||||
originalName: string | null;
|
||||
linkedReimbursementId: string | null;
|
||||
linkedItemId: string | null;
|
||||
linkedTravelOrderId: string | null;
|
||||
ocrAttempts: number;
|
||||
ocrLastError: string | null;
|
||||
ocrCompletedAt: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type InvoiceStatusResponse = {
|
||||
items: InvoiceStatusItem[];
|
||||
missingIds: string[];
|
||||
};
|
||||
|
||||
function toQueryString(query: Record<string, string | number | undefined>) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -170,6 +198,12 @@ export async function fetchBindableInvoices(query: {
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchInvoiceStatuses(ids: string[]) {
|
||||
return apiRequestData<InvoiceStatusResponse>(
|
||||
`/api/invoice-pool/status${toQueryString({ ids: ids.join(",") })}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function uploadInvoice(
|
||||
file: File,
|
||||
ocrType?: string,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { apiRequestData } from "./http";
|
||||
import { apiRequestBlob, apiRequestData } from "./http";
|
||||
import type { ExpenseStandardCheck, PrecheckResponse, RiskFlag } from "./feeControl";
|
||||
|
||||
export type ReimbursementStatus =
|
||||
@@ -165,6 +165,19 @@ export type ReimbursementDetail = {
|
||||
originalName: string | null;
|
||||
linkedItemId: string | null;
|
||||
}>;
|
||||
attachments: Array<{
|
||||
id: string;
|
||||
originalName: string;
|
||||
mimeType: string;
|
||||
fileSize: number;
|
||||
fileSha256: string;
|
||||
uploadedBy: {
|
||||
id: string;
|
||||
name: string;
|
||||
employeeNo: string;
|
||||
} | null;
|
||||
createdAt: string | null;
|
||||
}>;
|
||||
budgetHint: ReimbursementBudgetHint | null;
|
||||
pendingWorkflow: {
|
||||
instanceId: string;
|
||||
@@ -463,6 +476,38 @@ export async function deleteReimbursementItem(reimbursementId: string, itemId: s
|
||||
);
|
||||
}
|
||||
|
||||
export async function uploadReimbursementAttachments(reimbursementId: string, files: File[]) {
|
||||
const formData = new FormData();
|
||||
for (const file of files) {
|
||||
formData.append("files", file);
|
||||
}
|
||||
|
||||
return apiRequestData<{ items: ReimbursementDetail["attachments"] }>(
|
||||
`/api/reimbursements/${reimbursementId}/attachments`,
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteReimbursementAttachment(reimbursementId: string, attachmentId: string) {
|
||||
return apiRequestData<{ attachmentId: string; outcome: string }>(
|
||||
`/api/reimbursements/${reimbursementId}/attachments/${attachmentId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function reimbursementAttachmentFileUrl(reimbursementId: string, attachmentId: string) {
|
||||
return `/api/reimbursements/${reimbursementId}/attachments/${attachmentId}/file`;
|
||||
}
|
||||
|
||||
export async function downloadReimbursementAttachment(reimbursementId: string, attachmentId: string) {
|
||||
return apiRequestBlob(reimbursementAttachmentFileUrl(reimbursementId, attachmentId));
|
||||
}
|
||||
|
||||
|
||||
export async function linkReimbursementProject(reimbursementId: string, projectId: string | null) {
|
||||
return apiRequestData<{ projectId: string | null; project: { id: string; projectNo: string; name: string } | null }>(`/api/reimbursements/${reimbursementId}/link-project`, {
|
||||
|
||||
在新工单中引用
屏蔽一个用户