feat(fulfillment): 增加统一结算与多渠道人工履约

This commit is contained in:
yml2213
2026-11-05 16:14:56 +08:00
parent 548070c2ad
commit 4e83dbb84c
43 changed files with 1582 additions and 334 deletions
@@ -32,7 +32,7 @@ import {
fetchAdminManualFulfillmentPending,
fetchAdminPlatformCreditOwners,
} from '@/services/admin'
import type { ManualFulfillmentPendingItem } from '@/types/admin'
import type { ManualConfiguredChannel, ManualFulfillmentPendingItem } from '@/types/admin'
import { getAdminRole, getAdminUserId } from '@/utils/admin-auth'
type ConfirmFormValues = {
@@ -67,6 +67,7 @@ export function ManualFulfillmentConfirmModal({
const queryClient = useQueryClient()
const [form] = Form.useForm<ConfirmFormValues>()
const [selectedId, setSelectedId] = useState<number | null>(null)
const [selectedPlatformKey, setSelectedPlatformKey] = useState<string | null>(null)
const [fundAccountId, setFundAccountId] = useState<number | null>(null)
const [creditOwnerAdminUserId, setCreditOwnerAdminUserId] = useState<number | null>(null)
const role = getAdminRole()
@@ -91,30 +92,51 @@ export function ManualFulfillmentConfirmModal({
items.find((item) => item.orderItemId === initialOrderItemId) ||
items[0] ||
null
const configuredChannels = selected?.channels || []
const selectedChannel: ManualConfiguredChannel | null =
configuredChannels.find((channel) => channel.platformKey === selectedPlatformKey) ||
configuredChannels[0] ||
null
const fundAccountsQuery = useQuery({
queryKey: ['admin-manual-confirm-fund-accounts'],
queryFn: () => fetchAdminFundAccounts({ page: 1, pageSize: 100 }),
enabled: open && privileged && Boolean(selected?.isFundCash),
enabled: open && privileged && selectedChannel?.isFundCash === true,
retry: false,
})
const creditOwnersQuery = useQuery({
queryKey: ['admin-manual-confirm-credit-owners'],
queryFn: () => fetchAdminPlatformCreditOwners(),
enabled: open && privileged && Boolean(selected) && !selected?.isFundCash,
enabled:
open &&
privileged &&
Boolean(selectedChannel) &&
selectedChannel?.isFundCash !== true &&
selectedChannel?.isOnAccount !== true,
retry: false,
})
useEffect(() => {
if (!open) return
setSelectedId(initialOrderItemId || null)
setSelectedPlatformKey(null)
form.resetFields()
}, [open, initialOrderItemId, form])
useEffect(() => {
if (!open || !selected) return
setSelectedPlatformKey((current) => {
if (current && selected.channels.some((channel) => channel.platformKey === current)) {
return current
}
return selected.channels[0]?.platformKey || selected.platformKey || null
})
}, [open, selected?.orderItemId, selected?.channels, selected?.platformKey])
// 代录时默认选本人账户;客服没有代录权限,不展示选择。
useEffect(() => {
if (!open || !privileged) return
if (selected?.isFundCash) {
if (selectedChannel?.isFundCash) {
const accounts = (fundAccountsQuery.data?.data.items || []).filter(
(item) => item.accountType === 'agent' && item.status === 'active',
)
@@ -122,6 +144,11 @@ export function ManualFulfillmentConfirmModal({
setFundAccountId(own?.id ?? null)
return
}
if (selectedChannel?.isOnAccount) {
setCreditOwnerAdminUserId(null)
setFundAccountId(null)
return
}
const owners = creditOwnersQuery.data?.data.items || []
const own = owners.find((item) => item.id === currentUserId)
setCreditOwnerAdminUserId(own?.id ?? null)
@@ -129,7 +156,8 @@ export function ManualFulfillmentConfirmModal({
open,
privileged,
selected?.orderItemId,
selected?.isFundCash,
selectedChannel?.isFundCash,
selectedChannel?.isOnAccount,
currentUserId,
fundAccountsQuery.data,
creditOwnersQuery.data,
@@ -139,18 +167,25 @@ export function ManualFulfillmentConfirmModal({
mutationFn: (values: ConfirmFormValues) =>
confirmAdminManualFulfillment({
orderItemId: selected?.orderItemId || 0,
platformKey: selectedChannel?.platformKey || selected?.platformKey || '',
costFen: values.costYuan === undefined ? undefined : Math.round(values.costYuan * 100),
payeeName: values.payeeName || '',
note: values.note || '',
...(privileged && selected?.isFundCash && fundAccountId ? { fundAccountId } : {}),
...(privileged && selected && !selected.isFundCash && creditOwnerAdminUserId
...(privileged && selectedChannel?.isFundCash && fundAccountId ? { fundAccountId } : {}),
...(privileged &&
selectedChannel &&
!selectedChannel.isFundCash &&
!selectedChannel.isOnAccount &&
creditOwnerAdminUserId
? { creditOwnerAdminUserId }
: {}),
}),
onSuccess: (response) => {
const result = response.data
message.success(
`履约已确认,扣 ${result.creditQuantity} 积分,成本 ¥${(result.costFen / 100).toFixed(2)}`,
result.onAccountBillId
? `履约已确认,已生成统一结算虚拟账单 ¥${(result.costFen / 100).toFixed(2)}`
: `履约已确认,扣 ${result.creditQuantity} 积分,成本 ¥${(result.costFen / 100).toFixed(2)}`,
)
void queryClient.invalidateQueries({ queryKey: ['admin-manual-fulfillment-pending'] })
void queryClient.invalidateQueries({ queryKey: ['admin-order-detail'] })
@@ -167,7 +202,8 @@ export function ManualFulfillmentConfirmModal({
},
})
const showCostInput = selected?.costFen === null
const showCostInput = selectedChannel?.costFen === null
const costRequired = showCostInput && selectedChannel?.isOnAccount !== true
return (
<Modal
@@ -178,9 +214,12 @@ export function ManualFulfillmentConfirmModal({
icon: <CheckOutlined />,
disabled:
!selected ||
!selectedChannel ||
(privileged &&
((selected.isFundCash && !fundAccountId) ||
(!selected.isFundCash && !creditOwnerAdminUserId))),
((selectedChannel.isFundCash && !fundAccountId) ||
(!selectedChannel.isFundCash &&
!selectedChannel.isOnAccount &&
!creditOwnerAdminUserId))),
}}
cancelText="取消"
confirmLoading={confirmMutation.isPending}
@@ -227,12 +266,30 @@ export function ManualFulfillmentConfirmModal({
</Typography.Text>
</div>
<div>
<Typography.Text type="secondary">履约渠道:</Typography.Text>
<Tag color={PLATFORM_TAG_COLORS[selected?.platformKey || ''] || 'default'}>
{selected?.platformLabel || '-'}
</Tag>
{selected && !selected.isFundCash ? (
<Typography.Text>消耗 {selected.creditQuantity} 积分</Typography.Text>
<Typography.Text type="secondary">本次实际履约渠道:</Typography.Text>
{selected && configuredChannels.length > 1 ? (
<Select
value={selectedChannel?.platformKey}
onChange={(value) => {
setSelectedPlatformKey(value)
form.setFieldValue('costYuan', undefined)
}}
options={configuredChannels.map((channel) => ({
value: channel.platformKey,
label: channel.platformLabel,
}))}
style={{ width: '100%', marginTop: 6 }}
/>
) : (
<Tag color={PLATFORM_TAG_COLORS[selectedChannel?.platformKey || ''] || 'default'}>
{selectedChannel?.platformLabel || '-'}
</Tag>
)}
{selectedChannel && !selectedChannel.isFundCash && !selectedChannel.isOnAccount ? (
<Typography.Text>消耗 {selectedChannel.creditQuantity} 积分</Typography.Text>
) : null}
{selectedChannel?.isOnAccount ? (
<Typography.Text>只生成虚拟账单,不扣积分或备用金</Typography.Text>
) : null}
</div>
<div>
@@ -240,8 +297,8 @@ export function ManualFulfillmentConfirmModal({
<Typography.Text>¥{((selected?.revenueFen || 0) / 100).toFixed(2)}</Typography.Text>
</div>
{privileged && selected ? (
selected.isFundCash ? (
{privileged && selected && selectedChannel ? (
selectedChannel.isFundCash ? (
<div>
<Typography.Text type="secondary">代录到客服备用金账户:</Typography.Text>
<Select
@@ -260,7 +317,7 @@ export function ManualFulfillmentConfirmModal({
}))}
/>
</div>
) : (
) : selectedChannel.isOnAccount ? null : (
<div>
<Typography.Text type="secondary">代录到客服积分账户:</Typography.Text>
<Select
@@ -281,11 +338,19 @@ export function ManualFulfillmentConfirmModal({
) : null}
{showCostInput ? (
<Alert type="warning" showIcon message="该商品成本不固定,请填写本次实际成本" />
<Alert
type="warning"
showIcon
message={
selectedChannel?.isOnAccount
? '统一结算金额可以先留空,后续统一对账时补录'
: '该商品成本不固定,请填写本次实际成本'
}
/>
) : (
<Statistic
title="本次成本"
value={(selected?.costFen || 0) / 100}
value={(selectedChannel?.costFen || 0) / 100}
precision={2}
prefix="¥"
/>
@@ -295,11 +360,11 @@ export function ManualFulfillmentConfirmModal({
{showCostInput ? (
<Form.Item
name="costYuan"
label="实际成本(元)"
rules={[{ required: true, message: '请填写实际成本' }]}
label={selectedChannel?.isOnAccount ? '预计结算金额(元)' : '实际成本(元)'}
rules={costRequired ? [{ required: true, message: '请填写实际成本' }] : []}
>
<InputNumber
min={0.01}
min={selectedChannel?.isOnAccount ? 0 : 0.01}
precision={2}
style={{ width: '100%' }}
placeholder="例如 12.50"