mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-05 03:12:34 -06:00
start rmser
This commit is contained in:
50
internal/transport/http/handlers/invoices.go
Normal file
50
internal/transport/http/handlers/invoices.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
invService "rmser/internal/services/invoices"
|
||||
"rmser/pkg/logger"
|
||||
)
|
||||
|
||||
type InvoiceHandler struct {
|
||||
service *invService.Service
|
||||
}
|
||||
|
||||
func NewInvoiceHandler(service *invService.Service) *InvoiceHandler {
|
||||
return &InvoiceHandler{service: service}
|
||||
}
|
||||
|
||||
// SendInvoice godoc
|
||||
// @Summary Создать приходную накладную в iikoRMS
|
||||
// @Description Принимает JSON с данными накладной и отправляет их в iiko
|
||||
// @Tags invoices
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param input body invService.CreateRequestDTO true "Invoice Data"
|
||||
// @Success 200 {object} map[string]string "created_number"
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
func (h *InvoiceHandler) SendInvoice(c *gin.Context) {
|
||||
var req invService.CreateRequestDTO
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Неверный формат JSON: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
docNum, err := h.service.SendInvoiceToRMS(req)
|
||||
if err != nil {
|
||||
logger.Log.Error("Ошибка отправки накладной", zap.Error(err))
|
||||
// Возвращаем 502 Bad Gateway, т.к. ошибка скорее всего на стороне RMS
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"created_number": docNum,
|
||||
})
|
||||
}
|
||||
59
internal/transport/http/handlers/ocr.go
Normal file
59
internal/transport/http/handlers/ocr.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"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) {
|
||||
items, err := h.service.GetCatalogForIndexing()
|
||||
if err != nil {
|
||||
logger.Log.Error("Ошибка получения каталога для OCR", 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"`
|
||||
}
|
||||
|
||||
// SaveMatch сохраняет привязку (обучение)
|
||||
func (h *OCRHandler) SaveMatch(c *gin.Context) {
|
||||
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
|
||||
}
|
||||
|
||||
if err := h.service.SaveMapping(req.RawName, pID); 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"})
|
||||
}
|
||||
141
internal/transport/telegram/bot.go
Normal file
141
internal/transport/telegram/bot.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
tele "gopkg.in/telebot.v3"
|
||||
"gopkg.in/telebot.v3/middleware"
|
||||
|
||||
"rmser/config"
|
||||
"rmser/internal/services/ocr"
|
||||
"rmser/pkg/logger"
|
||||
)
|
||||
|
||||
type Bot struct {
|
||||
b *tele.Bot
|
||||
ocrService *ocr.Service
|
||||
adminIDs map[int64]struct{}
|
||||
}
|
||||
|
||||
func NewBot(cfg config.TelegramConfig, ocrService *ocr.Service) (*Bot, error) {
|
||||
pref := tele.Settings{
|
||||
Token: cfg.Token,
|
||||
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
|
||||
OnError: func(err error, c tele.Context) {
|
||||
logger.Log.Error("Telegram error", zap.Error(err))
|
||||
},
|
||||
}
|
||||
|
||||
b, err := tele.NewBot(pref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
admins := make(map[int64]struct{})
|
||||
for _, id := range cfg.AdminIDs {
|
||||
admins[id] = struct{}{}
|
||||
}
|
||||
|
||||
bot := &Bot{
|
||||
b: b,
|
||||
ocrService: ocrService,
|
||||
adminIDs: admins,
|
||||
}
|
||||
|
||||
bot.initHandlers()
|
||||
return bot, nil
|
||||
}
|
||||
|
||||
func (bot *Bot) Start() {
|
||||
logger.Log.Info("Запуск Telegram бота...")
|
||||
bot.b.Start()
|
||||
}
|
||||
|
||||
func (bot *Bot) Stop() {
|
||||
bot.b.Stop()
|
||||
}
|
||||
|
||||
// Middleware для проверки прав (только админы)
|
||||
func (bot *Bot) authMiddleware(next tele.HandlerFunc) tele.HandlerFunc {
|
||||
return func(c tele.Context) error {
|
||||
if len(bot.adminIDs) > 0 {
|
||||
if _, ok := bot.adminIDs[c.Sender().ID]; !ok {
|
||||
return c.Send("⛔ У вас нет доступа к этому боту.")
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) initHandlers() {
|
||||
bot.b.Use(middleware.Logger())
|
||||
bot.b.Use(bot.authMiddleware)
|
||||
|
||||
bot.b.Handle("/start", func(c tele.Context) error {
|
||||
return c.Send("👋 Привет! Я RMSER Bot.\nОтправь мне фото накладной или чека, и я попробую его распознать.")
|
||||
})
|
||||
|
||||
bot.b.Handle(tele.OnPhoto, bot.handlePhoto)
|
||||
}
|
||||
|
||||
func (bot *Bot) handlePhoto(c tele.Context) error {
|
||||
// 1. Скачиваем фото
|
||||
photo := c.Message().Photo
|
||||
// Берем файл самого высокого качества (последний в массиве, но telebot дает удобный доступ)
|
||||
file, err := bot.b.FileByID(photo.FileID)
|
||||
if err != nil {
|
||||
return c.Send("Ошибка доступа к файлу.")
|
||||
}
|
||||
|
||||
// Читаем тело файла
|
||||
fileURL := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", bot.b.Token, file.FilePath)
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
return c.Send("Ошибка скачивания файла.")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
imgData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Send("Ошибка чтения файла.")
|
||||
}
|
||||
|
||||
c.Send("⏳ Обрабатываю чек через OCR...")
|
||||
|
||||
// 2. Отправляем в сервис
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
items, err := bot.ocrService.ProcessReceiptImage(ctx, imgData)
|
||||
if err != nil {
|
||||
logger.Log.Error("OCR processing failed", zap.Error(err))
|
||||
return c.Send("❌ Ошибка распознавания: " + err.Error())
|
||||
}
|
||||
|
||||
// 3. Формируем отчет
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("🧾 <b>Результат (%d поз.):</b>\n\n", len(items)))
|
||||
|
||||
matchedCount := 0
|
||||
for _, item := range items {
|
||||
if item.IsMatched {
|
||||
matchedCount++
|
||||
sb.WriteString(fmt.Sprintf("✅ %s\n └ <code>%s</code> x %s = %s\n",
|
||||
item.RawName, item.Amount, item.Price, item.Sum))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("❓ <b>%s</b>\n └ Нет привязки!\n", item.RawName))
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("\nРаспознано: %d/%d", matchedCount, len(items)))
|
||||
|
||||
// Тут можно добавить кнопки, если что-то не распознано
|
||||
// Но для начала просто текст
|
||||
return c.Send(sb.String(), tele.ModeHTML)
|
||||
}
|
||||
Reference in New Issue
Block a user