package handlers import ( "encoding/json" "net/http" "git.mchus.pro/mchus/quoteforge/internal/localdb" "git.mchus.pro/mchus/quoteforge/internal/middleware" "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} } // GetVendorSpec returns the vendor spec (BOM) for a configuration. // GET /api/configs/:uuid/vendor-spec func (h *VendorSpecHandler) GetVendorSpec(c *gin.Context) { uuid := c.Param("uuid") username := middleware.GetUsername(c) var cfg localdb.LocalConfiguration if err := h.localDB.DB().Where("uuid = ? AND original_username = ? AND is_active = 1", uuid, username).First(&cfg).Error; 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) { uuid := c.Param("uuid") username := middleware.GetUsername(c) 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 } var cfg localdb.LocalConfiguration if err := h.localDB.DB().Where("uuid = ? AND original_username = ? AND is_active = 1", uuid, username).First(&cfg).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"}) return } // Assign sort_order if not set for i := range body.VendorSpec { if body.VendorSpec[i].SortOrder == 0 { body.VendorSpec[i].SortOrder = (i + 1) * 10 } } spec := localdb.VendorSpec(body.VendorSpec) if err := h.localDB.DB().Model(&cfg).Update("vendor_spec", spec).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) { uuid := c.Param("uuid") username := middleware.GetUsername(c) var cfg localdb.LocalConfiguration if err := h.localDB.DB().Where("uuid = ? AND original_username = ? AND is_active = 1", uuid, username).First(&cfg).Error; 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 } // Also compute aggregated LOTs 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) { uuid := c.Param("uuid") username := middleware.GetUsername(c) 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 } var cfg localdb.LocalConfiguration if err := h.localDB.DB().Where("uuid = ? AND original_username = ? AND is_active = 1", uuid, username).First(&cfg).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"}) 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}) }