订单相关以及佣金相关修复
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m57s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m57s
This commit is contained in:
@@ -19,7 +19,7 @@ type CommissionRecord struct {
|
||||
CommissionSource string `gorm:"column:commission_source;type:varchar(20);not null;index;comment:佣金来源 cost_diff-成本价差 one_time-一次性佣金" json:"commission_source"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:佣金金额(分)" json:"amount"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;default:0;comment:入账后钱包余额(分)" json:"balance_after"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-已冻结 2-解冻中 3-已发放 4-已失效" json:"status"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-已冻结 2-解冻中 3-已发放 4-已失效 99-待人工修正" json:"status"`
|
||||
ReleasedAt *time.Time `gorm:"column:released_at;comment:入账时间" json:"released_at"`
|
||||
Remark string `gorm:"column:remark;type:varchar(500);comment:备注" json:"remark"`
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ type CommissionRecordListRequest struct {
|
||||
CommissionSource *string `json:"commission_source" query:"commission_source" validate:"omitempty,oneof=cost_diff one_time" description:"佣金来源 (cost_diff:成本价差, one_time:一次性佣金)"`
|
||||
StartTime *string `json:"start_time" query:"start_time" validate:"omitempty" description:"开始时间"`
|
||||
EndTime *string `json:"end_time" query:"end_time" validate:"omitempty" description:"结束时间"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2 99" description:"状态 (1:已入账, 2:已失效, 99:待人工修正)"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2 3 4 99" description:"状态 (1:已冻结, 2:解冻中, 3:已发放, 4:已失效, 99:待人工修正)"`
|
||||
}
|
||||
|
||||
// CommissionRecordPageResult 佣金记录分页结果
|
||||
|
||||
@@ -65,14 +65,18 @@ type OrderResponse struct {
|
||||
PaidAt *time.Time `json:"paid_at,omitempty" description:"支付时间"`
|
||||
PaymentVoucherKey string `json:"payment_voucher_key,omitempty" description:"线下支付凭证对象存储file_key(仅线下支付订单有值)"`
|
||||
IsPurchaseOnBehalf bool `json:"is_purchase_on_behalf" description:"是否为代购订单"`
|
||||
CommissionStatus int `json:"commission_status" description:"佣金状态 (1:待计算, 2:已计算)"`
|
||||
CommissionStatusName string `json:"commission_status_name" description:"佣金状态名称(中文)"`
|
||||
CommissionStatus int `json:"commission_status" description:"佣金流程状态 (1:待计算, 2:已完成, 3:待人工处理)"`
|
||||
CommissionStatusName string `json:"commission_status_name" description:"佣金流程状态名称(中文)"`
|
||||
CommissionResult int `json:"commission_result" description:"佣金业务结果 (0:未知, 1:有佣金, 2:无佣金, 3:链路异常)"`
|
||||
CommissionResultName string `json:"commission_result_name" description:"佣金业务结果名称(中文)"`
|
||||
CommissionConfigVersion int `json:"commission_config_version" description:"佣金配置版本"`
|
||||
SellerShopID *uint `json:"seller_shop_id,omitempty" description:"销售店铺ID(业务归属字段)"`
|
||||
SellerShopName string `json:"seller_shop_name,omitempty" description:"销售店铺名称(业务归属展示)"`
|
||||
|
||||
// 操作者信息
|
||||
OperatorID *uint `json:"operator_id,omitempty" description:"操作者ID"`
|
||||
OperatorType string `json:"operator_type,omitempty" description:"操作者类型 (platform:平台, agent:代理)"`
|
||||
OperatorName string `json:"operator_name,omitempty" description:"操作者名称"`
|
||||
OperatorID *uint `json:"operator_id,omitempty" description:"操作者账号ID(真实发起操作的账号/主体ID)"`
|
||||
OperatorType string `json:"operator_type,omitempty" description:"操作者账号类型 (platform:平台, agent:代理, enterprise:企业, personal_customer:个人客户)"`
|
||||
OperatorName string `json:"operator_name,omitempty" description:"操作者名称(按账号语义展示)"`
|
||||
ActualPaidAmount *int64 `json:"actual_paid_amount,omitempty" description:"实际支付金额(分)"`
|
||||
|
||||
// 订单角色
|
||||
|
||||
@@ -37,7 +37,8 @@ type Order struct {
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
|
||||
|
||||
// 佣金信息
|
||||
CommissionStatus int `gorm:"column:commission_status;type:int;default:1;not null;comment:佣金状态 1-待计算 2-已计算" json:"commission_status"`
|
||||
CommissionStatus int `gorm:"column:commission_status;type:int;default:1;not null;comment:佣金流程状态 1-待计算 2-已完成 3-待人工处理" json:"commission_status"`
|
||||
CommissionResult int `gorm:"column:commission_result;type:int;default:0;not null;comment:佣金业务结果 0-未知 1-有佣金 2-无佣金 3-链路异常" json:"commission_result"`
|
||||
CommissionConfigVersion int `gorm:"column:commission_config_version;type:int;default:0;comment:佣金配置版本(订单创建时快照)" json:"commission_config_version"`
|
||||
SellerShopID *uint `gorm:"column:seller_shop_id;index;comment:销售店铺ID(用于成本价差佣金计算)" json:"seller_shop_id,omitempty"`
|
||||
SellerCostPrice int64 `gorm:"column:seller_cost_price;type:bigint;default:0;comment:销售成本价(分,用于计算利润)" json:"seller_cost_price"`
|
||||
@@ -51,8 +52,11 @@ type Order struct {
|
||||
IsPurchaseOnBehalf bool `gorm:"column:is_purchase_on_behalf;type:boolean;default:false;comment:是否为代购订单" json:"is_purchase_on_behalf"`
|
||||
|
||||
// 操作者信息(谁下的单)
|
||||
OperatorID *uint `gorm:"column:operator_id;index:idx_orders_operator_id;comment:操作者ID(谁下的单)" json:"operator_id,omitempty"`
|
||||
OperatorType string `gorm:"column:operator_type;type:varchar(20);comment:操作者类型(platform/agent)" json:"operator_type,omitempty"`
|
||||
OperatorAccountID *uint `gorm:"column:operator_account_id;index:idx_orders_operator_account_id;comment:操作者账号ID(权威字段)" json:"operator_account_id,omitempty"`
|
||||
OperatorAccountType string `gorm:"column:operator_account_type;type:varchar(32);default:'';not null;comment:操作者账号类型(platform/agent/enterprise/personal_customer)" json:"operator_account_type,omitempty"`
|
||||
OperatorAccountName string `gorm:"column:operator_account_name;type:varchar(255);default:'';not null;comment:操作者名称快照" json:"operator_account_name,omitempty"`
|
||||
OperatorID *uint `gorm:"column:operator_id;index:idx_orders_operator_id;comment:操作者ID(历史兼容字段,旧语义可能保存代理店铺ID)" json:"operator_id,omitempty"`
|
||||
OperatorType string `gorm:"column:operator_type;type:varchar(20);comment:操作者类型(历史兼容字段,旧语义为platform/agent)" json:"operator_type,omitempty"`
|
||||
|
||||
// 实际支付金额(可能与订单金额不同,如代理代购场景)
|
||||
ActualPaidAmount *int64 `gorm:"column:actual_paid_amount;type:bigint;comment:实际支付金额(分)" json:"actual_paid_amount,omitempty"`
|
||||
@@ -103,10 +107,22 @@ const (
|
||||
PaymentStatusRefunded = 4 // 已退款
|
||||
)
|
||||
|
||||
// 佣金状态常量
|
||||
// 佣金流程状态常量
|
||||
const (
|
||||
CommissionStatusPending = 1 // 待计算
|
||||
CommissionStatusCalculated = 2 // 已计算
|
||||
CommissionStatusPending = 1 // 待计算
|
||||
CommissionStatusCompleted = 2 // 已完成
|
||||
CommissionStatusPendingReview = 3 // 待人工处理
|
||||
|
||||
// CommissionStatusCalculated 兼容旧命名,语义已调整为“已完成”
|
||||
CommissionStatusCalculated = CommissionStatusCompleted
|
||||
)
|
||||
|
||||
// 佣金业务结果常量
|
||||
const (
|
||||
CommissionResultUnknown = 0 // 未知(历史兼容态)
|
||||
CommissionResultHasAmount = 1 // 有佣金
|
||||
CommissionResultNoAmount = 2 // 无佣金
|
||||
CommissionResultChainBroken = 3 // 链路异常
|
||||
)
|
||||
|
||||
// 订单角色常量
|
||||
@@ -117,21 +133,29 @@ const (
|
||||
PurchaseRolePurchaseForSubordinate = "purchase_for_subordinate" // 给下级购买
|
||||
)
|
||||
|
||||
// 操作者账号类型常量
|
||||
const (
|
||||
OperatorAccountTypePlatform = "platform" // 平台账号
|
||||
OperatorAccountTypeAgent = "agent" // 代理账号
|
||||
OperatorAccountTypeEnterprise = "enterprise" // 企业账号
|
||||
OperatorAccountTypePersonalCustomer = "personal_customer" // 个人客户
|
||||
)
|
||||
|
||||
// OrderItem 订单明细模型
|
||||
// 记录订单中购买的套餐明细,支持一个订单购买多个套餐
|
||||
type OrderItem struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
|
||||
OrderID uint `gorm:"column:order_id;index:idx_order_item_order_id;not null;comment:订单ID" json:"order_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
|
||||
PackageName string `gorm:"column:package_name;type:varchar(100);not null;comment:套餐名称(快照)" json:"package_name"`
|
||||
PackageType *string `gorm:"column:package_type;type:varchar(20);comment:套餐类型快照 formal-正式套餐 addon-加油包" json:"package_type"`
|
||||
Quantity int `gorm:"column:quantity;type:int;default:1;not null;comment:数量" json:"quantity"`
|
||||
UnitPrice int64 `gorm:"column:unit_price;type:bigint;not null;comment:单价(分)" json:"unit_price"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:小计金额(分)" json:"amount"`
|
||||
PackagePriceConfigStatus int `gorm:"column:package_price_config_status;type:int;default:0;not null;comment:套餐价格配置状态快照 0-未配置 1-赠送0价 2-已配置非0" json:"package_price_config_status"`
|
||||
PackageIsGift bool `gorm:"column:package_is_gift;type:boolean;default:false;not null;comment:套餐是否赠送快照 true-赠送 false-普通可售" json:"package_is_gift"`
|
||||
OrderID uint `gorm:"column:order_id;index:idx_order_item_order_id;not null;comment:订单ID" json:"order_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
|
||||
PackageName string `gorm:"column:package_name;type:varchar(100);not null;comment:套餐名称(快照)" json:"package_name"`
|
||||
PackageType *string `gorm:"column:package_type;type:varchar(20);comment:套餐类型快照 formal-正式套餐 addon-加油包" json:"package_type"`
|
||||
Quantity int `gorm:"column:quantity;type:int;default:1;not null;comment:数量" json:"quantity"`
|
||||
UnitPrice int64 `gorm:"column:unit_price;type:bigint;not null;comment:单价(分)" json:"unit_price"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:小计金额(分)" json:"amount"`
|
||||
PackagePriceConfigStatus int `gorm:"column:package_price_config_status;type:int;default:0;not null;comment:套餐价格配置状态快照 0-未配置 1-赠送0价 2-已配置非0" json:"package_price_config_status"`
|
||||
PackageIsGift bool `gorm:"column:package_is_gift;type:boolean;default:false;not null;comment:套餐是否赠送快照 true-赠送 false-普通可售" json:"package_is_gift"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -477,6 +477,28 @@ func (s *Service) fetchBuyerSnapshot(ctx context.Context, customerID uint) (phon
|
||||
return phone, nickname
|
||||
}
|
||||
|
||||
func (s *Service) buildPersonalCustomerOperatorSnapshot(ctx context.Context, customerID uint, nickname *string) (*uint, string, string) {
|
||||
if customerID == 0 {
|
||||
return nil, "", ""
|
||||
}
|
||||
|
||||
name := ""
|
||||
if nickname != nil {
|
||||
name = strings.TrimSpace(*nickname)
|
||||
}
|
||||
if name == "" {
|
||||
customer, err := s.personalCustomerStore.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Warn("buildPersonalCustomerOperatorSnapshot: 查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err))
|
||||
} else if customer != nil {
|
||||
name = strings.TrimSpace(customer.Nickname)
|
||||
}
|
||||
}
|
||||
|
||||
id := customerID
|
||||
return &id, model.OperatorAccountTypePersonalCustomer, name
|
||||
}
|
||||
|
||||
func (s *Service) buildPendingOrder(ctx context.Context, customerID uint, result *purchase_validation.PurchaseValidationResult, sellerCostPrice int64) (*model.Order, error) {
|
||||
orderType := resolveOrderType(result)
|
||||
if orderType == "" {
|
||||
@@ -515,6 +537,7 @@ func (s *Service) buildPendingOrder(ctx context.Context, customerID uint, result
|
||||
}
|
||||
|
||||
order.BuyerPhone, order.BuyerNickname = s.fetchBuyerSnapshot(ctx, customerID)
|
||||
order.OperatorAccountID, order.OperatorAccountType, order.OperatorAccountName = s.buildPersonalCustomerOperatorSnapshot(ctx, customerID, order.BuyerNickname)
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
@@ -78,8 +78,11 @@ func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "获取订单失败")
|
||||
}
|
||||
|
||||
if order.CommissionStatus == model.CommissionStatusCalculated {
|
||||
s.logger.Warn("订单佣金已计算,跳过", zap.String("order_id", fmt.Sprint(orderID)))
|
||||
if order.CommissionStatus == model.CommissionStatusCompleted || order.CommissionStatus == model.CommissionStatusPendingReview {
|
||||
s.logger.Warn("订单佣金流程已结束,跳过",
|
||||
zap.String("order_id", fmt.Sprint(orderID)),
|
||||
zap.Int("commission_status", order.CommissionStatus),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -88,13 +91,8 @@ func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "计算成本价差佣金失败")
|
||||
}
|
||||
|
||||
for _, record := range costDiffRecords {
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建成本价差佣金记录失败")
|
||||
}
|
||||
if err := s.creditCommissionInTx(ctx, tx, record); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "佣金入账失败")
|
||||
}
|
||||
if err := s.persistCommissionRecordsInTx(ctx, tx, costDiffRecords); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 代购订单不触发一次性佣金和累计充值更新
|
||||
@@ -110,8 +108,16 @@ func (s *Service) CalculateCommission(ctx context.Context, orderID uint) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Model(&model.Order{}).Where("id = ?", orderID).Update("commission_status", model.CommissionStatusCalculated).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新订单佣金状态失败")
|
||||
commissionStatus, commissionResult, err := s.resolveOrderCommissionOutcomeInTx(tx, order.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&model.Order{}).Where("id = ?", orderID).Updates(map[string]any{
|
||||
"commission_status": commissionStatus,
|
||||
"commission_result": commissionResult,
|
||||
}).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新订单佣金结果失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -190,12 +196,7 @@ func (s *Service) CalculateCostDiffCommission(ctx context.Context, order *model.
|
||||
Status: constants.CommissionStatusPendingReview,
|
||||
Remark: fmt.Sprintf("套餐系列[%d]未分配给该代理(shop_id=%d),成本价差链路断裂,请人工核查", *order.SeriesID, currentShop.ID),
|
||||
}
|
||||
if createErr := s.commissionRecordStore.Create(ctx, pendingRecord); createErr != nil {
|
||||
s.logger.Warn("创建待审佣金记录失败",
|
||||
zap.Uint("shop_id", currentShop.ID),
|
||||
zap.Uint("order_id", order.ID),
|
||||
zap.Error(createErr))
|
||||
}
|
||||
records = append(records, pendingRecord)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -284,13 +285,8 @@ func (s *Service) triggerOneTimeCommissionForCardInTx(ctx context.Context, tx *g
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建一次性佣金记录失败")
|
||||
}
|
||||
if err := s.creditCommissionInTx(ctx, tx, record); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "一次性佣金入账失败")
|
||||
}
|
||||
if err := s.persistCommissionRecordsInTx(ctx, tx, records); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
card.SetFirstRechargeTriggeredBySeries(seriesID, true)
|
||||
@@ -368,13 +364,8 @@ func (s *Service) triggerOneTimeCommissionForDeviceInTx(ctx context.Context, tx
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建一次性佣金记录失败")
|
||||
}
|
||||
if err := s.creditCommissionInTx(ctx, tx, record); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "一次性佣金入账失败")
|
||||
}
|
||||
if err := s.persistCommissionRecordsInTx(ctx, tx, records); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
device.SetFirstRechargeTriggeredBySeries(seriesID, true)
|
||||
@@ -544,12 +535,7 @@ func (s *Service) calculateChainOneTimeCommission(ctx context.Context, bottomSho
|
||||
Status: constants.CommissionStatusPendingReview,
|
||||
Remark: fmt.Sprintf("套餐系列[%d]未分配给该代理(shop_id=%d),一次性佣金链路断裂,请人工核查", seriesID, parentShopID),
|
||||
}
|
||||
if createErr := s.commissionRecordStore.Create(ctx, pendingRecord); createErr != nil {
|
||||
s.logger.Warn("创建待审佣金记录失败",
|
||||
zap.Uint("shop_id", parentShopID),
|
||||
zap.Uint("order_id", order.ID),
|
||||
zap.Error(createErr))
|
||||
}
|
||||
records = append(records, pendingRecord)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -686,6 +672,48 @@ func (s *Service) creditCommissionInTx(ctx context.Context, tx *gorm.DB, record
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) persistCommissionRecordsInTx(ctx context.Context, tx *gorm.DB, records []*model.CommissionRecord) error {
|
||||
for _, record := range records {
|
||||
if err := tx.Create(record).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建佣金记录失败")
|
||||
}
|
||||
if record.Status == constants.CommissionStatusPendingReview {
|
||||
continue
|
||||
}
|
||||
if err := s.creditCommissionInTx(ctx, tx, record); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "佣金入账失败")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) resolveOrderCommissionOutcomeInTx(tx *gorm.DB, orderID uint) (int, int, error) {
|
||||
var pendingReviewCount int64
|
||||
if err := tx.Model(&model.CommissionRecord{}).
|
||||
Where("order_id = ? AND status = ?", orderID, constants.CommissionStatusPendingReview).
|
||||
Count(&pendingReviewCount).Error; err != nil {
|
||||
return 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "统计待人工处理佣金记录失败")
|
||||
}
|
||||
|
||||
if pendingReviewCount > 0 {
|
||||
return model.CommissionStatusPendingReview, model.CommissionResultChainBroken, nil
|
||||
}
|
||||
|
||||
var releasedCount int64
|
||||
if err := tx.Model(&model.CommissionRecord{}).
|
||||
Where("order_id = ? AND status = ? AND amount > 0", orderID, constants.CommissionStatusReleased).
|
||||
Count(&releasedCount).Error; err != nil {
|
||||
return 0, 0, errors.Wrap(errors.CodeDatabaseError, err, "统计已发放佣金记录失败")
|
||||
}
|
||||
|
||||
if releasedCount > 0 {
|
||||
return model.CommissionStatusCompleted, model.CommissionResultHasAmount, nil
|
||||
}
|
||||
|
||||
return model.CommissionStatusCompleted, model.CommissionResultNoAmount, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreditCommission(ctx context.Context, record *model.CommissionRecord) error {
|
||||
return s.db.Transaction(func(tx *gorm.DB) error {
|
||||
return s.creditCommissionInTx(ctx, tx, record)
|
||||
|
||||
@@ -206,6 +206,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
operatorAccountID, operatorAccountType, operatorAccountName := s.buildAccountOperatorSnapshot(ctx, userID, operatorUserType)
|
||||
|
||||
// 提取资源所属店铺ID
|
||||
var seriesID *uint
|
||||
@@ -261,44 +262,44 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
|
||||
actualPaidAmount = &actualPaidAmountVal
|
||||
purchaseRole = model.PurchaseRoleSelfPurchase
|
||||
} else {
|
||||
isPlatformOwned := resourceShopID == nil || *resourceShopID == 0
|
||||
isPlatformOwned := resourceShopID == nil || *resourceShopID == 0
|
||||
|
||||
if isPlatformOwned {
|
||||
// ==== 子场景 1.1:平台自营(资源未分配给代理商)====
|
||||
// 平台直接销售给终端用户,无代理商参与,使用零售价,无需查询分配配置
|
||||
orderBuyerType = model.BuyerTypePersonal
|
||||
orderBuyerID = 0
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = false
|
||||
sellerCostPrice = 0
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRoleSelfPurchase
|
||||
actualPaidAmount = nil
|
||||
} else {
|
||||
// ==== 子场景 1.2:平台代购(资源属于代理商,平台代为购买)====
|
||||
purchaseBuyerID, buyerCostPriceMap, buyerTotalCost, purchasePaidAt, err := s.resolvePurchaseOnBehalfInfo(ctx, validationResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if isPlatformOwned {
|
||||
// ==== 子场景 1.1:平台自营(资源未分配给代理商)====
|
||||
// 平台直接销售给终端用户,无代理商参与,使用零售价,无需查询分配配置
|
||||
orderBuyerType = model.BuyerTypePersonal
|
||||
orderBuyerID = 0
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = &now
|
||||
isPurchaseOnBehalf = false
|
||||
sellerCostPrice = 0
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRoleSelfPurchase
|
||||
actualPaidAmount = nil
|
||||
} else {
|
||||
// ==== 子场景 1.2:平台代购(资源属于代理商,平台代为购买)====
|
||||
purchaseBuyerID, buyerCostPriceMap, buyerTotalCost, purchasePaidAt, err := s.resolvePurchaseOnBehalfInfo(ctx, validationResult)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = purchaseBuyerID
|
||||
itemUnitPriceMap = buyerCostPriceMap
|
||||
totalAmount = buyerTotalCost
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = purchasePaidAt
|
||||
isPurchaseOnBehalf = true
|
||||
sellerCostPrice = buyerTotalCost
|
||||
|
||||
// 设置操作者信息(平台代购)
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRolePurchasedByPlatform
|
||||
actualPaidAmount = nil
|
||||
}
|
||||
orderBuyerType = model.BuyerTypeAgent
|
||||
orderBuyerID = purchaseBuyerID
|
||||
itemUnitPriceMap = buyerCostPriceMap
|
||||
totalAmount = buyerTotalCost
|
||||
paymentMethod = model.PaymentMethodOffline
|
||||
paymentStatus = model.PaymentStatusPaid
|
||||
paidAt = purchasePaidAt
|
||||
isPurchaseOnBehalf = true
|
||||
sellerCostPrice = buyerTotalCost
|
||||
|
||||
// 设置操作者信息(平台代购)
|
||||
operatorID = nil
|
||||
operatorType = constants.OwnerTypePlatform
|
||||
purchaseRole = model.PurchaseRolePurchasedByPlatform
|
||||
actualPaidAmount = nil
|
||||
}
|
||||
}
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
@@ -445,6 +446,9 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
|
||||
SellerShopID: sellerShopID,
|
||||
SellerCostPrice: sellerCostPrice,
|
||||
IsPurchaseOnBehalf: isPurchaseOnBehalf,
|
||||
OperatorAccountID: operatorAccountID,
|
||||
OperatorAccountType: operatorAccountType,
|
||||
OperatorAccountName: operatorAccountName,
|
||||
OperatorID: operatorID,
|
||||
OperatorType: operatorType,
|
||||
ActualPaidAmount: actualPaidAmount,
|
||||
@@ -475,7 +479,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// 钱包支付:创建订单、扣款、激活套餐(在事务中完成)
|
||||
@@ -489,7 +493,7 @@ func (s *Service) CreateAdminOrder(ctx context.Context, req *dto.CreateAdminOrde
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
|
||||
} else {
|
||||
// 不应该到这里(DTO 验证已拒绝其他支付方式)
|
||||
@@ -563,6 +567,8 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
|
||||
}
|
||||
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
operatorUserType := middleware.GetUserTypeFromContext(ctx)
|
||||
operatorAccountID, operatorAccountType, operatorAccountName := s.buildAccountOperatorSnapshot(ctx, userID, operatorUserType)
|
||||
|
||||
// 提取资源所属店铺ID
|
||||
var resourceShopID *uint
|
||||
@@ -732,6 +738,9 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
|
||||
SellerShopID: sellerShopID,
|
||||
SellerCostPrice: sellerCostPrice,
|
||||
IsPurchaseOnBehalf: isPurchaseOnBehalf,
|
||||
OperatorAccountID: operatorAccountID,
|
||||
OperatorAccountType: operatorAccountType,
|
||||
OperatorAccountName: operatorAccountName,
|
||||
OperatorID: operatorID,
|
||||
OperatorType: operatorType,
|
||||
ActualPaidAmount: actualPaidAmount,
|
||||
@@ -752,7 +761,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
|
||||
}
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
|
||||
} else if req.PaymentMethod == model.PaymentMethodWallet {
|
||||
// 钱包支付:创建订单、扣款、激活套餐(在事务中完成)
|
||||
@@ -766,7 +775,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
|
||||
} else {
|
||||
// 其他支付方式:创建待支付订单(H5 端支持 wechat/alipay)
|
||||
@@ -777,7 +786,7 @@ func (s *Service) CreateH5Order(ctx context.Context, req *dto.CreateOrderRequest
|
||||
return nil, err
|
||||
}
|
||||
s.markOrderCreated(ctx, idempotencyKey, order.ID)
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,6 +918,57 @@ func (s *Service) fetchBuyerSnapshot(ctx context.Context, customerID uint) (phon
|
||||
return phone, nickname
|
||||
}
|
||||
|
||||
func (s *Service) buildAccountOperatorSnapshot(ctx context.Context, userID uint, userType int) (*uint, string, string) {
|
||||
if userID == 0 {
|
||||
return nil, "", ""
|
||||
}
|
||||
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", userID).First(&account).Error; err != nil {
|
||||
s.logger.Warn("buildAccountOperatorSnapshot: 查询账号失败", zap.Uint("user_id", userID), zap.Error(err))
|
||||
id := userID
|
||||
return &id, orderOperatorAccountTypeFromUserType(userType), ""
|
||||
}
|
||||
|
||||
id := account.ID
|
||||
return &id, orderOperatorAccountTypeFromUserType(account.UserType), account.Username
|
||||
}
|
||||
|
||||
func (s *Service) buildPersonalCustomerOperatorSnapshot(ctx context.Context, customerID uint, nickname *string) (*uint, string, string) {
|
||||
if customerID == 0 {
|
||||
return nil, "", ""
|
||||
}
|
||||
|
||||
name := ""
|
||||
if nickname != nil {
|
||||
name = strings.TrimSpace(*nickname)
|
||||
}
|
||||
if name == "" && s.personalCustomerStore != nil {
|
||||
customer, err := s.personalCustomerStore.GetByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Warn("buildPersonalCustomerOperatorSnapshot: 查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err))
|
||||
} else if customer != nil {
|
||||
name = strings.TrimSpace(customer.Nickname)
|
||||
}
|
||||
}
|
||||
|
||||
id := customerID
|
||||
return &id, model.OperatorAccountTypePersonalCustomer, name
|
||||
}
|
||||
|
||||
func orderOperatorAccountTypeFromUserType(userType int) string {
|
||||
switch userType {
|
||||
case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
|
||||
return model.OperatorAccountTypePlatform
|
||||
case constants.UserTypeAgent:
|
||||
return model.OperatorAccountTypeAgent
|
||||
case constants.UserTypeEnterprise:
|
||||
return model.OperatorAccountTypeEnterprise
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// getCostPrice 查询店铺对套餐的成本价
|
||||
// shopID: 店铺ID
|
||||
// packageID: 套餐ID
|
||||
@@ -1078,9 +1138,8 @@ func (s *Service) createOrderWithWalletPayment(ctx context.Context, order *model
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 事务外:佣金计算(异步)
|
||||
// 仅兼容极少数“平台代购但走钱包事务”的特殊场景,普通钱包订单不在此处入队佣金
|
||||
if order.PurchaseRole == model.PurchaseRolePurchasedByPlatform {
|
||||
// 3. 事务外:所有已支付且适用差价佣金的订单都进入佣金计算
|
||||
if order.CommissionStatus == model.CommissionStatusPending {
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
}
|
||||
|
||||
@@ -1113,7 +1172,7 @@ func (s *Service) Get(ctx context.Context, id uint) (*dto.OrderResponse, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.buildOrderResponse(order, items), nil
|
||||
return s.buildOrderResponse(ctx, order, items), nil
|
||||
}
|
||||
|
||||
func (s *Service) List(ctx context.Context, req *dto.OrderListRequest, buyerType string, buyerID uint) (*dto.OrderListResponse, error) {
|
||||
@@ -1204,7 +1263,7 @@ func (s *Service) List(ctx context.Context, req *dto.OrderListRequest, buyerType
|
||||
|
||||
var list []*dto.OrderResponse
|
||||
for _, o := range orders {
|
||||
list = append(list, s.buildOrderResponse(o, itemsMap[o.ID]))
|
||||
list = append(list, s.buildOrderResponse(ctx, o, itemsMap[o.ID]))
|
||||
}
|
||||
|
||||
return &dto.OrderListResponse{
|
||||
@@ -1478,6 +1537,7 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
|
||||
// 根据资源类型选择对应的钱包系统
|
||||
now := time.Now()
|
||||
actualPaidAmount := order.TotalAmount
|
||||
shouldEnqueueCommission := false
|
||||
|
||||
if resourceType == "shop" {
|
||||
// 代理钱包系统(店铺)
|
||||
@@ -1527,6 +1587,7 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
|
||||
|
||||
actualPaidAmountSnapshot := actualPaidAmount
|
||||
order.ActualPaidAmount = &actualPaidAmountSnapshot
|
||||
shouldEnqueueCommission = true
|
||||
|
||||
walletResult := tx.Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version).
|
||||
@@ -1645,7 +1706,9 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
|
||||
return err
|
||||
}
|
||||
|
||||
s.enqueueCommissionCalculation(ctx, orderID)
|
||||
if shouldEnqueueCommission && order.CommissionStatus == model.CommissionStatusPending {
|
||||
s.enqueueCommissionCalculation(ctx, orderID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1659,6 +1722,8 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
shouldResumeAfterPayment := false
|
||||
shouldEnqueueCommission := false
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.Order{}).
|
||||
Where("id = ? AND payment_status = ?", order.ID, model.PaymentStatusPending).
|
||||
@@ -1694,6 +1759,8 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
|
||||
// 回调更新成功后,同步内存对象,确保激活套餐时写入 paid_amount 快照
|
||||
actualPaidAmountSnapshot := actualPaidAmount
|
||||
order.ActualPaidAmount = &actualPaidAmountSnapshot
|
||||
shouldResumeAfterPayment = true
|
||||
shouldEnqueueCommission = true
|
||||
|
||||
return s.activatePackage(ctx, tx, order)
|
||||
})
|
||||
@@ -1702,8 +1769,12 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, orderNo string, pay
|
||||
return err
|
||||
}
|
||||
|
||||
s.tryResumeAfterPayment(ctx, order)
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
if shouldResumeAfterPayment {
|
||||
s.tryResumeAfterPayment(ctx, order)
|
||||
}
|
||||
if shouldEnqueueCommission && order.CommissionStatus == model.CommissionStatusPending {
|
||||
s.enqueueCommissionCalculation(ctx, order.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2169,24 +2240,24 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
|
||||
Creator: order.Creator,
|
||||
Updater: order.Creator,
|
||||
},
|
||||
OrderID: order.ID,
|
||||
OrderNo: order.OrderNo,
|
||||
PackageID: pkg.ID,
|
||||
PackageName: pkg.PackageName,
|
||||
UsageType: order.OrderType,
|
||||
DataLimitMB: pkg.RealDataMB,
|
||||
OrderID: order.ID,
|
||||
OrderNo: order.OrderNo,
|
||||
PackageID: pkg.ID,
|
||||
PackageName: pkg.PackageName,
|
||||
UsageType: order.OrderType,
|
||||
DataLimitMB: pkg.RealDataMB,
|
||||
VirtualTotalMBSnapshot: virtualTotalMBSnapshot,
|
||||
DisplayGainRatioSnapshot: displayGainRatioSnapshot,
|
||||
EnableVirtualDataSnapshot: enableVirtualDataSnapshot,
|
||||
Status: status,
|
||||
Priority: priority,
|
||||
MasterUsageID: &mainPackage.ID,
|
||||
ActivatedAt: &activatedAt,
|
||||
ExpiresAt: &expiresAt,
|
||||
DataResetCycle: pkg.DataResetCycle,
|
||||
PaidAmount: order.ActualPaidAmount,
|
||||
PackagePriceConfigStatus: pkg.PriceConfigStatus,
|
||||
PackageIsGift: pkg.IsGift,
|
||||
Status: status,
|
||||
Priority: priority,
|
||||
MasterUsageID: &mainPackage.ID,
|
||||
ActivatedAt: &activatedAt,
|
||||
ExpiresAt: &expiresAt,
|
||||
DataResetCycle: pkg.DataResetCycle,
|
||||
PaidAmount: order.ActualPaidAmount,
|
||||
PackagePriceConfigStatus: pkg.PriceConfigStatus,
|
||||
PackageIsGift: pkg.IsGift,
|
||||
}
|
||||
|
||||
if carrierType == "iot_card" {
|
||||
@@ -2223,7 +2294,7 @@ func (s *Service) enqueueCommissionCalculation(ctx context.Context, orderID uint
|
||||
zap.String("task_type", constants.TaskTypeCommission))
|
||||
}
|
||||
|
||||
func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderItem) *dto.OrderResponse {
|
||||
func (s *Service) buildOrderResponse(ctx context.Context, order *model.Order, items []*model.OrderItem) *dto.OrderResponse {
|
||||
var itemResponses []*dto.OrderItemResponse
|
||||
for _, item := range items {
|
||||
itemResponses = append(itemResponses, &dto.OrderItemResponse{
|
||||
@@ -2249,14 +2320,8 @@ func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderIte
|
||||
statusText = "已退款"
|
||||
}
|
||||
|
||||
// 查询操作者名称
|
||||
operatorName := ""
|
||||
if order.OperatorType == "agent" && order.OperatorID != nil {
|
||||
var shop model.Shop
|
||||
if err := s.db.Where("id = ?", *order.OperatorID).First(&shop).Error; err == nil {
|
||||
operatorName = shop.ShopName
|
||||
}
|
||||
}
|
||||
operatorID, operatorType, operatorName := s.resolveOrderOperatorView(ctx, order)
|
||||
sellerShopName := s.resolveSellerShopName(ctx, order.SellerShopID)
|
||||
|
||||
// 生成派生字段
|
||||
isPurchasedByParent := order.PurchaseRole == model.PurchaseRolePurchasedByParent
|
||||
@@ -2288,6 +2353,7 @@ func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderIte
|
||||
actualPaidAmountSnapshot := order.TotalAmount
|
||||
actualPaidAmount = &actualPaidAmountSnapshot
|
||||
}
|
||||
commissionResult := s.resolveOrderCommissionResult(ctx, order)
|
||||
|
||||
return &dto.OrderResponse{
|
||||
ID: order.ID,
|
||||
@@ -2308,10 +2374,14 @@ func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderIte
|
||||
IsPurchaseOnBehalf: order.IsPurchaseOnBehalf,
|
||||
CommissionStatus: order.CommissionStatus,
|
||||
CommissionStatusName: constants.GetOrderCommissionStatusName(order.CommissionStatus),
|
||||
CommissionResult: commissionResult,
|
||||
CommissionResultName: constants.GetOrderCommissionResultName(commissionResult),
|
||||
CommissionConfigVersion: order.CommissionConfigVersion,
|
||||
SellerShopID: order.SellerShopID,
|
||||
SellerShopName: sellerShopName,
|
||||
|
||||
OperatorID: order.OperatorID,
|
||||
OperatorType: order.OperatorType,
|
||||
OperatorID: operatorID,
|
||||
OperatorType: operatorType,
|
||||
OperatorName: operatorName,
|
||||
ActualPaidAmount: actualPaidAmount,
|
||||
|
||||
@@ -2331,6 +2401,90 @@ func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderIte
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) resolveOrderOperatorView(ctx context.Context, order *model.Order) (*uint, string, string) {
|
||||
if order.OperatorAccountID != nil || order.OperatorAccountType != "" || order.OperatorAccountName != "" {
|
||||
return order.OperatorAccountID, order.OperatorAccountType, order.OperatorAccountName
|
||||
}
|
||||
|
||||
if order.Source == constants.OrderSourceClient && order.BuyerType == model.BuyerTypePersonal {
|
||||
operatorID, operatorType, operatorName := s.buildPersonalCustomerOperatorSnapshot(ctx, order.BuyerID, order.BuyerNickname)
|
||||
if operatorID != nil || operatorType != "" || operatorName != "" {
|
||||
return operatorID, operatorType, operatorName
|
||||
}
|
||||
}
|
||||
|
||||
if order.Creator > 0 {
|
||||
operatorID, operatorType, operatorName := s.buildAccountOperatorSnapshot(ctx, order.Creator, 0)
|
||||
if operatorID != nil || operatorType != "" || operatorName != "" {
|
||||
return operatorID, operatorType, operatorName
|
||||
}
|
||||
}
|
||||
|
||||
if order.OperatorType == model.OperatorAccountTypeAgent && order.OperatorID != nil {
|
||||
return nil, model.OperatorAccountTypeAgent, s.resolveLegacyOperatorShopName(ctx, *order.OperatorID)
|
||||
}
|
||||
|
||||
if order.OperatorType == model.OperatorAccountTypePlatform {
|
||||
return nil, model.OperatorAccountTypePlatform, "平台"
|
||||
}
|
||||
|
||||
return nil, "", ""
|
||||
}
|
||||
|
||||
func (s *Service) resolveLegacyOperatorShopName(ctx context.Context, shopID uint) string {
|
||||
if shopID == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var shop model.Shop
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", shopID).First(&shop).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return shop.ShopName
|
||||
}
|
||||
|
||||
func (s *Service) resolveSellerShopName(ctx context.Context, sellerShopID *uint) string {
|
||||
if sellerShopID == nil || *sellerShopID == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var shop model.Shop
|
||||
if err := s.db.WithContext(ctx).Where("id = ?", *sellerShopID).First(&shop).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return shop.ShopName
|
||||
}
|
||||
|
||||
func (s *Service) resolveOrderCommissionResult(ctx context.Context, order *model.Order) int {
|
||||
if order.CommissionResult != model.CommissionResultUnknown {
|
||||
return order.CommissionResult
|
||||
}
|
||||
|
||||
if order.CommissionStatus == model.CommissionStatusPending {
|
||||
return model.CommissionResultUnknown
|
||||
}
|
||||
|
||||
var pendingReviewCount int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.CommissionRecord{}).
|
||||
Where("order_id = ? AND status = ?", order.ID, constants.CommissionStatusPendingReview).
|
||||
Count(&pendingReviewCount).Error; err == nil && pendingReviewCount > 0 {
|
||||
return model.CommissionResultChainBroken
|
||||
}
|
||||
|
||||
var releasedCount int64
|
||||
if err := s.db.WithContext(ctx).Model(&model.CommissionRecord{}).
|
||||
Where("order_id = ? AND status = ? AND amount > 0", order.ID, constants.CommissionStatusReleased).
|
||||
Count(&releasedCount).Error; err == nil && releasedCount > 0 {
|
||||
return model.CommissionResultHasAmount
|
||||
}
|
||||
|
||||
if order.CommissionStatus == model.CommissionStatusCompleted {
|
||||
return model.CommissionResultNoAmount
|
||||
}
|
||||
|
||||
return model.CommissionResultUnknown
|
||||
}
|
||||
|
||||
// WechatPayJSAPI 发起微信 JSAPI 支付
|
||||
// 通过 order.PaymentConfigID 动态加载支付配置,支持多商户场景
|
||||
func (s *Service) WechatPayJSAPI(ctx context.Context, orderID uint, openID string, buyerType string, buyerID uint) (*dto.WechatPayJSAPIResponse, error) {
|
||||
|
||||
@@ -627,7 +627,7 @@ func (s *Service) CreateWithdrawalRequest(ctx context.Context, shopID uint, req
|
||||
ActualAmount: actualAmount,
|
||||
WithdrawalMethod: req.WithdrawalMethod,
|
||||
AccountInfo: accountInfoJSON,
|
||||
Status: 1, // 待审核
|
||||
Status: constants.WithdrawalStatusPending, // 待审核
|
||||
}
|
||||
withdrawalRequest.Creator = currentUserID
|
||||
withdrawalRequest.Updater = currentUserID
|
||||
|
||||
@@ -45,8 +45,7 @@ func (s *OrderStore) Create(ctx context.Context, order *model.Order, items []*mo
|
||||
func (s *OrderStore) GetByID(ctx context.Context, id uint) (*model.Order, error) {
|
||||
var order model.Order
|
||||
query := s.db.WithContext(ctx).Where("id = ?", id)
|
||||
// 应用数据权限过滤(使用 seller_shop_id 字段)
|
||||
query = middleware.ApplySellerShopFilter(ctx, query)
|
||||
query = s.applyOrderScope(ctx, query)
|
||||
if err := query.First(&order).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,8 +55,7 @@ func (s *OrderStore) GetByID(ctx context.Context, id uint) (*model.Order, error)
|
||||
func (s *OrderStore) GetByIDWithItems(ctx context.Context, id uint) (*model.Order, []*model.OrderItem, error) {
|
||||
var order model.Order
|
||||
query := s.db.WithContext(ctx).Where("id = ?", id)
|
||||
// 应用数据权限过滤(使用 seller_shop_id 字段)
|
||||
query = middleware.ApplySellerShopFilter(ctx, query)
|
||||
query = s.applyOrderScope(ctx, query)
|
||||
if err := query.First(&order).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -73,8 +71,7 @@ func (s *OrderStore) GetByIDWithItems(ctx context.Context, id uint) (*model.Orde
|
||||
func (s *OrderStore) GetByOrderNo(ctx context.Context, orderNo string) (*model.Order, error) {
|
||||
var order model.Order
|
||||
query := s.db.WithContext(ctx).Where("order_no = ?", orderNo)
|
||||
// 应用数据权限过滤(使用 seller_shop_id 字段)
|
||||
query = middleware.ApplySellerShopFilter(ctx, query)
|
||||
query = s.applyOrderScope(ctx, query)
|
||||
if err := query.First(&order).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,17 +88,7 @@ func (s *OrderStore) List(ctx context.Context, opts *store.QueryOptions, filters
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Order{})
|
||||
|
||||
// 应用数据权限过滤
|
||||
// 代理用户:可以查看作为买家或操作者的订单
|
||||
// 平台用户/超管:可以查看所有订单
|
||||
subordinateShopIDs := middleware.GetSubordinateShopIDs(ctx)
|
||||
if len(subordinateShopIDs) > 0 {
|
||||
// 代理用户:WHERE (buyer_type = 'agent' AND buyer_id IN ?) OR operator_id IN ?
|
||||
query = query.Where(
|
||||
s.db.Where("buyer_type = ? AND buyer_id IN ?", model.BuyerTypeAgent, subordinateShopIDs).
|
||||
Or("operator_id IN ?", subordinateShopIDs),
|
||||
)
|
||||
}
|
||||
query = s.applyOrderScope(ctx, query)
|
||||
|
||||
if v, ok := filters["payment_status"]; ok {
|
||||
query = query.Where("payment_status = ?", v)
|
||||
@@ -210,6 +197,19 @@ func (s *OrderStore) UpdatePaymentStatus(ctx context.Context, id uint, status in
|
||||
return s.db.WithContext(ctx).Model(&model.Order{}).Where("id = ?", id).Updates(updates).Error
|
||||
}
|
||||
|
||||
func (s *OrderStore) applyOrderScope(ctx context.Context, query *gorm.DB) *gorm.DB {
|
||||
shopIDs := middleware.GetSubordinateShopIDs(ctx)
|
||||
if len(shopIDs) == 0 {
|
||||
return query
|
||||
}
|
||||
|
||||
return query.Where(
|
||||
s.db.Where("buyer_type = ? AND buyer_id IN ?", model.BuyerTypeAgent, shopIDs).
|
||||
Or("seller_shop_id IN ?", shopIDs).
|
||||
Or(s.db.Where("seller_shop_id IS NULL AND operator_type = ? AND operator_id IN ?", model.OperatorAccountTypeAgent, shopIDs)),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *OrderStore) GenerateOrderNo() string {
|
||||
now := time.Now()
|
||||
randomNum := rand.Intn(1000000)
|
||||
|
||||
@@ -192,7 +192,7 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
order, orderItems, buildErr := h.buildOrderAndItems(rechargeOrder, packages, totalAmount, seriesID, sellerCostPrice, now)
|
||||
order, orderItems, buildErr := h.buildOrderAndItems(ctx, rechargeOrder, packages, totalAmount, seriesID, sellerCostPrice, now)
|
||||
if buildErr != nil {
|
||||
return buildErr
|
||||
}
|
||||
@@ -352,6 +352,7 @@ func (h *AutoPurchaseHandler) loadPackages(ctx context.Context, packageIDs []uin
|
||||
}
|
||||
|
||||
func (h *AutoPurchaseHandler) buildOrderAndItems(
|
||||
ctx context.Context,
|
||||
rechargeOrder *model.RechargeOrder,
|
||||
packages []*model.Package,
|
||||
totalAmount int64,
|
||||
@@ -376,6 +377,7 @@ func (h *AutoPurchaseHandler) buildOrderAndItems(
|
||||
}
|
||||
|
||||
paidAmount := totalAmount
|
||||
operatorAccountID, operatorAccountName := h.buildPersonalCustomerOperatorSnapshot(ctx, rechargeOrder.UserID)
|
||||
order := &model.Order{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: rechargeOrder.UserID,
|
||||
@@ -396,6 +398,9 @@ func (h *AutoPurchaseHandler) buildOrderAndItems(
|
||||
Source: constants.OrderSourceClient,
|
||||
Generation: generation,
|
||||
ActualPaidAmount: &paidAmount,
|
||||
OperatorAccountID: operatorAccountID,
|
||||
OperatorAccountType: model.OperatorAccountTypePersonalCustomer,
|
||||
OperatorAccountName: operatorAccountName,
|
||||
SellerShopID: sellerShopID,
|
||||
SeriesID: seriesID,
|
||||
SellerCostPrice: sellerCostPrice,
|
||||
@@ -422,6 +427,21 @@ func (h *AutoPurchaseHandler) buildOrderAndItems(
|
||||
return order, items, nil
|
||||
}
|
||||
|
||||
func (h *AutoPurchaseHandler) buildPersonalCustomerOperatorSnapshot(ctx context.Context, customerID uint) (*uint, string) {
|
||||
if customerID == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
var customer model.PersonalCustomer
|
||||
if err := h.db.WithContext(ctx).Where("id = ?", customerID).First(&customer).Error; err != nil {
|
||||
id := customerID
|
||||
return &id, ""
|
||||
}
|
||||
|
||||
id := customerID
|
||||
return &id, customer.Nickname
|
||||
}
|
||||
|
||||
func (h *AutoPurchaseHandler) activatePackages(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
@@ -596,25 +616,25 @@ func (h *AutoPurchaseHandler) activateAddonPackage(
|
||||
Creator: order.Creator,
|
||||
Updater: order.Creator,
|
||||
},
|
||||
OrderID: order.ID,
|
||||
OrderNo: order.OrderNo,
|
||||
PackageID: pkg.ID,
|
||||
PackageName: pkg.PackageName,
|
||||
UsageType: order.OrderType,
|
||||
DataLimitMB: pkg.RealDataMB,
|
||||
OrderID: order.ID,
|
||||
OrderNo: order.OrderNo,
|
||||
PackageID: pkg.ID,
|
||||
PackageName: pkg.PackageName,
|
||||
UsageType: order.OrderType,
|
||||
DataLimitMB: pkg.RealDataMB,
|
||||
VirtualTotalMBSnapshot: virtualTotalMBSnapshot,
|
||||
DisplayGainRatioSnapshot: displayGainRatioSnapshot,
|
||||
EnableVirtualDataSnapshot: enableVirtualDataSnapshot,
|
||||
Status: constants.PackageUsageStatusActive,
|
||||
Priority: priority,
|
||||
MasterUsageID: &mainPackage.ID,
|
||||
ActivatedAt: &now,
|
||||
ExpiresAt: expiresAt,
|
||||
DataResetCycle: pkg.DataResetCycle,
|
||||
Generation: order.Generation,
|
||||
PaidAmount: order.ActualPaidAmount,
|
||||
PackagePriceConfigStatus: pkg.PriceConfigStatus,
|
||||
PackageIsGift: pkg.IsGift,
|
||||
Status: constants.PackageUsageStatusActive,
|
||||
Priority: priority,
|
||||
MasterUsageID: &mainPackage.ID,
|
||||
ActivatedAt: &now,
|
||||
ExpiresAt: expiresAt,
|
||||
DataResetCycle: pkg.DataResetCycle,
|
||||
Generation: order.Generation,
|
||||
PaidAmount: order.ActualPaidAmount,
|
||||
PackagePriceConfigStatus: pkg.PriceConfigStatus,
|
||||
PackageIsGift: pkg.IsGift,
|
||||
}
|
||||
|
||||
if carrierType == constants.AssetWalletResourceTypeIotCard {
|
||||
|
||||
Reference in New Issue
Block a user