Files
pig-farm-controller/internal/domain/recipe/recipe_service.go
2025-11-22 16:44:22 +08:00

529 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
// recipeServiceImpl 是 RecipeService 的实现
type recipeServiceImpl struct {
ctx context.Context
uow repository.UnitOfWork
nutrientRepo repository.NutrientRepository
rawMaterialRepo repository.RawMaterialRepository
pigTypeRepo repository.PigTypeRepository
}
// NewRecipeService 创建一个新的 RecipeService 实例
func NewRecipeService(ctx context.Context, uow repository.UnitOfWork, nutrientRepo repository.NutrientRepository, rawMaterialRepo repository.RawMaterialRepository, pigTypeRepo repository.PigTypeRepository) Service {
return &recipeServiceImpl{
ctx: ctx,
uow: uow,
nutrientRepo: nutrientRepo,
rawMaterialRepo: rawMaterialRepo,
pigTypeRepo: pigTypeRepo,
}
}
// 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
}