实现业务附件受控入库 V1(05-28-business-attachment-governance-v1)

这个提交包含在:
2026-05-28 19:44:15 +08:00
父节点 2c067866ef
当前提交 52e5284954
共修改 25 个文件,包含 1119 行新增和 21 行删除
@@ -31,6 +31,8 @@ import { remindWorkflowTask } from "../api/workflow";
import type { PrecheckResponse } from "../api/feeControl";
import { useAuth } from "../auth/AuthContext";
import { hasPermission } from "../auth/permissions";
import { AttachmentGovernanceBadges } from "../components/common/AttachmentGovernanceBadges";
import { useAttachmentDownloadReasonDialog } from "../components/common/useAttachmentDownloadReasonDialog";
import { ConfirmDialog } from "../components/common/ConfirmDialog";
import { MobileIcon } from "../components/mobile/MobileIcon";
import { MobileDetailHeader } from "../components/mobile/MobileDetailHeader";
@@ -55,6 +57,7 @@ import {
} from "../lib/invoiceStatus";
import { resolveErrorMessage } from "../lib/resolveErrorMessage";
import { resolveReimbursementDisplayCode } from "../lib/reimbursementDisplay";
import type { AttachmentGovernanceFields } from "../lib/attachmentGovernance";
import { TEST_IDS } from "../lib/testIds";
import { withUploadTimeout } from "../lib/uploadTimeout";
import { useInvoiceOcrTracker } from "../hooks/useInvoiceOcrTracker";
@@ -671,8 +674,11 @@ export function MobileReimbursementDetailPage() {
},
});
const { requestDownloadReason, downloadReasonDialog } = useAttachmentDownloadReasonDialog();
const openAttachmentMutation = useMutation({
mutationFn: (attachmentId: string) => downloadReimbursementAttachment(reimbursementId, attachmentId),
mutationFn: ({ attachmentId, reason }: { attachmentId: string; reason?: string }) =>
downloadReimbursementAttachment(reimbursementId, attachmentId, reason),
onSuccess: (result) => {
const url = URL.createObjectURL(result.blob);
window.open(url, "_blank", "noopener,noreferrer");
@@ -683,6 +689,14 @@ export function MobileReimbursementDetailPage() {
},
});
async function handleOpenAttachment(attachment: { id: string; originalName?: string } & AttachmentGovernanceFields) {
const reason = await requestDownloadReason(attachment);
if (reason === null) {
return;
}
openAttachmentMutation.mutate({ attachmentId: attachment.id, reason });
}
const handleSubmit = async () => {
if (!detail) {
return;
@@ -811,6 +825,7 @@ export function MobileReimbursementDetailPage() {
)}
data-testid="mobile-reimbursement-detail"
>
{downloadReasonDialog}
<MobileDetailHeader
title={displayCode}
subtitle="报销详情"
@@ -1078,6 +1093,7 @@ export function MobileReimbursementDetailPage() {
<div className="mt-1 text-xs text-zinc-500">
{Math.ceil(attachment.fileSize / 1024)} KB · {formatDateTime(attachment.createdAt)}
</div>
<AttachmentGovernanceBadges attachment={attachment} className="mt-2" />
</div>
<div className="mobile-two-action-grid mt-3">
<Button
@@ -1085,7 +1101,7 @@ export function MobileReimbursementDetailPage() {
variant="secondary"
type="button"
disabled={openAttachmentMutation.isPending}
onClick={() => openAttachmentMutation.mutate(attachment.id)}
onClick={() => void handleOpenAttachment(attachment)}
>
查看
</Button>