mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
добавил приветственный бонус и README
This commit is contained in:
@@ -72,27 +72,29 @@ func (r *pgRepository) GetUserByID(id uuid.UUID) (*account.User, error) {
|
||||
|
||||
// ConnectServer - Основная точка входа для добавления сервера
|
||||
func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedPass, name string) (*account.RMSServer, error) {
|
||||
// 1. Нормализация URL (удаляем слеш в конце, приводим к нижнему регистру)
|
||||
// Важно: мы не удаляем http/https, так как iiko может работать и так и так, но обычно это разные эндпоинты.
|
||||
// Для надежности уникальности можно вырезать протокол, но пока оставим как есть, просто тримминг.
|
||||
cleanURL := strings.TrimRight(strings.ToLower(strings.TrimSpace(rawURL)), "/")
|
||||
|
||||
var server account.RMSServer
|
||||
var created bool
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 2. Ищем, существует ли сервер с таким URL
|
||||
err := tx.Where("base_url = ?", cleanURL).First(&server).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// --- СЦЕНАРИЙ 1: НОВЫЙ СЕРВЕР ---
|
||||
// --- СЦЕНАРИЙ 1: НОВЫЙ СЕРВЕР (Приветственный бонус) ---
|
||||
trialDays := 30
|
||||
welcomeBalance := 10
|
||||
paidUntil := time.Now().AddDate(0, 0, trialDays)
|
||||
|
||||
server = account.RMSServer{
|
||||
BaseURL: cleanURL,
|
||||
Name: name,
|
||||
MaxUsers: 5, // Дефолтное ограничение
|
||||
BaseURL: cleanURL,
|
||||
Name: name,
|
||||
MaxUsers: 5,
|
||||
Balance: welcomeBalance,
|
||||
PaidUntil: &paidUntil,
|
||||
}
|
||||
if err := tx.Create(&server).Error; err != nil {
|
||||
return err
|
||||
@@ -100,11 +102,9 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP
|
||||
created = true
|
||||
} else {
|
||||
// --- СЦЕНАРИЙ 2: СУЩЕСТВУЮЩИЙ СЕРВЕР ---
|
||||
// Проверяем лимит пользователей
|
||||
var userCount int64
|
||||
tx.Model(&account.ServerUser{}).Where("server_id = ?", server.ID).Count(&userCount)
|
||||
if userCount >= int64(server.MaxUsers) {
|
||||
// Проверяем, может пользователь УЖЕ там есть? Тогда это не добавление, а обновление кредов
|
||||
var exists int64
|
||||
tx.Model(&account.ServerUser{}).Where("server_id = ? AND user_id = ?", server.ID, userID).Count(&exists)
|
||||
if exists == 0 {
|
||||
@@ -113,18 +113,24 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Определяем роль
|
||||
targetRole := account.RoleOperator
|
||||
if created {
|
||||
targetRole = account.RoleOwner
|
||||
}
|
||||
|
||||
// 4. Создаем или обновляем связь с пользователем
|
||||
// Сбрасываем активность других серверов
|
||||
if err := tx.Model(&account.ServerUser{}).Where("user_id = ?", userID).Update("is_active", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var existingLink account.ServerUser
|
||||
err = tx.Where("server_id = ? AND user_id = ?", server.ID, userID).First(&existingLink).Error
|
||||
if err == nil {
|
||||
existingLink.Login = login
|
||||
existingLink.EncryptedPassword = encryptedPass
|
||||
existingLink.IsActive = true
|
||||
return tx.Save(&existingLink).Error
|
||||
}
|
||||
|
||||
userLink := account.ServerUser{
|
||||
ServerID: server.ID,
|
||||
UserID: userID,
|
||||
@@ -133,22 +139,6 @@ func (r *pgRepository) ConnectServer(userID uuid.UUID, rawURL, login, encryptedP
|
||||
Login: login,
|
||||
EncryptedPassword: encryptedPass,
|
||||
}
|
||||
|
||||
// Upsert для связи (на случай если пользователь подключает уже подключенный сервер, обновляем пароль)
|
||||
// Если пользователь уже был OWNER/ADMIN, роль НЕ понижаем.
|
||||
// Поэтому используем хитрый Upsert: обновляем роль только если запись новая.
|
||||
|
||||
var existingLink account.ServerUser
|
||||
err = tx.Where("server_id = ? AND user_id = ?", server.ID, userID).First(&existingLink).Error
|
||||
if err == nil {
|
||||
// Запись есть -> обновляем креды и делаем активным, роль НЕ меняем
|
||||
existingLink.Login = login
|
||||
existingLink.EncryptedPassword = encryptedPass
|
||||
existingLink.IsActive = true
|
||||
return tx.Save(&existingLink).Error
|
||||
}
|
||||
|
||||
// Записи нет -> создаем с вычисленной ролью
|
||||
return tx.Create(&userLink).Error
|
||||
})
|
||||
|
||||
|
||||
@@ -156,7 +156,43 @@ func (bot *Bot) handleStartCommand(c tele.Context) error {
|
||||
if payload != "" && strings.HasPrefix(payload, "invite_") {
|
||||
return bot.handleInviteLink(c, strings.TrimPrefix(payload, "invite_"))
|
||||
}
|
||||
return bot.renderMainMenu(c)
|
||||
|
||||
welcomeTxt := "🚀 <b>RMSer — ваш умный ассистент для iiko</b>\n\n" +
|
||||
"Больше не нужно вводить накладные вручную. Просто сфотографируйте чек, а я сделаю всё остальное.\n\n" +
|
||||
"<b>Почему это удобно:</b>\n" +
|
||||
"🧠 <b>Самообучение:</b> Сопоставьте товар один раз, и в следующий раз я узнаю его сам.\n" +
|
||||
"⚙️ <b>Гибкая настройка:</b> Укажите склад по умолчанию и ограничьте область поиска товаров только нужными категориями.\n" +
|
||||
"👥 <b>Работа в команде:</b> Приглашайте сотрудников, распределяйте роли и управляйте доступом прямо в Mini App.\n\n" +
|
||||
"🎁 <b>Старт без риска:</b> Дарим 10 накладных на 30 дней каждому новому серверу!"
|
||||
|
||||
return bot.renderMainMenuWithText(c, welcomeTxt)
|
||||
}
|
||||
|
||||
// Вспомогательный метод для рендера (чтобы не дублировать код меню)
|
||||
func (bot *Bot) renderMainMenuWithText(c tele.Context, text string) error {
|
||||
bot.fsm.Reset(c.Sender().ID)
|
||||
userDB, _ := bot.accountRepo.GetUserByTelegramID(c.Sender().ID)
|
||||
activeServer, _ := bot.accountRepo.GetActiveServer(userDB.ID)
|
||||
|
||||
menu := &tele.ReplyMarkup{}
|
||||
btnServers := menu.Data("🖥 Серверы", "nav_servers")
|
||||
btnDicts := menu.Data("🔄 Справочники", "nav_dicts")
|
||||
btnBalance := menu.Data("💰 Баланс", "nav_balance")
|
||||
|
||||
var rows []tele.Row
|
||||
rows = append(rows, menu.Row(btnServers, btnDicts))
|
||||
rows = append(rows, menu.Row(btnBalance))
|
||||
|
||||
if activeServer != nil {
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, activeServer.ID)
|
||||
if role == account.RoleOwner || role == account.RoleAdmin {
|
||||
btnApp := menu.WebApp("📱 Открыть приложение", &tele.WebApp{URL: bot.webAppURL})
|
||||
rows = append(rows, menu.Row(btnApp))
|
||||
}
|
||||
}
|
||||
|
||||
menu.Inline(rows...)
|
||||
return c.EditOrSend(text, menu, tele.ModeHTML)
|
||||
}
|
||||
|
||||
func (bot *Bot) handleInviteLink(c tele.Context, serverIDStr string) error {
|
||||
@@ -759,7 +795,7 @@ func (bot *Bot) handlePhoto(c tele.Context) error {
|
||||
if err != nil {
|
||||
return c.Send("Ошибка чтения файла.")
|
||||
}
|
||||
c.Send("⏳ Обрабатываю чек: создаю черновик и распознаю...")
|
||||
c.Send("⏳ <b>ИИ анализирует документ...</b>\nОбрабатываю позиции и ищу совпадения в вашей базе iiko.", tele.ModeHTML)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
||||
defer cancel()
|
||||
draft, err := bot.ocrService.ProcessReceiptImage(ctx, userDB.ID, imgData)
|
||||
@@ -777,14 +813,14 @@ func (bot *Bot) handlePhoto(c tele.Context) error {
|
||||
fullURL := fmt.Sprintf("%s/invoice/%s", baseURL, draft.ID.String())
|
||||
var msgText string
|
||||
if matchedCount == len(draft.Items) {
|
||||
msgText = fmt.Sprintf("✅ <b>Успех!</b> Все позиции (%d) распознаны.\n\nПереходите к созданию накладной.", len(draft.Items))
|
||||
msgText = fmt.Sprintf("✅ <b>Все позиции распознаны!</b>\n\nВсе товары найдены в вашей номенклатуре. Проверьте количество (%d) и цену в приложении перед отправкой.", len(draft.Items))
|
||||
} else {
|
||||
msgText = fmt.Sprintf("⚠️ <b>Внимание!</b> Распознано %d из %d позиций.\n\nНекоторые товары требуют ручного сопоставления.", matchedCount, len(draft.Items))
|
||||
msgText = fmt.Sprintf("⚠️ <b>Распознано позиций: %d из %d</b>\n\nОстальные товары я вижу впервые. Воспользуйтесь <b>удобным интерфейсом сопоставления</b> в приложении — я запомню ваш выбор навсегда.", matchedCount, len(draft.Items))
|
||||
}
|
||||
menu := &tele.ReplyMarkup{}
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, draft.RMSServerID)
|
||||
if role != account.RoleOperator {
|
||||
btnOpen := menu.WebApp("📝 Открыть накладную", &tele.WebApp{URL: fullURL})
|
||||
btnOpen := menu.WebApp("📝 Открыть и сопоставить", &tele.WebApp{URL: fullURL})
|
||||
menu.Inline(menu.Row(btnOpen))
|
||||
} else {
|
||||
msgText += "\n\n<i>(Редактирование доступно Администратору)</i>"
|
||||
@@ -807,18 +843,32 @@ func (bot *Bot) handleConfirmNameNo(c tele.Context) error {
|
||||
return c.EditOrSend("🏷 Хорошо, введите желаемое <b>название</b>:")
|
||||
}
|
||||
|
||||
// Обновленный метод saveServerFinal (добавление уведомления о бонусе)
|
||||
func (bot *Bot) saveServerFinal(c tele.Context, userID int64, serverName string) error {
|
||||
ctx := bot.fsm.GetContext(userID)
|
||||
userDB, _ := bot.accountRepo.GetOrCreateUser(userID, c.Sender().Username, "", "")
|
||||
encPass, _ := bot.cryptoManager.Encrypt(ctx.TempPassword)
|
||||
|
||||
server, err := bot.accountRepo.ConnectServer(userDB.ID, ctx.TempURL, ctx.TempLogin, encPass, serverName)
|
||||
if err != nil {
|
||||
return c.Send("Ошибка подключения сервера: " + err.Error())
|
||||
return c.Send("❌ Ошибка подключения сервера: " + err.Error())
|
||||
}
|
||||
bot.fsm.Reset(userID)
|
||||
|
||||
role, _ := bot.accountRepo.GetUserRole(userDB.ID, server.ID)
|
||||
c.Send(fmt.Sprintf("✅ Сервер <b>%s</b> подключен!\nВаша роль: <b>%s</b>", server.Name, role), tele.ModeHTML)
|
||||
|
||||
successMsg := fmt.Sprintf("✅ Сервер <b>%s</b> успешно подключен!\nВаша роль: <b>%s</b>\n\n", server.Name, role)
|
||||
|
||||
// Проверяем, новый ли это сервер по балансу и дате создания (упрощенно для уведомления)
|
||||
if server.Balance == 10 {
|
||||
successMsg += "🎁 Вам начислен <b>приветственный бонус: 10 накладных</b> на 30 дней! Пользуйтесь с удовольствием.\n\n"
|
||||
}
|
||||
|
||||
successMsg += "Начинаю первичную синхронизацию данных..."
|
||||
|
||||
c.Send(successMsg, tele.ModeHTML)
|
||||
go bot.syncService.SyncAllData(userDB.ID)
|
||||
|
||||
return bot.renderMainMenu(c)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user