实现库存管理相关逻辑

This commit is contained in:
2025-11-25 18:10:28 +08:00
parent ae27eb142d
commit 44ff3b19d6
15 changed files with 1531 additions and 22 deletions

View File

@@ -0,0 +1,170 @@
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
}