七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s

This commit is contained in:
2026-07-25 17:06:58 +08:00
parent ad9f613dd6
commit 73f5125d3d
249 changed files with 17137 additions and 877 deletions

View File

@@ -57,6 +57,15 @@ type PurchaseValidationResult struct {
}
func (s *Service) ValidateCardPurchase(ctx context.Context, cardID uint, packageIDs []uint) (*PurchaseValidationResult, error) {
return s.validateCardPurchase(ctx, cardID, packageIDs, false)
}
// ValidatePersonalCardPurchase 校验个人客户卡套餐购买,并允许当前世代生效中套餐续费。
func (s *Service) ValidatePersonalCardPurchase(ctx context.Context, cardID uint, packageIDs []uint) (*PurchaseValidationResult, error) {
return s.validateCardPurchase(ctx, cardID, packageIDs, true)
}
func (s *Service) validateCardPurchase(ctx context.Context, cardID uint, packageIDs []uint, allowRenewal bool) (*PurchaseValidationResult, error) {
card, err := s.iotCardStore.GetByID(ctx, cardID)
if err != nil {
if err == gorm.ErrRecordNotFound {
@@ -79,7 +88,11 @@ func (s *Service) ValidateCardPurchase(ctx context.Context, cardID uint, package
sellerShopID = *card.ShopID
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, sellerShopID)
renewablePackageIDs, err := s.loadRenewablePackageIDs(ctx, constants.AssetWalletResourceTypeIotCard, card.ID, card.Generation, packageIDs, allowRenewal)
if err != nil {
return nil, err
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, sellerShopID, renewablePackageIDs)
if err != nil {
return nil, err
}
@@ -96,6 +109,15 @@ func (s *Service) ValidateCardPurchase(ctx context.Context, cardID uint, package
}
func (s *Service) ValidateDevicePurchase(ctx context.Context, deviceID uint, packageIDs []uint) (*PurchaseValidationResult, error) {
return s.validateDevicePurchase(ctx, deviceID, packageIDs, false)
}
// ValidatePersonalDevicePurchase 校验个人客户设备套餐购买,并允许当前世代生效中套餐续费。
func (s *Service) ValidatePersonalDevicePurchase(ctx context.Context, deviceID uint, packageIDs []uint) (*PurchaseValidationResult, error) {
return s.validateDevicePurchase(ctx, deviceID, packageIDs, true)
}
func (s *Service) validateDevicePurchase(ctx context.Context, deviceID uint, packageIDs []uint, allowRenewal bool) (*PurchaseValidationResult, error) {
device, err := s.deviceStore.GetByID(ctx, deviceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
@@ -114,7 +136,11 @@ func (s *Service) ValidateDevicePurchase(ctx context.Context, deviceID uint, pac
sellerShopID = *device.ShopID
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, sellerShopID)
renewablePackageIDs, err := s.loadRenewablePackageIDs(ctx, constants.AssetWalletResourceTypeDevice, device.ID, device.Generation, packageIDs, allowRenewal)
if err != nil {
return nil, err
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, sellerShopID, renewablePackageIDs)
if err != nil {
return nil, err
}
@@ -133,7 +159,7 @@ func (s *Service) ValidateDevicePurchase(ctx context.Context, deviceID uint, pac
// validatePackages 验证套餐列表是否可购买
// sellerShopID > 0 表示代理渠道,校验该代理的 allocation.shelf_status
// sellerShopID == 0 表示平台自营渠道,校验 package.shelf_status
func (s *Service) validatePackages(ctx context.Context, packageIDs []uint, seriesID uint, sellerShopID uint) ([]*model.Package, int64, error) {
func (s *Service) validatePackages(ctx context.Context, packageIDs []uint, seriesID uint, sellerShopID uint, renewablePackageIDs map[uint]struct{}) ([]*model.Package, int64, error) {
if len(packageIDs) == 0 {
return nil, 0, errors.New(errors.CodeInvalidParam, "请选择至少一个套餐")
}
@@ -164,9 +190,10 @@ func (s *Service) validatePackages(ctx context.Context, packageIDs []uint, serie
return nil, 0, errors.New(errors.CodeInvalidParam, "套餐已禁用")
}
_, canRenewOffShelf := renewablePackageIDs[pkgID]
if sellerShopID > 0 {
// 代理渠道:检查上架状态并获取分配记录,使用零售价
allocation, allocErr := s.validateAgentAllocation(ctx, sellerShopID, pkgID)
allocation, allocErr := s.validateAgentAllocation(ctx, sellerShopID, pkgID, canRenewOffShelf)
if allocErr != nil {
return nil, 0, allocErr
}
@@ -177,7 +204,7 @@ func (s *Service) validatePackages(ctx context.Context, packageIDs []uint, serie
}
totalPrice += effectiveRetailPrice
} else {
if pkg.ShelfStatus != constants.ShelfStatusOn {
if pkg.ShelfStatus != constants.ShelfStatusOn && !canRenewOffShelf {
return nil, 0, errors.New(errors.CodeInvalidParam, "套餐已下架")
}
totalPrice += packageprice.PackageEffectiveRetailPrice(pkg)
@@ -230,7 +257,7 @@ func (s *Service) validatePackageUsageRules(ctx context.Context, carrierType str
}
// validateAgentAllocation 校验卖家代理的分配记录上架状态,并返回分配记录
func (s *Service) validateAgentAllocation(ctx context.Context, sellerShopID, packageID uint) (*model.ShopPackageAllocation, error) {
func (s *Service) validateAgentAllocation(ctx context.Context, sellerShopID, packageID uint, allowOffShelf bool) (*model.ShopPackageAllocation, error) {
allocation, err := s.packageAllocationStore.GetByShopAndPackageForSystem(ctx, sellerShopID, packageID)
if err != nil {
if err == gorm.ErrRecordNotFound {
@@ -239,13 +266,44 @@ func (s *Service) validateAgentAllocation(ctx context.Context, sellerShopID, pac
return nil, errors.Wrap(errors.CodeInternalError, err, "查询套餐分配记录失败")
}
if allocation.ShelfStatus != constants.ShelfStatusOn {
if allocation.Status != constants.StatusEnabled {
return nil, errors.New(errors.CodeInvalidParam, "套餐已禁用")
}
if allocation.ShelfStatus != constants.ShelfStatusOn && !allowOffShelf {
return nil, errors.New(errors.CodeInvalidParam, "套餐已下架")
}
return allocation, nil
}
func (s *Service) loadRenewablePackageIDs(ctx context.Context, assetType string, assetID uint, generation int, packageIDs []uint, allowRenewal bool) (map[uint]struct{}, error) {
result := make(map[uint]struct{})
if !allowRenewal || len(packageIDs) == 0 {
return result, nil
}
query := s.db.WithContext(ctx).Model(&model.PackageUsage{}).
Distinct("package_id").
Where("generation = ? AND status = ? AND refund_id IS NULL AND package_id IN ?", generation, constants.PackageUsageStatusActive, packageIDs)
switch assetType {
case constants.AssetWalletResourceTypeIotCard:
query = query.Where("usage_type = ? AND iot_card_id = ?", constants.PackageUsageTypeSingleCard, assetID)
case constants.AssetWalletResourceTypeDevice:
query = query.Where("usage_type = ? AND device_id = ?", constants.AssetWalletResourceTypeDevice, assetID)
default:
return nil, errors.New(errors.CodeInvalidParam)
}
var ids []uint
if err := query.Pluck("package_id", &ids).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐续费资格失败")
}
for _, id := range ids {
result[id] = struct{}{}
}
return result, nil
}
// GetPurchasePrice 获取购买价格
// 代理渠道sellerShopID > 0返回 allocation.RetailPrice平台渠道返回 Package.SuggestedRetailPrice
func (s *Service) GetPurchasePrice(ctx context.Context, pkg *model.Package, sellerShopID uint) (int64, error) {
@@ -297,7 +355,7 @@ func (s *Service) ValidateAdminOfflineCardPurchase(ctx context.Context, cardID u
return nil, errors.New(errors.CodeInvalidParam, "该卡未关联套餐系列,无法购买套餐")
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, 0)
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, 0, nil)
if err != nil {
return nil, err
}
@@ -328,7 +386,7 @@ func (s *Service) ValidateAdminOfflineDevicePurchase(ctx context.Context, device
return nil, errors.New(errors.CodeInvalidParam, "该设备未关联套餐系列,无法购买套餐")
}
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, 0)
packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, 0, nil)
if err != nil {
return nil, err
}