feat(distribution): 重构分销后台为SaaS商户工作台并全面商业化优化
- 商品货架 (Accounts.tsx): 支持期望售价/固定金额/百分比多模式实时联动加价与批量调价,高亮100%独享溢价 - 订单中心 (Orders.tsx): 透传账号游戏租客信息,新增收益对账抽屉透明拆解底价、手续费、分成与自主溢价 - 结算与规则 (Settlements.tsx & Rules.tsx): 纠正角色倒错,商户视角展现周期对账单与分成合作政策 - 店铺装修 (SiteSettings.tsx): 废除手写JSON,提供横幅/公告/页脚可视化列表编辑器与前台实时模拟器 - 数据大盘 (Dashboard.tsx): 对齐最新订单字段,大字展示分销商净收益 - 后端数据层 (dto.go, repository.go, handler.go): 聚合计算分销商订单收益,支持商品与租客搜索
This commit is contained in:
@@ -417,13 +417,22 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
|
||||
if size > 100 {
|
||||
size = 100
|
||||
}
|
||||
base := r.db.WithContext(ctx).Table("rental_orders ro").Joins("JOIN distribution_order_financials f ON f.order_id = ro.id")
|
||||
base := r.db.WithContext(ctx).Table("rental_orders ro").
|
||||
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
|
||||
Joins("LEFT JOIN rental_listings rl ON rl.id = ro.listing_id").
|
||||
Joins("LEFT JOIN game_accounts ga ON ga.id = ro.account_id").
|
||||
Joins("LEFT JOIN users u ON u.id = ro.renter_id")
|
||||
|
||||
if query.SiteID > 0 {
|
||||
base = base.Where("ro.distribution_site_id = ?", query.SiteID)
|
||||
}
|
||||
if query.Status != "" {
|
||||
base = base.Where("ro.status = ?", query.Status)
|
||||
}
|
||||
if strings.TrimSpace(query.Keyword) != "" {
|
||||
kw := "%" + strings.TrimSpace(query.Keyword) + "%"
|
||||
base = base.Where("(ro.order_no LIKE ? OR rl.listing_no LIKE ? OR ga.title LIKE ? OR u.phone LIKE ?)", kw, kw, kw, kw)
|
||||
}
|
||||
switch strings.TrimSpace(query.Kind) {
|
||||
case "sold":
|
||||
base = base.Where("EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND p.party_role IN ('distributor_sell','distributor_b'))")
|
||||
@@ -438,16 +447,99 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
|
||||
return nil, err
|
||||
}
|
||||
items := make([]DistributionOrderDTO, 0)
|
||||
selectSQL := "ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id, ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode, f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent, f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at"
|
||||
selectSQL := `
|
||||
ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id,
|
||||
ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode,
|
||||
f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent,
|
||||
f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at,
|
||||
COALESCE(rl.listing_no, '') AS listing_no,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
COALESCE(ga.server_region, '') AS server_region,
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
ro.estimated_duration_hours AS rent_duration_hours,
|
||||
ro.rented_at AS rented_at,
|
||||
COALESCE(u.phone, '') AS renter_phone,
|
||||
COALESCE(u.nickname, '') AS renter_nickname,
|
||||
COALESCE(ro.owner_rent_amount_cent, 0) AS owner_rent_amount_cent,
|
||||
COALESCE(ro.base_price_cent, 0) AS base_price_cent,
|
||||
COALESCE(ro.shelf_markup_cent, 0) AS shelf_markup_cent,
|
||||
COALESCE(ro.deposit_amount_cent, 0) AS deposit_amount_cent
|
||||
`
|
||||
if err := base.Select(selectSQL).Order("ro.id DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
orderIDs := make([]uint64, len(items))
|
||||
for i, it := range items {
|
||||
orderIDs[i] = it.OrderID
|
||||
}
|
||||
var parties []model.DistributionOrderParty
|
||||
pq := r.db.WithContext(ctx).Where("order_id IN ?", orderIDs)
|
||||
if query.SiteID > 0 {
|
||||
pq = pq.Where("site_id = ?", query.SiteID)
|
||||
}
|
||||
if err := pq.Find(&parties).Error; err == nil {
|
||||
partiesByOrder := make(map[uint64][]model.DistributionOrderParty)
|
||||
for _, p := range parties {
|
||||
partiesByOrder[p.OrderID] = append(partiesByOrder[p.OrderID], p)
|
||||
}
|
||||
for i := range items {
|
||||
plist := partiesByOrder[items[i].OrderID]
|
||||
var totalEarning int64
|
||||
var markupCent int64
|
||||
var splitCent int64
|
||||
var roles []string
|
||||
for _, p := range plist {
|
||||
totalEarning += p.IncomeAmountCent
|
||||
if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" {
|
||||
roles = append(roles, "sell")
|
||||
markupCent += items[i].ShelfMarkupCent
|
||||
splitCent += (p.IncomeAmountCent - items[i].ShelfMarkupCent)
|
||||
} else if p.PartyRole == "distributor_receive" || p.PartyRole == "distributor_a" {
|
||||
roles = append(roles, "receive")
|
||||
splitCent += p.IncomeAmountCent
|
||||
}
|
||||
}
|
||||
items[i].DistributorEarningCent = totalEarning
|
||||
items[i].DistributorMarkupCent = markupCent
|
||||
items[i].DistributorSplitCent = splitCent
|
||||
items[i].DistributorRole = strings.Join(roles, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &DistributionOrderPage{Items: items, Total: total, Page: page, PageSize: size}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*DistributionOrderDTO, error) {
|
||||
var item DistributionOrderDTO
|
||||
err := r.db.WithContext(ctx).Table("rental_orders ro").Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").Select("ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id, ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode, f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent, f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at").Where("ro.id = ?", id).Scan(&item).Error
|
||||
selectSQL := `
|
||||
ro.id AS order_id, ro.order_no, ro.distribution_site_id AS site_id, ro.listing_id, ro.account_id, ro.owner_id, ro.renter_id,
|
||||
ro.status, ro.handoff_status, ro.refund_status, ro.settlement_status, ro.distribution_mode,
|
||||
f.gross_paid_amount_cent, f.refund_amount_cent, f.net_paid_amount_cent, f.payment_fee_cent, f.service_fee_cent,
|
||||
f.markup_amount_cent, f.tax_amount_cent, f.split_base_amount_cent, f.rounding_adjustment_cent, ro.created_at,
|
||||
COALESCE(rl.listing_no, '') AS listing_no,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
COALESCE(ga.server_region, '') AS server_region,
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
ro.estimated_duration_hours AS rent_duration_hours,
|
||||
ro.rented_at AS rented_at,
|
||||
COALESCE(u.phone, '') AS renter_phone,
|
||||
COALESCE(u.nickname, '') AS renter_nickname,
|
||||
COALESCE(ro.owner_rent_amount_cent, 0) AS owner_rent_amount_cent,
|
||||
COALESCE(ro.base_price_cent, 0) AS base_price_cent,
|
||||
COALESCE(ro.shelf_markup_cent, 0) AS shelf_markup_cent,
|
||||
COALESCE(ro.deposit_amount_cent, 0) AS deposit_amount_cent
|
||||
`
|
||||
err := r.db.WithContext(ctx).Table("rental_orders ro").
|
||||
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
|
||||
Joins("LEFT JOIN rental_listings rl ON rl.id = ro.listing_id").
|
||||
Joins("LEFT JOIN game_accounts ga ON ga.id = ro.account_id").
|
||||
Joins("LEFT JOIN users u ON u.id = ro.renter_id").
|
||||
Select(selectSQL).Where("ro.id = ?", id).Scan(&item).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -457,6 +549,28 @@ func (r *Repository) FindOrder(ctx context.Context, adminID, id uint64) (*Distri
|
||||
if err := r.ensureSiteAccess(ctx, adminID, item.SiteID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var parties []model.DistributionOrderParty
|
||||
if err := r.db.WithContext(ctx).Where("order_id = ? AND site_id = ?", item.OrderID, item.SiteID).Find(&parties).Error; err == nil {
|
||||
var totalEarning int64
|
||||
var markupCent int64
|
||||
var splitCent int64
|
||||
var roles []string
|
||||
for _, p := range parties {
|
||||
totalEarning += p.IncomeAmountCent
|
||||
if p.PartyRole == "distributor_sell" || p.PartyRole == "distributor_b" {
|
||||
roles = append(roles, "sell")
|
||||
markupCent += item.ShelfMarkupCent
|
||||
splitCent += (p.IncomeAmountCent - item.ShelfMarkupCent)
|
||||
} else if p.PartyRole == "distributor_receive" || p.PartyRole == "distributor_a" {
|
||||
roles = append(roles, "receive")
|
||||
splitCent += p.IncomeAmountCent
|
||||
}
|
||||
}
|
||||
item.DistributorEarningCent = totalEarning
|
||||
item.DistributorMarkupCent = markupCent
|
||||
item.DistributorSplitCent = splitCent
|
||||
item.DistributorRole = strings.Join(roles, ",")
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user