refactor(distribution): 重写分销迁移并落地利润百分比分成与货架比例
将 000064-000078 合并为 064/065/066,默认规则改为收号 35%、卖出 40%、服务费 25%。 货架调价改为增加分站 1 元兑换哈夫币比例。sql-guard 仅保护 main 已发布迁移。
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"hfb_sys/backend/internal/auditlog"
|
||||
"hfb_sys/backend/internal/model"
|
||||
listingpkg "hfb_sys/backend/internal/modules/listing"
|
||||
"hfb_sys/backend/internal/modules/wallet"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
@@ -319,12 +320,19 @@ func (r *Repository) CreateRule(ctx context.Context, adminID uint64, req CreateR
|
||||
if effectiveFrom.IsZero() {
|
||||
effectiveFrom = time.Now()
|
||||
}
|
||||
row := model.DistributionRuleVersion{
|
||||
SiteID: req.SiteID, Mode: strings.TrimSpace(req.Mode), Version: version, Status: "draft",
|
||||
parsed := Rule{
|
||||
PlatformReceiveRatio: req.PlatformReceiveRatio, PlatformSellRatio: req.PlatformSellRatio,
|
||||
DistributorReceiveRatio: req.DistributorReceiveRatio, DistributorSellRatio: req.DistributorSellRatio,
|
||||
DistributorARatio: req.DistributorARatio, DistributorBRatio: req.DistributorBRatio,
|
||||
ServiceFeeValue: req.ServiceFeeValue, ServiceFeeUnit: strings.TrimSpace(req.ServiceFeeUnit),
|
||||
}
|
||||
normalizeRulePercents(&parsed)
|
||||
row := model.DistributionRuleVersion{
|
||||
SiteID: req.SiteID, Mode: strings.TrimSpace(req.Mode), Version: version, Status: "draft",
|
||||
PlatformReceiveRatio: parsed.PlatformReceiveRatio, PlatformSellRatio: parsed.PlatformSellRatio,
|
||||
DistributorReceiveRatio: parsed.DistributorReceiveRatio, DistributorSellRatio: parsed.DistributorSellRatio,
|
||||
DistributorARatio: parsed.DistributorARatio, DistributorBRatio: parsed.DistributorBRatio,
|
||||
ServiceFeeValue: parsed.ServiceFeeValue, ServiceFeeUnit: parsed.ServiceFeeUnit,
|
||||
PaymentFeeBPS: req.PaymentFeeBPS, TaxEnabled: req.TaxEnabled, TaxRateBPS: req.TaxRateBPS,
|
||||
EffectiveFrom: effectiveFrom, CreatedBy: &adminID,
|
||||
}
|
||||
@@ -443,23 +451,24 @@ func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*Distri
|
||||
|
||||
func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, status string) ([]AccountDisplayDTO, error) {
|
||||
type accountDisplayRow struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
SiteID uint64 `gorm:"column:site_id"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
DisplayStatus string `gorm:"column:display_status"`
|
||||
MarkupAmountCent int64 `gorm:"column:markup_amount_cent"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
ListingStatus string `gorm:"column:listing_status"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
ID uint64 `gorm:"column:id"`
|
||||
SiteID uint64 `gorm:"column:site_id"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
DisplayStatus string `gorm:"column:display_status"`
|
||||
MarkupAmountCent int64 `gorm:"column:markup_amount_cent"`
|
||||
ShelfRatioDelta float64 `gorm:"column:shelf_ratio_delta"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
ListingStatus string `gorm:"column:listing_status"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
}
|
||||
|
||||
var rows []accountDisplayRow
|
||||
@@ -472,6 +481,7 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
|
||||
dsa.listing_id,
|
||||
dsa.display_status,
|
||||
COALESCE(dsa.markup_amount_cent, 0) AS markup_amount_cent,
|
||||
COALESCE(dsa.shelf_ratio_delta, 0) AS shelf_ratio_delta,
|
||||
COALESCE(l.listing_no, '') AS listing_no,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
@@ -496,13 +506,17 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
|
||||
}
|
||||
items := make([]AccountDisplayDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
sitePrice, baseRatio, siteRatio := listingpkg.ApplyShelfRatio(row.HafCoinAmount, row.PriceCent, row.ShelfRatioDelta)
|
||||
items = append(items, AccountDisplayDTO{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
AccountID: row.AccountID,
|
||||
ListingID: row.ListingID,
|
||||
DisplayStatus: row.DisplayStatus,
|
||||
MarkupAmountCent: row.MarkupAmountCent,
|
||||
MarkupAmountCent: sitePrice - row.PriceCent,
|
||||
ShelfRatioDelta: row.ShelfRatioDelta,
|
||||
BaseRatio: baseRatio,
|
||||
SiteRatio: siteRatio,
|
||||
ListingNo: row.ListingNo,
|
||||
Title: row.Title,
|
||||
GameName: row.GameName,
|
||||
@@ -511,6 +525,7 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
|
||||
RankLevel: row.RankLevel,
|
||||
HafCoinAmount: row.HafCoinAmount,
|
||||
PriceCent: row.PriceCent,
|
||||
SitePriceCent: sitePrice,
|
||||
DepositAmountCent: row.DepositAmountCent,
|
||||
ListingStatus: row.ListingStatus,
|
||||
InTransaction: row.InTransaction,
|
||||
@@ -913,12 +928,14 @@ func (r *Repository) CreateFinancialSnapshot(ctx context.Context, adminID, order
|
||||
for _, party := range req.Parties {
|
||||
partyInputs = append(partyInputs, PartyProfitInput{PartyRole: party.PartyRole, IncomeAmountCent: party.IncomeAmountCent, CostAmountCent: party.CostAmountCent})
|
||||
}
|
||||
result, err := CalculateSettlement(Rule{Mode: rule.Mode, ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, PaymentFeeBPS: rule.PaymentFeeBPS, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS}, SettlementInput{GrossPaidAmountCent: req.GrossPaidAmountCent, RefundAmountCent: req.RefundAmountCent, MarkupAmountCent: req.MarkupAmountCent, GrossProfitByParty: partyInputs})
|
||||
parsed := ruleFromVersion(rule)
|
||||
result, err := CalculateSettlement(parsed, SettlementInput{GrossPaidAmountCent: req.GrossPaidAmountCent, RefundAmountCent: req.RefundAmountCent, MarkupAmountCent: req.MarkupAmountCent, GrossProfitByParty: partyInputs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
financial := model.DistributionOrderFinancial{OrderID: orderID, SiteID: req.SiteID, RuleVersionID: req.RuleVersionID, GrossPaidAmountCent: result.GrossPaidAmountCent, RefundAmountCent: result.RefundAmountCent, NetPaidAmountCent: result.NetPaidAmountCent, PaymentFeeBPS: rule.PaymentFeeBPS, PaymentFeeCent: result.PaymentFeeCent, ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, ServiceFeeCent: result.ServiceFeeCent, MarkupAmountCent: result.MarkupAmountCent, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS, TaxAmountCent: result.TaxAmountCent, SplitBaseAmountCent: result.SplitBaseAmountCent, RoundingAdjustmentCent: SplitRemainderCent(result.SplitBaseAmountCent, SplitSettlement(Rule{Mode: rule.Mode}, result.SplitBaseAmountCent, result.MarkupAmountCent)), SettlementStatus: "pending"}
|
||||
splits := SplitSettlement(parsed, result.SplitBaseAmountCent)
|
||||
financial := model.DistributionOrderFinancial{OrderID: orderID, SiteID: req.SiteID, RuleVersionID: req.RuleVersionID, GrossPaidAmountCent: result.GrossPaidAmountCent, RefundAmountCent: result.RefundAmountCent, NetPaidAmountCent: result.NetPaidAmountCent, PaymentFeeBPS: rule.PaymentFeeBPS, PaymentFeeCent: result.PaymentFeeCent, ServiceFeeValue: parsed.ServiceFeeValue, ServiceFeeUnit: parsed.ServiceFeeUnit, ServiceFeeCent: result.ServiceFeeCent, MarkupAmountCent: result.MarkupAmountCent, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS, TaxAmountCent: result.TaxAmountCent, SplitBaseAmountCent: result.SplitBaseAmountCent, RoundingAdjustmentCent: SplitRoundingCent(result.SplitBaseAmountCent, result.ServiceFeeCent, splits), SettlementStatus: "pending"}
|
||||
if err := tx.Create(&financial).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1184,16 +1201,23 @@ func (r *Repository) OnboardSite(ctx context.Context, req OnboardSiteRequest) (*
|
||||
return err
|
||||
}
|
||||
|
||||
// 一键开通只落草稿,禁止把文档占位 3.5/4 当成已生效分成。
|
||||
// 一键开通只落草稿。字段存百分数;口头 3.5/4/2.5 写入时换成 35/40/25。
|
||||
parsed := Rule{
|
||||
DistributorReceiveRatio: req.DistributorReceiveRatio,
|
||||
PlatformSellRatio: req.PlatformSellRatio,
|
||||
ServiceFeeValue: req.ServiceFeeValue,
|
||||
ServiceFeeUnit: ServiceFeeUnitPercent,
|
||||
}
|
||||
normalizeRulePercents(&parsed)
|
||||
rule := model.DistributionRuleVersion{
|
||||
SiteID: &site.ID,
|
||||
Mode: ModeTwoPartyDistributorReceive,
|
||||
Version: 1,
|
||||
Status: "draft",
|
||||
DistributorReceiveRatio: req.DistributorReceiveRatio,
|
||||
PlatformSellRatio: req.PlatformSellRatio,
|
||||
ServiceFeeValue: req.ServiceFeeValue,
|
||||
ServiceFeeUnit: ServiceFeeUnitPercent,
|
||||
DistributorReceiveRatio: parsed.DistributorReceiveRatio,
|
||||
PlatformSellRatio: parsed.PlatformSellRatio,
|
||||
ServiceFeeValue: parsed.ServiceFeeValue,
|
||||
ServiceFeeUnit: parsed.ServiceFeeUnit,
|
||||
PaymentFeeBPS: 50,
|
||||
TaxEnabled: taxEnabled,
|
||||
TaxRateBPS: taxRate,
|
||||
@@ -1264,19 +1288,19 @@ func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string
|
||||
}
|
||||
|
||||
type publicListingRow struct {
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
MarkupAmountCent int64 `gorm:"column:markup_amount_cent"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
ShelfRatioDelta float64 `gorm:"column:shelf_ratio_delta"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
}
|
||||
|
||||
baseQuery := r.db.WithContext(ctx).
|
||||
@@ -1302,9 +1326,9 @@ func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
COALESCE(ga.rank_level, '') AS rank_level,
|
||||
COALESCE(ga.haf_coin_amount, 0) AS haf_coin_amount,
|
||||
COALESCE(l.price_cent, 0) + COALESCE(dsa.markup_amount_cent, 0) AS price_cent,
|
||||
COALESCE(l.price_cent, 0) AS price_cent,
|
||||
COALESCE(l.deposit_amount_cent, 0) AS deposit_amount_cent,
|
||||
COALESCE(dsa.markup_amount_cent, 0) AS markup_amount_cent,
|
||||
COALESCE(dsa.shelf_ratio_delta, 0) AS shelf_ratio_delta,
|
||||
COALESCE(l.in_transaction, 0) AS in_transaction
|
||||
`).
|
||||
Order("dsa.id DESC").
|
||||
@@ -1317,6 +1341,7 @@ func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string
|
||||
|
||||
items := make([]PublicDistributionListingDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
sitePrice, _, _ := listingpkg.ApplyShelfRatio(row.HafCoinAmount, row.PriceCent, row.ShelfRatioDelta)
|
||||
items = append(items, PublicDistributionListingDTO{
|
||||
ListingID: row.ListingID,
|
||||
ListingNo: row.ListingNo,
|
||||
@@ -1327,9 +1352,10 @@ func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RankLevel: row.RankLevel,
|
||||
HafCoinAmount: row.HafCoinAmount,
|
||||
PriceCent: row.PriceCent,
|
||||
PriceCent: sitePrice,
|
||||
DepositAmountCent: row.DepositAmountCent,
|
||||
MarkupAmountCent: row.MarkupAmountCent,
|
||||
MarkupAmountCent: sitePrice - row.PriceCent,
|
||||
ShelfRatioDelta: row.ShelfRatioDelta,
|
||||
InTransaction: row.InTransaction,
|
||||
SiteCode: site.SiteCode,
|
||||
SiteName: site.SiteName,
|
||||
@@ -1456,8 +1482,8 @@ func (r *Repository) UpdateSiteBranding(ctx context.Context, adminID, siteID uin
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateAccountMarkup(ctx context.Context, adminID, id uint64, markupCent int64) (*AccountDisplayDTO, error) {
|
||||
if markupCent < 0 {
|
||||
func (r *Repository) UpdateAccountMarkup(ctx context.Context, adminID, id uint64, ratioDelta float64) (*AccountDisplayDTO, error) {
|
||||
if ratioDelta < 0 {
|
||||
return nil, ErrInvalidAmount
|
||||
}
|
||||
var row model.DistributionSiteAccount
|
||||
@@ -1470,7 +1496,7 @@ func (r *Repository) UpdateAccountMarkup(ctx context.Context, adminID, id uint64
|
||||
if err := r.ensureSiteAccess(ctx, adminID, row.SiteID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Model(&row).Update("markup_amount_cent", markupCent).Error; err != nil {
|
||||
if err := r.db.WithContext(ctx).Model(&row).Update("shelf_ratio_delta", ratioDelta).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
displays, err := r.ListAccountDisplays(ctx, row.SiteID, "")
|
||||
@@ -1492,7 +1518,7 @@ func (r *Repository) BatchUpdateAccountMarkup(ctx context.Context, adminID, site
|
||||
if err := r.ensureSiteAccess(ctx, adminID, siteID); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.MarkupAmountCent < 0 {
|
||||
if req.ShelfRatioDelta < 0 {
|
||||
return ErrInvalidAmount
|
||||
}
|
||||
if len(req.ListingIDs) == 0 {
|
||||
@@ -1500,7 +1526,7 @@ func (r *Repository) BatchUpdateAccountMarkup(ctx context.Context, adminID, site
|
||||
}
|
||||
return r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("site_id = ? AND listing_id IN ?", siteID, req.ListingIDs).
|
||||
Update("markup_amount_cent", req.MarkupAmountCent).Error
|
||||
Update("shelf_ratio_delta", req.ShelfRatioDelta).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID uint64, q ReceivedListingQuery) (*ReceivedListingPage, error) {
|
||||
|
||||
Reference in New Issue
Block a user