diff --git a/internal/model/dto/package_series_dto.go b/internal/model/dto/package_series_dto.go index a00a437..d4240db 100644 --- a/internal/model/dto/package_series_dto.go +++ b/internal/model/dto/package_series_dto.go @@ -28,6 +28,7 @@ type CreatePackageSeriesRequest struct { SeriesCode string `json:"series_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"系列编码"` SeriesName string `json:"series_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"系列名称"` Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"` + EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"` OneTimeCommissionConfig *SeriesOneTimeCommissionConfigDTO `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金规则配置"` } @@ -35,6 +36,7 @@ type CreatePackageSeriesRequest struct { type UpdatePackageSeriesRequest struct { SeriesName *string `json:"series_name" validate:"omitempty,min=1,max=255" minLength:"1" maxLength:"255" description:"系列名称"` Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"` + EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"` OneTimeCommissionConfig *SeriesOneTimeCommissionConfigDTO `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金规则配置"` } diff --git a/internal/service/package_series/service.go b/internal/service/package_series/service.go index ad0443d..2245a48 100644 --- a/internal/service/package_series/service.go +++ b/internal/service/package_series/service.go @@ -43,8 +43,18 @@ func (s *Service) Create(ctx context.Context, req *dto.CreatePackageSeriesReques } series.Creator = currentUserID + if req.EnableOneTimeCommission != nil { + series.EnableOneTimeCommission = *req.EnableOneTimeCommission + } + if req.OneTimeCommissionConfig != nil { config := s.dtoToModelConfig(req.OneTimeCommissionConfig) + config.Enable = series.EnableOneTimeCommission + if err := series.SetOneTimeCommissionConfig(config); err != nil { + return nil, errors.Wrap(errors.CodeInternalError, err, "设置一次性佣金配置失败") + } + } else if series.EnableOneTimeCommission { + config := &model.OneTimeCommissionConfig{Enable: true} if err := series.SetOneTimeCommissionConfig(config); err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "设置一次性佣金配置失败") } @@ -88,11 +98,24 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageSer if req.Description != nil { series.Description = *req.Description } + if req.EnableOneTimeCommission != nil { + series.EnableOneTimeCommission = *req.EnableOneTimeCommission + } if req.OneTimeCommissionConfig != nil { config := s.dtoToModelConfig(req.OneTimeCommissionConfig) + config.Enable = series.EnableOneTimeCommission if err := series.SetOneTimeCommissionConfig(config); err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "设置一次性佣金配置失败") } + } else if req.EnableOneTimeCommission != nil { + existingConfig, _ := series.GetOneTimeCommissionConfig() + if existingConfig == nil { + existingConfig = &model.OneTimeCommissionConfig{} + } + existingConfig.Enable = series.EnableOneTimeCommission + if err := series.SetOneTimeCommissionConfig(existingConfig); err != nil { + return nil, errors.Wrap(errors.CodeInternalError, err, "设置一次性佣金配置失败") + } } series.Updater = currentUserID diff --git a/openspec/config.yaml b/openspec/config.yaml index d58a620..186ada6 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -73,9 +73,75 @@ rules: - 性能敏感操作必须考虑 Redis 缓存 tasks: + # 契约规则 - tasks.md 是契约,不可擅自变更 - 禁止跳过任务、合并任务、简化任务(除非获得许可) - 必须逐项完成并标记状态 + + # TDD 工作流(必须遵守) + - "任务组 0 必须是测试准备:生成测试 + 运行确认全部 FAIL" + - "按功能单元组织任务,而非按技术层级(Store/Service/Handler)" + - "每个功能单元完成后必须有验证步骤:运行相关测试确认 PASS" + - "最终验证必须包含:全部验收测试 PASS + 全部流程测试 PASS" + + # 任务组结构模板 + # ``` + # ## 0. 测试准备(实现前执行) + # - [ ] 0.1 生成验收测试和流程测试(/opsx:gen-tests) + # - [ ] 0.2 运行测试确认全部 FAIL(证明测试有效) + # + # ## 1. 基础设施(数据库 + Model) + # - [ ] 1.x 创建迁移、Model、DTO + # - [ ] 1.y 验证:编译通过 + # + # ## 2. 功能单元 A(完整垂直切片) + # - [ ] 2.1 Store 层 + # - [ ] 2.2 Service 层 + # - [ ] 2.3 Handler 层 + 路由 + # - [ ] 2.4 **验证:功能 A 相关验收测试 PASS** + # + # ## N. 最终验证 + # - [ ] N.1 全部验收测试 PASS + # - [ ] N.2 全部流程测试 PASS + # - [ ] N.3 完整测试套件无回归 + # ``` + + # 其他规则 - 每个任务必须包含验证步骤(单元测试、集成测试、lsp_diagnostics) - 数据库变更必须包含迁移文件(使用 golang-migrate) - 新增 Handler 必须更新文档生成器(cmd/api/docs.go 和 cmd/gendocs/main.go) + + # 共识锁定规则 + consensus: + - 在 /opsx:explore 讨论后,必须使用 /opsx:lock 锁定共识 + - consensus.md 必须包含四个维度:要做什么、不做什么、关键约束、验收标准 + - 每个维度必须由用户逐条确认(不能一次性确认全部) + - 验收标准必须是可测量的(禁止模糊表述如"性能要好") + - proposal 生成时必须验证与 consensus 的一致性 + + # 验收测试规则 + acceptance_tests: + - Spec 的每个 Scenario 必须对应一个验收测试用例 + - 验收测试在实现前生成,预期全部 FAIL + - 每个测试必须包含"破坏点"注释(说明什么代码变更会导致失败) + - 测试使用 table-driven 模式(同一 API 多场景) + - 测试文件位置:tests/acceptance/{capability}_acceptance_test.go + - 验收测试使用 IntegrationTestEnv,不要 mock 依赖 + + # 业务流程测试规则 + business_flow_tests: + - Spec 必须包含 Business Flows 部分(多 API 业务场景) + - 每个 Business Flow 对应一个流程测试 + - 流程测试的 steps 之间共享状态(如 ID) + - 每个 step 必须声明依赖(依赖哪些前置 step) + - 每个 step 必须包含"破坏点"注释 + - 测试文件位置:tests/flows/{capability}_{flow}_flow_test.go + + # 测试金字塔比例 + test_pyramid: + - 验收测试(单 API 契约):30% + - 流程测试(多 API 业务场景):15% + - 集成测试(组件集成):25% + - 单元测试(复杂逻辑):30% + - 单元测试仅保留:纯函数、状态机、复杂业务规则、边界条件 + - 单元测试删除:简单 CRUD、DTO 转换、配置读取