fix(order): 修正仲裁后号主实际收入

This commit is contained in:
yml2213
2026-11-02 18:34:40 +08:00
parent 599e22331d
commit 5334c2b218
4 changed files with 141 additions and 86 deletions
+23 -29
View File
@@ -199,7 +199,7 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Adm
}
}
if len(incomeOrderIDs) > 0 {
ownerIncome, err = r.latestOwnerIncomeByOrder(ctx, incomeOrderIDs)
ownerIncome, err = r.ownerActualIncomeByOrder(ctx, incomeOrderIDs)
if err != nil {
return nil, err
}
@@ -255,48 +255,42 @@ func (r *Repository) adminOrderCountQuery(ctx context.Context, query AdminOrderQ
return r.adminOrderJoinQuery(ctx)
}
// latestOwnerIncomeByOrder 批量读取每单「最新一次结账」的号主实际到手金额(分)。
// ownerActualIncomeByOrder 批量读取每单号主最终实际结算金额(分)。
//
// 后台订单列表专用:一次查询覆盖整页,不会退化为逐行查询的 N+1。
// 只取结账表两列,不加载 content / evidence_urls 等大字段。
// 结账行只能代表协商结果,不能代表仲裁后的最终落账:
// - 平台代管订单:最终金额写入 rental_orders.offline_settlement_amount_cent;
// - 普通钱包订单:最终金额以 wallet_ledger 中号主的实际入账合计为准。
//
// 语义:多轮还价是原地更新同一行,但订单可能经历多次结账流程,
// 因此按 order_id 取 MAX(id) 作为最终结账行。
//
// 执行计划(MySQL 8.4 实测,EXPLAIN FORMAT=TREE):
//
// -> Nested loop inner join
// -> Materialize
// -> Group aggregate: max(order_checkouts.id)
// -> Covering index scan on order_checkouts using idx_order_checkouts_order_id
// -> Single-row index lookup on oc using PRIMARY (id=latest.latest_id)
//
// 用 STRAIGHT_JOIN 固定连接顺序:先物化「每单最新结账 id」小集合(走
// idx_order_checkouts_order_id 覆盖索引),再按主键逐行取金额。避免优化器
// 在数据量增长后改选全表扫描;开销只与当前页订单数相关,与结账表总量无关。
func (r *Repository) latestOwnerIncomeByOrder(ctx context.Context, orderIDs []uint64) (map[uint64]int64, error) {
// 仲裁可能不更新历史结账行,甚至 dispute.checkout_id 为空,因此不能继续取
// order_checkouts 的 owner_income_amount_cent,否则列表会显示仲裁前金额。
func (r *Repository) ownerActualIncomeByOrder(ctx context.Context, orderIDs []uint64) (map[uint64]int64, error) {
result := make(map[uint64]int64, len(orderIDs))
if len(orderIDs) == 0 {
return result, nil
}
latest := r.db.WithContext(ctx).
Table("order_checkouts").
Select("order_id, MAX(id) AS latest_id").
Where("order_id IN ?", orderIDs).
Group("order_id")
var rows []struct {
OrderID uint64
ID uint64
OwnerIncomeAmountCent int64
}
if err := r.db.WithContext(ctx).
Table("(?) AS latest", latest).
Select("oc.order_id, oc.owner_income_amount_cent").
Joins("STRAIGHT_JOIN order_checkouts AS oc ON oc.id = latest.latest_id").
Table("rental_orders AS o").
Select(`o.id,
CASE WHEN o.settlement_mode = 'platform_managed'
THEN COALESCE(o.offline_settlement_amount_cent, 0)
ELSE COALESCE(SUM(wl.amount_cent), 0)
END AS owner_income_amount_cent`).
Joins(`LEFT JOIN wallet_ledger AS wl
ON wl.order_id = o.id
AND wl.user_id = o.owner_id
AND wl.direction = 'in'
AND wl.biz_type IN ?`, []string{walletBizOwnerIncome, walletBizDepositCompensation, "arbitration_owner_income"}).
Where("o.id IN ?", orderIDs).
Group("o.id, o.settlement_mode, o.offline_settlement_amount_cent").
Scan(&rows).Error; err != nil {
return nil, err
}
for _, row := range rows {
result[row.OrderID] = row.OwnerIncomeAmountCent
result[row.ID] = row.OwnerIncomeAmountCent
}
return result, nil
}