добавил ручки для фронта

This commit is contained in:
2025-11-30 02:02:11 +03:00
parent da62ea5b98
commit 714844058f
9 changed files with 99 additions and 9 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
# Python virtual environment
.venv
.venv
*.py

View File

@@ -5,6 +5,7 @@ import (
"log"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/shopspring/decimal"
@@ -71,7 +72,11 @@ func main() {
recService := recServicePkg.NewService(recRepo)
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, pyClient)
invoiceService := invServicePkg.NewService(rmsClient)
// --- Инициализация Handler'ов ---
invoiceHandler := handlers.NewInvoiceHandler(invoiceService)
ocrHandler := handlers.NewOCRHandler(ocrService)
recommendHandler := handlers.NewRecommendationsHandler(recService)
// --- БЛОК ПРОВЕРКИ СИНХРОНИЗАЦИИ (Run-once on start) ---
go func() {
@@ -129,16 +134,33 @@ func main() {
} else {
logger.Log.Warn("Telegram token не задан, бот не запущен")
}
// 5. Запуск HTTP сервера (Gin)
if cfg.App.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
// --- Настройка CORS ---
// Разрешаем запросы с любых источников для разработки Frontend
corsConfig := cors.DefaultConfig()
corsConfig.AllowAllOrigins = true // В продакшене заменить на AllowOrigins: []string{"http://domain.com"}
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}
corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization"}
r.Use(cors.New(corsConfig))
api := r.Group("/api")
{
// ... другие роуты ...
// Invoices
api.POST("/invoices/send", invoiceHandler.SendInvoice)
// Recommendations
api.GET("/recommendations", recommendHandler.GetRecommendations)
// OCR
api.GET("/ocr/catalog", ocrHandler.GetCatalog)
api.GET("/ocr/matches", ocrHandler.GetMatches)
api.POST("/ocr/match", ocrHandler.SaveMatch)
}
// Простой хелсчек

5
go.mod
View File

@@ -3,6 +3,7 @@ module rmser
go 1.25.1
require (
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.6.0
@@ -22,13 +23,13 @@ require (
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect

10
go.sum
View File

@@ -132,9 +132,11 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
@@ -163,8 +165,8 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=

View File

@@ -28,4 +28,7 @@ type Repository interface {
// FindMatch ищет товар по точному совпадению названия
FindMatch(rawName string) (*uuid.UUID, error)
// GetAllMatches возвращает все существующие привязки
GetAllMatches() ([]ProductMatch, error)
}

View File

@@ -47,3 +47,11 @@ func (r *pgRepository) FindMatch(rawName string) (*uuid.UUID, error) {
return &match.ProductID, nil
}
func (r *pgRepository) GetAllMatches() ([]ocr.ProductMatch, error) {
var matches []ocr.ProductMatch
// Preload("Product") загружает связанную сущность товара,
// чтобы мы видели не только ID, но и название товара из каталога.
err := r.db.Preload("Product").Order("updated_at DESC").Find(&matches).Error
return matches, err
}

View File

@@ -67,7 +67,6 @@ func (s *Service) ProcessReceiptImage(ctx context.Context, imgData []byte) ([]Pr
} else {
// Если не нашли, пробуем найти точное совпадение по имени в каталоге (на всякий случай)
// (В реальном проекте тут может быть нечеткий поиск, но пока точный)
// TODO: Добавить метод FindByName в репозиторий каталога, если нужно
}
processed = append(processed, item)
@@ -118,6 +117,11 @@ func (s *Service) SaveMapping(rawName string, productID uuid.UUID) error {
return s.ocrRepo.SaveMatch(rawName, productID)
}
// GetKnownMatches возвращает список всех обученных связей
func (s *Service) GetKnownMatches() ([]ocr.ProductMatch, error) {
return s.ocrRepo.GetAllMatches()
}
// FindKnownMatch ищет, знаем ли мы уже этот товар
func (s *Service) FindKnownMatch(rawName string) (*uuid.UUID, error) {
return s.ocrRepo.FindMatch(rawName)

View File

@@ -57,3 +57,15 @@ func (h *OCRHandler) SaveMatch(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "saved"})
}
// GetMatches возвращает список всех обученных связей
func (h *OCRHandler) GetMatches(c *gin.Context) {
matches, err := h.service.GetKnownMatches()
if err != nil {
logger.Log.Error("Ошибка получения списка матчей", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, matches)
}

View File

@@ -0,0 +1,37 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"rmser/internal/services/recommend"
"rmser/pkg/logger"
)
type RecommendationsHandler struct {
service *recommend.Service
}
func NewRecommendationsHandler(service *recommend.Service) *RecommendationsHandler {
return &RecommendationsHandler{service: service}
}
// GetRecommendations godoc
// @Summary Получить список рекомендаций
// @Description Возвращает сгенерированные рекомендации (проблемные зоны учета)
// @Tags recommendations
// @Produce json
// @Success 200 {array} recommendations.Recommendation
// @Failure 500 {object} map[string]string
func (h *RecommendationsHandler) GetRecommendations(c *gin.Context) {
recs, err := h.service.GetRecommendations()
if err != nil {
logger.Log.Error("Ошибка получения рекомендаций", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, recs)
}