diff --git a/dto/control.py b/dto/control.py new file mode 100644 index 0000000..5552355 --- /dev/null +++ b/dto/control.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass + +from aiogram.types import Message + + +@dataclass +class EditProductContext: + product_id: int + mode: str + msg: Message diff --git a/handlers/admins/__init__.py b/handlers/admins/__init__.py index df4f58b..5e7a61d 100644 --- a/handlers/admins/__init__.py +++ b/handlers/admins/__init__.py @@ -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] diff --git a/handlers/admins/inline_mode.py b/handlers/admins/inline_mode.py index ca88765..3549bdd 100644 --- a/handlers/admins/inline_mode.py +++ b/handlers/admins/inline_mode.py @@ -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 diff --git a/handlers/admins/menu.py b/handlers/admins/menu.py new file mode 100644 index 0000000..e178d4e --- /dev/null +++ b/handlers/admins/menu.py @@ -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) diff --git a/handlers/admins/product_mgmt.py b/handlers/admins/product_mgmt.py new file mode 100644 index 0000000..73c9db0 --- /dev/null +++ b/handlers/admins/product_mgmt.py @@ -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"✍️ Введите {product_editing_mapping.get(mode)}", + 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"✍️ Введите {product_editing_mapping.get(mode)}", + 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) diff --git a/handlers/client/__init__.py b/handlers/client/__init__.py index 7cc4294..71b8f95 100644 --- a/handlers/client/__init__.py +++ b/handlers/client/__init__.py @@ -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, ] diff --git a/handlers/client/cart.py b/handlers/client/cart.py index 66e50fa..70a776c 100644 --- a/handlers/client/cart.py +++ b/handlers/client/cart.py @@ -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)), ), ) diff --git a/handlers/client/catalogue.py b/handlers/client/catalogue.py index 29ea8dc..2befea1 100644 --- a/handlers/client/catalogue.py +++ b/handlers/client/catalogue.py @@ -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", diff --git a/handlers/client/checkout.py b/handlers/client/checkout.py index 0907cf5..24dfcbe 100644 --- a/handlers/client/checkout.py +++ b/handlers/client/checkout.py @@ -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( - "🟢 Ваш заказ успешно отправлен!\n" - "📦 Мы свяжемся с вами для уточнения деталей в ближайшее время.\n\n" - "‼️ Чтобы убедиться, что вы общаетесь с доверенным контактом ШВЕЙТЕХЦЕНТР, перешлите любое сообщение человека в этот чат.", - 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( + "🟢 Ваш заказ успешно отправлен!\n" + "📦 Мы свяжемся с вами для уточнения деталей в ближайшее время.\n\n" + "‼️ Чтобы убедиться, что вы общаетесь с доверенным контактом ШВЕЙТЕХЦЕНТР, перешлите любое сообщение человека в этот чат.", + reply_markup=return_menu, + ) + except Exception as e: await cb.message.edit_text( "🍃 Что-то пошло не так, повторите попытку позже...", diff --git a/handlers/client/invoices.py b/handlers/client/invoices.py index d29c134..b60a937 100644 --- a/handlers/client/invoices.py +++ b/handlers/client/invoices.py @@ -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 diff --git a/handlers/client/menu.py b/handlers/client/menu.py index b0425fc..836d110 100644 --- a/handlers/client/menu.py +++ b/handlers/client/menu.py @@ -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, diff --git a/handlers/client/products.py b/handlers/client/products.py index e06514d..ca0eb5b 100644 --- a/handlers/client/products.py +++ b/handlers/client/products.py @@ -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, diff --git a/handlers/client/search.py b/handlers/client/search.py new file mode 100644 index 0000000..f864a91 --- /dev/null +++ b/handlers/client/search.py @@ -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" + ), + ) diff --git a/misc/kb/admins.py b/misc/kb/admins.py index d70db04..6290adb 100644 --- a/misc/kb/admins.py +++ b/misc/kb/admins.py @@ -1,12 +1,26 @@ -from typing import Optional +from typing import Optional, Callable from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder -placeholder_kb = InlineKeyboardBuilder( - [[InlineKeyboardButton(text="🍃", callback_data="...")]] +main_menu: InlineKeyboardMarkup = InlineKeyboardBuilder( + [ + [InlineKeyboardButton(text="📦 Изменить товары", callback_data="cat:root")], + [ + InlineKeyboardButton( + text="📜 Изменить категории", callback_data="edit:categories" + ) + ], + # [InlineKeyboardButton(text="", callback_data="")], + ] ).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( user_id: int, has_mention: bool, mention: Optional[str] = None ) -> InlineKeyboardMarkup: @@ -44,3 +58,26 @@ def verify_payment(invoice_id: int): ] ] ).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() diff --git a/misc/kb/client.py b/misc/kb/client.py index 6f1e502..ee143bf 100644 --- a/misc/kb/client.py +++ b/misc/kb/client.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Callable from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder @@ -6,10 +6,6 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from db.models import Category, Product from dto.cart import CartItemDTO -return_menu = InlineKeyboardBuilder( - [[InlineKeyboardButton(text="⬅️ В меню", callback_data="menu:main")]] -).as_markup() - def pagination_row( *, @@ -61,11 +57,13 @@ def render_category( children: list[Category], parent_id: Optional[int] = None, show_menu: bool = False, + *, + category_cb_factory: Callable[[Category], str], ) -> InlineKeyboardMarkup: btns = [ InlineKeyboardButton( text=category.name, - callback_data=f"cat:{category.id}", + callback_data=category_cb_factory(category), ) for category in children ] @@ -88,35 +86,37 @@ def render_category( def render_products( products: list[Product], *, - parent_id: int, - cat_id: int, - page: int = 0, - show_prev: Optional[bool] = False, - show_next: Optional[bool] = False, + product_cb_factory: Callable[[Product], str], + back_cb: Optional[str] = None, + prev_cb: Optional[str] = None, + next_cb: Optional[str] = None, + columns: int = 2, ) -> InlineKeyboardMarkup: - btns = [ - InlineKeyboardButton(text=product.name, callback_data=f"product:{product.id}") - for product in products - ] + builder = InlineKeyboardBuilder() - nav = pagination_row( - page=page, - 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, - ) + if back_cb: + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=back_cb)) - return InlineKeyboardBuilder( - [ - [ - InlineKeyboardButton( - text="⬅️ Назад", - callback_data=f"cat:{parent_id}", - ) - ] + for i in range(0, len(products), columns): + row = [ + InlineKeyboardButton( + text=p.name, + callback_data=product_cb_factory(p), + ) + for p in products[i : i + columns] ] - + [btns[i : i + 2] for i in range(0, len(products), 2)] - + [nav] - ).as_markup() + builder.row(*row) + + 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( @@ -165,6 +165,7 @@ def render_cart( page: int = 0, show_prev: Optional[bool] = False, show_next: Optional[bool] = False, + show_purchase: Optional[bool] = False, ) -> InlineKeyboardMarkup: btns = [ InlineKeyboardButton( @@ -180,19 +181,25 @@ def render_cart( next_cb=f"cart:{page + 1}" if show_next else None, ) - return InlineKeyboardBuilder( + markup: list[list[InlineKeyboardButton]] = ( [ [ InlineKeyboardButton( text="⬅️ Назад", callback_data="menu:main", ) - ] + ], ] + [btns[i : i + 2] for i in range(0, len(cart_items), 2)] + [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: diff --git a/misc/kb/common.py b/misc/kb/common.py new file mode 100644 index 0000000..fb147ee --- /dev/null +++ b/misc/kb/common.py @@ -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() diff --git a/misc/states.py b/misc/states.py index 6d86e95..b5c1eac 100644 --- a/misc/states.py +++ b/misc/states.py @@ -10,3 +10,11 @@ class CheckoutStorage(StatesGroup): class PaymentStorage(StatesGroup): refresh = State() + + +class SearchStorage(StatesGroup): + query = State() + + +class AdminControlStorage(StatesGroup): + edit_product = State() diff --git a/misc/texts.py b/misc/texts.py index d4f9417..07be491 100644 --- a/misc/texts.py +++ b/misc/texts.py @@ -1,11 +1,17 @@ from db.models import Product from dto.cart import CartDTO +product_editing_mapping = { + "name": "новое название", + "description": "новое описание", + "price": "новую цену", +} + def get_product_description(product: Product) -> str: - output = f"📦 {product.name}\n\n🏷️ {product.price}₽" + output = f"📦 {product.name}\n\n🏷️ {product.price}₽" if product.description: - output += f"\n\n📜 {product.description}" + output += f"\n\n📜 {product.description}" return output diff --git a/repositories/invoices.py b/repositories/invoices.py index bb3afac..3a2d20c 100644 --- a/repositories/invoices.py +++ b/repositories/invoices.py @@ -1,6 +1,6 @@ from typing import Optional -from sqlalchemy import select, update +from sqlalchemy import select from db.models import Invoice from sqlalchemy.ext.asyncio import AsyncSession @@ -37,19 +37,15 @@ class InvoiceRepository: async def update_invoice_msg( self, session: AsyncSession, *, invoice_id: int, inline_message_id: int ): - stmt = ( - update(Invoice.__table__) - .where(Invoice.id == invoice_id) - .values(inline_message_id=inline_message_id) - ) - await session.execute(stmt) + invoice = await self.get_invoice_by_id(session, invoice_id) + invoice.inline_message_id = inline_message_id + + await session.commit() async def update_invoice_status( self, session: AsyncSession, *, invoice_id: int, status: InvoiceStatus ): - stmt = ( - update(Invoice.__table__) - .where(Invoice.id == invoice_id) - .values(status=status) - ) - await session.execute(stmt) + invoice = await self.get_invoice_by_id(session, invoice_id) + invoice.status = status + + await session.commit() diff --git a/repositories/order_items.py b/repositories/order_items.py index 3f987af..2930e1c 100644 --- a/repositories/order_items.py +++ b/repositories/order_items.py @@ -1,5 +1,5 @@ from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import delete, func, select, update +from sqlalchemy import delete, func, select from typing import Optional from db.models.orders import Order, OrderItem, OrderStatus @@ -55,13 +55,9 @@ class OrderItemRepository: async def update_order_item_quantity( self, session: AsyncSession, order_item_id: int, *, quantity: int = 0 ): - stmt = ( - update(OrderItem.__table__) - .where(OrderItem.id == order_item_id) - .values(quantity=quantity) - ) + item = await self.get_item_by_id(session, order_item_id) + item.quantity = quantity - await session.execute(stmt) await session.commit() async def create_order_item( @@ -82,7 +78,7 @@ class OrderItemRepository: return order_item 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.commit() diff --git a/repositories/orders.py b/repositories/orders.py index 286bacc..f3d8bdb 100644 --- a/repositories/orders.py +++ b/repositories/orders.py @@ -48,13 +48,21 @@ class OrderRepository: return order 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.commit() 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.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() diff --git a/repositories/products.py b/repositories/products.py index 6da6d05..9efaf34 100644 --- a/repositories/products.py +++ b/repositories/products.py @@ -1,7 +1,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, update +from sqlalchemy.sql.expression import func from typing import Optional +from config import PAGE_SIZE from db.models import Product @@ -31,10 +33,59 @@ class ProductRepository: self, session: AsyncSession, product_id: int, *, file_id: str ) -> None: stmt = ( - update(Product.__table__) + update(Product) .where(Product.id == product_id) .where(Product.file_id is None) .values(file_id=file_id) ) await session.execute(stmt) 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 diff --git a/services/orders.py b/services/orders.py index 90a4b08..729c77f 100644 --- a/services/orders.py +++ b/services/orders.py @@ -52,7 +52,7 @@ class OrderService: session, order_item.id, quantity=order_item.quantity + quantity ) - return order_item.quantity + quantity + return order_item.quantity async def remove_from_cart( self, @@ -162,7 +162,9 @@ class OrderService: 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) cart_items = []