mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
205 lines
5.6 KiB
Go
205 lines
5.6 KiB
Go
package fifo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// QueryScope allows callers to inject custom query modifiers (preloads, filters, etc).
|
|
type QueryScope func(*gorm.DB) *gorm.DB
|
|
|
|
type StockableKey string
|
|
type UsableKey string
|
|
|
|
func (k StockableKey) String() string {
|
|
return string(k)
|
|
}
|
|
|
|
func (k UsableKey) String() string {
|
|
return string(k)
|
|
}
|
|
|
|
// StockableColumns describes the minimum columns required for a stock-bearing row.
|
|
type StockableColumns struct {
|
|
ID string
|
|
ProductWarehouseID string
|
|
TotalQuantity string
|
|
TotalUsedQuantity string
|
|
CreatedAt string
|
|
}
|
|
|
|
// UsableColumns describes the required columns for rows that consume stock.
|
|
type UsableColumns struct {
|
|
ID string
|
|
ProductWarehouseID string
|
|
UsageQuantity string
|
|
PendingQuantity string
|
|
CreatedAt string
|
|
}
|
|
|
|
// StockableConfig registers a table that introduces stock into the system (purchases, transfers, etc).
|
|
type StockableConfig struct {
|
|
Key StockableKey
|
|
Table string
|
|
Columns StockableColumns
|
|
// OrderBy accepts raw column expressions, evaluated in-order (e.g. []string{"created_at ASC", "id ASC"}).
|
|
OrderBy []string
|
|
// Scope lets a module append base filters (e.g. exclude drafts).
|
|
Scope QueryScope
|
|
}
|
|
|
|
// UsableConfig registers a table that consumes stock (recordings, adjustments, sales, etc).
|
|
type UsableConfig struct {
|
|
Key UsableKey
|
|
Table string
|
|
Columns UsableColumns
|
|
OrderBy []string
|
|
Scope QueryScope
|
|
}
|
|
|
|
var (
|
|
stockableRegistry = make(map[StockableKey]StockableConfig)
|
|
usableRegistry = make(map[UsableKey]UsableConfig)
|
|
registryMu sync.RWMutex
|
|
)
|
|
|
|
// RegisterStockable stores the configuration so services can perform FIFO operations generically.
|
|
func RegisterStockable(cfg StockableConfig) error {
|
|
if err := validateStockableConfig(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
registryMu.Lock()
|
|
defer registryMu.Unlock()
|
|
|
|
key := StockableKey(strings.TrimSpace(cfg.Key.String()))
|
|
if _, exists := stockableRegistry[key]; exists {
|
|
return fmt.Errorf("stockable key %q already registered", key)
|
|
}
|
|
|
|
stockableRegistry[key] = cfg
|
|
return nil
|
|
}
|
|
|
|
// RegisterUsable stores the configuration for stock-consuming tables.
|
|
func RegisterUsable(cfg UsableConfig) error {
|
|
if err := validateUsableConfig(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
registryMu.Lock()
|
|
defer registryMu.Unlock()
|
|
|
|
key := UsableKey(strings.TrimSpace(cfg.Key.String()))
|
|
if _, exists := usableRegistry[key]; exists {
|
|
return fmt.Errorf("usable key %q already registered", key)
|
|
}
|
|
|
|
usableRegistry[key] = cfg
|
|
return nil
|
|
}
|
|
|
|
// Stockable returns the registered configuration for the key (if any).
|
|
func Stockable(key StockableKey) (StockableConfig, bool) {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
|
|
cfg, ok := stockableRegistry[key]
|
|
return cfg, ok
|
|
}
|
|
|
|
// Usable returns the registered configuration for the key (if any).
|
|
func Usable(key UsableKey) (UsableConfig, bool) {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
|
|
cfg, ok := usableRegistry[key]
|
|
return cfg, ok
|
|
}
|
|
|
|
// Stockables exposes a copy of the current registry (useful for iterating pending requests).
|
|
func Stockables() map[StockableKey]StockableConfig {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
|
|
if len(stockableRegistry) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make(map[StockableKey]StockableConfig, len(stockableRegistry))
|
|
for key, cfg := range stockableRegistry {
|
|
result[key] = cfg
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Usables exposes a copy of the usable registry.
|
|
func Usables() map[UsableKey]UsableConfig {
|
|
registryMu.RLock()
|
|
defer registryMu.RUnlock()
|
|
|
|
if len(usableRegistry) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make(map[UsableKey]UsableConfig, len(usableRegistry))
|
|
for key, cfg := range usableRegistry {
|
|
result[key] = cfg
|
|
}
|
|
return result
|
|
}
|
|
|
|
func validateStockableConfig(cfg StockableConfig) error {
|
|
if strings.TrimSpace(cfg.Key.String()) == "" {
|
|
return errors.New("stockable key is required")
|
|
}
|
|
if strings.TrimSpace(cfg.Table) == "" {
|
|
return fmt.Errorf("table name is required for stockable %q", cfg.Key)
|
|
}
|
|
|
|
cols := cfg.Columns
|
|
switch {
|
|
case strings.TrimSpace(cols.ID) == "":
|
|
return fmt.Errorf("column id is required for stockable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.ProductWarehouseID) == "":
|
|
return fmt.Errorf("column product warehouse id is required for stockable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.TotalQuantity) == "":
|
|
return fmt.Errorf("column total quantity is required for stockable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.TotalUsedQuantity) == "":
|
|
return fmt.Errorf("column total used quantity is required for stockable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.CreatedAt) == "":
|
|
return fmt.Errorf("column created_at is required for stockable %q", cfg.Key)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateUsableConfig(cfg UsableConfig) error {
|
|
if strings.TrimSpace(cfg.Key.String()) == "" {
|
|
return errors.New("usable key is required")
|
|
}
|
|
if strings.TrimSpace(cfg.Table) == "" {
|
|
return fmt.Errorf("table name is required for usable %q", cfg.Key)
|
|
}
|
|
|
|
cols := cfg.Columns
|
|
switch {
|
|
case strings.TrimSpace(cols.ID) == "":
|
|
return fmt.Errorf("column id is required for usable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.ProductWarehouseID) == "":
|
|
return fmt.Errorf("column product warehouse id is required for usable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.UsageQuantity) == "":
|
|
return fmt.Errorf("column usage quantity is required for usable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.PendingQuantity) == "":
|
|
return fmt.Errorf("column pending quantity is required for usable %q", cfg.Key)
|
|
case strings.TrimSpace(cols.CreatedAt) == "":
|
|
return fmt.Errorf("column created_at is required for usable %q", cfg.Key)
|
|
}
|
|
|
|
return nil
|
|
}
|