feat(finance): 完成财务控制、L1 钱包硬化与迁移收敛
- P0/P1:影子总账追加调整、关账/退款/资金/积分幂等、成本版本追溯、对账订单项维度 - 迁移收敛:原 103–114 合并为 103_finance_foundation、104_finance_controls、105_work_order_consignment - 数据:L1 钱包 BIGINT/CHECK/奖励幂等;重复奖励审计口径修正与人工核对清单 - 报表:销售毛利缺失成本收入提示,旧收支页明确现金流口径 - 文档:同步迁移基线、合并评估、代码复核与上线说明
This commit is contained in:
@@ -144,11 +144,13 @@ function AdminApplication() {
|
||||
<Route index element={<Navigate to="/admin/dashboard" replace />} />
|
||||
<Route element={<RequireRole roles={['admin', 'operator', 'support']} />}>
|
||||
<Route path="dashboard" element={<AdminDashboardPage />} />
|
||||
<Route path="orders" element={<AdminOrdersPage />} />
|
||||
<Route path="orders/:orderId" element={<AdminOrderDetailPage />} />
|
||||
<Route path="tasks" element={<AdminTasksPage />} />
|
||||
<Route path="tasks/:taskId" element={<AdminTaskDetailPage />} />
|
||||
</Route>
|
||||
<Route element={<RequireRole roles={['admin', 'operator', 'support', 'finance']} />}>
|
||||
<Route path="orders" element={<AdminOrdersPage />} />
|
||||
<Route path="orders/:orderId" element={<AdminOrderDetailPage />} />
|
||||
</Route>
|
||||
<Route element={<RequireRole roles={['admin', 'operator', 'finance']} />}>
|
||||
<Route path="finance" element={<AdminFinancePage />} />
|
||||
<Route path="finance/ledger" element={<AdminFinanceLedgerPage />} />
|
||||
|
||||
@@ -214,6 +214,7 @@ export default function AdminLayout() {
|
||||
const operationItems: MenuProps['items'] =
|
||||
role === 'finance'
|
||||
? [
|
||||
{ key: '/admin/orders', icon: <OrderedListOutlined />, label: '订单' },
|
||||
financeSubMenu,
|
||||
{ key: '/admin/worker-platform', icon: <TrophyOutlined />, label: '资金审核' },
|
||||
]
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
@@ -78,7 +79,10 @@ const MATCH_STATUS_META: Record<string, { label: string; color: string }> = {
|
||||
export default function AdminFinanceLedgerPage() {
|
||||
const { message } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
const canConfig = hasAdminPermission('finance.config')
|
||||
const canConfig =
|
||||
hasAdminPermission('finance.post') ||
|
||||
hasAdminPermission('finance.close') ||
|
||||
hasAdminPermission('finance.reconcile')
|
||||
const [range, setRange] = useState<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()])
|
||||
const [entryPage, setEntryPage] = useState(1)
|
||||
const [entryPageSize, setEntryPageSize] = useState(20)
|
||||
@@ -297,13 +301,27 @@ export default function AdminFinanceLedgerPage() {
|
||||
render: (_, row) =>
|
||||
canConfig ? (
|
||||
row.status === 'closed' ? (
|
||||
<Button size="small" onClick={() => reopenMutation.mutate(row.periodKey)}>
|
||||
反关账
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认反关账?"
|
||||
description="反关账会重新开放该期间,之后可继续写入凭证。"
|
||||
okText="反关账"
|
||||
cancelText="取消"
|
||||
onConfirm={() => reopenMutation.mutate(row.periodKey)}
|
||||
>
|
||||
<Button size="small">反关账</Button>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<Button size="small" type="primary" onClick={() => closeMutation.mutate(row.periodKey)}>
|
||||
关账
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认关账?"
|
||||
description="关账后该期间不可再新增凭证,且会校验人工成本与资金流水门槛。"
|
||||
okText="关账"
|
||||
cancelText="取消"
|
||||
onConfirm={() => closeMutation.mutate(row.periodKey)}
|
||||
>
|
||||
<Button size="small" type="primary">
|
||||
关账
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)
|
||||
) : null,
|
||||
},
|
||||
|
||||
@@ -162,7 +162,17 @@ export default function AdminFinancePage() {
|
||||
return (
|
||||
<section className="page-stack">
|
||||
<FinanceSubNav current="/admin/finance" />
|
||||
<PageHeader title="收支明细" description="按日期查看订单收款、退款、打手结算和平台现金流。" />
|
||||
<PageHeader
|
||||
title="收支明细"
|
||||
description="按日期查看订单收款、退款、打手结算与平台现金收付;打手报酬是成本口径,不参与净现金流。"
|
||||
/>
|
||||
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="本页是现金收付口径,不是损益表"
|
||||
description="打手报酬不进入净现金流;毛利、成本和缺失成本请到「销售毛利」查看,避免把奖励成本与提现现金重复相加。"
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<Form<FinanceFilterForm>
|
||||
|
||||
@@ -289,7 +289,7 @@ export default function AdminOrderDetailPage() {
|
||||
title="基础信息"
|
||||
extra={
|
||||
<Space>
|
||||
{hasAdminPermission('finance.config') ? (
|
||||
{hasAdminPermission('finance.post') ? (
|
||||
<Button
|
||||
danger
|
||||
icon={<RollbackOutlined />}
|
||||
@@ -562,7 +562,7 @@ export default function AdminOrderDetailPage() {
|
||||
title: '操作',
|
||||
width: 90,
|
||||
render: (_, row) =>
|
||||
hasAdminPermission('finance.config') && row.status === 'confirmed' ? (
|
||||
hasAdminPermission('finance.post') && row.status === 'confirmed' ? (
|
||||
<Button size="small" danger onClick={() => setVoidTarget(row)}>
|
||||
撤销
|
||||
</Button>
|
||||
|
||||
@@ -60,7 +60,7 @@ const COST_STAGE_LABEL: Record<string, { label: string; color: string }> = {
|
||||
export default function AdminSalesProfitPage() {
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm<FilterForm>()
|
||||
const canConfig = hasAdminPermission('finance.config')
|
||||
const canConfig = hasAdminPermission('finance.post')
|
||||
const defaultDateFrom = dayjs().subtract(29, 'day').format('YYYY-MM-DD')
|
||||
const defaultDateTo = dayjs().format('YYYY-MM-DD')
|
||||
const [filters, setFilters] = useState<{
|
||||
@@ -315,7 +315,7 @@ export default function AdminSalesProfitPage() {
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message={`有 ${summary.costMissingCount} 个订单项没有成本快照,当前毛利偏高`}
|
||||
message={`有 ${summary.costMissingCount} 个订单项没有成本快照,涉及收入 ${formatMoney(summary.costMissingRevenueFen)},当前毛利偏高`}
|
||||
description="请先在「商品与成本」中补齐成本项/版本并回填快照,再使用毛利数据做经营决策。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -78,6 +78,7 @@ export type AdminSalesProfitOverview = {
|
||||
netRevenueFen: number
|
||||
costAmountFen: number
|
||||
costMissingCount: number
|
||||
costMissingRevenueFen: number
|
||||
grossProfitFen: number
|
||||
unmappedRevenueFen: number
|
||||
unmappedRevenuePct: number
|
||||
|
||||
Reference in New Issue
Block a user