重构: 手续费改为百分比/固定二选一,清理旧 Skin/Order/ShipLog 演示链路
手续费: - 新增 fee_type 字段(rate/fixed),百分比与固定金额二选一,不再叠加 - calculateServiceFee 按 fee_type 分支计算 - 商户创建/更新校验 fee_type,订单落库快照 fee_type - 前端表单改为下拉选择手续费类型,动态显示对应输入框 - 测试拆为 TestFeeRate + TestFeeFixed 清理旧链路: - 删除旧 Skin/Order/ShipLog 模型及 OrderService/SkinService - Dashboard 迁移到 FulfillmentService - 上游 /api/open/v1 改为基于 FulfillmentOrder 实现,接口契约不变 - 推送留痕改用 AuditLog,不再建 ShipLog 表 - 删除前端 Skins/Orders/ShipLogs/Distributors 页面及路由、菜单、API、类型 - 新增迁移 003: 添加 fee_type 列并 DROP 旧表
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
@@ -103,11 +103,14 @@ export default function MerchantCenter() {
|
||||
|
||||
const canManage = merchantRole === 'owner' || merchantRole === 'operator'
|
||||
const canFinance = merchantRole === 'owner' || merchantRole === 'finance'
|
||||
const enabledFeatures = useMemo(() => new Set(featuresToList(merchant?.features)), [merchant?.features])
|
||||
const hasFeature = useCallback((feature: string) => !merchant || enabledFeatures.has(feature), [enabledFeatures, merchant])
|
||||
|
||||
const loadCurrent = useCallback(async () => {
|
||||
const data = await merchantApi.current()
|
||||
setMerchant(data.merchant)
|
||||
setMerchantRole(data.role)
|
||||
return data
|
||||
}, [])
|
||||
|
||||
const loadProducts = useCallback(async (page = products.page, size = products.size) => {
|
||||
@@ -120,37 +123,86 @@ export default function MerchantCenter() {
|
||||
setOrders(data)
|
||||
}, [orders.page, orders.size])
|
||||
|
||||
const loadWallet = useCallback(async (page = ledger.page, size = ledger.size) => {
|
||||
const [walletData, ledgerData] = await Promise.all([
|
||||
merchantApi.wallet(),
|
||||
merchantApi.ledger({ page, size }),
|
||||
])
|
||||
const loadWallet = useCallback(async (
|
||||
page = ledger.page,
|
||||
size = ledger.size,
|
||||
role = merchantRole,
|
||||
) => {
|
||||
const walletData = await merchantApi.wallet()
|
||||
setWallet(walletData)
|
||||
setLedger(ledgerData)
|
||||
}, [ledger.page, ledger.size])
|
||||
if (role === 'owner' || role === 'finance') {
|
||||
const ledgerData = await merchantApi.ledger({ page, size })
|
||||
setLedger(ledgerData)
|
||||
} else {
|
||||
setLedger({ list: [], total: 0, page, size })
|
||||
}
|
||||
}, [ledger.page, ledger.size, merchantRole])
|
||||
|
||||
const loadIntegrations = useCallback(async () => {
|
||||
const [clientData, callbackData, memberData] = await Promise.all([
|
||||
merchantApi.apiClients(),
|
||||
merchantApi.callbacks(),
|
||||
merchantApi.members(),
|
||||
])
|
||||
const loadAPIClients = useCallback(async (role = merchantRole) => {
|
||||
if (role !== 'owner' && role !== 'operator') {
|
||||
setApiClients([])
|
||||
return
|
||||
}
|
||||
const clientData = await merchantApi.apiClients()
|
||||
setApiClients(clientData || [])
|
||||
}, [merchantRole])
|
||||
|
||||
const loadCallbacks = useCallback(async (role = merchantRole) => {
|
||||
if (role !== 'owner' && role !== 'operator') {
|
||||
setCallbacks([])
|
||||
return
|
||||
}
|
||||
const callbackData = await merchantApi.callbacks()
|
||||
setCallbacks(callbackData || [])
|
||||
}, [merchantRole])
|
||||
|
||||
const loadMembers = useCallback(async (role = merchantRole) => {
|
||||
if (role !== 'owner' && role !== 'operator') {
|
||||
setMembers([])
|
||||
return
|
||||
}
|
||||
const memberData = await merchantApi.members()
|
||||
setMembers(memberData || [])
|
||||
}, [])
|
||||
}, [merchantRole])
|
||||
|
||||
const loadActiveTab = useCallback(async (role = merchantRole, tab = activeTab) => {
|
||||
switch (tab) {
|
||||
case 'products':
|
||||
await loadProducts()
|
||||
return
|
||||
case 'orders':
|
||||
await loadOrders()
|
||||
return
|
||||
case 'wallet':
|
||||
await loadWallet(undefined, undefined, role)
|
||||
return
|
||||
case 'api':
|
||||
await loadAPIClients(role)
|
||||
return
|
||||
case 'callbacks':
|
||||
await loadCallbacks(role)
|
||||
return
|
||||
case 'members':
|
||||
await loadMembers(role)
|
||||
return
|
||||
}
|
||||
}, [activeTab, loadAPIClients, loadCallbacks, loadMembers, loadOrders, loadProducts, loadWallet, merchantRole])
|
||||
|
||||
const loadAll = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await loadCurrent()
|
||||
await Promise.all([loadProducts(), loadOrders(), loadWallet(), loadIntegrations()])
|
||||
const current = await loadCurrent()
|
||||
const nextTab = resolveEnabledTab(activeTab, current.merchant.features)
|
||||
if (nextTab !== activeTab) {
|
||||
setActiveTab(nextTab)
|
||||
}
|
||||
await loadActiveTab(current.role, nextTab)
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '加载失败')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [loadCurrent, loadProducts, loadOrders, loadWallet, loadIntegrations])
|
||||
}, [activeTab, loadActiveTab, loadCurrent])
|
||||
|
||||
useEffect(() => {
|
||||
loadAll()
|
||||
@@ -232,7 +284,7 @@ export default function MerchantCenter() {
|
||||
setApiCredential(credential)
|
||||
setApiClientOpen(false)
|
||||
message.success('API 客户端已创建')
|
||||
loadIntegrations()
|
||||
loadAPIClients()
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '创建失败')
|
||||
}
|
||||
@@ -249,7 +301,7 @@ export default function MerchantCenter() {
|
||||
setCallbackCredential(credential)
|
||||
setCallbackOpen(false)
|
||||
message.success('回调已创建')
|
||||
loadIntegrations()
|
||||
loadCallbacks()
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '创建失败')
|
||||
}
|
||||
@@ -265,7 +317,7 @@ export default function MerchantCenter() {
|
||||
})
|
||||
setMemberOpen(false)
|
||||
message.success('成员已添加')
|
||||
loadIntegrations()
|
||||
loadMembers()
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '添加失败')
|
||||
}
|
||||
@@ -275,7 +327,7 @@ export default function MerchantCenter() {
|
||||
try {
|
||||
await merchantApi.updateApiClientStatus(record.id, record.status === 'active' ? 'disabled' : 'active')
|
||||
message.success('状态已更新')
|
||||
loadIntegrations()
|
||||
loadAPIClients()
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '更新失败')
|
||||
}
|
||||
@@ -285,7 +337,7 @@ export default function MerchantCenter() {
|
||||
try {
|
||||
await merchantApi.updateCallbackStatus(record.id, record.status === 'active' ? 'disabled' : 'active')
|
||||
message.success('状态已更新')
|
||||
loadIntegrations()
|
||||
loadCallbacks()
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '更新失败')
|
||||
}
|
||||
@@ -314,7 +366,9 @@ export default function MerchantCenter() {
|
||||
{ title: '商户单号', dataIndex: 'client_order_no', width: 160, ellipsis: true },
|
||||
{ title: 'SKU', dataIndex: 'product_sku', width: 150, render: (v) => <Typography.Text code>{v}</Typography.Text> },
|
||||
{ title: '商品', dataIndex: 'product_name', ellipsis: true },
|
||||
{ title: '金额', dataIndex: 'amount', width: 100, render: money },
|
||||
{ title: '基础金额', dataIndex: 'base_amount', width: 100, render: money },
|
||||
{ title: '手续费', dataIndex: 'service_fee_amount', width: 100, render: money },
|
||||
{ title: '扣款合计', dataIndex: 'amount', width: 100, render: money },
|
||||
{ title: '支付', dataIndex: 'payment_status', width: 90, render: paymentStatusTag },
|
||||
{ title: '履约', dataIndex: 'fulfillment_status', width: 100, render: fulfillmentStatusTag },
|
||||
{ title: '上游单号', dataIndex: 'provider_order_no', width: 140, ellipsis: true, render: (v) => v || '-' },
|
||||
@@ -399,6 +453,7 @@ export default function MerchantCenter() {
|
||||
{
|
||||
key: 'products',
|
||||
label: '商品',
|
||||
disabled: !hasFeature('products'),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
@@ -419,6 +474,7 @@ export default function MerchantCenter() {
|
||||
{
|
||||
key: 'orders',
|
||||
label: '履约订单',
|
||||
disabled: !hasFeature('orders'),
|
||||
children: (
|
||||
<Table
|
||||
rowKey="id"
|
||||
@@ -434,6 +490,7 @@ export default function MerchantCenter() {
|
||||
{
|
||||
key: 'wallet',
|
||||
label: '钱包',
|
||||
disabled: !hasFeature('wallet'),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
@@ -455,20 +512,25 @@ export default function MerchantCenter() {
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={ledgerColumns}
|
||||
dataSource={ledger.list}
|
||||
tableLayout="fixed"
|
||||
pagination={pageConfig(ledger, loadWallet)}
|
||||
/>
|
||||
{canFinance ? (
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
columns={ledgerColumns}
|
||||
dataSource={ledger.list}
|
||||
tableLayout="fixed"
|
||||
pagination={pageConfig(ledger, loadWallet)}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text type="secondary">当前角色可查看余额,钱包流水仅财务或负责人可见。</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
label: 'API 客户端',
|
||||
disabled: !hasFeature('api'),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
@@ -486,6 +548,7 @@ export default function MerchantCenter() {
|
||||
{
|
||||
key: 'callbacks',
|
||||
label: '回调',
|
||||
disabled: !hasFeature('callbacks'),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
@@ -679,6 +742,31 @@ function centsToYuan(value?: number | null) {
|
||||
return Number(((value || 0) / 100).toFixed(2))
|
||||
}
|
||||
|
||||
function featuresToList(features?: string) {
|
||||
const list = (features || '')
|
||||
.split(/[,\s]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
return list.length > 0 ? list : ['products', 'orders', 'wallet', 'api', 'callbacks']
|
||||
}
|
||||
|
||||
function resolveEnabledTab(current: string, features?: string) {
|
||||
const enabled = new Set(featuresToList(features))
|
||||
const tabFeatures: Record<string, string | null> = {
|
||||
products: 'products',
|
||||
orders: 'orders',
|
||||
wallet: 'wallet',
|
||||
api: 'api',
|
||||
callbacks: 'callbacks',
|
||||
members: null,
|
||||
}
|
||||
const feature = tabFeatures[current]
|
||||
if (feature === null || enabled.has(feature)) {
|
||||
return current
|
||||
}
|
||||
return ['products', 'orders', 'wallet', 'api', 'callbacks'].find((key) => enabled.has(key)) || 'members'
|
||||
}
|
||||
|
||||
function money(value?: number | null) {
|
||||
return `¥${centsToYuan(value).toFixed(2)}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user