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
@@ -36,7 +36,7 @@ func ReceivePricingFromAssetSummary(raw datatypes.JSON) ReceivePricingView {
}
}
func (s *Service) applyMainSiteReceivePricing(ctx context.Context, req *CreateRequest, receiveRatioDelta float64, onDistributionSite bool) error {
func (s *Service) applyMainSiteReceivePricing(ctx context.Context, req *CreateRequest, receiveRatioDelta, receiveCostDelta float64, onDistributionSite bool) error {
if req == nil {
return nil
}
@@ -48,11 +48,12 @@ func (s *Service) applyMainSiteReceivePricing(ctx context.Context, req *CreateRe
if err != nil {
return err
}
applyMainSiteReceivePricing(req, options, config, receiveRatioDelta, onDistributionSite)
applyMainSiteReceivePricing(req, options, config, receiveRatioDelta, receiveCostDelta, onDistributionSite)
return nil
}
func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.PublishOptionsDTO, config salePriceConfig, receiveRatioDelta float64, onDistributionSite bool) {
func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.PublishOptionsDTO, config salePriceConfig, receiveRatioDelta, receiveCostDelta float64, onDistributionSite bool) {
if req == nil {
return
}
@@ -70,18 +71,20 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
if requestedRatio <= 0 {
requestedRatio = readSummaryNumber(breakdown["accelerated_sale_ratio"])
}
costDelta := NormalizeSiteReceiveCostDelta(receiveCostDelta)
ratioDelta := NormalizeSiteReceiveRatioDelta(receiveRatioDelta)
sellerRatio := clampSaleRatio(referenceRatio, requestedRatio)
delta := NormalizeSiteReceiveRatioDelta(receiveRatioDelta)
// 分站新口径:收号比例 = R + 压价点数(收号价更低),买家定价锚定主站参考比例 R,不随收号价联动。
if onDistributionSite {
sellerRatio = ApplySiteReceiveRatioDelta(referenceRatio, delta)
sellerRatio = ApplySiteReceiveCostDelta(referenceRatio, costDelta)
}
coinWan := coinM * 100
sellerCoinBasePrice := roundMoney(coinWan / sellerRatio)
consumablePrice := consumableValue(req.AssetSummary)
sellerTotalPrice := roundMoney(sellerCoinBasePrice + consumablePrice)
dailyLoss := readSummaryNumber(req.AssetSummary["daily_loss_m"])
pricing := calculateExternalPlatformPricing(coinM, sellerRatio, sellerCoinBasePrice, consumablePrice, config)
// 买家价始终按主站口径推导:分站只改收号侧,卖价与主站同基准(压价差进入三方分成池)。
pricing := calculateExternalPlatformPricing(coinM, referenceRatio, referenceSellerCoinBasePrice(coinWan, referenceRatio), consumablePrice, config)
breakdown["seller_reference_ratio"] = referenceRatio
breakdown["seller_ratio"] = sellerRatio
breakdown["seller_coin_base_price"] = sellerCoinBasePrice
@@ -89,7 +92,12 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
breakdown["consumable_price"] = consumablePrice
breakdown["daily_loss_ratio_adjustment"] = calculateDailyLossRatioAdjustment(dailyLoss)
breakdown["accelerated_sale_ratio"] = sellerRatio
breakdown["site_receive_ratio_delta"] = delta
if onDistributionSite {
breakdown["site_receive_cost_delta"] = costDelta
breakdown["pricing_model"] = "site_cost_decoupled_v1"
} else {
breakdown["site_receive_ratio_delta"] = ratioDelta
}
breakdown["buyer_coin_base_price"] = pricing.BuyerCoinBasePrice
breakdown["buyer_total_price"] = pricing.BuyerTotalPrice
breakdown["buyer_ratio"] = pricing.BuyerRatio
@@ -100,6 +108,36 @@ func applyMainSiteReceivePricing(req *CreateRequest, options systemconfig.Publis
req.PriceCent = yuanToCent(pricing.BuyerTotalPrice)
}
// NormalizeSiteReceiveCostDelta 将分站收号压价点数限制在 [0, 20]。
// 点数越大,分站收号比例越高(相对主站参考比例),收号价越低;0 表示与主站同价收号。
func NormalizeSiteReceiveCostDelta(delta float64) float64 {
if delta < 0 || math.IsNaN(delta) || math.IsInf(delta, 0) {
return 0
}
if delta > maxSiteReceiveRatioDelta {
return maxSiteReceiveRatioDelta
}
return roundRatio(delta)
}
// ApplySiteReceiveCostDelta 按新口径计算分站收号比例:R + 压价点数(收号价更低)。
// 与旧 ApplySiteReceiveRatioDelta 相反方向,买家价已解耦、不再从收号比例推导。
func ApplySiteReceiveCostDelta(referenceRatio, delta float64) float64 {
if referenceRatio <= 0 {
return 0
}
siteRatio := roundRatio(referenceRatio + NormalizeSiteReceiveCostDelta(delta))
return siteRatio
}
// referenceSellerCoinBasePrice 计算主站参考比例下的号主基础价,用作买家定价锚点。
func referenceSellerCoinBasePrice(coinWan, referenceRatio float64) float64 {
if referenceRatio <= 0 {
return 0
}
return roundMoney(coinWan / referenceRatio)
}
// NormalizeSiteReceiveRatioDelta 将分站收号下调点数限制在 [0, 20]。
func NormalizeSiteReceiveRatioDelta(delta float64) float64 {
if delta < 0 || math.IsNaN(delta) || math.IsInf(delta, 0) {