product management, unification of kb and so on.
This commit is contained in:
10
dto/control.py
Normal file
10
dto/control.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EditProductContext:
|
||||||
|
product_id: int
|
||||||
|
mode: str
|
||||||
|
msg: Message
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
from .inline_mode import router as inline_router
|
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 sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from misc.filters import IsVerified
|
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 misc.utils import b64_to_dict, dict_to_b64
|
||||||
from repositories.invoices import InvoiceRepository
|
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 .cart import router as cart_router
|
||||||
from .checkout import router as checkout_router
|
from .checkout import router as checkout_router
|
||||||
from .security import router as security_router
|
from .security import router as security_router
|
||||||
|
from .search import router as search_router
|
||||||
|
|
||||||
routers = [
|
routers = [
|
||||||
invoice_router,
|
invoice_router,
|
||||||
@@ -14,4 +15,5 @@ routers = [
|
|||||||
cart_router,
|
cart_router,
|
||||||
checkout_router,
|
checkout_router,
|
||||||
security_router,
|
security_router,
|
||||||
|
search_router,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ from aiogram.fsm.context import FSMContext
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config import PAGE_SIZE
|
from config import PAGE_SIZE
|
||||||
from misc.kb.client import (
|
from misc.kb.client import render_cart
|
||||||
render_cart,
|
from misc.kb.common import return_menu
|
||||||
)
|
|
||||||
from services.orders import OrderService
|
from services.orders import OrderService
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
@@ -25,11 +24,16 @@ async def cart_init(
|
|||||||
cart = await order_service.build_pagination_cart_dto(
|
cart = await order_service.build_pagination_cart_dto(
|
||||||
session, customer=cb.from_user.id
|
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(
|
await cb.message.edit_text(
|
||||||
f"total: {cart.total}₽",
|
f"total: {cart.total}₽ | {len(cart.items)}",
|
||||||
reply_markup=render_cart(
|
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
|
session, customer=msg.from_user.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not len(cart.items):
|
||||||
|
await msg.answer("🛒 Корзина пуста.", reply_markup=return_menu)
|
||||||
|
return
|
||||||
|
|
||||||
await msg.answer(
|
await msg.answer(
|
||||||
f"total: {cart.total}₽",
|
f"total: {cart.total}₽",
|
||||||
reply_markup=render_cart(
|
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":
|
if cat_id == "root":
|
||||||
children = await categories_repo.get_categories_by_parent_id(session)
|
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(
|
await cb.message.edit_text(
|
||||||
"category selection",
|
"category selection",
|
||||||
reply_markup=kb,
|
reply_markup=kb,
|
||||||
@@ -61,11 +63,14 @@ async def subcatalogue(
|
|||||||
"hya",
|
"hya",
|
||||||
reply_markup=render_products(
|
reply_markup=render_products(
|
||||||
products[:PAGE_SIZE],
|
products[:PAGE_SIZE],
|
||||||
parent_id=parent_id,
|
product_cb_factory=lambda p: f"product:{p.id}",
|
||||||
cat_id=cat_id,
|
back_cb=f"cat:{parent_id}",
|
||||||
page=0,
|
next_cb=(
|
||||||
show_prev=products_len < 0,
|
f"products:{cat_id}:{0 + 1}"
|
||||||
show_next=products_len > PAGE_SIZE,
|
if products_len > PAGE_SIZE
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
columns=2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -74,16 +79,20 @@ async def subcatalogue(
|
|||||||
"hya",
|
"hya",
|
||||||
reply_markup=render_products(
|
reply_markup=render_products(
|
||||||
products[:PAGE_SIZE],
|
products[:PAGE_SIZE],
|
||||||
parent_id=parent_id,
|
product_cb_factory=lambda p: f"product:{p.id}",
|
||||||
cat_id=cat_id,
|
back_cb=f"cat:{parent_id}",
|
||||||
page=0,
|
prev_cb=f"products:{cat_id}:{0 - 1}" if products_len < 0 else None,
|
||||||
show_prev=products_len < 0,
|
next_cb=(
|
||||||
show_next=products_len > PAGE_SIZE,
|
f"products:{cat_id}:{0 + 1}" if products_len > PAGE_SIZE else None
|
||||||
|
),
|
||||||
|
columns=2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
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(
|
await cb.message.edit_text(
|
||||||
"category selection",
|
"category selection",
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ from aiogram.fsm.context import FSMContext
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config import NOTIFICATION_CHANNEL
|
from config import NOTIFICATION_CHANNEL
|
||||||
|
from db.models.orders import OrderStatus
|
||||||
from dto.checkout import CheckoutContext
|
from dto.checkout import CheckoutContext
|
||||||
from misc.kb.admins import customer_contacts
|
from misc.kb.admins import customer_contacts
|
||||||
from misc.kb.client import (
|
from misc.kb.client import order_confirmation, order_specs_confirmation
|
||||||
order_confirmation,
|
from misc.kb.common import return_menu
|
||||||
order_specs_confirmation,
|
|
||||||
return_menu,
|
|
||||||
)
|
|
||||||
from misc.states import CheckoutStorage
|
from misc.states import CheckoutStorage
|
||||||
from misc.texts import get_order_item_list
|
from misc.texts import get_order_item_list
|
||||||
from misc.utils import is_valid_phone
|
from misc.utils import is_valid_phone
|
||||||
@@ -152,13 +150,6 @@ async def payment_create(
|
|||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
try:
|
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)
|
cart = await order_service.build_full_cart_dto(session, cb.from_user.id)
|
||||||
await cb.bot.send_message(
|
await cb.bot.send_message(
|
||||||
NOTIFICATION_CHANNEL,
|
NOTIFICATION_CHANNEL,
|
||||||
@@ -173,7 +164,17 @@ async def payment_create(
|
|||||||
cb.from_user.id, bool(cb.from_user.username), cb.from_user.username
|
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:
|
except Exception as e:
|
||||||
await cb.message.edit_text(
|
await cb.message.edit_text(
|
||||||
"🍃 Что-то пошло не так, повторите попытку позже...",
|
"🍃 Что-то пошло не так, повторите попытку позже...",
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
|
|
||||||
from db.models.invoices import InvoiceStatus
|
from db.models.invoices import InvoiceStatus
|
||||||
from misc.kb.admins import verify_payment
|
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 repositories.invoices import InvoiceRepository
|
||||||
from config import card_info
|
from config import card_info
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ router = Router()
|
|||||||
|
|
||||||
|
|
||||||
@router.message(Command("start"))
|
@router.message(Command("start"))
|
||||||
async def main_menu(
|
async def user_main_menu(
|
||||||
msg: Message,
|
msg: Message,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
@@ -29,7 +29,7 @@ async def main_menu(
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("menu:"))
|
@router.callback_query(F.data.startswith("menu:"))
|
||||||
async def main_menu_cb(
|
async def user_main_menu_cb(
|
||||||
cb: CallbackQuery,
|
cb: CallbackQuery,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from aiogram.types import CallbackQuery, FSInputFile
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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 (
|
from misc.kb.client import (
|
||||||
render_product_interactions,
|
render_product_interactions,
|
||||||
render_products,
|
render_products,
|
||||||
@@ -46,11 +47,15 @@ async def products_pagination(
|
|||||||
await cb.message.edit_reply_markup(
|
await cb.message.edit_reply_markup(
|
||||||
reply_markup=render_products(
|
reply_markup=render_products(
|
||||||
products[:PAGE_SIZE],
|
products[:PAGE_SIZE],
|
||||||
parent_id=category.parent_id,
|
product_cb_factory=lambda p: f"product:{p.id}",
|
||||||
cat_id=category_id,
|
back_cb=f"cat:{category.parent_id}",
|
||||||
page=page_id,
|
prev_cb=(f"products:{category_id}:{page_id - 1}" if page_id > 0 else None),
|
||||||
show_prev=page_id > 0,
|
next_cb=(
|
||||||
show_next=products_len > PAGE_SIZE,
|
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_id = int(cb.data.split(":")[1])
|
||||||
product = await products_repo.get_product_by_id(session, product_id)
|
product = await products_repo.get_product_by_id(session, product_id)
|
||||||
|
|
||||||
order_item = await order_service.get_cart_product_amount(
|
if cb.from_user.id in VERIFIED_ACCOUNTS:
|
||||||
session, customer=cb.from_user.id, product_id=product_id
|
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:
|
if product.file_id:
|
||||||
await cb.message.delete()
|
await cb.message.delete()
|
||||||
@@ -78,24 +92,14 @@ async def product_card(
|
|||||||
msg = await cb.message.answer_photo(
|
msg = await cb.message.answer_photo(
|
||||||
product.file_id,
|
product.file_id,
|
||||||
caption=get_product_description(product),
|
caption=get_product_description(product),
|
||||||
reply_markup=render_product_interactions(
|
reply_markup=kb,
|
||||||
product_id=product.id,
|
|
||||||
category_id=product.category_id,
|
|
||||||
show_cart=bool(order_item),
|
|
||||||
cart_amount=order_item,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not os.path.isfile(product.img_path):
|
if not os.path.isfile(product.img_path):
|
||||||
await cb.message.edit_text(
|
await cb.message.edit_text(
|
||||||
get_product_description(product),
|
get_product_description(product),
|
||||||
reply_markup=render_product_interactions(
|
reply_markup=kb,
|
||||||
product_id=product.id,
|
|
||||||
category_id=product.category_id,
|
|
||||||
show_cart=bool(order_item),
|
|
||||||
cart_amount=order_item,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -103,12 +107,7 @@ async def product_card(
|
|||||||
msg = await cb.message.answer_photo(
|
msg = await cb.message.answer_photo(
|
||||||
FSInputFile(product.img_path),
|
FSInputFile(product.img_path),
|
||||||
caption=get_product_description(product),
|
caption=get_product_description(product),
|
||||||
reply_markup=render_product_interactions(
|
reply_markup=kb,
|
||||||
product_id=product.id,
|
|
||||||
category_id=product.category_id,
|
|
||||||
show_cart=bool(order_item),
|
|
||||||
cart_amount=order_item,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not msg.photo:
|
if not msg.photo:
|
||||||
@@ -120,7 +119,7 @@ async def product_card(
|
|||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("cart_action:"))
|
@router.callback_query(F.data.startswith("cart_action:"))
|
||||||
async def add_to_cart(
|
async def cart_action(
|
||||||
cb: CallbackQuery,
|
cb: CallbackQuery,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
session: AsyncSession,
|
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"
|
||||||
|
),
|
||||||
|
)
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
from typing import Optional
|
from typing import Optional, Callable
|
||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
placeholder_kb = InlineKeyboardBuilder(
|
main_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
||||||
[[InlineKeyboardButton(text="🍃", callback_data="...")]]
|
[
|
||||||
|
[InlineKeyboardButton(text="📦 Изменить товары", callback_data="cat:root")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="📜 Изменить категории", callback_data="edit:categories"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
# [InlineKeyboardButton(text="", callback_data="")],
|
||||||
|
]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def back_to_product_kb(product_id: int, *, cb_factory: Callable[[int], str]):
|
||||||
|
return InlineKeyboardBuilder(
|
||||||
|
[[InlineKeyboardButton(text="⬅️", callback_data=cb_factory(product_id))]]
|
||||||
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
def customer_contacts(
|
def customer_contacts(
|
||||||
user_id: int, has_mention: bool, mention: Optional[str] = None
|
user_id: int, has_mention: bool, mention: Optional[str] = None
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
@@ -44,3 +58,26 @@ def verify_payment(invoice_id: int):
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def edit_product(product_id: int, category_id: int):
|
||||||
|
prefix = f"edit_product:{product_id}"
|
||||||
|
return InlineKeyboardBuilder(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="✍️ Название", callback_data=f"{prefix}:name"
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="✍️ Описание",
|
||||||
|
callback_data=f"{prefix}:description",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="💸 Цена", callback_data=f"{prefix}:price")],
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(text="👁️", callback_data=f"{prefix}:hide"),
|
||||||
|
InlineKeyboardButton(text="❌", callback_data=f"{prefix}:delete"),
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="⬅️", callback_data=f"cat:{category_id}")],
|
||||||
|
]
|
||||||
|
).as_markup()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Optional, Callable
|
||||||
|
|
||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
@@ -6,10 +6,6 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
|||||||
from db.models import Category, Product
|
from db.models import Category, Product
|
||||||
from dto.cart import CartItemDTO
|
from dto.cart import CartItemDTO
|
||||||
|
|
||||||
return_menu = InlineKeyboardBuilder(
|
|
||||||
[[InlineKeyboardButton(text="⬅️ В меню", callback_data="menu:main")]]
|
|
||||||
).as_markup()
|
|
||||||
|
|
||||||
|
|
||||||
def pagination_row(
|
def pagination_row(
|
||||||
*,
|
*,
|
||||||
@@ -61,11 +57,13 @@ def render_category(
|
|||||||
children: list[Category],
|
children: list[Category],
|
||||||
parent_id: Optional[int] = None,
|
parent_id: Optional[int] = None,
|
||||||
show_menu: bool = False,
|
show_menu: bool = False,
|
||||||
|
*,
|
||||||
|
category_cb_factory: Callable[[Category], str],
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
btns = [
|
btns = [
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=category.name,
|
text=category.name,
|
||||||
callback_data=f"cat:{category.id}",
|
callback_data=category_cb_factory(category),
|
||||||
)
|
)
|
||||||
for category in children
|
for category in children
|
||||||
]
|
]
|
||||||
@@ -88,35 +86,37 @@ def render_category(
|
|||||||
def render_products(
|
def render_products(
|
||||||
products: list[Product],
|
products: list[Product],
|
||||||
*,
|
*,
|
||||||
parent_id: int,
|
product_cb_factory: Callable[[Product], str],
|
||||||
cat_id: int,
|
back_cb: Optional[str] = None,
|
||||||
page: int = 0,
|
prev_cb: Optional[str] = None,
|
||||||
show_prev: Optional[bool] = False,
|
next_cb: Optional[str] = None,
|
||||||
show_next: Optional[bool] = False,
|
columns: int = 2,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
btns = [
|
builder = InlineKeyboardBuilder()
|
||||||
InlineKeyboardButton(text=product.name, callback_data=f"product:{product.id}")
|
|
||||||
for product in products
|
|
||||||
]
|
|
||||||
|
|
||||||
nav = pagination_row(
|
if back_cb:
|
||||||
page=page,
|
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=back_cb))
|
||||||
prev_cb=f"products:{cat_id}:{page - 1}" if show_prev else None,
|
|
||||||
next_cb=f"products:{cat_id}:{page + 1}" if show_next else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
return InlineKeyboardBuilder(
|
for i in range(0, len(products), columns):
|
||||||
[
|
row = [
|
||||||
[
|
InlineKeyboardButton(
|
||||||
InlineKeyboardButton(
|
text=p.name,
|
||||||
text="⬅️ Назад",
|
callback_data=product_cb_factory(p),
|
||||||
callback_data=f"cat:{parent_id}",
|
)
|
||||||
)
|
for p in products[i : i + columns]
|
||||||
]
|
|
||||||
]
|
]
|
||||||
+ [btns[i : i + 2] for i in range(0, len(products), 2)]
|
builder.row(*row)
|
||||||
+ [nav]
|
|
||||||
).as_markup()
|
nav_buttons = []
|
||||||
|
if prev_cb:
|
||||||
|
nav_buttons.append(InlineKeyboardButton(text="◀️", callback_data=prev_cb))
|
||||||
|
if next_cb:
|
||||||
|
nav_buttons.append(InlineKeyboardButton(text="▶️", callback_data=next_cb))
|
||||||
|
|
||||||
|
if nav_buttons:
|
||||||
|
builder.row(*nav_buttons)
|
||||||
|
|
||||||
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
def render_product_interactions(
|
def render_product_interactions(
|
||||||
@@ -165,6 +165,7 @@ def render_cart(
|
|||||||
page: int = 0,
|
page: int = 0,
|
||||||
show_prev: Optional[bool] = False,
|
show_prev: Optional[bool] = False,
|
||||||
show_next: Optional[bool] = False,
|
show_next: Optional[bool] = False,
|
||||||
|
show_purchase: Optional[bool] = False,
|
||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
btns = [
|
btns = [
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
@@ -180,19 +181,25 @@ def render_cart(
|
|||||||
next_cb=f"cart:{page + 1}" if show_next else None,
|
next_cb=f"cart:{page + 1}" if show_next else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return InlineKeyboardBuilder(
|
markup: list[list[InlineKeyboardButton]] = (
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="⬅️ Назад",
|
text="⬅️ Назад",
|
||||||
callback_data="menu:main",
|
callback_data="menu:main",
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
+ [btns[i : i + 2] for i in range(0, len(cart_items), 2)]
|
+ [btns[i : i + 2] for i in range(0, len(cart_items), 2)]
|
||||||
+ [nav]
|
+ [nav]
|
||||||
+ [[InlineKeyboardButton(text="🔷 Оформить заказ", callback_data="checkout")]]
|
)
|
||||||
).as_markup()
|
|
||||||
|
if show_purchase:
|
||||||
|
markup.append(
|
||||||
|
[InlineKeyboardButton(text="🔷 Оформить заказ", callback_data="checkout")]
|
||||||
|
)
|
||||||
|
|
||||||
|
return InlineKeyboardBuilder(markup).as_markup()
|
||||||
|
|
||||||
|
|
||||||
def order_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
def order_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
||||||
|
|||||||
10
misc/kb/common.py
Normal file
10
misc/kb/common.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
placeholder_kb: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
||||||
|
[[InlineKeyboardButton(text="🍃", callback_data="...")]]
|
||||||
|
).as_markup()
|
||||||
|
|
||||||
|
return_menu: InlineKeyboardMarkup = InlineKeyboardBuilder(
|
||||||
|
[[InlineKeyboardButton(text="⬅️ В меню", callback_data="menu:main")]]
|
||||||
|
).as_markup()
|
||||||
@@ -10,3 +10,11 @@ class CheckoutStorage(StatesGroup):
|
|||||||
|
|
||||||
class PaymentStorage(StatesGroup):
|
class PaymentStorage(StatesGroup):
|
||||||
refresh = State()
|
refresh = State()
|
||||||
|
|
||||||
|
|
||||||
|
class SearchStorage(StatesGroup):
|
||||||
|
query = State()
|
||||||
|
|
||||||
|
|
||||||
|
class AdminControlStorage(StatesGroup):
|
||||||
|
edit_product = State()
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
from db.models import Product
|
from db.models import Product
|
||||||
from dto.cart import CartDTO
|
from dto.cart import CartDTO
|
||||||
|
|
||||||
|
product_editing_mapping = {
|
||||||
|
"name": "новое название",
|
||||||
|
"description": "новое описание",
|
||||||
|
"price": "новую цену",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_product_description(product: Product) -> str:
|
def get_product_description(product: Product) -> str:
|
||||||
output = f"📦 <b>{product.name}</b>\n\n<b><i>🏷️ {product.price}₽</i></b>"
|
output = f"📦 <b>{product.name}</b>\n\n<b>🏷️ {product.price}₽</b>"
|
||||||
if product.description:
|
if product.description:
|
||||||
output += f"\n\n📜 {product.description}"
|
output += f"\n\n📜 <b><i>{product.description}</i></b>"
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select
|
||||||
from db.models import Invoice
|
from db.models import Invoice
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
@@ -37,19 +37,15 @@ class InvoiceRepository:
|
|||||||
async def update_invoice_msg(
|
async def update_invoice_msg(
|
||||||
self, session: AsyncSession, *, invoice_id: int, inline_message_id: int
|
self, session: AsyncSession, *, invoice_id: int, inline_message_id: int
|
||||||
):
|
):
|
||||||
stmt = (
|
invoice = await self.get_invoice_by_id(session, invoice_id)
|
||||||
update(Invoice.__table__)
|
invoice.inline_message_id = inline_message_id
|
||||||
.where(Invoice.id == invoice_id)
|
|
||||||
.values(inline_message_id=inline_message_id)
|
await session.commit()
|
||||||
)
|
|
||||||
await session.execute(stmt)
|
|
||||||
|
|
||||||
async def update_invoice_status(
|
async def update_invoice_status(
|
||||||
self, session: AsyncSession, *, invoice_id: int, status: InvoiceStatus
|
self, session: AsyncSession, *, invoice_id: int, status: InvoiceStatus
|
||||||
):
|
):
|
||||||
stmt = (
|
invoice = await self.get_invoice_by_id(session, invoice_id)
|
||||||
update(Invoice.__table__)
|
invoice.status = status
|
||||||
.where(Invoice.id == invoice_id)
|
|
||||||
.values(status=status)
|
await session.commit()
|
||||||
)
|
|
||||||
await session.execute(stmt)
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import delete, func, select, update
|
from sqlalchemy import delete, func, select
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from db.models.orders import Order, OrderItem, OrderStatus
|
from db.models.orders import Order, OrderItem, OrderStatus
|
||||||
@@ -55,13 +55,9 @@ class OrderItemRepository:
|
|||||||
async def update_order_item_quantity(
|
async def update_order_item_quantity(
|
||||||
self, session: AsyncSession, order_item_id: int, *, quantity: int = 0
|
self, session: AsyncSession, order_item_id: int, *, quantity: int = 0
|
||||||
):
|
):
|
||||||
stmt = (
|
item = await self.get_item_by_id(session, order_item_id)
|
||||||
update(OrderItem.__table__)
|
item.quantity = quantity
|
||||||
.where(OrderItem.id == order_item_id)
|
|
||||||
.values(quantity=quantity)
|
|
||||||
)
|
|
||||||
|
|
||||||
await session.execute(stmt)
|
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
async def create_order_item(
|
async def create_order_item(
|
||||||
@@ -82,7 +78,7 @@ class OrderItemRepository:
|
|||||||
return order_item
|
return order_item
|
||||||
|
|
||||||
async def delete_item(self, session: AsyncSession, item_id: int):
|
async def delete_item(self, session: AsyncSession, item_id: int):
|
||||||
stmt = delete(OrderItem.__table__).where(OrderItem.id == item_id)
|
stmt = delete(OrderItem).where(OrderItem.id == item_id)
|
||||||
|
|
||||||
await session.execute(stmt)
|
await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@@ -48,13 +48,21 @@ class OrderRepository:
|
|||||||
return order
|
return order
|
||||||
|
|
||||||
async def remove_order_by_id(self, session: AsyncSession, order_id: int):
|
async def remove_order_by_id(self, session: AsyncSession, order_id: int):
|
||||||
stmt = delete(Order.__table__).where(Order.id == order_id)
|
stmt = delete(Order).where(Order.id == order_id)
|
||||||
|
|
||||||
await session.execute(stmt)
|
await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
async def remove_order_by_customer(self, session: AsyncSession, customer: int):
|
async def remove_order_by_customer(self, session: AsyncSession, customer: int):
|
||||||
stmt = delete(Order.__table__).where(Order.customer == customer)
|
stmt = delete(Order).where(Order.customer == customer)
|
||||||
|
|
||||||
await session.execute(stmt)
|
await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
async def update_order_status(
|
||||||
|
self, session: AsyncSession, order_id: int, status: OrderStatus
|
||||||
|
):
|
||||||
|
order = await self.get_order_by_id(session, order_id)
|
||||||
|
order.status = status
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, update
|
from sqlalchemy import select, update
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from config import PAGE_SIZE
|
||||||
from db.models import Product
|
from db.models import Product
|
||||||
|
|
||||||
|
|
||||||
@@ -31,10 +33,59 @@ class ProductRepository:
|
|||||||
self, session: AsyncSession, product_id: int, *, file_id: str
|
self, session: AsyncSession, product_id: int, *, file_id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
stmt = (
|
stmt = (
|
||||||
update(Product.__table__)
|
update(Product)
|
||||||
.where(Product.id == product_id)
|
.where(Product.id == product_id)
|
||||||
.where(Product.file_id is None)
|
.where(Product.file_id is None)
|
||||||
.values(file_id=file_id)
|
.values(file_id=file_id)
|
||||||
)
|
)
|
||||||
await session.execute(stmt)
|
await session.execute(stmt)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
async def search(
|
||||||
|
self,
|
||||||
|
session: AsyncSession,
|
||||||
|
query: str,
|
||||||
|
*,
|
||||||
|
limit: int = PAGE_SIZE + 1,
|
||||||
|
offset: int = 0,
|
||||||
|
) -> list[Optional[Product]]:
|
||||||
|
ts_query = func.plainto_tsquery("simple", query)
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
select(Product)
|
||||||
|
.where(Product.search_vector.op("@@")(ts_query))
|
||||||
|
.order_by(func.ts_rank(Product.search_vector, ts_query).desc())
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
)
|
||||||
|
|
||||||
|
res = await session.scalars(stmt)
|
||||||
|
|
||||||
|
return list(res)
|
||||||
|
|
||||||
|
async def update_product_name_by_id(
|
||||||
|
self, session: AsyncSession, product_id: int, name: str
|
||||||
|
):
|
||||||
|
product = await self.get_product_by_id(session, product_id=product_id)
|
||||||
|
product.name = name
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
return product
|
||||||
|
|
||||||
|
async def update_product_description_by_id(
|
||||||
|
self, session: AsyncSession, product_id: int, description: str
|
||||||
|
):
|
||||||
|
product = await self.get_product_by_id(session, product_id=product_id)
|
||||||
|
product.description = description
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
return product
|
||||||
|
|
||||||
|
async def update_product_price_by_id(
|
||||||
|
self, session: AsyncSession, product_id: int, price: str
|
||||||
|
):
|
||||||
|
product = await self.get_product_by_id(session, product_id=product_id)
|
||||||
|
product.price = price
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
return product
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class OrderService:
|
|||||||
session, order_item.id, quantity=order_item.quantity + quantity
|
session, order_item.id, quantity=order_item.quantity + quantity
|
||||||
)
|
)
|
||||||
|
|
||||||
return order_item.quantity + quantity
|
return order_item.quantity
|
||||||
|
|
||||||
async def remove_from_cart(
|
async def remove_from_cart(
|
||||||
self,
|
self,
|
||||||
@@ -162,7 +162,9 @@ class OrderService:
|
|||||||
|
|
||||||
return CartDTO(items=cart_items, total=total, order_id=order.id)
|
return CartDTO(items=cart_items, total=total, order_id=order.id)
|
||||||
|
|
||||||
async def build_full_cart_dto(self, session: AsyncSession, customer: int):
|
async def build_full_cart_dto(
|
||||||
|
self, session: AsyncSession, customer: int
|
||||||
|
) -> CartDTO:
|
||||||
order = await self.order_repo.get_draft_order_by_user(session, customer)
|
order = await self.order_repo.get_draft_order_by_user(session, customer)
|
||||||
|
|
||||||
cart_items = []
|
cart_items = []
|
||||||
|
|||||||
Reference in New Issue
Block a user