package distribution import ( "errors" "time" "hfb_sys/backend/internal/model" "gorm.io/gorm" ) // ResolveOrderSourceTx 校验站点、账号展示关系和生效规则,供正常订单创建事务写入快照引用。 func ResolveOrderSourceTx(tx *gorm.DB, siteCode string, listingID, accountID uint64, at time.Time) (*model.DistributionSite, *model.DistributionRuleVersion, error) { if siteCode == "" { return nil, nil, nil } var site model.DistributionSite if err := tx.Where("site_code = ? AND status = 'active' AND display_account_enabled = ?", siteCode, true).First(&site).Error; err != nil { return nil, nil, ErrSiteNotFound } var relation model.DistributionSiteAccount if err := tx.Where("site_id = ? AND listing_id = ? AND account_id = ? AND display_status = 'enabled'", site.ID, listingID, accountID).First(&relation).Error; err != nil { return nil, nil, ErrAccountDisplayNotFound } var rule model.DistributionRuleVersion query := tx.Where("mode = ? AND status = 'active' AND effective_from <= ? AND (effective_to IS NULL OR effective_to > ?)", ModeTwoPartyDistributorReceive, at, at). Where("site_id = ? OR site_id IS NULL", site.ID). Order("CASE WHEN site_id IS NULL THEN 1 ELSE 0 END, effective_from DESC, id DESC") if err := query.First(&rule).Error; err != nil { return nil, nil, ErrRuleNotFound } return &site, &rule, nil } // SnapshotPaidOrderTx 在支付确认事务内幂等写入财务和参与方快照。 func SnapshotPaidOrderTx(tx *gorm.DB, orderID uint64, grossPaidCent int64) error { if !tx.Migrator().HasTable(&model.DistributionOrderFinancial{}) { return nil } var order model.RentalOrder if err := tx.First(&order, orderID).Error; err != nil { return err } if order.DistributionSiteID == nil || order.DistributionRuleVersionID == nil { return nil } var rule model.DistributionRuleVersion if err := tx.First(&rule, *order.DistributionRuleVersionID).Error; err != nil { return err } markup := order.PlatformFeeCent result, err := CalculateSettlement(Rule{ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, PaymentFeeBPS: rule.PaymentFeeBPS, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS}, SettlementInput{GrossPaidAmountCent: grossPaidCent, MarkupAmountCent: markup, GrossProfitByParty: []PartyProfitInput{{PartyRole: "distributor_receive", IncomeAmountCent: order.OwnerRentAmountCent}, {PartyRole: "platform_sell", IncomeAmountCent: order.PlatformFeeCent}}}) if err != nil { return err } financial := model.DistributionOrderFinancial{OrderID: order.ID, SiteID: *order.DistributionSiteID, RuleVersionID: rule.ID, GrossPaidAmountCent: grossPaidCent, NetPaidAmountCent: result.NetPaidAmountCent, PaymentFeeBPS: rule.PaymentFeeBPS, PaymentFeeCent: result.PaymentFeeCent, ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, ServiceFeeCent: result.ServiceFeeCent, MarkupAmountCent: markup, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS, TaxAmountCent: result.TaxAmountCent, SplitBaseAmountCent: result.SplitBaseAmountCent, SettlementStatus: "pending"} if err := tx.Where("order_id = ?", order.ID).Assign(financial).FirstOrCreate(&financial).Error; err != nil { return err } var count int64 if err := tx.Model(&model.DistributionOrderParty{}).Where("order_id = ?", order.ID).Count(&count).Error; err != nil { return err } if count > 0 { return nil } parties := []model.DistributionOrderParty{ {OrderID: order.ID, SiteID: *order.DistributionSiteID, PartyRole: "distributor_receive", PartyType: "site", PartyID: *order.DistributionSiteID, Ratio: rule.DistributorReceiveRatio, IncomeAmountCent: order.OwnerRentAmountCent, ProfitAmountCent: order.OwnerRentAmountCent}, {OrderID: order.ID, SiteID: *order.DistributionSiteID, PartyRole: "platform_sell", PartyType: "platform", Ratio: rule.PlatformSellRatio, IncomeAmountCent: order.PlatformFeeCent, ProfitAmountCent: order.PlatformFeeCent}, } return tx.Create(&parties).Error } // ApplyRefundTx 按累计成功退款金额重算手续费和分成基数,不覆盖订单创建时的规则版本。 func ApplyRefundTx(tx *gorm.DB, orderID uint64, refundAmountCent int64) error { if !tx.Migrator().HasTable(&model.DistributionOrderFinancial{}) { return nil } var financial model.DistributionOrderFinancial if err := tx.Where("order_id = ?", orderID).First(&financial).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } return err } if refundAmountCent < 0 || refundAmountCent > financial.GrossPaidAmountCent { return ErrInvalidAmount } var rule model.DistributionRuleVersion if err := tx.First(&rule, financial.RuleVersionID).Error; err != nil { return err } result, err := CalculateSettlement(Rule{ServiceFeeValue: rule.ServiceFeeValue, ServiceFeeUnit: rule.ServiceFeeUnit, PaymentFeeBPS: rule.PaymentFeeBPS, TaxEnabled: rule.TaxEnabled, TaxRateBPS: rule.TaxRateBPS}, SettlementInput{GrossPaidAmountCent: financial.GrossPaidAmountCent, RefundAmountCent: refundAmountCent, MarkupAmountCent: financial.MarkupAmountCent}) if err != nil { return err } return tx.Model(&financial).Updates(map[string]any{"refund_amount_cent": refundAmountCent, "net_paid_amount_cent": result.NetPaidAmountCent, "payment_fee_cent": result.PaymentFeeCent, "service_fee_cent": result.ServiceFeeCent, "split_base_amount_cent": result.SplitBaseAmountCent, "settlement_status": "pending"}).Error }