125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
package article
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
|
)
|
|
|
|
// ErrMissingCategoryForLot is returned when a lot has no category in local_pricelist_items.lot_category.
|
|
var ErrMissingCategoryForLot = errors.New("missing_category_for_lot")
|
|
|
|
type MissingCategoryForLotError struct {
|
|
LotName string
|
|
}
|
|
|
|
func (e *MissingCategoryForLotError) Error() string {
|
|
if e == nil || strings.TrimSpace(e.LotName) == "" {
|
|
return ErrMissingCategoryForLot.Error()
|
|
}
|
|
return fmt.Sprintf("%s: %s", ErrMissingCategoryForLot.Error(), e.LotName)
|
|
}
|
|
|
|
func (e *MissingCategoryForLotError) Unwrap() error {
|
|
return ErrMissingCategoryForLot
|
|
}
|
|
|
|
type Group string
|
|
|
|
const (
|
|
GroupCPU Group = "CPU"
|
|
GroupMEM Group = "MEM"
|
|
GroupGPU Group = "GPU"
|
|
GroupDISK Group = "DISK"
|
|
GroupNET Group = "NET"
|
|
GroupPSU Group = "PSU"
|
|
)
|
|
|
|
// GroupForLotCategory maps pricelist lot_category codes into article groups.
|
|
// Unknown/unrelated categories return ok=false.
|
|
func GroupForLotCategory(cat string) (group Group, ok bool) {
|
|
c := strings.ToUpper(strings.TrimSpace(cat))
|
|
switch c {
|
|
case "CPU":
|
|
return GroupCPU, true
|
|
case "MEM":
|
|
return GroupMEM, true
|
|
case "GPU":
|
|
return GroupGPU, true
|
|
case "M2", "SSD", "HDD", "EDSFF", "HHHL":
|
|
return GroupDISK, true
|
|
case "NIC", "HCA", "DPU":
|
|
return GroupNET, true
|
|
case "HBA":
|
|
return GroupNET, true
|
|
case "PSU", "PS":
|
|
return GroupPSU, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
// ResolveLotCategoriesStrict resolves categories for lotNames using local_pricelist_items.lot_category
|
|
// for a given server pricelist id. If any lot is missing or has empty category, returns an error.
|
|
func ResolveLotCategoriesStrict(local *localdb.LocalDB, serverPricelistID uint, lotNames []string) (map[string]string, error) {
|
|
if local == nil {
|
|
return nil, fmt.Errorf("local db is nil")
|
|
}
|
|
cats, err := local.GetLocalLotCategoriesByServerPricelistID(serverPricelistID, lotNames)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
missing := make([]string, 0)
|
|
for _, lot := range lotNames {
|
|
cat := strings.TrimSpace(cats[lot])
|
|
if cat == "" {
|
|
missing = append(missing, lot)
|
|
continue
|
|
}
|
|
cats[lot] = cat
|
|
}
|
|
if len(missing) > 0 {
|
|
fallback, err := local.GetLocalComponentCategoriesByLotNames(missing)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, lot := range missing {
|
|
if cat := strings.TrimSpace(fallback[lot]); cat != "" {
|
|
cats[lot] = cat
|
|
}
|
|
}
|
|
for _, lot := range missing {
|
|
if strings.TrimSpace(cats[lot]) == "" {
|
|
return nil, &MissingCategoryForLotError{LotName: lot}
|
|
}
|
|
}
|
|
}
|
|
return cats, nil
|
|
}
|
|
|
|
// NormalizeServerModel produces a stable article segment for the server model.
|
|
func NormalizeServerModel(model string) string {
|
|
trimmed := strings.TrimSpace(model)
|
|
if trimmed == "" {
|
|
return ""
|
|
}
|
|
upper := strings.ToUpper(trimmed)
|
|
var b strings.Builder
|
|
for _, r := range upper {
|
|
if r >= 'A' && r <= 'Z' {
|
|
b.WriteRune(r)
|
|
continue
|
|
}
|
|
if r >= '0' && r <= '9' {
|
|
b.WriteRune(r)
|
|
continue
|
|
}
|
|
if r == '.' {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|