169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
"github.com/shopspring/decimal"
|
|
"github.com/wmms/wmms/internal/model"
|
|
"github.com/wmms/wmms/internal/repository"
|
|
)
|
|
|
|
type StockService struct {
|
|
stockRepo *repository.StockRepo
|
|
}
|
|
|
|
func NewStockService() *StockService {
|
|
return &StockService{
|
|
stockRepo: repository.NewStockRepo(),
|
|
}
|
|
}
|
|
|
|
// ClassifyStock 与 repository.stockWarningHavingSQL 是同一套规则,后者负责筛出预警列表,
|
|
// 两者必须保持一致:只有配置了启用状态且大于 0 的预警阈值、并且现存量已经触达该阈值时
|
|
// 才是 low。
|
|
//
|
|
// 这里刻意没有独立的"已耗尽"状态:没设过阈值的物料即使库存归零也只是 normal——那是操作
|
|
// 者自己选择不被告知,不是告警。
|
|
func ClassifyStock(totalOnHand, warningStock decimal.Decimal, warningEnabled bool) string {
|
|
if !warningEnabled || warningStock.LessThanOrEqual(decimal.Zero) {
|
|
return model.StockStatusNormal
|
|
}
|
|
if totalOnHand.LessThanOrEqual(warningStock) {
|
|
return model.StockStatusLow
|
|
}
|
|
return model.StockStatusNormal
|
|
}
|
|
|
|
func classifyAll(stocks []model.Stock) {
|
|
for i := range stocks {
|
|
stocks[i].StockStatus = ClassifyStock(stocks[i].TotalOnHand, stocks[i].WarningStock, stocks[i].WarningEnabled)
|
|
}
|
|
}
|
|
|
|
func (s *StockService) GetAllStock(search string, limit, offset int) ([]model.Stock, int64, error) {
|
|
stocks, total, err := s.stockRepo.GetAllStock(search, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
classifyAll(stocks)
|
|
return stocks, total, nil
|
|
}
|
|
|
|
func (s *StockService) GetByMaterialID(materialID int64) (*model.Stock, error) {
|
|
stock, err := s.stockRepo.GetByMaterialID(materialID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stock.StockStatus = ClassifyStock(stock.TotalOnHand, stock.WarningStock, stock.WarningEnabled)
|
|
return stock, nil
|
|
}
|
|
|
|
func (s *StockService) GetWarningStock(limit, offset int) ([]model.Stock, int64, error) {
|
|
stocks, total, err := s.stockRepo.GetWarningStock(limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
classifyAll(stocks)
|
|
return stocks, total, nil
|
|
}
|
|
|
|
func (s *StockService) UpdateWarningStock(materialID int64, minimumAvailable decimal.Decimal, enabled bool) error {
|
|
return s.stockRepo.UpdateWarningStock(materialID, minimumAvailable, enabled)
|
|
}
|
|
|
|
func (s *StockService) GetLogsByMaterialID(materialID int64, limit, offset int) ([]model.StockLogResponse, int64, error) {
|
|
logs, total, err := s.stockRepo.GetLogsByMaterialID(materialID, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var responses []model.StockLogResponse
|
|
balance := decimal.Zero
|
|
for _, log := range logs {
|
|
quantity := decimal.NewFromInt(int64(log.Quantity))
|
|
if log.Type == "in" {
|
|
balance = balance.Add(quantity)
|
|
} else {
|
|
balance = balance.Sub(quantity)
|
|
}
|
|
|
|
changeType := "outbound"
|
|
if log.Type == "in" {
|
|
changeType = "inbound"
|
|
}
|
|
|
|
beforeQuantity := balance.Sub(quantity)
|
|
if log.Type != "in" {
|
|
beforeQuantity = balance.Add(quantity)
|
|
}
|
|
|
|
responses = append(responses, model.StockLogResponse{
|
|
ID: log.ID,
|
|
MaterialID: log.MaterialID,
|
|
ChangeType: changeType,
|
|
ChangeQuantity: quantity,
|
|
BeforeQuantity: beforeQuantity,
|
|
AfterQuantity: balance,
|
|
ReferenceID: log.RelatedOrderID,
|
|
ReferenceType: log.RelatedOrderType,
|
|
CreatedAt: log.CreatedAt,
|
|
})
|
|
}
|
|
return responses, total, nil
|
|
}
|
|
|
|
func (s *StockService) GetAllLogs(limit, offset int) ([]model.StockLogResponse, int64, error) {
|
|
logs, total, err := s.stockRepo.GetAllLogsWithBalance(limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
var responses []model.StockLogResponse
|
|
for _, log := range logs {
|
|
changeType := "outbound"
|
|
if log.Type == "in" {
|
|
changeType = "inbound"
|
|
}
|
|
|
|
quantity := decimal.NewFromInt(int64(log.Quantity))
|
|
beforeQuantity := log.RunningBalance.Sub(quantity)
|
|
if log.Type != "in" {
|
|
beforeQuantity = log.RunningBalance.Add(quantity)
|
|
}
|
|
|
|
// inbound=+quantity, outbound=-quantity
|
|
changeQuantity := quantity
|
|
if log.Type != "in" {
|
|
changeQuantity = quantity.Neg()
|
|
}
|
|
|
|
responses = append(responses, model.StockLogResponse{
|
|
ID: log.ID,
|
|
MaterialID: log.MaterialID,
|
|
ChangeType: changeType,
|
|
ChangeQuantity: changeQuantity,
|
|
BeforeQuantity: beforeQuantity,
|
|
AfterQuantity: log.RunningBalance,
|
|
ReferenceID: log.RelatedOrderID,
|
|
ReferenceType: log.RelatedOrderType,
|
|
CreatedAt: log.CreatedAt,
|
|
})
|
|
}
|
|
return responses, total, nil
|
|
}
|
|
|
|
func (s *StockService) MaterialExists(materialID int64) (bool, error) {
|
|
_, err := s.stockRepo.GetByMaterialID(materialID)
|
|
if err == sql.ErrNoRows {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *StockService) GetInventoryBalanceDetails(materialID int64) ([]model.InventoryBalanceDetail, error) {
|
|
return s.stockRepo.GetInventoryBalanceDetails(materialID)
|
|
}
|