product management, unification of kb and so on.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
from .inline_mode import router as inline_router
|
||||
from .menu import router as menu_router
|
||||
from .product_mgmt import router as product_router
|
||||
|
||||
routers = [inline_router]
|
||||
routers = [inline_router, menu_router, product_router]
|
||||
|
||||
@@ -11,7 +11,8 @@ from aiogram.utils.deep_linking import create_start_link
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from misc.filters import IsVerified
|
||||
from misc.kb.admins import payment_link, placeholder_kb
|
||||
from misc.kb.admins import payment_link
|
||||
from misc.kb.common import placeholder_kb
|
||||
from misc.utils import b64_to_dict, dict_to_b64
|
||||
from repositories.invoices import InvoiceRepository
|
||||
|
||||
|
||||
31
handlers/admins/menu.py
Normal file
31
handlers/admins/menu.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import logging
|
||||
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import Command
|
||||
|
||||
from misc.filters import IsVerified
|
||||
from misc.kb.admins import main_menu
|
||||
|
||||
router = Router()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.message(IsVerified(), Command("start"))
|
||||
async def admin_main_menu(
|
||||
msg: Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await state.clear()
|
||||
|
||||
await msg.answer("admin", reply_markup=main_menu)
|
||||
|
||||
|
||||
@router.callback_query(IsVerified(), F.data.startswith("menu:"))
|
||||
async def admin_main_menu_cb(
|
||||
cb: CallbackQuery,
|
||||
state: FSMContext,
|
||||
):
|
||||
await state.clear()
|
||||
await cb.message.edit_text("admin", reply_markup=main_menu)
|
||||
95
handlers/admins/product_mgmt.py
Normal file
95
handlers/admins/product_mgmt.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import logging
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from dto.control import EditProductContext
|
||||
from misc.filters import IsVerified
|
||||
from misc.kb.admins import back_to_product_kb
|
||||
from misc.states import AdminControlStorage
|
||||
from misc.texts import product_editing_mapping
|
||||
from repositories.products import ProductRepository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(IsVerified(), F.data.startswith("edit_product:"))
|
||||
async def edit_product(cb: CallbackQuery, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
data = cb.data.split(":")
|
||||
product_id = data[1]
|
||||
mode = data[2]
|
||||
|
||||
if cb.message.photo:
|
||||
await cb.message.delete()
|
||||
msg = await cb.message.answer(
|
||||
f"<b>✍️ Введите {product_editing_mapping.get(mode)}</b>",
|
||||
reply_markup=back_to_product_kb(
|
||||
product_id, cb_factory=lambda p: f"product:{p}"
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
msg = cb.message
|
||||
await cb.message.edit_text(
|
||||
f"<b>✍️ Введите {product_editing_mapping.get(mode)}</b>",
|
||||
reply_markup=back_to_product_kb(
|
||||
product_id, cb_factory=lambda p: f"product:{p}"
|
||||
),
|
||||
)
|
||||
|
||||
ctx = EditProductContext(int(product_id), mode, msg)
|
||||
await state.set_state(AdminControlStorage.edit_product)
|
||||
await state.set_data({"ctx": ctx})
|
||||
|
||||
|
||||
@router.message(AdminControlStorage.edit_product)
|
||||
async def edit_product_query(
|
||||
msg: Message,
|
||||
state: FSMContext,
|
||||
session: AsyncSession,
|
||||
products_repo: ProductRepository,
|
||||
):
|
||||
data = await state.get_data()
|
||||
await state.clear()
|
||||
|
||||
await msg.delete()
|
||||
|
||||
ctx: EditProductContext = data.get("ctx", EditProductContext)
|
||||
kb = back_to_product_kb(ctx.product_id, cb_factory=lambda p: f"product:{p}")
|
||||
|
||||
try:
|
||||
await ctx.msg.edit_text("⏳")
|
||||
if ctx.mode == "name":
|
||||
await products_repo.update_product_name_by_id(
|
||||
session, ctx.product_id, msg.text
|
||||
)
|
||||
elif ctx.mode == "description":
|
||||
await products_repo.update_product_description_by_id(
|
||||
session, ctx.product_id, msg.text
|
||||
)
|
||||
elif ctx.mode == "price":
|
||||
if msg.text.isdigit():
|
||||
await products_repo.update_product_price_by_id(
|
||||
session, ctx.product_id, int(msg.text)
|
||||
)
|
||||
else:
|
||||
await ctx.msg.edit_text(
|
||||
"❌ Вы ввели не число, попробуйте ещё раз...",
|
||||
reply_markup=kb,
|
||||
)
|
||||
await state.set_state(AdminControlStorage.edit_product)
|
||||
await state.set_data({"ctx": ctx})
|
||||
return
|
||||
else:
|
||||
await ctx.msg.edit_text("🍃", reply_markup=kb)
|
||||
return
|
||||
|
||||
await ctx.msg.edit_text("✅ Изменения применены.", reply_markup=kb)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
await ctx.msg.edit_text(text="‼️ Что-то пошло не так.", reply_markup=kb)
|
||||
@@ -5,6 +5,7 @@ from .products import router as products_router
|
||||
from .cart import router as cart_router
|
||||
from .checkout import router as checkout_router
|
||||
from .security import router as security_router
|
||||
from .search import router as search_router
|
||||
|
||||
routers = [
|
||||
invoice_router,
|
||||
@@ -14,4 +15,5 @@ routers = [
|
||||
cart_router,
|
||||
checkout_router,
|
||||
security_router,
|
||||
search_router,
|
||||
]
|
||||
|
||||
@@ -5,9 +5,8 @@ from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import PAGE_SIZE
|
||||
from misc.kb.client import (
|
||||
render_cart,
|
||||
)
|
||||
from misc.kb.client import render_cart
|
||||
from misc.kb.common import return_menu
|
||||
from services.orders import OrderService
|
||||
|
||||
router = Router()
|
||||
@@ -25,11 +24,16 @@ async def cart_init(
|
||||
cart = await order_service.build_pagination_cart_dto(
|
||||
session, customer=cb.from_user.id
|
||||
)
|
||||
if not len(cart.items):
|
||||
await cb.message.edit_text("🛒 Корзина пуста.", reply_markup=return_menu)
|
||||
return
|
||||
|
||||
await cb.message.edit_text(
|
||||
f"total: {cart.total}₽",
|
||||
f"total: {cart.total}₽ | {len(cart.items)}",
|
||||
reply_markup=render_cart(
|
||||
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
||||
cart.items[:PAGE_SIZE],
|
||||
show_next=len(cart.items) > PAGE_SIZE,
|
||||
show_purchase=bool(len(cart.items)),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -47,10 +51,16 @@ async def cart_cmd(
|
||||
session, customer=msg.from_user.id
|
||||
)
|
||||
|
||||
if not len(cart.items):
|
||||
await msg.answer("🛒 Корзина пуста.", reply_markup=return_menu)
|
||||
return
|
||||
|
||||
await msg.answer(
|
||||
f"total: {cart.total}₽",
|
||||
reply_markup=render_cart(
|
||||
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
||||
cart.items[:PAGE_SIZE],
|
||||
show_next=len(cart.items) > PAGE_SIZE,
|
||||
show_purchase=bool(len(cart.items)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ async def subcatalogue(
|
||||
|
||||
if cat_id == "root":
|
||||
children = await categories_repo.get_categories_by_parent_id(session)
|
||||
kb = render_category(children, show_menu=True)
|
||||
kb = render_category(
|
||||
children, show_menu=True, category_cb_factory=lambda c: f"cat:{c.id}"
|
||||
)
|
||||
await cb.message.edit_text(
|
||||
"category selection",
|
||||
reply_markup=kb,
|
||||
@@ -61,11 +63,14 @@ async def subcatalogue(
|
||||
"hya",
|
||||
reply_markup=render_products(
|
||||
products[:PAGE_SIZE],
|
||||
parent_id=parent_id,
|
||||
cat_id=cat_id,
|
||||
page=0,
|
||||
show_prev=products_len < 0,
|
||||
show_next=products_len > PAGE_SIZE,
|
||||
product_cb_factory=lambda p: f"product:{p.id}",
|
||||
back_cb=f"cat:{parent_id}",
|
||||
next_cb=(
|
||||
f"products:{cat_id}:{0 + 1}"
|
||||
if products_len > PAGE_SIZE
|
||||
else None
|
||||
),
|
||||
columns=2,
|
||||
),
|
||||
)
|
||||
return
|
||||
@@ -74,16 +79,20 @@ async def subcatalogue(
|
||||
"hya",
|
||||
reply_markup=render_products(
|
||||
products[:PAGE_SIZE],
|
||||
parent_id=parent_id,
|
||||
cat_id=cat_id,
|
||||
page=0,
|
||||
show_prev=products_len < 0,
|
||||
show_next=products_len > PAGE_SIZE,
|
||||
product_cb_factory=lambda p: f"product:{p.id}",
|
||||
back_cb=f"cat:{parent_id}",
|
||||
prev_cb=f"products:{cat_id}:{0 - 1}" if products_len < 0 else None,
|
||||
next_cb=(
|
||||
f"products:{cat_id}:{0 + 1}" if products_len > PAGE_SIZE else None
|
||||
),
|
||||
columns=2,
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
kb = render_category(children, parent_id)
|
||||
kb = render_category(
|
||||
children, parent_id, category_cb_factory=lambda c: f"cat:{c.id}"
|
||||
)
|
||||
|
||||
await cb.message.edit_text(
|
||||
"category selection",
|
||||
|
||||
@@ -6,13 +6,11 @@ from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import NOTIFICATION_CHANNEL
|
||||
from db.models.orders import OrderStatus
|
||||
from dto.checkout import CheckoutContext
|
||||
from misc.kb.admins import customer_contacts
|
||||
from misc.kb.client import (
|
||||
order_confirmation,
|
||||
order_specs_confirmation,
|
||||
return_menu,
|
||||
)
|
||||
from misc.kb.client import order_confirmation, order_specs_confirmation
|
||||
from misc.kb.common import return_menu
|
||||
from misc.states import CheckoutStorage
|
||||
from misc.texts import get_order_item_list
|
||||
from misc.utils import is_valid_phone
|
||||
@@ -152,13 +150,6 @@ async def payment_create(
|
||||
await state.clear()
|
||||
|
||||
try:
|
||||
await cb.message.edit_text(
|
||||
"<b>🟢 Ваш заказ успешно отправлен!</b>\n"
|
||||
"<i>📦 Мы свяжемся с вами для уточнения деталей в ближайшее время.</i>\n\n"
|
||||
"<b>‼️ Чтобы убедиться, что вы общаетесь с доверенным контактом ШВЕЙТЕХЦЕНТР, перешлите любое сообщение человека в этот чат.</b>",
|
||||
reply_markup=return_menu,
|
||||
)
|
||||
|
||||
cart = await order_service.build_full_cart_dto(session, cb.from_user.id)
|
||||
await cb.bot.send_message(
|
||||
NOTIFICATION_CHANNEL,
|
||||
@@ -173,7 +164,17 @@ async def payment_create(
|
||||
cb.from_user.id, bool(cb.from_user.username), cb.from_user.username
|
||||
),
|
||||
)
|
||||
await order_repo.remove_order_by_customer(session, cb.from_user.id)
|
||||
await order_repo.update_order_status(
|
||||
session, cart.order_id, OrderStatus.CREATED
|
||||
)
|
||||
|
||||
await cb.message.edit_text(
|
||||
"<b>🟢 Ваш заказ успешно отправлен!</b>\n"
|
||||
"<i>📦 Мы свяжемся с вами для уточнения деталей в ближайшее время.</i>\n\n"
|
||||
"<b>‼️ Чтобы убедиться, что вы общаетесь с доверенным контактом ШВЕЙТЕХЦЕНТР, перешлите любое сообщение человека в этот чат.</b>",
|
||||
reply_markup=return_menu,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await cb.message.edit_text(
|
||||
"🍃 Что-то пошло не так, повторите попытку позже...",
|
||||
|
||||
@@ -11,7 +11,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from db.models.invoices import InvoiceStatus
|
||||
from misc.kb.admins import verify_payment
|
||||
from misc.kb.client import confirm_payment, return_menu
|
||||
from misc.kb.client import confirm_payment
|
||||
from misc.kb.common import return_menu
|
||||
from repositories.invoices import InvoiceRepository
|
||||
from config import card_info
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ router = Router()
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def main_menu(
|
||||
async def user_main_menu(
|
||||
msg: Message,
|
||||
state: FSMContext,
|
||||
session: AsyncSession,
|
||||
@@ -29,7 +29,7 @@ async def main_menu(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("menu:"))
|
||||
async def main_menu_cb(
|
||||
async def user_main_menu_cb(
|
||||
cb: CallbackQuery,
|
||||
state: FSMContext,
|
||||
session: AsyncSession,
|
||||
|
||||
@@ -5,7 +5,8 @@ from aiogram.types import CallbackQuery, FSInputFile
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from config import PAGE_SIZE
|
||||
from config import PAGE_SIZE, VERIFIED_ACCOUNTS
|
||||
from misc.kb import admins
|
||||
from misc.kb.client import (
|
||||
render_product_interactions,
|
||||
render_products,
|
||||
@@ -46,11 +47,15 @@ async def products_pagination(
|
||||
await cb.message.edit_reply_markup(
|
||||
reply_markup=render_products(
|
||||
products[:PAGE_SIZE],
|
||||
parent_id=category.parent_id,
|
||||
cat_id=category_id,
|
||||
page=page_id,
|
||||
show_prev=page_id > 0,
|
||||
show_next=products_len > PAGE_SIZE,
|
||||
product_cb_factory=lambda p: f"product:{p.id}",
|
||||
back_cb=f"cat:{category.parent_id}",
|
||||
prev_cb=(f"products:{category_id}:{page_id - 1}" if page_id > 0 else None),
|
||||
next_cb=(
|
||||
f"products:{category_id}:{page_id + 1}"
|
||||
if products_len > PAGE_SIZE
|
||||
else None
|
||||
),
|
||||
columns=2,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -68,9 +73,18 @@ async def product_card(
|
||||
product_id = int(cb.data.split(":")[1])
|
||||
product = await products_repo.get_product_by_id(session, product_id)
|
||||
|
||||
order_item = await order_service.get_cart_product_amount(
|
||||
session, customer=cb.from_user.id, product_id=product_id
|
||||
)
|
||||
if cb.from_user.id in VERIFIED_ACCOUNTS:
|
||||
kb = admins.edit_product(product_id=product.id, category_id=product.category_id)
|
||||
else:
|
||||
order_item = await order_service.get_cart_product_amount(
|
||||
session, customer=cb.from_user.id, product_id=product_id
|
||||
)
|
||||
kb = render_product_interactions(
|
||||
product_id=product.id,
|
||||
category_id=product.category_id,
|
||||
show_cart=bool(order_item),
|
||||
cart_amount=order_item,
|
||||
)
|
||||
|
||||
if product.file_id:
|
||||
await cb.message.delete()
|
||||
@@ -78,24 +92,14 @@ async def product_card(
|
||||
msg = await cb.message.answer_photo(
|
||||
product.file_id,
|
||||
caption=get_product_description(product),
|
||||
reply_markup=render_product_interactions(
|
||||
product_id=product.id,
|
||||
category_id=product.category_id,
|
||||
show_cart=bool(order_item),
|
||||
cart_amount=order_item,
|
||||
),
|
||||
reply_markup=kb,
|
||||
)
|
||||
return
|
||||
|
||||
if not os.path.isfile(product.img_path):
|
||||
await cb.message.edit_text(
|
||||
get_product_description(product),
|
||||
reply_markup=render_product_interactions(
|
||||
product_id=product.id,
|
||||
category_id=product.category_id,
|
||||
show_cart=bool(order_item),
|
||||
cart_amount=order_item,
|
||||
),
|
||||
reply_markup=kb,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -103,12 +107,7 @@ async def product_card(
|
||||
msg = await cb.message.answer_photo(
|
||||
FSInputFile(product.img_path),
|
||||
caption=get_product_description(product),
|
||||
reply_markup=render_product_interactions(
|
||||
product_id=product.id,
|
||||
category_id=product.category_id,
|
||||
show_cart=bool(order_item),
|
||||
cart_amount=order_item,
|
||||
),
|
||||
reply_markup=kb,
|
||||
)
|
||||
|
||||
if not msg.photo:
|
||||
@@ -120,7 +119,7 @@ async def product_card(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("cart_action:"))
|
||||
async def add_to_cart(
|
||||
async def cart_action(
|
||||
cb: CallbackQuery,
|
||||
state: FSMContext,
|
||||
session: AsyncSession,
|
||||
|
||||
58
handlers/client/search.py
Normal file
58
handlers/client/search.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from typing import Optional
|
||||
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from misc.kb.client import render_products
|
||||
from misc.kb.common import return_menu
|
||||
from misc.states import SearchStorage
|
||||
from repositories.products import ProductRepository
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "find")
|
||||
async def search_trigger(cb: CallbackQuery, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
await cb.message.edit_text("🔍 Введите запрос:", reply_markup=return_menu)
|
||||
await state.set_state(SearchStorage.query)
|
||||
await state.set_data({"msg": cb.message})
|
||||
|
||||
|
||||
@router.message(SearchStorage.query)
|
||||
async def searching(
|
||||
msg: Message,
|
||||
state: FSMContext,
|
||||
session: AsyncSession,
|
||||
products_repo: ProductRepository,
|
||||
):
|
||||
data = await state.get_data()
|
||||
await state.clear()
|
||||
|
||||
orig_msg: Optional[Message] = data.get("msg")
|
||||
results = await products_repo.search(
|
||||
session,
|
||||
msg.text,
|
||||
)
|
||||
|
||||
if not orig_msg:
|
||||
await msg.answer(
|
||||
"here",
|
||||
reply_markup=render_products(
|
||||
results,
|
||||
product_cb_factory=lambda p: f"product:{p.id}",
|
||||
back_cb="menu:main",
|
||||
next_cb="",
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
await orig_msg.edit_text(
|
||||
"here",
|
||||
reply_markup=render_products(
|
||||
results, product_cb_factory=lambda p: f"product:{p.id}", back_cb="menu:main"
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user