feat(P20T2-4): 差旅支付轨道卡片与发票绑定前端交互
这个提交包含在:
@@ -0,0 +1,127 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { TravelPaymentChannel, TravelPaymentOrder } from "../../api/travelPayments";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../ui/dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import { Input } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
|
||||
const PAYMENT_CHANNEL_OPTIONS: Array<{ value: TravelPaymentChannel; label: string }> = [
|
||||
{ value: "bank_transfer", label: "银行转账" },
|
||||
{ value: "corporate_card", label: "企业卡" },
|
||||
{ value: "virtual_card", label: "虚拟卡" },
|
||||
{ value: "manual", label: "手工录入" },
|
||||
{ value: "other", label: "其他" },
|
||||
];
|
||||
|
||||
export type TravelOrderMarkPaidDialogProps = {
|
||||
open: boolean;
|
||||
order: TravelPaymentOrder | null;
|
||||
pending?: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConfirm: (payload: {
|
||||
paymentVoucherNo: string | null;
|
||||
paymentDate: string | null;
|
||||
paymentChannel: TravelPaymentChannel | null;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export function TravelOrderMarkPaidDialog({
|
||||
open,
|
||||
order,
|
||||
pending = false,
|
||||
onOpenChange,
|
||||
onConfirm,
|
||||
}: TravelOrderMarkPaidDialogProps) {
|
||||
const [paymentVoucherNo, setPaymentVoucherNo] = useState("");
|
||||
const [paymentDate, setPaymentDate] = useState("");
|
||||
const [paymentChannel, setPaymentChannel] = useState<TravelPaymentChannel | "">("bank_transfer");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
setPaymentVoucherNo(order?.paymentVoucherNo ?? "");
|
||||
setPaymentDate(order?.paymentDate ?? "");
|
||||
setPaymentChannel((order?.paymentChannel as TravelPaymentChannel | null) ?? "bank_transfer");
|
||||
}, [open, order]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>标记差旅订单已支付</DialogTitle>
|
||||
<DialogDescription>
|
||||
确认订单 `{order?.externalOrderNo ?? "-"}` 已完成对公支付;系统会按幂等键复用现有 ERP journal 出站记录。
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<Label htmlFor="travel-order-payment-voucher">付款凭证号</Label>
|
||||
<Input
|
||||
id="travel-order-payment-voucher"
|
||||
className="mt-1"
|
||||
placeholder="可留空"
|
||||
value={paymentVoucherNo}
|
||||
onChange={(event) => setPaymentVoucherNo(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="travel-order-payment-date">付款日期</Label>
|
||||
<Input
|
||||
id="travel-order-payment-date"
|
||||
className="mt-1"
|
||||
type="date"
|
||||
value={paymentDate}
|
||||
onChange={(event) => setPaymentDate(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="travel-order-payment-channel">付款渠道</Label>
|
||||
<select
|
||||
id="travel-order-payment-channel"
|
||||
className="select-std mt-1"
|
||||
value={paymentChannel}
|
||||
onChange={(event) => setPaymentChannel(event.target.value as TravelPaymentChannel | "")}
|
||||
>
|
||||
{PAYMENT_CHANNEL_OPTIONS.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)} disabled={pending}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
onConfirm({
|
||||
paymentVoucherNo: paymentVoucherNo.trim() || null,
|
||||
paymentDate: paymentDate || null,
|
||||
paymentChannel: paymentChannel || null,
|
||||
})
|
||||
}
|
||||
disabled={pending}
|
||||
>
|
||||
{pending ? "提交中..." : "确认已支付"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { mapApiErrorMessage } from "../../api/errorMessages";
|
||||
import {
|
||||
bindInvoice,
|
||||
fetchInvoicePoolList,
|
||||
type InvoicePoolListItem,
|
||||
} from "../../api/invoicePool";
|
||||
import { isApiError } from "../../api/types";
|
||||
import {
|
||||
fetchTravelPaymentTrack,
|
||||
markTravelOrderPaid,
|
||||
suggestFreezeTravelTransaction,
|
||||
type TravelPaymentAnomaly,
|
||||
type TravelPaymentChannel,
|
||||
type TravelPaymentOrder,
|
||||
} from "../../api/travelPayments";
|
||||
import { CardSkeleton } from "../common/CardSkeleton";
|
||||
import { StatusBadge } from "../common/StatusBadge";
|
||||
import { Button } from "../ui/button";
|
||||
import { TravelOrderMarkPaidDialog } from "./TravelOrderMarkPaidDialog";
|
||||
|
||||
function resolveError(error: unknown) {
|
||||
if (isApiError(error)) {
|
||||
return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return "请求失败,请稍后重试。";
|
||||
}
|
||||
|
||||
function freezeReasonFromAnomaly(code: string): "amount_mismatch" | "currency_mismatch" | "missing_order" | "duplicate" | "manual_review" {
|
||||
if (code === "amount_mismatch") return "amount_mismatch";
|
||||
if (code === "currency_mismatch") return "currency_mismatch";
|
||||
if (code === "missing_order") return "missing_order";
|
||||
if (code === "duplicate") return "duplicate";
|
||||
return "manual_review";
|
||||
}
|
||||
|
||||
function anomalyTone(level: TravelPaymentAnomaly["level"]) {
|
||||
if (level === "high") {
|
||||
return "border-red-200 bg-red-50 text-red-700";
|
||||
}
|
||||
if (level === "medium") {
|
||||
return "border-amber-200 bg-amber-50 text-amber-700";
|
||||
}
|
||||
return "border-zinc-200 bg-zinc-50 text-zinc-700";
|
||||
}
|
||||
|
||||
function paymentChannelLabel(value: TravelPaymentChannel | string | null) {
|
||||
if (value === "bank_transfer") return "银行转账";
|
||||
if (value === "corporate_card") return "企业卡";
|
||||
if (value === "virtual_card") return "虚拟卡";
|
||||
if (value === "manual") return "手工录入";
|
||||
if (value === "other") return "其他";
|
||||
return "-";
|
||||
}
|
||||
|
||||
function erpStageLabel(order: TravelPaymentOrder) {
|
||||
const status = order.erpStage.exportState.lastOutboxStatus;
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
if (order.erpStage.binding?.externalId) {
|
||||
return "已同步";
|
||||
}
|
||||
return "ERP 出站未开启";
|
||||
}
|
||||
|
||||
type TravelPaymentTrackCardProps = {
|
||||
travelRequestId: string;
|
||||
};
|
||||
|
||||
export function TravelPaymentTrackCard({ travelRequestId }: TravelPaymentTrackCardProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const [markPaidOrder, setMarkPaidOrder] = useState<TravelPaymentOrder | null>(null);
|
||||
const [selectedInvoiceMap, setSelectedInvoiceMap] = useState<Record<string, string>>({});
|
||||
|
||||
const trackQuery = useQuery({
|
||||
enabled: Boolean(travelRequestId),
|
||||
queryKey: ["travel-requests", "payment-track", travelRequestId],
|
||||
queryFn: () => fetchTravelPaymentTrack(travelRequestId),
|
||||
});
|
||||
|
||||
const availableInvoicesQuery = useQuery({
|
||||
enabled: trackQuery.data?.permissions.canManagePayments === true,
|
||||
queryKey: ["invoice-pool", "available-for-travel-bind"],
|
||||
queryFn: () =>
|
||||
fetchInvoicePoolList({
|
||||
status: "available",
|
||||
ownerScope: "all",
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
}),
|
||||
});
|
||||
|
||||
const markPaidMutation = useMutation({
|
||||
mutationFn: ({ orderId, payload }: { orderId: string; payload: { paymentVoucherNo: string | null; paymentDate: string | null; paymentChannel: TravelPaymentChannel | null } }) =>
|
||||
markTravelOrderPaid(orderId, payload),
|
||||
onSuccess: async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["travel-requests"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["invoice-pool"] }),
|
||||
]);
|
||||
toast.success("差旅订单已标记为已支付");
|
||||
setMarkPaidOrder(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveError(error));
|
||||
},
|
||||
});
|
||||
|
||||
const bindInvoiceMutation = useMutation({
|
||||
mutationFn: ({ invoiceId, orderId }: { invoiceId: string; orderId: string }) =>
|
||||
bindInvoice(invoiceId, { travelOrderId: orderId }),
|
||||
onSuccess: async (_, variables) => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["travel-requests"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["invoice-pool"] }),
|
||||
]);
|
||||
setSelectedInvoiceMap((current) => ({ ...current, [variables.orderId]: "" }));
|
||||
toast.success("发票已绑定到差旅订单");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveError(error));
|
||||
},
|
||||
});
|
||||
|
||||
const freezeMutation = useMutation({
|
||||
mutationFn: ({ transactionId, reasonCode }: { transactionId: string; reasonCode: "amount_mismatch" | "currency_mismatch" | "missing_order" | "duplicate" | "manual_review" }) =>
|
||||
suggestFreezeTravelTransaction(transactionId, { reasonCode, note: null }),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["travel-requests", "payment-track", travelRequestId] });
|
||||
toast.success("已记录冻结建议");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(resolveError(error));
|
||||
},
|
||||
});
|
||||
|
||||
const anomaliesByTransactionId = useMemo(() => {
|
||||
const map = new Map<string, TravelPaymentAnomaly[]>();
|
||||
for (const item of trackQuery.data?.anomalies ?? []) {
|
||||
const list = map.get(item.transactionId) ?? [];
|
||||
list.push(item);
|
||||
map.set(item.transactionId, list);
|
||||
}
|
||||
return map;
|
||||
}, [trackQuery.data?.anomalies]);
|
||||
|
||||
const availableInvoices = availableInvoicesQuery.data?.items ?? [];
|
||||
const canManagePayments = trackQuery.data?.permissions.canManagePayments ?? false;
|
||||
|
||||
if (trackQuery.isLoading) {
|
||||
return <CardSkeleton lines={8} />;
|
||||
}
|
||||
|
||||
if (trackQuery.isError) {
|
||||
return (
|
||||
<div className="card-std p-6">
|
||||
<h3 className="text-base font-semibold text-zinc-900">对公支付轨道</h3>
|
||||
<p className="mt-3 text-sm text-zinc-500">支付轨道加载失败,但不影响差旅主页面继续使用。</p>
|
||||
<p className="mt-2 text-sm text-red-600">{resolveError(trackQuery.error)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const track = trackQuery.data;
|
||||
if (!track) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card-std space-y-5 p-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-zinc-900">对公支付轨道</h3>
|
||||
<p className="mt-1 text-sm text-zinc-500">授权、交易、发票绑定与 ERP 支付出站在此统一查看。</p>
|
||||
</div>
|
||||
{canManagePayments ? <span className="text-xs text-zinc-500">具备支付管理权限</span> : null}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-2">
|
||||
<div className="rounded-xl border border-zinc-200 p-4">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">申请预算账轨</h4>
|
||||
{track.requestBudgetTrack.items.length ? (
|
||||
<div className="mt-3 space-y-2 text-sm text-zinc-600">
|
||||
{track.requestBudgetTrack.items.map((item) => (
|
||||
<div key={item.budgetPoolId} className="rounded-lg bg-zinc-50 px-3 py-2">
|
||||
<div className="font-medium text-zinc-900">{item.budgetPoolName ?? item.budgetPoolCode ?? item.budgetPoolId}</div>
|
||||
<div className="mt-1 text-xs">reserve {item.reserve ?? "0.00"} · confirm {item.confirm ?? "0.00"} · release {item.release ?? "0.00"}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-3 text-sm text-zinc-500">当前没有申请预算流水。</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-zinc-200 p-4">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">支付预算账轨</h4>
|
||||
{track.corporateBudgetTrack.items.length ? (
|
||||
<div className="mt-3 space-y-2 text-sm text-zinc-600">
|
||||
{track.corporateBudgetTrack.items.map((item) => (
|
||||
<div key={item.budgetPoolId} className="rounded-lg bg-zinc-50 px-3 py-2">
|
||||
<div className="font-medium text-zinc-900">{item.budgetPoolName ?? item.budgetPoolCode ?? item.budgetPoolId}</div>
|
||||
<div className="mt-1 text-xs">authorize {item.authorize ?? "0.00"} · settle {item.settle ?? "0.00"} · reverse {item.reverse ?? "0.00"}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-3 text-sm text-zinc-500">当前没有企业卡/支付预算流水。</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{track.authorizations.length ? (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">企业卡授权</h4>
|
||||
<div className="grid gap-3 xl:grid-cols-2">
|
||||
{track.authorizations.map((item) => (
|
||||
<div key={item.id} className="rounded-xl border border-zinc-200 p-4 text-sm">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-medium text-zinc-900">{item.externalAuthNo}</div>
|
||||
<StatusBadge status={item.status} />
|
||||
</div>
|
||||
<div className="mt-2 space-y-1 text-xs text-zinc-500">
|
||||
<div>来源:{item.source}</div>
|
||||
<div>授权金额:{item.authorizedAmount} {item.currency}</div>
|
||||
<div>已结算:{item.settledAmount} · 已冲回:{item.reversedAmount}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">差旅订单支付闭环</h4>
|
||||
{track.travelOrders.length ? (
|
||||
<div className="space-y-4">
|
||||
{track.travelOrders.map((order) => {
|
||||
const selectedInvoiceId = selectedInvoiceMap[order.id] ?? "";
|
||||
return (
|
||||
<div key={order.id} className="rounded-xl border border-zinc-200 p-4">
|
||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="font-medium text-zinc-900">{order.externalOrderNo}</p>
|
||||
<StatusBadge status={order.status} />
|
||||
</div>
|
||||
<div className="mt-2 space-y-1 text-xs text-zinc-500">
|
||||
<div>来源:{order.source}</div>
|
||||
<div>金额:{order.amount} {order.currency}</div>
|
||||
<div>付款凭证:{order.paymentVoucherNo ?? "-"}</div>
|
||||
<div>付款日期:{order.paymentDate ?? "-"} · 渠道:{paymentChannelLabel(order.paymentChannel)}</div>
|
||||
<div>ERP 状态:{erpStageLabel(order)}</div>
|
||||
</div>
|
||||
</div>
|
||||
{canManagePayments ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{order.actions.canMarkPaid ? (
|
||||
<Button type="button" variant="secondary" onClick={() => setMarkPaidOrder(order)}>
|
||||
标记已支付
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-4 xl:grid-cols-2">
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="font-medium text-zinc-900">已匹配交易</div>
|
||||
{order.matchedTransactions.length ? (
|
||||
order.matchedTransactions.map((transaction) => {
|
||||
const transactionAnomalies = anomaliesByTransactionId.get(transaction.id) ?? [];
|
||||
return (
|
||||
<div key={transaction.id} className="rounded-lg bg-zinc-50 px-3 py-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-medium text-zinc-900">{transaction.externalTxnId}</div>
|
||||
<StatusBadge status={transaction.status} />
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
{transaction.amount} {transaction.currency} · {transaction.txnType}
|
||||
</div>
|
||||
{transaction.freezeReasonCode ? (
|
||||
<div className="mt-2 text-xs text-amber-700">已建议冻结:{transaction.freezeReasonCode}</div>
|
||||
) : null}
|
||||
{canManagePayments && transactionAnomalies.length && !transaction.freezeReasonCode ? (
|
||||
<div className="mt-3">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={freezeMutation.isPending}
|
||||
onClick={() =>
|
||||
freezeMutation.mutate({
|
||||
transactionId: transaction.id,
|
||||
reasonCode: freezeReasonFromAnomaly(transactionAnomalies[0].code),
|
||||
})
|
||||
}
|
||||
>
|
||||
{freezeMutation.isPending ? "提交中..." : "建议冻结"}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">当前没有匹配到已导入交易。</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="font-medium text-zinc-900">已绑发票</div>
|
||||
{order.boundInvoices.length ? (
|
||||
order.boundInvoices.map((invoice) => (
|
||||
<div key={invoice.id} className="rounded-lg bg-zinc-50 px-3 py-3 text-xs text-zinc-600">
|
||||
<div className="font-medium text-zinc-900">{invoice.invoiceCode ?? invoice.originalName ?? invoice.id}</div>
|
||||
<div className="mt-1">金额:{invoice.totalAmount ?? "-"}</div>
|
||||
<div>票号:{invoice.invoiceNumber ?? "-"}</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">当前没有已绑定发票。</p>
|
||||
)}
|
||||
|
||||
{canManagePayments && order.actions.canBindInvoice ? (
|
||||
<div className="rounded-lg border border-dashed border-zinc-200 p-3">
|
||||
{availableInvoicesQuery.isError ? (
|
||||
<p className="text-xs text-zinc-500">可用发票列表暂不可用,可去发票池页面完成绑定。</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<select
|
||||
className="select-std"
|
||||
value={selectedInvoiceId}
|
||||
onChange={(event) =>
|
||||
setSelectedInvoiceMap((current) => ({
|
||||
...current,
|
||||
[order.id]: event.target.value,
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="">选择可用发票</option>
|
||||
{availableInvoices.map((invoice: InvoicePoolListItem) => (
|
||||
<option key={invoice.id} value={invoice.id}>
|
||||
{invoice.invoiceCode ?? invoice.originalName ?? invoice.id} ({invoice.totalAmount ?? "-"})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
disabled={bindInvoiceMutation.isPending || !selectedInvoiceId}
|
||||
onClick={() => bindInvoiceMutation.mutate({ invoiceId: selectedInvoiceId, orderId: order.id })}
|
||||
>
|
||||
{bindInvoiceMutation.isPending ? "绑定中..." : "绑定发票"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">当前差旅申请还没有进入对公支付闭环。</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{track.anomalies.length ? (
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-semibold text-zinc-900">异常提示</h4>
|
||||
<div className="space-y-2">
|
||||
{track.anomalies.map((item) => (
|
||||
<div key={`${item.transactionId}-${item.code}`} className={`rounded-lg border px-3 py-3 text-sm ${anomalyTone(item.level)}`}>
|
||||
<div className="font-medium">{item.title}</div>
|
||||
<div className="mt-1 text-xs">{item.detail}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<TravelOrderMarkPaidDialog
|
||||
open={Boolean(markPaidOrder)}
|
||||
order={markPaidOrder}
|
||||
pending={markPaidMutation.isPending}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setMarkPaidOrder(null);
|
||||
}
|
||||
}}
|
||||
onConfirm={(payload) => {
|
||||
if (!markPaidOrder) {
|
||||
return;
|
||||
}
|
||||
markPaidMutation.mutate({ orderId: markPaidOrder.id, payload });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户