package purchase_validation import ( "context" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "gorm.io/gorm" ) type Service struct { db *gorm.DB iotCardStore *postgres.IotCardStore deviceStore *postgres.DeviceStore packageStore *postgres.PackageStore packageAllocationStore *postgres.ShopPackageAllocationStore } func New( db *gorm.DB, iotCardStore *postgres.IotCardStore, deviceStore *postgres.DeviceStore, packageStore *postgres.PackageStore, packageAllocationStore *postgres.ShopPackageAllocationStore, ) *Service { return &Service{ db: db, iotCardStore: iotCardStore, deviceStore: deviceStore, packageStore: packageStore, packageAllocationStore: packageAllocationStore, } } type PurchaseValidationResult struct { Card *model.IotCard Device *model.Device Packages []*model.Package TotalPrice int64 } func (s *Service) ValidateCardPurchase(ctx context.Context, cardID uint, packageIDs []uint) (*PurchaseValidationResult, error) { card, err := s.iotCardStore.GetByID(ctx, cardID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在") } return nil, err } if card.SeriesID == nil || *card.SeriesID == 0 { return nil, errors.New(errors.CodeInvalidParam, "该卡未关联套餐系列,无法购买套餐") } // 确定卖家店铺ID:卡所属店铺即为卖家(代理渠道),平台自营时为0 var sellerShopID uint if card.ShopID != nil { sellerShopID = *card.ShopID } packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, sellerShopID) if err != nil { return nil, err } return &PurchaseValidationResult{ Card: card, Packages: packages, TotalPrice: totalPrice, }, nil } func (s *Service) ValidateDevicePurchase(ctx context.Context, deviceID uint, packageIDs []uint) (*PurchaseValidationResult, error) { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "设备不存在") } return nil, err } if device.SeriesID == nil || *device.SeriesID == 0 { return nil, errors.New(errors.CodeInvalidParam, "该设备未关联套餐系列,无法购买套餐") } // 确定卖家店铺ID:设备所属店铺即为卖家(代理渠道),平台自营时为0 var sellerShopID uint if device.ShopID != nil { sellerShopID = *device.ShopID } packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, sellerShopID) if err != nil { return nil, err } return &PurchaseValidationResult{ Device: device, Packages: packages, TotalPrice: totalPrice, }, nil } // 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) { if len(packageIDs) == 0 { return nil, 0, errors.New(errors.CodeInvalidParam, "请选择至少一个套餐") } var packages []*model.Package var totalPrice int64 for _, pkgID := range packageIDs { pkg, err := s.packageStore.GetByID(ctx, pkgID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, 0, errors.New(errors.CodeInvalidParam, "套餐不存在") } return nil, 0, err } if pkg.SeriesID != seriesID { return nil, 0, errors.New(errors.CodeInvalidParam, "套餐不在可购买范围内") } // Package.status 为全局开关,任何渠道都必须检查 if pkg.Status != constants.StatusEnabled { return nil, 0, errors.New(errors.CodeInvalidParam, "套餐已禁用") } if sellerShopID > 0 { // 代理渠道:检查卖家代理的 allocation.shelf_status,不检查 package.shelf_status if err := s.validateAgentShelfStatus(ctx, sellerShopID, pkgID); err != nil { return nil, 0, err } } else { // 平台自营渠道:检查 package.shelf_status if pkg.ShelfStatus != constants.ShelfStatusOn { return nil, 0, errors.New(errors.CodeInvalidParam, "套餐已下架") } } packages = append(packages, pkg) totalPrice += pkg.SuggestedRetailPrice } return packages, totalPrice, nil } // validateAgentShelfStatus 校验卖家代理的分配记录上架状态 func (s *Service) validateAgentShelfStatus(ctx context.Context, sellerShopID, packageID uint) error { // 使用不带数据权限过滤的查询,避免 buyer ctx 的权限限制干扰系统级校验 allocation, err := s.packageAllocationStore.GetByShopAndPackageForSystem(ctx, sellerShopID, packageID) if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeInvalidParam, "套餐已下架") } return errors.Wrap(errors.CodeInternalError, err, "查询套餐分配记录失败") } if allocation.ShelfStatus != constants.ShelfStatusOn { return errors.New(errors.CodeInvalidParam, "套餐已下架") } return nil } func (s *Service) GetPurchasePrice(ctx context.Context, pkg *model.Package, buyerType string) int64 { return pkg.SuggestedRetailPrice } // ValidateAdminOfflineCardPurchase 后台 offline 订单专用卡验证 // 绕过代理 Allocation 上架检查,仅验证套餐全局状态 func (s *Service) ValidateAdminOfflineCardPurchase(ctx context.Context, cardID uint, packageIDs []uint) (*PurchaseValidationResult, error) { card, err := s.iotCardStore.GetByID(ctx, cardID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeIotCardNotFound, "IoT卡不存在") } return nil, err } if card.SeriesID == nil || *card.SeriesID == 0 { return nil, errors.New(errors.CodeInvalidParam, "该卡未关联套餐系列,无法购买套餐") } packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *card.SeriesID, 0) if err != nil { return nil, err } return &PurchaseValidationResult{ Card: card, Packages: packages, TotalPrice: totalPrice, }, nil } // ValidateAdminOfflineDevicePurchase 后台 offline 订单专用设备验证 // 绕过代理 Allocation 上架检查,仅验证套餐全局状态 func (s *Service) ValidateAdminOfflineDevicePurchase(ctx context.Context, deviceID uint, packageIDs []uint) (*PurchaseValidationResult, error) { device, err := s.deviceStore.GetByID(ctx, deviceID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "设备不存在") } return nil, err } if device.SeriesID == nil || *device.SeriesID == 0 { return nil, errors.New(errors.CodeInvalidParam, "该设备未关联套餐系列,无法购买套餐") } packages, totalPrice, err := s.validatePackages(ctx, packageIDs, *device.SeriesID, 0) if err != nil { return nil, err } return &PurchaseValidationResult{ Device: device, Packages: packages, TotalPrice: totalPrice, }, nil }