material_manage_system/internal/service/inbound.go

270 lines
6.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"time"
"github.com/shopspring/decimal"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/internal/repository"
)
type InboundService struct {
inboundRepo *repository.InboundRepo
materialSvc *MaterialService
stockRepo *repository.StockRepo
}
func NewInboundService() *InboundService {
return &InboundService{
inboundRepo: repository.NewInboundRepo(),
materialSvc: NewMaterialService(),
stockRepo: repository.NewStockRepo(),
}
}
func (s *InboundService) Create(req *model.CreateInboundRequest) (*model.InboundOrder, error) {
order := &model.InboundOrder{
InboundDate: time.Now(),
Handler: req.Handler,
Supplier: req.Supplier,
Items: make([]model.InboundItem, len(req.Items)),
}
// Validate all items first (outside transaction)
for i, item := range req.Items {
exists, err := s.materialSvc.Exists(item.MaterialID)
if err != nil {
return nil, err
}
if !exists {
return nil, ErrMaterialNotFound
}
if item.WarningStock != nil && item.WarningStock.IsNegative() {
return nil, ErrInvalidWarningStock
}
order.Items[i] = model.InboundItem{
MaterialID: item.MaterialID,
Quantity: item.Quantity,
}
}
// Start transaction
tx, err := repository.BeginTx()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
// Create inbound order within transaction
err = s.inboundRepo.CreateTx(tx, order)
if err != nil {
return nil, err
}
// Update stock and create logs within transaction
for i := range order.Items {
item := &order.Items[i]
// 这里必须用赋值而不是 := ,否则 err 会被遮蔽,defer 里的回滚判断看不到它。
var material *model.Material
material, err = s.materialSvc.GetByID(item.MaterialID)
if err != nil {
return nil, err
}
item.MaterialCode = material.MaterialCode
item.MaterialName = material.Name
err = s.stockRepo.UpdateStockTx(tx, item.MaterialID, decimal.NewFromInt(int64(item.Quantity)))
if err != nil {
return nil, err
}
// 入库明细上带的预警阈值在同一个事务里写入,保证物料的第一个阈值和第一行库存
// 同时落库。阈值为 nil 表示本次不改动已存的值。
if warningStock := req.Items[i].WarningStock; warningStock != nil {
err = s.stockRepo.UpdateWarningStockTx(tx, item.MaterialID, *warningStock, true)
if err != nil {
return nil, err
}
}
stockLog := &model.StockLog{
MaterialID: item.MaterialID,
Type: "in",
Quantity: item.Quantity,
RelatedOrderType: "inbound",
RelatedOrderID: order.ID,
}
err = s.stockRepo.CreateLogTx(tx, stockLog)
if err != nil {
return nil, err
}
}
// Commit transaction
if err = tx.Commit(); err != nil {
return nil, err
}
order.Status = "completed"
return order, nil
}
func (s *InboundService) GetByID(id int64) (*model.InboundOrder, error) {
return s.inboundRepo.GetByID(id)
}
func (s *InboundService) List(search string, limit, offset int) ([]model.InboundOrder, int64, error) {
return s.inboundRepo.List(search, limit, offset)
}
func (s *InboundService) GetByMaterialID(materialID int64) ([]model.InboundRecord, error) {
return s.inboundRepo.GetByMaterialID(materialID)
}
func (s *InboundService) Delete(id int64) error {
// Get the inbound order with items
order, err := s.inboundRepo.GetByID(id)
if err != nil {
return err
}
// Start transaction
tx, err := repository.BeginTx()
if err != nil {
return err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
// Reverse stock for each item within transaction
for _, item := range order.Items {
err = s.stockRepo.UpdateStockTx(tx, item.MaterialID, decimal.NewFromInt(int64(-item.Quantity)))
if err != nil {
return err
}
}
// Delete stock logs within transaction
err = s.stockRepo.DeleteLogsTx(tx, "inbound", id)
if err != nil {
return err
}
// Delete the inbound order (items will be cascade deleted)
err = s.inboundRepo.DeleteTx(tx, id)
if err != nil {
return err
}
// Commit transaction
if err = tx.Commit(); err != nil {
return err
}
return nil
}
func (s *InboundService) Update(id int64, req *model.UpdateInboundRequest) (*model.InboundOrder, error) {
// Get old order data
oldOrder, err := s.inboundRepo.GetByID(id)
if err != nil {
return nil, err
}
// 阈值校验放在事务之外,避免为非法输入白开一个事务
for _, item := range req.Items {
if item.WarningStock != nil && item.WarningStock.IsNegative() {
return nil, ErrInvalidWarningStock
}
}
// Start transaction
tx, err := repository.BeginTx()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Rollback()
}
}()
// Reverse stock for old items (subtract what was previously added)
for _, item := range oldOrder.Items {
_, err = s.stockRepo.GetStockForUpdateTx(tx, item.MaterialID)
if err != nil {
return nil, err
}
err = s.stockRepo.UpdateStockTx(tx, item.MaterialID, decimal.NewFromInt(int64(-item.Quantity)))
if err != nil {
return nil, err
}
}
// Delete old stock logs
err = s.stockRepo.DeleteLogsTx(tx, "inbound", id)
if err != nil {
return nil, err
}
// Update order in repository
err = s.inboundRepo.UpdateTx(tx, id, req)
if err != nil {
return nil, err
}
// Add stock for new items and create logs
for _, item := range req.Items {
_, err = s.stockRepo.GetStockForUpdateTx(tx, item.MaterialID)
if err != nil {
return nil, err
}
err = s.stockRepo.UpdateStockTx(tx, item.MaterialID, decimal.NewFromInt(int64(item.Quantity)))
if err != nil {
return nil, err
}
// 阈值随明细一起更新,nil 表示不改动已存的值,避免编辑旧入库单时把刚调好的
// 阈值覆盖回旧数。
if item.WarningStock != nil {
err = s.stockRepo.UpdateWarningStockTx(tx, item.MaterialID, *item.WarningStock, true)
if err != nil {
return nil, err
}
}
stockLog := &model.StockLog{
MaterialID: item.MaterialID,
Type: "in",
Quantity: item.Quantity,
RelatedOrderType: "inbound",
RelatedOrderID: id,
}
err = s.stockRepo.CreateLogTx(tx, stockLog)
if err != nil {
return nil, err
}
}
// Commit transaction
if err = tx.Commit(); err != nil {
return nil, err
}
// Get updated order with material details
updatedOrder, err := s.inboundRepo.GetByID(id)
if err != nil {
return nil, err
}
updatedOrder.Status = "completed"
return updatedOrder, nil
}