separated clients handlers, cart->checkout ppline
This commit is contained in:
@@ -17,3 +17,4 @@ POSTGRES_URL = os.getenv(
|
|||||||
### Constants ###
|
### Constants ###
|
||||||
|
|
||||||
PAGE_SIZE = 4
|
PAGE_SIZE = 4
|
||||||
|
VERIFIED_ACCOUNTS = [1026030711]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -17,3 +18,4 @@ class CartItemDTO:
|
|||||||
class CartDTO:
|
class CartDTO:
|
||||||
items: list[CartItemDTO]
|
items: list[CartItemDTO]
|
||||||
total: int
|
total: int
|
||||||
|
order_id: Optional[int] = None
|
||||||
|
|||||||
18
dto/checkout.py
Normal file
18
dto/checkout.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CheckoutContext:
|
||||||
|
orig_msg: Message
|
||||||
|
order_id: int
|
||||||
|
name: Optional[str] = None
|
||||||
|
phone: Optional[str] = None
|
||||||
|
address: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PaymentContext:
|
||||||
|
notification_msg: Message
|
||||||
@@ -1,9 +1,4 @@
|
|||||||
from aiogram import Dispatcher
|
from .client import routers as client_routers
|
||||||
|
from .admins import routers as admins_routers
|
||||||
|
|
||||||
from .client import router as client_router
|
__all__ = ["client_routers", "admins_routers"]
|
||||||
from .admins import router as admins_router
|
|
||||||
|
|
||||||
|
|
||||||
def init_handlers(dp: Dispatcher):
|
|
||||||
dp.include_router(client_router)
|
|
||||||
dp.include_router(admins_router)
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
from aiogram import Router
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
@router.message()
|
|
||||||
async def echo(msg: Message):
|
|
||||||
await msg.answer(msg.text)
|
|
||||||
1
handlers/admins/__init__.py
Normal file
1
handlers/admins/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
routers = []
|
||||||
@@ -1,347 +0,0 @@
|
|||||||
import os
|
|
||||||
from re import Match
|
|
||||||
from aiogram import Router, F
|
|
||||||
from aiogram.types import CallbackQuery, FSInputFile, Message
|
|
||||||
from aiogram.filters import Command
|
|
||||||
from aiogram.fsm.context import FSMContext
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from config import PAGE_SIZE
|
|
||||||
from misc.kb import main_menu_kb, render_category
|
|
||||||
from misc.kb.client import (
|
|
||||||
get_back_to_catalogue,
|
|
||||||
render_cart,
|
|
||||||
render_product_interactions,
|
|
||||||
render_products,
|
|
||||||
)
|
|
||||||
from misc.texts import get_product_description
|
|
||||||
from repositories.categories import CategoriesRepository
|
|
||||||
from repositories.products import ProductRepository
|
|
||||||
from services.orders import OrderService
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("start"))
|
|
||||||
async def main_menu(
|
|
||||||
msg: Message, state: FSMContext, session: AsyncSession, order_service: OrderService
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
cart_items = await order_service.get_cart_items_count(session, msg.from_user.id)
|
|
||||||
await msg.answer(
|
|
||||||
"hii!", reply_markup=main_menu_kb(cart_items)
|
|
||||||
) # TODO: Write a welcome message
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("menu:"))
|
|
||||||
async def main_menu_cb(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
order_service: OrderService,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
cart_items = await order_service.get_cart_items_count(session, cb.from_user.id)
|
|
||||||
await cb.message.edit_text(
|
|
||||||
"hii!", reply_markup=main_menu_kb(cart_items)
|
|
||||||
) # TODO: Write a welcome message
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("orders"))
|
|
||||||
async def get_orders(
|
|
||||||
msg: Message,
|
|
||||||
state: FSMContext,
|
|
||||||
order_service: OrderService,
|
|
||||||
session: AsyncSession,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
count = await order_service.get_order_count(session, msg.from_user.id)
|
|
||||||
await msg.answer(f"you have {count} orders.")
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("cat:"))
|
|
||||||
async def subcatalogue(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
categories_repo: CategoriesRepository,
|
|
||||||
products_repo: ProductRepository,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
cat_id = cb.data.split(":")[1]
|
|
||||||
|
|
||||||
if cat_id == "root":
|
|
||||||
children = await categories_repo.get_categories_by_parent_id(session)
|
|
||||||
kb = render_category(children, show_menu=True)
|
|
||||||
await cb.message.edit_text(
|
|
||||||
"category selection",
|
|
||||||
reply_markup=kb,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
cat_id = int(cat_id)
|
|
||||||
|
|
||||||
children = await categories_repo.get_categories_by_parent_id(session, cat_id)
|
|
||||||
category = await categories_repo.get_category_by_id(session, cat_id)
|
|
||||||
parent_id = category.parent_id or "root"
|
|
||||||
|
|
||||||
if not children:
|
|
||||||
products = await products_repo.get_product_by_category(
|
|
||||||
session, cat_id, limit=PAGE_SIZE + 1, offset=0
|
|
||||||
)
|
|
||||||
products_len = len(products)
|
|
||||||
|
|
||||||
if not products:
|
|
||||||
await cb.message.edit_text(
|
|
||||||
"🍃 Здесь ничего нет...",
|
|
||||||
reply_markup=get_back_to_catalogue(parent_id),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if cb.message.photo:
|
|
||||||
await cb.message.delete()
|
|
||||||
await cb.message.answer(
|
|
||||||
"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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
await cb.message.edit_text(
|
|
||||||
"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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
kb = render_category(children, parent_id)
|
|
||||||
|
|
||||||
await cb.message.edit_text(
|
|
||||||
"category selection",
|
|
||||||
reply_markup=kb,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
|
||||||
F.data.regexp(r"products:(?P<category_id>.*):(?P<page_id>\d+)").as_(
|
|
||||||
"pagination_match"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
async def products_pagination(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
pagination_match: Match[str],
|
|
||||||
session: AsyncSession,
|
|
||||||
products_repo: ProductRepository,
|
|
||||||
categories_repo: CategoriesRepository,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
pagination_data = pagination_match.groupdict()
|
|
||||||
category_id = int(pagination_data.get("category_id"))
|
|
||||||
category = await categories_repo.get_category_by_id(session, category_id)
|
|
||||||
page_id = int(pagination_data.get("page_id", 0))
|
|
||||||
|
|
||||||
products = await products_repo.get_product_by_category(
|
|
||||||
session, category_id, limit=PAGE_SIZE + 1, offset=page_id * PAGE_SIZE
|
|
||||||
)
|
|
||||||
products_len = len(products)
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("product:"))
|
|
||||||
async def product_card(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
products_repo: ProductRepository,
|
|
||||||
order_service: OrderService,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
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 product.file_id:
|
|
||||||
await cb.message.delete()
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
await cb.message.delete()
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if not msg.photo:
|
|
||||||
return # TODO: Add logging
|
|
||||||
|
|
||||||
await products_repo.add_product_file_id(
|
|
||||||
session, product_id, file_id=msg.photo[-1].file_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("cart_action:"))
|
|
||||||
async def add_to_cart(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
order_service: OrderService,
|
|
||||||
products_repo: ProductRepository,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
action = cb.data.split(":")[1]
|
|
||||||
product_id = int(cb.data.split(":")[2])
|
|
||||||
product = await products_repo.get_product_by_id(session, product_id)
|
|
||||||
show_cart = True
|
|
||||||
|
|
||||||
if action == "add":
|
|
||||||
quantity = await order_service.add_to_cart(
|
|
||||||
session, customer=cb.from_user.id, product_id=product_id
|
|
||||||
)
|
|
||||||
elif action == "remove":
|
|
||||||
quantity = await order_service.remove_from_cart(
|
|
||||||
session, customer=cb.from_user.id, product_id=product_id
|
|
||||||
)
|
|
||||||
show_cart = bool(quantity)
|
|
||||||
else:
|
|
||||||
show_cart = False
|
|
||||||
quantity = 0
|
|
||||||
await order_service.clear_cart_item(
|
|
||||||
session, customer=cb.from_user.id, product_id=product_id
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await cb.message.edit_reply_markup(
|
|
||||||
reply_markup=render_product_interactions(
|
|
||||||
product_id=product_id,
|
|
||||||
category_id=product.category_id,
|
|
||||||
show_cart=show_cart,
|
|
||||||
cart_amount=quantity,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
await cb.answer("🍃")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "cart")
|
|
||||||
async def cart_init(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
order_service: OrderService,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
cart = await order_service.build_cart_dto(session, customer=cb.from_user.id)
|
|
||||||
|
|
||||||
await cb.message.edit_text(
|
|
||||||
f"total: {cart.total}₽",
|
|
||||||
reply_markup=render_cart(
|
|
||||||
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("cart"))
|
|
||||||
async def cart_cmd(
|
|
||||||
msg: Message,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
order_service: OrderService,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
cart = await order_service.build_cart_dto(session, customer=msg.from_user.id)
|
|
||||||
|
|
||||||
await msg.answer(
|
|
||||||
f"total: {cart.total}₽",
|
|
||||||
reply_markup=render_cart(
|
|
||||||
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("cart:"))
|
|
||||||
async def cart_pagination(
|
|
||||||
cb: CallbackQuery,
|
|
||||||
state: FSMContext,
|
|
||||||
session: AsyncSession,
|
|
||||||
order_service: OrderService,
|
|
||||||
):
|
|
||||||
await state.clear()
|
|
||||||
|
|
||||||
page = int(cb.data.split(":")[1])
|
|
||||||
|
|
||||||
cart = await order_service.build_cart_dto(
|
|
||||||
session, customer=cb.from_user.id, offset=page * PAGE_SIZE
|
|
||||||
)
|
|
||||||
|
|
||||||
await cb.message.edit_reply_markup(
|
|
||||||
reply_markup=render_cart(
|
|
||||||
cart.items[:PAGE_SIZE],
|
|
||||||
page=page,
|
|
||||||
show_next=len(cart.items) > PAGE_SIZE,
|
|
||||||
show_prev=page > 0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
15
handlers/client/__init__.py
Normal file
15
handlers/client/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from .menu import router as menu_router
|
||||||
|
from .catalogue import router as catalogue_router
|
||||||
|
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
|
||||||
|
|
||||||
|
routers = [
|
||||||
|
menu_router,
|
||||||
|
catalogue_router,
|
||||||
|
products_router,
|
||||||
|
cart_router,
|
||||||
|
checkout_router,
|
||||||
|
security_router,
|
||||||
|
]
|
||||||
80
handlers/client/cart.py
Normal file
80
handlers/client/cart.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
from aiogram.filters import Command
|
||||||
|
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 services.orders import OrderService
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "cart")
|
||||||
|
async def cart_init(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cart = await order_service.build_pagination_cart_dto(
|
||||||
|
session, customer=cb.from_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
await cb.message.edit_text(
|
||||||
|
f"total: {cart.total}₽",
|
||||||
|
reply_markup=render_cart(
|
||||||
|
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("cart"))
|
||||||
|
async def cart_cmd(
|
||||||
|
msg: Message,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cart = await order_service.build_pagination_cart_dto(
|
||||||
|
session, customer=msg.from_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
await msg.answer(
|
||||||
|
f"total: {cart.total}₽",
|
||||||
|
reply_markup=render_cart(
|
||||||
|
cart.items[:PAGE_SIZE], show_next=len(cart.items) > PAGE_SIZE
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("cart:"))
|
||||||
|
async def cart_pagination(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
page = int(cb.data.split(":")[1])
|
||||||
|
|
||||||
|
cart = await order_service.build_pagination_cart_dto(
|
||||||
|
session, customer=cb.from_user.id, offset=page * PAGE_SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
await cb.message.edit_reply_markup(
|
||||||
|
reply_markup=render_cart(
|
||||||
|
cart.items[:PAGE_SIZE],
|
||||||
|
page=page,
|
||||||
|
show_next=len(cart.items) > PAGE_SIZE,
|
||||||
|
show_prev=page > 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
91
handlers/client/catalogue.py
Normal file
91
handlers/client/catalogue.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from config import PAGE_SIZE
|
||||||
|
from misc.kb import render_category
|
||||||
|
from misc.kb.client import (
|
||||||
|
get_back_to_catalogue,
|
||||||
|
render_products,
|
||||||
|
)
|
||||||
|
from repositories.categories import CategoriesRepository
|
||||||
|
from repositories.products import ProductRepository
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("cat:"))
|
||||||
|
async def subcatalogue(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
categories_repo: CategoriesRepository,
|
||||||
|
products_repo: ProductRepository,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cat_id = cb.data.split(":")[1]
|
||||||
|
|
||||||
|
if cat_id == "root":
|
||||||
|
children = await categories_repo.get_categories_by_parent_id(session)
|
||||||
|
kb = render_category(children, show_menu=True)
|
||||||
|
await cb.message.edit_text(
|
||||||
|
"category selection",
|
||||||
|
reply_markup=kb,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
cat_id = int(cat_id)
|
||||||
|
|
||||||
|
children = await categories_repo.get_categories_by_parent_id(session, cat_id)
|
||||||
|
category = await categories_repo.get_category_by_id(session, cat_id)
|
||||||
|
parent_id = category.parent_id or "root"
|
||||||
|
|
||||||
|
if not children:
|
||||||
|
products = await products_repo.get_product_by_category(
|
||||||
|
session, cat_id, limit=PAGE_SIZE + 1, offset=0
|
||||||
|
)
|
||||||
|
products_len = len(products)
|
||||||
|
|
||||||
|
if not products:
|
||||||
|
await cb.message.edit_text(
|
||||||
|
"🍃 Здесь ничего нет...",
|
||||||
|
reply_markup=get_back_to_catalogue(parent_id),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if cb.message.photo:
|
||||||
|
await cb.message.delete()
|
||||||
|
await cb.message.answer(
|
||||||
|
"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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await cb.message.edit_text(
|
||||||
|
"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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
kb = render_category(children, parent_id)
|
||||||
|
|
||||||
|
await cb.message.edit_text(
|
||||||
|
"category selection",
|
||||||
|
reply_markup=kb,
|
||||||
|
)
|
||||||
122
handlers/client/checkout.py
Normal file
122
handlers/client/checkout.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from dto.checkout import CheckoutContext
|
||||||
|
from misc.kb.client import (
|
||||||
|
order_confirmation,
|
||||||
|
order_specs_confirmation,
|
||||||
|
return_menu,
|
||||||
|
)
|
||||||
|
from misc.states import CheckoutStorage
|
||||||
|
from misc.texts import get_order_item_list
|
||||||
|
from misc.utils import is_valid_phone
|
||||||
|
from repositories.orders import OrderRepository
|
||||||
|
from services.orders import OrderService
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "checkout")
|
||||||
|
async def checkout(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cart = await order_service.build_full_cart_dto(session, cb.from_user.id)
|
||||||
|
await cb.message.edit_text(
|
||||||
|
text=get_order_item_list(cart), reply_markup=order_confirmation(cart.order_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("confirm_order:"))
|
||||||
|
async def order_confirm(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_repo: OrderRepository,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
order_id = cb.data.split(":")[1]
|
||||||
|
if not order_id.isdigit():
|
||||||
|
await cb.message.edit_text("🍃 Что-то пошло не так.", reply_markup=return_menu)
|
||||||
|
return
|
||||||
|
|
||||||
|
order = await order_repo.get_order_by_id(session, int(order_id))
|
||||||
|
if not order or order.customer != cb.from_user.id:
|
||||||
|
await cb.message.edit_text("🍃 Что-то пошло не так.", reply_markup=return_menu)
|
||||||
|
return
|
||||||
|
|
||||||
|
await cb.message.edit_text("<b>👤 Укажите ваше имя:</b>", reply_markup=return_menu)
|
||||||
|
|
||||||
|
ctx = CheckoutContext(orig_msg=cb.message, order_id=order.id)
|
||||||
|
await state.set_state(CheckoutStorage.name)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CheckoutStorage.name)
|
||||||
|
async def checkout_name(msg: Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
ctx: CheckoutContext = data.get("ctx", CheckoutContext)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
ctx.name = msg.text
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
await ctx.orig_msg.edit_text(
|
||||||
|
f"<i>👤 Имя: {ctx.name}</i>\n\n<b>📱 Укажите ваш номер телефона:</b>",
|
||||||
|
reply_markup=return_menu,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.set_state(CheckoutStorage.phone)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CheckoutStorage.phone)
|
||||||
|
async def checkout_phone(msg: Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
ctx: CheckoutContext = data.get("ctx", CheckoutContext)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await msg.delete()
|
||||||
|
if not is_valid_phone(msg.text):
|
||||||
|
await ctx.orig_msg.edit_text(
|
||||||
|
f"❌ <i>{msg.text}</i> не является действительным номером, попробуйте ещё раз.",
|
||||||
|
reply_markup=return_menu,
|
||||||
|
)
|
||||||
|
await state.set_state(CheckoutStorage.phone)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.phone = msg.text
|
||||||
|
await ctx.orig_msg.edit_text(
|
||||||
|
f"<i>👤 Имя: {ctx.name}</i>\n<i>📱 Номер телефона: {ctx.phone}</i>\n\n<b>📍 Укажите ваш адрес (город, улица):</b>",
|
||||||
|
reply_markup=return_menu,
|
||||||
|
)
|
||||||
|
await state.set_state(CheckoutStorage.address)
|
||||||
|
await state.set_data({"ctx": ctx})
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CheckoutStorage.address)
|
||||||
|
async def checkout_address(msg: Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
ctx: CheckoutContext = data.get("ctx", CheckoutContext)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await msg.delete()
|
||||||
|
|
||||||
|
ctx.address = msg.text
|
||||||
|
await ctx.orig_msg.edit_text(
|
||||||
|
"<b>‼️ Проверьте правильность ваших данных.</b>\n\n"
|
||||||
|
"━━━━━━━━━━━━━━\n\n"
|
||||||
|
f"<i>👤 Имя: {ctx.name}</i>\n"
|
||||||
|
f"<i>📱 Номер телефона: {ctx.phone}</i>\n"
|
||||||
|
f"<i>📍 Ваш адрес: {ctx.address}</i>\n\n"
|
||||||
|
"━━━━━━━━━━━━━━",
|
||||||
|
reply_markup=order_specs_confirmation(ctx.order_id),
|
||||||
|
) # TODO: State-driven Payment System.
|
||||||
37
handlers/client/menu.py
Normal file
37
handlers/client/menu.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import CallbackQuery, Message
|
||||||
|
from aiogram.filters import Command
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from misc.kb import main_menu_kb
|
||||||
|
from services.orders import OrderService
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("start"))
|
||||||
|
async def main_menu(
|
||||||
|
msg: Message, state: FSMContext, session: AsyncSession, order_service: OrderService
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cart_items = await order_service.get_cart_items_count(session, msg.from_user.id)
|
||||||
|
await msg.answer(
|
||||||
|
"hii!", reply_markup=main_menu_kb(cart_items)
|
||||||
|
) # TODO: Write a welcome message
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("menu:"))
|
||||||
|
async def main_menu_cb(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
cart_items = await order_service.get_cart_items_count(session, cb.from_user.id)
|
||||||
|
await cb.message.edit_text(
|
||||||
|
"hii!", reply_markup=main_menu_kb(cart_items)
|
||||||
|
) # TODO: Write a welcome message
|
||||||
164
handlers/client/products.py
Normal file
164
handlers/client/products.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import os
|
||||||
|
from re import Match
|
||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import CallbackQuery, FSInputFile
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from config import PAGE_SIZE
|
||||||
|
from misc.kb.client import (
|
||||||
|
render_product_interactions,
|
||||||
|
render_products,
|
||||||
|
)
|
||||||
|
from misc.texts import get_product_description
|
||||||
|
from repositories.categories import CategoriesRepository
|
||||||
|
from repositories.products import ProductRepository
|
||||||
|
from services.orders import OrderService
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
F.data.regexp(r"products:(?P<category_id>.*):(?P<page_id>\d+)").as_(
|
||||||
|
"pagination_match"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
async def products_pagination(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
pagination_match: Match[str],
|
||||||
|
session: AsyncSession,
|
||||||
|
products_repo: ProductRepository,
|
||||||
|
categories_repo: CategoriesRepository,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
pagination_data = pagination_match.groupdict()
|
||||||
|
category_id = int(pagination_data.get("category_id"))
|
||||||
|
category = await categories_repo.get_category_by_id(session, category_id)
|
||||||
|
page_id = int(pagination_data.get("page_id", 0))
|
||||||
|
|
||||||
|
products = await products_repo.get_product_by_category(
|
||||||
|
session, category_id, limit=PAGE_SIZE + 1, offset=page_id * PAGE_SIZE
|
||||||
|
)
|
||||||
|
products_len = len(products)
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("product:"))
|
||||||
|
async def product_card(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
products_repo: ProductRepository,
|
||||||
|
order_service: OrderService,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
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 product.file_id:
|
||||||
|
await cb.message.delete()
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await cb.message.delete()
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not msg.photo:
|
||||||
|
return # TODO: Add logging
|
||||||
|
|
||||||
|
await products_repo.add_product_file_id(
|
||||||
|
session, product_id, file_id=msg.photo[-1].file_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("cart_action:"))
|
||||||
|
async def add_to_cart(
|
||||||
|
cb: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
session: AsyncSession,
|
||||||
|
order_service: OrderService,
|
||||||
|
products_repo: ProductRepository,
|
||||||
|
):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
action = cb.data.split(":")[1]
|
||||||
|
product_id = int(cb.data.split(":")[2])
|
||||||
|
product = await products_repo.get_product_by_id(session, product_id)
|
||||||
|
show_cart = True
|
||||||
|
|
||||||
|
if action == "add":
|
||||||
|
quantity = await order_service.add_to_cart(
|
||||||
|
session, customer=cb.from_user.id, product_id=product_id
|
||||||
|
)
|
||||||
|
elif action == "remove":
|
||||||
|
quantity = await order_service.remove_from_cart(
|
||||||
|
session, customer=cb.from_user.id, product_id=product_id
|
||||||
|
)
|
||||||
|
show_cart = bool(quantity)
|
||||||
|
else:
|
||||||
|
show_cart = False
|
||||||
|
quantity = 0
|
||||||
|
await order_service.clear_cart_item(
|
||||||
|
session, customer=cb.from_user.id, product_id=product_id
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await cb.message.edit_reply_markup(
|
||||||
|
reply_markup=render_product_interactions(
|
||||||
|
product_id=product_id,
|
||||||
|
category_id=product.category_id,
|
||||||
|
show_cart=show_cart,
|
||||||
|
cart_amount=quantity,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
await cb.answer("🍃")
|
||||||
|
raise e
|
||||||
20
handlers/client/security.py
Normal file
20
handlers/client/security.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.enums import MessageOriginType
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
from config import VERIFIED_ACCOUNTS
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.forward_origin)
|
||||||
|
async def security_check(msg: Message):
|
||||||
|
if (
|
||||||
|
msg.forward_origin.type == MessageOriginType.USER
|
||||||
|
and msg.forward_origin.sender_user.id in VERIFIED_ACCOUNTS
|
||||||
|
):
|
||||||
|
text = "🍃 Вы общаетесь с оффициальным аккаунтом Екатерины."
|
||||||
|
else:
|
||||||
|
text = "<b>❌ Вы общаетесь с подставным лицом. Мы советуем закончить диалог с мошенником как можно быстрее.</b>"
|
||||||
|
|
||||||
|
await msg.answer(text)
|
||||||
8
main.py
8
main.py
@@ -5,7 +5,7 @@ from aiogram import Bot, Dispatcher
|
|||||||
from aiogram.client.default import DefaultBotProperties
|
from aiogram.client.default import DefaultBotProperties
|
||||||
|
|
||||||
from config import BOT_TOKEN
|
from config import BOT_TOKEN
|
||||||
from handlers import init_handlers
|
from handlers import admins_routers, client_routers
|
||||||
|
|
||||||
from middlewares.di import DIMiddleware
|
from middlewares.di import DIMiddleware
|
||||||
from middlewares.repository import RepositoryMiddleware
|
from middlewares.repository import RepositoryMiddleware
|
||||||
@@ -61,7 +61,11 @@ async def main():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
init_handlers(dp)
|
for ar in admins_routers:
|
||||||
|
dp.include_router(ar)
|
||||||
|
|
||||||
|
for cr in client_routers:
|
||||||
|
dp.include_router(cr)
|
||||||
|
|
||||||
await dp.start_polling(bot)
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ 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(
|
||||||
*,
|
*,
|
||||||
@@ -15,6 +19,9 @@ def pagination_row(
|
|||||||
) -> list[InlineKeyboardButton]:
|
) -> list[InlineKeyboardButton]:
|
||||||
row = []
|
row = []
|
||||||
|
|
||||||
|
if not (prev_cb or next_cb):
|
||||||
|
return row
|
||||||
|
|
||||||
if prev_cb:
|
if prev_cb:
|
||||||
row.append(InlineKeyboardButton(text="⬅️", callback_data=prev_cb))
|
row.append(InlineKeyboardButton(text="⬅️", callback_data=prev_cb))
|
||||||
|
|
||||||
@@ -161,7 +168,7 @@ def render_cart(
|
|||||||
) -> InlineKeyboardMarkup:
|
) -> InlineKeyboardMarkup:
|
||||||
btns = [
|
btns = [
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text=f"{cart_item.quantity} | {cart_item.name} | {cart_item.total}₽",
|
text=f"🛒 {cart_item.quantity} | {cart_item.name} | {cart_item.total}₽",
|
||||||
callback_data=f"product:{cart_item.product_id}",
|
callback_data=f"product:{cart_item.product_id}",
|
||||||
)
|
)
|
||||||
for cart_item in cart_items
|
for cart_item in cart_items
|
||||||
@@ -184,4 +191,33 @@ def render_cart(
|
|||||||
]
|
]
|
||||||
+ [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()
|
||||||
|
|
||||||
|
|
||||||
|
def order_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardBuilder(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🟢 Подтвердить заказ",
|
||||||
|
callback_data=f"confirm_order:{order_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="❌ Отменить", callback_data="menu:main")],
|
||||||
|
]
|
||||||
|
).as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
def order_specs_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
||||||
|
return InlineKeyboardBuilder(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="🟢 Перейти к оплате",
|
||||||
|
callback_data=f"payment:{order_id}",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[InlineKeyboardButton(text="❌", callback_data="menu:main")],
|
||||||
|
]
|
||||||
).as_markup()
|
).as_markup()
|
||||||
|
|||||||
7
misc/states.py
Normal file
7
misc/states.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from aiogram.fsm.state import StatesGroup, State
|
||||||
|
|
||||||
|
|
||||||
|
class CheckoutStorage(StatesGroup):
|
||||||
|
name = State()
|
||||||
|
phone = State()
|
||||||
|
address = State()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from db.models import Product
|
from db.models import Product
|
||||||
|
from dto.cart import CartDTO
|
||||||
|
|
||||||
|
|
||||||
def get_product_description(product: Product) -> str:
|
def get_product_description(product: Product) -> str:
|
||||||
@@ -7,3 +8,18 @@ def get_product_description(product: Product) -> str:
|
|||||||
output += f"\n\n📜 {product.description}"
|
output += f"\n\n📜 {product.description}"
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_order_item_list(cart: CartDTO):
|
||||||
|
text = "🛍 Состав заказа:\n"
|
||||||
|
for i, product in enumerate(cart.items):
|
||||||
|
text += f"\n{i+1}. <b>{product.name}</b>\n•\t<i>{product.quantity} × {product.price} = {product.total}₽</i>\n"
|
||||||
|
|
||||||
|
text += f"\n━━━━━━━━━━━━━━\n💰 Итого: {cart.total}₽"
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
# def get_order_notification(cart: CartDTO):
|
||||||
|
# text
|
||||||
|
# FIXME
|
||||||
|
|||||||
10
misc/utils.py
Normal file
10
misc/utils.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import phonenumbers
|
||||||
|
from phonenumbers import NumberParseException
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_phone(phone: str, region="RU") -> bool:
|
||||||
|
try:
|
||||||
|
parsed = phonenumbers.parse(phone, region)
|
||||||
|
return phonenumbers.is_valid_number(parsed)
|
||||||
|
except NumberParseException:
|
||||||
|
return False
|
||||||
@@ -156,7 +156,7 @@ class OrderService:
|
|||||||
|
|
||||||
return await self.order_items_repo.get_items_count(session, order_id=order.id)
|
return await self.order_items_repo.get_items_count(session, order_id=order.id)
|
||||||
|
|
||||||
async def build_cart_dto(
|
async def build_pagination_cart_dto(
|
||||||
self,
|
self,
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
*,
|
*,
|
||||||
@@ -190,4 +190,30 @@ class OrderService:
|
|||||||
cart_items.append(cart_item)
|
cart_items.append(cart_item)
|
||||||
total += cart_item.total
|
total += cart_item.total
|
||||||
|
|
||||||
|
return CartDTO(items=cart_items, total=total, order_id=order.id)
|
||||||
|
|
||||||
|
async def build_full_cart_dto(self, session: AsyncSession, customer: int):
|
||||||
|
order = await self.order_repo.get_draft_order_by_user(session, customer)
|
||||||
|
|
||||||
|
cart_items = []
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
if not order:
|
||||||
return CartDTO(items=cart_items, total=total)
|
return CartDTO(items=cart_items, total=total)
|
||||||
|
|
||||||
|
for cart_item in order.items:
|
||||||
|
if cart_item.quantity <= 0:
|
||||||
|
await self.order_items_repo.delete_item(session, item_id=cart_item.id)
|
||||||
|
continue
|
||||||
|
|
||||||
|
dto = CartItemDTO(
|
||||||
|
product_id=cart_item.product.id,
|
||||||
|
name=cart_item.product.name,
|
||||||
|
quantity=cart_item.quantity,
|
||||||
|
price=cart_item.product.price,
|
||||||
|
)
|
||||||
|
|
||||||
|
cart_items.append(dto)
|
||||||
|
total += dto.total
|
||||||
|
|
||||||
|
return CartDTO(cart_items, total=total, order_id=order.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user