feat: 新增代理分配套餐上架状态(shelf_status)功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m56s

- 新增数据库迁移:为 shop_package_allocation 表添加 shelf_status 字段
- 更新模型/DTO:ShopPackageAllocation 增加 ShelfStatus 字段及相关枚举
- 更新套餐分配 Service:支持上架/下架状态管理逻辑
- 更新套餐 Store/Service:根据 shelf_status 过滤可售套餐
- 更新购买验证 Service:引入上架状态校验逻辑
- 归档 OpenSpec 变更:2026-03-02-agent-allocation-shelf-status
- 同步更新主规范文档:allocation-shelf-status、package-management、purchase-validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 15:38:54 +08:00
parent 8efe79526a
commit 61155952a7
22 changed files with 677 additions and 44 deletions

View File

@@ -58,11 +58,13 @@ func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filte
query := s.db.WithContext(ctx).Model(&model.Package{})
// 代理用户额外过滤:只能看到已分配的套餐
// 代理用户额外过滤:只能看到已分配allocation.status=启用)的套餐
// 不按 tb_package.shelf_status 过滤,代理套餐可见性由 allocation.shelf_status 决定
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
if userType == constants.UserTypeAgent && shopID > 0 {
query = query.Joins("INNER JOIN tb_shop_package_allocation ON tb_shop_package_allocation.package_id = tb_package.id").
isAgent := userType == constants.UserTypeAgent && shopID > 0
if isAgent {
query = query.Joins("INNER JOIN tb_shop_package_allocation ON tb_shop_package_allocation.package_id = tb_package.id AND tb_shop_package_allocation.deleted_at IS NULL").
Where("tb_shop_package_allocation.shop_id = ? AND tb_shop_package_allocation.status = ?",
shopID, constants.StatusEnabled)
}
@@ -77,7 +79,12 @@ func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filte
query = query.Where("tb_package.status = ?", status)
}
if shelfStatus, ok := filters["shelf_status"]; ok {
query = query.Where("tb_package.shelf_status = ?", shelfStatus)
if isAgent {
// 代理用户按自己的 allocation.shelf_status 过滤,不使用平台全局值
query = query.Where("tb_shop_package_allocation.shelf_status = ?", shelfStatus)
} else {
query = query.Where("tb_package.shelf_status = ?", shelfStatus)
}
}
if packageType, ok := filters["package_type"].(string); ok && packageType != "" {
query = query.Where("tb_package.package_type = ?", packageType)

View File

@@ -32,6 +32,16 @@ func (s *ShopPackageAllocationStore) GetByID(ctx context.Context, id uint) (*mod
return &allocation, nil
}
// GetByIDForSystem 根据ID获取分配记录不应用数据权限过滤
// 用于 Service 层需要先查到记录再做业务权限校验的场景
func (s *ShopPackageAllocationStore) GetByIDForSystem(ctx context.Context, id uint) (*model.ShopPackageAllocation, error) {
var allocation model.ShopPackageAllocation
if err := s.db.WithContext(ctx).First(&allocation, id).Error; err != nil {
return nil, err
}
return &allocation, nil
}
func (s *ShopPackageAllocationStore) GetByShopAndPackage(ctx context.Context, shopID, packageID uint) (*model.ShopPackageAllocation, error) {
var allocation model.ShopPackageAllocation
query := s.db.WithContext(ctx).Where("shop_id = ? AND package_id = ?", shopID, packageID)
@@ -43,6 +53,18 @@ func (s *ShopPackageAllocationStore) GetByShopAndPackage(ctx context.Context, sh
return &allocation, nil
}
// GetByShopAndPackageForSystem 根据店铺和套餐查询分配记录(不应用数据权限过滤)
// 用于系统内部查询场景(如购买校验),避免因 ctx 权限限制导致无法查到目标记录
func (s *ShopPackageAllocationStore) GetByShopAndPackageForSystem(ctx context.Context, shopID, packageID uint) (*model.ShopPackageAllocation, error) {
var allocation model.ShopPackageAllocation
if err := s.db.WithContext(ctx).
Where("shop_id = ? AND package_id = ?", shopID, packageID).
First(&allocation).Error; err != nil {
return nil, err
}
return &allocation, nil
}
func (s *ShopPackageAllocationStore) Update(ctx context.Context, allocation *model.ShopPackageAllocation) error {
return s.db.WithContext(ctx).Save(allocation).Error
}
@@ -106,6 +128,17 @@ func (s *ShopPackageAllocationStore) UpdateStatus(ctx context.Context, id uint,
}).Error
}
// UpdateShelfStatus 更新分配记录的上下架状态
func (s *ShopPackageAllocationStore) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int, updater uint) error {
return s.db.WithContext(ctx).
Model(&model.ShopPackageAllocation{}).
Where("id = ?", id).
Updates(map[string]interface{}{
"shelf_status": shelfStatus,
"updater": updater,
}).Error
}
func (s *ShopPackageAllocationStore) GetByShopID(ctx context.Context, shopID uint) ([]*model.ShopPackageAllocation, error) {
var allocations []*model.ShopPackageAllocation
query := s.db.WithContext(ctx).Where("shop_id = ? AND status = 1", shopID)