package service import ( "context" "errors" "fmt" "time" "git.huangwc.com/pig/pig-farm-controller/internal/app/dto" "git.huangwc.com/pig/pig-farm-controller/internal/domain/inventory" "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" ) // 定义库存应用服务特定的错误 var ( ErrInventoryRawMaterialNotFound = errors.New("原料不存在") ErrInventoryInsufficientStock = errors.New("原料库存不足") ) // InventoryService 定义了库存相关的应用服务接口 type InventoryService interface { AdjustStock(ctx context.Context, req *dto.StockAdjustmentRequest) (*dto.StockLogResponse, error) ListCurrentStock(ctx context.Context, req *dto.ListCurrentStockRequest) (*dto.ListCurrentStockResponse, error) ListStockLogs(ctx context.Context, req *dto.ListStockLogRequest) (*dto.ListStockLogResponse, error) } // inventoryServiceImpl 是 InventoryService 接口的实现 type inventoryServiceImpl struct { ctx context.Context invSvc inventory.InventoryCoreService rawMatRepo repository.RawMaterialRepository } // NewInventoryService 创建一个新的 InventoryService 实例 func NewInventoryService(ctx context.Context, invSvc inventory.InventoryCoreService, rawMatRepo repository.RawMaterialRepository) InventoryService { return &inventoryServiceImpl{ ctx: ctx, invSvc: invSvc, rawMatRepo: rawMatRepo, } } // AdjustStock 手动调整库存 func (s *inventoryServiceImpl) AdjustStock(ctx context.Context, req *dto.StockAdjustmentRequest) (*dto.StockLogResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "AdjustStock") // 调用领域服务执行核心业务逻辑 log, err := s.invSvc.AdjustStock(serviceCtx, req.RawMaterialID, req.ChangeAmount, models.StockLogSourceManual, nil, req.Remarks) if err != nil { if errors.Is(err, inventory.ErrRawMaterialNotFound) { return nil, ErrInventoryRawMaterialNotFound } if errors.Is(err, inventory.ErrInsufficientStock) { return nil, ErrInventoryInsufficientStock } return nil, fmt.Errorf("调整库存失败: %w", err) } // 手动加载 RawMaterial 信息,因为 CreateRawMaterialStockLog 不会预加载它 rawMaterial, err := s.rawMatRepo.GetRawMaterialByID(serviceCtx, log.RawMaterialID) if err != nil { // 理论上不应该发生,因为 AdjustStock 内部已经检查过 return nil, fmt.Errorf("获取原料信息失败: %w", err) } log.RawMaterial = *rawMaterial return dto.ConvertStockLogToDTO(log), nil } // ListCurrentStock 列出所有原料的当前库存 func (s *inventoryServiceImpl) ListCurrentStock(ctx context.Context, req *dto.ListCurrentStockRequest) (*dto.ListCurrentStockResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListCurrentStock") // 1. 获取分页的原料列表 rawMatOpts := repository.RawMaterialListOptions{ Name: req.RawMaterialName, OrderBy: req.OrderBy, // 注意:这里的排序可能需要调整,比如按原料名排序 } rawMaterials, total, err := s.rawMatRepo.ListRawMaterials(serviceCtx, rawMatOpts, req.Page, req.PageSize) if err != nil { return nil, fmt.Errorf("获取原料列表失败: %w", err) } if len(rawMaterials) == 0 { return &dto.ListCurrentStockResponse{ List: []dto.CurrentStockResponse{}, Pagination: dto.PaginationDTO{Page: req.Page, PageSize: req.PageSize, Total: total}, }, nil } // 2. 提取原料ID并批量获取它们的最新库存日志 materialIDs := make([]uint32, len(rawMaterials)) for i, rm := range rawMaterials { materialIDs[i] = rm.ID } latestLogMap, err := s.rawMatRepo.BatchGetLatestStockLogsForMaterials(serviceCtx, materialIDs) if err != nil { return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err) } // 3. 组合原料信息和库存信息 stockDTOs := make([]dto.CurrentStockResponse, len(rawMaterials)) for i, rm := range rawMaterials { log, _ := latestLogMap[rm.ID] // 如果找不到,log会是零值 stockDTOs[i] = *dto.ConvertCurrentStockToDTO(&rm, &log) } return &dto.ListCurrentStockResponse{ List: stockDTOs, Pagination: dto.PaginationDTO{Page: req.Page, PageSize: req.PageSize, Total: total}, }, nil } // ListStockLogs 列出库存变动历史 func (s *inventoryServiceImpl) ListStockLogs(ctx context.Context, req *dto.ListStockLogRequest) (*dto.ListStockLogResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListStockLogs") // 解析时间字符串 var startTime, endTime *time.Time if req.StartTime != nil && *req.StartTime != "" { t, err := time.Parse(time.RFC3339, *req.StartTime) if err != nil { return nil, fmt.Errorf("无效的开始时间格式: %w", err) } startTime = &t } if req.EndTime != nil && *req.EndTime != "" { t, err := time.Parse(time.RFC3339, *req.EndTime) if err != nil { return nil, fmt.Errorf("无效的结束时间格式: %w", err) } endTime = &t } // 转换 source types sourceTypes := make([]models.StockLogSourceType, len(req.SourceTypes)) for i, st := range req.SourceTypes { sourceTypes[i] = models.StockLogSourceType(st) } opts := repository.StockLogListOptions{ RawMaterialID: req.RawMaterialID, SourceTypes: sourceTypes, StartTime: startTime, EndTime: endTime, OrderBy: req.OrderBy, } logs, total, err := s.invSvc.ListStockLogs(serviceCtx, opts, req.Page, req.PageSize) if err != nil { return nil, fmt.Errorf("获取库存日志列表失败: %w", err) } return dto.ConvertStockLogListToDTO(logs, total, req.Page, req.PageSize), nil }