2901-zustend для стора. сохранение черновиков построчно

редактор xml пока не работает, но есть
ui переработал
This commit is contained in:
2026-01-29 10:58:58 +03:00
parent b99e328d35
commit 4da5fdd130
23 changed files with 2391 additions and 1384 deletions

View File

@@ -2,6 +2,7 @@ package handlers
import (
"fmt"
"io"
"net/http"
"time"
@@ -11,15 +12,20 @@ import (
"go.uber.org/zap"
"rmser/internal/services/drafts"
"rmser/internal/services/ocr"
"rmser/pkg/logger"
)
type DraftsHandler struct {
service *drafts.Service
service *drafts.Service
ocrService *ocr.Service
}
func NewDraftsHandler(service *drafts.Service) *DraftsHandler {
return &DraftsHandler{service: service}
func NewDraftsHandler(service *drafts.Service, ocrService *ocr.Service) *DraftsHandler {
return &DraftsHandler{
service: service,
ocrService: ocrService,
}
}
func (h *DraftsHandler) GetDraft(c *gin.Context) {
@@ -354,3 +360,54 @@ func (h *DraftsHandler) ReorderItems(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}
// Upload обрабатывает загрузку файла и прогоняет через OCR
func (h *DraftsHandler) Upload(c *gin.Context) {
// Лимит размера тела запроса (20MB)
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 20<<20)
// Получаем файл из формы
fileHeader, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "file is required"})
return
}
// Открываем файл для чтения
file, err := fileHeader.Open()
if err != nil {
logger.Log.Error("Failed to open uploaded file", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to open file"})
return
}
defer file.Close()
// Читаем байты файла
fileBytes, err := io.ReadAll(file)
if err != nil {
logger.Log.Error("Failed to read file bytes", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read file"})
return
}
// Получаем userID из контекста
userID := c.MustGet("userID").(uuid.UUID)
// Вызываем ProcessDocument
draft, err := h.ocrService.ProcessDocument(c.Request.Context(), userID, fileBytes, fileHeader.Filename)
if err != nil {
// Если черновик создан, но произошла ошибка OCR, возвращаем черновик со статусом ERROR
// Проверяем, что draft не nil (черновик был создан)
if draft != nil {
c.JSON(http.StatusOK, draft)
return
}
// Если черновик не был создан, возвращаем ошибку
logger.Log.Error("ProcessDocument failed", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Успешная обработка
c.JSON(http.StatusOK, draft)
}

View File

@@ -22,6 +22,11 @@ type SettingsHandler struct {
accountRepo account.Repository
catalogRepo catalog.Repository
notifier Notifier // Поле для отправки уведомлений
rmsFactory RMSFactory
}
type RMSFactory interface {
ClearCacheForUser(userID uuid.UUID)
}
func NewSettingsHandler(accRepo account.Repository, catRepo catalog.Repository) *SettingsHandler {
@@ -31,6 +36,11 @@ func NewSettingsHandler(accRepo account.Repository, catRepo catalog.Repository)
}
}
// SetRMSFactory используется для внедрения зависимости после инициализации
func (h *SettingsHandler) SetRMSFactory(f RMSFactory) {
h.rmsFactory = f
}
// SetNotifier используется для внедрения зависимости после инициализации
func (h *SettingsHandler) SetNotifier(n Notifier) {
h.notifier = n
@@ -388,3 +398,107 @@ func (h *SettingsHandler) RemoveUser(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "removed"})
}
// --- Server Management ---
// ServerShortDTO - краткая информация о сервере для списка
type ServerShortDTO struct {
ID string `json:"id"`
Name string `json:"name"`
Role string `json:"role"` // OWNER, ADMIN, OPERATOR
IsActive bool `json:"is_active"`
}
// GetUserServers возвращает список всех серверов пользователя с ролями и флагом активности
func (h *SettingsHandler) GetUserServers(c *gin.Context) {
userID := c.MustGet("userID").(uuid.UUID)
servers, err := h.accountRepo.GetAllAvailableServers(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
activeServer, err := h.accountRepo.GetActiveServer(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var activeServerID *uuid.UUID
if activeServer != nil {
activeServerID = &activeServer.ID
}
response := make([]ServerShortDTO, 0, len(servers))
for _, server := range servers {
role, err := h.accountRepo.GetUserRole(userID, server.ID)
if err != nil {
role = account.RoleOperator
}
response = append(response, ServerShortDTO{
ID: server.ID.String(),
Name: server.Name,
Role: string(role),
IsActive: activeServerID != nil && server.ID == *activeServerID,
})
}
c.JSON(http.StatusOK, response)
}
// SwitchActiveServerRequest - запрос на переключение активного сервера
type SwitchActiveServerRequest struct {
ServerID string `json:"server_id" binding:"required"`
}
// SwitchActiveServer переключает активный сервер пользователя
func (h *SettingsHandler) SwitchActiveServer(c *gin.Context) {
userID := c.MustGet("userID").(uuid.UUID)
var req SwitchActiveServerRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
serverID, err := uuid.Parse(req.ServerID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid server_id format"})
return
}
// Проверяем, что сервер доступен пользователю
servers, err := h.accountRepo.GetAllAvailableServers(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var serverExists bool
for _, s := range servers {
if s.ID == serverID {
serverExists = true
break
}
}
if !serverExists {
c.JSON(http.StatusNotFound, gin.H{"error": "server not found or not accessible"})
return
}
// Переключаем активный сервер
if err := h.accountRepo.SetActiveServer(userID, serverID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Сбрасываем кэш RMS клиента
if h.rmsFactory != nil {
h.rmsFactory.ClearCacheForUser(userID)
}
c.JSON(http.StatusOK, gin.H{"status": "active_server_changed"})
}