99 lines
5.3 KiB
Go
99 lines
5.3 KiB
Go
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
|
|
}
|