mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
0202-финиш перед десктопом
пересчет поправил редактирование с перепроведением галка автопроведения работает рекомендации починил
This commit is contained in:
@@ -203,50 +203,98 @@ type CommitRequestDTO struct {
|
||||
SupplierID string `json:"supplier_id"`
|
||||
Comment string `json:"comment"`
|
||||
IncomingDocNum string `json:"incoming_document_number"`
|
||||
IsProcessed bool `json:"is_processed"`
|
||||
}
|
||||
|
||||
func (h *DraftsHandler) CommitDraft(c *gin.Context) {
|
||||
userID := c.MustGet("userID").(uuid.UUID)
|
||||
// Защита от паники
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Log.Error("CRITICAL PANIC in CommitDraft Handler",
|
||||
zap.Any("panic", r),
|
||||
zap.Stack("stack"),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Internal Server Error: %v", r)})
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Log.Info("--- HANDLER: Start CommitDraft ---", zap.String("path", c.Request.URL.Path))
|
||||
|
||||
userID, ok := c.Get("userID")
|
||||
if !ok {
|
||||
logger.Log.Error("HANDLER: UserID missing in context")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
userUUID := userID.(uuid.UUID)
|
||||
logger.Log.Info("HANDLER: UserID extracted", zap.String("user_id", userUUID.String()))
|
||||
|
||||
draftID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
logger.Log.Warn("HANDLER: Invalid DraftID", zap.String("param", c.Param("id")), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid draft id"})
|
||||
return
|
||||
}
|
||||
logger.Log.Info("HANDLER: DraftID parsed", zap.String("draft_id", draftID.String()))
|
||||
|
||||
var req CommitRequestDTO
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.Log.Error("HANDLER: JSON Binding failed", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
logger.Log.Info("HANDLER: Payload bound",
|
||||
zap.String("date_incoming", req.DateIncoming),
|
||||
zap.String("store_id", req.StoreID),
|
||||
zap.String("supplier_id", req.SupplierID),
|
||||
zap.String("incoming_doc_num", req.IncomingDocNum),
|
||||
zap.Bool("is_processed", req.IsProcessed),
|
||||
)
|
||||
|
||||
date, err := time.Parse("2006-01-02", req.DateIncoming)
|
||||
if err != nil {
|
||||
logger.Log.Error("HANDLER: Date parsing failed", zap.String("date", req.DateIncoming), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid date format"})
|
||||
return
|
||||
}
|
||||
|
||||
storeID, err := uuid.Parse(req.StoreID)
|
||||
if err != nil {
|
||||
logger.Log.Error("HANDLER: StoreID parsing failed", zap.String("store_id", req.StoreID), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid store id"})
|
||||
return
|
||||
}
|
||||
|
||||
supplierID, err := uuid.Parse(req.SupplierID)
|
||||
if err != nil {
|
||||
logger.Log.Error("HANDLER: SupplierID parsing failed", zap.String("supplier_id", req.SupplierID), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid supplier id"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Info("HANDLER: Calling UpdateDraftHeader...",
|
||||
zap.String("draft_id", draftID.String()),
|
||||
zap.String("store_id", storeID.String()),
|
||||
zap.String("supplier_id", supplierID.String()),
|
||||
zap.Time("date", date),
|
||||
)
|
||||
|
||||
if err := h.service.UpdateDraftHeader(draftID, &storeID, &supplierID, date, req.Comment, req.IncomingDocNum); err != nil {
|
||||
logger.Log.Error("HANDLER: UpdateDraftHeader failed", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update header: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
docNum, err := h.service.CommitDraft(draftID, userID)
|
||||
logger.Log.Info("HANDLER: Calling CommitDraft service...", zap.String("draft_id", draftID.String()), zap.String("user_id", userUUID.String()))
|
||||
|
||||
docNum, err := h.service.CommitDraft(draftID, userUUID, req.IsProcessed)
|
||||
if err != nil {
|
||||
logger.Log.Error("Commit failed", zap.Error(err))
|
||||
logger.Log.Warn("HANDLER: CommitDraft service failed", zap.Error(err))
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": "RMS error: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Info("HANDLER: Success!", zap.String("doc_num", docNum))
|
||||
c.JSON(http.StatusOK, gin.H{"status": "completed", "document_number": docNum})
|
||||
}
|
||||
|
||||
|
||||
@@ -110,3 +110,23 @@ func (h *InvoiceHandler) SyncInvoices(c *gin.Context) {
|
||||
"message": "Синхронизация запущена",
|
||||
})
|
||||
}
|
||||
|
||||
// GetStats godoc
|
||||
// @Summary Получить статистику по накладным
|
||||
// @Description Возвращает статистику по накладным для текущего пользователя
|
||||
// @Tags invoices
|
||||
// @Produce json
|
||||
// @Success 200 {object} invService.InvoiceStatsDTO
|
||||
// @Failure 500 {object} map[string]string
|
||||
func (h *InvoiceHandler) GetStats(c *gin.Context) {
|
||||
userID := c.MustGet("userID").(uuid.UUID)
|
||||
|
||||
stats, err := h.service.GetStats(userID)
|
||||
if err != nil {
|
||||
logger.Log.Error("Ошибка получения статистики", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Ошибка получения статистики"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
@@ -4,29 +4,56 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"rmser/internal/domain/account"
|
||||
"rmser/internal/services/recommend"
|
||||
"rmser/pkg/logger"
|
||||
)
|
||||
|
||||
type RecommendationsHandler struct {
|
||||
service *recommend.Service
|
||||
service *recommend.Service
|
||||
accountRepo account.Repository
|
||||
}
|
||||
|
||||
func NewRecommendationsHandler(service *recommend.Service) *RecommendationsHandler {
|
||||
return &RecommendationsHandler{service: service}
|
||||
func NewRecommendationsHandler(service *recommend.Service, accountRepo account.Repository) *RecommendationsHandler {
|
||||
return &RecommendationsHandler{
|
||||
service: service,
|
||||
accountRepo: accountRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRecommendations godoc
|
||||
// @Summary Получить список рекомендаций
|
||||
// @Description Возвращает сгенерированные рекомендации (проблемные зоны учета)
|
||||
// @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()
|
||||
userID := c.MustGet("userID").(uuid.UUID)
|
||||
|
||||
// Получаем активный сервер пользователя
|
||||
server, err := h.accountRepo.GetActiveServer(userID)
|
||||
if err != nil {
|
||||
logger.Log.Error("Ошибка получения активного сервера", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get active server"})
|
||||
return
|
||||
}
|
||||
if server == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "no active server"})
|
||||
return
|
||||
}
|
||||
|
||||
// Сначала обновляем рекомендации
|
||||
if err := h.service.RefreshRecommendations(server.ID); err != nil {
|
||||
logger.Log.Error("Ошибка обновления рекомендаций", zap.Error(err))
|
||||
// Не прерываем выполнение, продолжаем с текущими данными
|
||||
}
|
||||
|
||||
// Затем получаем рекомендации
|
||||
recs, err := h.service.GetRecommendations(server.ID)
|
||||
if err != nil {
|
||||
logger.Log.Error("Ошибка получения рекомендаций", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
@@ -48,13 +49,16 @@ func (h *SettingsHandler) SetNotifier(n Notifier) {
|
||||
|
||||
// SettingsResponse - DTO для отдачи настроек
|
||||
type SettingsResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
BaseURL string `json:"base_url"`
|
||||
DefaultStoreID *string `json:"default_store_id"` // Nullable
|
||||
RootGroupID *string `json:"root_group_id"` // Nullable
|
||||
AutoConduct bool `json:"auto_conduct"`
|
||||
Role string `json:"role"` // OWNER, ADMIN, OPERATOR
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
BaseURL string `json:"base_url"`
|
||||
DefaultStoreID *string `json:"default_store_id"` // Nullable
|
||||
RootGroupID *string `json:"root_group_id"` // Nullable
|
||||
AutoConduct bool `json:"auto_conduct"`
|
||||
Role string `json:"role"` // OWNER, ADMIN, OPERATOR
|
||||
SyncInterval int `json:"sync_interval"` // Интервал синхронизации в минутах
|
||||
LastSyncAt *time.Time `json:"last_sync_at"` // Время последней синхронизации
|
||||
LastActivityAt *time.Time `json:"last_activity_at"` // Время последней активности
|
||||
}
|
||||
|
||||
// GetSettings возвращает настройки активного сервера + роль пользователя
|
||||
@@ -77,11 +81,14 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
resp := SettingsResponse{
|
||||
ID: server.ID.String(),
|
||||
Name: server.Name,
|
||||
BaseURL: server.BaseURL,
|
||||
AutoConduct: server.AutoProcess,
|
||||
Role: string(role),
|
||||
ID: server.ID.String(),
|
||||
Name: server.Name,
|
||||
BaseURL: server.BaseURL,
|
||||
AutoConduct: server.AutoProcess,
|
||||
Role: string(role),
|
||||
SyncInterval: server.SyncInterval,
|
||||
LastSyncAt: server.LastSyncAt,
|
||||
LastActivityAt: server.LastActivityAt,
|
||||
}
|
||||
|
||||
if server.DefaultStoreID != nil {
|
||||
@@ -96,16 +103,17 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// UpdateSettingsDTO
|
||||
// UpdateSettingsDTO - DTO для частичного обновления настроек (PATCH-семантика)
|
||||
type UpdateSettingsDTO struct {
|
||||
Name string `json:"name"`
|
||||
DefaultStoreID string `json:"default_store_id"`
|
||||
RootGroupID string `json:"root_group_id"`
|
||||
AutoProcess bool `json:"auto_process"`
|
||||
AutoConduct bool `json:"auto_conduct"`
|
||||
Name *string `json:"name"`
|
||||
DefaultStoreID *string `json:"default_store_id"`
|
||||
RootGroupID *string `json:"root_group_id"`
|
||||
AutoProcess *bool `json:"auto_process"` // Legacy для обратной совместимости
|
||||
AutoConduct *bool `json:"auto_conduct"` // Новое поле
|
||||
SyncInterval *int `json:"sync_interval,omitempty"` // Интервал синхронизации в минутах (5 - 10080)
|
||||
}
|
||||
|
||||
// UpdateSettings сохраняет настройки
|
||||
// UpdateSettings сохраняет настройки с PATCH-семантикой
|
||||
func (h *SettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
userID := c.MustGet("userID").(uuid.UUID)
|
||||
|
||||
@@ -115,6 +123,11 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Логирование полученных данных для отладки
|
||||
logger.Log.Info("Получен запрос на обновление настроек",
|
||||
zap.Any("request", req),
|
||||
)
|
||||
|
||||
server, err := h.accountRepo.GetActiveServer(userID)
|
||||
if err != nil || server == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "No active server"})
|
||||
@@ -132,31 +145,56 @@ func (h *SettingsHandler) UpdateSettings(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
server.Name = req.Name
|
||||
// Обновление имени (только если передано)
|
||||
if req.Name != nil {
|
||||
server.Name = *req.Name
|
||||
}
|
||||
|
||||
if req.AutoConduct {
|
||||
server.AutoProcess = true
|
||||
} else {
|
||||
server.AutoProcess = req.AutoProcess || req.AutoConduct
|
||||
// Обновление флага авто-проведения
|
||||
if req.AutoConduct != nil {
|
||||
server.AutoProcess = *req.AutoConduct
|
||||
} else if req.AutoProcess != nil {
|
||||
// Fallback для старых клиентов, которые используют legacy поле
|
||||
server.AutoProcess = *req.AutoProcess
|
||||
}
|
||||
|
||||
if req.DefaultStoreID != "" {
|
||||
if uid, err := uuid.Parse(req.DefaultStoreID); err == nil {
|
||||
server.DefaultStoreID = &uid
|
||||
// Обновление интервала синхронизации
|
||||
if req.SyncInterval != nil {
|
||||
// Валидация диапазона: от 5 минут до 1 недели (10080 минут)
|
||||
if *req.SyncInterval < 5 || *req.SyncInterval > 10080 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "sync_interval должен быть от 5 минут до 1 недели (10080 минут)"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
server.DefaultStoreID = nil
|
||||
server.SyncInterval = *req.SyncInterval
|
||||
}
|
||||
|
||||
if req.RootGroupID != "" {
|
||||
if uid, err := uuid.Parse(req.RootGroupID); err == nil {
|
||||
server.RootGroupGUID = &uid
|
||||
// Обновление DefaultStoreID
|
||||
if req.DefaultStoreID != nil {
|
||||
if *req.DefaultStoreID == "" {
|
||||
// Пустая строка -> сбрасываем в nil
|
||||
server.DefaultStoreID = nil
|
||||
} else {
|
||||
// UUID -> обновляем
|
||||
if uid, err := uuid.Parse(*req.DefaultStoreID); err == nil {
|
||||
server.DefaultStoreID = &uid
|
||||
}
|
||||
}
|
||||
} else {
|
||||
server.RootGroupGUID = nil
|
||||
}
|
||||
// Если nil -> не трогаем текущее значение
|
||||
|
||||
// Обновление RootGroupID
|
||||
if req.RootGroupID != nil {
|
||||
if *req.RootGroupID == "" {
|
||||
// Пустая строка -> сбрасываем в nil
|
||||
server.RootGroupGUID = nil
|
||||
} else {
|
||||
// UUID -> обновляем
|
||||
if uid, err := uuid.Parse(*req.RootGroupID); err == nil {
|
||||
server.RootGroupGUID = &uid
|
||||
}
|
||||
}
|
||||
}
|
||||
// Если nil -> не трогаем текущее значение
|
||||
|
||||
if err := h.accountRepo.SaveServerSettings(server); err != nil {
|
||||
logger.Log.Error("Failed to save settings", zap.Error(err))
|
||||
|
||||
@@ -202,17 +202,17 @@ func (bot *Bot) registrationMiddleware(next tele.HandlerFunc) tele.HandlerFunc {
|
||||
|
||||
func (bot *Bot) handleStartCommand(c tele.Context) error {
|
||||
payload := c.Message().Payload
|
||||
|
||||
|
||||
// Обработка desktop авторизации
|
||||
if payload != "" && strings.HasPrefix(payload, "auth_") {
|
||||
sessionID := strings.TrimPrefix(payload, "auth_")
|
||||
telegramID := c.Sender().ID
|
||||
|
||||
|
||||
logger.Log.Info("Обработка desktop авторизации",
|
||||
zap.String("session_id", sessionID),
|
||||
zap.Int64("telegram_id", telegramID),
|
||||
)
|
||||
|
||||
|
||||
if err := bot.authService.ConfirmDesktopAuth(sessionID, telegramID); err != nil {
|
||||
logger.Log.Error("Ошибка подтверждения desktop авторизации",
|
||||
zap.String("session_id", sessionID),
|
||||
@@ -221,10 +221,10 @@ func (bot *Bot) handleStartCommand(c tele.Context) error {
|
||||
)
|
||||
return c.Send("❌ Ошибка авторизации. Попробуйте снова.", tele.ModeHTML)
|
||||
}
|
||||
|
||||
|
||||
return c.Send("✅ Авторизация успешна! Вы можете вернуться в приложение.", tele.ModeHTML)
|
||||
}
|
||||
|
||||
|
||||
if payload != "" && strings.HasPrefix(payload, "invite_") {
|
||||
return bot.handleInviteLink(c, strings.TrimPrefix(payload, "invite_"))
|
||||
}
|
||||
@@ -417,19 +417,70 @@ func (bot *Bot) renderServersMenu(c tele.Context) error {
|
||||
}
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, s.ID)
|
||||
label := fmt.Sprintf("%s %s (%s)", icon, s.Name, role)
|
||||
btn := menu.Data(label, "set_server_"+s.ID.String())
|
||||
btn := menu.Data(label, "srv_menu_"+s.ID.String())
|
||||
rows = append(rows, menu.Row(btn))
|
||||
}
|
||||
|
||||
btnAdd := menu.Data("➕ Добавить сервер", "act_add_server")
|
||||
btnDel := menu.Data("⚙️ Управление / Удаление", "act_del_server_menu")
|
||||
btnBack := menu.Data("🔙 Назад", "nav_main")
|
||||
|
||||
rows = append(rows, menu.Row(btnAdd, btnDel))
|
||||
rows = append(rows, menu.Row(btnAdd))
|
||||
rows = append(rows, menu.Row(btnBack))
|
||||
menu.Inline(rows...)
|
||||
|
||||
txt := fmt.Sprintf("<b>🖥 Ваши серверы (%d):</b>\n\nНажмите на сервер, чтобы сделать его активным.", len(servers))
|
||||
txt := fmt.Sprintf("<b>🖥 Ваши серверы (%d):</b>\n\nНажмите на сервер для управления.", len(servers))
|
||||
return c.EditOrSend(txt, menu, tele.ModeHTML)
|
||||
}
|
||||
|
||||
// renderServerMenu показывает подменю управления конкретным сервером
|
||||
func (bot *Bot) renderServerMenu(c tele.Context, serverID uuid.UUID) error {
|
||||
userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID)
|
||||
server, err := bot.accountRepo.GetServerByID(serverID)
|
||||
if err != nil {
|
||||
return c.Send("Ошибка: сервер не найден")
|
||||
}
|
||||
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
|
||||
activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID)
|
||||
isActive := activeServer != nil && activeServer.ID == server.ID
|
||||
|
||||
menu := &tele.ReplyMarkup{}
|
||||
var rows []tele.Row
|
||||
|
||||
// Кнопка "Выбрать активным" (доступна всем)
|
||||
if !isActive {
|
||||
btnSetActive := menu.Data("✅ Выбрать активным", "srv_set_active_"+server.ID.String())
|
||||
rows = append(rows, menu.Row(btnSetActive))
|
||||
} else {
|
||||
btnActive := menu.Data("🟢 Активный сервер", "noop")
|
||||
rows = append(rows, menu.Row(btnActive))
|
||||
}
|
||||
|
||||
// Кнопка "Показать URL/Логин" (доступна Admin и Owner)
|
||||
if role == account.RoleOwner || role == account.RoleAdmin {
|
||||
btnShowCreds := menu.Data("👁 Показать URL/Логин", "srv_show_creds_"+server.ID.String())
|
||||
btnInvite := menu.Data("📩 Пригласить сотрудника", fmt.Sprintf("gen_invite_%s", server.ID.String()))
|
||||
rows = append(rows, menu.Row(btnShowCreds, btnInvite))
|
||||
}
|
||||
|
||||
// Кнопка "Обновить логин-пароль" (только Owner)
|
||||
if role == account.RoleOwner {
|
||||
btnUpdateCreds := menu.Data("✏️ Обновить логин-пароль", "srv_update_creds_"+server.ID.String())
|
||||
rows = append(rows, menu.Row(btnUpdateCreds))
|
||||
}
|
||||
|
||||
// Кнопка "Удалить сервер" (только Owner)
|
||||
if role == account.RoleOwner {
|
||||
btnDelete := menu.Data("❌ Удалить сервер", "srv_delete_"+server.ID.String())
|
||||
rows = append(rows, menu.Row(btnDelete))
|
||||
}
|
||||
|
||||
btnBack := menu.Data("🔙 Назад к списку", "nav_servers")
|
||||
rows = append(rows, menu.Row(btnBack))
|
||||
menu.Inline(rows...)
|
||||
|
||||
txt := fmt.Sprintf("<b>⚙️ Управление сервером</b>\n\n🏢 <b>Название:</b> %s\n🔗 <b>URL:</b> %s\n👤 <b>Ваша роль:</b> %s",
|
||||
server.Name, server.BaseURL, role)
|
||||
return c.EditOrSend(txt, menu, tele.ModeHTML)
|
||||
}
|
||||
|
||||
@@ -536,6 +587,131 @@ func (bot *Bot) handleCallback(c tele.Context) error {
|
||||
return bot.handleBillingCallbacks(c, data, userDB)
|
||||
}
|
||||
|
||||
// Обработка кнопок подменю сервера
|
||||
if strings.HasPrefix(data, "srv_menu_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_menu_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
return bot.renderServerMenu(c, targetID)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "srv_set_active_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_set_active_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
if err := bot.accountRepo.SetActiveServer(userDB.ID, targetID); err != nil {
|
||||
logger.Log.Error("Failed to set active server", zap.Error(err))
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: доступ запрещен"})
|
||||
}
|
||||
bot.rmsFactory.ClearCacheForUser(userDB.ID)
|
||||
c.Respond(&tele.CallbackResponse{Text: "✅ Сервер выбран активным"})
|
||||
return bot.renderServerMenu(c, targetID)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "srv_show_creds_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_show_creds_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
server, err := bot.accountRepo.GetServerByID(targetID)
|
||||
if err != nil {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"})
|
||||
}
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
|
||||
if role != account.RoleOwner && role != account.RoleAdmin {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: недостаточно прав"})
|
||||
}
|
||||
// Получаем личные креды пользователя через GetServerUsers
|
||||
serverUsers, err := bot.accountRepo.GetServerUsers(server.ID)
|
||||
if err != nil {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка получения данных"})
|
||||
}
|
||||
var login string
|
||||
for _, su := range serverUsers {
|
||||
if su.UserID == userDB.ID {
|
||||
login = su.Login
|
||||
break
|
||||
}
|
||||
}
|
||||
if login == "" {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "У вас нет сохраненных учетных данных"})
|
||||
}
|
||||
c.Respond()
|
||||
return c.Send(fmt.Sprintf("🔑 <b>Учетные данные сервера</b>\n\n🏢 <b>Название:</b> %s\n🔗 <b>URL:</b> %s\n👤 <b>Логин:</b> %s\n🔒 <b>Пароль:</b> ***скрыт***",
|
||||
server.Name, server.BaseURL, login), tele.ModeHTML)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "srv_update_creds_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_update_creds_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
server, err := bot.accountRepo.GetServerByID(targetID)
|
||||
if err != nil {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: сервер не найден"})
|
||||
}
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
|
||||
if role != account.RoleOwner {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка: только владелец может обновлять учетные данные"})
|
||||
}
|
||||
// Сохраняем ID сервера в контексте FSM
|
||||
bot.fsm.UpdateContext(c.Sender().ID, func(ctx *UserContext) {
|
||||
ctx.EditingServerID = server.ID
|
||||
ctx.TempURL = server.BaseURL
|
||||
})
|
||||
bot.fsm.SetState(c.Sender().ID, StateUpdateServerLogin)
|
||||
c.Respond()
|
||||
return c.EditOrSend("✏️ <b>Обновление учетных данных</b>\n\nВведите новый <b>логин</b> для сервера <b>"+server.Name+"</b>.\n\n(Напишите 'отмена' для выхода)", tele.ModeHTML)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "srv_delete_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_delete_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
role, err := bot.accountRepo.GetUserRole(userDB.ID, targetID)
|
||||
if err != nil {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка прав доступа"})
|
||||
}
|
||||
if role != account.RoleOwner {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Только владелец может удалить сервер"})
|
||||
}
|
||||
// Подтверждение удаления
|
||||
menu := &tele.ReplyMarkup{}
|
||||
btnYes := menu.Data("✅ Да, удалить", "srv_delete_confirm_"+targetID.String())
|
||||
btnNo := menu.Data("❌ Отмена", "srv_menu_"+targetID.String())
|
||||
menu.Inline(menu.Row(btnYes), menu.Row(btnNo))
|
||||
server, _ := bot.accountRepo.GetServerByID(targetID)
|
||||
return c.EditOrSend("⚠️ <b>Подтверждение удаления</b>\n\nВы уверены, что хотите удалить сервер <b>"+server.Name+"</b>?\n\nЭто действие необратимо!", menu, tele.ModeHTML)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "srv_delete_confirm_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "srv_delete_confirm_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
if idx := strings.Index(serverIDStr, "|"); idx != -1 {
|
||||
serverIDStr = serverIDStr[:idx]
|
||||
}
|
||||
targetID := parseUUID(serverIDStr)
|
||||
if err := bot.accountRepo.DeleteServer(targetID); err != nil {
|
||||
return c.Respond(&tele.CallbackResponse{Text: "Ошибка удаления"})
|
||||
}
|
||||
bot.rmsFactory.ClearCacheForUser(userDB.ID)
|
||||
c.Respond(&tele.CallbackResponse{Text: "Сервер удален"})
|
||||
return bot.renderServersMenu(c)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(data, "set_server_") {
|
||||
serverIDStr := strings.TrimPrefix(data, "set_server_")
|
||||
serverIDStr = strings.TrimSpace(serverIDStr)
|
||||
@@ -799,6 +975,7 @@ func (bot *Bot) handleText(c tele.Context) error {
|
||||
userID := c.Sender().ID
|
||||
state := bot.fsm.GetState(userID)
|
||||
text := strings.TrimSpace(c.Text())
|
||||
userDB, _ := bot.accountRepo.GetUserByTelegramID(userID)
|
||||
|
||||
if bot.maintenanceMode && !bot.isDev(userID) {
|
||||
return c.Send("Сервис на обслуживании", tele.ModeHTML)
|
||||
@@ -888,6 +1065,52 @@ func (bot *Bot) handleText(c tele.Context) error {
|
||||
ctx.BillingTargetURL = text
|
||||
})
|
||||
return bot.renderTariffShowcase(c, text)
|
||||
|
||||
case StateUpdateServerLogin:
|
||||
ctx := bot.fsm.GetContext(userID)
|
||||
if ctx.EditingServerID == uuid.Nil {
|
||||
bot.fsm.Reset(userID)
|
||||
return bot.renderMainMenu(c)
|
||||
}
|
||||
bot.fsm.UpdateContext(userID, func(uCtx *UserContext) {
|
||||
uCtx.TempLogin = text
|
||||
uCtx.State = StateUpdateServerPassword
|
||||
})
|
||||
return c.Send("🔑 Введите новый <b>пароль</b>:")
|
||||
|
||||
case StateUpdateServerPassword:
|
||||
password := text
|
||||
ctx := bot.fsm.GetContext(userID)
|
||||
if ctx.EditingServerID == uuid.Nil {
|
||||
bot.fsm.Reset(userID)
|
||||
return bot.renderMainMenu(c)
|
||||
}
|
||||
server, err := bot.accountRepo.GetServerByID(ctx.EditingServerID)
|
||||
if err != nil {
|
||||
bot.fsm.Reset(userID)
|
||||
return c.Send("❌ Ошибка: сервер не найден")
|
||||
}
|
||||
// Проверяем новые креды
|
||||
msg, _ := bot.b.Send(c.Sender(), "⏳ Проверяю подключение...")
|
||||
tempClient := bot.rmsFactory.CreateClientFromRawCredentials(ctx.TempURL, ctx.TempLogin, password)
|
||||
if err := tempClient.Auth(); err != nil {
|
||||
bot.b.Delete(msg)
|
||||
bot.fsm.Reset(userID)
|
||||
return c.Send(fmt.Sprintf("❌ Ошибка авторизации: %v\nПроверьте логин/пароль.", err))
|
||||
}
|
||||
// Шифруем пароль и сохраняем
|
||||
encPass, _ := bot.cryptoManager.Encrypt(password)
|
||||
// Обновляем креды через ConnectServer (он обновит существующую связь)
|
||||
_, err = bot.accountRepo.ConnectServer(userDB.ID, ctx.TempURL, ctx.TempLogin, encPass, server.Name)
|
||||
bot.b.Delete(msg)
|
||||
if err != nil {
|
||||
bot.fsm.Reset(userID)
|
||||
return c.Send("❌ Ошибка обновления данных")
|
||||
}
|
||||
bot.fsm.Reset(userID)
|
||||
bot.rmsFactory.ClearCacheForUser(userDB.ID)
|
||||
c.Send("✅ <b>Учетные данные обновлены!</b>\n\nТеперь вы можете использовать новые логин и пароль для подключения к серверу.", tele.ModeHTML)
|
||||
return bot.renderServerMenu(c, server.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -17,10 +17,12 @@ const (
|
||||
StateAddServerConfirmName
|
||||
StateAddServerInputName
|
||||
StateBillingGiftURL
|
||||
StateUpdateServerLogin // Обновление логина для существующего сервера
|
||||
StateUpdateServerPassword // Обновление пароля для существующего сервера
|
||||
|
||||
// Состояния редактора черновиков (начиная с 100)
|
||||
StateDraftEditItemName State = 100 // Ожидание ввода нового названия позиции
|
||||
StateDraftEditItemQty State = 101 // Ожидание ввода количества
|
||||
StateDraftEditItemName State = 100 // Ожидание ввода нового названия позиции
|
||||
StateDraftEditItemQty State = 101 // Ожидание ввода количества
|
||||
StateDraftEditItemPrice State = 102 // Ожидание ввода цены
|
||||
)
|
||||
|
||||
@@ -34,6 +36,9 @@ type UserContext struct {
|
||||
TempPassword string
|
||||
TempServerName string
|
||||
|
||||
// Поля для обновления сервера
|
||||
EditingServerID uuid.UUID // ID редактируемого сервера
|
||||
|
||||
// Поля для биллинга
|
||||
BillingTargetURL string
|
||||
|
||||
|
||||
Reference in New Issue
Block a user