完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)
这个提交包含在:
@@ -4,17 +4,17 @@ import { useSearchParams } from "react-router-dom";
|
||||
import {
|
||||
cancelPaymentBatch,
|
||||
createPaymentBatch,
|
||||
deletePaymentBatchReceiptAttachment,
|
||||
downloadPaymentBatchReceiptAttachment,
|
||||
executePaymentBatch,
|
||||
fetchPaymentBatchCandidates,
|
||||
fetchPaymentBatchReceiptAttachments,
|
||||
fetchPaymentBatches,
|
||||
reviewPaymentBatch,
|
||||
updatePaymentBatchItem,
|
||||
uploadPaymentBatchReceiptAttachments,
|
||||
voidPaymentBatchReceiptAttachment,
|
||||
type PaymentBatch,
|
||||
type PaymentBatchReceiptAttachment,
|
||||
type PaymentBatchEntityType,
|
||||
type PaymentChannel,
|
||||
} from "../api/paymentBatches";
|
||||
import { isApiError } from "../api/types";
|
||||
@@ -33,6 +33,15 @@ import { useAttachmentDownloadReasonDialog } from "../components/common/useAttac
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../components/ui/dialog";
|
||||
import { Textarea } from "../components/ui/textarea";
|
||||
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { DateTextInput } from "../components/common/DateTextInput";
|
||||
@@ -78,8 +87,7 @@ function paymentItemStatusLabel(value: string) {
|
||||
}
|
||||
|
||||
function reviewNextStepLabel(value: PaymentBatch["reviewRequirement"]["nextStep"]) {
|
||||
if (value === "first_review") return "等待首轮复核";
|
||||
if (value === "second_review") return "等待二次复核";
|
||||
if (value === "first_review") return "等待独立复核";
|
||||
return "可执行付款标记";
|
||||
}
|
||||
|
||||
@@ -105,13 +113,15 @@ function resolveError(error: unknown) {
|
||||
|
||||
export function PaymentBatchesPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { permissions } = useAuth();
|
||||
const { me, permissions } = useAuth();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const canManageBatch = hasPermission(permissions, "payment.batch.manage");
|
||||
const canReviewBatch = hasPermission(permissions, "risk.review.manage");
|
||||
const [entityType, setEntityType] = useState<PaymentBatchEntityType>("reimbursement");
|
||||
const canReviewBatch =
|
||||
hasPermission(permissions, "payment.batch.review") &&
|
||||
(me?.roleCodes.includes("manager") || me?.roleCodes.includes("ceo"));
|
||||
const [name, setName] = useState("");
|
||||
const [entityIds, setEntityIds] = useState("");
|
||||
const [candidateSearch, setCandidateSearch] = useState("");
|
||||
const [selectedCandidateIds, setSelectedCandidateIds] = useState<string[]>([]);
|
||||
const [defaultPaymentDate, setDefaultPaymentDate] = useState("");
|
||||
const [defaultPaymentChannel, setDefaultPaymentChannel] = useState<PaymentChannel | "">("");
|
||||
const [voucherPrefix, setVoucherPrefix] = useState("");
|
||||
@@ -120,15 +130,28 @@ export function PaymentBatchesPage() {
|
||||
const [error, setError] = useState("");
|
||||
const [showCreatePanel, setShowCreatePanel] = useState(false);
|
||||
const [confirmPaymentAction, setConfirmPaymentAction] = useState<{
|
||||
kind: "financeReview" | "financePayment" | "cancel";
|
||||
kind: "financeReview" | "financePayment";
|
||||
batchId: string;
|
||||
batchNo: string;
|
||||
highRisk: boolean;
|
||||
totalAmount: string;
|
||||
} | null>(null);
|
||||
const [reasonAction, setReasonAction] = useState<
|
||||
| { kind: "cancel"; batchId: string; batchNo: string }
|
||||
| { kind: "voidReceipt"; batchId: string; batchNo: string; attachmentId: string; attachmentName: string }
|
||||
| null
|
||||
>(null);
|
||||
const [actionReason, setActionReason] = useState("");
|
||||
|
||||
const listQuery = useQuery({
|
||||
queryKey: ["payment-batches"],
|
||||
queryFn: () => fetchPaymentBatches(),
|
||||
});
|
||||
const candidatesQuery = useQuery({
|
||||
queryKey: ["payment-batch-candidates", candidateSearch],
|
||||
enabled: showCreatePanel && canManageBatch,
|
||||
queryFn: () => fetchPaymentBatchCandidates({ q: candidateSearch, page: 1, pageSize: 50 }),
|
||||
});
|
||||
|
||||
const selectedBatchId = searchParams.get("id") ?? "";
|
||||
const batches = useMemo(() => listQuery.data?.items ?? [], [listQuery.data?.items]);
|
||||
@@ -148,13 +171,12 @@ export function PaymentBatchesPage() {
|
||||
const canEditReceiptAttachments =
|
||||
canManageBatch &&
|
||||
!!selectedBatch &&
|
||||
!["executed", "cancelled"].includes(selectedBatch.status);
|
||||
selectedBatch.status !== "cancelled";
|
||||
const canReviewSelectedBatch =
|
||||
canReviewBatch &&
|
||||
!!selectedBatch &&
|
||||
(
|
||||
(selectedBatch.status === "draft" && selectedBatch.reviewRequirement.nextStep === "first_review") ||
|
||||
(selectedBatch.status === "reviewed" && selectedBatch.reviewRequirement.nextStep === "second_review")
|
||||
selectedBatch.status === "draft" && selectedBatch.reviewRequirement.nextStep === "first_review"
|
||||
);
|
||||
const canExecuteSelectedBatch =
|
||||
canManageBatch &&
|
||||
@@ -168,6 +190,7 @@ export function PaymentBatchesPage() {
|
||||
|
||||
const refresh = async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["payment-batches"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["payment-batch-candidates"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["payment-batch-receipts"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["ap-operations"] });
|
||||
};
|
||||
@@ -181,15 +204,17 @@ export function PaymentBatchesPage() {
|
||||
const createMutation = useMutation({
|
||||
mutationFn: async () =>
|
||||
createPaymentBatch({
|
||||
entityType,
|
||||
name: name.trim() || `${entityType}-batch`,
|
||||
entityIds: entityIds.split(/[\s,]+/).map((item) => item.trim()).filter(Boolean),
|
||||
entityType: "reimbursement",
|
||||
name: name.trim() || "报销付款批次",
|
||||
entityIds: selectedCandidateIds,
|
||||
defaultPaymentDate: defaultPaymentDate || undefined,
|
||||
defaultPaymentChannel: defaultPaymentChannel || undefined,
|
||||
voucherPrefix: voucherPrefix || undefined,
|
||||
}),
|
||||
onSuccess: async ({ item }) => {
|
||||
setError("");
|
||||
setSelectedCandidateIds([]);
|
||||
setCandidateSearch("");
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.set("id", item.id);
|
||||
setSearchParams(next, { replace: true });
|
||||
@@ -236,9 +261,11 @@ export function PaymentBatchesPage() {
|
||||
});
|
||||
|
||||
const cancelMutation = useMutation({
|
||||
mutationFn: cancelPaymentBatch,
|
||||
mutationFn: ({ batchId, reason }: { batchId: string; reason: string }) => cancelPaymentBatch(batchId, reason),
|
||||
onSuccess: async () => {
|
||||
setError("");
|
||||
setReasonAction(null);
|
||||
setActionReason("");
|
||||
await refresh();
|
||||
toast.success("付款批次已取消");
|
||||
},
|
||||
@@ -322,17 +349,16 @@ export function PaymentBatchesPage() {
|
||||
downloadReceiptMutation.mutate({ attachment, reason });
|
||||
}
|
||||
|
||||
const deleteReceiptMutation = useMutation({
|
||||
mutationFn: async (attachmentId: string) => {
|
||||
if (!selectedBatch) {
|
||||
throw new Error("请先选择付款批次");
|
||||
}
|
||||
return deletePaymentBatchReceiptAttachment(selectedBatch.id, attachmentId);
|
||||
const voidReceiptMutation = useMutation({
|
||||
mutationFn: async ({ batchId, attachmentId, reason }: { batchId: string; attachmentId: string; reason: string }) => {
|
||||
return voidPaymentBatchReceiptAttachment(batchId, attachmentId, reason);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setError("");
|
||||
setReasonAction(null);
|
||||
setActionReason("");
|
||||
await refresh();
|
||||
toast.success("银行回单附件已删除");
|
||||
toast.success("银行回单已留痕作废");
|
||||
},
|
||||
onError: (err) => {
|
||||
const message = resolveError(err);
|
||||
@@ -353,7 +379,7 @@ export function PaymentBatchesPage() {
|
||||
返回列表
|
||||
</Button>
|
||||
) : null}
|
||||
{!selectedBatchId ? (
|
||||
{!selectedBatchId && canManageBatch ? (
|
||||
<Button type="button" onClick={() => setShowCreatePanel(true)}>
|
||||
新建批次
|
||||
</Button>
|
||||
@@ -374,41 +400,64 @@ export function PaymentBatchesPage() {
|
||||
当前账号仅可查看批次并执行复核,创建与编辑入口仅对付款批次管理员开放。
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid gap-3 lg:grid-cols-[180px_minmax(0,1fr)]">
|
||||
<div className="space-y-2">
|
||||
<Label>实体类型</Label>
|
||||
<select
|
||||
className="select-std"
|
||||
data-testid="payment-batch-create-entity-type"
|
||||
value={entityType}
|
||||
disabled={!canManageBatch}
|
||||
onChange={(event) => setEntityType(event.target.value as PaymentBatchEntityType)}
|
||||
>
|
||||
<option value="reimbursement">报销付款</option>
|
||||
<option value="travel_order">差旅订单付款</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>批次名称</Label>
|
||||
<Input
|
||||
data-testid="payment-batch-create-name"
|
||||
value={name}
|
||||
disabled={!canManageBatch}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
placeholder="例如:2026Q2-财务付款批次-01"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>批次名称</Label>
|
||||
<Input
|
||||
data-testid="payment-batch-create-name"
|
||||
value={name}
|
||||
disabled={!canManageBatch}
|
||||
onChange={(event) => setName(event.target.value)}
|
||||
placeholder="例如:2026Q2-财务付款批次-01"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>待付款单据 ID</Label>
|
||||
<textarea
|
||||
className="min-h-24 w-full rounded-lg border border-zinc-300 px-3 py-2 text-sm disabled:bg-zinc-50"
|
||||
data-testid="payment-batch-create-entity-ids"
|
||||
value={entityIds}
|
||||
<Label>选择待付款报销</Label>
|
||||
<Input
|
||||
data-testid="payment-batch-candidate-search"
|
||||
value={candidateSearch}
|
||||
disabled={!canManageBatch}
|
||||
onChange={(event) => setEntityIds(event.target.value)}
|
||||
placeholder="输入报销或差旅待付款单据 ID,多个用逗号或换行分隔"
|
||||
onChange={(event) => setCandidateSearch(event.target.value)}
|
||||
placeholder="按报销编号、人员或摘要搜索"
|
||||
/>
|
||||
<div className="max-h-72 space-y-2 overflow-y-auto rounded-lg border border-zinc-200 p-2" data-testid="payment-batch-candidates">
|
||||
{candidatesQuery.isLoading ? <div className="px-2 py-3 text-sm text-zinc-500">正在加载待付款报销...</div> : null}
|
||||
{candidatesQuery.isError ? <div className="px-2 py-3 text-sm text-red-600">{resolveError(candidatesQuery.error)}</div> : null}
|
||||
{(candidatesQuery.data?.items ?? []).map((candidate) => {
|
||||
const selected = selectedCandidateIds.includes(candidate.entityId);
|
||||
return (
|
||||
<label
|
||||
key={candidate.entityId}
|
||||
className="flex cursor-pointer items-start gap-3 rounded-lg border border-zinc-200 bg-white px-3 py-3 hover:border-zinc-300"
|
||||
data-testid="payment-batch-candidate-row"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-1 h-4 w-4"
|
||||
checked={selected}
|
||||
onChange={(event) => setSelectedCandidateIds((current) =>
|
||||
event.target.checked
|
||||
? Array.from(new Set([...current, candidate.entityId]))
|
||||
: current.filter((id) => id !== candidate.entityId),
|
||||
)}
|
||||
/>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="flex flex-wrap items-center justify-between gap-2 text-sm font-medium text-zinc-900">
|
||||
<span>{candidate.businessNo} · {candidate.employee.name}</span>
|
||||
<span>{candidate.amount} {candidate.currencyCode}</span>
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-zinc-500">
|
||||
{formatDisplayDate(candidate.businessDate)}
|
||||
{candidate.summary ? ` · ${candidate.summary}` : ""}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
{!candidatesQuery.isLoading && !candidatesQuery.isError && !(candidatesQuery.data?.items ?? []).length ? (
|
||||
<div className="px-2 py-3 text-sm text-zinc-500">没有可加入批次的已审批报销。</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="text-xs text-zinc-500">已选择 {selectedCandidateIds.length} 项;创建时服务端会再次核对审批和付款状态。</div>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
@@ -444,7 +493,7 @@ export function PaymentBatchesPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" disabled={createMutation.isPending || !canManageBatch} onClick={() => createMutation.mutate()}>
|
||||
<Button type="button" disabled={createMutation.isPending || !canManageBatch || selectedCandidateIds.length === 0} onClick={() => createMutation.mutate()}>
|
||||
{createMutation.isPending ? "创建中..." : "创建批次"}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -474,9 +523,11 @@ export function PaymentBatchesPage() {
|
||||
kind: "financeReview",
|
||||
batchId: selectedBatch.id,
|
||||
batchNo: selectedBatch.batchNo,
|
||||
highRisk: selectedBatch.highRisk,
|
||||
totalAmount: selectedBatch.totalAmount,
|
||||
})}
|
||||
>
|
||||
{selectedBatch.reviewRequirement.nextStep === "second_review" ? "二次复核" : "复核"}
|
||||
复核
|
||||
</Button>
|
||||
) : null}
|
||||
{canManageBatch ? (
|
||||
@@ -487,6 +538,8 @@ export function PaymentBatchesPage() {
|
||||
kind: "financePayment",
|
||||
batchId: selectedBatch.id,
|
||||
batchNo: selectedBatch.batchNo,
|
||||
highRisk: selectedBatch.highRisk,
|
||||
totalAmount: selectedBatch.totalAmount,
|
||||
})}
|
||||
>
|
||||
执行
|
||||
@@ -497,11 +550,14 @@ export function PaymentBatchesPage() {
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={cancelMutation.isPending || !canCancelSelectedBatch}
|
||||
onClick={() => setConfirmPaymentAction({
|
||||
onClick={() => {
|
||||
setActionReason("");
|
||||
setReasonAction({
|
||||
kind: "cancel",
|
||||
batchId: selectedBatch.id,
|
||||
batchNo: selectedBatch.batchNo,
|
||||
})}
|
||||
});
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
@@ -517,10 +573,11 @@ export function PaymentBatchesPage() {
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">复核进度</div><div className="detail-fact-card__value">{selectedBatch.reviewRequirement.completedReviewCount}/{selectedBatch.reviewRequirement.requiredReviewCount}</div></div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-3 text-sm text-zinc-600">
|
||||
当前要求:{selectedBatch.reviewRequirement.requiredReviewCount === 2 ? `批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},需两轮复核` : "单轮复核后可执行"}。
|
||||
当前要求:单次独立复核后可执行。
|
||||
{selectedBatch.highRisk ? ` 批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},执行前需再次核对付款对象、金额和回单,但不增加审批层级。` : ""}
|
||||
下一步:{reviewNextStepLabel(selectedBatch.reviewRequirement.nextStep)}。
|
||||
{selectedBatch.reviewedBy ? ` 首轮复核人:${reviewerDisplayName(selectedBatch.reviewedBy)}` : ""}
|
||||
{selectedBatch.secondaryReviewedBy ? ` 二次复核人:${reviewerDisplayName(selectedBatch.secondaryReviewedBy)}` : ""}
|
||||
{selectedBatch.reviewedBy ? ` 复核人:${reviewerDisplayName(selectedBatch.reviewedBy)}` : ""}
|
||||
{selectedBatch.cancelledReason ? ` 取消原因:${selectedBatch.cancelledReason}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -586,7 +643,7 @@ export function PaymentBatchesPage() {
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={downloadReceiptMutation.isPending}
|
||||
disabled={downloadReceiptMutation.isPending || Boolean(attachment.archivedAt)}
|
||||
onClick={() => void handleDownloadReceipt(attachment)}
|
||||
>
|
||||
下载
|
||||
@@ -596,10 +653,19 @@ export function PaymentBatchesPage() {
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={!canEditReceiptAttachments || deleteReceiptMutation.isPending}
|
||||
onClick={() => deleteReceiptMutation.mutate(attachment.id)}
|
||||
disabled={selectedBatch.status === "cancelled" || Boolean(attachment.archivedAt) || voidReceiptMutation.isPending}
|
||||
onClick={() => {
|
||||
setActionReason("");
|
||||
setReasonAction({
|
||||
kind: "voidReceipt",
|
||||
batchId: selectedBatch.id,
|
||||
batchNo: selectedBatch.batchNo,
|
||||
attachmentId: attachment.id,
|
||||
attachmentName: attachment.originalName,
|
||||
});
|
||||
}}
|
||||
>
|
||||
删除
|
||||
{attachment.archivedAt ? "已作废" : "作废"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -764,18 +830,87 @@ export function PaymentBatchesPage() {
|
||||
targetName={confirmPaymentAction?.batchNo}
|
||||
description={
|
||||
confirmPaymentAction?.kind === "financeReview"
|
||||
? "确认后将写入付款批次复核记录;高风险批次仍需满足双人复核要求。"
|
||||
: confirmPaymentAction?.kind === "cancel"
|
||||
? "取消后该付款批次不能继续执行,历史记录会保留。"
|
||||
: "确认后将执行付款批次并影响付款状态,请确认批次、金额和复核要求均已核对。"
|
||||
? confirmPaymentAction.highRisk
|
||||
? `该批次金额为 ${confirmPaymentAction.totalAmount},请核对付款对象和金额。确认后完成一次独立复核,不增加第二审批层级。`
|
||||
: "确认后将完成一次独立复核并写入付款批次审计记录。"
|
||||
: confirmPaymentAction?.highRisk
|
||||
? `这是高额付款批次,金额 ${confirmPaymentAction.totalAmount}。请再次核对付款对象、金额、渠道和回单;确认后将更新源单付款状态。`
|
||||
: "确认后将执行付款批次并影响付款状态,请确认批次、金额和回单均已核对。"
|
||||
}
|
||||
onConfirm={async () => {
|
||||
if (!confirmPaymentAction) return;
|
||||
if (confirmPaymentAction.kind === "financeReview") reviewMutation.mutate(confirmPaymentAction.batchId);
|
||||
if (confirmPaymentAction.kind === "financePayment") executeMutation.mutate(confirmPaymentAction.batchId);
|
||||
if (confirmPaymentAction.kind === "cancel") cancelMutation.mutate(confirmPaymentAction.batchId);
|
||||
}}
|
||||
/>
|
||||
<Dialog
|
||||
open={Boolean(reasonAction)}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setReasonAction(null);
|
||||
setActionReason("");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-md p-5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{reasonAction?.kind === "voidReceipt" ? "作废银行回单" : "取消付款批次"}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{reasonAction?.kind === "voidReceipt"
|
||||
? `回单:${reasonAction.attachmentName}。文件记录会保留并标记为已作废。`
|
||||
: reasonAction
|
||||
? `批次:${reasonAction.batchNo}。只取消尚未执行的付款项,已执行项不会回滚。`
|
||||
: "请填写原因。"}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Textarea
|
||||
value={actionReason}
|
||||
onChange={(event) => setActionReason(event.target.value)}
|
||||
aria-label={reasonAction?.kind === "voidReceipt" ? "回单作废原因" : "批次取消原因"}
|
||||
rows={4}
|
||||
maxLength={500}
|
||||
placeholder={reasonAction?.kind === "voidReceipt" ? "说明回单为何作废" : "说明批次为何不再继续"}
|
||||
/>
|
||||
<div className="flex items-center justify-between text-xs text-zinc-500">
|
||||
<span>原因需填写 2–500 个字符,并写入审计记录。</span>
|
||||
<span>{actionReason.trim().length}/500</span>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => {
|
||||
setReasonAction(null);
|
||||
setActionReason("");
|
||||
}}>
|
||||
返回
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
disabled={
|
||||
actionReason.trim().length < 2 ||
|
||||
cancelMutation.isPending ||
|
||||
voidReceiptMutation.isPending
|
||||
}
|
||||
onClick={() => {
|
||||
if (!reasonAction) return;
|
||||
const reason = actionReason.trim();
|
||||
if (reasonAction.kind === "cancel") {
|
||||
cancelMutation.mutate({ batchId: reasonAction.batchId, reason });
|
||||
} else {
|
||||
voidReceiptMutation.mutate({
|
||||
batchId: reasonAction.batchId,
|
||||
attachmentId: reasonAction.attachmentId,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{reasonAction?.kind === "voidReceipt" ? "确认作废" : "确认取消"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</ListWorkspace>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户