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

@@ -425,6 +425,14 @@ func (s *Service) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus in
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
userType := middleware.GetUserTypeFromContext(ctx)
// 代理用户:修改自己分配记录的 shelf_status不影响平台全局套餐状态
if userType == constants.UserTypeAgent {
return s.updateAgentShelfStatus(ctx, id, shelfStatus, currentUserID)
}
// 平台/超管:修改套餐全局 shelf_status
pkg, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
@@ -433,7 +441,7 @@ func (s *Service) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus in
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
if shelfStatus == 1 && pkg.Status == constants.StatusDisabled {
if shelfStatus == constants.ShelfStatusOn && pkg.Status == constants.StatusDisabled {
return errors.New(errors.CodeInvalidStatus, "禁用的套餐不能上架,请先启用")
}
@@ -447,6 +455,43 @@ func (s *Service) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus in
return nil
}
// updateAgentShelfStatus 代理上下架路径:更新分配记录的 shelf_status
func (s *Service) updateAgentShelfStatus(ctx context.Context, packageID uint, shelfStatus int, updaterID uint) error {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID == 0 {
return errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺")
}
// 查找代理对该套餐的分配记录
allocation, err := s.packageAllocationStore.GetByShopAndPackage(ctx, shopID, packageID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "该套餐未分配给您,无法操作上下架")
}
return errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
// 上架时检查套餐全局禁用状态
if shelfStatus == constants.ShelfStatusOn {
pkg, err := s.packageStore.GetByID(ctx, packageID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "套餐不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
if pkg.Status == constants.StatusDisabled {
return errors.New(errors.CodeInvalidStatus, "套餐已禁用,无法上架")
}
}
if err := s.packageAllocationStore.UpdateShelfStatus(ctx, allocation.ID, shelfStatus, updaterID); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "更新上下架状态失败")
}
return nil
}
func (s *Service) toResponse(ctx context.Context, pkg *model.Package) *dto.PackageResponse {
var seriesID *uint
if pkg.SeriesID > 0 {
@@ -488,6 +533,8 @@ func (s *Service) toResponse(ctx context.Context, pkg *model.Package) *dto.Packa
resp.CostPrice = allocation.CostPrice
profitMargin := pkg.SuggestedRetailPrice - allocation.CostPrice
resp.ProfitMargin = &profitMargin
// 代理查询时shelf_status 返回自己分配记录的值,而非平台全局值
resp.ShelfStatus = allocation.ShelfStatus
}
}
@@ -547,6 +594,8 @@ func (s *Service) toResponseWithAllocation(_ context.Context, pkg *model.Package
resp.CostPrice = allocation.CostPrice
profitMargin := pkg.SuggestedRetailPrice - allocation.CostPrice
resp.ProfitMargin = &profitMargin
// 代理查询时shelf_status 返回自己分配记录的值,而非平台全局值
resp.ShelfStatus = allocation.ShelfStatus
}
}