package handlers import ( "encoding/json" "errors" "net/http" "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 } func NewVendorSpecHandler(localDB *localdb.LocalDB) *VendorSpecHandler { return &VendorSpecHandler{localDB: localDB} } // 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 { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } for i := range body.VendorSpec { if body.VendorSpec[i].SortOrder == 0 { body.VendorSpec[i].SortOrder = (i + 1) * 10 } } spec := localdb.VendorSpec(body.VendorSpec) specJSON, err := json.Marshal(spec) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.localDB.DB().Model(cfg).Update("vendor_spec", string(specJSON)).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"vendor_spec": spec}) } // 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 { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } bookRepo := repository.NewPartnumberBookRepository(h.localDB.DB()) resolver := services.NewVendorSpecResolver(bookRepo) resolved, err := resolver.Resolve(body.VendorSpec) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } book, _ := bookRepo.GetActiveBook() aggregated, err := services.AggregateLOTs(resolved, book, bookRepo) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 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 { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 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, }) } itemsJSON, err := json.Marshal(newItems) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if err := h.localDB.DB().Model(cfg).Update("items", string(itemsJSON)).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"items": newItems}) }