实现库存管理相关逻辑
This commit is contained in:
170
internal/domain/inventory/inventory_service.go
Normal file
170
internal/domain/inventory/inventory_service.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user