package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/mchus/quoteforge/internal/middleware" "github.com/mchus/quoteforge/internal/models" "github.com/mchus/quoteforge/internal/repository" "github.com/mchus/quoteforge/internal/services/alerts" "github.com/mchus/quoteforge/internal/services/pricing" ) type PricingHandler struct { pricingService *pricing.Service alertService *alerts.Service componentRepo *repository.ComponentRepository statsRepo *repository.StatsRepository } func NewPricingHandler( pricingService *pricing.Service, alertService *alerts.Service, componentRepo *repository.ComponentRepository, statsRepo *repository.StatsRepository, ) *PricingHandler { return &PricingHandler{ pricingService: pricingService, alertService: alertService, componentRepo: componentRepo, statsRepo: statsRepo, } } func (h *PricingHandler) GetStats(c *gin.Context) { newAlerts, _ := h.alertService.GetNewAlertsCount() topComponents, _ := h.statsRepo.GetTopComponents(10) trendingComponents, _ := h.statsRepo.GetTrendingComponents(10) c.JSON(http.StatusOK, gin.H{ "new_alerts_count": newAlerts, "top_components": topComponents, "trending_components": trendingComponents, }) } func (h *PricingHandler) ListComponents(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) filter := repository.ComponentFilter{ Category: c.Query("category"), Vendor: c.Query("vendor"), Search: c.Query("search"), } if page < 1 { page = 1 } if perPage < 1 || perPage > 100 { perPage = 20 } offset := (page - 1) * perPage components, total, err := h.componentRepo.List(filter, offset, perPage) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "components": components, "total": total, "page": page, "per_page": perPage, }) } func (h *PricingHandler) GetComponentPricing(c *gin.Context) { lotName := c.Param("lot_name") component, err := h.componentRepo.GetByLotName(lotName) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "component not found"}) return } stats, err := h.pricingService.GetPriceStats(lotName, 0) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "component": component, "price_stats": stats, }) } type UpdatePriceRequest struct { LotName string `json:"lot_name" binding:"required"` Method models.PriceMethod `json:"method"` PeriodDays int `json:"period_days"` ManualPrice *float64 `json:"manual_price"` Reason string `json:"reason"` } func (h *PricingHandler) UpdatePrice(c *gin.Context) { userID := middleware.GetUserID(c) var req UpdatePriceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.ManualPrice != nil && *req.ManualPrice > 0 { err := h.pricingService.SetManualPrice(req.LotName, *req.ManualPrice, req.Reason, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } if req.Method != "" { err := h.pricingService.UpdatePriceMethod(req.LotName, req.Method, req.PeriodDays) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } } c.JSON(http.StatusOK, gin.H{"message": "price updated"}) } func (h *PricingHandler) RecalculateAll(c *gin.Context) { // This would be better as a background job c.JSON(http.StatusAccepted, gin.H{"message": "recalculation started"}) } func (h *PricingHandler) ListAlerts(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) filter := repository.AlertFilter{ Status: models.AlertStatus(c.Query("status")), Severity: models.AlertSeverity(c.Query("severity")), Type: models.AlertType(c.Query("type")), LotName: c.Query("lot_name"), } alertsList, total, err := h.alertService.List(filter, page, perPage) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "alerts": alertsList, "total": total, "page": page, "per_page": perPage, }) } func (h *PricingHandler) AcknowledgeAlert(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"}) return } if err := h.alertService.Acknowledge(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "acknowledged"}) } func (h *PricingHandler) ResolveAlert(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"}) return } if err := h.alertService.Resolve(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "resolved"}) } func (h *PricingHandler) IgnoreAlert(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"}) return } if err := h.alertService.Ignore(uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "ignored"}) }