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 }