feat(distribution): 分站可下调收号比例且不超过主站上限

让分销站能按自身能力下调收号比例扩大利润,同时把主站回收比例作为硬边界,避免分站把比例加得比主站更高。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-15 20:35:30 +08:00
co-authored by Cursor
parent 1c86bef765
commit c9d9160ff4
27 changed files with 1159 additions and 106 deletions
@@ -20,6 +20,7 @@ import (
"github.com/redis/go-redis/v9"
"golang.org/x/crypto/bcrypt"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -64,6 +65,13 @@ func normalizeSettlementHoldDays(days int) (int, error) {
return days, nil
}
func normalizeReceiveRatioDelta(delta float64) (float64, error) {
if delta < 0 {
return 0, ErrInvalidAmount
}
return listingpkg.NormalizeSiteReceiveRatioDelta(delta), nil
}
type Repository struct {
db *gorm.DB
redis *redis.Client
@@ -256,6 +264,13 @@ func (r *Repository) CreateSite(ctx context.Context, req CreateSiteRequest) (*Si
if req.ListingQrcodeURL != nil {
row.ListingQrcodeURL = strings.TrimSpace(*req.ListingQrcodeURL)
}
if req.ReceiveRatioDelta != nil {
delta, err := normalizeReceiveRatioDelta(*req.ReceiveRatioDelta)
if err != nil {
return nil, err
}
row.ReceiveRatioDelta = delta
}
if req.TaxEnabled != nil {
row.TaxEnabled = *req.TaxEnabled
}
@@ -293,6 +308,27 @@ func (r *Repository) UpdateSite(ctx context.Context, adminID, id uint64, req Upd
if err := r.ensureSiteAccessWithDB(ctx, tx, adminID, row.ID); err != nil {
return err
}
globalAccess := adminID == 0
if !globalAccess {
global, accessErr := r.hasGlobalDistributionAccessWithDB(ctx, tx, adminID)
if accessErr != nil {
return accessErr
}
globalAccess = global
}
if req.ReceiveRatioDelta != nil {
delta, err := normalizeReceiveRatioDelta(*req.ReceiveRatioDelta)
if err != nil {
return err
}
row.ReceiveRatioDelta = delta
}
if !globalAccess {
if err := tx.Save(&row).Error; err != nil {
return err
}
return nil
}
if req.SiteName != nil {
row.SiteName = strings.TrimSpace(*req.SiteName)
}
@@ -474,6 +510,7 @@ func siteDTO(row model.DistributionSite) SiteDTO {
DisplayAccountEnabled: row.DisplayAccountEnabled,
DefaultSupportGroupID: row.DefaultSupportGroupID,
ListingQrcodeURL: row.ListingQrcodeURL,
ReceiveRatioDelta: row.ReceiveRatioDelta,
TaxEnabled: row.TaxEnabled,
TaxRateBPS: row.TaxRateBPS,
SettlementHoldDays: row.SettlementHoldDays,
@@ -2097,6 +2134,7 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
BannersJSON: site.BannersJSON,
AnnouncementsJSON: site.AnnouncementsJSON,
FooterInfoJSON: site.FooterInfoJSON,
ReceiveRatioDelta: site.ReceiveRatioDelta,
}
if r.redis != nil {
if bytes, err := json.Marshal(res); err == nil {
@@ -2347,28 +2385,29 @@ func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID u
}
type receivedListingRow struct {
ID uint64 `gorm:"column:id"`
ListingNo string `gorm:"column:listing_no"`
AccountID uint64 `gorm:"column:account_id"`
OwnerID uint64 `gorm:"column:owner_id"`
OwnerUsername string `gorm:"column:owner_username"`
OwnerPhone string `gorm:"column:owner_phone"`
GameName string `gorm:"column:game_name"`
Title string `gorm:"column:title"`
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"`
Status string `gorm:"column:status"`
ReviewStatus string `gorm:"column:review_status"`
ReviewReason string `gorm:"column:review_reason"`
InTransaction bool `gorm:"column:in_transaction"`
DistributionSiteID uint64 `gorm:"column:distribution_site_id"`
DisplayStatus string `gorm:"column:display_status"`
CreatedAt time.Time `gorm:"column:created_at"`
PublishedAt *time.Time `gorm:"column:published_at"`
ID uint64 `gorm:"column:id"`
ListingNo string `gorm:"column:listing_no"`
AccountID uint64 `gorm:"column:account_id"`
OwnerID uint64 `gorm:"column:owner_id"`
OwnerUsername string `gorm:"column:owner_username"`
OwnerPhone string `gorm:"column:owner_phone"`
GameName string `gorm:"column:game_name"`
Title string `gorm:"column:title"`
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"`
AssetSummary datatypes.JSON `gorm:"column:asset_summary"`
PriceCent int64 `gorm:"column:price_cent"`
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
Status string `gorm:"column:status"`
ReviewStatus string `gorm:"column:review_status"`
ReviewReason string `gorm:"column:review_reason"`
InTransaction bool `gorm:"column:in_transaction"`
DistributionSiteID uint64 `gorm:"column:distribution_site_id"`
DisplayStatus string `gorm:"column:display_status"`
CreatedAt time.Time `gorm:"column:created_at"`
PublishedAt *time.Time `gorm:"column:published_at"`
}
var rows []receivedListingRow
@@ -2377,6 +2416,7 @@ func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID u
COALESCE(u.nickname, '') AS owner_username,
COALESCE(u.phone, '') AS owner_phone,
ga.game_name, ga.title, ga.server_region, ga.login_platform, ga.rank_level, ga.haf_coin_amount,
ga.asset_summary,
rl.price_cent, rl.deposit_amount_cent, rl.status, rl.review_status, rl.review_reason,
rl.in_transaction, COALESCE(rl.distribution_site_id, 0) AS distribution_site_id,
COALESCE(dsa.display_status, 'enabled') AS display_status,
@@ -2396,27 +2436,31 @@ func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID u
if r.DisplayStatus == "disabled" {
status = "offline"
}
pricing := listingpkg.ReceivePricingFromAssetSummary(r.AssetSummary)
items = append(items, ReceivedListingDTO{
ListingID: r.ID,
ListingNo: r.ListingNo,
SiteID: r.DistributionSiteID,
AccountID: r.AccountID,
OwnerID: r.OwnerID,
OwnerPhone: maskedPhone,
OwnerNickname: r.OwnerUsername,
Title: r.Title,
GameName: r.GameName,
ServerRegion: r.ServerRegion,
LoginPlatform: r.LoginPlatform,
RankLevel: r.RankLevel,
HafCoinAmount: r.HafCoinAmount,
PriceCent: r.PriceCent,
DepositAmountCent: r.DepositAmountCent,
Status: status,
ReviewStatus: r.ReviewStatus,
ReviewReason: r.ReviewReason,
PublishedAt: r.PublishedAt,
CreatedAt: r.CreatedAt,
ListingID: r.ID,
ListingNo: r.ListingNo,
SiteID: r.DistributionSiteID,
AccountID: r.AccountID,
OwnerID: r.OwnerID,
OwnerPhone: maskedPhone,
OwnerNickname: r.OwnerUsername,
Title: r.Title,
GameName: r.GameName,
ServerRegion: r.ServerRegion,
LoginPlatform: r.LoginPlatform,
RankLevel: r.RankLevel,
HafCoinAmount: r.HafCoinAmount,
SellerRatio: pricing.SellerRatio,
SellerTotalPriceCent: pricing.SellerTotalPriceCent,
BuyerRatio: pricing.BuyerRatio,
PriceCent: r.PriceCent,
DepositAmountCent: r.DepositAmountCent,
Status: status,
ReviewStatus: r.ReviewStatus,
ReviewReason: r.ReviewReason,
PublishedAt: r.PublishedAt,
CreatedAt: r.CreatedAt,
})
}