feat(distribution): 分站收号压价解耦买家价(方案 B 新口径)

核心语义反转:receive_cost_delta 为压价点数,收号比例 = 主站参考比例 R + 点数,
点数越大号主收号价越低;买家售价锚定主站参考比例推导,不再随收号价联动。

- 迁移 000072:distribution_sites 新增 receive_cost_delta(旧 receive_ratio_delta 保留只读)
- receive_pricing:新增 Normalize/ApplySiteReceiveCostDelta;分站定价买家侧改用
  R 锚点(referenceSellerCoinBasePrice),快照写入 site_receive_cost_delta 与 pricing_model 标记
- site_receive:新增 resolveSiteReceiveCostDelta 读取链路,旧 ResolveReceiveRatioDelta 保留
- 分成不变:压价差经结算层 profit 自动进入 35/40/25 三方分成池;
  货架自主加价仍 100% 归分站,两条边界均以主站为锚
- 权限对齐旧字段:主站全局管理员与分站操作员均可设置
- 前端三端同步:主站 pricing.ts/usePricingCalculator 买卖解耦试算、
  主站后台弹窗改「收号压价」价格语义文案、分销后台 SiteSettings 同步
- 测试:单测锚定 R=43 时 cost_delta=3 → 收号 217.4 / 卖价 263.2 不变 / markup 池 30.6→54.9
This commit is contained in:
yml2213
2026-09-17 15:54:56 +08:00
parent 8dc21d2757
commit 22fcaa5945
17 changed files with 253 additions and 62 deletions
+5 -1
View File
@@ -129,6 +129,7 @@ export interface Site {
footer_info_json?: string
listing_qrcode_url?: string
receive_ratio_delta?: number
receive_cost_delta?: number
created_at: string
}
@@ -338,7 +339,10 @@ export async function listSites(): Promise<Site[]> {
return extract(resp).items ?? []
}
export async function updateSite(siteID: number, payload: Partial<Pick<Site, 'receive_ratio_delta'>>): Promise<Site> {
export async function updateSite(
siteID: number,
payload: Partial<Pick<Site, 'receive_ratio_delta' | 'receive_cost_delta'>>
): Promise<Site> {
const resp = await api.put<ApiResponse<Site>>(`/distribution/sites/${siteID}`, payload)
return extract(resp)
}
+11 -11
View File
@@ -84,7 +84,7 @@ export default function SiteSettings() {
icp: '',
contact: '',
})
const [receiveRatioDelta, setReceiveRatioDelta] = useState(0)
const [receiveCostDelta, setReceiveCostDelta] = useState(0)
const [savingRatio, setSavingRatio] = useState(false)
// SMS config state
@@ -140,7 +140,7 @@ export default function SiteSettings() {
} catch {
setFooterInfo({})
}
setReceiveRatioDelta(Math.max(0, found.receive_ratio_delta || 0))
setReceiveCostDelta(Math.max(0, found.receive_cost_delta || 0))
try {
const sms = await fetchSiteSMSConfig(id)
@@ -234,10 +234,10 @@ export default function SiteSettings() {
if (!siteID) return
setSavingRatio(true)
try {
const updated = await updateSite(siteID, { receive_ratio_delta: receiveRatioDelta })
const updated = await updateSite(siteID, { receive_cost_delta: receiveCostDelta })
setCurrentSite(updated)
setReceiveRatioDelta(Math.max(0, updated.receive_ratio_delta || 0))
message.success('收号比例已保存,本站上传账号将按新比例计价')
setReceiveCostDelta(Math.max(0, updated.receive_cost_delta || 0))
message.success('收号压价已保存,本站收号价比主站更低,售价与主站一致')
} catch (error: unknown) {
const respMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message
message.error(respMessage || '保存收号比例失败')
@@ -421,26 +421,26 @@ export default function SiteSettings() {
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<div>
<Typography.Title level={5} style={{ marginBottom: 4 }}>
本站收号比例
本站收号压价
</Typography.Title>
<Typography.Text type="secondary">
主站回收比例是上限。下调点数越大,号主底价越高、本站利润空间越大,但不能高于主站比例。0 表示与主站一致。
压价点数越大,号主在本站拿到的收号价越低。买家售价保持与主站一致,压价差进入三方分成(本站 35% / 卖出方 40% / 平台 25%)。0 表示与主站同价收号。
</Typography.Text>
</div>
<Space wrap align="center">
<Typography.Text>相对主站下调</Typography.Text>
<Typography.Text>收号价比主站低</Typography.Text>
<InputNumber
min={0}
max={20}
step={0.1}
precision={1}
value={receiveRatioDelta}
onChange={(value) => setReceiveRatioDelta(Math.max(0, Number(value || 0)))}
value={receiveCostDelta}
onChange={(value) => setReceiveCostDelta(Math.max(0, Number(value || 0)))}
addonAfter="点"
style={{ width: 160 }}
/>
<Button type="primary" icon={<SaveOutlined />} loading={savingRatio} onClick={() => void handleSaveReceiveRatio()}>
保存收号比例
保存收号压价
</Button>
</Space>
</Space>