package inventory import ( "context" "errors" "fmt" "sync" "time" "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 ( ErrRawMaterialNotFound = errors.New("原料不存在") ErrInsufficientStock = errors.New("原料库存不足") ) // InventoryCoreService 定义了库存领域的核心业务服务接口 type InventoryCoreService interface { // AdjustStock 调整指定原料的库存 AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error) // GetCurrentStock 获取单个原料的当前库存量 GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error) // BatchGetCurrentStock 批量获取多个原料的当前库存量 BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error) // ListStockLogs 分页查询库存变动日志 ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) } // inventoryCoreServiceImpl 是 InventoryCoreService 的实现 type inventoryCoreServiceImpl struct { ctx context.Context uow repository.UnitOfWork rawMatRepo repository.RawMaterialRepository // 全局库存调整锁,确保所有 AdjustStock 操作串行执行 adjustStockMutex sync.Mutex } // NewInventoryCoreService 创建一个新的 InventoryCoreService 实例 func NewInventoryCoreService(ctx context.Context, uow repository.UnitOfWork, rawMatRepo repository.RawMaterialRepository) InventoryCoreService { return &inventoryCoreServiceImpl{ ctx: ctx, uow: uow, rawMatRepo: rawMatRepo, } } // AdjustStock 调整指定原料的库存 func (s *inventoryCoreServiceImpl) AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "AdjustStock") // 使用全局锁确保所有库存调整操作串行执行 s.adjustStockMutex.Lock() defer s.adjustStockMutex.Unlock() var createdLog *models.RawMaterialStockLog err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { // 在事务中创建 RawMaterialRepository 的新实例 txRawMatRepo := repository.NewGormRawMaterialRepository(serviceCtx, tx) // 1. 检查原料是否存在 _, err := txRawMatRepo.GetRawMaterialByID(serviceCtx, rawMaterialID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrRawMaterialNotFound } return fmt.Errorf("检查原料是否存在时出错: %w", err) } // 2. 获取当前库存 (在程序锁的保护下,这里是安全的) latestLog, err := txRawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID) if err != nil { return fmt.Errorf("获取最新库存日志失败: %w", err) } var beforeQuantity float32 = 0 if latestLog != nil { beforeQuantity = latestLog.AfterQuantity } // 3. 计算新库存并检查是否充足 afterQuantity := beforeQuantity + changeAmount if afterQuantity < 0 { return ErrInsufficientStock } // 4. 创建新的库存日志 newLog := &models.RawMaterialStockLog{ RawMaterialID: rawMaterialID, ChangeAmount: changeAmount, BeforeQuantity: beforeQuantity, AfterQuantity: afterQuantity, SourceType: sourceType, SourceID: sourceID, HappenedAt: time.Now(), Remarks: remarks, } if err := txRawMatRepo.CreateRawMaterialStockLog(serviceCtx, newLog); err != nil { return fmt.Errorf("创建库存日志失败: %w", err) } createdLog = newLog return nil }) if err != nil { return nil, err // 直接返回事务中发生的错误 } return createdLog, nil } // GetCurrentStock 获取单个原料的当前库存量 func (s *inventoryCoreServiceImpl) GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentStock") latestLog, err := s.rawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID) if err != nil { return 0, fmt.Errorf("获取最新库存日志失败: %w", err) } if latestLog == nil { // 如果没有日志,说明从未入库,库存为0 return 0, nil } return latestLog.AfterQuantity, nil } // BatchGetCurrentStock 批量获取多个原料的当前库存量 func (s *inventoryCoreServiceImpl) BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "BatchGetCurrentStock") logMap, err := s.rawMatRepo.BatchGetLatestStockLogsForMaterials(serviceCtx, rawMaterialIDs) if err != nil { return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err) } stockMap := make(map[uint32]float32, len(rawMaterialIDs)) for _, id := range rawMaterialIDs { if log, ok := logMap[id]; ok { stockMap[id] = log.AfterQuantity } else { // 如果某个原料在 logMap 中不存在,说明它没有任何库存记录,库存为0 stockMap[id] = 0 } } return stockMap, nil } // ListStockLogs 分页查询库存变动日志 func (s *inventoryCoreServiceImpl) ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListStockLogs") logs, total, err := s.rawMatRepo.ListStockLogs(serviceCtx, opts, page, pageSize) if err != nil { return nil, 0, fmt.Errorf("获取库存日志列表失败: %w", err) } return logs, total, nil }