159 lines
5.6 KiB
Go
159 lines
5.6 KiB
Go
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, // 注意:这里的排序可能需要调整,比如按原料名排序
|
||
HasStock: req.HasStock,
|
||
}
|
||
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
|
||
}
|