R30-1 R30-9: 建立零人工发布门禁并补齐高频回归
这个提交包含在:
@@ -150,7 +150,7 @@ export function FeatureFlagsPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="功能开关"
|
||||
subtitle="统一治理 ERP、验真与请假日历灰度。Env 仍是硬闸,在线开关用于部门级灰度。"
|
||||
subtitle="统一治理外部桥接、验真与请假日历灰度。Env 仍是硬闸,在线开关用于部门级灰度。"
|
||||
/>
|
||||
|
||||
{error ? (
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
uploadPurchaseAttachment,
|
||||
withdrawPurchaseRequest,
|
||||
type PurchaseErpBinding,
|
||||
type PurchaseRequestDetail,
|
||||
type PurchaseRequestErpRequeueTarget,
|
||||
type PurchaseRequestErpTrack,
|
||||
type PurchaseRequestItemPayload,
|
||||
@@ -514,7 +515,23 @@ export function PurchaseRequestsPage() {
|
||||
archivedFiles: archiveOrderQuery.data?.archiveOrder?.archivedFiles ?? [],
|
||||
});
|
||||
},
|
||||
onSuccess: async () => {
|
||||
onSuccess: async (data) => {
|
||||
if (selectedId) {
|
||||
setArchivePoNo(data.archiveOrder.poNo ?? "");
|
||||
setArchiveContractNo(data.archiveOrder.contractNo ?? "");
|
||||
queryClient.setQueryData<{ archiveOrder: Awaited<ReturnType<typeof fetchPurchaseOrderArchive>>["archiveOrder"] }>(
|
||||
["purchase-requests", "archive-order", selectedId],
|
||||
{ archiveOrder: data.archiveOrder },
|
||||
);
|
||||
queryClient.setQueryData<PurchaseRequestDetail | undefined>(["purchase-requests", "detail", selectedId], (current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
archiveOrder: data.archiveOrder,
|
||||
}
|
||||
: current,
|
||||
);
|
||||
}
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["purchase-requests", "detail", selectedId] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["purchase-requests", "archive-order", selectedId] }),
|
||||
@@ -1227,13 +1244,13 @@ export function PurchaseRequestsPage() {
|
||||
<div className="space-y-3 rounded-lg border border-zinc-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-zinc-900">采购 ERP 同步轨道</h4>
|
||||
<p className="text-sm text-zinc-500">展示 Commitment / Accrual / PO/合同摘要三段 ERP 同步状态,失败后可在此重试。</p>
|
||||
<h4 className="text-base font-semibold text-zinc-900">采购桥接轨道</h4>
|
||||
<p className="text-sm text-zinc-500">展示 Commitment / Accrual / PO/合同摘要三段外部桥接状态,失败后可在此重试。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{erpTrackQuery.isLoading ? (
|
||||
<p className="rounded-lg bg-zinc-50 px-3 py-2 text-sm text-zinc-500">ERP 轨道加载中...</p>
|
||||
<p className="rounded-lg bg-zinc-50 px-3 py-2 text-sm text-zinc-500">桥接轨道加载中...</p>
|
||||
) : erpTrackQuery.isError ? (
|
||||
<p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{resolveError(erpTrackQuery.error)}</p>
|
||||
) : erpTrack ? (
|
||||
|
||||
@@ -252,7 +252,7 @@ export function ReimbursementDetailPage() {
|
||||
]);
|
||||
setActionError("");
|
||||
const suffix = result.requeued ? "已重入队" : result.duplicated ? "已复用现有队列" : "已入队";
|
||||
toast.success(`ERP 导出${suffix}`);
|
||||
toast.success(`财务桥接出队${suffix}`);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isApiError(error)) {
|
||||
@@ -268,8 +268,8 @@ export function ReimbursementDetailPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
setActionError("ERP 导出失败,请稍后重试");
|
||||
toast.error("ERP 导出失败,请稍后重试");
|
||||
setActionError("财务桥接出队失败,请稍后重试");
|
||||
toast.error("财务桥接出队失败,请稍后重试");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -461,11 +461,11 @@ export function ReimbursementDetailPage() {
|
||||
<div className="card-std space-y-4 p-6" data-testid="reimbursement-erp-export">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-base font-semibold text-zinc-900">ERP 导出</h3>
|
||||
<p className="text-sm text-zinc-500">已付款报销单会按标准凭证切片通过 Outbox 异步发送到 ERP。</p>
|
||||
<h3 className="text-base font-semibold text-zinc-900">财务桥接</h3>
|
||||
<p className="text-sm text-zinc-500">已付款报销单会按标准凭证切片通过 Outbox 异步发送到财务桥接出口。</p>
|
||||
</div>
|
||||
<Button onClick={() => erpExportMutation.mutate()} disabled={erpExportMutation.isPending}>
|
||||
{erpExportMutation.isPending ? "导出中..." : "导出到 ERP"}
|
||||
{erpExportMutation.isPending ? "出队中..." : "发送到财务桥接"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -478,7 +478,7 @@ export function ReimbursementDetailPage() {
|
||||
erpPreviewQuery.error.details,
|
||||
erpPreviewQuery.error.message,
|
||||
)}`
|
||||
: "加载 ERP 导出预览失败"}
|
||||
: "加载财务桥接预览失败"}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const ITEMS = [
|
||||
{ to: "/system/approval-rules", title: "审批规则", description: "维护仍在使用的简单审批链与规则。" },
|
||||
{ to: "/system/master-data", title: "主数据", description: "维护费用、法体、成本中心等主数据。" },
|
||||
{ to: "/system/notification-channels", title: "通知渠道", description: "继续保留 IM/邮件等补充通知渠道。" },
|
||||
{ to: "/system/integrations/erp", title: "ERP 集成", description: "查看备用集成能力与出入口。" },
|
||||
{ to: "/system/integrations/erp", title: "外部系统桥接", description: "查看财务与外部系统桥接能力及出入口。" },
|
||||
];
|
||||
|
||||
export function SystemSettingsPage() {
|
||||
|
||||
在新工单中引用
屏蔽一个用户