feat(finance): 支持代录指定客服账户,并上线影子总账/对账/关账/快手平台退款
- 人工确认履约:管理员/财务/运营可传 fundAccountId / creditOwnerAdminUserId 指定客服备用金/积分账户;客服仍强制本人账户 - 新增迁移 112:finance_accounts / journal_entries / journal_lines / periods / reconciliations / reconciliation_items,种子 11 个科目 - 影子总账:销售事实按日生成收入/退款/成本凭证,支持手工凭证、试算平衡; 不接管钱包余额;总账同步接入定时任务与退款重算 - 期间关账/反关账:关账先补凭证并校验试算平衡,closed 期间禁止新增凭证 - 平台对账:JSON 账单导入,按 provider + platform_order_id 匹配订单/退款, 标记 matched/amount_mismatch/missing_order/missing_refund/duplicate - 平台退款:快手行业(91)同意退款 API 接入,成功后落 source=platform; 抖音/拼多多返回 501,保留手工退款 - 新增后台「财务 → 总账与对账」页面(总账/期间/对账三个 Tab) - 集成测试扩展到代录、总账平衡、关账阻断、手工凭证、对账匹配 验证:backend check 844(840 pass / 4 skip);frontend check 172 + build; 迁移 112 在 scratch 库全量执行并重跑幂等;本地生产副本已升级到 112, 销售事实同步 56 条凭证,借贷合计 1,885,351,864 = 1,885,351,864。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AccountBookOutlined,
|
||||
ApartmentOutlined,
|
||||
AuditOutlined,
|
||||
LineChartOutlined,
|
||||
WalletOutlined,
|
||||
} from '@ant-design/icons'
|
||||
@@ -30,6 +31,11 @@ export const FINANCE_NAV_ITEMS = [
|
||||
label: '收支明细',
|
||||
icon: <AccountBookOutlined />,
|
||||
},
|
||||
{
|
||||
key: '/admin/finance/ledger',
|
||||
label: '总账与对账',
|
||||
icon: <AuditOutlined />,
|
||||
},
|
||||
] as const
|
||||
|
||||
type FinanceNavKey = (typeof FINANCE_NAV_ITEMS)[number]['key']
|
||||
|
||||
@@ -26,8 +26,14 @@ import {
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { confirmAdminManualFulfillment, fetchAdminManualFulfillmentPending } from '@/services/admin'
|
||||
import {
|
||||
confirmAdminManualFulfillment,
|
||||
fetchAdminFundAccounts,
|
||||
fetchAdminManualFulfillmentPending,
|
||||
fetchAdminPlatformCreditOwners,
|
||||
} from '@/services/admin'
|
||||
import type { ManualFulfillmentPendingItem } from '@/types/admin'
|
||||
import { getAdminRole, getAdminUserId } from '@/utils/admin-auth'
|
||||
|
||||
type ConfirmFormValues = {
|
||||
costYuan?: number
|
||||
@@ -61,6 +67,11 @@ export function ManualFulfillmentConfirmModal({
|
||||
const queryClient = useQueryClient()
|
||||
const [form] = Form.useForm<ConfirmFormValues>()
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null)
|
||||
const [fundAccountId, setFundAccountId] = useState<number | null>(null)
|
||||
const [creditOwnerAdminUserId, setCreditOwnerAdminUserId] = useState<number | null>(null)
|
||||
const role = getAdminRole()
|
||||
const privileged = role !== 'support'
|
||||
const currentUserId = getAdminUserId()
|
||||
|
||||
const pendingQuery = useQuery({
|
||||
queryKey: ['admin-manual-fulfillment-pending', 'order', orderId],
|
||||
@@ -81,12 +92,49 @@ export function ManualFulfillmentConfirmModal({
|
||||
items[0] ||
|
||||
null
|
||||
|
||||
const fundAccountsQuery = useQuery({
|
||||
queryKey: ['admin-manual-confirm-fund-accounts'],
|
||||
queryFn: () => fetchAdminFundAccounts({ page: 1, pageSize: 100 }),
|
||||
enabled: open && privileged && Boolean(selected?.isFundCash),
|
||||
retry: false,
|
||||
})
|
||||
const creditOwnersQuery = useQuery({
|
||||
queryKey: ['admin-manual-confirm-credit-owners'],
|
||||
queryFn: () => fetchAdminPlatformCreditOwners(),
|
||||
enabled: open && privileged && Boolean(selected) && !selected?.isFundCash,
|
||||
retry: false,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
setSelectedId(initialOrderItemId || null)
|
||||
form.resetFields()
|
||||
}, [open, initialOrderItemId, form])
|
||||
|
||||
// 代录时默认选本人账户;客服没有代录权限,不展示选择。
|
||||
useEffect(() => {
|
||||
if (!open || !privileged) return
|
||||
if (selected?.isFundCash) {
|
||||
const accounts = (fundAccountsQuery.data?.data.items || []).filter(
|
||||
(item) => item.accountType === 'agent' && item.status === 'active',
|
||||
)
|
||||
const own = accounts.find((item) => item.ownerAdminUserId === currentUserId)
|
||||
setFundAccountId(own?.id ?? null)
|
||||
return
|
||||
}
|
||||
const owners = creditOwnersQuery.data?.data.items || []
|
||||
const own = owners.find((item) => item.id === currentUserId)
|
||||
setCreditOwnerAdminUserId(own?.id ?? null)
|
||||
}, [
|
||||
open,
|
||||
privileged,
|
||||
selected?.orderItemId,
|
||||
selected?.isFundCash,
|
||||
currentUserId,
|
||||
fundAccountsQuery.data,
|
||||
creditOwnersQuery.data,
|
||||
])
|
||||
|
||||
const confirmMutation = useMutation({
|
||||
mutationFn: (values: ConfirmFormValues) =>
|
||||
confirmAdminManualFulfillment({
|
||||
@@ -94,6 +142,10 @@ export function ManualFulfillmentConfirmModal({
|
||||
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
|
||||
? { creditOwnerAdminUserId }
|
||||
: {}),
|
||||
}),
|
||||
onSuccess: (response) => {
|
||||
const result = response.data
|
||||
@@ -122,7 +174,14 @@ export function ManualFulfillmentConfirmModal({
|
||||
open={open}
|
||||
title="确认人工履约"
|
||||
okText="确认履约"
|
||||
okButtonProps={{ icon: <CheckOutlined />, disabled: !selected }}
|
||||
okButtonProps={{
|
||||
icon: <CheckOutlined />,
|
||||
disabled:
|
||||
!selected ||
|
||||
(privileged &&
|
||||
((selected.isFundCash && !fundAccountId) ||
|
||||
(!selected.isFundCash && !creditOwnerAdminUserId))),
|
||||
}}
|
||||
cancelText="取消"
|
||||
confirmLoading={confirmMutation.isPending}
|
||||
onOk={() => {
|
||||
@@ -181,6 +240,46 @@ export function ManualFulfillmentConfirmModal({
|
||||
<Typography.Text>¥{((selected?.revenueFen || 0) / 100).toFixed(2)}</Typography.Text>
|
||||
</div>
|
||||
|
||||
{privileged && selected ? (
|
||||
selected.isFundCash ? (
|
||||
<div>
|
||||
<Typography.Text type="secondary">代录到客服备用金账户:</Typography.Text>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={fundAccountId ?? undefined}
|
||||
placeholder="请选择客服备用金账户"
|
||||
style={{ width: '100%', marginTop: 6 }}
|
||||
loading={fundAccountsQuery.isLoading}
|
||||
onChange={(value) => setFundAccountId(value)}
|
||||
options={(fundAccountsQuery.data?.data.items || [])
|
||||
.filter((item) => item.accountType === 'agent' && item.status === 'active')
|
||||
.map((item) => ({
|
||||
value: item.id,
|
||||
label: `${item.ownerName || item.accountNo} · 余额 ¥${(item.balanceFen / 100).toFixed(2)}`,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Typography.Text type="secondary">代录到客服积分账户:</Typography.Text>
|
||||
<Select
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
value={creditOwnerAdminUserId ?? undefined}
|
||||
placeholder="请选择客服账号"
|
||||
style={{ width: '100%', marginTop: 6 }}
|
||||
loading={creditOwnersQuery.isLoading}
|
||||
onChange={(value) => setCreditOwnerAdminUserId(value)}
|
||||
options={(creditOwnersQuery.data?.data.items || []).map((item) => ({
|
||||
value: item.id,
|
||||
label: item.displayName || item.username,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{showCostInput ? (
|
||||
<Alert type="warning" showIcon message="该商品成本不固定,请填写本次实际成本" />
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user