package handlers import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/shopspring/decimal" "go.uber.org/zap" ocrService "rmser/internal/services/ocr" "rmser/pkg/logger" ) type OCRHandler struct { service *ocrService.Service } func NewOCRHandler(service *ocrService.Service) *OCRHandler { return &OCRHandler{service: service} } // GetCatalog возвращает список товаров для OCR сервиса func (h *OCRHandler) GetCatalog(c *gin.Context) { // Если этот эндпоинт дергает Python-скрипт без токена пользователя - это проблема безопасности. // Либо Python скрипт должен передавать токен админа/системы и ID сервера в query. // ПОКА: Предполагаем, что запрос идет от фронта или с заголовком X-Telegram-User-ID. // Если заголовка нет (вызов от скрипта), пробуем взять server_id из query (небезопасно, но для MVP) // Или лучше так: этот метод вызывается Фронтендом для поиска? Нет, название GetCatalogForIndexing намекает на OCR. // Оставим пока требование UserID. userID := c.MustGet("userID").(uuid.UUID) items, err := h.service.GetCatalogForIndexing(userID) if err != nil { logger.Log.Error("Ошибка получения каталога", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, items) } type MatchRequest struct { RawName string `json:"raw_name" binding:"required"` ProductID string `json:"product_id" binding:"required"` Quantity float64 `json:"quantity"` ContainerID *string `json:"container_id"` } func (h *OCRHandler) SaveMatch(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) var req MatchRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } pID, err := uuid.Parse(req.ProductID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid product_id format"}) return } qty := decimal.NewFromFloat(1.0) if req.Quantity > 0 { qty = decimal.NewFromFloat(req.Quantity) } var contID *uuid.UUID if req.ContainerID != nil && *req.ContainerID != "" { if uid, err := uuid.Parse(*req.ContainerID); err == nil { contID = &uid } } if err := h.service.SaveMapping(userID, req.RawName, pID, qty, contID); err != nil { logger.Log.Error("Ошибка сохранения матчинга", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "saved"}) } func (h *OCRHandler) DeleteMatch(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) rawName := c.Query("raw_name") if rawName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "raw_name is required"}) return } if err := h.service.DeleteMatch(userID, rawName); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "deleted"}) } func (h *OCRHandler) SearchProducts(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) query := c.Query("q") products, err := h.service.SearchProducts(userID, query) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, products) } func (h *OCRHandler) GetMatches(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) matches, err := h.service.GetKnownMatches(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, matches) } func (h *OCRHandler) GetUnmatched(c *gin.Context) { userID := c.MustGet("userID").(uuid.UUID) items, err := h.service.GetUnmatchedItems(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, items) }