from aiogram import F, Router from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message from sqlalchemy.ext.asyncio import AsyncSession from dto.catalogue import CategoryActionContext, ProductActionContext from misc.filters import IsVerified from misc.kb.client import created_category, get_back_to_catalogue from misc.kb.common import return_menu from misc.mapper import parse_cat_id from misc.states import AdminControlStorage from repositories.categories import CategoriesRepository from repositories.products import ProductRepository from services.catalogue import CatalogueService router = Router() def skip_product_description_kb(parent_id: str) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="Пропустить", callback_data="new_product:skip_description")], [InlineKeyboardButton(text="⬅️ Назад", callback_data=f"cat:{parent_id}")], ] ) def skip_product_photo_kb(parent_id: str) -> InlineKeyboardMarkup: return InlineKeyboardMarkup( inline_keyboard=[ [InlineKeyboardButton(text="Пропустить", callback_data="new_product:skip_photo")], [InlineKeyboardButton(text="⬅️ Назад", callback_data=f"cat:{parent_id}")], ] ) @router.callback_query(IsVerified(), F.data.regexp(r"^new:(category|product):.+$")) async def creation_init( cb: CallbackQuery, state: FSMContext, session: AsyncSession, catalogue_service: CatalogueService, ): await state.clear() data = cb.data.split(":") mode = data[1] cat_id = data[2] path = await catalogue_service.get_path(session, cat_id) if mode == "category": await cb.message.edit_text( f"{path} -> ...\n\n🏷️ Введите название новой категории.", reply_markup=get_back_to_catalogue(cat_id), ) ctx = CategoryActionContext(cb.message, cat_id) await state.set_state(AdminControlStorage.new_category) await state.set_data({"ctx": ctx}) if mode == "product": await cb.message.edit_text( f"{path} -> ...\n\n📦 Введите название товара.", reply_markup=get_back_to_catalogue(cat_id), ) await state.set_state(AdminControlStorage.new_product_name) await state.set_data({"ctx": ProductActionContext(cb.message, cat_id)}) @router.message(AdminControlStorage.new_category) async def create_new_category( msg: Message, state: FSMContext, session: AsyncSession, categories_repo: CategoriesRepository, ): await msg.delete() data = await state.get_data() ctx: CategoryActionContext | None = data.get("ctx") if not ctx: await msg.reply("❌ Ошибка при создании категории.", reply_markup=return_menu) return category = await categories_repo.add_category( session, name=msg.text, parent_id=parse_cat_id(ctx.parent_id) ) await ctx.msg.edit_text( f"✅ Категория {msg.text} создана успешно.", reply_markup=created_category( category.id, f"🛒 {category.name}", category.parent_id or "root" ), ) @router.message(IsVerified(), AdminControlStorage.new_product_name) async def create_product_name(msg: Message, state: FSMContext): if not msg.text or not msg.text.strip(): await msg.reply("❌ Название не может быть пустым.") return data = await state.get_data() ctx: ProductActionContext | None = data.get("ctx") if not ctx: await state.clear() await msg.reply("❌ Ошибка при создании товара.", reply_markup=return_menu) return await msg.delete() await ctx.msg.edit_text( "📜 Введите описание товара.", reply_markup=skip_product_description_kb(str(ctx.parent_id)), ) await state.update_data(name=msg.text.strip()) await state.set_state(AdminControlStorage.new_product_description) @router.callback_query(IsVerified(), F.data == "new_product:skip_description") async def skip_product_description(cb: CallbackQuery, state: FSMContext): data = await state.get_data() ctx: ProductActionContext | None = data.get("ctx") if not ctx: await state.clear() await cb.answer("Ошибка при создании товара.", show_alert=True) return await cb.message.edit_text( "💸 Введите цену товара целым числом.", reply_markup=get_back_to_catalogue(ctx.parent_id), ) await state.update_data(description=None) await state.set_state(AdminControlStorage.new_product_price) await cb.answer() @router.message(IsVerified(), AdminControlStorage.new_product_description) async def create_product_description(msg: Message, state: FSMContext): if not msg.text or not msg.text.strip(): await msg.reply("❌ Описание не может быть пустым.") return data = await state.get_data() ctx: ProductActionContext | None = data.get("ctx") if not ctx: await state.clear() await msg.reply("❌ Ошибка при создании товара.", reply_markup=return_menu) return await msg.delete() await ctx.msg.edit_text( "💸 Введите цену товара целым числом.", reply_markup=get_back_to_catalogue(ctx.parent_id), ) await state.update_data(description=msg.text.strip()) await state.set_state(AdminControlStorage.new_product_price) @router.message(IsVerified(), AdminControlStorage.new_product_price) async def create_product_price( msg: Message, state: FSMContext, session: AsyncSession, products_repo: ProductRepository, ): if not msg.text or not msg.text.isdigit() or int(msg.text) <= 0: await msg.reply("❌ Цена должна быть положительным целым числом.") return data = await state.get_data() ctx: ProductActionContext | None = data.get("ctx") if not ctx: await state.clear() await msg.reply("❌ Ошибка при создании товара.", reply_markup=return_menu) return await msg.delete() product = await products_repo.add_product( session, category_id=parse_cat_id(ctx.parent_id), name=data["name"], description=data["description"], price=int(msg.text), ) await ctx.msg.edit_text( "✅ Основные данные сохранены.\n\n📷 Отправьте фотографию товара или пропустите этот шаг.", reply_markup=skip_product_photo_kb(str(ctx.parent_id)), ) await state.update_data(product_id=product.id) await state.set_state(AdminControlStorage.new_product_photo) @router.callback_query(IsVerified(), F.data == "new_product:skip_photo") async def skip_product_photo(cb: CallbackQuery, state: FSMContext): data = await state.get_data() ctx: ProductActionContext | None = data.get("ctx") await state.clear() parent_id = ctx.parent_id if ctx else "root" await cb.message.edit_text("✅ Товар добавлен.", reply_markup=get_back_to_catalogue(parent_id)) await cb.answer() @router.message(IsVerified(), AdminControlStorage.new_product_photo) async def create_product_photo( msg: Message, state: FSMContext, session: AsyncSession, products_repo: ProductRepository, ): data = await state.get_data() if not msg.photo or not data.get("product_id"): await msg.delete() await msg.reply("❌ Отправьте именно фотографию или нажмите «Пропустить».") return await state.clear() await msg.delete() await products_repo.add_product_file_id( session, int(data["product_id"]), file_id=msg.photo[-1].file_id ) ctx: ProductActionContext = data["ctx"] await ctx.msg.edit_text("✅ Товар добавлен.", reply_markup=get_back_to_catalogue(ctx.parent_id))