feat(distribution): 补齐退款加价回退、本站自卖与开通默认规则

退款时压缩卖方加价,人工/自动批次仅完结单可入账,号主收入进主站钱包;仲裁后重算释放并支持作废未入账 DS。开通时四种模式按默认生效,对外口径合并为 35/40/25,不再单独展示通道费。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-10 18:13:46 +08:00
co-authored by Cursor
parent e621722218
commit 768902f2f7
28 changed files with 627 additions and 263 deletions
+8 -3
View File
@@ -115,6 +115,7 @@ export interface Site {
display_account_enabled: boolean
tax_enabled: boolean
tax_rate_bps: number
settlement_hold_days?: number
logo_url?: string
favicon_url?: string
banners_json?: string
@@ -302,9 +303,7 @@ export interface OnboardSitePayload {
display_account_enabled?: boolean
tax_enabled?: boolean
tax_rate_bps?: number
distributor_receive_ratio?: number
platform_sell_ratio?: number
service_fee_value?: number
settlement_hold_days?: number
}
export interface OnboardSiteResult {
@@ -440,6 +439,11 @@ export async function settleBatch(id: number): Promise<SettlementBatch> {
return extract(resp)
}
export async function cancelSettlementBatch(id: number): Promise<SettlementBatch> {
const resp = await api.post<ApiResponse<SettlementBatch>>(`/admin/distribution/settlement-batches/${id}/cancel`)
return extract(resp)
}
export async function listSettlementItems(batchID: number): Promise<SettlementItem[]> {
const resp = await api.get<ApiResponse<{ items: SettlementItem[] }>>(`/admin/distribution/settlement-batches/${batchID}/items`)
return extract(resp).items ?? []
@@ -509,6 +513,7 @@ export interface SiteWallet {
pending_release_cent: number
total_settled_cent: number
total_withdrawn_cent: number
settlement_hold_days?: number
}
export async function fetchSiteWallet(siteID: number): Promise<SiteWallet> {
+1 -7
View File
@@ -1030,13 +1030,7 @@ export default function Orders({ kind }: { kind: 'all' | 'sold' | 'receive' }) {
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ color: '#595959' }}>3. 减:支付通道手续费(千分之五):</span>
<span style={{ color: '#cf1322' }}>
- ¥{((selectedOrder.payment_fee_cent || 0) / 100).toFixed(2)}
</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ color: '#595959' }}>4. 可分润主站提价利润池:</span>
<span style={{ color: '#595959' }}>3. 可分润主站提价利润池:</span>
<strong>¥{((selectedOrder.split_base_amount_cent || 0) / 100).toFixed(2)}</strong>
</div>
+2 -8
View File
@@ -152,7 +152,7 @@ export default function Rules() {
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
租客通过本分销站点租赁主站共享号源池账号,获得扣除号主底价及通道费后的<strong>主站提价利润池 40% 分成</strong>。
租客通过本分销站点租赁主站共享号源池账号,获得扣除号主底价后的<strong>主站提价利润池 40% 分成</strong>。
</Typography.Text>
</Space>
</div>
@@ -200,7 +200,7 @@ export default function Rules() {
</Typography.Text>
</div>
<Typography.Text type="secondary" style={{ fontSize: 13 }}>
号主本金 100% 刚兑保障,支付通道手续费固定为 0.5%,订单履约验收后系统自动打款至商户钱包。
号主本金 100% 刚兑保障。利润按收号 / 卖出 / 服务费切分,订单完成并过冷却期后系统自动释放入账至商户钱包。
</Typography.Text>
</Space>
</div>
@@ -270,12 +270,6 @@ export default function Rules() {
width: 110,
render: (_: unknown, r: Rule) => <span>{r.service_fee_value}%</span>,
},
{
title: '通道手续费',
width: 110,
dataIndex: 'payment_fee_bps',
render: (v: number) => <span>{(v / 100).toFixed(2)}%</span>,
},
{
title: '生效时间',
dataIndex: 'effective_from',
+41 -10
View File
@@ -32,6 +32,7 @@ import {
listSettlementItems,
listSites,
settleBatch,
cancelSettlementBatch,
getMe,
type SettlementBatch,
type SettlementItem,
@@ -41,9 +42,11 @@ import { useMember } from '../MemberContext'
import type { Dayjs } from 'dayjs'
const STATUS_LABELS: Record<string, { label: string; color: string }> = {
settled: { label: '已打款结算', color: 'success' },
draft: { label: '待审核出账', color: 'warning' },
settled: { label: '已释放入账', color: 'success' },
draft: { label: '待释放入账', color: 'warning' },
pending: { label: '待释放入账', color: 'warning' },
processing: { label: '结算处理中', color: 'processing' },
cancelled: { label: '已作废', color: 'default' },
}
const PARTY_ROLE_LABELS: Record<string, string> = {
@@ -132,16 +135,34 @@ export default function Settlements() {
async function handleSettle(batch: SettlementBatch) {
Modal.confirm({
title: '确认打款结算?',
content: `即将对批次【${batch.batch_no}】执行结算操作,打款金额 ${formatMoney(batch.total_amount_cent)} 将计入商户可用余额。`,
title: '立即释放入账?',
content: `即将把批次【${batch.batch_no}】的 ${formatMoney(batch.total_amount_cent)} 转入商户可提现余额,人工批次会绕过冷却期。`,
onOk: async () => {
try {
await settleBatch(batch.id)
message.success(`批次 ${batch.batch_no} 已成功结算入账!`)
message.success(`批次 ${batch.batch_no} 已释放入账`)
if (siteID) void load(siteID)
} catch (error: unknown) {
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
message.error(respMessage ?? '结算失败')
message.error(respMessage ?? '释放失败')
}
},
})
}
async function handleCancel(batch: SettlementBatch) {
Modal.confirm({
title: '作废未入账批次?',
content: `作废【${batch.batch_no}】后,其中订单可重新进入自动释放或新的人工批次。`,
okButtonProps: { danger: true },
onOk: async () => {
try {
await cancelSettlementBatch(batch.id)
message.success(`批次 ${batch.batch_no} 已作废`)
if (siteID) void load(siteID)
} catch (error: unknown) {
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
message.error(respMessage ?? '作废失败')
}
},
})
@@ -170,7 +191,7 @@ export default function Settlements() {
</Typography.Title>
<Typography.Text type="secondary">
{isSuperadmin
? '系统按站点自动结算入账并生成批次;也可手工创建批次立即结算'
? '冷却期满后系统按站点按天自动释放入账;也可手工创建批次立即入账,或作废未入账草稿以免挡住自动释放'
: '订单完成冷却期结束后系统自动释放入账,此处查看对账批次与入账明细'}
</Typography.Text>
</div>
@@ -288,7 +309,7 @@ export default function Settlements() {
},
{
title: '操作',
width: 160,
width: 240,
render: (_: unknown, row: SettlementBatch) => (
<Space>
<Button
@@ -299,14 +320,24 @@ export default function Settlements() {
>
查看明细
</Button>
{isSuperadmin && row.status !== 'settled' && (
{isSuperadmin && row.status !== 'settled' && row.status !== 'cancelled' && (
<Button
type="link"
size="small"
style={{ color: '#52c41a' }}
onClick={() => void handleSettle(row)}
>
确认打款结算
立即释放入账
</Button>
)}
{isSuperadmin && row.status !== 'settled' && row.status !== 'cancelled' && (
<Button
type="link"
size="small"
danger
onClick={() => void handleCancel(row)}
>
作废
</Button>
)}
</Space>
+12 -31
View File
@@ -43,12 +43,10 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
form.resetFields()
form.setFieldsValue({
member_type: 'operator',
distributor_receive_ratio: 35,
platform_sell_ratio: 40,
service_fee_value: 25,
display_account_enabled: true,
tax_enabled: false,
tax_rate_bps: 100,
settlement_hold_days: 7,
})
setModalOpen(true)
}
@@ -67,9 +65,7 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
display_account_enabled: values.display_account_enabled,
tax_enabled: values.tax_enabled,
tax_rate_bps: values.tax_enabled ? values.tax_rate_bps : 0,
distributor_receive_ratio: Number(values.distributor_receive_ratio),
platform_sell_ratio: Number(values.platform_sell_ratio),
service_fee_value: Number(values.service_fee_value),
settlement_hold_days: Number(values.settlement_hold_days ?? 7),
}
const res = await onboardSite(payload)
message.success('分销站点及分销商管理员开通成功!')
@@ -254,31 +250,18 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
<Divider style={{ margin: '16px 0' }} />
<Typography.Title level={5}>3. 分销比例与佣金规则初始化</Typography.Title>
<Typography.Title level={5}>3. 结算冷却期与展示</Typography.Title>
<Typography.Paragraph type="secondary" style={{ marginTop: -8 }}>
开通后四种分成模式均按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖 75% + 25%)。之后可在规则页调整。
</Typography.Paragraph>
<Space direction="horizontal" style={{ width: '100%' }} size="middle">
<Form.Item
label="分销商收号分成 (%)"
name="distributor_receive_ratio"
tooltip="卖出后利润的 35%"
label="结算冷却期 (天)"
name="settlement_hold_days"
tooltip="订单完成后进入冷却期,期满自动释放到可提现余额;0 表示完成即释放"
style={{ flex: 1 }}
>
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="平台卖号分成 (%)"
name="platform_sell_ratio"
tooltip="卖出后利润的 40%"
style={{ flex: 1 }}
>
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="平台服务费 (%)"
name="service_fee_value"
tooltip="卖出后利润的 25%"
style={{ flex: 1 }}
>
<InputNumber min={0} max={100} step={1} style={{ width: '100%' }} />
<InputNumber min={0} max={365} step={1} style={{ width: '100%' }} />
</Form.Item>
</Space>
@@ -330,10 +313,8 @@ export default function Sites({ isSuperadmin = true }: { isSuperadmin?: boolean
<Descriptions.Item label="所属角色">
{successResult.member.member_type}
</Descriptions.Item>
<Descriptions.Item label="初始规则版本">
收号分成: {successResult.rule.distributor_receive_ratio}% · 卖号分成:{' '}
{successResult.rule.platform_sell_ratio}% · 服务费:{' '}
{successResult.rule.service_fee_value}%
<Descriptions.Item label="初始分成规则">
四种模式均已按默认生效:收号 35% / 卖出 40% / 服务费 25%(本站自卖 75% + 25%),可到规则页调整
</Descriptions.Item>
</Descriptions>
<div style={{ marginTop: 16, textAlign: 'right' }}>
+4 -1
View File
@@ -167,6 +167,9 @@ export default function Wallet() {
</Typography.Title>
<Typography.Text type="secondary">
查看分销结算收益到账情况,申请提取收益并实时追踪财务打款流水。
{wallet?.settlement_hold_days != null
? ` 当前站点冷却期 ${wallet.settlement_hold_days} 天。`
: ''}
</Typography.Text>
</div>
<Space>
@@ -208,7 +211,7 @@ export default function Wallet() {
<Col flex="1">
<Card bordered={false} style={{ background: '#fafafa' }}>
<Statistic
title="待释放收益 (订单完成冷却中)"
title="待释放收益 (冷却期内)"
value={((wallet?.pending_release_cent ?? 0) / 100).toFixed(2)}
prefix={<HourglassOutlined style={{ color: '#1677ff' }} />}
suffix="元"