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:
@@ -17,6 +17,7 @@ type SiteDTO struct {
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id,omitempty"`
|
||||
ListingQrcodeURL string `json:"listing_qrcode_url"`
|
||||
ReceiveRatioDelta float64 `json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta float64 `json:"receive_cost_delta"`
|
||||
TaxEnabled bool `json:"tax_enabled"`
|
||||
TaxRateBPS int64 `json:"tax_rate_bps"`
|
||||
SettlementHoldDays int `json:"settlement_hold_days"`
|
||||
@@ -32,6 +33,7 @@ type CreateSiteRequest struct {
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id"`
|
||||
ListingQrcodeURL *string `json:"listing_qrcode_url"`
|
||||
ReceiveRatioDelta *float64 `json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta *float64 `json:"receive_cost_delta"`
|
||||
TaxEnabled *bool `json:"tax_enabled"`
|
||||
TaxRateBPS *int64 `json:"tax_rate_bps"`
|
||||
SettlementHoldDays *int `json:"settlement_hold_days"`
|
||||
@@ -45,6 +47,7 @@ type UpdateSiteRequest struct {
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id"`
|
||||
ListingQrcodeURL *string `json:"listing_qrcode_url"`
|
||||
ReceiveRatioDelta *float64 `json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta *float64 `json:"receive_cost_delta"`
|
||||
TaxEnabled *bool `json:"tax_enabled"`
|
||||
TaxRateBPS *int64 `json:"tax_rate_bps"`
|
||||
SettlementHoldDays *int `json:"settlement_hold_days"`
|
||||
@@ -248,6 +251,7 @@ type PublicSiteInfoDTO struct {
|
||||
FooterInfoJSON string `json:"footer_info_json"`
|
||||
DefaultSupportGroupID *uint64 `json:"default_support_group_id,omitempty"`
|
||||
ReceiveRatioDelta float64 `json:"receive_ratio_delta"`
|
||||
ReceiveCostDelta float64 `json:"receive_cost_delta"`
|
||||
}
|
||||
|
||||
type UpdateAccountDisplayRequest struct {
|
||||
|
||||
@@ -73,6 +73,13 @@ func normalizeReceiveRatioDelta(delta float64) (float64, error) {
|
||||
return listingpkg.NormalizeSiteReceiveRatioDelta(delta), nil
|
||||
}
|
||||
|
||||
func normalizeReceiveCostDelta(delta float64) (float64, error) {
|
||||
if delta < 0 {
|
||||
return 0, ErrInvalidAmount
|
||||
}
|
||||
return listingpkg.NormalizeSiteReceiveCostDelta(delta), nil
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
@@ -333,6 +340,13 @@ func (r *Repository) CreateSite(ctx context.Context, req CreateSiteRequest) (*Si
|
||||
}
|
||||
row.ReceiveRatioDelta = delta
|
||||
}
|
||||
if req.ReceiveCostDelta != nil {
|
||||
delta, err := normalizeReceiveCostDelta(*req.ReceiveCostDelta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
row.ReceiveCostDelta = delta
|
||||
}
|
||||
if req.TaxEnabled != nil {
|
||||
row.TaxEnabled = *req.TaxEnabled
|
||||
}
|
||||
@@ -385,6 +399,13 @@ func (r *Repository) UpdateSite(ctx context.Context, adminID, id uint64, req Upd
|
||||
}
|
||||
row.ReceiveRatioDelta = delta
|
||||
}
|
||||
if req.ReceiveCostDelta != nil {
|
||||
delta, err := normalizeReceiveCostDelta(*req.ReceiveCostDelta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
row.ReceiveCostDelta = delta
|
||||
}
|
||||
if !globalAccess {
|
||||
if err := tx.Save(&row).Error; err != nil {
|
||||
return err
|
||||
@@ -573,6 +594,7 @@ func siteDTO(row model.DistributionSite) SiteDTO {
|
||||
DefaultSupportGroupID: row.DefaultSupportGroupID,
|
||||
ListingQrcodeURL: row.ListingQrcodeURL,
|
||||
ReceiveRatioDelta: row.ReceiveRatioDelta,
|
||||
ReceiveCostDelta: row.ReceiveCostDelta,
|
||||
TaxEnabled: row.TaxEnabled,
|
||||
TaxRateBPS: row.TaxRateBPS,
|
||||
SettlementHoldDays: row.SettlementHoldDays,
|
||||
@@ -2330,6 +2352,7 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
|
||||
AnnouncementsJSON: site.AnnouncementsJSON,
|
||||
FooterInfoJSON: site.FooterInfoJSON,
|
||||
ReceiveRatioDelta: site.ReceiveRatioDelta,
|
||||
ReceiveCostDelta: site.ReceiveCostDelta,
|
||||
}
|
||||
if r.redis != nil {
|
||||
if bytes, err := json.Marshal(res); err == nil {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestCalculateSellerReferenceRatioUsesMainSiteConfig(t *testing.T) {
|
||||
|
||||
func TestApplyMainSiteReceivePricingOverwritesClientRatio(t *testing.T) {
|
||||
req := fullReceivePricingRequest(10)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, false)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, false)
|
||||
breakdown := ensurePriceBreakdown(req.AssetSummary)
|
||||
if readSummaryNumber(breakdown["seller_ratio"]) != 43 {
|
||||
t.Fatalf("seller_ratio = %v, want 43", breakdown["seller_ratio"])
|
||||
@@ -61,7 +61,7 @@ func TestApplyMainSiteReceivePricingOverwritesClientRatio(t *testing.T) {
|
||||
|
||||
func TestApplyMainSiteReceivePricingKeepsAcceleratedRatioWithinMainSiteRange(t *testing.T) {
|
||||
req := fullReceivePricingRequest(50)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, false)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, false)
|
||||
breakdown := ensurePriceBreakdown(req.AssetSummary)
|
||||
if readSummaryNumber(breakdown["seller_ratio"]) != 50 {
|
||||
t.Fatalf("accelerated seller_ratio = %v, want 50", breakdown["seller_ratio"])
|
||||
@@ -71,27 +71,55 @@ func TestApplyMainSiteReceivePricingKeepsAcceleratedRatioWithinMainSiteRange(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistributionReceiveRatioCannotExceedMainSite(t *testing.T) {
|
||||
func TestDistributionReceiveCostDeltaZeroKeepsMainSitePrice(t *testing.T) {
|
||||
req := fullReceivePricingRequest(50)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, true)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, true)
|
||||
breakdown := ensurePriceBreakdown(req.AssetSummary)
|
||||
if readSummaryNumber(breakdown["seller_ratio"]) != 43 {
|
||||
t.Fatalf("distribution seller_ratio = %v, want main-site ceiling 43", breakdown["seller_ratio"])
|
||||
t.Fatalf("distribution seller_ratio = %v, want main-site reference 43 when cost delta is 0", breakdown["seller_ratio"])
|
||||
}
|
||||
if readSummaryNumber(breakdown["buyer_total_price"]) != 263.2 {
|
||||
t.Fatalf("distribution buyer_total_price = %v, want 263.2 anchored to main site", breakdown["buyer_total_price"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistributionReceiveRatioDeltaLowersBelowMainSite(t *testing.T) {
|
||||
func TestDistributionReceiveCostDeltaLowersReceivePriceKeepsSellPrice(t *testing.T) {
|
||||
req := fullReceivePricingRequest(50)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 3, true)
|
||||
applyMainSiteReceivePricing(&req, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 3, true)
|
||||
breakdown := ensurePriceBreakdown(req.AssetSummary)
|
||||
if readSummaryNumber(breakdown["seller_ratio"]) != 40 {
|
||||
t.Fatalf("distribution seller_ratio = %v, want 40 after lowering 3 points", breakdown["seller_ratio"])
|
||||
// 压价 3 点:收号比例 = R+3 = 46,号主收 217.4(低于主站 232.6)。
|
||||
if readSummaryNumber(breakdown["seller_ratio"]) != 46 {
|
||||
t.Fatalf("distribution seller_ratio = %v, want 46 (R+3) under cost-decoupled model", breakdown["seller_ratio"])
|
||||
}
|
||||
if readSummaryNumber(breakdown["seller_reference_ratio"]) != 43 {
|
||||
t.Fatalf("seller_reference_ratio = %v, want 43", breakdown["seller_reference_ratio"])
|
||||
if got := readSummaryNumber(breakdown["seller_coin_base_price"]); got != 217.4 {
|
||||
t.Fatalf("seller_coin_base_price = %v, want 217.4 (lower than main site 232.6)", got)
|
||||
}
|
||||
if readSummaryNumber(breakdown["site_receive_ratio_delta"]) != 3 {
|
||||
t.Fatalf("site_receive_ratio_delta = %v, want 3", breakdown["site_receive_ratio_delta"])
|
||||
// 卖价锚定主站:买家比例仍为 38、买家价仍为 263.2,不随收号价下降。
|
||||
if readSummaryNumber(breakdown["buyer_ratio"]) != 38 {
|
||||
t.Fatalf("buyer_ratio = %v, want 38 (decoupled from receive ratio)", breakdown["buyer_ratio"])
|
||||
}
|
||||
if readSummaryNumber(breakdown["buyer_total_price"]) != 263.2 {
|
||||
t.Fatalf("buyer_total_price = %v, want 263.2 unchanged by cost delta", breakdown["buyer_total_price"])
|
||||
}
|
||||
// 注意:发布快照的 platform_markup_amount 仍为主站口径提价(30.6,锚定价→买价);
|
||||
// 压价差不在此体现,而是结算层 profit = 净付款 − 号主快照价(217.4)自动进入 35/40/25 分成池。
|
||||
if readSummaryNumber(breakdown["platform_markup_amount"]) != 30.6 {
|
||||
t.Fatalf("platform_markup_amount = %v, want 30.6 (main-site anchored markup)", breakdown["platform_markup_amount"])
|
||||
}
|
||||
if readSummaryNumber(breakdown["site_receive_cost_delta"]) != 3 {
|
||||
t.Fatalf("site_receive_cost_delta = %v, want 3", breakdown["site_receive_cost_delta"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistributionReceivePriceNeverAboveMainSite(t *testing.T) {
|
||||
mainReq := fullReceivePricingRequest(50)
|
||||
applyMainSiteReceivePricing(&mainReq, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 0, false)
|
||||
siteReq := fullReceivePricingRequest(50)
|
||||
applyMainSiteReceivePricing(&siteReq, systemconfig.DefaultPublishOptions(), defaultSalePriceConfig(), 0, 10, true)
|
||||
mainPrice := readSummaryNumber(ensurePriceBreakdown(mainReq.AssetSummary)["seller_total_price"])
|
||||
sitePrice := readSummaryNumber(ensurePriceBreakdown(siteReq.AssetSummary)["seller_total_price"])
|
||||
if sitePrice >= mainPrice {
|
||||
t.Fatalf("distribution receive price %v must stay below main-site %v (dead boundary)", sitePrice, mainPrice)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ func (s *Service) Create(ctx context.Context, ownerID uint64, req CreateRequest,
|
||||
if len(siteCode) > 0 {
|
||||
effectiveSiteCode = strings.TrimSpace(siteCode[0])
|
||||
}
|
||||
delta, onDistribution := s.repo.ResolveReceiveRatioDelta(ctx, 0, effectiveSiteCode)
|
||||
if err := s.applyMainSiteReceivePricing(ctx, &req, delta, onDistribution); err != nil {
|
||||
delta, onDistribution := s.repo.resolveSiteReceiveCostDelta(ctx, 0, effectiveSiteCode)
|
||||
if err := s.applyMainSiteReceivePricing(ctx, &req, 0, delta, onDistribution); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules, err := s.publishRules(ctx)
|
||||
@@ -49,8 +49,8 @@ func (s *Service) Update(ctx context.Context, ownerID uint64, id uint64, req Upd
|
||||
if len(siteCode) > 0 {
|
||||
effectiveSiteCode = strings.TrimSpace(siteCode[0])
|
||||
}
|
||||
delta, onDistribution := s.repo.ResolveReceiveRatioDelta(ctx, id, effectiveSiteCode)
|
||||
if err := s.applyMainSiteReceivePricing(ctx, &req, delta, onDistribution); err != nil {
|
||||
delta, onDistribution := s.repo.resolveSiteReceiveCostDelta(ctx, id, effectiveSiteCode)
|
||||
if err := s.applyMainSiteReceivePricing(ctx, &req, 0, delta, onDistribution); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules, err := s.publishRules(ctx)
|
||||
|
||||
@@ -1116,7 +1116,7 @@ func TestCreateDistributionListingUsesMainSiteReceiveRatio(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDistributionListingAppliesSiteReceiveRatioDelta(t *testing.T) {
|
||||
func TestCreateDistributionListingAppliesSiteReceiveCostDelta(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := database.MigrateListingLifecycleTestSchema(db); err != nil {
|
||||
t.Fatalf("migrate err: %v", err)
|
||||
@@ -1129,11 +1129,11 @@ func TestCreateDistributionListingAppliesSiteReceiveRatioDelta(t *testing.T) {
|
||||
}
|
||||
site := model.DistributionSite{
|
||||
SiteCode: "dist_ratio_down",
|
||||
SiteName: "下调收号站",
|
||||
SiteName: "压价收号站",
|
||||
Domain: "ratio-down.example.com",
|
||||
Status: "active",
|
||||
DisplayAccountEnabled: true,
|
||||
ReceiveRatioDelta: 3,
|
||||
ReceiveCostDelta: 3,
|
||||
}
|
||||
if err := db.Create(&site).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1169,8 +1169,8 @@ func TestCreateDistributionListingAppliesSiteReceiveRatioDelta(t *testing.T) {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
pricing := ReceivePricingFromAssetSummary(mustMarshalSummary(t, dto.AssetSummary))
|
||||
if pricing.SellerRatio != 40 {
|
||||
t.Fatalf("seller_ratio = %v, want 40 after site delta 3 below main-site 43", pricing.SellerRatio)
|
||||
if pricing.SellerRatio != 46 {
|
||||
t.Fatalf("seller_ratio = %v, want 46 after site cost delta 3 (R=43+3) under decoupled model", pricing.SellerRatio)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,48 @@ import (
|
||||
"hfb_sys/backend/internal/model"
|
||||
)
|
||||
|
||||
// ReceiveCostDeltaBySiteCode 读取分站收号压价点数(新口径,收号比例 = R + 点数)。
|
||||
func (r *Repository) ReceiveCostDeltaBySiteCode(ctx context.Context, siteCode string) float64 {
|
||||
siteCode = strings.TrimSpace(siteCode)
|
||||
if r == nil || r.db == nil || siteCode == "" {
|
||||
return 0
|
||||
}
|
||||
var delta float64
|
||||
_ = r.db.WithContext(ctx).Model(&model.DistributionSite{}).
|
||||
Select("COALESCE(receive_cost_delta, 0)").
|
||||
Where("site_code = ? AND status = 'active'", siteCode).
|
||||
Limit(1).
|
||||
Scan(&delta)
|
||||
return NormalizeSiteReceiveCostDelta(delta)
|
||||
}
|
||||
|
||||
// resolveSiteReceiveCostDelta 按站点编码或商品归属站点解析压价点数。
|
||||
// 返回 onDistribution 表示当前上下文是否为分站(false 时压价不生效)。
|
||||
func (r *Repository) resolveSiteReceiveCostDelta(ctx context.Context, listingID uint64, siteCode string) (float64, bool) {
|
||||
if code := strings.TrimSpace(siteCode); code != "" {
|
||||
return r.ReceiveCostDeltaBySiteCode(ctx, code), true
|
||||
}
|
||||
if r == nil || r.db == nil || listingID == 0 {
|
||||
return 0, false
|
||||
}
|
||||
var siteID *uint64
|
||||
if err := r.db.WithContext(ctx).Model(&model.RentalListing{}).
|
||||
Select("distribution_site_id").
|
||||
Where("id = ?", listingID).
|
||||
Limit(1).
|
||||
Scan(&siteID).Error; err != nil || siteID == nil || *siteID == 0 {
|
||||
return 0, false
|
||||
}
|
||||
var delta float64
|
||||
_ = r.db.WithContext(ctx).Model(&model.DistributionSite{}).
|
||||
Select("COALESCE(receive_cost_delta, 0)").
|
||||
Where("id = ? AND status = 'active'", *siteID).
|
||||
Limit(1).
|
||||
Scan(&delta)
|
||||
return NormalizeSiteReceiveCostDelta(delta), true
|
||||
}
|
||||
|
||||
// ReceiveRatioDeltaBySiteCode 旧口径(加价抢货)读取,保留只读兼容。
|
||||
func (r *Repository) ReceiveRatioDeltaBySiteCode(ctx context.Context, siteCode string) float64 {
|
||||
siteCode = strings.TrimSpace(siteCode)
|
||||
if r == nil || r.db == nil || siteCode == "" {
|
||||
@@ -21,6 +63,7 @@ func (r *Repository) ReceiveRatioDeltaBySiteCode(ctx context.Context, siteCode s
|
||||
return NormalizeSiteReceiveRatioDelta(delta)
|
||||
}
|
||||
|
||||
// ResolveReceiveRatioDelta 旧口径解析入口,保留供旧快照读侧使用。
|
||||
func (r *Repository) ResolveReceiveRatioDelta(ctx context.Context, listingID uint64, siteCode string) (float64, bool) {
|
||||
if code := strings.TrimSpace(siteCode); code != "" {
|
||||
return r.ReceiveRatioDeltaBySiteCode(ctx, code), true
|
||||
|
||||
Reference in New Issue
Block a user