实现套餐生效条件覆盖与购买快照

This commit is contained in:
2026-07-22 20:51:12 +09:00
parent c7f8b4c702
commit 9818537239
30 changed files with 1092 additions and 149 deletions

View File

@@ -8,6 +8,7 @@ import (
"strings"
"time"
packagedomain "github.com/break/junhong_cmp_fiber/internal/domain/package"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
@@ -2222,6 +2223,11 @@ func (s *Service) markOrderCreated(ctx context.Context, idempotencyKey string, o
// activateMainPackage 任务 8.2-8.4: 主套餐激活逻辑
func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *model.Order, pkg *model.Package, carrierType string, carrierID uint, now time.Time) error {
terms, err := s.resolvePackageTerms(ctx, pkg, order.SellerShopID)
if err != nil {
s.logger.Error("生成套餐计时快照失败", zap.Uint("package_id", pkg.ID), zap.Error(err))
return err
}
if err := s.lockPackageCarrier(ctx, tx, carrierType, carrierID); err != nil {
return err
}
@@ -2252,19 +2258,17 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
priority = 1
activatedAt = now
// 使用工具函数计算过期时间
expiresAt = packagepkg.CalculateExpiryTime(pkg.CalendarType, activatedAt, pkg.DurationMonths, pkg.DurationDays)
expiresAt = packagepkg.CalculateExpiryTime(terms.CalendarType, activatedAt, terms.DurationMonths, terms.DurationDays)
// 计算下次重置时间(基于套餐周期类型)
nextResetAt = packagepkg.CalculateNextResetTime(pkg.DataResetCycle, pkg.CalendarType, now, activatedAt)
nextResetAt = packagepkg.CalculateNextResetTime(pkg.DataResetCycle, terms.CalendarType, now, activatedAt)
// REALNAME-02: 后台囤货场景三维决策(卡类型 + 购买路径 + expiry_base + 实名状态)
// 查询卡类型(行业卡永远直接激活,不等实名)
// 同时查询载体当前实名状态:已实名则跳过 pending直接激活
var cardCategory string
var currentlyRealnamed bool
if carrierType == "iot_card" {
var card model.IotCard
if err := tx.Select("card_category", "real_name_status").First(&card, carrierID).Error; err == nil {
cardCategory = card.CardCategory
if err := tx.Select("real_name_status").First(&card, carrierID).Error; err == nil {
currentlyRealnamed = card.RealNameStatus == constants.RealNameStatusVerified
}
// err != nil 时 currentlyRealnamed 保守默认 false不阻断购买流程
@@ -2285,9 +2289,9 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
// 判断是否 C 端购买C 端购买前已做实名前置检查,直接激活)
isCEndPurchase := order.BuyerType == model.BuyerTypePersonal
if cardCategory != "industry" && !isCEndPurchase {
if !isCEndPurchase {
// 后台囤货路径:按 expiry_base 决定是否等实名
if pkg.ExpiryBase != "from_purchase" {
if terms.ExpiryBase != constants.PackageExpiryBaseFromPurchase {
if currentlyRealnamed {
s.logger.Info("购买时载体已实名,直接激活套餐",
zap.String("carrier_type", carrierType),
@@ -2303,7 +2307,7 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
}
// from_purchase立即激活status 已为 Active保持不变
}
// 行业卡或 C 端购买直接激活status 已为 Active保持不变
// C 端购买已完成实名前置校验,直接激活。
}
// 创建套餐使用记录
@@ -2332,6 +2336,7 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}
terms.Apply(usage)
if carrierType == "iot_card" {
usage.IotCardID = carrierID
@@ -2362,6 +2367,11 @@ func (s *Service) activateMainPackage(ctx context.Context, tx *gorm.DB, order *m
// activateAddonPackage 任务 8.5-8.7: 加油包激活逻辑
func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *model.Order, pkg *model.Package, carrierType string, carrierID uint, now time.Time) error {
terms, err := s.resolvePackageTerms(ctx, pkg, order.SellerShopID)
if err != nil {
s.logger.Error("生成加油包计时快照失败", zap.Uint("package_id", pkg.ID), zap.Error(err))
return err
}
// 任务 8.5-8.6: 检查是否有可挂靠主套餐(待生效、未过期的生效中或已用完)
mainPackage, err := packagepkg.FindAttachableMainPackageForAddon(tx, carrierType, carrierID, now)
if err == gorm.ErrRecordNotFound {
@@ -2421,6 +2431,7 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
PackagePriceConfigStatus: pkg.PriceConfigStatus,
PackageIsGift: pkg.IsGift,
}
terms.Apply(usage)
if carrierType == "iot_card" {
usage.IotCardID = carrierID
@@ -2436,6 +2447,20 @@ func (s *Service) activateAddonPackage(ctx context.Context, tx *gorm.DB, order *
return nil
}
func (s *Service) resolvePackageTerms(ctx context.Context, pkg *model.Package, sellerShopID *uint) (packagedomain.TermsSnapshot, error) {
var allocation *model.ShopPackageAllocation
if sellerShopID != nil && *sellerShopID > 0 {
found, err := s.shopPackageAllocationStore.GetByShopAndPackageForSystem(ctx, *sellerShopID, pkg.ID)
if err != nil && err != gorm.ErrRecordNotFound {
return packagedomain.TermsSnapshot{}, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐分配失败")
}
if err == nil {
allocation = found
}
}
return packagedomain.ResolveTermsSnapshot(pkg, allocation)
}
func (s *Service) enqueueCommissionCalculation(ctx context.Context, orderID uint) {
if s.queueClient == nil {
s.logger.Warn("队列客户端未初始化,跳过佣金计算任务入队", zap.Uint("order_id", orderID))