mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
Перевел на multi-tenant
Добавил поставщиков Накладные успешно создаются из фронта
This commit is contained in:
258
cmd/main.go
258
cmd/main.go
@@ -1,24 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"rmser/config"
|
||||
"rmser/internal/domain/catalog"
|
||||
"rmser/internal/domain/invoices"
|
||||
"rmser/internal/infrastructure/db"
|
||||
"rmser/internal/infrastructure/ocr_client"
|
||||
|
||||
"rmser/internal/transport/http/middleware"
|
||||
tgBot "rmser/internal/transport/telegram"
|
||||
|
||||
// Репозитории (инфраструктура)
|
||||
// Repositories
|
||||
accountPkg "rmser/internal/infrastructure/repository/account"
|
||||
catalogPkg "rmser/internal/infrastructure/repository/catalog"
|
||||
draftsPkg "rmser/internal/infrastructure/repository/drafts"
|
||||
invoicesPkg "rmser/internal/infrastructure/repository/invoices"
|
||||
@@ -26,43 +25,45 @@ import (
|
||||
opsRepoPkg "rmser/internal/infrastructure/repository/operations"
|
||||
recipesPkg "rmser/internal/infrastructure/repository/recipes"
|
||||
recRepoPkg "rmser/internal/infrastructure/repository/recommendations"
|
||||
suppliersPkg "rmser/internal/infrastructure/repository/suppliers"
|
||||
|
||||
"rmser/internal/infrastructure/rms"
|
||||
|
||||
// Services
|
||||
draftsServicePkg "rmser/internal/services/drafts"
|
||||
invServicePkg "rmser/internal/services/invoices" // Сервис накладных
|
||||
ocrServicePkg "rmser/internal/services/ocr"
|
||||
recServicePkg "rmser/internal/services/recommend"
|
||||
"rmser/internal/services/sync"
|
||||
"rmser/internal/transport/http/handlers" // Хендлеры
|
||||
|
||||
// Handlers
|
||||
"rmser/internal/transport/http/handlers"
|
||||
|
||||
"rmser/pkg/crypto"
|
||||
"rmser/pkg/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. Загрузка конфигурации
|
||||
// 1. Config
|
||||
cfg, err := config.LoadConfig(".")
|
||||
if err != nil {
|
||||
log.Fatalf("Ошибка загрузки конфига: %v", err)
|
||||
}
|
||||
// OCR Client
|
||||
pyClient := ocr_client.NewClient(cfg.OCR.ServiceURL)
|
||||
|
||||
// 2. Инициализация логгера
|
||||
// 2. Logger
|
||||
logger.Init(cfg.App.Mode)
|
||||
defer logger.Log.Sync()
|
||||
|
||||
logger.Log.Info("Запуск приложения rmser", zap.String("mode", cfg.App.Mode))
|
||||
|
||||
// 3a. Подключение Redis (Новое)
|
||||
// redisClient, err := redis.NewClient(cfg.Redis.Addr, cfg.Redis.Password, cfg.Redis.DB)
|
||||
// if err != nil {
|
||||
// logger.Log.Fatal("Ошибка подключения к Redis", zap.Error(err))
|
||||
// }
|
||||
|
||||
// 3. Подключение к БД (PostgreSQL)
|
||||
// 3. Crypto & DB
|
||||
if cfg.Security.SecretKey == "" {
|
||||
logger.Log.Fatal("Security.SecretKey не задан в конфиге!")
|
||||
}
|
||||
cryptoManager := crypto.NewCryptoManager(cfg.Security.SecretKey)
|
||||
database := db.NewPostgresDB(cfg.DB.DSN)
|
||||
|
||||
// 4. Инициализация слоев
|
||||
rmsClient := rms.NewClient(cfg.RMS.BaseURL, cfg.RMS.Login, cfg.RMS.Password)
|
||||
// 4. Repositories
|
||||
accountRepo := accountPkg.NewRepository(database)
|
||||
catalogRepo := catalogPkg.NewRepository(database)
|
||||
recipesRepo := recipesPkg.NewRepository(database)
|
||||
invoicesRepo := invoicesPkg.NewRepository(database)
|
||||
@@ -70,124 +71,89 @@ func main() {
|
||||
recRepo := recRepoPkg.NewRepository(database)
|
||||
ocrRepo := ocrRepoPkg.NewRepository(database)
|
||||
draftsRepo := draftsPkg.NewRepository(database)
|
||||
supplierRepo := suppliersPkg.NewRepository(database)
|
||||
|
||||
syncService := sync.NewService(rmsClient, catalogRepo, recipesRepo, invoicesRepo, opsRepo)
|
||||
// 5. RMS Factory
|
||||
rmsFactory := rms.NewFactory(accountRepo, cryptoManager)
|
||||
|
||||
// 6. Services
|
||||
pyClient := ocr_client.NewClient(cfg.OCR.ServiceURL)
|
||||
|
||||
syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo)
|
||||
recService := recServicePkg.NewService(recRepo)
|
||||
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, pyClient)
|
||||
draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, rmsClient)
|
||||
invoiceService := invServicePkg.NewService(rmsClient)
|
||||
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, pyClient)
|
||||
draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, rmsFactory)
|
||||
|
||||
// --- Инициализация Handler'ов ---
|
||||
invoiceHandler := handlers.NewInvoiceHandler(invoiceService)
|
||||
// 7. Handlers
|
||||
draftsHandler := handlers.NewDraftsHandler(draftsService)
|
||||
ocrHandler := handlers.NewOCRHandler(ocrService)
|
||||
recommendHandler := handlers.NewRecommendationsHandler(recService)
|
||||
|
||||
// --- БЛОК ПРОВЕРКИ СИНХРОНИЗАЦИИ (Run-once on start) ---
|
||||
go func() {
|
||||
logger.Log.Info(">>> Запуск тестовой синхронизации...")
|
||||
|
||||
// 1. Каталог
|
||||
if err := syncService.SyncCatalog(); err != nil {
|
||||
logger.Log.Error("Ошибка синхронизации каталога", zap.Error(err))
|
||||
} else {
|
||||
logger.Log.Info("<<< Каталог успешно синхронизирован")
|
||||
}
|
||||
|
||||
// 2. Техкарты
|
||||
if err := syncService.SyncRecipes(); err != nil {
|
||||
logger.Log.Error("Ошибка синхронизации техкарт", zap.Error(err))
|
||||
} else {
|
||||
logger.Log.Info("<<< Техкарты успешно синхронизированы")
|
||||
}
|
||||
|
||||
// 3. Накладные
|
||||
if err := syncService.SyncInvoices(); err != nil {
|
||||
logger.Log.Error("Ошибка синхронизации накладных", zap.Error(err))
|
||||
} else {
|
||||
logger.Log.Info("<<< Накладные успешно синхронизированы")
|
||||
}
|
||||
// 4. Складские операции
|
||||
if err := syncService.SyncStoreOperations(); err != nil {
|
||||
logger.Log.Error("Ошибка синхронизации операций", zap.Error(err))
|
||||
} else {
|
||||
logger.Log.Info("<<< Операции успешно синхронизированы")
|
||||
}
|
||||
logger.Log.Info(">>> Запуск расчета рекомендаций...")
|
||||
if err := recService.RefreshRecommendations(); err != nil {
|
||||
logger.Log.Error("Ошибка расчета рекомендаций", zap.Error(err))
|
||||
} else {
|
||||
// Для отладки можно вывести пару штук
|
||||
recs, _ := recService.GetRecommendations()
|
||||
logger.Log.Info("<<< Анализ завершен", zap.Int("found", len(recs)))
|
||||
}
|
||||
// === ТЕСТ ОТПРАВКИ НАКЛАДНОЙ ===
|
||||
// Запускаем через небольшую паузу, чтобы логи не перемешались
|
||||
// time.Sleep(2 * time.Second)
|
||||
// runManualInvoiceTest(rmsClient, catalogRepo)
|
||||
// ===============================
|
||||
}()
|
||||
// -------------------------------------------------------
|
||||
// Запуск бота (в отдельной горутине, т.к. Start() блокирует поток)
|
||||
// 8. Telegram Bot (Передаем syncService)
|
||||
if cfg.Telegram.Token != "" {
|
||||
bot, err := tgBot.NewBot(cfg.Telegram, ocrService)
|
||||
// !!! syncService добавлен в аргументы
|
||||
bot, err := tgBot.NewBot(cfg.Telegram, ocrService, syncService, accountRepo, rmsFactory, cryptoManager)
|
||||
if err != nil {
|
||||
logger.Log.Fatal("Ошибка создания Telegram бота", zap.Error(err))
|
||||
}
|
||||
go bot.Start()
|
||||
defer bot.Stop() // Graceful shutdown
|
||||
} else {
|
||||
logger.Log.Warn("Telegram token не задан, бот не запущен")
|
||||
defer bot.Stop()
|
||||
}
|
||||
|
||||
// 5. Запуск HTTP сервера (Gin)
|
||||
// 9. HTTP Server
|
||||
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"}
|
||||
corsConfig.AllowAllOrigins = true
|
||||
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
|
||||
corsConfig.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "X-Telegram-User-ID"}
|
||||
r.Use(cors.New(corsConfig))
|
||||
|
||||
api := r.Group("/api")
|
||||
{
|
||||
// Invoices
|
||||
api.POST("/invoices/send", invoiceHandler.SendInvoice)
|
||||
|
||||
// Черновики
|
||||
api.Use(middleware.AuthMiddleware(accountRepo))
|
||||
{
|
||||
// Drafts & Invoices
|
||||
api.GET("/drafts", draftsHandler.GetDrafts)
|
||||
api.GET("/drafts/:id", draftsHandler.GetDraft)
|
||||
api.DELETE("/drafts/:id", draftsHandler.DeleteDraft)
|
||||
api.PATCH("/drafts/:id/items/:itemId", draftsHandler.UpdateItem)
|
||||
api.POST("/drafts/:id/commit", draftsHandler.CommitDraft)
|
||||
api.POST("/drafts/container", draftsHandler.AddContainer) // Добавление новой фасовки
|
||||
api.POST("/drafts/container", draftsHandler.AddContainer)
|
||||
|
||||
// Склады
|
||||
// Dictionaries
|
||||
api.GET("/dictionaries", draftsHandler.GetDictionaries)
|
||||
api.GET("/dictionaries/stores", draftsHandler.GetStores)
|
||||
|
||||
// Recommendations
|
||||
api.GET("/recommendations", recommendHandler.GetRecommendations)
|
||||
|
||||
// OCR
|
||||
// OCR & Matching
|
||||
api.GET("/ocr/catalog", ocrHandler.GetCatalog)
|
||||
api.GET("/ocr/matches", ocrHandler.GetMatches)
|
||||
api.POST("/ocr/match", ocrHandler.SaveMatch)
|
||||
api.DELETE("/ocr/match", ocrHandler.DeleteMatch)
|
||||
api.GET("/ocr/unmatched", ocrHandler.GetUnmatched)
|
||||
api.GET("/ocr/search", ocrHandler.SearchProducts)
|
||||
|
||||
// Manual Sync Trigger
|
||||
api.POST("/sync/all", func(c *gin.Context) {
|
||||
userID := c.MustGet("userID").(uuid.UUID)
|
||||
// Запускаем в горутине, чтобы не держать соединение
|
||||
go func() {
|
||||
if err := syncService.SyncAllData(userID); err != nil {
|
||||
logger.Log.Error("Manual sync failed", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
c.JSON(200, gin.H{"status": "sync_started", "message": "Синхронизация запущена в фоне"})
|
||||
})
|
||||
}
|
||||
|
||||
// Простой хелсчек
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "ok",
|
||||
"time": time.Now().Format(time.RFC3339),
|
||||
})
|
||||
c.JSON(200, gin.H{"status": "ok", "time": time.Now().Format(time.RFC3339)})
|
||||
})
|
||||
|
||||
logger.Log.Info("Сервер запускается", zap.String("port", cfg.App.Port))
|
||||
@@ -195,107 +161,3 @@ func main() {
|
||||
logger.Log.Fatal("Ошибка запуска сервера", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// runManualInvoiceTest создает тестовую накладную и отправляет её в RMS
|
||||
func runManualInvoiceTest(client rms.ClientI, catRepo catalog.Repository) {
|
||||
logger.Log.Info(">>> [TEST] Начало теста создания накладной...")
|
||||
|
||||
// === НАСТРОЙКИ ТЕСТА ===
|
||||
const (
|
||||
MyStoreGUID = "1239d270-1bbe-f64f-b7ea-5f00518ef508" // <-- ВАШ СКЛАД
|
||||
MySupplierGUID = "780aa87e-2688-4f99-915b-7924ca392ac1" // <-- ВАШ ПОСТАВЩИК
|
||||
MyProductGUID = "607a1e96-f539-45d2-8709-3919f94bdc3e" // <-- ВАШ ТОВАР (Опционально)
|
||||
)
|
||||
// =======================
|
||||
|
||||
// 1. Поиск товара
|
||||
var targetProduct catalog.Product
|
||||
var found bool
|
||||
|
||||
// Если задан конкретный ID товара в константе - ищем его
|
||||
if MyProductGUID != "00000000-0000-0000-0000-000000000000" {
|
||||
products, _ := catRepo.GetAll() // В реальном коде лучше GetByID, но у нас repo пока простой
|
||||
targetUUID := uuid.MustParse(MyProductGUID)
|
||||
for _, p := range products {
|
||||
if p.ID == targetUUID {
|
||||
targetProduct = p
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Log.Error("[TEST] Товар с указанным MyProductGUID не найден в локальной БД")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Иначе ищем первый попавшийся GOODS
|
||||
products, err := catRepo.GetAll()
|
||||
if err != nil || len(products) == 0 {
|
||||
logger.Log.Error("[TEST] БД пуста. Выполните SyncCatalog.")
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range products {
|
||||
// Ищем именно товар (GOODS) или заготовку (PREPARED), но не группу и не блюдо, если нужно
|
||||
if p.Type == "GOODS" {
|
||||
targetProduct = p
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logger.Log.Warn("[TEST] Не найдено товаров с типом GOODS. Берем первый попавшийся.")
|
||||
targetProduct = products[0]
|
||||
}
|
||||
}
|
||||
|
||||
logger.Log.Info("[TEST] Используем товар",
|
||||
zap.String("name", targetProduct.Name),
|
||||
zap.String("type", targetProduct.Type),
|
||||
zap.String("uuid", targetProduct.ID.String()),
|
||||
)
|
||||
|
||||
// 2. Парсинг ID Склада и Поставщика
|
||||
storeID := uuid.Nil
|
||||
if MyStoreGUID != "00000000-0000-0000-0000-000000000000" {
|
||||
storeID = uuid.MustParse(MyStoreGUID)
|
||||
} else {
|
||||
logger.Log.Warn("[TEST] ID склада не задан! iiko вернет ошибку валидации.")
|
||||
storeID = uuid.New() // Рандом, чтобы XML собрался
|
||||
}
|
||||
|
||||
supplierID := uuid.Nil
|
||||
if MySupplierGUID != "00000000-0000-0000-0000-000000000000" {
|
||||
supplierID = uuid.MustParse(MySupplierGUID)
|
||||
} else {
|
||||
logger.Log.Warn("[TEST] ID поставщика не задан!")
|
||||
supplierID = uuid.New()
|
||||
}
|
||||
|
||||
// 3. Формируем накладную
|
||||
testInv := invoices.Invoice{
|
||||
ID: uuid.Nil,
|
||||
DocumentNumber: fmt.Sprintf("TEST-%d", time.Now().Unix()%10000),
|
||||
DateIncoming: time.Now(),
|
||||
SupplierID: supplierID,
|
||||
DefaultStoreID: storeID,
|
||||
Status: "NEW",
|
||||
Items: []invoices.InvoiceItem{
|
||||
{
|
||||
ProductID: targetProduct.ID,
|
||||
Amount: decimal.NewFromFloat(5.0),
|
||||
Price: decimal.NewFromFloat(100.0),
|
||||
Sum: decimal.NewFromFloat(500.0),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 4. Отправляем
|
||||
logger.Log.Info("[TEST] Отправка запроса в RMS...")
|
||||
docNum, err := client.CreateIncomingInvoice(testInv)
|
||||
if err != nil {
|
||||
logger.Log.Error("[TEST] Ошибка отправки накладной", zap.Error(err))
|
||||
} else {
|
||||
logger.Log.Info("[TEST] УСПЕХ! Накладная создана.", zap.String("doc_number", docNum))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user