From 851682d579a122e687918dc3fd871f144b296fc2 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Sat, 22 Nov 2025 21:29:23 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E6=96=B9=E9=A2=86=E5=9F=9F=E5=B1=82?= =?UTF-8?q?=E6=96=B9=E6=B3=95+=E9=87=8D=E6=9E=84=E9=85=8D=E6=96=B9?= =?UTF-8?q?=E9=A2=86=E5=9F=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/archive/recipe-management/index.md | 4 +- internal/core/component_initializers.go | 20 +- internal/domain/recipe/nutrient_service.go | 146 +++++ .../domain/recipe/pig_age_stage_service.go | 113 ++++ internal/domain/recipe/pig_breed_service.go | 112 ++++ internal/domain/recipe/pig_type_service.go | 139 +++++ .../domain/recipe/raw_material_service.go | 184 ++++++ internal/domain/recipe/recipe_core_service.go | 189 ++++++ internal/domain/recipe/recipe_service.go | 580 +----------------- .../repository/raw_material_repository.go | 14 +- .../infra/repository/recipe_repository.go | 68 +- 11 files changed, 982 insertions(+), 587 deletions(-) create mode 100644 internal/domain/recipe/nutrient_service.go create mode 100644 internal/domain/recipe/pig_age_stage_service.go create mode 100644 internal/domain/recipe/pig_breed_service.go create mode 100644 internal/domain/recipe/pig_type_service.go create mode 100644 internal/domain/recipe/raw_material_service.go create mode 100644 internal/domain/recipe/recipe_core_service.go diff --git a/design/archive/recipe-management/index.md b/design/archive/recipe-management/index.md index c394a5b..612428d 100644 --- a/design/archive/recipe-management/index.md +++ b/design/archive/recipe-management/index.md @@ -58,4 +58,6 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66 8. 实现配方领域的web接口 9. 实现修改原料营养信息 10. 实现修改猪营养需求 -11. 配方模型定义和仓库层增删改查方法 \ No newline at end of file +11. 配方模型定义和仓库层增删改查方法 +12. 配方领域层方法 +13. 重构配方领域 \ No newline at end of file diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index b354386..97ed6f0 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -86,6 +86,7 @@ type Repositories struct { pigTypeRepo repository.PigTypeRepository rawMaterialRepo repository.RawMaterialRepository nutrientRepo repository.NutrientRepository + recipeRepo repository.RecipeRepository unitOfWork repository.UnitOfWork } @@ -117,6 +118,7 @@ func initRepositories(ctx context.Context, db *gorm.DB) *Repositories { pigTypeRepo: repository.NewGormPigTypeRepository(logs.AddCompName(baseCtx, "PigTypeRepo"), db), rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db), nutrientRepo: repository.NewGormNutrientRepository(logs.AddCompName(baseCtx, "NutrientRepo"), db), + recipeRepo: repository.NewGormRecipeRepository(logs.AddCompName(baseCtx, "RecipeRepo"), db), unitOfWork: repository.NewGormUnitOfWork(logs.AddCompName(baseCtx, "UnitOfWork"), db), } } @@ -214,13 +216,21 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr taskFactory, ) - // 配方管理服务 + // 配方管理相关 + nutrientService := recipe.NewNutrientService(logs.AddCompName(baseCtx, "NutrientService"), infra.repos.nutrientRepo) + pigAgeStageService := recipe.NewPigAgeStageService(logs.AddCompName(baseCtx, "PigAgeStageService"), infra.repos.pigTypeRepo) + pigBreedService := recipe.NewPigBreedService(logs.AddCompName(baseCtx, "PigBreedService"), infra.repos.pigTypeRepo) + pigTypeService := recipe.NewPigTypeService(logs.AddCompName(baseCtx, "PigTypeService"), infra.repos.unitOfWork, infra.repos.pigTypeRepo) + rawMaterialService := recipe.NewRawMaterialService(logs.AddCompName(baseCtx, "RawMaterialService"), infra.repos.unitOfWork, infra.repos.rawMaterialRepo) + recipeCoreService := recipe.NewRecipeCoreService(logs.AddCompName(baseCtx, "RecipeCoreService"), infra.repos.unitOfWork, infra.repos.recipeRepo) recipeService := recipe.NewRecipeService( logs.AddCompName(baseCtx, "RecipeService"), - infra.repos.unitOfWork, - infra.repos.nutrientRepo, - infra.repos.rawMaterialRepo, - infra.repos.pigTypeRepo, + nutrientService, + rawMaterialService, + pigBreedService, + pigAgeStageService, + pigTypeService, + recipeCoreService, ) return &DomainServices{ diff --git a/internal/domain/recipe/nutrient_service.go b/internal/domain/recipe/nutrient_service.go new file mode 100644 index 0000000..48ec8e8 --- /dev/null +++ b/internal/domain/recipe/nutrient_service.go @@ -0,0 +1,146 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrNutrientNameConflict = fmt.Errorf("营养种类名称已存在") + ErrNutrientNotFound = fmt.Errorf("营养种类不存在") +) + +// NutrientService 定义了营养种类领域的核心业务服务接口 +type NutrientService interface { + CreateNutrient(ctx context.Context, name, description string) (*models.Nutrient, error) + UpdateNutrient(ctx context.Context, id uint32, name, description string) (*models.Nutrient, error) + DeleteNutrient(ctx context.Context, id uint32) error + GetNutrient(ctx context.Context, id uint32) (*models.Nutrient, error) + ListNutrients(ctx context.Context, opts repository.NutrientListOptions, page, pageSize int) ([]models.Nutrient, int64, error) +} + +// nutrientServiceImpl 是 NutrientService 的实现 +type nutrientServiceImpl struct { + ctx context.Context + nutrientRepo repository.NutrientRepository +} + +// NewNutrientService 创建一个新的 NutrientService 实例 +func NewNutrientService(ctx context.Context, nutrientRepo repository.NutrientRepository) NutrientService { + return &nutrientServiceImpl{ + ctx: ctx, + nutrientRepo: nutrientRepo, + } +} + +// CreateNutrient 实现了创建营养种类的核心业务逻辑 +func (s *nutrientServiceImpl) CreateNutrient(ctx context.Context, name, description string) (*models.Nutrient, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateNutrient") + + // 检查名称是否已存在 + existing, err := s.nutrientRepo.GetNutrientByName(serviceCtx, name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // 只有不是记录未找到的错误才返回 + return nil, fmt.Errorf("检查营养种类名称失败: %w", err) + } + if existing != nil { + return nil, ErrNutrientNameConflict + } + + nutrient := &models.Nutrient{ + Name: name, + Description: description, + } + + if err := s.nutrientRepo.CreateNutrient(serviceCtx, nutrient); err != nil { + return nil, fmt.Errorf("创建营养种类失败: %w", err) + } + + return nutrient, nil +} + +// UpdateNutrient 实现了更新营养种类的核心业务逻辑 +func (s *nutrientServiceImpl) UpdateNutrient(ctx context.Context, id uint32, name, description string) (*models.Nutrient, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateNutrient") + + // 检查要更新的实体是否存在 + nutrient, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { // 如果是记录未找到错误,则返回领域错误 + return nil, ErrNutrientNotFound + } + return nil, fmt.Errorf("获取待更新的营养种类失败: %w", err) + } + + // 如果名称有变动,检查新名称是否与其它记录冲突 + if nutrient.Name != name { + existing, err := s.nutrientRepo.GetNutrientByName(serviceCtx, name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("检查新的营养种类名称失败: %w", err) + } + if existing != nil && existing.ID != id { + return nil, ErrNutrientNameConflict + } + } + + nutrient.Name = name + nutrient.Description = description + + if err := s.nutrientRepo.UpdateNutrient(serviceCtx, nutrient); err != nil { + return nil, fmt.Errorf("更新营养种类失败: %w", err) + } + + return nutrient, nil +} + +// DeleteNutrient 实现了删除营养种类的核心业务逻辑 +func (s *nutrientServiceImpl) DeleteNutrient(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteNutrient") + + // 检查实体是否存在 + _, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrNutrientNotFound + } + return fmt.Errorf("获取待删除的营养种类失败: %w", err) + } + + if err := s.nutrientRepo.DeleteNutrient(serviceCtx, id); err != nil { + return fmt.Errorf("删除营养种类失败: %w", err) + } + + return nil +} + +// GetNutrient 实现了获取单个营养种类的逻辑 +func (s *nutrientServiceImpl) GetNutrient(ctx context.Context, id uint32) (*models.Nutrient, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetNutrient") + + nutrient, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrNutrientNotFound + } + return nil, fmt.Errorf("获取营养种类失败: %w", err) + } + return nutrient, nil +} + +// ListNutrients 实现了列出营养种类的逻辑 +func (s *nutrientServiceImpl) ListNutrients(ctx context.Context, opts repository.NutrientListOptions, page, pageSize int) ([]models.Nutrient, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListNutrients") + + nutrients, total, err := s.nutrientRepo.ListNutrients(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取营养种类列表失败: %w", err) + } + return nutrients, total, nil +} diff --git a/internal/domain/recipe/pig_age_stage_service.go b/internal/domain/recipe/pig_age_stage_service.go new file mode 100644 index 0000000..d7b74ec --- /dev/null +++ b/internal/domain/recipe/pig_age_stage_service.go @@ -0,0 +1,113 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrPigAgeStageInUse = fmt.Errorf("猪年龄阶段正在被猪类型使用,无法删除") + ErrPigAgeStageNotFound = fmt.Errorf("猪年龄阶段不存在") +) + +// PigAgeStageService 定义了猪年龄阶段领域的核心业务服务接口 +type PigAgeStageService interface { + CreatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error + GetPigAgeStageByID(ctx context.Context, id uint32) (*models.PigAgeStage, error) + UpdatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error + DeletePigAgeStage(ctx context.Context, id uint32) error + ListPigAgeStages(ctx context.Context, opts repository.PigAgeStageListOptions, page, pageSize int) ([]models.PigAgeStage, int64, error) +} + +// pigAgeStageServiceImpl 是 PigAgeStageService 的实现 +type pigAgeStageServiceImpl struct { + ctx context.Context + pigTypeRepo repository.PigTypeRepository // PigAgeStage 相关的操作目前在 PigTypeRepository 中 +} + +// NewPigAgeStageService 创建一个新的 PigAgeStageService 实例 +func NewPigAgeStageService(ctx context.Context, pigTypeRepo repository.PigTypeRepository) PigAgeStageService { + return &pigAgeStageServiceImpl{ + ctx: ctx, + pigTypeRepo: pigTypeRepo, + } +} + +// CreatePigAgeStage 实现了创建猪年龄阶段的核心业务逻辑 +func (s *pigAgeStageServiceImpl) CreatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigAgeStage") + if err := s.pigTypeRepo.CreatePigAgeStage(serviceCtx, ageStage); err != nil { + return fmt.Errorf("创建猪年龄阶段失败: %w", err) + } + return nil +} + +// GetPigAgeStageByID 实现了获取单个猪年龄阶段的逻辑 +func (s *pigAgeStageServiceImpl) GetPigAgeStageByID(ctx context.Context, id uint32) (*models.PigAgeStage, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigAgeStageByID") + + ageStage, err := s.pigTypeRepo.GetPigAgeStageByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrPigAgeStageNotFound + } + return nil, fmt.Errorf("获取猪年龄阶段失败: %w", err) + } + return ageStage, nil +} + +// UpdatePigAgeStage 实现了更新猪年龄阶段的核心业务逻辑 +func (s *pigAgeStageServiceImpl) UpdatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigAgeStage") + if err := s.pigTypeRepo.UpdatePigAgeStage(serviceCtx, ageStage); err != nil { + return fmt.Errorf("更新猪年龄阶段失败: %w", err) + } + return nil +} + +// DeletePigAgeStage 实现了删除猪年龄阶段的核心业务逻辑 +func (s *pigAgeStageServiceImpl) DeletePigAgeStage(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigAgeStage") + + // 检查是否有猪类型关联到该年龄阶段 + opts := repository.PigTypeListOptions{AgeStageID: &id} + pigTypes, _, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, 1, 1) // 只需检查是否存在,所以取1条 + if err != nil { + return fmt.Errorf("检查猪年龄阶段关联失败: %w", err) + } + if len(pigTypes) > 0 { + return ErrPigAgeStageInUse + } + + // 检查实体是否存在 + _, err = s.pigTypeRepo.GetPigAgeStageByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigAgeStageNotFound + } + return fmt.Errorf("获取待删除的猪年龄阶段失败: %w", err) + } + + if err := s.pigTypeRepo.DeletePigAgeStage(serviceCtx, id); err != nil { + return fmt.Errorf("删除猪年龄阶段失败: %w", err) + } + return nil +} + +// ListPigAgeStages 实现了列出猪年龄阶段的逻辑 +func (s *pigAgeStageServiceImpl) ListPigAgeStages(ctx context.Context, opts repository.PigAgeStageListOptions, page, pageSize int) ([]models.PigAgeStage, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigAgeStages") + ageStages, total, err := s.pigTypeRepo.ListPigAgeStages(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取猪年龄阶段列表失败: %w", err) + } + return ageStages, total, nil +} diff --git a/internal/domain/recipe/pig_breed_service.go b/internal/domain/recipe/pig_breed_service.go new file mode 100644 index 0000000..8b380fa --- /dev/null +++ b/internal/domain/recipe/pig_breed_service.go @@ -0,0 +1,112 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrPigBreedInUse = fmt.Errorf("猪品种正在被猪类型使用,无法删除") + ErrPigBreedNotFound = fmt.Errorf("猪品种不存在") +) + +// PigBreedService 定义了猪品种领域的核心业务服务接口 +type PigBreedService interface { + CreatePigBreed(ctx context.Context, breed *models.PigBreed) error + GetPigBreedByID(ctx context.Context, id uint32) (*models.PigBreed, error) + UpdatePigBreed(ctx context.Context, breed *models.PigBreed) error + DeletePigBreed(ctx context.Context, id uint32) error + ListPigBreeds(ctx context.Context, opts repository.PigBreedListOptions, page, pageSize int) ([]models.PigBreed, int64, error) +} + +// pigBreedServiceImpl 是 PigBreedService 的实现 +type pigBreedServiceImpl struct { + ctx context.Context + pigTypeRepo repository.PigTypeRepository // PigBreed 相关的操作目前在 PigTypeRepository 中 +} + +// NewPigBreedService 创建一个新的 PigBreedService 实例 +func NewPigBreedService(ctx context.Context, pigTypeRepo repository.PigTypeRepository) PigBreedService { + return &pigBreedServiceImpl{ + ctx: ctx, + pigTypeRepo: pigTypeRepo, + } +} + +// CreatePigBreed 实现了创建猪品种的核心业务逻辑 +func (s *pigBreedServiceImpl) CreatePigBreed(ctx context.Context, breed *models.PigBreed) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBreed") + if err := s.pigTypeRepo.CreatePigBreed(serviceCtx, breed); err != nil { + return fmt.Errorf("创建猪品种失败: %w", err) + } + return nil +} + +// GetPigBreedByID 实现了获取单个猪品种的逻辑 +func (s *pigBreedServiceImpl) GetPigBreedByID(ctx context.Context, id uint32) (*models.PigBreed, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBreedByID") + breed, err := s.pigTypeRepo.GetPigBreedByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrPigBreedNotFound + } + return nil, fmt.Errorf("获取猪品种失败: %w", err) + } + return breed, nil +} + +// UpdatePigBreed 实现了更新猪品种的核心业务逻辑 +func (s *pigBreedServiceImpl) UpdatePigBreed(ctx context.Context, breed *models.PigBreed) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBreed") + if err := s.pigTypeRepo.UpdatePigBreed(serviceCtx, breed); err != nil { + return fmt.Errorf("更新猪品种失败: %w", err) + } + return nil +} + +// DeletePigBreed 实现了删除猪品种的核心业务逻辑 +func (s *pigBreedServiceImpl) DeletePigBreed(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBreed") + + // 检查是否有猪类型关联到该品种 + opts := repository.PigTypeListOptions{BreedID: &id} + pigTypes, _, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, 1, 1) // 只需检查是否存在,所以取1条 + if err != nil { + return fmt.Errorf("检查猪品种关联失败: %w", err) + } + if len(pigTypes) > 0 { + return ErrPigBreedInUse + } + + // 检查实体是否存在 + _, err = s.pigTypeRepo.GetPigBreedByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigBreedNotFound + } + return fmt.Errorf("获取待删除的猪品种失败: %w", err) + } + + if err := s.pigTypeRepo.DeletePigBreed(serviceCtx, id); err != nil { + return fmt.Errorf("删除猪品种失败: %w", err) + } + return nil +} + +// ListPigBreeds 实现了列出猪品种的逻辑 +func (s *pigBreedServiceImpl) ListPigBreeds(ctx context.Context, opts repository.PigBreedListOptions, page, pageSize int) ([]models.PigBreed, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigBreeds") + breeds, total, err := s.pigTypeRepo.ListPigBreeds(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取猪品种列表失败: %w", err) + } + return breeds, total, nil +} diff --git a/internal/domain/recipe/pig_type_service.go b/internal/domain/recipe/pig_type_service.go new file mode 100644 index 0000000..82058f2 --- /dev/null +++ b/internal/domain/recipe/pig_type_service.go @@ -0,0 +1,139 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrPigTypeNotFound = fmt.Errorf("猪类型不存在") +) + +// PigTypeService 定义了猪类型领域的核心业务服务接口 +type PigTypeService interface { + CreatePigType(ctx context.Context, pigType *models.PigType) error + GetPigTypeByID(ctx context.Context, id uint32) (*models.PigType, error) + UpdatePigType(ctx context.Context, pigType *models.PigType) error + DeletePigType(ctx context.Context, id uint32) error + ListPigTypes(ctx context.Context, opts repository.PigTypeListOptions, page, pageSize int) ([]models.PigType, int64, error) + UpdatePigTypeNutrientRequirements(ctx context.Context, pigTypeID uint32, requirements []models.PigNutrientRequirement) error +} + +// pigTypeServiceImpl 是 PigTypeService 的实现 +type pigTypeServiceImpl struct { + ctx context.Context + uow repository.UnitOfWork + pigTypeRepo repository.PigTypeRepository +} + +// NewPigTypeService 创建一个新的 PigTypeService 实例 +func NewPigTypeService(ctx context.Context, uow repository.UnitOfWork, pigTypeRepo repository.PigTypeRepository) PigTypeService { + return &pigTypeServiceImpl{ + ctx: ctx, + uow: uow, + pigTypeRepo: pigTypeRepo, + } +} + +// CreatePigType 实现了创建猪类型的核心业务逻辑 +func (s *pigTypeServiceImpl) CreatePigType(ctx context.Context, pigType *models.PigType) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigType") + if err := s.pigTypeRepo.CreatePigType(serviceCtx, pigType); err != nil { + return fmt.Errorf("创建猪类型失败: %w", err) + } + return nil +} + +// GetPigTypeByID 实现了获取单个猪类型的逻辑 +func (s *pigTypeServiceImpl) GetPigTypeByID(ctx context.Context, id uint32) (*models.PigType, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigTypeByID") + pigType, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrPigTypeNotFound + } + return nil, fmt.Errorf("获取猪类型失败: %w", err) + } + return pigType, nil +} + +// UpdatePigType 实现了更新猪类型的核心业务逻辑 +func (s *pigTypeServiceImpl) UpdatePigType(ctx context.Context, pigType *models.PigType) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigType") + if err := s.pigTypeRepo.UpdatePigType(serviceCtx, pigType); err != nil { + return fmt.Errorf("更新猪类型失败: %m", err) + } + return nil +} + +// DeletePigType 实现了删除猪类型的核心业务逻辑 +func (s *pigTypeServiceImpl) DeletePigType(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigType") + + // 检查实体是否存在 + _, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigTypeNotFound + } + return fmt.Errorf("获取待删除的猪类型失败: %w", err) + } + + if err := s.pigTypeRepo.DeletePigType(serviceCtx, id); err != nil { + return fmt.Errorf("删除猪类型失败: %w", err) + } + return nil +} + +// ListPigTypes 实现了列出猪类型的逻辑 +func (s *pigTypeServiceImpl) ListPigTypes(ctx context.Context, opts repository.PigTypeListOptions, page, pageSize int) ([]models.PigType, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigTypes") + pigTypes, total, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取猪类型列表失败: %w", err) + } + return pigTypes, total, nil +} + +// UpdatePigTypeNutrientRequirements 实现了全量更新猪类型营养需求的核心业务逻辑 +func (s *pigTypeServiceImpl) UpdatePigTypeNutrientRequirements(ctx context.Context, pigTypeID uint32, requirements []models.PigNutrientRequirement) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigTypeNutrientRequirements") + + // 1. 检查猪类型是否存在 + if _, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, pigTypeID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigTypeNotFound + } + return fmt.Errorf("获取待更新营养需求的猪类型失败: %w", err) + } + + // 2. 在事务中执行替换操作 + err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + // 2.1. 删除旧的关联记录 + if err := s.pigTypeRepo.DeletePigNutrientRequirementsByPigTypeIDTx(serviceCtx, tx, pigTypeID); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + // 2.2. 创建新的关联记录 + if err := s.pigTypeRepo.CreateBatchPigNutrientRequirementsTx(serviceCtx, tx, requirements); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + return nil + }) + + if err != nil { + return fmt.Errorf("更新猪类型营养需求事务执行失败: %w", err) + } + + // 3. 操作成功,直接返回 nil + return nil +} diff --git a/internal/domain/recipe/raw_material_service.go b/internal/domain/recipe/raw_material_service.go new file mode 100644 index 0000000..1ade9ec --- /dev/null +++ b/internal/domain/recipe/raw_material_service.go @@ -0,0 +1,184 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrRawMaterialNameConflict = fmt.Errorf("原料名称已存在") + ErrRawMaterialNotFound = fmt.Errorf("原料不存在") +) + +// RawMaterialService 定义了原料领域的核心业务服务接口 +type RawMaterialService interface { + CreateRawMaterial(ctx context.Context, name, description string) (*models.RawMaterial, error) + UpdateRawMaterial(ctx context.Context, id uint32, name, description string) (*models.RawMaterial, error) + DeleteRawMaterial(ctx context.Context, id uint32) error + GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) + ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) + UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error +} + +// rawMaterialServiceImpl 是 RawMaterialService 的实现 +type rawMaterialServiceImpl struct { + ctx context.Context + uow repository.UnitOfWork + rawMaterialRepo repository.RawMaterialRepository +} + +// NewRawMaterialService 创建一个新的 RawMaterialService 实例 +func NewRawMaterialService(ctx context.Context, uow repository.UnitOfWork, rawMaterialRepo repository.RawMaterialRepository) RawMaterialService { + return &rawMaterialServiceImpl{ + ctx: ctx, + uow: uow, + rawMaterialRepo: rawMaterialRepo, + } +} + +// CreateRawMaterial 实现了创建原料的核心业务逻辑 +func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, description string) (*models.RawMaterial, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial") + + // 检查名称是否已存在 + existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("检查原料名称失败: %w", err) + } + if existing != nil { + return nil, ErrRawMaterialNameConflict + } + + rawMaterial := &models.RawMaterial{ + Name: name, + Description: description, + } + + if err := s.rawMaterialRepo.CreateRawMaterial(serviceCtx, rawMaterial); err != nil { + return nil, fmt.Errorf("创建原料失败: %w", err) + } + + return rawMaterial, nil +} + +// UpdateRawMaterial 实现了更新原料的核心业务逻辑 +func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, name, description string) (*models.RawMaterial, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial") + + // 检查要更新的实体是否存在 + rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRawMaterialNotFound + } + return nil, fmt.Errorf("获取待更新的原料失败: %w", err) + } + + // 如果名称有变动,检查新名称是否与其它记录冲突 + if rawMaterial.Name != name { + existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("检查新的原料名称失败: %w", err) + } + if existing != nil && existing.ID != id { + return nil, ErrRawMaterialNameConflict + } + } + + rawMaterial.Name = name + rawMaterial.Description = description + + if err := s.rawMaterialRepo.UpdateRawMaterial(serviceCtx, rawMaterial); err != nil { + return nil, fmt.Errorf("更新原料失败: %w", err) + } + + return rawMaterial, nil +} + +// DeleteRawMaterial 实现了删除原料的核心业务逻辑 +func (s *rawMaterialServiceImpl) DeleteRawMaterial(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteRawMaterial") + + // 检查实体是否存在 + _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRawMaterialNotFound + } + return fmt.Errorf("获取待删除的原料失败: %w", err) + } + + if err := s.rawMaterialRepo.DeleteRawMaterial(serviceCtx, id); err != nil { + return fmt.Errorf("删除原料失败: %w", err) + } + + return nil +} + +// GetRawMaterial 实现了获取单个原料的逻辑 +func (s *rawMaterialServiceImpl) GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRawMaterial") + + rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRawMaterialNotFound + } + return nil, fmt.Errorf("获取原料失败: %w", err) + } + return rawMaterial, nil +} + +// ListRawMaterials 实现了列出原料的逻辑 +func (s *rawMaterialServiceImpl) ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterials") + + rawMaterials, total, err := s.rawMaterialRepo.ListRawMaterials(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取原料列表失败: %w", err) + } + return rawMaterials, total, nil +} + +// UpdateRawMaterialNutrients 实现了全量更新原料营养成分的业务逻辑 +func (s *rawMaterialServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients") + + // 1. 检查原料是否存在 + if _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, rawMaterialID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRawMaterialNotFound + } + return fmt.Errorf("获取待更新的原料失败: %w", err) + } + + // 2. 在事务中执行替换操作 + err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + // 2.1. 删除旧的关联记录 + if err := s.rawMaterialRepo.DeleteNutrientsByRawMaterialIDTx(serviceCtx, tx, rawMaterialID); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + // 2.2. 创建新的关联记录 + if err := s.rawMaterialRepo.CreateBatchRawMaterialNutrientsTx(serviceCtx, tx, nutrients); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + return nil + }) + + if err != nil { + return fmt.Errorf("更新原料营养成分事务执行失败: %w", err) + } + + // 3. 操作成功,直接返回 nil + return nil +} diff --git a/internal/domain/recipe/recipe_core_service.go b/internal/domain/recipe/recipe_core_service.go new file mode 100644 index 0000000..523f7a2 --- /dev/null +++ b/internal/domain/recipe/recipe_core_service.go @@ -0,0 +1,189 @@ +package recipe + +import ( + "context" + "errors" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" + + "gorm.io/gorm" +) + +// 定义领域特定的错误 +var ( + ErrRecipeNotFound = fmt.Errorf("配方不存在") + ErrRecipeNameConflict = fmt.Errorf("配方名称已存在") +) + +// RecipeCoreService 定义了配方领域的核心业务服务接口 +type RecipeCoreService interface { + CreateRecipe(ctx context.Context, recipe *models.Recipe) (*models.Recipe, error) + GetRecipeByID(ctx context.Context, id uint32) (*models.Recipe, error) + GetRecipeByName(ctx context.Context, name string) (*models.Recipe, error) + UpdateRecipe(ctx context.Context, recipe *models.Recipe) (*models.Recipe, error) + DeleteRecipe(ctx context.Context, id uint32) error + ListRecipes(ctx context.Context, opts repository.RecipeListOptions, page, pageSize int) ([]models.Recipe, int64, error) + UpdateRecipeIngredients(ctx context.Context, recipeID uint32, ingredients []models.RecipeIngredient) error +} + +// recipeCoreServiceImpl 是 RecipeCoreService 的实现 +type recipeCoreServiceImpl struct { + ctx context.Context + uow repository.UnitOfWork + recipeRepo repository.RecipeRepository +} + +// NewRecipeCoreService 创建一个新的 RecipeCoreService 实例 +func NewRecipeCoreService(ctx context.Context, uow repository.UnitOfWork, recipeRepo repository.RecipeRepository) RecipeCoreService { + return &recipeCoreServiceImpl{ + ctx: ctx, + uow: uow, + recipeRepo: recipeRepo, + } +} + +// CreateRecipe 实现了创建配方的核心业务逻辑 +func (s *recipeCoreServiceImpl) CreateRecipe(ctx context.Context, recipe *models.Recipe) (*models.Recipe, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRecipe") + + // 检查名称是否已存在 + existing, err := s.recipeRepo.GetRecipeByName(serviceCtx, recipe.Name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("检查配方名称失败: %w", err) + } + if existing != nil { + return nil, ErrRecipeNameConflict + } + + if err := s.recipeRepo.CreateRecipe(serviceCtx, recipe); err != nil { + return nil, fmt.Errorf("创建配方失败: %w", err) + } + return recipe, nil +} + +// GetRecipeByID 实现了获取单个配方的逻辑 +func (s *recipeCoreServiceImpl) GetRecipeByID(ctx context.Context, id uint32) (*models.Recipe, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRecipeByID") + recipe, err := s.recipeRepo.GetRecipeByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRecipeNotFound + } + return nil, fmt.Errorf("获取配方失败: %w", err) + } + return recipe, nil +} + +// GetRecipeByName 实现了根据名称获取单个配方的逻辑 +func (s *recipeCoreServiceImpl) GetRecipeByName(ctx context.Context, name string) (*models.Recipe, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRecipeByName") + recipe, err := s.recipeRepo.GetRecipeByName(serviceCtx, name) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRecipeNotFound + } + return nil, fmt.Errorf("获取配方失败: %w", err) + } + return recipe, nil +} + +// UpdateRecipe 实现了更新配方的核心业务逻辑 +func (s *recipeCoreServiceImpl) UpdateRecipe(ctx context.Context, recipe *models.Recipe) (*models.Recipe, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRecipe") + + // 检查要更新的实体是否存在 + existingRecipe, err := s.recipeRepo.GetRecipeByID(serviceCtx, recipe.ID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrRecipeNotFound + } + return nil, fmt.Errorf("获取待更新的配方失败: %w", err) + } + + // 如果名称有变动,检查新名称是否与其它记录冲突 + if existingRecipe.Name != recipe.Name { + existing, err := s.recipeRepo.GetRecipeByName(serviceCtx, recipe.Name) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("检查新的配方名称失败: %w", err) + } + if existing != nil && existing.ID != recipe.ID { + return nil, ErrRecipeNameConflict + } + } + + if err := s.recipeRepo.UpdateRecipe(serviceCtx, recipe); err != nil { + return nil, fmt.Errorf("更新配方失败: %w", err) + } + return recipe, nil +} + +// DeleteRecipe 实现了删除配方的核心业务逻辑 +func (s *recipeCoreServiceImpl) DeleteRecipe(ctx context.Context, id uint32) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteRecipe") + + // 检查实体是否存在 + _, err := s.recipeRepo.GetRecipeByID(serviceCtx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRecipeNotFound + } + return fmt.Errorf("获取待删除的配方失败: %w", err) + } + + if err := s.recipeRepo.DeleteRecipe(serviceCtx, id); err != nil { + return fmt.Errorf("删除配方失败: %w", err) + } + return nil +} + +// ListRecipes 实现了列出配方的逻辑 +func (s *recipeCoreServiceImpl) ListRecipes(ctx context.Context, opts repository.RecipeListOptions, page, pageSize int) ([]models.Recipe, int64, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRecipes") + recipes, total, err := s.recipeRepo.ListRecipes(serviceCtx, opts, page, pageSize) + if err != nil { + return nil, 0, fmt.Errorf("获取配方列表失败: %w", err) + } + return recipes, total, nil +} + +// UpdateRecipeIngredients 实现了全量更新配方原料的业务逻辑 +func (s *recipeCoreServiceImpl) UpdateRecipeIngredients(ctx context.Context, recipeID uint32, ingredients []models.RecipeIngredient) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRecipeIngredients") + + // 1. 检查配方是否存在 + if _, err := s.recipeRepo.GetRecipeByID(serviceCtx, recipeID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRecipeNotFound + } + return fmt.Errorf("获取待更新原料的配方失败: %w", err) + } + + // 2. 在事务中执行替换操作 + err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + // 2.1. 删除旧的关联记录 + if err := s.recipeRepo.DeleteRecipeIngredientsByRecipeIDTx(serviceCtx, tx, recipeID); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + // 2.2. 创建新的关联记录 + // 确保每个原料都设置了正确的 RecipeID + for i := range ingredients { + ingredients[i].RecipeID = recipeID + } + if err := s.recipeRepo.CreateBatchRecipeIngredientsTx(serviceCtx, tx, ingredients); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + return nil + }) + + if err != nil { + return fmt.Errorf("更新配方原料事务执行失败: %w", err) + } + + // 3. 操作成功,直接返回 nil + return nil +} diff --git a/internal/domain/recipe/recipe_service.go b/internal/domain/recipe/recipe_service.go index 3641275..4ca5032 100644 --- a/internal/domain/recipe/recipe_service.go +++ b/internal/domain/recipe/recipe_service.go @@ -2,563 +2,47 @@ package recipe import ( "context" - "errors" - "fmt" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" - - "gorm.io/gorm" -) - -// 定义领域特定的错误 -var ( - ErrNutrientNameConflict = fmt.Errorf("营养种类名称已存在") - ErrNutrientNotFound = fmt.Errorf("营养种类不存在") - ErrRawMaterialNameConflict = fmt.Errorf("原料名称已存在") - ErrRawMaterialNotFound = fmt.Errorf("原料不存在") - ErrPigBreedInUse = fmt.Errorf("猪品种正在被猪类型使用,无法删除") - ErrPigBreedNotFound = fmt.Errorf("猪品种不存在") - ErrPigAgeStageInUse = fmt.Errorf("猪年龄阶段正在被猪类型使用,无法删除") - ErrPigAgeStageNotFound = fmt.Errorf("猪年龄阶段不存在") - ErrPigTypeNotFound = fmt.Errorf("猪类型不存在") ) // Service 定义了配方与原料领域的核心业务服务接口 +// 该接口聚合了所有子领域的服务接口 type Service interface { - // 营养种类相关接口 - CreateNutrient(ctx context.Context, name, description string) (*models.Nutrient, error) - UpdateNutrient(ctx context.Context, id uint32, name, description string) (*models.Nutrient, error) - DeleteNutrient(ctx context.Context, id uint32) error - GetNutrient(ctx context.Context, id uint32) (*models.Nutrient, error) - ListNutrients(ctx context.Context, opts repository.NutrientListOptions, page, pageSize int) ([]models.Nutrient, int64, error) - - // 原料相关接口 - CreateRawMaterial(ctx context.Context, name, description string) (*models.RawMaterial, error) - UpdateRawMaterial(ctx context.Context, id uint32, name, description string) (*models.RawMaterial, error) - DeleteRawMaterial(ctx context.Context, id uint32) error - GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) - ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) - UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error - - // 猪品种相关接口 - CreatePigBreed(ctx context.Context, breed *models.PigBreed) error - GetPigBreedByID(ctx context.Context, id uint32) (*models.PigBreed, error) - UpdatePigBreed(ctx context.Context, breed *models.PigBreed) error - DeletePigBreed(ctx context.Context, id uint32) error - ListPigBreeds(ctx context.Context, opts repository.PigBreedListOptions, page, pageSize int) ([]models.PigBreed, int64, error) - - // 猪年龄阶段相关接口 - CreatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error - GetPigAgeStageByID(ctx context.Context, id uint32) (*models.PigAgeStage, error) - UpdatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error - DeletePigAgeStage(ctx context.Context, id uint32) error - ListPigAgeStages(ctx context.Context, opts repository.PigAgeStageListOptions, page, pageSize int) ([]models.PigAgeStage, int64, error) - - // 猪类型相关接口 - CreatePigType(ctx context.Context, pigType *models.PigType) error - GetPigTypeByID(ctx context.Context, id uint32) (*models.PigType, error) - UpdatePigType(ctx context.Context, pigType *models.PigType) error - DeletePigType(ctx context.Context, id uint32) error - ListPigTypes(ctx context.Context, opts repository.PigTypeListOptions, page, pageSize int) ([]models.PigType, int64, error) - UpdatePigTypeNutrientRequirements(ctx context.Context, pigTypeID uint32, requirements []models.PigNutrientRequirement) error + NutrientService + RawMaterialService + PigBreedService + PigAgeStageService + PigTypeService + RecipeCoreService } -// recipeServiceImpl 是 RecipeService 的实现 +// recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现 type recipeServiceImpl struct { - ctx context.Context - uow repository.UnitOfWork - nutrientRepo repository.NutrientRepository - rawMaterialRepo repository.RawMaterialRepository - pigTypeRepo repository.PigTypeRepository + ctx context.Context + NutrientService + RawMaterialService + PigBreedService + PigAgeStageService + PigTypeService + RecipeCoreService } -// NewRecipeService 创建一个新的 RecipeService 实例 -func NewRecipeService(ctx context.Context, uow repository.UnitOfWork, nutrientRepo repository.NutrientRepository, rawMaterialRepo repository.RawMaterialRepository, pigTypeRepo repository.PigTypeRepository) Service { +// NewRecipeService 创建一个新的 Service 实例 +func NewRecipeService( + ctx context.Context, + nutrientService NutrientService, + rawMaterialService RawMaterialService, + pigBreedService PigBreedService, + pigAgeStageService PigAgeStageService, + pigTypeService PigTypeService, + recipeCoreService RecipeCoreService, +) Service { return &recipeServiceImpl{ - ctx: ctx, - uow: uow, - nutrientRepo: nutrientRepo, - rawMaterialRepo: rawMaterialRepo, - pigTypeRepo: pigTypeRepo, + ctx: ctx, + NutrientService: nutrientService, + RawMaterialService: rawMaterialService, + PigBreedService: pigBreedService, + PigAgeStageService: pigAgeStageService, + PigTypeService: pigTypeService, + RecipeCoreService: recipeCoreService, } } - -// CreateNutrient 实现了创建营养种类的核心业务逻辑 -func (s *recipeServiceImpl) CreateNutrient(ctx context.Context, name, description string) (*models.Nutrient, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateNutrient") - - // 检查名称是否已存在 - existing, err := s.nutrientRepo.GetNutrientByName(serviceCtx, name) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { // 只有不是记录未找到的错误才返回 - return nil, fmt.Errorf("检查营养种类名称失败: %w", err) - } - if existing != nil { - return nil, ErrNutrientNameConflict - } - - nutrient := &models.Nutrient{ - Name: name, - Description: description, - } - - if err := s.nutrientRepo.CreateNutrient(serviceCtx, nutrient); err != nil { - return nil, fmt.Errorf("创建营养种类失败: %w", err) - } - - return nutrient, nil -} - -// UpdateNutrient 实现了更新营养种类的核心业务逻辑 -func (s *recipeServiceImpl) UpdateNutrient(ctx context.Context, id uint32, name, description string) (*models.Nutrient, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateNutrient") - - // 检查要更新的实体是否存在 - nutrient, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { // 如果是记录未找到错误,则返回领域错误 - return nil, ErrNutrientNotFound - } - return nil, fmt.Errorf("获取待更新的营养种类失败: %w", err) - } - - // 如果名称有变动,检查新名称是否与其它记录冲突 - if nutrient.Name != name { - existing, err := s.nutrientRepo.GetNutrientByName(serviceCtx, name) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("检查新的营养种类名称失败: %w", err) - } - if existing != nil && existing.ID != id { - return nil, ErrNutrientNameConflict - } - } - - nutrient.Name = name - nutrient.Description = description - - if err := s.nutrientRepo.UpdateNutrient(serviceCtx, nutrient); err != nil { - return nil, fmt.Errorf("更新营养种类失败: %w", err) - } - - return nutrient, nil -} - -// DeleteNutrient 实现了删除营养种类的核心业务逻辑 -func (s *recipeServiceImpl) DeleteNutrient(ctx context.Context, id uint32) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteNutrient") - - // 检查实体是否存在 - _, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrNutrientNotFound - } - return fmt.Errorf("获取待删除的营养种类失败: %w", err) - } - - if err := s.nutrientRepo.DeleteNutrient(serviceCtx, id); err != nil { - return fmt.Errorf("删除营养种类失败: %w", err) - } - - return nil -} - -// GetNutrient 实现了获取单个营养种类的逻辑 -func (s *recipeServiceImpl) GetNutrient(ctx context.Context, id uint32) (*models.Nutrient, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetNutrient") - - nutrient, err := s.nutrientRepo.GetNutrientByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrNutrientNotFound - } - return nil, fmt.Errorf("获取营养种类失败: %w", err) - } - return nutrient, nil -} - -// ListNutrients 实现了列出营养种类的逻辑 -func (s *recipeServiceImpl) ListNutrients(ctx context.Context, opts repository.NutrientListOptions, page, pageSize int) ([]models.Nutrient, int64, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListNutrients") - - nutrients, total, err := s.nutrientRepo.ListNutrients(serviceCtx, opts, page, pageSize) - if err != nil { - return nil, 0, fmt.Errorf("获取营养种类列表失败: %w", err) - } - return nutrients, total, nil -} - -// CreateRawMaterial 实现了创建原料的核心业务逻辑 -func (s *recipeServiceImpl) CreateRawMaterial(ctx context.Context, name, description string) (*models.RawMaterial, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial") - - // 检查名称是否已存在 - existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("检查原料名称失败: %w", err) - } - if existing != nil { - return nil, ErrRawMaterialNameConflict - } - - rawMaterial := &models.RawMaterial{ - Name: name, - Description: description, - } - - if err := s.rawMaterialRepo.CreateRawMaterial(serviceCtx, rawMaterial); err != nil { - return nil, fmt.Errorf("创建原料失败: %w", err) - } - - return rawMaterial, nil -} - -// UpdateRawMaterial 实现了更新原料的核心业务逻辑 -func (s *recipeServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, name, description string) (*models.RawMaterial, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial") - - // 检查要更新的实体是否存在 - rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrRawMaterialNotFound - } - return nil, fmt.Errorf("获取待更新的原料失败: %w", err) - } - - // 如果名称有变动,检查新名称是否与其它记录冲突 - if rawMaterial.Name != name { - existing, err := s.rawMaterialRepo.GetRawMaterialByName(serviceCtx, name) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("检查新的原料名称失败: %w", err) - } - if existing != nil && existing.ID != id { - return nil, ErrRawMaterialNameConflict - } - } - - rawMaterial.Name = name - rawMaterial.Description = description - - if err := s.rawMaterialRepo.UpdateRawMaterial(serviceCtx, rawMaterial); err != nil { - return nil, fmt.Errorf("更新原料失败: %w", err) - } - - return rawMaterial, nil -} - -// DeleteRawMaterial 实现了删除原料的核心业务逻辑 -func (s *recipeServiceImpl) DeleteRawMaterial(ctx context.Context, id uint32) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeleteRawMaterial") - - // 检查实体是否存在 - _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrRawMaterialNotFound - } - return fmt.Errorf("获取待删除的原料失败: %w", err) - } - - if err := s.rawMaterialRepo.DeleteRawMaterial(serviceCtx, id); err != nil { - return fmt.Errorf("删除原料失败: %w", err) - } - - return nil -} - -// GetRawMaterial 实现了获取单个原料的逻辑 -func (s *recipeServiceImpl) GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetRawMaterial") - - rawMaterial, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrRawMaterialNotFound - } - return nil, fmt.Errorf("获取原料失败: %w", err) - } - return rawMaterial, nil -} - -// ListRawMaterials 实现了列出原料的逻辑 -func (s *recipeServiceImpl) ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterials") - - rawMaterials, total, err := s.rawMaterialRepo.ListRawMaterials(serviceCtx, opts, page, pageSize) - if err != nil { - return nil, 0, fmt.Errorf("获取原料列表失败: %w", err) - } - return rawMaterials, total, nil -} - -// UpdateRawMaterialNutrients 实现了全量更新原料营养成分的业务逻辑 -func (s *recipeServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients") - - // 1. 检查原料是否存在 - if _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, rawMaterialID); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrRawMaterialNotFound - } - return fmt.Errorf("获取待更新的原料失败: %w", err) - } - - // 2. 在事务中执行替换操作 - err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { - // 2.1. 删除旧的关联记录 - if err := s.rawMaterialRepo.DeleteNutrientsByRawMaterialIDTx(serviceCtx, tx, rawMaterialID); err != nil { - return err // 错误已在仓库层封装,直接返回 - } - - // 2.2. 创建新的关联记录 - if err := s.rawMaterialRepo.CreateBatchRawMaterialNutrientsTx(serviceCtx, tx, nutrients); err != nil { - return err // 错误已在仓库层封装,直接返回 - } - - return nil - }) - - if err != nil { - return fmt.Errorf("更新原料营养成分事务执行失败: %w", err) - } - - // 3. 操作成功,直接返回 nil - return nil -} - -// CreatePigBreed 实现了创建猪品种的核心业务逻辑 -func (s *recipeServiceImpl) CreatePigBreed(ctx context.Context, breed *models.PigBreed) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBreed") - if err := s.pigTypeRepo.CreatePigBreed(serviceCtx, breed); err != nil { - return fmt.Errorf("创建猪品种失败: %w", err) - } - return nil -} - -// GetPigBreedByID 实现了获取单个猪品种的逻辑 -func (s *recipeServiceImpl) GetPigBreedByID(ctx context.Context, id uint32) (*models.PigBreed, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigBreedByID") - breed, err := s.pigTypeRepo.GetPigBreedByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrPigBreedNotFound - } - return nil, fmt.Errorf("获取猪品种失败: %w", err) - } - return breed, nil -} - -// UpdatePigBreed 实现了更新猪品种的核心业务逻辑 -func (s *recipeServiceImpl) UpdatePigBreed(ctx context.Context, breed *models.PigBreed) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigBreed") - if err := s.pigTypeRepo.UpdatePigBreed(serviceCtx, breed); err != nil { - return fmt.Errorf("更新猪品种失败: %w", err) - } - return nil -} - -// DeletePigBreed 实现了删除猪品种的核心业务逻辑 -func (s *recipeServiceImpl) DeletePigBreed(ctx context.Context, id uint32) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigBreed") - - // 检查是否有猪类型关联到该品种 - opts := repository.PigTypeListOptions{BreedID: &id} - pigTypes, _, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, 1, 1) // 只需检查是否存在,所以取1条 - if err != nil { - return fmt.Errorf("检查猪品种关联失败: %w", err) - } - if len(pigTypes) > 0 { - return ErrPigBreedInUse - } - - // 检查实体是否存在 - _, err = s.pigTypeRepo.GetPigBreedByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrPigBreedNotFound - } - return fmt.Errorf("获取待删除的猪品种失败: %w", err) - } - - if err := s.pigTypeRepo.DeletePigBreed(serviceCtx, id); err != nil { - return fmt.Errorf("删除猪品种失败: %w", err) - } - return nil -} - -// ListPigBreeds 实现了列出猪品种的逻辑 -func (s *recipeServiceImpl) ListPigBreeds(ctx context.Context, opts repository.PigBreedListOptions, page, pageSize int) ([]models.PigBreed, int64, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigBreeds") - breeds, total, err := s.pigTypeRepo.ListPigBreeds(serviceCtx, opts, page, pageSize) - if err != nil { - return nil, 0, fmt.Errorf("获取猪品种列表失败: %w", err) - } - return breeds, total, nil -} - -// CreatePigAgeStage 实现了创建猪年龄阶段的核心业务逻辑 -func (s *recipeServiceImpl) CreatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigAgeStage") - if err := s.pigTypeRepo.CreatePigAgeStage(serviceCtx, ageStage); err != nil { - return fmt.Errorf("创建猪年龄阶段失败: %w", err) - } - return nil -} - -// GetPigAgeStageByID 实现了获取单个猪年龄阶段的逻辑 -func (s *recipeServiceImpl) GetPigAgeStageByID(ctx context.Context, id uint32) (*models.PigAgeStage, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigAgeStageByID") - ageStage, err := s.pigTypeRepo.GetPigAgeStageByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrPigAgeStageNotFound - } - return nil, fmt.Errorf("获取猪年龄阶段失败: %w", err) - } - return ageStage, nil -} - -// UpdatePigAgeStage 实现了更新猪年龄阶段的核心业务逻辑 -func (s *recipeServiceImpl) UpdatePigAgeStage(ctx context.Context, ageStage *models.PigAgeStage) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigAgeStage") - if err := s.pigTypeRepo.UpdatePigAgeStage(serviceCtx, ageStage); err != nil { - return fmt.Errorf("更新猪年龄阶段失败: %w", err) - } - return nil -} - -// DeletePigAgeStage 实现了删除猪年龄阶段的核心业务逻辑 -func (s *recipeServiceImpl) DeletePigAgeStage(ctx context.Context, id uint32) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigAgeStage") - - // 检查是否有猪类型关联到该年龄阶段 - opts := repository.PigTypeListOptions{AgeStageID: &id} - pigTypes, _, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, 1, 1) // 只需检查是否存在,所以取1条 - if err != nil { - return fmt.Errorf("检查猪年龄阶段关联失败: %w", err) - } - if len(pigTypes) > 0 { - return ErrPigAgeStageInUse - } - - // 检查实体是否存在 - _, err = s.pigTypeRepo.GetPigAgeStageByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrPigAgeStageNotFound - } - return fmt.Errorf("获取待删除的猪年龄阶段失败: %w", err) - } - - if err := s.pigTypeRepo.DeletePigAgeStage(serviceCtx, id); err != nil { - return fmt.Errorf("删除猪年龄阶段失败: %w", err) - } - return nil -} - -// ListPigAgeStages 实现了列出猪年龄阶段的逻辑 -func (s *recipeServiceImpl) ListPigAgeStages(ctx context.Context, opts repository.PigAgeStageListOptions, page, pageSize int) ([]models.PigAgeStage, int64, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigAgeStages") - ageStages, total, err := s.pigTypeRepo.ListPigAgeStages(serviceCtx, opts, page, pageSize) - if err != nil { - return nil, 0, fmt.Errorf("获取猪年龄阶段列表失败: %w", err) - } - return ageStages, total, nil -} - -// CreatePigType 实现了创建猪类型的核心业务逻辑 -func (s *recipeServiceImpl) CreatePigType(ctx context.Context, pigType *models.PigType) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigType") - if err := s.pigTypeRepo.CreatePigType(serviceCtx, pigType); err != nil { - return fmt.Errorf("创建猪类型失败: %w", err) - } - return nil -} - -// GetPigTypeByID 实现了获取单个猪类型的逻辑 -func (s *recipeServiceImpl) GetPigTypeByID(ctx context.Context, id uint32) (*models.PigType, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetPigTypeByID") - pigType, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, ErrPigTypeNotFound - } - return nil, fmt.Errorf("获取猪类型失败: %w", err) - } - return pigType, nil -} - -// UpdatePigType 实现了更新猪类型的核心业务逻辑 -func (s *recipeServiceImpl) UpdatePigType(ctx context.Context, pigType *models.PigType) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigType") - if err := s.pigTypeRepo.UpdatePigType(serviceCtx, pigType); err != nil { - return fmt.Errorf("更新猪类型失败: %m", err) - } - return nil -} - -// DeletePigType 实现了删除猪类型的核心业务逻辑 -func (s *recipeServiceImpl) DeletePigType(ctx context.Context, id uint32) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "DeletePigType") - - // 检查实体是否存在 - _, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrPigTypeNotFound - } - return fmt.Errorf("获取待删除的猪类型失败: %w", err) - } - - if err := s.pigTypeRepo.DeletePigType(serviceCtx, id); err != nil { - return fmt.Errorf("删除猪类型失败: %w", err) - } - return nil -} - -// ListPigTypes 实现了列出猪类型的逻辑 -func (s *recipeServiceImpl) ListPigTypes(ctx context.Context, opts repository.PigTypeListOptions, page, pageSize int) ([]models.PigType, int64, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListPigTypes") - pigTypes, total, err := s.pigTypeRepo.ListPigTypes(serviceCtx, opts, page, pageSize) - if err != nil { - return nil, 0, fmt.Errorf("获取猪类型列表失败: %w", err) - } - return pigTypes, total, nil -} - -// UpdatePigTypeNutrientRequirements 实现了全量更新猪类型营养需求的核心业务逻辑 -func (s *recipeServiceImpl) UpdatePigTypeNutrientRequirements(ctx context.Context, pigTypeID uint32, requirements []models.PigNutrientRequirement) error { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdatePigTypeNutrientRequirements") - - // 1. 检查猪类型是否存在 - if _, err := s.pigTypeRepo.GetPigTypeByID(serviceCtx, pigTypeID); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrPigTypeNotFound - } - return fmt.Errorf("获取待更新营养需求的猪类型失败: %w", err) - } - - // 2. 在事务中执行替换操作 - err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { - // 2.1. 删除旧的关联记录 - if err := s.pigTypeRepo.DeletePigNutrientRequirementsByPigTypeIDTx(serviceCtx, tx, pigTypeID); err != nil { - return err // 错误已在仓库层封装,直接返回 - } - - // 2.2. 创建新的关联记录 - if err := s.pigTypeRepo.CreateBatchPigNutrientRequirementsTx(serviceCtx, tx, requirements); err != nil { - return err // 错误已在仓库层封装,直接返回 - } - - return nil - }) - - if err != nil { - return fmt.Errorf("更新猪类型营养需求事务执行失败: %w", err) - } - - // 3. 操作成功,直接返回 nil - return nil -} diff --git a/internal/infra/repository/raw_material_repository.go b/internal/infra/repository/raw_material_repository.go index 3df2102..1862ba4 100644 --- a/internal/infra/repository/raw_material_repository.go +++ b/internal/infra/repository/raw_material_repository.go @@ -176,13 +176,21 @@ func (r *gormRawMaterialRepository) DeleteNutrientsByRawMaterialIDTx(ctx context // CreateBatchRawMaterialNutrientsTx 在事务中批量创建原料营养成分 func (r *gormRawMaterialRepository) CreateBatchRawMaterialNutrientsTx(ctx context.Context, db *gorm.DB, nutrients []models.RawMaterialNutrient) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateBatchRawMaterialNutrientsTx") + // 如果没有要创建的记录,直接返回成功,避免执行空的Create语句 if len(nutrients) == 0 { return nil } - repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateBatchRawMaterialNutrientsTx") - tx := db.WithContext(repoCtx) - if err := tx.Create(&nutrients).Error; err != nil { + + // 确保每个营养都关联到正确的原料ID + // 注意:这里假设传入的 nutrients 已经设置了正确的 RawMaterialID + for i := range nutrients { + if nutrients[i].RawMaterialID == 0 { + return fmt.Errorf("创建原料营养时 RecipeID 不能为空") + } + } + if err := db.WithContext(repoCtx).Create(&nutrients).Error; err != nil { return fmt.Errorf("批量创建原料营养成分失败: %w", err) } return nil diff --git a/internal/infra/repository/recipe_repository.go b/internal/infra/repository/recipe_repository.go index 4d416a6..176cc1b 100644 --- a/internal/infra/repository/recipe_repository.go +++ b/internal/infra/repository/recipe_repository.go @@ -25,8 +25,11 @@ type RecipeRepository interface { GetRecipeByName(ctx context.Context, name string) (*models.Recipe, error) ListRecipes(ctx context.Context, opts RecipeListOptions, page, pageSize int) ([]models.Recipe, int64, error) UpdateRecipe(ctx context.Context, recipe *models.Recipe) error - UpdateRecipeIngredients(ctx context.Context, recipeID uint32, ingredients []models.RecipeIngredient) error DeleteRecipe(ctx context.Context, id uint32) error + // 在事务中删除配方原料 + DeleteRecipeIngredientsByRecipeIDTx(ctx context.Context, tx *gorm.DB, recipeID uint32) error + // 在事务中批量创建配方原料 + CreateBatchRecipeIngredientsTx(ctx context.Context, tx *gorm.DB, ingredients []models.RecipeIngredient) error } // gormRecipeRepository 是 RecipeRepository 的 GORM 实现 @@ -43,12 +46,12 @@ func NewGormRecipeRepository(ctx context.Context, db *gorm.DB) RecipeRepository // CreateRecipe 创建一个新的配方,并处理其关联的配方原料 func (r *gormRecipeRepository) CreateRecipe(ctx context.Context, recipe *models.Recipe) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateRecipe") - return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { - if err := tx.Create(recipe).Error; err != nil { - return fmt.Errorf("创建配方失败: %w", err) - } - return nil - }) + // 注意:这里的事务只针对 Recipe 本身,如果 RecipeIngredient 也在同一个 Create 中,GORM 会自动处理。 + // 但如果 RecipeIngredient 是单独操作,则需要外部事务。 + if err := r.db.WithContext(repoCtx).Create(recipe).Error; err != nil { + return fmt.Errorf("创建配方失败: %w", err) + } + return nil } // GetRecipeByID 根据ID获取单个配方,并预加载其关联的配方原料和原料信息 @@ -130,29 +133,6 @@ func (r *gormRecipeRepository) UpdateRecipe(ctx context.Context, recipe *models. return nil } -// UpdateRecipeIngredients 更新配方关联的原料列表 -func (r *gormRecipeRepository) UpdateRecipeIngredients(ctx context.Context, recipeID uint32, ingredients []models.RecipeIngredient) error { - repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateRecipeIngredients") - - return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error { - // 1. 删除所有旧的关联配方原料 - if err := tx.Where("recipe_id = ?", recipeID).Delete(&models.RecipeIngredient{}).Error; err != nil { - return fmt.Errorf("删除旧的配方原料失败: %w", err) - } - - // 2. 批量创建新的关联配方原料 - if len(ingredients) > 0 { - for i := range ingredients { - ingredients[i].RecipeID = recipeID - } - if err := tx.Create(&ingredients).Error; err != nil { - return fmt.Errorf("创建新的配方原料失败: %w", err) - } - } - return nil - }) -} - // DeleteRecipe 根据ID删除一个配方,并级联软删除关联的 RecipeIngredient 记录 func (r *gormRecipeRepository) DeleteRecipe(ctx context.Context, id uint32) error { repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteRecipe") @@ -180,3 +160,31 @@ func (r *gormRecipeRepository) DeleteRecipe(ctx context.Context, id uint32) erro return nil }) } + +// DeleteRecipeIngredientsByRecipeIDTx 在给定事务中删除配方原料 +func (r *gormRecipeRepository) DeleteRecipeIngredientsByRecipeIDTx(ctx context.Context, tx *gorm.DB, recipeID uint32) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteRecipeIngredientsByRecipeIDTx") + if err := tx.WithContext(repoCtx).Where("recipe_id = ?", recipeID).Delete(&models.RecipeIngredient{}).Error; err != nil { + return fmt.Errorf("删除配方 %d 的原料失败: %w", recipeID, err) + } + return nil +} + +// CreateBatchRecipeIngredientsTx 在给定事务中批量创建配方原料 +func (r *gormRecipeRepository) CreateBatchRecipeIngredientsTx(ctx context.Context, tx *gorm.DB, ingredients []models.RecipeIngredient) error { + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateBatchRecipeIngredientsTx") + if len(ingredients) == 0 { + return nil // 没有原料需要创建 + } + // 确保每个原料都关联到正确的配方ID + // 注意:这里假设传入的 ingredients 已经设置了正确的 RecipeID + for i := range ingredients { + if ingredients[i].RecipeID == 0 { + return fmt.Errorf("创建配方原料时 RecipeID 不能为空") + } + } + if err := tx.WithContext(repoCtx).Create(&ingredients).Error; err != nil { + return fmt.Errorf("批量创建配方原料失败: %w", err) + } + return nil +}