225 lines
8.2 KiB
Python
225 lines
8.2 KiB
Python
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} <i>-> ...</i>\n\n<b>🏷️ Введите название новой категории.</b>",
|
|
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} <i>-> ...</i>\n\n<b>📦 Введите название товара.</b>",
|
|
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(
|
|
"<b>📜 Введите описание товара.</b>",
|
|
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(
|
|
"<b>💸 Введите цену товара целым числом.</b>",
|
|
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(
|
|
"<b>💸 Введите цену товара целым числом.</b>",
|
|
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<b>📷 Отправьте фотографию товара или пропустите этот шаг.</b>",
|
|
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))
|