refactor(distribution): 重写分销迁移并落地利润百分比分成与货架比例

将 000064-000078 合并为 064/065/066,默认规则改为收号 35%、卖出 40%、服务费 25%。
货架调价改为增加分站 1 元兑换哈夫币比例。sql-guard 仅保护 main 已发布迁移。
This commit is contained in:
yml2213
2026-09-08 19:02:58 +08:00
parent 48a2961245
commit d762b33f54
48 changed files with 745 additions and 1073 deletions
+2
View File
@@ -38,6 +38,8 @@ type OrderDTO struct {
BasePriceCent int64 `json:"base_price_cent,omitempty"`
ShelfMarkupCent int64 `json:"shelf_markup_cent,omitempty"`
MarkupSiteID uint64 `json:"markup_site_id,omitempty"`
ShelfRatio float64 `json:"shelf_ratio,omitempty"`
ShelfRatioDelta float64 `json:"shelf_ratio_delta,omitempty"`
RentOriginalAmountCent int64 `json:"rent_original_amount_cent,omitempty"`
RentDiscountAmountCent int64 `json:"rent_discount_amount_cent,omitempty"`
PureCoinOriginalAmountCent int64 `json:"pure_coin_original_amount_cent,omitempty"`
+27 -10
View File
@@ -11,6 +11,7 @@ import (
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/chat"
"hfb_sys/backend/internal/modules/distribution"
listingpkg "hfb_sys/backend/internal/modules/listing"
"hfb_sys/backend/internal/modules/notification"
"hfb_sys/backend/internal/modules/rentergrowth"
"hfb_sys/backend/internal/processlog"
@@ -58,14 +59,28 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
return err
}
rentHours := estimateOrderDurationHours(snapshot)
shelfMarkupCent, markupSiteID, err := loadShelfMarkupTx(tx, req.DistributionSiteCode, listing.ID)
shelfRatioDelta, markupSiteID, err := loadShelfRatioTx(tx, req.DistributionSiteCode, listing.ID)
if err != nil {
return err
}
pricing := buildOrderPricing(listing, account)
basePriceCent := listing.PriceCent
if shelfMarkupCent > 0 {
pricing.RentAmountCent += shelfMarkupCent
sitePriceCent, _, siteRatio := listingpkg.ApplyShelfRatio(account.HafCoinAmount, listing.PriceCent, shelfRatioDelta)
shelfMarkupCent := sitePriceCent - basePriceCent
if sitePriceCent != listing.PriceCent {
pricing.RentAmountCent = sitePriceCent
if pricing.OwnerRentAmountCent > pricing.RentAmountCent {
pricing.OwnerRentAmountCent = pricing.RentAmountCent
}
pricing.PlatformFeeCent = maxCent(pricing.RentAmountCent-pricing.OwnerRentAmountCent, 0)
if pricing.ExtraItemAmountCent > pricing.RentAmountCent {
pricing.ExtraItemAmountCent = pricing.RentAmountCent
}
pricing.PureCoinAmountCent = maxCent(pricing.RentAmountCent-pricing.ExtraItemAmountCent, 0)
if pricing.OwnerPureCoinAmountCent > pricing.OwnerRentAmountCent {
pricing.OwnerPureCoinAmountCent = pricing.OwnerRentAmountCent
}
pricing.PureCoinPlatformFeeCent = maxCent(pricing.PureCoinAmountCent-pricing.OwnerPureCoinAmountCent, 0)
}
distributionSite, distributionRule, err := distribution.ResolveOrderSourceTx(tx, req.DistributionSiteCode, listing.ID, account.ID, time.Now())
if err != nil {
@@ -116,6 +131,8 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
BasePriceCent: basePriceCent,
ShelfMarkupCent: shelfMarkupCent,
MarkupSiteID: markupSiteID,
ShelfRatio: siteRatio,
ShelfRatioDelta: shelfRatioDelta,
RentOriginalAmountCent: rentOriginalAmountCent,
RentDiscountAmountCent: rentDiscountAmountCent,
PureCoinOriginalAmountCent: pricing.PureCoinAmountCent,
@@ -168,17 +185,17 @@ func (r *Repository) Create(ctx context.Context, renterID uint64, req CreateRequ
return r.FindForUser(ctx, renterID, createdID)
}
func loadShelfMarkupTx(tx *gorm.DB, siteCode string, listingID uint64) (int64, uint64, error) {
func loadShelfRatioTx(tx *gorm.DB, siteCode string, listingID uint64) (float64, uint64, error) {
siteCode = strings.TrimSpace(siteCode)
if siteCode == "" || listingID == 0 {
return 0, 0, nil
}
var row struct {
SiteID uint64 `gorm:"column:site_id"`
Markup int64 `gorm:"column:markup"`
SiteID uint64 `gorm:"column:site_id"`
Delta float64 `gorm:"column:delta"`
}
err := tx.Table("distribution_site_accounts AS dsa").
Select("dsa.site_id, COALESCE(dsa.markup_amount_cent, 0) AS markup").
Select("dsa.site_id, COALESCE(dsa.shelf_ratio_delta, 0) AS delta").
Joins("INNER JOIN distribution_sites AS ds ON ds.id = dsa.site_id AND ds.site_code = ? AND ds.status = 'active'", siteCode).
Where("dsa.listing_id = ? AND dsa.display_status = 'enabled'", listingID).
Take(&row).Error
@@ -188,10 +205,10 @@ func loadShelfMarkupTx(tx *gorm.DB, siteCode string, listingID uint64) (int64, u
}
return 0, 0, err
}
if row.Markup < 0 {
row.Markup = 0
if row.Delta < 0 {
row.Delta = 0
}
return row.Markup, row.SiteID, nil
return row.Delta, row.SiteID, nil
}
func orderAccountSourceSnapshot(tx *gorm.DB, listingID uint64) (string, string, error) {
@@ -95,6 +95,8 @@ func (row orderRow) toAdminDTO() OrderDTO {
BasePriceCent: row.BasePriceCent,
ShelfMarkupCent: row.ShelfMarkupCent,
MarkupSiteID: row.MarkupSiteID,
ShelfRatio: row.ShelfRatio,
ShelfRatioDelta: row.ShelfRatioDelta,
RentOriginalAmountCent: effectiveRentOriginalAmountCent(row.RentalOrder),
RentDiscountAmountCent: row.RentDiscountAmountCent,
PureCoinOriginalAmountCent: orderPureCoinOriginalAmountCent(row.RentalOrder),
+1 -8
View File
@@ -291,14 +291,7 @@ func effectiveCheckoutDepositDeductCent(checkout model.OrderCheckout) int64 {
}
func orderConsumptionPrepaidCent(order model.RentalOrder) int64 {
prepaid := order.RentAmountCent
if order.ShelfMarkupCent > 0 {
prepaid -= order.ShelfMarkupCent
if prepaid < 0 {
prepaid = 0
}
}
return prepaid
return order.RentAmountCent
}
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmountCent int64, coinConsumedM float64, depositDeductAmountCent int64) checkoutSettlement {
@@ -298,6 +298,7 @@ func TestCreateOrderSnapshotsShelfMarkup(t *testing.T) {
ServerRegion: "国服",
LoginPlatform: "steam",
Title: "加价账号",
HafCoinAmount: 100_000_000,
AssetSummary: datatypes.JSON([]byte(`{
"price_breakdown": {
"seller_total_price": 80,
@@ -325,7 +326,7 @@ func TestCreateOrderSnapshotsShelfMarkup(t *testing.T) {
t.Fatal(err)
}
if err := db.Create(&model.DistributionSiteAccount{
SiteID: site.ID, AccountID: account.ID, ListingID: listing.ID, DisplayStatus: "enabled", MarkupAmountCent: 10000,
SiteID: site.ID, AccountID: account.ID, ListingID: listing.ID, DisplayStatus: "enabled", ShelfRatioDelta: 25,
}).Error; err != nil {
t.Fatal(err)
}
@@ -343,30 +344,30 @@ func TestCreateOrderSnapshotsShelfMarkup(t *testing.T) {
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if dto.RentAmountCent == nil || *dto.RentAmountCent != 20000 {
t.Fatalf("paid rent = %v, want 20000", dto.RentAmountCent)
if dto.RentAmountCent == nil || *dto.RentAmountCent != 8000 {
t.Fatalf("paid rent = %v, want 8000", dto.RentAmountCent)
}
if dto.BasePriceCent != 10000 || dto.ShelfMarkupCent != 10000 || dto.MarkupSiteID != site.ID {
t.Fatalf("markup snapshot = base %d markup %d site %d", dto.BasePriceCent, dto.ShelfMarkupCent, dto.MarkupSiteID)
if dto.BasePriceCent != 10000 || dto.ShelfMarkupCent != -2000 || dto.MarkupSiteID != site.ID || dto.ShelfRatioDelta != 25 {
t.Fatalf("ratio snapshot = base %d deltaCent %d site %d ratioDelta %v", dto.BasePriceCent, dto.ShelfMarkupCent, dto.MarkupSiteID, dto.ShelfRatioDelta)
}
var saved model.RentalOrder
if err := db.First(&saved, dto.ID).Error; err != nil {
t.Fatal(err)
}
if saved.OwnerRentAmountCent != 8000 || saved.PlatformFeeCent != 2000 {
t.Fatalf("owner/platform must stay on base price, got %d/%d", saved.OwnerRentAmountCent, saved.PlatformFeeCent)
if saved.OwnerRentAmountCent != 8000 {
t.Fatalf("owner rent = %d, want 8000", saved.OwnerRentAmountCent)
}
if err := db.Model(&model.DistributionSiteAccount{}).Where("listing_id = ?", listing.ID).Update("markup_amount_cent", 0).Error; err != nil {
if err := db.Model(&model.DistributionSiteAccount{}).Where("listing_id = ?", listing.ID).Update("shelf_ratio_delta", 0).Error; err != nil {
t.Fatal(err)
}
var after model.RentalOrder
if err := db.First(&after, dto.ID).Error; err != nil {
t.Fatal(err)
}
if after.ShelfMarkupCent != 10000 || after.RentAmountCent != 20000 {
t.Fatalf("historical order must keep markup snapshot, got markup %d rent %d", after.ShelfMarkupCent, after.RentAmountCent)
if after.ShelfRatioDelta != 25 || after.RentAmountCent != 8000 {
t.Fatalf("historical order must keep ratio snapshot, got delta %v rent %d", after.ShelfRatioDelta, after.RentAmountCent)
}
}
@@ -118,19 +118,19 @@ func TestBuildOrderPricingDoesNotDiscountAllExtraItemOrder(t *testing.T) {
}
}
func TestCheckoutDoesNotRefundShelfMarkup(t *testing.T) {
func TestCheckoutRefundsSiteRent(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 20000,
RentAmountCent: 8000,
OwnerRentAmountCent: 8000,
PlatformFeeCent: 2000,
PlatformFeeCent: 0,
BasePriceCent: 10000,
ShelfMarkupCent: 10000,
ShelfRatioDelta: 25,
DepositAmountCent: 1000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 100,
"buyer_coin_base_price": 80,
"seller_coin_base_price": 80,
"consumable_price": 0
}
@@ -139,11 +139,11 @@ func TestCheckoutDoesNotRefundShelfMarkup(t *testing.T) {
}
settlement := calculateCheckoutSettlement(order, 0, 100, 0)
if settlement.RentRefundCent != 0 {
t.Fatalf("full consume must not refund shelf markup, rent refund = %d", settlement.RentRefundCent)
t.Fatalf("full consume must not refund rent, rent refund = %d", settlement.RentRefundCent)
}
zeroUse := calculateCheckoutSettlement(order, 0, 0, 0)
if zeroUse.RentRefundCent != 10000 {
t.Fatalf("unused listing refunds base price only, got %d want 10000", zeroUse.RentRefundCent)
if zeroUse.RentRefundCent != 8000 {
t.Fatalf("unused listing refunds site rent, got %d want 8000", zeroUse.RentRefundCent)
}
}