fix(force-recharge): 补充强充配置缺失的接口和数据库字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m19s

- 订单管理:增加 payment_method 字段支持,合并代购订单逻辑
- 套餐系列分配:增加强充配置字段(enable_force_recharge、force_recharge_amount、force_recharge_trigger_type)
- 数据库迁移:添加 force_recharge_trigger_type 字段
- 测试:更新订单服务测试用例
- OpenSpec:归档 fix-force-recharge-missing-interfaces 变更
This commit is contained in:
2026-01-31 15:34:32 +08:00
parent d309951493
commit d81bd242a4
21 changed files with 1090 additions and 388 deletions

View File

@@ -95,6 +95,14 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
userID := middleware.GetUserIDFromContext(ctx)
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
orderBuyerType := buyerType
orderBuyerID := buyerID
totalAmount := validationResult.TotalPrice
paymentMethod := req.PaymentMethod
paymentStatus := model.PaymentStatusPending
var paidAt *time.Time
isPurchaseOnBehalf := false
var seriesID *uint
var sellerShopID *uint
var sellerCostPrice int64
@@ -102,6 +110,22 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
if validationResult.Allocation != nil {
seriesID = &validationResult.Allocation.SeriesID
sellerShopID = &validationResult.Allocation.ShopID
}
if req.PaymentMethod == model.PaymentMethodOffline {
purchaseBuyerID, buyerCostPrice, purchasePaidAt, err := s.resolvePurchaseOnBehalfInfo(ctx, validationResult)
if err != nil {
return nil, err
}
orderBuyerType = model.BuyerTypeAgent
orderBuyerID = purchaseBuyerID
totalAmount = buyerCostPrice
paymentMethod = model.PaymentMethodOffline
paymentStatus = model.PaymentStatusPaid
paidAt = purchasePaidAt
isPurchaseOnBehalf = true
sellerCostPrice = buyerCostPrice
} else if validationResult.Allocation != nil {
sellerCostPrice = utils.CalculateCostPrice(validationResult.Allocation, validationResult.TotalPrice)
}
@@ -112,25 +136,68 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
},
OrderNo: s.orderStore.GenerateOrderNo(),
OrderType: req.OrderType,
BuyerType: buyerType,
BuyerID: buyerID,
BuyerType: orderBuyerType,
BuyerID: orderBuyerID,
IotCardID: req.IotCardID,
DeviceID: req.DeviceID,
TotalAmount: validationResult.TotalPrice,
PaymentStatus: model.PaymentStatusPending,
TotalAmount: totalAmount,
PaymentMethod: paymentMethod,
PaymentStatus: paymentStatus,
PaidAt: paidAt,
CommissionStatus: model.CommissionStatusPending,
CommissionConfigVersion: configVersion,
SeriesID: seriesID,
SellerShopID: sellerShopID,
SellerCostPrice: sellerCostPrice,
IsPurchaseOnBehalf: isPurchaseOnBehalf,
}
var items []*model.OrderItem
for _, pkg := range validationResult.Packages {
items := s.buildOrderItems(userID, validationResult.Packages)
if req.PaymentMethod == model.PaymentMethodOffline {
if err := s.createOrderWithActivation(ctx, order, items); err != nil {
return nil, err
}
s.enqueueCommissionCalculation(ctx, order.ID)
return s.buildOrderResponse(order, items), nil
}
if err := s.orderStore.Create(ctx, order, items); err != nil {
return nil, err
}
return s.buildOrderResponse(order, items), nil
}
func (s *Service) resolvePurchaseOnBehalfInfo(ctx context.Context, result *purchase_validation.PurchaseValidationResult) (uint, int64, *time.Time, error) {
var resourceShopID *uint
if result.Card != nil {
resourceShopID = result.Card.ShopID
} else if result.Device != nil {
resourceShopID = result.Device.ShopID
}
if resourceShopID == nil || *resourceShopID == 0 {
return 0, 0, nil, errors.New(errors.CodeInvalidParam, "资源未分配给代理商,无法代购")
}
buyerAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, *resourceShopID, result.Allocation.SeriesID)
if err != nil {
return 0, 0, nil, errors.New(errors.CodeInvalidParam, "买家没有该套餐系列的分配配置")
}
buyerCostPrice := utils.CalculateCostPrice(buyerAllocation, result.TotalPrice)
now := time.Now()
return *resourceShopID, buyerCostPrice, &now, nil
}
func (s *Service) buildOrderItems(operatorID uint, packages []*model.Package) []*model.OrderItem {
items := make([]*model.OrderItem, 0, len(packages))
for _, pkg := range packages {
item := &model.OrderItem{
BaseModel: model.BaseModel{
Creator: userID,
Updater: userID,
Creator: operatorID,
Updater: operatorID,
},
PackageID: pkg.ID,
PackageName: pkg.PackageName,
@@ -141,11 +208,24 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, buyer
items = append(items, item)
}
if err := s.orderStore.Create(ctx, order, items); err != nil {
return nil, err
}
return items
}
return s.buildOrderResponse(order, items), nil
func (s *Service) createOrderWithActivation(ctx context.Context, order *model.Order, items []*model.OrderItem) error {
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(order).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建代购订单失败")
}
for _, item := range items {
item.OrderID = order.ID
}
if err := tx.CreateInBatches(items, 100).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败")
}
return s.activatePackage(ctx, tx, order)
})
}
func (s *Service) Get(ctx context.Context, id uint) (*dto.OrderResponse, error) {
@@ -544,6 +624,7 @@ func (s *Service) buildOrderResponse(order *model.Order, items []*model.OrderIte
PaymentStatus: order.PaymentStatus,
PaymentStatusText: statusText,
PaidAt: order.PaidAt,
IsPurchaseOnBehalf: order.IsPurchaseOnBehalf,
CommissionStatus: order.CommissionStatus,
CommissionConfigVersion: order.CommissionConfigVersion,
Items: itemResponses,
@@ -751,119 +832,3 @@ func (s *Service) GetPurchaseCheck(ctx context.Context, req *dto.PurchaseCheckRe
return response, nil
}
func (s *Service) CreatePurchaseOnBehalf(ctx context.Context, req *dto.CreatePurchaseOnBehalfRequest, operatorID uint) (*dto.OrderResponse, error) {
var validationResult *purchase_validation.PurchaseValidationResult
var resourceShopID *uint
var err error
if req.OrderType == model.OrderTypeSingleCard {
if req.IotCardID == nil {
return nil, errors.New(errors.CodeInvalidParam, "单卡购买必须指定IoT卡ID")
}
validationResult, err = s.purchaseValidationService.ValidateCardPurchase(ctx, *req.IotCardID, req.PackageIDs)
if err != nil {
return nil, err
}
if validationResult.Card != nil {
resourceShopID = validationResult.Card.ShopID
}
} else if req.OrderType == model.OrderTypeDevice {
if req.DeviceID == nil {
return nil, errors.New(errors.CodeInvalidParam, "设备购买必须指定设备ID")
}
validationResult, err = s.purchaseValidationService.ValidateDevicePurchase(ctx, *req.DeviceID, req.PackageIDs)
if err != nil {
return nil, err
}
if validationResult.Device != nil {
resourceShopID = validationResult.Device.ShopID
}
} else {
return nil, errors.New(errors.CodeInvalidParam, "无效的订单类型")
}
if resourceShopID == nil || *resourceShopID == 0 {
return nil, errors.New(errors.CodeInvalidParam, "资源未分配给代理商,无法代购")
}
buyerID := *resourceShopID
buyerAllocation, err := s.seriesAllocationStore.GetByShopAndSeries(ctx, buyerID, validationResult.Allocation.SeriesID)
if err != nil {
return nil, errors.New(errors.CodeInvalidParam, "买家没有该套餐系列的分配配置")
}
buyerCostPrice := utils.CalculateCostPrice(buyerAllocation, validationResult.TotalPrice)
configVersion := s.snapshotCommissionConfig(ctx, validationResult.Allocation.ID)
var seriesID *uint
var sellerShopID *uint
if validationResult.Allocation != nil {
seriesID = &validationResult.Allocation.SeriesID
sellerShopID = &validationResult.Allocation.ShopID
}
now := time.Now()
order := &model.Order{
BaseModel: model.BaseModel{
Creator: operatorID,
Updater: operatorID,
},
OrderNo: s.orderStore.GenerateOrderNo(),
OrderType: req.OrderType,
BuyerType: model.BuyerTypeAgent,
BuyerID: buyerID,
IotCardID: req.IotCardID,
DeviceID: req.DeviceID,
TotalAmount: buyerCostPrice,
PaymentMethod: model.PaymentMethodOffline,
PaymentStatus: model.PaymentStatusPaid,
PaidAt: &now,
CommissionStatus: model.CommissionStatusPending,
CommissionConfigVersion: configVersion,
SeriesID: seriesID,
SellerShopID: sellerShopID,
SellerCostPrice: buyerCostPrice,
IsPurchaseOnBehalf: true,
}
var items []*model.OrderItem
for _, pkg := range validationResult.Packages {
item := &model.OrderItem{
BaseModel: model.BaseModel{
Creator: operatorID,
Updater: operatorID,
},
PackageID: pkg.ID,
PackageName: pkg.PackageName,
Quantity: 1,
UnitPrice: pkg.SuggestedRetailPrice,
Amount: pkg.SuggestedRetailPrice,
}
items = append(items, item)
}
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Create(order).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建代购订单失败")
}
for _, item := range items {
item.OrderID = order.ID
}
if err := tx.CreateInBatches(items, 100).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "创建订单明细失败")
}
return s.activatePackage(ctx, tx, order)
})
if err != nil {
return nil, err
}
s.enqueueCommissionCalculation(ctx, order.ID)
return s.buildOrderResponse(order, items), nil
}