Files
hfb_sys/backend/internal/modules/distribution/pricing_test.go
T
yml2213 ac777f5b16 补全分销分账结算闭环并修复站点越权
- 权限落库:新增 000066 迁移,注册 11 个 distribution:* 权限并授权 super_admin/ops/finance
- 越权修复:siteAccessAllowed 校验贯穿站点/规则/订单/展示/支付通道/结算全部按 ID 操作
- 分账闭环:SplitSettlement 按比例拆分分成基数,销售加价归卖出方,尾差记入 rounding_adjustment_cent 归平台;支付快照与退款重算均按拆分写入参与方金额
- 结算批次:创建时聚合周期内待结算订单生成明细并汇总金额,结算时校验明细与总额一致后联动置位财务快照
- 手工补数接口禁止覆盖已有快照并写入审计日志
- 杂项:二维码池校验站点启用状态且不吞真实 DB 错误;绑定支付通道校验商户配置存在;表探测改为 sync.Once 缓存
2026-09-05 11:12:44 +08:00

103 lines
4.1 KiB
Go

package distribution
import "testing"
func TestDefaultRules(t *testing.T) {
cases := []struct {
mode string
check func(Rule) bool
}{
{ModeTwoPartyDistributorReceive, func(r Rule) bool { return r.DistributorReceiveRatio == 3.5 && r.PlatformSellRatio == 4 }},
{ModeTwoPartyPlatformReceive, func(r Rule) bool { return r.PlatformReceiveRatio == 3.5 && r.DistributorSellRatio == 4 }},
{ModeThreeParty, func(r Rule) bool { return r.DistributorARatio == 3.5 && r.DistributorBRatio == 4 }},
}
for _, item := range cases {
rule, err := DefaultRule(item.mode)
if err != nil || !item.check(rule) || rule.ServiceFeeValue != 2.5 || rule.PaymentFeeBPS != 50 {
t.Fatalf("default rule %s = %#v, err=%v", item.mode, rule, err)
}
}
}
func TestCalculateSettlementDeductsFeeBeforeSplit(t *testing.T) {
rule := Rule{ServiceFeeValue: 0, ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 50}
got, err := CalculateSettlement(rule, SettlementInput{GrossPaidAmountCent: 10001, RefundAmountCent: 1000, MarkupAmountCent: 300})
if err != nil {
t.Fatal(err)
}
if got.NetPaidAmountCent != 9001 || got.PaymentFeeCent != 45 || got.SplitBaseAmountCent != 8956 {
t.Fatalf("unexpected settlement: %#v", got)
}
}
func TestCalculateSettlementTaxesEachPositivePartyProfit(t *testing.T) {
rule := Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount, PaymentFeeBPS: 0, TaxEnabled: true, TaxRateBPS: 100}
got, err := CalculateSettlement(rule, SettlementInput{
GrossPaidAmountCent: 10000,
GrossProfitByParty: []PartyProfitInput{
{PartyRole: "distributor_a", IncomeAmountCent: 1000, CostAmountCent: 400},
{PartyRole: "distributor_b", IncomeAmountCent: 500, CostAmountCent: 700},
},
})
if err != nil {
t.Fatal(err)
}
if got.TaxAmountCent != 6 || len(got.TaxByParty) != 2 || got.TaxByParty[0].TaxAmountCent != 6 || got.TaxByParty[1].TaxAmountCent != 0 {
t.Fatalf("unexpected tax result: %#v", got)
}
}
func TestCalculateSettlementRejectsRefundOverPayment(t *testing.T) {
_, err := CalculateSettlement(Rule{ServiceFeeUnit: ServiceFeeUnitFixedAmount}, SettlementInput{GrossPaidAmountCent: 1, RefundAmountCent: 2})
if err != ErrInvalidAmount {
t.Fatalf("err = %v", err)
}
}
func TestSplitSettlementTwoPartyDistributorReceive(t *testing.T) {
rule := Rule{Mode: ModeTwoPartyDistributorReceive, DistributorReceiveRatio: 3.5, PlatformSellRatio: 4}
splits := SplitSettlement(rule, 10000, 0)
if len(splits) != 2 {
t.Fatalf("splits = %#v", splits)
}
if splits[0].PartyRole != "distributor_receive" || splits[0].AmountCent != 350 {
t.Fatalf("distributor split = %#v", splits[0])
}
if splits[1].PartyRole != "platform_sell" || splits[1].AmountCent != 400 {
t.Fatalf("platform split = %#v", splits[1])
}
if got := SplitRemainderCent(10000, splits); got != 9250 {
t.Fatalf("remainder = %d, want 9250", got)
}
}
func TestSplitSettlementMarkupGoesToSeller(t *testing.T) {
// 两方-分销收号模式:卖出方是主站(platform_sell)。
rule := Rule{Mode: ModeTwoPartyDistributorReceive, DistributorReceiveRatio: 3.5, PlatformSellRatio: 4}
splits := SplitSettlement(rule, 10000, 300)
if splits[1].AmountCent != 700 {
t.Fatalf("platform split with markup = %d, want 700", splits[1].AmountCent)
}
// 两方-分销卖出模式:卖出方是分销网(distributor_sell)。
ruleSell := Rule{Mode: ModeTwoPartyPlatformReceive, DistributorSellRatio: 4, PlatformReceiveRatio: 3.5}
splitsSell := SplitSettlement(ruleSell, 10000, 300)
if splitsSell[0].PartyRole != "distributor_sell" || splitsSell[0].AmountCent != 700 {
t.Fatalf("distributor sell split with markup = %#v", splitsSell[0])
}
if got := SplitRemainderCent(10000, splitsSell); got != 8950 {
t.Fatalf("remainder = %d, want 8950", got)
}
}
func TestSplitSettlementThreeParty(t *testing.T) {
rule := Rule{Mode: ModeThreeParty, DistributorARatio: 3.5, DistributorBRatio: 4}
splits := SplitSettlement(rule, 10001, 0)
if len(splits) != 2 || splits[0].AmountCent != 350 || splits[1].AmountCent != 400 {
t.Fatalf("splits = %#v", splits)
}
// 尾差归平台:10001 - 750 = 9251。
if got := SplitRemainderCent(10001, splits); got != 9251 {
t.Fatalf("remainder = %d, want 9251", got)
}
}