const { Markup } = require('telegraf'); const User = require('../models/User'); const Product = require('../models/Product'); const Order = require('../models/Order'); const ExcelJS = require('exceljs'); // Helper to get Dashboard Buttons const getDashboardButtons = () => { return [ [Markup.button.callback("๐Ÿ“Š Statistika", "admin_stats"), Markup.button.callback("๐Ÿ“ฆ Yangi buyurtmalar", "admin_orders_new")], [Markup.button.callback("โž• Mahsulot qo'shish", "admin_add_product"), Markup.button.callback("๐Ÿ—‘ Mahsulot o'chirish", "admin_delete_product")], [Markup.button.callback("โœ๏ธ Mahsulotni tahrirlash", "admin_edit_product_list"), Markup.button.callback("๐Ÿ‘ฅ Foydalanuvchilar", "admin_users")], [Markup.button.callback("๐Ÿ“‰ Excel Export", "admin_excel_export"), Markup.button.callback("๐Ÿ“ข Reklama yuborish", "admin_broadcast")], [Markup.button.callback("๐Ÿ”ฅ Aksiya (Flash Sale)", "admin_flash_sale"), Markup.button.callback("๐Ÿ“ฆ Ombor Boshqaruvi", "admin_inventory")], [Markup.button.callback("โš™๏ธ Do'kon Sozlamalari", "admin_settings"), Markup.button.callback("๐Ÿ”‘ API Integratsiya", "admin_api")] // Added API Button ]; }; // Admin Dashboard Menu exports.showDashboard = (ctx) => { // Middleware ensures ctx.i18n is present const i18n = ctx.i18n; const text = i18n.admin.dash_title; // Dynamic Buttons const buttons = [ [Markup.button.callback(i18n.admin.btn_stats, "admin_stats"), Markup.button.callback(i18n.admin.btn_new_orders, "admin_orders_new")], [Markup.button.callback(i18n.admin.btn_add_prod, "admin_add_product"), Markup.button.callback(i18n.admin.btn_del_prod, "admin_delete_product")], [Markup.button.callback(i18n.admin.btn_edit_prod, "admin_edit_product_list"), Markup.button.callback(i18n.admin.btn_users, "admin_users")], [Markup.button.callback(i18n.admin.btn_excel, "admin_excel_export"), Markup.button.callback(i18n.admin.btn_broadcast, "admin_broadcast")], [Markup.button.callback(i18n.admin.btn_flash, "admin_flash_sale"), Markup.button.callback(i18n.admin.btn_inv, "admin_inventory")], [Markup.button.callback(i18n.admin.btn_settings, "admin_settings"), Markup.button.callback(i18n.admin.btn_api, "admin_api")] ]; const keyboard = Markup.inlineKeyboard(buttons); try { if (ctx.callbackQuery) { ctx.editMessageText(text, { parse_mode: 'Markdown', ...keyboard }).catch(e => ctx.replyWithMarkdown(text, keyboard)); } else { ctx.replyWithMarkdown(text, keyboard); } } catch (e) { ctx.replyWithMarkdown(text, keyboard); } }; // Statistics exports.showStats = async (ctx) => { try { const i18n = ctx.i18n; const userCount = await User.countDocuments(); const productCount = await Product.countDocuments(); const orders = await Order.find(); const revenue = orders .filter(o => o.status === 'completed' || o.status === 'delivered') .reduce((sum, o) => sum + o.total, 0); const text = `${i18n.admin.stats_title}\n\n` + `${i18n.admin.stats_users}: ${userCount}\n` + `${i18n.admin.stats_prods}: ${productCount}\n` + `${i18n.admin.stats_orders}: ${orders.length}\n` + `${i18n.admin.stats_rev}: ${revenue.toLocaleString()} ${i18n.currency}`; const keyboard = Markup.inlineKeyboard([[Markup.button.callback(i18n.admin.back, "admin_dashboard")]]); ctx.editMessageText(text, { parse_mode: 'HTML', ...keyboard }) .catch(e => ctx.replyWithHTML(text, keyboard)); } catch (err) { console.error(err); ctx.answerCbQuery(ctx.i18n.admin.error); } }; // Show New Orders exports.showNewOrders = async (ctx) => { try { const i18n = ctx.i18n; const orders = await Order.find({ status: 'new' }); const backBtn = [Markup.button.callback(i18n.admin.back, "admin_dashboard")]; if (!orders || orders.length === 0) { return ctx.editMessageText(i18n.admin.no_new_orders, Markup.inlineKeyboard([backBtn])) .catch(e => ctx.reply(i18n.admin.no_new_orders, Markup.inlineKeyboard([backBtn]))); } const orderButtons = orders.map(o => [Markup.button.callback(`#${o.id} - ${o.total.toLocaleString()} ${i18n.currency}`, `admin_order_${o.id}`)]); orderButtons.push(backBtn); const text = `${i18n.admin.btn_new_orders}: ${orders.length}`; const keyboard = Markup.inlineKeyboard(orderButtons); ctx.editMessageText(text, { parse_mode: 'Markdown', ...keyboard }) .catch(e => ctx.replyWithMarkdown(text, keyboard)); } catch (err) { console.error(err); ctx.answerCbQuery(ctx.i18n.admin.error); } }; // View Single Order Details exports.viewOrder = async (ctx, orderId) => { try { const i18n = ctx.i18n; const id = parseInt(orderId); if (isNaN(id)) return ctx.answerCbQuery("โŒ Invalid Order ID"); const order = await Order.findOne({ id: id }); if (!order) return ctx.answerCbQuery("Buyurtma topilmadi."); // Low Priority to localize this tiny string fallback let text = `${i18n.admin.order_title}${order.id}**\n` + `${i18n.admin.client}: ${order.user}\n` + `${i18n.admin.tel}: ${order.phone}\n` + `${i18n.admin.date}: ${new Date(order.createdAt).toLocaleString()}\n` + `${i18n.admin.status_label}: ${order.status.toUpperCase()}\n\n` + `${i18n.admin.items}\n`; order.items.forEach(i => { text += `- ${i.name} (${i.count}x) - ${i.price.toLocaleString()} ${i18n.currency}\n`; }); text += `\n${i18n.admin.total_label} ${order.total.toLocaleString()} ${i18n.currency}`; if (order.location) { ctx.replyWithLocation(order.location.latitude, order.location.longitude); } // Dynamic Buttons based on Status let actionButtons = []; if (order.status === 'new') { actionButtons = [ [Markup.button.callback(i18n.admin.btn_accept, `order_accept_${order.id}`), Markup.button.callback(i18n.admin.btn_reject, `order_reject_${order.id}`)] ]; } else if (order.status === 'accepted') { actionButtons = [ [Markup.button.callback(i18n.admin.btn_invoice, `gen_invoice_${order.id}`)], [Markup.button.callback(i18n.admin.btn_ship, `order_ship_${order.id}`)], [Markup.button.callback(i18n.admin.btn_reject, `order_reject_${order.id}`)] ]; } else if (order.status === 'shipping') { actionButtons = [ [Markup.button.callback(i18n.admin.btn_deliver, `order_deliver_${order.id}`)], [Markup.button.callback(i18n.admin.btn_reject, `order_reject_${order.id}`)] ]; } else if (order.status === 'delivered') { actionButtons = [ [Markup.button.callback(i18n.admin.btn_archive, "admin_orders_new")] ]; } actionButtons.push([Markup.button.callback(i18n.admin.back, "admin_orders_new")]); const contextKeyboard = Markup.inlineKeyboard(actionButtons); ctx.editMessageText(text, { parse_mode: 'HTML', ...contextKeyboard }) .catch(e => ctx.replyWithHTML(text, contextKeyboard)); } catch (err) { console.error(err); } }; // Helper: Generate Invoice Text const generateInvoice = (order) => { const date = new Date(order.createdAt).toLocaleString('uz-UZ'); let items = ""; order.items.forEach(i => { items += `- ${i.name} (${i.count}x) : ${i.price.toLocaleString()} so'm\n`; }); return `๐Ÿงพ **CHECK**\n\n` + `๐Ÿ†” Buyurtma: #${order.id}\n` + `๐Ÿ“… Sana: ${date}\n` + `๐Ÿ‘ค Mijoz: ${order.user} (${order.phone})\n` + `๐Ÿ’ณ To'lov: ${order.paymentMethod === 'click' ? 'Click/Karta' : 'Naqd'}\n` + `------------------------------\n` + `${items}` + `------------------------------\n` + `๐Ÿ’ฐ **JAMI: ${order.total.toLocaleString()} so'm**\n\n` + `โœ… Status: TO'LANDI`; }; // Accept Order exports.acceptOrder = async (ctx, orderId) => { try { const id = parseInt(orderId); await Order.updateOne({ id: id }, { status: 'accepted' }); const order = await Order.findOne({ id: id }); ctx.answerCbQuery(ctx.i18n.admin.alert_accepted); // Notify User if (order) { const User = require('../models/User'); const locales = require('../locales'); const user = await User.findOne({ id: order.userId }); const lang = (user && user.language) ? user.language : 'uz'; const i18n = locales[lang] || locales.uz; ctx.telegram.sendMessage(order.userId, `${i18n.status_accepted}\n#${id} ${i18n.status_proccess || "Accepted"}.`); } // Refresh View exports.viewOrder(ctx, id); } catch (err) { console.error(err); } }; // Ship Order exports.shipOrder = async (ctx, orderId) => { try { const id = parseInt(orderId); await Order.updateOne({ id: id }, { status: 'shipping' }); const order = await Order.findOne({ id: id }); ctx.answerCbQuery(ctx.i18n.admin.alert_shipping); // Notify User if (order) { const User = require('../models/User'); const locales = require('../locales'); const user = await User.findOne({ id: order.userId }); const lang = (user && user.language) ? user.language : 'uz'; const i18n = locales[lang] || locales.uz; ctx.telegram.sendMessage(order.userId, `${i18n.status_shipping}\n#${id}`); } exports.viewOrder(ctx, id); } catch (e) { console.error(e); } }; // Deliver Order exports.deliverOrder = async (ctx, orderId) => { try { const id = parseInt(orderId); await Order.updateOne({ id: id }, { status: 'delivered' }); const order = await Order.findOne({ id: id }); ctx.answerCbQuery(ctx.i18n.admin.alert_delivered); // Notify User if (order) { const User = require('../models/User'); const locales = require('../locales'); const user = await User.findOne({ id: order.userId }); const lang = (user && user.language) ? user.language : 'uz'; const i18n = locales[lang] || locales.uz; ctx.telegram.sendMessage(order.userId, `${i18n.status_delivered}\n#${id}`); } exports.viewOrder(ctx, id); } catch (e) { console.error(e); } }; // Generate Invoice Action Handler exports.showInvoice = async (ctx, orderId) => { try { const id = parseInt(orderId); const order = await Order.findOne({ id: id }); if (!order) return ctx.answerCbQuery("Buyurtma yo'q"); const invoice = generateInvoice(order); await ctx.replyWithMarkdown(invoice); ctx.answerCbQuery("Chek tayyor!"); } catch (e) { console.error(e); } }; exports.toggleBlockUser = async (ctx, userId, isBlock) => { try { await User.updateOne({ id: userId }, { isBlocked: isBlock }); const msg = isBlock ? "Foydalanuvchi bloklandi ๐Ÿšซ" : "Foydalanuvchi blokdan chiqarildi โœ…"; ctx.answerCbQuery(msg); exports.manageUser(ctx, userId); // Refresh view } catch (e) { console.error(e); } }; // --- API Key Management --- const crypto = require('crypto'); const Settings = require('../models/Settings'); exports.showApiMenu = async (ctx) => { try { const apiKeySetting = await Settings.findOne({ key: 'api_secret_key' }); const apiKey = apiKeySetting ? apiKeySetting.value : "โš ๏ธ Mavjud emas"; let text = `๐Ÿ”‘ **API Integratsiya Sozlamalari**\n\n` + `Sizning API Kalitingiz (Secret Key):\n` + `${apiKey}\n\n` + `โš ๏ธ **Eslatma:** Bu kalit orqali kelajakdagi APK mobil ilovani bot bazasiga ulashingiz mumkin. Kalitni birovga bermang!`; const keyboard = Markup.inlineKeyboard([ [Markup.button.callback("๐Ÿ”„ Yangi Kalit Yaratish", "admin_api_generate")], [Markup.button.callback("๐Ÿ”™ Orqaga", "admin_dashboard")] ]); ctx.editMessageText(text, { parse_mode: 'HTML', ...keyboard }) .catch(e => ctx.replyWithHTML(text, keyboard)); } catch (e) { console.error(e); ctx.reply("Xatolik"); } }; exports.generateApiKey = async (ctx) => { try { // Generate new key const newKey = "sk_live_" + crypto.randomBytes(16).toString('hex'); await Settings.findOneAndUpdate( { key: 'api_secret_key' }, { value: newKey }, { upsert: true, new: true } ); ctx.answerCbQuery("Yangi kalit yaratildi โœ…"); exports.showApiMenu(ctx); } catch (e) { console.error(e); ctx.answerCbQuery("Xatolik"); } }; // --- Mass Price Update --- exports.startMassPriceUpdate = (ctx) => { ctx.reply("๐Ÿ“ˆ **Narxlarni ommaviy o'zgartirish**\n\nBarcha mahsulotlar narxini foiz (%) hisobida oshirish yoki kamaytirish mumkin.\n\nMasalan:\n+10 => Narxlar 10% ga oshadi.\n-5 => Narxlar 5% ga kamayadi.\n\nIltimos, foizni yozing:", Markup.keyboard([['โŒ Bekor qilish']]).resize()); ctx.session.isMassUpdating = true; }; exports.handleMassUpdate = async (ctx) => { if (!ctx.session.isMassUpdating) return false; const text = ctx.message.text; if (text === 'โŒ Bekor qilish') { ctx.session.isMassUpdating = false; exports.showDashboard(ctx); // Return to dashboard return true; } const percent = parseFloat(text); if (isNaN(percent)) { ctx.reply("โš ๏ธ Iltimos, raqam yozing (masalan: 10 yoki -5)."); return true; } try { const products = await Product.find(); let changed = 0; for (const p of products) { const oldPrice = p.price; const newPrice = Math.round(oldPrice * (1 + percent / 100)); // Simple formula await Product.updateOne({ id: p.id }, { price: newPrice }); changed++; } ctx.reply(`โœ… **Muvaffaqiyatli!**\n\n${changed} ta mahsulot narxi ${percent}% ga o'zgartirildi.`); ctx.session.isMassUpdating = false; exports.showDashboard(ctx); } catch (e) { console.error(e); ctx.reply("Xatolik yuz berdi."); } return true; }; // Reject Order (Refund Logic) exports.rejectOrder = async (ctx, orderId) => { try { const id = parseInt(orderId); const order = await Order.findOne({ id: id }); if (!order) return ctx.answerCbQuery("Buyurtma topilmadi"); // Restore Stock Logic (Automatic Refund) if (order.status !== 'canceled') { const Product = require('../models/Product'); for (const item of order.items) { await Product.updateOne({ id: item.id }, { $inc: { quantity: item.count } }); } } await Order.updateOne({ id: id }, { status: 'canceled' }); ctx.answerCbQuery("Buyurtma bekor qilindi โŒ"); // Notify User if (order) { const User = require('../models/User'); const locales = require('../locales'); const user = await User.findOne({ id: order.userId }); const lang = (user && user.language) ? user.language : 'uz'; const i18n = locales[lang] || locales.uz; ctx.telegram.sendMessage(order.userId, `${i18n.status_canceled}\nBuyurtma #${id} bekor qilindi.\n(Sabab bo'lsa admin bilan bog'laning)`); } exports.showNewOrders(ctx); } catch (err) { console.error(err); } }; // Edit Product List exports.showEditProductList = async (ctx) => { try { const i18n = ctx.i18n; const products = await Product.find(); const backBtn = [Markup.button.callback(i18n.admin.back, "admin_dashboard")]; if (!products || products.length === 0) { return ctx.editMessageText(i18n.admin.no_edit_prod, Markup.inlineKeyboard([backBtn])) .catch(e => ctx.reply(i18n.admin.no_edit_prod, Markup.inlineKeyboard([backBtn]))); } const buttons = products.map(p => [Markup.button.callback(`โœ๏ธ ${p.name || 'Nomsiz'}`, `edit_prod_${p.id}`)]); buttons.push(backBtn); ctx.editMessageText(i18n.admin.select_edit, Markup.inlineKeyboard(buttons)) .catch(e => ctx.reply(i18n.admin.select_edit, Markup.inlineKeyboard(buttons))); } catch (err) { console.error(err); } }; // Delete Product List exports.showDeleteProductList = async (ctx) => { try { const i18n = ctx.i18n; const products = await Product.find(); const backBtn = [Markup.button.callback(i18n.admin.back, "admin_dashboard")]; if (!products || products.length === 0) { return ctx.editMessageText(i18n.admin.no_del_prod, Markup.inlineKeyboard([backBtn])) .catch(e => ctx.reply(i18n.admin.no_del_prod, Markup.inlineKeyboard([backBtn]))); } const buttons = products.map(p => [Markup.button.callback(`๐Ÿ—‘ ${p.name}`, `delete_prod_${p.id}`)]); buttons.push(backBtn); ctx.editMessageText(i18n.admin.select_del, Markup.inlineKeyboard(buttons)) .catch(e => ctx.reply(i18n.admin.select_del, Markup.inlineKeyboard(buttons))); } catch (err) { console.error(err); } }; exports.deleteProduct = async (ctx, prodId) => { try { const i18n = ctx.i18n; const id = parseInt(prodId); const result = await Product.findOneAndDelete({ id: id }); if (result) { ctx.answerCbQuery(i18n.admin.del_success, { show_alert: true }); } else { ctx.answerCbQuery(i18n.admin.del_fail, { show_alert: true }); } // Refresh list exports.showDeleteProductList(ctx); } catch (err) { console.error("Delete Error:", err); ctx.answerCbQuery(ctx.i18n.admin.error); } }; // Excel Export exports.exportOrders = async (ctx) => { try { ctx.reply("๐Ÿ“‰ Fayl tayyorlanmoqda..."); const orders = await Order.find().sort({ createdAt: -1 }); const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Buyurtmalar'); worksheet.columns = [ { header: 'ID', key: 'id', width: 15 }, { header: 'Mijoz', key: 'user', width: 20 }, { header: 'Telefon', key: 'phone', width: 15 }, { header: 'Manzil', key: 'address', width: 30 }, { header: 'Yetkazib berish', key: 'delivery', width: 15 }, { header: 'To\'lov', key: 'payment', width: 10 }, { header: 'Jami (so\'m)', key: 'total', width: 15 }, { header: 'Status', key: 'status', width: 10 }, { header: 'Vaqt', key: 'date', width: 20 }, { header: 'Mahsulotlar', key: 'items', width: 50 }, { header: 'Izoh', key: 'comment', width: 30 } ]; orders.forEach(order => { const itemsStr = order.items.map(i => `${i.name} (${i.count}x)`).join(', '); const locStr = order.location ? `${order.location.latitude}, ${order.location.longitude}` : 'Olib ketish'; worksheet.addRow({ id: order.id, user: order.user, phone: order.phone, address: locStr, delivery: order.deliveryMethod, payment: order.paymentMethod, total: order.total, status: order.status, date: new Date(order.createdAt).toLocaleString(), items: itemsStr, comment: order.comment || '' }); }); const buffer = await workbook.xlsx.writeBuffer(); await ctx.replyWithDocument({ source: buffer, filename: `Buyurtmalar_${new Date().toLocaleDateString()}.xlsx` }); } catch (err) { console.error("Excel Export Error:", err); ctx.reply("Fayl yaratishda xato bo'ldi."); } };