202 lines
5.6 KiB
Go
202 lines
5.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
|
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
|
"git.mchus.pro/mchus/quoteforge/internal/services"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// VendorSpecHandler handles vendor BOM spec operations for a configuration.
|
|
type VendorSpecHandler struct {
|
|
localDB *localdb.LocalDB
|
|
configService *services.LocalConfigurationService
|
|
}
|
|
|
|
func NewVendorSpecHandler(localDB *localdb.LocalDB) *VendorSpecHandler {
|
|
return &VendorSpecHandler{
|
|
localDB: localDB,
|
|
configService: services.NewLocalConfigurationService(localDB, nil, nil, func() bool { return false }),
|
|
}
|
|
}
|
|
|
|
// lookupConfig finds an active configuration by UUID using the standard localDB method.
|
|
func (h *VendorSpecHandler) lookupConfig(uuid string) (*localdb.LocalConfiguration, error) {
|
|
cfg, err := h.localDB.GetConfigurationByUUID(uuid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !cfg.IsActive {
|
|
return nil, errors.New("not active")
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// GetVendorSpec returns the vendor spec (BOM) for a configuration.
|
|
// GET /api/configs/:uuid/vendor-spec
|
|
func (h *VendorSpecHandler) GetVendorSpec(c *gin.Context) {
|
|
cfg, err := h.lookupConfig(c.Param("uuid"))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
|
|
return
|
|
}
|
|
|
|
spec := cfg.VendorSpec
|
|
if spec == nil {
|
|
spec = localdb.VendorSpec{}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"vendor_spec": spec})
|
|
}
|
|
|
|
// PutVendorSpec saves (replaces) the vendor spec for a configuration.
|
|
// PUT /api/configs/:uuid/vendor-spec
|
|
func (h *VendorSpecHandler) PutVendorSpec(c *gin.Context) {
|
|
cfg, err := h.lookupConfig(c.Param("uuid"))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
VendorSpec []localdb.VendorSpecItem `json:"vendor_spec"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
RespondError(c, http.StatusBadRequest, "invalid request", err)
|
|
return
|
|
}
|
|
|
|
for i := range body.VendorSpec {
|
|
if body.VendorSpec[i].SortOrder == 0 {
|
|
body.VendorSpec[i].SortOrder = (i + 1) * 10
|
|
}
|
|
// Persist canonical LOT mapping only.
|
|
body.VendorSpec[i].LotMappings = normalizeLotMappings(body.VendorSpec[i].LotMappings)
|
|
body.VendorSpec[i].ResolvedLotName = ""
|
|
body.VendorSpec[i].ResolutionSource = ""
|
|
body.VendorSpec[i].ManualLotSuggestion = ""
|
|
body.VendorSpec[i].LotQtyPerPN = 0
|
|
body.VendorSpec[i].LotAllocations = nil
|
|
}
|
|
|
|
spec := localdb.VendorSpec(body.VendorSpec)
|
|
if _, err := h.configService.UpdateVendorSpecNoAuth(cfg.UUID, spec); err != nil {
|
|
RespondError(c, http.StatusInternalServerError, "internal server error", err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"vendor_spec": spec})
|
|
}
|
|
|
|
func normalizeLotMappings(in []localdb.VendorSpecLotMapping) []localdb.VendorSpecLotMapping {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
merged := make(map[string]int, len(in))
|
|
order := make([]string, 0, len(in))
|
|
for _, m := range in {
|
|
lot := strings.TrimSpace(m.LotName)
|
|
if lot == "" {
|
|
continue
|
|
}
|
|
qty := m.QuantityPerPN
|
|
if qty < 1 {
|
|
qty = 1
|
|
}
|
|
if _, exists := merged[lot]; !exists {
|
|
order = append(order, lot)
|
|
}
|
|
merged[lot] += qty
|
|
}
|
|
out := make([]localdb.VendorSpecLotMapping, 0, len(order))
|
|
for _, lot := range order {
|
|
out = append(out, localdb.VendorSpecLotMapping{
|
|
LotName: lot,
|
|
QuantityPerPN: merged[lot],
|
|
})
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ResolveVendorSpec resolves vendor PN → LOT without modifying the cart.
|
|
// POST /api/configs/:uuid/vendor-spec/resolve
|
|
func (h *VendorSpecHandler) ResolveVendorSpec(c *gin.Context) {
|
|
if _, err := h.lookupConfig(c.Param("uuid")); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
VendorSpec []localdb.VendorSpecItem `json:"vendor_spec"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
RespondError(c, http.StatusBadRequest, "invalid request", err)
|
|
return
|
|
}
|
|
|
|
bookRepo := repository.NewPartnumberBookRepository(h.localDB.DB())
|
|
resolver := services.NewVendorSpecResolver(bookRepo)
|
|
|
|
resolved, err := resolver.Resolve(body.VendorSpec)
|
|
if err != nil {
|
|
RespondError(c, http.StatusInternalServerError, "internal server error", err)
|
|
return
|
|
}
|
|
|
|
book, _ := bookRepo.GetActiveBook()
|
|
aggregated, err := services.AggregateLOTs(resolved, book, bookRepo)
|
|
if err != nil {
|
|
RespondError(c, http.StatusInternalServerError, "internal server error", err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"resolved": resolved,
|
|
"aggregated": aggregated,
|
|
})
|
|
}
|
|
|
|
// ApplyVendorSpec applies the resolved BOM to the cart (Estimate items).
|
|
// POST /api/configs/:uuid/vendor-spec/apply
|
|
func (h *VendorSpecHandler) ApplyVendorSpec(c *gin.Context) {
|
|
cfg, err := h.lookupConfig(c.Param("uuid"))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
Items []struct {
|
|
LotName string `json:"lot_name"`
|
|
Quantity int `json:"quantity"`
|
|
UnitPrice float64 `json:"unit_price"`
|
|
} `json:"items"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
RespondError(c, http.StatusBadRequest, "invalid request", err)
|
|
return
|
|
}
|
|
|
|
newItems := make(localdb.LocalConfigItems, 0, len(body.Items))
|
|
for _, it := range body.Items {
|
|
newItems = append(newItems, localdb.LocalConfigItem{
|
|
LotName: it.LotName,
|
|
Quantity: it.Quantity,
|
|
UnitPrice: it.UnitPrice,
|
|
})
|
|
}
|
|
|
|
if _, err := h.configService.ApplyVendorSpecItemsNoAuth(cfg.UUID, newItems); err != nil {
|
|
RespondError(c, http.StatusInternalServerError, "internal server error", err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"items": newItems})
|
|
}
|