Spaces:
Paused
Paused
| const { Scenes, Markup } = require('telegraf'); | |
| const Product = require('../../models/Product'); | |
| const ExcelJS = require('exceljs'); | |
| const axios = require('axios'); | |
| const inventoryScene = new Scenes.WizardScene( | |
| 'admin_inventory', | |
| // Step 1: Menu | |
| async (ctx) => { | |
| // Inject i18n | |
| if (!ctx.i18n) { | |
| const User = require('../../models/User'); | |
| const locales = require('../../locales'); | |
| const user = await User.findOne({ id: ctx.from.id }); | |
| const lang = (user && user.language) ? user.language : 'uz'; | |
| ctx.i18n = locales[lang] || locales.uz; | |
| } | |
| const i18n = ctx.i18n; | |
| const text = i18n.admin.inv_title; | |
| const buttons = [ | |
| [Markup.button.callback(i18n.admin.inv_mass_price, "mass_price_update")], | |
| [Markup.button.callback(i18n.admin.inv_excel, "excel_import")], | |
| [Markup.button.callback(i18n.admin.back, "back_dashboard")] | |
| ]; | |
| if (ctx.callbackQuery) { | |
| await ctx.editMessageText(text, { parse_mode: 'Markdown', ...Markup.inlineKeyboard(buttons) }); | |
| } else { | |
| await ctx.replyWithMarkdown(text, Markup.inlineKeyboard(buttons)); | |
| } | |
| return ctx.wizard.next(); | |
| }, | |
| // Step 2: Handle Choice | |
| async (ctx) => { | |
| // Ensure i18n | |
| if (!ctx.i18n) { | |
| const locales = require('../../locales'); | |
| ctx.i18n = locales.uz; // Fallback | |
| } | |
| const i18n = ctx.i18n; | |
| if (!ctx.callbackQuery && !ctx.message) return; | |
| const txt = ctx.message ? ctx.message.text : ''; | |
| if (txt === '/start' || txt === i18n.admin.btn_reject || txt === '❌ Bekor qilish') return ctx.scene.leave(); | |
| if (ctx.callbackQuery) { | |
| const data = ctx.callbackQuery.data; | |
| if (data === 'back_dashboard') { | |
| await ctx.scene.leave(); | |
| const adminController = require('../../controllers/adminController'); | |
| // Re-fetch user logic inside controller usually handles i18n, but here we call it directly. | |
| // adminController uses ctx.i18n, so it's fine. | |
| return adminController.showDashboard(ctx); | |
| } | |
| if (data === 'mass_price_update') { | |
| await ctx.reply(i18n.admin.inv_price_prompt, { parse_mode: 'Markdown', ...Markup.keyboard([[i18n.admin.btn_reject]]).resize() }); | |
| ctx.wizard.state.action = 'mass_price'; | |
| return ctx.wizard.next(); | |
| } | |
| if (data === 'excel_import') { | |
| await ctx.reply(i18n.admin.inv_excel_prompt, Markup.keyboard([[i18n.admin.btn_reject]]).resize()); | |
| ctx.wizard.state.action = 'excel_import'; | |
| return ctx.wizard.next(); | |
| } | |
| } | |
| }, | |
| // Step 3: Execution | |
| async (ctx) => { | |
| // Ensure i18n | |
| if (!ctx.i18n) { | |
| const locales = require('../../locales'); | |
| ctx.i18n = locales.uz; | |
| } | |
| const i18n = ctx.i18n; | |
| const txt = ctx.message ? ctx.message.text : ''; | |
| if (txt === i18n.admin.btn_reject || txt === '❌ Bekor qilish') { | |
| await ctx.reply(i18n.admin.back || "Bekor qilindi.", Markup.removeKeyboard()); | |
| await ctx.scene.leave(); | |
| const adminController = require('../../controllers/adminController'); | |
| return adminController.showDashboard(ctx); | |
| } | |
| // --- Mass Price Update Logic --- | |
| if (ctx.wizard.state.action === 'mass_price') { | |
| const percent = parseInt(txt); | |
| if (isNaN(percent) || percent === 0) { | |
| await ctx.reply(i18n.admin.error_num || "Error"); | |
| return; | |
| } | |
| await ctx.reply(i18n.admin.broadcast_sending || "⏳..."); | |
| const products = await Product.find({}); | |
| let count = 0; | |
| for (let p of products) { | |
| try { | |
| if (!p.id || !p.name) continue; // Skip invalid | |
| const change = (p.price * percent) / 100; | |
| p.price = Math.round(p.price + change); | |
| await p.save(); | |
| count++; | |
| } catch (e) { | |
| console.error("Update error:", e.message); | |
| } | |
| } | |
| await ctx.reply(i18n.admin.inv_updated.replace('{count}', count), { parse_mode: 'Markdown', ...Markup.removeKeyboard() }); | |
| return ctx.scene.leave(); | |
| } | |
| // --- Excel Import Logic --- | |
| if (ctx.wizard.state.action === 'excel_import') { | |
| if (!ctx.message.document) { | |
| await ctx.reply(i18n.admin.error || "Error"); | |
| return; | |
| } | |
| const doc = ctx.message.document; | |
| if (!doc.file_name.endsWith('.xlsx')) { | |
| await ctx.reply("Only .xlsx"); | |
| return; | |
| } | |
| await ctx.reply(i18n.admin.broadcast_sending || "⏳..."); | |
| try { | |
| const fileLink = await ctx.telegram.getFileLink(doc.file_id); | |
| const response = await axios({ | |
| url: fileLink.href, | |
| method: 'GET', | |
| responseType: 'arraybuffer' | |
| }); | |
| const workbook = new ExcelJS.Workbook(); | |
| await workbook.xlsx.load(response.data); | |
| const worksheet = workbook.getWorksheet(1); // First sheet | |
| let imported = 0; | |
| let updated = 0; | |
| // Iterate rows (skip header) | |
| worksheet.eachRow(async (row, rowNumber) => { | |
| if (rowNumber === 1) return; // Header | |
| const id = row.getCell(1).value; | |
| const name = row.getCell(2).value; | |
| const price = row.getCell(3).value; | |
| const category = row.getCell(4).value; // logic: text or id? let's assume raw text/id | |
| const quantity = row.getCell(5).value; | |
| const description = row.getCell(6).value; | |
| if (id && name && price) { | |
| const existing = await Product.findOne({ id: id }); | |
| if (existing) { | |
| existing.name = name; | |
| existing.price = price; | |
| existing.category = category; | |
| existing.quantity = quantity || 0; | |
| existing.description = description || ""; | |
| await existing.save(); | |
| updated++; | |
| } else { | |
| const newProd = new Product({ | |
| id: id, | |
| name: name, | |
| price: price, | |
| category: category, | |
| quantity: quantity || 0, | |
| description: description || "" | |
| }); | |
| await newProd.save(); | |
| imported++; | |
| } | |
| } | |
| }); | |
| await ctx.reply(`${i18n.admin.inv_import_success}\n🆕: ~${imported}\n🔄: ~${updated}`, { parse_mode: 'Markdown', ...Markup.removeKeyboard() }); | |
| return ctx.scene.leave(); | |
| } catch (err) { | |
| console.error(err); | |
| await ctx.reply(i18n.admin.error); | |
| return ctx.scene.leave(); | |
| } | |
| } | |
| } | |
| ); | |
| module.exports = inventoryScene; | |