- 结算明细加唯一键 (order_id, party_id):自动释放与手工批次并发时以唯一键冲突为准跳过该参与方,不再重复入账 - 自动释放先无锁筛出本站未入账参与方,收敛加锁范围;手工批次建批次后先锁周期内财务快照,与自动释放串行化 - 累计退款额改为「本次回调金额与已退款成功流水合计取较大值」,多笔退款与退款结果重复回调不再把口径写小 - 关单三路径(AdminClose / AdminRejectRefund 的 close 分支 / AdminSeal)补写 release_at,接线原死代码 EnsureCompletedReleaseAtTx - 仲裁分销重算改用实际退款额(扣除押金暂扣),避免站点被多扣 - 订单列表与详情的提价分成取 min(收入, 货架加价),退款压缩加价后不再显示负数 - 新增迁移 000070(结算明细唯一键)、000071(补回填 closed 订单释放时间) - 新增 5 个测试文件:结算幂等与展示钳制、结算 job 幂等、累计退款、关单补写释放时间、仲裁押金暂扣口径
109 lines
3.7 KiB
Go
109 lines
3.7 KiB
Go
package distributionsettlement
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/database"
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/internal/modules/distribution"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupSettlementJobTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db := database.NewTestDB()
|
|
if err := database.AutoMigrateIfMissing(db,
|
|
&model.AdminUser{}, &model.RentalListing{}, &model.GameAccount{}, &model.User{},
|
|
&model.RentalOrder{}, &model.DistributionSite{}, &model.DistributionRuleVersion{},
|
|
&model.DistributionOrderFinancial{}, &model.DistributionOrderParty{},
|
|
&model.DistributionSettlementBatch{}, &model.DistributionSettlementItem{},
|
|
&model.WalletAccount{}, &model.WalletLedger{}, &model.Dispute{},
|
|
); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// TestRunReleasesDueSettlementOnce 断言结算 job 会把到期订单入账到站点钱包并置财务快照为 settled,
|
|
// 且重复执行不会重复入账(幂等)。
|
|
func TestRunReleasesDueSettlementOnce(t *testing.T) {
|
|
db := setupSettlementJobTestDB(t)
|
|
job := New(db, nil, zap.NewNop())
|
|
now := time.Now()
|
|
past := now.Add(-time.Minute)
|
|
|
|
site := model.DistributionSite{
|
|
SiteCode: "job_site", SiteName: "定时结算站", Domain: "job.dist.com",
|
|
Status: "active", DisplayAccountEnabled: true, SettlementHoldDays: 0,
|
|
}
|
|
if err := db.Create(&site).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rule := model.DistributionRuleVersion{
|
|
SiteID: &site.ID, Mode: distribution.ModeTwoPartyDistributorReceive, Version: 1, Status: "active",
|
|
DistributorReceiveRatio: 35, PlatformSellRatio: 40,
|
|
ServiceFeeUnit: distribution.ServiceFeeUnitFixedAmount, EffectiveFrom: now.Add(-time.Hour),
|
|
}
|
|
if err := db.Create(&rule).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
order := model.RentalOrder{
|
|
OrderNo: "ORD-JOB-1", ListingID: 1, AccountID: 2, OwnerID: 3, RenterID: 4,
|
|
Status: "completed", RentAmountCent: 10000, OwnerRentAmountCent: 6000, RefundStatus: "none",
|
|
DistributionSiteID: &site.ID, DistributionRuleVersionID: &rule.ID,
|
|
}
|
|
if err := db.Create(&order).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Create(&model.DistributionOrderFinancial{
|
|
OrderID: order.ID, SiteID: site.ID, RuleVersionID: rule.ID,
|
|
GrossPaidAmountCent: 10000, NetPaidAmountCent: 10000,
|
|
SplitBaseAmountCent: 4000, SettlementStatus: "pending", ReleaseAt: &past,
|
|
}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := db.Create(&model.DistributionOrderParty{
|
|
OrderID: order.ID, SiteID: site.ID, PartyRole: "distributor_receive",
|
|
PartyType: distribution.PartyTypeSite, PartyID: site.ID, IncomeAmountCent: 1400,
|
|
}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
job.run(t.Context())
|
|
|
|
var financial model.DistributionOrderFinancial
|
|
if err := db.Where("order_id = ?", order.ID).First(&financial).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if financial.SettlementStatus != "settled" || financial.ReleasedAt == nil {
|
|
t.Fatalf("财务快照未置为已结算: %+v", financial)
|
|
}
|
|
var wallet model.WalletAccount
|
|
if err := db.Where("user_id = 0 AND distribution_site_id = ?", site.ID).First(&wallet).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if wallet.AvailableBalanceCent != 1400 {
|
|
t.Fatalf("站点钱包 = %d, want 1400", wallet.AvailableBalanceCent)
|
|
}
|
|
|
|
// 第二次执行不得重复入账。
|
|
job.run(t.Context())
|
|
if err := db.Where("user_id = 0 AND distribution_site_id = ?", site.ID).First(&wallet).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if wallet.AvailableBalanceCent != 1400 {
|
|
t.Fatalf("重复执行后站点钱包 = %d, want 1400(幂等)", wallet.AvailableBalanceCent)
|
|
}
|
|
var itemCount int64
|
|
if err := db.Model(&model.DistributionSettlementItem{}).Where("order_id = ?", order.ID).
|
|
Count(&itemCount).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if itemCount != 1 {
|
|
t.Fatalf("结算明细 = %d, want 1", itemCount)
|
|
}
|
|
}
|