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" ) // StockQuerier 定义了从外部领域查询库存的接口。 // 这样,配方领域就不需要知道库存是如何存储或计算的。 type StockQuerier interface { // GetCurrentStock 根据原料ID获取当前库存量。 GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error) } // 定义领域特定的错误 var ( ErrRawMaterialNameConflict = fmt.Errorf("原料名称已存在") ErrRawMaterialNotFound = fmt.Errorf("原料不存在") ErrStockNotEmpty = fmt.Errorf("原料尚有库存,无法删除") ErrRawMaterialInUseByRecipe = fmt.Errorf("原料已被配方使用,无法删除") ) // RawMaterialService 定义了原料领域的核心业务服务接口 type RawMaterialService interface { CreateRawMaterial(ctx context.Context, name, description string, referencePrice float32) (*models.RawMaterial, error) UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32) (*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 stockQuerier StockQuerier } // NewRawMaterialService 创建一个新的 RawMaterialService 实例 func NewRawMaterialService(ctx context.Context, uow repository.UnitOfWork, rawMaterialRepo repository.RawMaterialRepository, stockQuerier StockQuerier) RawMaterialService { return &rawMaterialServiceImpl{ ctx: ctx, uow: uow, rawMaterialRepo: rawMaterialRepo, stockQuerier: stockQuerier, } } // CreateRawMaterial 实现了创建原料的核心业务逻辑 func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, description string, referencePrice float32) (*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, ReferencePrice: referencePrice, } 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, referencePrice float32) (*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 rawMaterial.ReferencePrice = referencePrice 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) } // 检查原料是否有库存 stock, err := s.stockQuerier.GetCurrentStock(serviceCtx, id) if err != nil { return fmt.Errorf("检查原料库存失败: %w", err) } if stock > 0 { // 如果库存大于0,返回业务错误,阻止删除 return ErrStockNotEmpty } // 检查原料是否被配方使用 isUsed, err := s.rawMaterialRepo.IsRawMaterialUsedInRecipes(serviceCtx, id) if err != nil { return fmt.Errorf("检查原料是否被配方使用失败: %w", err) } if isUsed { return ErrRawMaterialInUseByRecipe } 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 }