package handlers import ( "errors" "io" "net/http" "strconv" "strings" "git.mchus.pro/mchus/priceforge/internal/models" "git.mchus.pro/mchus/priceforge/internal/services/pricelist" "github.com/gin-gonic/gin" ) type PricelistHandler struct { service *pricelist.Service dbUser string } func NewPricelistHandler(service *pricelist.Service, dbUser string) *PricelistHandler { return &PricelistHandler{service: service, dbUser: dbUser} } func (h *PricelistHandler) List(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20")) source := c.Query("source") activeOnly := c.DefaultQuery("active_only", "false") == "true" var ( list []models.PricelistSummary total int64 err error ) if activeOnly { list, total, err = h.service.ListActiveBySource(page, perPage, source) } else { list, total, err = h.service.ListBySource(page, perPage, source) } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"pricelists": list, "total": total, "page": page, "per_page": perPage}) } func (h *PricelistHandler) Get(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"}) return } pl, err := h.service.GetByID(uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"}) return } c.JSON(http.StatusOK, pl) } func (h *PricelistHandler) Create(c *gin.Context) { canWrite, debugInfo := h.service.CanWriteDebug() if !canWrite { c.JSON(http.StatusForbidden, gin.H{"error": "pricelist write is not allowed", "debug": debugInfo}) return } var req struct { Source string `json:"source"` Items []struct { LotName string `json:"lot_name"` Price float64 `json:"price"` } `json:"items"` } if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } source := string(models.NormalizePricelistSource(req.Source)) createdBy := h.dbUser if strings.TrimSpace(createdBy) == "" { createdBy = "unknown" } items := make([]pricelist.CreateItemInput, 0, len(req.Items)) for _, item := range req.Items { items = append(items, pricelist.CreateItemInput{LotName: item.LotName, Price: item.Price}) } pl, err := h.service.CreateForSourceWithProgress(createdBy, source, items, nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, pl) } func (h *PricelistHandler) CreateWithProgress(c *gin.Context) { canWrite, debugInfo := h.service.CanWriteDebug() if !canWrite { c.JSON(http.StatusForbidden, gin.H{"error": "pricelist write is not allowed", "debug": debugInfo}) return } var req struct { Source string `json:"source"` Items []struct { LotName string `json:"lot_name"` Price float64 `json:"price"` } `json:"items"` } if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } source := string(models.NormalizePricelistSource(req.Source)) createdBy := h.dbUser if strings.TrimSpace(createdBy) == "" { createdBy = "unknown" } items := make([]pricelist.CreateItemInput, 0, len(req.Items)) for _, item := range req.Items { items = append(items, pricelist.CreateItemInput{LotName: item.LotName, Price: item.Price}) } c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") c.Header("Connection", "keep-alive") flusher, ok := c.Writer.(http.Flusher) if !ok { pl, err := h.service.CreateForSourceWithProgress(createdBy, source, items, nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, pl) return } send := func(payload gin.H) { c.SSEvent("progress", payload); flusher.Flush() } send(gin.H{"current": 0, "total": 100, "status": "starting", "message": "Запуск..."}) pl, err := h.service.CreateForSourceWithProgress(createdBy, source, items, func(p pricelist.CreateProgress) { send(gin.H{"current": p.Current, "total": p.Total, "status": p.Status, "message": p.Message, "updated": p.Updated, "errors": p.Errors, "lot_name": p.LotName}) }) if err != nil { send(gin.H{"status": "error", "message": err.Error()}) return } send(gin.H{"current": 100, "total": 100, "status": "completed", "message": "Готово", "pricelist": pl}) } func (h *PricelistHandler) Delete(c *gin.Context) { canWrite, debugInfo := h.service.CanWriteDebug() if !canWrite { c.JSON(http.StatusForbidden, gin.H{"error": "pricelist write is not allowed", "debug": debugInfo}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"}) return } if err := h.service.Delete(uint(id)); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "pricelist deleted"}) } func (h *PricelistHandler) SetActive(c *gin.Context) { canWrite, debugInfo := h.service.CanWriteDebug() if !canWrite { c.JSON(http.StatusForbidden, gin.H{"error": "pricelist write is not allowed", "debug": debugInfo}) return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"}) return } var req struct { IsActive bool `json:"is_active"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.service.SetActive(uint(id), req.IsActive); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "updated", "is_active": req.IsActive}) } func (h *PricelistHandler) GetItems(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"}) return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "50")) search := c.Query("search") items, total, err := h.service.GetItems(uint(id), page, perPage, search) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"items": items, "total": total, "page": page, "per_page": perPage}) } func (h *PricelistHandler) GetLotNames(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"}) return } lotNames, err := h.service.GetLotNames(uint(id)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"lot_names": lotNames, "total": len(lotNames)}) } func (h *PricelistHandler) CanWrite(c *gin.Context) { canWrite, debugInfo := h.service.CanWriteDebug() c.JSON(http.StatusOK, gin.H{"can_write": canWrite, "debug": debugInfo}) } func (h *PricelistHandler) GetLatest(c *gin.Context) { source := string(models.NormalizePricelistSource(c.DefaultQuery("source", string(models.PricelistSourceEstimate)))) pl, err := h.service.GetLatestActiveBySource(source) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "no pricelists available"}) return } c.JSON(http.StatusOK, pl) }