|
|
|
@@ -2160,23 +2160,25 @@ func createOpenPlatformCheckout(t *testing.T, db *gorm.DB, order model.RentalOrd
|
|
|
|
|
return checkout
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestListAdminUsesFinalizedCheckoutOwnerIncome 校验后台订单列表带回「号主实际收入」,
|
|
|
|
|
// 且金额取自结账表最终行(已结账/已仲裁时才是定论值)。
|
|
|
|
|
func TestListAdminUsesFinalizedCheckoutOwnerIncome(t *testing.T) {
|
|
|
|
|
// TestListAdminPlatformManagedArbitrationUsesOfflineSettlement 校验平台代管订单仲裁后
|
|
|
|
|
// 列表读取线下实际结算金额,不能继续展示旧结账行中的金额。
|
|
|
|
|
func TestListAdminPlatformManagedArbitrationUsesOfflineSettlement(t *testing.T) {
|
|
|
|
|
db := setupOrderTestDB(t)
|
|
|
|
|
repo := NewRepository(db)
|
|
|
|
|
adminID := uint64(7002)
|
|
|
|
|
_, renter, order := createPlatformManagedCheckoutOrder(t, db, adminID)
|
|
|
|
|
order.Status = orderStatusCompleted
|
|
|
|
|
order.SettlementStatus = settlementStatusSettled
|
|
|
|
|
order.SettlementStatus = settlementStatusArbitrated
|
|
|
|
|
order.OfflineSettlementStatus = offlineSettlementStatusSettled
|
|
|
|
|
order.OfflineSettlementAmountCent = 382
|
|
|
|
|
if err := db.Save(&order).Error; err != nil {
|
|
|
|
|
t.Fatalf("save completed order failed: %v", err)
|
|
|
|
|
t.Fatalf("save arbitrated order failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
checkout := createOpenPlatformCheckout(t, db, order, renter.ID)
|
|
|
|
|
checkout.Status = checkoutStatusAccepted
|
|
|
|
|
// 模拟仲裁前遗留的结账金额,列表不能把它当成最终收入。
|
|
|
|
|
checkout.OwnerIncomeAmountCent = 6000
|
|
|
|
|
if err := db.Save(&checkout).Error; err != nil {
|
|
|
|
|
t.Fatalf("save finalized checkout failed: %v", err)
|
|
|
|
|
t.Fatalf("save stale checkout failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := repo.ListAdmin(t.Context(), AdminOrderQuery{Page: 1, PageSize: 20})
|
|
|
|
@@ -2186,16 +2188,15 @@ func TestListAdminUsesFinalizedCheckoutOwnerIncome(t *testing.T) {
|
|
|
|
|
if len(result.Items) != 1 {
|
|
|
|
|
t.Fatalf("ListAdmin() returned %d items, want 1", len(result.Items))
|
|
|
|
|
}
|
|
|
|
|
if result.Items[0].SettlementStatus != settlementStatusSettled {
|
|
|
|
|
t.Fatalf("settlement status = %q, want %q", result.Items[0].SettlementStatus, settlementStatusSettled)
|
|
|
|
|
if result.Items[0].SettlementStatus != settlementStatusArbitrated {
|
|
|
|
|
t.Fatalf("settlement status = %q, want %q", result.Items[0].SettlementStatus, settlementStatusArbitrated)
|
|
|
|
|
}
|
|
|
|
|
// 列表需直接展示号主实际收入,供客服在列表页核对结算。
|
|
|
|
|
income := result.Items[0].OwnerIncomeAmountCent
|
|
|
|
|
if income == nil {
|
|
|
|
|
t.Fatalf("owner income = nil, want 6000 for settled order")
|
|
|
|
|
t.Fatalf("owner income = nil, want 382 for platform-managed arbitrated order")
|
|
|
|
|
}
|
|
|
|
|
if *income != 6000 {
|
|
|
|
|
t.Fatalf("owner income = %d, want 6000", *income)
|
|
|
|
|
if *income != 382 {
|
|
|
|
|
t.Fatalf("owner income = %d, want 382", *income)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2227,28 +2228,54 @@ func TestListAdminOmitsOwnerIncomeWhileNegotiating(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestListAdminOwnerIncomeUsesLatestCheckout 校验一单存在多行结账记录时取最新一行(MAX(id))。
|
|
|
|
|
func TestListAdminOwnerIncomeUsesLatestCheckout(t *testing.T) {
|
|
|
|
|
// TestListAdminWalletArbitrationUsesWalletIncome 校验普通钱包订单仲裁后列表汇总
|
|
|
|
|
// 号主实际钱包入账,而不是读取仲裁前遗留的结账金额。
|
|
|
|
|
func TestListAdminWalletArbitrationUsesWalletIncome(t *testing.T) {
|
|
|
|
|
db := setupOrderTestDB(t)
|
|
|
|
|
repo := NewRepository(db)
|
|
|
|
|
adminID := uint64(7004)
|
|
|
|
|
_, renter, order := createPlatformManagedCheckoutOrder(t, db, adminID)
|
|
|
|
|
owner, renter, order := createPlatformManagedCheckoutOrder(t, db, adminID)
|
|
|
|
|
order.Status = orderStatusCompleted
|
|
|
|
|
order.SettlementStatus = settlementStatusSettled
|
|
|
|
|
order.SettlementStatus = settlementStatusArbitrated
|
|
|
|
|
order.SettlementMode = settlementModeOwnerWallet
|
|
|
|
|
order.OfflineSettlementStatus = offlineSettlementStatusNone
|
|
|
|
|
order.OfflineSettlementAmountCent = 0
|
|
|
|
|
if err := db.Save(&order).Error; err != nil {
|
|
|
|
|
t.Fatalf("save completed order failed: %v", err)
|
|
|
|
|
t.Fatalf("save arbitrated order failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
first := createOpenPlatformCheckout(t, db, order, renter.ID)
|
|
|
|
|
first.Status = checkoutStatusAccepted
|
|
|
|
|
first.OwnerIncomeAmountCent = 1111
|
|
|
|
|
if err := db.Save(&first).Error; err != nil {
|
|
|
|
|
t.Fatalf("save first checkout failed: %v", err)
|
|
|
|
|
checkout := createOpenPlatformCheckout(t, db, order, renter.ID)
|
|
|
|
|
checkout.OwnerIncomeAmountCent = 56013
|
|
|
|
|
if err := db.Save(&checkout).Error; err != nil {
|
|
|
|
|
t.Fatalf("save stale checkout failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
second := createOpenPlatformCheckout(t, db, order, renter.ID)
|
|
|
|
|
second.Status = checkoutStatusAccepted
|
|
|
|
|
second.OwnerIncomeAmountCent = 2222
|
|
|
|
|
if err := db.Save(&second).Error; err != nil {
|
|
|
|
|
t.Fatalf("save second checkout failed: %v", err)
|
|
|
|
|
|
|
|
|
|
orderID := order.ID
|
|
|
|
|
ledgers := []model.WalletLedger{
|
|
|
|
|
{
|
|
|
|
|
LedgerNo: "WL-ARBITRATION-OWNER",
|
|
|
|
|
UserID: owner.ID,
|
|
|
|
|
OrderID: &orderID,
|
|
|
|
|
Direction: "in",
|
|
|
|
|
AmountCent: 38613,
|
|
|
|
|
BalanceType: "available",
|
|
|
|
|
BizType: "arbitration_owner_income",
|
|
|
|
|
BizNo: order.OrderNo,
|
|
|
|
|
Remark: "仲裁按实际消耗结算给号主",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
LedgerNo: "WL-ARBITRATION-OTHER-USER",
|
|
|
|
|
UserID: renter.ID,
|
|
|
|
|
OrderID: &orderID,
|
|
|
|
|
Direction: "in",
|
|
|
|
|
AmountCent: 999999,
|
|
|
|
|
BalanceType: "available",
|
|
|
|
|
BizType: "arbitration_owner_income",
|
|
|
|
|
BizNo: order.OrderNo,
|
|
|
|
|
Remark: "不应计入号主收入",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
if err := db.Create(&ledgers).Error; err != nil {
|
|
|
|
|
t.Fatalf("create wallet ledgers failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := repo.ListAdmin(t.Context(), AdminOrderQuery{Page: 1, PageSize: 20})
|
|
|
|
@@ -2259,8 +2286,36 @@ func TestListAdminOwnerIncomeUsesLatestCheckout(t *testing.T) {
|
|
|
|
|
t.Fatalf("ListAdmin() returned %d items, want 1", len(result.Items))
|
|
|
|
|
}
|
|
|
|
|
income := result.Items[0].OwnerIncomeAmountCent
|
|
|
|
|
if income == nil || *income != 2222 {
|
|
|
|
|
t.Fatalf("owner income = %v, want 2222 from latest checkout row", income)
|
|
|
|
|
if income == nil || *income != 38613 {
|
|
|
|
|
t.Fatalf("owner income = %v, want 38613 from arbitration wallet income", income)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestListAdminArbitratedWithoutPayoutReturnsZero 校验仲裁已经形成终态但无实际入账时,
|
|
|
|
|
// 列表返回明确的 0 元,而不是因为缺少结账行显示为未知(null)。
|
|
|
|
|
func TestListAdminArbitratedWithoutPayoutReturnsZero(t *testing.T) {
|
|
|
|
|
db := setupOrderTestDB(t)
|
|
|
|
|
repo := NewRepository(db)
|
|
|
|
|
adminID := uint64(7005)
|
|
|
|
|
_, _, order := createPlatformManagedCheckoutOrder(t, db, adminID)
|
|
|
|
|
order.Status = orderStatusClosed
|
|
|
|
|
order.SettlementStatus = settlementStatusArbitrated
|
|
|
|
|
order.OfflineSettlementStatus = offlineSettlementStatusNone
|
|
|
|
|
order.OfflineSettlementAmountCent = 0
|
|
|
|
|
if err := db.Save(&order).Error; err != nil {
|
|
|
|
|
t.Fatalf("save arbitrated order failed: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result, err := repo.ListAdmin(t.Context(), AdminOrderQuery{Page: 1, PageSize: 20})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListAdmin() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(result.Items) != 1 {
|
|
|
|
|
t.Fatalf("ListAdmin() returned %d items, want 1", len(result.Items))
|
|
|
|
|
}
|
|
|
|
|
income := result.Items[0].OwnerIncomeAmountCent
|
|
|
|
|
if income == nil || *income != 0 {
|
|
|
|
|
t.Fatalf("owner income = %v, want explicit zero for arbitrated order without payout", income)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2513,23 +2568,24 @@ func seedSettledAdminOrders(t *testing.T, db *gorm.DB, from, to int) {
|
|
|
|
|
t.Fatalf("create listing %d failed: %v", i, err)
|
|
|
|
|
}
|
|
|
|
|
order := model.RentalOrder{
|
|
|
|
|
OrderNo: fmt.Sprintf("ORD-N1-%04d", i),
|
|
|
|
|
ListingID: listing.ID,
|
|
|
|
|
AccountID: account.ID,
|
|
|
|
|
OwnerID: owner.ID,
|
|
|
|
|
RenterID: renter.ID,
|
|
|
|
|
Status: orderStatusCompleted,
|
|
|
|
|
SettlementStatus: settlementStatusSettled,
|
|
|
|
|
HandoffMode: handoffModePlatform,
|
|
|
|
|
SettlementMode: settlementModePlatformManaged,
|
|
|
|
|
ManagedAdminID: &adminID,
|
|
|
|
|
RentAmountCent: 10000,
|
|
|
|
|
OwnerRentAmountCent: 8000,
|
|
|
|
|
PlatformFeeCent: 2000,
|
|
|
|
|
DepositAmountCent: 1000,
|
|
|
|
|
EstimatedDurationHours: 24,
|
|
|
|
|
OfflineSettlementStatus: offlineSettlementStatusNone,
|
|
|
|
|
AccountSnapshot: datatypes.JSON([]byte(`{"haf_coin_amount":1000000}`)),
|
|
|
|
|
OrderNo: fmt.Sprintf("ORD-N1-%04d", i),
|
|
|
|
|
ListingID: listing.ID,
|
|
|
|
|
AccountID: account.ID,
|
|
|
|
|
OwnerID: owner.ID,
|
|
|
|
|
RenterID: renter.ID,
|
|
|
|
|
Status: orderStatusCompleted,
|
|
|
|
|
SettlementStatus: settlementStatusSettled,
|
|
|
|
|
HandoffMode: handoffModePlatform,
|
|
|
|
|
SettlementMode: settlementModePlatformManaged,
|
|
|
|
|
ManagedAdminID: &adminID,
|
|
|
|
|
RentAmountCent: 10000,
|
|
|
|
|
OwnerRentAmountCent: 8000,
|
|
|
|
|
PlatformFeeCent: 2000,
|
|
|
|
|
DepositAmountCent: 1000,
|
|
|
|
|
EstimatedDurationHours: 24,
|
|
|
|
|
OfflineSettlementStatus: offlineSettlementStatusNone,
|
|
|
|
|
OfflineSettlementAmountCent: int64(1000 + i),
|
|
|
|
|
AccountSnapshot: datatypes.JSON([]byte(`{"haf_coin_amount":1000000}`)),
|
|
|
|
|
}
|
|
|
|
|
if err := db.Create(&order).Error; err != nil {
|
|
|
|
|
t.Fatalf("create order %d failed: %v", i, err)
|
|
|
|
@@ -2600,26 +2656,28 @@ func (c *sqlCaptureLogger) Trace(_ context.Context, _ time.Time, fc func() (stri
|
|
|
|
|
c.statements = append(c.statements, sql)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLatestOwnerIncomeQueryIsSingleBatchStatement 锁定号主实际收入的取数形态:
|
|
|
|
|
// 整页一条语句、用 STRAIGHT_JOIN 固定「先物化最新结账 id,再按主键取金额」的顺序。
|
|
|
|
|
// 该形态对应 EXPLAIN:覆盖索引扫描 + 主键单行查找,不随行数产生额外查询。
|
|
|
|
|
func TestLatestOwnerIncomeQueryIsSingleBatchStatement(t *testing.T) {
|
|
|
|
|
// TestOwnerActualIncomeQueryIsSingleBatchStatement 锁定号主实际收入的取数形态:
|
|
|
|
|
// 整页一条语句,平台代管读取线下结算金额,其他订单汇总钱包最终入账。
|
|
|
|
|
func TestOwnerActualIncomeQueryIsSingleBatchStatement(t *testing.T) {
|
|
|
|
|
capture := &sqlCaptureLogger{Interface: logger.Default.LogMode(logger.Silent)}
|
|
|
|
|
db := setupOrderTestDB(t).Session(&gorm.Session{Logger: capture})
|
|
|
|
|
repo := NewRepository(db)
|
|
|
|
|
|
|
|
|
|
if _, err := repo.latestOwnerIncomeByOrder(t.Context(), []uint64{11, 12, 13}); err != nil {
|
|
|
|
|
t.Fatalf("latestOwnerIncomeByOrder() error = %v", err)
|
|
|
|
|
if _, err := repo.ownerActualIncomeByOrder(t.Context(), []uint64{11, 12, 13}); err != nil {
|
|
|
|
|
t.Fatalf("ownerActualIncomeByOrder() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(capture.statements) != 1 {
|
|
|
|
|
t.Fatalf("取数语句条数 = %d, want 1(多行必须合并为单条批量查询)", len(capture.statements))
|
|
|
|
|
}
|
|
|
|
|
sql := capture.statements[0]
|
|
|
|
|
if !strings.Contains(sql, "STRAIGHT_JOIN order_checkouts") {
|
|
|
|
|
t.Fatalf("缺少 STRAIGHT_JOIN 固定连接顺序,实际 SQL:\n%s", sql)
|
|
|
|
|
if !strings.Contains(sql, "LEFT JOIN wallet_ledger AS wl") {
|
|
|
|
|
t.Fatalf("缺少钱包入账汇总,实际 SQL:\n%s", sql)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(sql, "offline_settlement_amount_cent") {
|
|
|
|
|
t.Fatalf("缺少平台代管线下结算金额,实际 SQL:\n%s", sql)
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(sql, "GROUP BY") {
|
|
|
|
|
t.Fatalf("缺少按 order_id 分组的 MAX(id) 子查询,实际 SQL:\n%s", sql)
|
|
|
|
|
t.Fatalf("缺少按订单分组汇总,实际 SQL:\n%s", sql)
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(sql, "SELECT *") {
|
|
|
|
|
t.Fatalf("应只取所需列,避免读取 content/evidence_urls 大字段,实际 SQL:\n%s", sql)
|
|
|
|
|