171 lines
5.7 KiB
Go
171 lines
5.7 KiB
Go
|
|
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
|
|||
|
|
}
|