diff --git a/.github/workflows/python-poetry-code-quality.yml b/.github/workflows/python-poetry-code-quality.yml
new file mode 100644
index 0000000..3b66ff6
--- /dev/null
+++ b/.github/workflows/python-poetry-code-quality.yml
@@ -0,0 +1,66 @@
+name: code-quality
+
+on:
+ push:
+
+jobs:
+ pylint:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.13"]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v3
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: install poetry
+ run: pipx install poetry
+
+ - name: Check pyproject.toml validity
+ run: poetry check --no-interaction
+
+ - name: Install deps
+ if: steps.cache-deps.cache-hit != 'true'
+ run: |
+ poetry config virtualenvs.in-project true
+ poetry install --no-interaction
+
+ - name: Analysing the code with pylint
+ run: poetry run pylint bot
+
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: [ "3.13" ]
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v3
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install poetry
+ run: pipx install poetry
+
+ - name: Check pyproject.toml validity
+ run: poetry check --no-interaction
+
+ - name: Install deps
+ if: steps.cache-deps.cache-hit != 'true'
+ run: |
+ poetry config virtualenvs.in-project true
+ poetry install --no-interaction
+
+ - name: Test with pytest
+ run: |
+ poetry run pytest -v --cov=bot
\ No newline at end of file
diff --git a/README.md b/README.md
index 0b2a480..a953f6a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-
-[](https://github.com/NikitosKey/alcounting_bot/actions/workflows/main.yml)
+[](https://github.com/NikitosKey/alcounting_bot/actions/workflows/python-poetry-code-quality.yml)
# Alcounting Telegram Bot
@@ -9,7 +8,8 @@ A bot designed to help manage parties and prevent organizers from going into the
- [Description](#description)
- [Guide](#Guide)
-- [User Documentation](#user-documentation)
+- [Documentation](#documentation)
+- [Contributing](#contributing)
## Description
diff --git a/bot/__init__.py b/bot/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/bot/__main__.py b/bot/__main__.py
index c728dab..ae75550 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -1,9 +1,13 @@
+"""
+This module initializes and runs the bot application.
+"""
+
import os
import logging
from telegram import Update
from telegram.ext import Application
-from handlers import register_handlers
+from bot.handlers import register_handlers
# Set up logging
logging.basicConfig(
@@ -18,12 +22,12 @@
def main() -> None:
"""Allow running a bot."""
# Create the Application and pass it your bot's token.
- application = Application.builder().token(os.getenv('BOT_TOKEN')).build()
+ application = Application.builder().token(os.getenv("BOT_TOKEN")).build()
# Register all handlers
register_handlers(application)
- # Run the bot until the user presses Ctrl-C
+ # Run the bot
application.run_polling(allowed_updates=Update.ALL_TYPES)
diff --git a/bot/database/__init__.py b/bot/database/__init__.py
index 977ae2f..d4c7cd7 100644
--- a/bot/database/__init__.py
+++ b/bot/database/__init__.py
@@ -1,11 +1,8 @@
+"""Module for database operations."""
+
from bot.database.database import Database
from bot.database.user import User
from bot.database.product import Product
from bot.database.order import Order
-__all__ = [
- 'Database',
- 'User',
- 'Product',
- 'Order'
-]
\ No newline at end of file
+__all__ = ["Database", "User", "Product", "Order"]
diff --git a/bot/database/database.py b/bot/database/database.py
index 2acc467..289cb23 100644
--- a/bot/database/database.py
+++ b/bot/database/database.py
@@ -1,215 +1,212 @@
+"""Module for working with the database"""
+
+import logging
+import os
import sqlite3
-from bot.database.user import User
-from bot.database.product import Product
from bot.database.order import Order
+from bot.database.product import Product
+from bot.database.user import User
+
+DATABASE_DEFAULT_PATH = "data/database.db"
-database_path = '../data/database.db'
class Database:
- def __init__(self):
- pass
+ """Class for working with sqlite3 database"""
- def create_tables(self) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
+ def __init__(self, path: str = DATABASE_DEFAULT_PATH):
+ self.fix_path(path)
- # --- создаём таблицу с меню ---
- cur.execute("""
+ self.conn = sqlite3.connect(path)
+ self.cur = self.conn.cursor()
+
+ self.cur.execute(
+ """
CREATE TABLE IF NOT EXISTS Products (
- name TEXT NOT NULL PRIMARY KEY,
- description TEXT NOT NULL,
- price INTEGER
+ name TEXT NOT NULL PRIMARY KEY,
+ description TEXT NOT NULL,
+ price INTEGER
);
- """)
-
- cur.execute("""
+ """
+ )
+ self.cur.execute(
+ """
CREATE TABLE IF NOT EXISTS Users (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- type TEXT NOT NULL
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ type TEXT NOT NULL
);
- """)
-
- # --- создаём таблицу с покупками ---
- cur.execute("""
+ """
+ )
+ self.cur.execute(
+ """
CREATE TABLE IF NOT EXISTS Orders (
- date TEXT NOT NULL PRIMARY KEY,
- product TEXT NOT NULL,
- customer_id INTEGER,
- barman_id INTEGER,
- status TEXT NOT NULL,
- FOREIGN KEY (product) REFERENCES products(product),
- FOREIGN KEY (customer_id) REFERENCES user_base(id),
- FOREIGN KEY (barman_id) REFERENCES user_base(id)
+ date TEXT NOT NULL PRIMARY KEY,
+ product TEXT NOT NULL,
+ customer_id INTEGER,
+ barman_id INTEGER,
+ status TEXT NOT NULL,
+ FOREIGN KEY (product) REFERENCES Products(name),
+ FOREIGN KEY (customer_id) REFERENCES Users(id),
+ FOREIGN KEY (barman_id) REFERENCES Users(id)
);
- """)
- conn.commit()
- conn.close()
+ """
+ )
+ self.conn.commit()
+ logging.getLogger(__name__).debug("Database created")
- def insert_order(self, order: Order) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("""
- INSERT INTO orders (date, product, customer_id, barman_id, status)
- VALUES (?, ?, ?, ?, ?)""",
- (order.date, order.product, order.customer_id, order.barman_id, order.status))
- conn.commit()
- conn.close()
+ def __del__(self):
+ self.conn.close()
- def insert_product(self, product: Product) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("""
- INSERT OR IGNORE INTO products (name, description, price)
- values (?, ?, ?)""",
- (product.name, product.description, product.price)
- )
- conn.commit()
- conn.close()
+ def fix_path(self, path: str) -> None:
+ """Create a directory and file if they do not exist"""
+ if not os.path.exists(path) and path != ":memory:":
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w", encoding="utf-8") as file:
+ file.close()
def insert_user(self, user: User) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("""
- INSERT OR IGNORE INTO Users values (?, ?, ?)""",
- (user.id, user.name, user.type)
- )
- conn.commit()
- conn.close()
+ """Insert a new user into the Users table."""
+ if user is None:
+ raise ValueError("User cannot be None")
+ self.cur.execute(
+ """
+ INSERT OR IGNORE INTO Users (id, name, type)
+ VALUES (?, ?, ?)""",
+ (user.id, user.name, user.type),
+ )
+ self.conn.commit()
- def delete_order(self, order: Order) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('DELETE FROM Orders WHERE date = ?', (order.date,))
- conn.commit()
- conn.close()
+ def insert_product(self, product: Product) -> None:
+ """Insert a new product into the Products table."""
+ if product is None:
+ raise ValueError("Product cannot be None")
+ self.cur.execute(
+ """
+ INSERT OR IGNORE INTO Products (name, description, price)
+ VALUES (?, ?, ?)""",
+ (product.name, product.description, product.price),
+ )
+ self.conn.commit()
+
+ def insert_order(self, order: Order) -> None:
+ """Insert a new order into the Orders table."""
+ if order is None:
+ raise ValueError("Order cannot be None")
+ self.cur.execute(
+ """
+ INSERT INTO Orders (date, product, customer_id, barman_id, status)
+ VALUES (?, ?, ?, ?, ?)""",
+ (
+ order.date,
+ order.product,
+ order.customer_id,
+ order.barman_id,
+ order.status,
+ ),
+ )
+ self.conn.commit()
def delete_user(self, user: User) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('DELETE FROM Users WHERE id = ?', (user.id,))
- conn.commit()
- conn.close()
+ """Delete a user from the Users table."""
+ if user is None:
+ raise ValueError("User cannot be None")
+ self.cur.execute("DELETE FROM Users WHERE id = ?", (user.id,))
+ self.conn.commit()
def delete_product(self, product: Product) -> None:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('DELETE FROM Products WHERE name = ?', (product.name,))
- conn.commit()
- conn.close()
-
- def get_all_products(self):
- # Получение списка списков из бд
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('SELECT * FROM Products')
- rows: list = cur.fetchall()
- conn.close()
- if rows is None:
- return None
- # Конвертирование в список Products
- result: list[Product] = []
- for row in rows:
- result.append(Product(row[0], row[1], row[2]))
-
- return result
-
- def get_all_users(self):
- # Получение списка списков из бд
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('SELECT * FROM Users')
- rows: list = cur.fetchall()
- conn.close()
- if rows is None:
- return None
- # Конвертирование в список Products
- result: list[User] = []
- for row in rows:
- result.append(User(row[0], row[1], row[2]))
-
- return result
-
- def get_all_orders(self):
- # Получение списка списков из бд
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('SELECT * FROM Orders')
- rows: list = cur.fetchall()
- conn.close()
- if rows is None:
- return None
- # Конвертирование в список Products
- result: list[Order] = []
- for row in rows:
- result.append(Order(row[0], row[1], row[2], row[3], row[4]))
-
- return result
+ """Delete a product from the Products table."""
+ if product is None:
+ raise ValueError("Product cannot be None")
+ self.cur.execute("DELETE FROM Products WHERE name = ?", (product.name,))
+ self.conn.commit()
- def get_user_by_id(self, user_id: int) -> User:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('SELECT * FROM Users WHERE id = ?', (user_id,))
- found = cur.fetchone()
- conn.close()
- if found is None:
- return None
- return User(found[0], found[1], found[2])
+ def delete_order(self, order: Order) -> None:
+ """Delete an order from the Orders table."""
+ if order is None:
+ raise ValueError("Order cannot be None")
+ self.cur.execute("DELETE FROM Orders WHERE date = ?", (order.date,))
+ self.conn.commit()
+
+ def get_all_products(self) -> list[Product]:
+ """Retrieve all products from the Products table."""
+ self.cur.execute("SELECT * FROM Products")
+ rows = self.cur.fetchall()
+ return [Product(row[0], row[1], row[2]) for row in rows]
+
+ def get_all_users(self) -> list[User]:
+ """Retrieve all users from the Users table."""
+ self.cur.execute("SELECT * FROM Users")
+ rows = self.cur.fetchall()
+ return [User(row[0], row[1], row[2]) for row in rows]
+
+ def get_all_orders(self) -> list[Order]:
+ """Retrieve all orders from the Orders table."""
+ self.cur.execute("SELECT * FROM Orders")
+ rows = self.cur.fetchall()
+ return [Order(row[0], row[1], row[2], row[3], row[4]) for row in rows]
+ def get_user_by_id(self, user_id: int) -> User:
+ """Retrieve a user by their ID from the Users table."""
+ self.cur.execute("SELECT * FROM Users WHERE id = ?", (user_id,))
+ found = self.cur.fetchone()
+ return User(found[0], found[1], found[2]) if found else None
def get_product_by_name(self, name: str) -> Product:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute('SELECT * FROM Products WHERE name = ?', (name,))
- found = cur.fetchone()
- if found is None:
- return None
- conn.close()
- return Product(found[0], found[1], found[2])
-
+ """Retrieve a product by its name from the Products table."""
+ self.cur.execute("SELECT * FROM Products WHERE name = ?", (name,))
+ found = self.cur.fetchone()
+ return Product(found[0], found[1], found[2]) if found else None
def get_order_by_date(self, order_date: str) -> Order:
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("SELECT * FROM Orders WHERE date = ?", (order_date,))
- found = cur.fetchone()
- conn.close()
- return Order(found[0], found[1], found[2], found[3], found[4])
-
-
- def get_orders_by_customer_id(self, id: int):
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("SELECT * FROM Orders WHERE customer_id = ?", (id,))
- rows: list = cur.fetchall()
- conn.close()
- if rows is None:
- return None
- # Конвертирование в список Products
- result: list[Order] = []
- for row in rows:
- result.append(Order(row[0], row[1], row[2], row[3], row[4]))
- return result
-
- def get_orders_queue(self):
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("SELECT * FROM Orders WHERE status = ?", ("размещён",))
- rows: list = cur.fetchall()
- conn.close()
- if rows is None:
- return None
- # Конвертирование в список Products
- result: list[Order] = []
- for row in rows:
- result.append(Order(row[0], row[1], row[2], row[3], row[4]))
- return result
-
- def update_order(self, order: Order):
- conn = sqlite3.connect(database_path)
- cur = conn.cursor()
- cur.execute("""
- UPDATE orders SET barman_id = ?, status = ? WHERE date = ?""", (order.barman_id, order.status, order.date,))
- conn.commit()
- conn.close()
+ """Retrieve an order by its date from the Orders table."""
+ self.cur.execute("SELECT * FROM Orders WHERE date = ?", (order_date,))
+ found = self.cur.fetchone()
+ return (
+ Order(found[0], found[1], found[2], found[3], found[4]) if found else None
+ )
+
+ def get_orders_by_customer_id(self, customer_id: int) -> list[Order]:
+ """Retrieve orders by customer ID from the Orders table."""
+ self.cur.execute("SELECT * FROM Orders WHERE customer_id = ?", (customer_id,))
+ rows = self.cur.fetchall()
+ return [Order(row[0], row[1], row[2], row[3], row[4]) for row in rows]
+
+ def get_orders_queue(self) -> list[Order]:
+ """Retrieve orders with status 'размещён' from the Orders table."""
+ self.cur.execute("SELECT * FROM Orders WHERE status = ?", ("размещён",))
+ rows = self.cur.fetchall()
+ return [Order(row[0], row[1], row[2], row[3], row[4]) for row in rows]
+
+ def update_user(self, user: User) -> None:
+ """Update a user in the Users table."""
+ if user is None:
+ raise ValueError("User cannot be None")
+ self.cur.execute(
+ """
+ UPDATE Users SET name = ?, type = ? WHERE id = ?""",
+ (user.name, user.type, user.id),
+ )
+ self.conn.commit()
+
+ def update_product(self, product: Product) -> None:
+ """Update a product in the Products table."""
+ if product is None:
+ raise ValueError("Product cannot be None")
+ self.cur.execute(
+ """
+ UPDATE Products SET description = ?, price = ? WHERE name = ?""",
+ (product.description, product.price, product.name),
+ )
+ self.conn.commit()
+
+ def update_order(self, order: Order) -> None:
+ """Update an order in the Orders table."""
+ if order is None:
+ raise ValueError("Order cannot be None")
+ self.cur.execute(
+ """
+ UPDATE Orders SET barman_id = ?, status = ? WHERE date = ?""",
+ (order.barman_id, order.status, order.date),
+ )
+ self.conn.commit()
diff --git a/bot/database/order.py b/bot/database/order.py
index fa6a52d..eaf2e51 100644
--- a/bot/database/order.py
+++ b/bot/database/order.py
@@ -1,52 +1,54 @@
-import datetime
+"""Module for the Order class."""
-from bot.database.product import Product
+from dataclasses import dataclass
+
+@dataclass
class Order:
+ """Class representing an order in the system."""
- def __init__(self, date, product, customer_id, barman_id, status) -> None:
- """self.id = id"""
- self.date: str = date
- self.product: str = product
- self.customer_id: int = customer_id
- self.barman_id: int = barman_id
- self.status: str = status
+ date: str
+ product: str
+ customer_id: int
+ barman_id: int
+ status: str
- if self.status not in ['размещён', 'завершён']:
- raise ValueError("Invalid status type")
+ def get_order_date(self) -> str:
+ """Get the date of the order."""
+ return self.date
- """def get_order_id(self) -> int:
- return self.id"""
+ def get_order_product(self) -> str:
+ """Get the product of the order."""
+ return self.product
def get_order_customer_id(self) -> int:
+ """Get the customer ID of the order."""
return self.customer_id
- def get_order_product(self) -> Product:
- return self.choice
-
- def get_order_date(self) -> datetime:
- return self.date
+ def get_order_barman_id(self) -> int:
+ """Get the barman ID of the order."""
+ return self.barman_id
def get_order_status(self) -> str:
+ """Get the status of the order."""
return self.status
- def get_order_barman_id(self) -> str:
- return self.barman_id
-
- """def set_order_id(self, val) -> None:
- self.id = val"""
-
- def set_order_customer_id(self, val) -> None:
- self.customer_id = val
+ def set_order_date(self, date: str):
+ """Set the date of the order."""
+ self.date = date
- def get_order_product(self, val) -> None:
- self.product = val
+ def set_order_product(self, product: str):
+ """Set the product of the order."""
+ self.product = product
- def set_order_date(self, val) -> None:
- self.date = val
+ def set_order_customer_id(self, customer_id: int):
+ """Set the customer ID of the order."""
+ self.customer_id = customer_id
- def set_order_status(self, val) -> None:
- self.status = val
+ def set_order_barman_id(self, barman_id: int):
+ """Set the barman ID of the order."""
+ self.barman_id = barman_id
- def set_order_barman_id(self, val) -> None:
- self.barman_id = val
+ def set_order_status(self, status: str):
+ """Set the status of the order."""
+ self.status = status
diff --git a/bot/database/product.py b/bot/database/product.py
index da2c025..e829953 100644
--- a/bot/database/product.py
+++ b/bot/database/product.py
@@ -1,27 +1,36 @@
+"""Module for the Product class."""
+
+from dataclasses import dataclass
+
+
+@dataclass
class Product:
+ """Class representing a product in the system."""
- def __init__(self, name, description, price) -> None:
- # self.tag = tag
- self.name = name
- # self.photo = photo
- self.description = description
- self.price = price
- # self.composition = composition
+ name: str
+ description: str
+ price: float
def get_name(self) -> str:
+ """Get the name of the product."""
return self.name
def get_price(self) -> float:
+ """Get the price of the product."""
return self.price
def get_description(self) -> str:
+ """Get the description of the product."""
return self.description
- def set_name(self, val) -> None:
+ def set_name(self, val: str) -> None:
+ """Set the name of the product."""
self.name = val
- def set_price(self, val) -> None:
+ def set_price(self, val: float) -> None:
+ """Set the price of the product."""
self.price = val
- def set_description(self, val) -> None:
+ def set_description(self, val: str) -> None:
+ """Set the description of the product."""
self.description = val
diff --git a/bot/database/user.py b/bot/database/user.py
index b7c30c2..7a9ce10 100644
--- a/bot/database/user.py
+++ b/bot/database/user.py
@@ -1,27 +1,43 @@
+"""Module for the User class."""
+
+from dataclasses import dataclass
+
+
+@dataclass
class User:
+ """Class representing a user in the system."""
- def __init__(self, id, name, type) -> None:
- self.id = id
- self.name = name
- self.type = type
+ id: int
+ name: str
+ type: str = "customer"
- if self.type not in ['barman', 'admin', 'customer']:
+ def __post_init__(self):
+ """Post-initialization processing."""
+ if self.type not in ["barman", "admin", "customer"]:
raise ValueError("Invalid user type")
def get_user_name(self) -> str:
+ """Get the name of the user."""
return self.name
- def get_user_id(self) -> str:
+ def get_user_id(self) -> int:
+ """Get the ID of the user."""
return self.id
def get_user_type(self) -> str:
+ """Get the type of the user."""
return self.type
- def set_user_name(self, name) -> None:
+ def set_user_name(self, name: str) -> None:
+ """Set the name of the user."""
self.name = name
- def set_user_id(self, user_id) -> None:
+ def set_user_id(self, user_id: int) -> None:
+ """Set the ID of the user."""
self.id = user_id
- def set_user_type(self, user_type) -> None:
+ def set_user_type(self, user_type: str) -> None:
+ """Set the type of the user."""
+ if user_type not in ["barman", "admin", "customer"]:
+ raise ValueError("Invalid user type")
self.type = user_type
diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py
index 9f5a8b3..481f8c6 100644
--- a/bot/handlers/__init__.py
+++ b/bot/handlers/__init__.py
@@ -1,16 +1,27 @@
+"""
+This module contains the handler registration for the bot.
+"""
+
from telegram.ext import CallbackQueryHandler, CommandHandler
from bot.handlers.callback_handler import callback_handler
from bot.handlers.help_handler import help_handler
from bot.handlers.menu_handler import menu_handler
from bot.handlers.start_handler import start_handler
+from bot.handlers.error_handler import error_handler
+
def register_handlers(application):
+ """
+ Register all handlers to the application.
+ """
application.add_handler(CallbackQueryHandler(callback_handler))
- application.add_handler(CommandHandler('help', help_handler))
- application.add_handler(CommandHandler('menu', menu_handler))
- application.add_handler(CommandHandler('start', start_handler))
+ application.add_handler(CommandHandler("help", help_handler))
+ application.add_handler(CommandHandler("menu", menu_handler))
+ application.add_handler(CommandHandler("start", start_handler))
+ application.add_error_handler(error_handler)
+
__all__ = [
- 'register_handlers',
-]
\ No newline at end of file
+ "register_handlers",
+]
diff --git a/bot/handlers/callback_handler.py b/bot/handlers/callback_handler.py
index f844597..914f93b 100644
--- a/bot/handlers/callback_handler.py
+++ b/bot/handlers/callback_handler.py
@@ -1,48 +1,43 @@
+"""
+This module contains the callback handler for the bot.
+"""
+
import logging
from telegram import Update
from telegram.ext import CallbackContext
from telegram.constants import ParseMode
from bot.database import Database
-from bot.roles.admin import Admin
-from bot.roles.barman import Barman
-from bot.roles.customer import Customer
-
+from bot.roles import role_associations
+from bot.settings import load_texts
-async def callback_handler(update: Update, context: CallbackContext) -> None:
+async def callback_handler(update: Update, _context: CallbackContext) -> None:
"""
- This handler processes the inline buttons on the menu
+ This handler processes the inline buttons on the menu.
"""
- database = Database()
+ db = Database()
tg_user = update.effective_user
- current_user = database.get_user_by_id(tg_user.id)
+ current_user = db.get_user_by_id(tg_user.id)
data = update.callback_query.data
- text = ''
+ text = ""
markup = None
- if current_user.type == "customer":
- text, markup = Customer.on_button_tap(Customer, data, tg_user.id)
- if markup is None:
- text, markup = Customer.back_to_customer_menu(Customer, data, tg_user.id)
- elif current_user.type == "barman":
- text, markup = Barman.on_button_tap(Barman, data, tg_user.id)
- if markup is None:
- text, markup = Barman.back_to_barman_menu(Barman, data, tg_user.id)
- elif current_user.type == "admin":
- pass
+ role_class = role_associations.get(current_user.type)
+ texts = load_texts(tg_user.language_code)["texts"]
+ role_obj = role_class(db, tg_user.id, texts)
+ if role_class:
+ text, markup = role_obj.on_button_tap(data)
+ if not text or markup is None:
+ text, markup = "Callback, Err0r", role_obj.build_menu()
else:
- logging.getLogger(__name__).error("incorrect user type")
+ logging.error("incorrect user type")
- logging.getLogger(__name__).info(f'{update.effective_user.id} Callbackdata: {data}')
+ logging.info("Callbackdata: %s", data)
- # Close the query to end the client-side loading animation
await update.callback_query.answer()
- # Update message content with corresponding menu section
await update.callback_query.edit_message_text(
- text,
- ParseMode.HTML,
- reply_markup=markup
- )
\ No newline at end of file
+ text, ParseMode.HTML, reply_markup=markup
+ )
diff --git a/bot/handlers/error_handler.py b/bot/handlers/error_handler.py
new file mode 100644
index 0000000..0e8d4f2
--- /dev/null
+++ b/bot/handlers/error_handler.py
@@ -0,0 +1,17 @@
+"""
+This module contains the error handler for the bot.
+"""
+
+import logging
+from telegram import Update
+from telegram.ext import CallbackContext
+
+
+async def error_handler(update: Update, context: CallbackContext) -> None:
+ """
+ Log the error caused by an update.
+ """
+ logging.error(
+ msg="Exception while handling an update:" + str(update.effective_user.id),
+ exc_info=context.error,
+ )
diff --git a/bot/handlers/help_handler.py b/bot/handlers/help_handler.py
index a3e3340..9f32389 100644
--- a/bot/handlers/help_handler.py
+++ b/bot/handlers/help_handler.py
@@ -1,9 +1,19 @@
+"""
+This module defines the help handler for the bot.
+"""
+
import logging
from telegram import Update
from telegram.ext import ContextTypes
+from bot.settings import load_texts
+
-async def help_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+async def help_handler(update: Update, _context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /help is issued."""
- logging.getLogger(__name__).info(f'{update.message.from_user.id} use {update.message.text}')
- await update.message.reply_text("Помощь.")
\ No newline at end of file
+ logging.getLogger(__name__).info(
+ "%s use %s", update.message.from_user.id, update.message.text
+ )
+ await update.message.reply_text(
+ load_texts(update.effective_user.language_code)["texts"]["help"]
+ )
diff --git a/bot/handlers/menu_handler.py b/bot/handlers/menu_handler.py
index 8d28a49..1b26324 100644
--- a/bot/handlers/menu_handler.py
+++ b/bot/handlers/menu_handler.py
@@ -1,45 +1,36 @@
+"""
+This module contains the menu handler for the bot.
+"""
+
import logging
from telegram import Update
from telegram.ext import CallbackContext
from telegram.constants import ParseMode
from bot.database import Database
-from bot.roles import Customer, Barman
+from bot.roles import role_associations
+from bot.settings import load_texts
+
async def menu_handler(update: Update, context: CallbackContext) -> None:
"""
This handler sends a menu with the inline buttons we pre-assigned above
"""
- logging.getLogger(__name__).info(f'{update.message.from_user.id} use {update.message.text}')
+ logging.getLogger(__name__).info(
+ "%s use %s", update.message.from_user.id, update.message.text
+ )
- database = Database()
+ db = Database()
tg_user = update.effective_user
- current_user = database.get_user_by_id(tg_user.id)
-
- if current_user.type == "customer":
- await context.bot.send_message(
- update.message.from_user.id,
- Customer.CUSTOMER_MENU_TEXT,
- parse_mode=ParseMode.HTML,
- reply_markup=Customer.build_customer_menu(Customer)
- )
+ current_user = db.get_user_by_id(tg_user.id)
+ role_class = role_associations.get(current_user.type)
+ texts = load_texts(tg_user.language_code)["texts"]
+ role_object = role_class(db, tg_user.id, texts)
- elif current_user.type == "barman":
- await context.bot.send_message(
- update.message.from_user.id,
- Customer.CUSTOMER_MENU_TEXT,
- parse_mode=ParseMode.HTML,
- reply_markup=Barman.build_barman_menu(Barman)
- )
- pass
- elif current_user.type == "admin":
- """await context.bot.send_message(
- update.message.from_user.id,
- CUSTOMER_MENU_TEXT,
- parse_mode=ParseMode.HTML,
- reply_markup=build_customer_menu()
- )"""
- pass
- else:
- logging.getLogger(__name__).error("incorrect user type")
+ await context.bot.send_message(
+ update.message.from_user.id,
+ texts["menu"],
+ parse_mode=ParseMode.HTML,
+ reply_markup=role_object.build_menu(),
+ )
diff --git a/bot/handlers/start_handler.py b/bot/handlers/start_handler.py
index df4fb21..e4f3a9c 100644
--- a/bot/handlers/start_handler.py
+++ b/bot/handlers/start_handler.py
@@ -1,19 +1,25 @@
+"""
+This module defines the start handler for the bot.
+"""
+
import logging
from telegram import Update
from telegram.ext import ContextTypes
from bot.database import Database, User
+from bot.settings import load_texts
-async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
+async def start_handler(update: Update, _context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /start is issued."""
- logging.getLogger(__name__).info(f'{update.message.from_user.id} use {update.message.text}')
+ logging.getLogger(__name__).info(
+ "%s use %s", update.message.from_user.id, update.message.text
+ )
user = update.effective_user
- """await update.message.reply_html(
- rf"Hi {user.mention_html()}!",
- reply_markup=ForceReply(selective=True),
- )"""
+
database = Database()
- new_user = User(user.id, user.name, "customer")
+ new_user = User(user.id, user.name)
database.insert_user(new_user)
- await update.message.reply_text('Привет! Я бот. Нажимай на кнопку "/help" для получения подсказок по командам.')
\ No newline at end of file
+ await update.message.reply_text(
+ load_texts(user.language_code)["texts"]["start"],
+ )
diff --git a/bot/roles/__init__.py b/bot/roles/__init__.py
index 824fafc..ff2c1af 100644
--- a/bot/roles/__init__.py
+++ b/bot/roles/__init__.py
@@ -1,9 +1,11 @@
+"""
+This module initializes role associations for the bot.
+"""
+
from bot.roles.admin import Admin
from bot.roles.barman import Barman
from bot.roles.customer import Customer
-__all__ = [
- 'Admin',
- 'Barman',
- 'Customer'
- ]
\ No newline at end of file
+__all__ = ["Admin", "Barman", "Customer", "role_associations"]
+
+role_associations = {"admin": Admin, "customer": Customer, "barman": Barman}
diff --git a/bot/roles/admin.py b/bot/roles/admin.py
index ee467b0..bd77e90 100644
--- a/bot/roles/admin.py
+++ b/bot/roles/admin.py
@@ -1,64 +1,11 @@
-import logging
+"""
+This module defines the Admin role for the bot.
+"""
-from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
-from telegram.constants import ParseMode
-from telegram.ext import CallbackContext
+from bot.roles.barman import Barman
-# Pre-assign menu text
-FIRST_MENU_MARKUP = None
-FIRST_MENU = "Админ Панель\n\nХуй"
-SECOND_MENU = "Menu 2\n\nЗалупа"
-# Pre-assign button text
-NEXT_BUTTON = "Next"
-BACK_BUTTON = "Back"
-TUTORIAL_BUTTON = "Tutorial"
-
-# Build keyboards
-def build_menu_keyboard():
- buttons = []
- return InlineKeyboardMarkup(buttons)
-
-
-class Admin:
- def __init__(self):
- pass
-
- async def menu(update: Update, context: CallbackContext) -> None:
- """
- This handler sends a menu with the inline buttons we pre-assigned above
- """
- # logging.getLogger(__name__).info(f'{update.message.from_user.id} use {update.message.text}')
-
- await context.bot.send_message(
- update.message.from_user.id,
- FIRST_MENU,
- parse_mode=ParseMode.HTML,
- reply_markup=FIRST_MENU_MARKUP
- )
-
- async def on_button_tap(update: Update, context: CallbackContext) -> None:
- """
- This handler processes the inline buttons on the menu
- """
-
- data = update.callback_query.data
- text = ''
- markup = None
-
- if data == NEXT_BUTTON:
- text = SECOND_MENU
- markup = SECOND_MENU_MARKUP
- elif data == BACK_BUTTON:
- text = FIRST_MENU
- markup = FIRST_MENU_MARKUP
-
- # Close the query to end the client-side loading animation
- await update.callback_query.answer()
-
- # Update message content with corresponding menu section
- await update.callback_query.edit_message_text(
- text,
- ParseMode.HTML,
- reply_markup=markup
- )
+class Admin(Barman):
+ """
+ Represents an admin role in the bot.
+ """
diff --git a/bot/roles/barman.py b/bot/roles/barman.py
index 8bc85f7..6cac802 100644
--- a/bot/roles/barman.py
+++ b/bot/roles/barman.py
@@ -1,101 +1,126 @@
-import logging
-from telegram import ForceReply, Update, InlineKeyboardMarkup, InlineKeyboardButton
+"""
+This module contains the Barman class which represents a barman interacting with the bot.
+"""
+import logging
+from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from bot.roles.customer import Customer
-from bot.database import Database, Order
+from bot.database import Database
class Barman(Customer):
- QUEUE_BUTTON = "Очередь заказов"
- QUEUE_TEXT = "Очередь"
- COMPLETE_BUTTON = "Завершить заказ"
+ """
+ A class to represent a barman interacting with the bot.
+ """
+
+ def __init__(self, db: Database, tg_user_id: int, texts: dict):
+ super().__init__(db, tg_user_id, texts)
+ self.texts = texts
def create_barman_buttons_menu(self):
- buttons = Customer.create_customer_menu_buttons(Customer)
- buttons.append([InlineKeyboardButton(self.QUEUE_BUTTON, callback_data=self.QUEUE_BUTTON)])
+ """Create buttons for the barman menu"""
+ buttons = self.create_customer_menu_buttons()
+ buttons.append(
+ [
+ InlineKeyboardButton(
+ self.texts["queue_button"], callback_data=self.texts["queue_button"]
+ )
+ ]
+ )
return buttons
- def build_barman_menu(self):
- return InlineKeyboardMarkup(self.create_barman_buttons_menu(self))
+ def build_menu(self) -> InlineKeyboardMarkup:
+ return InlineKeyboardMarkup(self.create_barman_buttons_menu())
- def build_queue_menu(self) -> InlineKeyboardMarkup:
- db = Database()
- my_orders = db.get_orders_queue()
+ def __build_queue_menu(self) -> InlineKeyboardMarkup:
+ my_orders = self.db.get_orders_queue()
buttons = []
if my_orders is not None:
for my_order in my_orders:
- button = InlineKeyboardButton(f'{my_order.date[:-7]} {my_order.product}', callback_data=f'chose_{my_order.date}')
+ button = InlineKeyboardButton(
+ f"{my_order.date[:-7]} {my_order.product}",
+ callback_data=f"complete_{my_order.date}",
+ )
buttons.append([button])
else:
- logging.getLogger(__name__).info(f"No orders in database")
- buttons.append(
- [InlineKeyboardButton(Customer.BACK_TO_MENU_BUTTON, callback_data=Customer.BACK_TO_MENU_BUTTON)])
+ logging.getLogger(__name__).info("No orders in database")
+ buttons.append(self.back_to_menu_button)
return InlineKeyboardMarkup(buttons)
- def build_pre_complete_order_menu(self, data):
- return InlineKeyboardMarkup([[InlineKeyboardButton(self.COMPLETE_BUTTON, callback_data=data)],
- [InlineKeyboardButton(Customer.BACK_BUTTON, callback_data=self.QUEUE_BUTTON)],
- [InlineKeyboardButton(Customer.BACK_TO_MENU_BUTTON, callback_data=Customer.BACK_TO_MENU_BUTTON)]
- ])
-
- def build_complete_order_menu(self):
- return InlineKeyboardMarkup([
- [InlineKeyboardButton(Customer.BACK_BUTTON, callback_data=self.QUEUE_BUTTON)],
- [InlineKeyboardButton(Customer.BACK_TO_MENU_BUTTON, callback_data=Customer.BACK_TO_MENU_BUTTON)]
- ])
-
- def back_to_barman_menu(self, data, tg_user_id):
- text = ''
- markup = None
-
- # Обработка кнопки назад в меню
- if data == self.BACK_TO_MENU_BUTTON:
- logging.getLogger(__name__).info(f'{tg_user_id} return to the barman_menu')
- text = self.CUSTOMER_MENU_TEXT
- markup = self.build_barman_menu(self)
-
- return text, markup
-
-
- def on_button_tap(self, data, tg_user_id):
- text = ''
- markup = None
-
- text, markup = Customer.on_button_tap(Customer, data, tg_user_id)
-
- db = Database()
-
- my_orders: list[Order] = db.get_all_orders()
- order_dates = [str(Order.get_order_date()) for Order in my_orders]
-
-
- if data == self.QUEUE_BUTTON:
- logging.getLogger(__name__).info(
- f'{tg_user_id} press the QUEUE_BUTTON or return to the QUEUE menu')
- text = f'{self.QUEUE_BUTTON}'
- markup = self.build_queue_menu(self)
-
- if data[6:] in order_dates and data[:6] == "chose_":
- logging.getLogger(__name__).info(f'{tg_user_id} watch for the {data}')
- db = Database()
- order = db.get_order_by_date(data[6:])
- text = f'''
- Заказ от: {order.date[:-7]}\nПродукт: {order.product}\nId покупателя: {order.customer_id}\nId бармена: {order.barman_id}\nСтатус: {order.status}'''
- markup = self.build_pre_complete_order_menu(self, str("next"+data))
-
- if data[10:] in order_dates and data[:10] == "nextchose_":
- db = Database()
- order = db.get_order_by_date(data[10:])
- order.set_order_barman_id(tg_user_id)
- order.set_order_status("завершён")
- db.update_order(order)
- text = f'''Заказ завершён!!!\nОт: {order.date[:-7]}\nПродукт: {order.product}\nId покупателя: {order.customer_id}\nId бармена: {order.barman_id}\nСтатус: {order.status}'''
- markup = self.build_complete_order_menu(self)
-
+ def __build_pre_complete_order_menu(self, data) -> InlineKeyboardMarkup:
+ buttons = [
+ [
+ InlineKeyboardButton(
+ self.texts["complete_button"], callback_data="c" + data
+ )
+ ],
+ [
+ InlineKeyboardButton(
+ self.texts["back_button"], callback_data=self.texts["queue_button"]
+ )
+ ],
+ self.back_to_menu_button,
+ ]
+ return InlineKeyboardMarkup(buttons)
+ def __build_complete_order_menu(self) -> InlineKeyboardMarkup:
+ buttons = [
+ [
+ InlineKeyboardButton(
+ self.texts["back_button"], callback_data=self.texts["queue_button"]
+ )
+ ],
+ self.back_to_menu_button,
+ ]
+ return InlineKeyboardMarkup(buttons)
+ def __handle_queue_button(self):
+ logging.getLogger(__name__).info(
+ "%s press the QUEUE_BUTTON or return to the QUEUE menu", self.tg_user_id
+ )
+ text = self.texts["queue_text"]
+ markup = self.__build_queue_menu()
return text, markup
+ def __handle_pre_complete_order(self, data):
+ logging.getLogger(__name__).info("%s watch for the %s", self.tg_user_id, data)
+ order = self.db.get_order_by_date(data[9:])
+ text = (
+ f"""Заказ от: {order.date[:-7]}\n"""
+ f"""Продукт: {order.product}\n"""
+ f"""Id покупателя: {order.customer_id}\n"""
+ f"""Id бармена: {order.barman_id}\n"""
+ f"""Статус: {order.status}"""
+ )
+ markup = self.__build_pre_complete_order_menu(data)
+ return text, markup
+ def __handle_complete_order(self, data):
+ logging.getLogger(__name__).info("%s approved the %s", self.tg_user_id, data)
+ order = self.db.get_order_by_date(data[10:])
+ order.set_order_barman_id(self.tg_user_id)
+ order.set_order_status("завершён")
+ self.db.update_order(order)
+ text = (
+ f"""Заказ завершён!!!\n"""
+ f"""От: {order.date[:-7]}\n"""
+ f"""Продукт: {order.product}\n"""
+ f"""Id покупателя: {order.customer_id}\n"""
+ f"""Id бармена: {order.barman_id}\n"""
+ f"""Статус: {order.status}"""
+ )
+ markup = self.__build_complete_order_menu()
+ return text, markup
+ def on_button_tap(self, data) -> (str, InlineKeyboardMarkup):
+ text, markup = super().on_button_tap(data)
+ if text != "Err0r":
+ return text, markup
+ if data == self.texts["queue_button"]:
+ return self.__handle_queue_button()
+ if data.startswith("complete_"):
+ return self.__handle_pre_complete_order(data)
+ if data.startswith("ccomplete_"):
+ return self.__handle_complete_order(data)
+ return "Err0r", self.build_menu()
diff --git a/bot/roles/customer.py b/bot/roles/customer.py
index 4975506..e1ffe8c 100644
--- a/bot/roles/customer.py
+++ b/bot/roles/customer.py
@@ -1,191 +1,287 @@
+"""
+This module contains the Customer class which represents a customer interacting with the bot.
+"""
+
import logging
import datetime
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
-
-from bot.database import Database, Product, Order
+from bot.database import Product, Order
class Customer:
- CUSTOMER_MENU_TEXT = "Менюшечка\n\n"
- SHOW_PRODUCTS_TEXT = "Барная карта\n\n Нажмите на напиток, чтобы показать подробности."
- MAKE_ORDER_TEXT = "Выбор напитка\n\n Нажмите на напиток, чтобы заказать"
- SHOW_ORDERS_TEXT = "Мои заказы\n\n"
-
- # Тексты для кнопок
- SHOW_PRODUCTS_BUTTON = "Барная карта"
- MAKE_ORDER_BUTTON = "Сделать заказ"
- SHOW_ORDERS_BUTTON = "Мои заказы"
- APPROVE_ORDERS_BUTTON = "Подтвердить заказ."
- BACK_TO_MENU_BUTTON = "Вернуться в главное меню"
- BACK_BUTTON = "Назад"
-
- def __init__(self):
-
- pass
+ """
+ A class to represent a customer interacting with the bot.
+ """
+
+ def __init__(self, db, tg_user_id, texts):
+ """
+ Initialize the Customer with database, Telegram user ID, and texts.
+ """
+ self.db = db
+ self.tg_user_id = tg_user_id
+ self.texts = texts
+ self.back_to_menu_button = [
+ InlineKeyboardButton(
+ self.texts["back_to_menu_button"],
+ callback_data=self.texts["back_to_menu_button"],
+ )
+ ]
def create_customer_menu_buttons(self):
- return [[InlineKeyboardButton(self.SHOW_PRODUCTS_BUTTON, callback_data=self.SHOW_PRODUCTS_BUTTON)],
- [InlineKeyboardButton(self.MAKE_ORDER_BUTTON, callback_data=self.MAKE_ORDER_BUTTON)],
- [InlineKeyboardButton(self.SHOW_ORDERS_BUTTON, callback_data=self.SHOW_ORDERS_BUTTON)]]
-
-
- def build_customer_menu(self) -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup(self.create_customer_menu_buttons(self))
-
- def build_show_products_menu(self) -> InlineKeyboardMarkup:
- db = Database()
- my_products = db.get_all_products()
- buttons = []
- if my_products is not None:
- for product in my_products:
- button = InlineKeyboardButton(product.name, callback_data=f'shown_{product.name}')
- buttons.append([button])
- else:
- logging.getLogger(__name__).info(f"No products in database")
- buttons.append([InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)])
+ """Create buttons for the customer menu."""
+ return [
+ [
+ InlineKeyboardButton(
+ self.texts["show_products_button"],
+ callback_data=self.texts["show_products_button"],
+ )
+ ],
+ [
+ InlineKeyboardButton(
+ self.texts["make_order_button"],
+ callback_data=self.texts["make_order_button"],
+ )
+ ],
+ [
+ InlineKeyboardButton(
+ self.texts["show_orders_button"],
+ callback_data=self.texts["show_orders_button"],
+ )
+ ],
+ ]
+
+ def build_menu(self) -> InlineKeyboardMarkup:
+ """
+ Build the customer menu with inline buttons.
+ """
+ buttons = self.create_customer_menu_buttons()
return InlineKeyboardMarkup(buttons)
- """def build_show_product_info_menu() -> InlineKeyboardMarkup:
- buttons = [[InlineKeyboardButton(BACK_TO_CUSTOMER_MENU_BUTTON, callback_data=BACK_TO_CUSTOMER_MENU_BUTTON)],
- [InlineKeyboardButton(BACK_BUTTON, callback_data=SHOW_PRODUCTS_BUTTON)]]
- return InlineKeyboardMarkup(buttons)"""
-
- def build_show_product_info_menu(self, data) -> InlineKeyboardMarkup:
- buttons = [[InlineKeyboardButton("Сделать заказ", callback_data=data)],
- [InlineKeyboardButton(self.BACK_BUTTON, callback_data=self.SHOW_PRODUCTS_BUTTON)],
- [InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)]]
- return InlineKeyboardMarkup(buttons)
-
- def build_make_orders_menu(self) -> InlineKeyboardMarkup:
- db = Database()
- my_products = db.get_all_products()
+ def __build_show_products_menu(self) -> InlineKeyboardMarkup:
+ """
+ Build the menu to show products.
+ """
+ my_products = self.db.get_all_products()
buttons = []
if my_products is not None:
for product in my_products:
- button = InlineKeyboardButton(product.name, callback_data=f'chose_{product.name}')
+ button = InlineKeyboardButton(
+ product.name, callback_data=f"shown_{product.name}"
+ )
buttons.append([button])
else:
- logging.getLogger(__name__).info(f"No products in database")
- buttons.append([InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)])
+ logging.getLogger(__name__).info("No products in database")
+ buttons.append(self.back_to_menu_button)
return InlineKeyboardMarkup(buttons)
- def build_pre_approve_order_menu(self, data) -> InlineKeyboardMarkup:
- buttons = [[InlineKeyboardButton(self.APPROVE_ORDERS_BUTTON, callback_data=data)],
- [InlineKeyboardButton(self.BACK_BUTTON, callback_data=self.MAKE_ORDER_BUTTON)],
- [InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)]]
+ def __build_show_product_info_menu(self, data) -> InlineKeyboardMarkup:
+ """
+ Build the menu to show product information.
+ """
+ buttons = [
+ [InlineKeyboardButton("Сделать заказ", callback_data="chose_" + data[6:])],
+ [
+ InlineKeyboardButton(
+ self.texts["back_button"],
+ callback_data=self.texts["show_products_button"],
+ )
+ ],
+ self.back_to_menu_button,
+ ]
return InlineKeyboardMarkup(buttons)
- def build_approve_order_menu(self, pre_data) -> InlineKeyboardMarkup:
- buttons = ([InlineKeyboardButton(self.BACK_BUTTON, callback_data=pre_data)],
- [InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)])
+ def __back_to_menu(self):
+ """
+ Return to the main menu.
+ """
+ return self.texts["menu"], self.build_menu()
+
+ def __build_pre_approve_order_menu(self, data) -> InlineKeyboardMarkup:
+ """
+ Build the menu to pre-approve an order.
+ """
+ buttons = [
+ [
+ InlineKeyboardButton(
+ self.texts["approve_orders_button"], callback_data=data
+ )
+ ],
+ [
+ InlineKeyboardButton(
+ self.texts["back_button"],
+ callback_data=self.texts["make_order_button"],
+ )
+ ],
+ self.back_to_menu_button,
+ ]
+ return InlineKeyboardMarkup(buttons)
+ def __build_approve_order_menu(self, pre_data) -> InlineKeyboardMarkup:
+ """
+ Build the menu to approve an order.
+ """
+ buttons = [
+ [InlineKeyboardButton(self.texts["back_button"], callback_data=pre_data)],
+ self.back_to_menu_button,
+ ]
return InlineKeyboardMarkup(buttons)
- def build_show_orders_menu(self, id) -> InlineKeyboardMarkup:
- db = Database()
- my_orders = db.get_orders_by_customer_id(id)
+ def __build_show_orders_menu(self) -> InlineKeyboardMarkup:
+ """
+ Build the menu to show orders.
+ """
+ my_orders = self.db.get_orders_by_customer_id(self.tg_user_id)
buttons = []
if my_orders is not None:
for my_order in my_orders:
- button = InlineKeyboardButton(my_order.date[:-7], callback_data=f'shown_{my_order.date}')
+ button = InlineKeyboardButton(
+ my_order.date[:-7], callback_data=my_order.date
+ )
buttons.append([button])
else:
- logging.getLogger(__name__).info(f"No orders found for user")
-
- buttons.append([InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)])
+ logging.getLogger(__name__).info(
+ "No orders found for user %s", self.tg_user_id
+ )
+ buttons.append(self.back_to_menu_button)
return InlineKeyboardMarkup(buttons)
- def build_show_order_info_menu(self) -> InlineKeyboardMarkup:
- buttons = [[InlineKeyboardButton(self.BACK_BUTTON, callback_data=self.SHOW_ORDERS_BUTTON)],
- [InlineKeyboardButton(self.BACK_TO_MENU_BUTTON, callback_data=self.BACK_TO_MENU_BUTTON)]]
+ def __build_show_order_info_menu(self) -> InlineKeyboardMarkup:
+ """
+ Build the menu to show order information.
+ """
+ buttons = [
+ [
+ InlineKeyboardButton(
+ self.texts["back_button"],
+ callback_data=self.texts["show_orders_button"],
+ )
+ ],
+ self.back_to_menu_button,
+ ]
return InlineKeyboardMarkup(buttons)
-
- def back_to_customer_menu(self, data, tg_user_id) -> [str, InlineKeyboardMarkup]:
- text = ''
- markup = None
- # Обработка кнопки назад в меню
- if data == self.BACK_TO_MENU_BUTTON:
- logging.getLogger(__name__).info(f'{tg_user_id} return to the CUSTOMER_MENU')
- text = self.CUSTOMER_MENU_TEXT
- markup = self.build_customer_menu(self)
- return text, markup
-
- def on_button_tap(self, data, tg_user_id) -> (str, InlineKeyboardMarkup):
- text = ''
- markup = None
-
- db = Database()
- my_products: list[Product] = db.get_all_products()
- product_names = [Product.get_name() for Product in my_products]
-
- my_orders: list[Order] = db.get_all_orders()
- order_dates = [str(Order.get_order_date()) for Order in my_orders]
-
- # Обработка кнопок Барной карты
- if data == self.SHOW_PRODUCTS_BUTTON:
- logging.getLogger(__name__).info(
- f'{tg_user_id} press the SHOW_PRODUCTS_BUTTON or return to SHOW_PRODUCTS menu')
- text = self.SHOW_PRODUCTS_TEXT
- markup = self.build_show_products_menu(self)
-
-
- if data[6:] in product_names and data[:6] == "shown_":
- logging.getLogger(__name__).info(f'{tg_user_id} watch for the {data[6:]}')
- db = Database()
- product = db.get_product_by_name(data[6:])
- text = f'{product.name}\nОписание:\n{product.description}\nЦена:\n{product.price}руб.'
- markup = self.build_show_product_info_menu(self, str(data[1:]))
-
- # Обработка кнопок для создания заказа
- if data == self.MAKE_ORDER_BUTTON:
- logging.getLogger(__name__).info(
- f'{tg_user_id} press the MAKE_ORDER_BUTTON or return to MAKE_ORDER menu')
- text = self.MAKE_ORDER_TEXT
- markup = self.build_make_orders_menu(self)
-
-
- if (data[6:] in product_names and data[:6] == "chose_") or (
- data[5:] in product_names and data[:5] == 'hown_'):
- if data[:5] == 'hown_':
- data = 's' + data
- logging.getLogger(__name__).info(f'{tg_user_id} chosen the {data}')
- text = f'Выбран {data[6:]}.\nПодтвердите ваш заказ.'
- print(str("next" + data)[:10])
- markup = self.build_pre_approve_order_menu(self, str('next' + data))
-
-
- if data[10:] in product_names and (data[:10] == "nextchose_" or data[:10] == "nextshown_"):
- order = Order(str(datetime.datetime.now()), data[10:], tg_user_id, None, 'размещён')
- db = Database()
- db.insert_order(order)
- text = f'Заказ оформлен!\nВремя заказа: {order.date[:-7]}\nПродукт:\n{order.product}\nId покупателя:\n{order.customer_id}'
- markup = self.build_approve_order_menu(self, data[4:])
-
- # Обработка кнопок просмотра заказов
- if data == self.SHOW_ORDERS_BUTTON:
- logging.getLogger(__name__).info(
- f'{tg_user_id} press the SHOW_ORDERS_BUTTON or return to SHOW_ORDERS menu')
- text = self.SHOW_ORDERS_TEXT
- markup = self.build_show_orders_menu(self, tg_user_id)
-
- if data[6:] in order_dates and data[:6] == "shown_":
- logging.getLogger(__name__).info(f'{tg_user_id} watch for the {data}')
- db = Database()
- order = db.get_order_by_date(data[6:])
- text = f'''
- Заказ от: {order.date[:-7]}\n
- Продукт: {order.product}
- Id покупателя: {order.customer_id}
- Id бармена: {order.barman_id}
- Статус: {order.status} '''
- markup = self.build_show_order_info_menu(self)
-
-
- return (text, markup)
-
- # Обработка нажатия кнопок
-
-
-
-
+ def __handle_show_products_button(self):
+ """
+ Handle the show products button press.
+ """
+ logging.getLogger(__name__).info(
+ "%s press the SHOW_PRODUCTS_BUTTON or return to SHOW_PRODUCTS menu",
+ self.tg_user_id,
+ )
+ text = self.texts["show_products_text"]
+ markup = self.__build_show_products_menu()
+ return text, markup
+
+ def __handle_make_order_button(self):
+ """
+ Handle the make order button press.
+ """
+ logging.getLogger(__name__).info(
+ "%s press the MAKE_ORDER_BUTTON or return to MAKE_ORDER menu",
+ self.tg_user_id,
+ )
+ text = self.texts["make_order_text"]
+ markup = self.__build_show_products_menu()
+ return text, markup
+
+ def __handle_show_orders_button(self):
+ """
+ Handle the show orders button press.
+ """
+ logging.getLogger(__name__).info(
+ "%s press the SHOW_ORDERS_BUTTON or return to SHOW_ORDERS menu",
+ self.tg_user_id,
+ )
+ text = self.texts["show_orders_text"]
+ markup = self.__build_show_orders_menu()
+ return text, markup
+
+ def __handle_product_info(self, data):
+ """
+ Handle the product info button press.
+ """
+ logging.getLogger(__name__).info(
+ "%s watch for the %s", self.tg_user_id, data[6:]
+ )
+ product: Product = self.db.get_product_by_name(data[6:])
+ text = (
+ f"""{product.name}\n"""
+ f"""Описание:\n{product.description}\n"""
+ f"""Цена: {product.price}руб."""
+ )
+ markup = self.__build_show_product_info_menu(data)
+ return text, markup
+
+ def __handle_pre_approve_order(self, data):
+ """
+ Handle the pre-approve order button press.
+ """
+ logging.getLogger(__name__).info("%s chosen the %s", self.tg_user_id, data)
+ text = f"Выбран {data[6:]},.\nПодтвердите ваш заказ."
+ markup = self.__build_pre_approve_order_menu(f"next{data}")
+ return text, markup
+
+ def __handle_approve_order(self, data):
+ """
+ Handle the approve order button press.
+ """
+ logging.getLogger(__name__).info("%s approved the %s", self.tg_user_id, data)
+ order = Order(
+ str(datetime.datetime.now()), data[10:], self.tg_user_id, None, "размещён"
+ )
+ self.db.insert_order(order)
+ text = (
+ f"Заказ оформлен!\n"
+ f"Время заказа: {order.date[:-7]}\n"
+ f"Продукт:\n{order.product}\nСтатус: {order.status}"
+ )
+ markup = self.__build_approve_order_menu(data[4:])
+ return text, markup
+
+ def __handle_order_info(self, data):
+ """
+ Handle the order info button press.
+ """
+ logging.getLogger(__name__).info("%s watch for the %s", self.tg_user_id, data)
+ order = self.db.get_order_by_date(data)
+ barman = self.db.get_user_by_id(order.barman_id)
+ text = (
+ f"""Заказ от: {order.date[:-7]}\n"""
+ f"""Продукт: {order.product}\n"""
+ f"""Бармен: {barman if barman is None else barman.name}\n"""
+ f"""Статус: {order.status}"""
+ )
+ markup = self.__build_show_order_info_menu()
+ return text, markup
+
+ def on_button_tap(self, data) -> (str, InlineKeyboardMarkup):
+ """
+ Handle button tap events.
+ """
+ orders_dates = [order.date for order in self.db.get_all_orders()]
+
+ top_menus_associations = {
+ self.texts["show_products_button"]: self.__handle_show_products_button,
+ self.texts["make_order_button"]: self.__handle_make_order_button,
+ self.texts["show_orders_button"]: self.__handle_show_orders_button,
+ self.texts["back_to_menu_button"]: self.__back_to_menu,
+ }
+
+ for callback_prefix, action in [
+ ("shown_", self.__handle_product_info),
+ ("chose_", self.__handle_pre_approve_order),
+ ("hown_", self.__handle_pre_approve_order),
+ ("nextchose_", self.__handle_approve_order),
+ ("nextshown_", self.__handle_approve_order),
+ ]:
+ if data.startswith(callback_prefix):
+ return action(data)
+
+ if data in orders_dates:
+ return self.__handle_order_info(data)
+
+ if data in top_menus_associations:
+ return top_menus_associations[data]()
+
+ logging.getLogger(__name__).error("Incorrect button pressed")
+ return "Err0r", self.build_menu()
diff --git a/bot/settings/__init__.py b/bot/settings/__init__.py
new file mode 100644
index 0000000..9226e09
--- /dev/null
+++ b/bot/settings/__init__.py
@@ -0,0 +1,12 @@
+"""
+This module initializes settings for the bot.
+"""
+
+from bot.settings.load_texts import load_texts
+from bot.settings.set_commands import set_commands
+
+
+__all__ = [
+ "load_texts",
+ "set_commands",
+]
diff --git a/bot/settings/load_texts.py b/bot/settings/load_texts.py
new file mode 100644
index 0000000..00f6817
--- /dev/null
+++ b/bot/settings/load_texts.py
@@ -0,0 +1,20 @@
+"""
+This module provides a function to load localized texts from TOML files.
+"""
+
+import toml
+
+
+def load_texts(locale="ru"):
+ """
+ Load localized texts from a TOML file based on the given locale.
+
+ Args:
+ locale (str): The locale code (e.g., "ru" or "en").
+
+ Returns:
+ dict: The loaded texts from the TOML file.
+ """
+ locale = locale if locale in ["ru", "en"] else "ru"
+ with open(f"res/locales/{locale}.toml", encoding="utf-8") as f:
+ return toml.load(f)
diff --git a/bot/settings/set_commands.py b/bot/settings/set_commands.py
new file mode 100644
index 0000000..0323e33
--- /dev/null
+++ b/bot/settings/set_commands.py
@@ -0,0 +1,19 @@
+"""
+This module provides a function to set bot commands based on the given locale.
+"""
+
+from telegram import BotCommand
+from bot.settings import load_texts
+
+
+async def set_commands(bot, locale="ru"):
+ """
+ Set bot commands based on the given locale.
+ """
+ texts = load_texts(locale)["texts"]
+ commands = [
+ BotCommand("start", texts["start_description"]),
+ BotCommand("help", texts["help_description"]),
+ BotCommand("menu", texts["menu_description"]),
+ ]
+ await bot.set_my_commands(commands=commands)
diff --git a/poetry.lock b/poetry.lock
index 825bc84..5e2a202 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -919,6 +919,17 @@ files = [
[package.extras]
tests = ["pytest", "pytest-cov"]
+[[package]]
+name = "toml"
+version = "0.10.2"
+description = "Python Library for Tom's Obvious, Minimal Language"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
+
[[package]]
name = "tomlkit"
version = "0.13.2"
@@ -944,4 +955,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.13"
-content-hash = "dce4d2b1f9df85cc3bf956f4252f5680faed629948870181c3fc61e730af27f6"
+content-hash = "563788d94d4c4b4be30c1f186ca7a5e91a0b01359d0c4e5b19f9512f2c8f24ce"
diff --git a/pyproject.toml b/pyproject.toml
index bd26672..f0de402 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,6 +10,7 @@ package-mode = false
[tool.poetry.dependencies]
python = "^3.13"
python-telegram-bot = "^21.4"
+toml = "^0.10.2"
[tool.poetry.group.dev.dependencies]
commitizen = "^4.1.0"
@@ -24,6 +25,7 @@ pytest-sugar = "^1.0.0"
[tool.pytest.ini_options]
addopts = "--flake8"
+flake8-max-line-length = 100
[build-system]
requires = ["poetry-core"]
diff --git a/res/locales/ru.toml b/res/locales/ru.toml
new file mode 100644
index 0000000..fd5a3d5
--- /dev/null
+++ b/res/locales/ru.toml
@@ -0,0 +1,22 @@
+[texts]
+start = 'Привет! Я бот. Нажимай на кнопку "/help" для получения подсказок по командам.'
+start_description = 'Запуск и перезапуск бота.'
+help = """Список команд:
+/start - запуск бота,
+/help - показать список команд,
+/menu - показать менюшечку."""
+help_description = 'Показывает список доступных команд.'
+menu = 'Менюшечка'
+menu_description = 'Показывает менюшечку.'
+show_products_text = 'Барная карта
Нажмите на напиток, чтобы показать подробности.'
+make_order_text = 'Выбор напитка
Нажмите на напиток, чтобы заказать'
+show_orders_text = 'Мои заказы '
+show_products_button = 'Барная карта'
+make_order_button = 'Сделать заказ'
+show_orders_button = 'Мои заказы'
+approve_orders_button = 'Подтвердить заказ.'
+back_to_menu_button = 'Вернуться в главное меню'
+back_button = 'Назад'
+queue_button = "Очередь заказов"
+queue_text = "Очередь"
+complete_button = "Завершить заказ"
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..8ba90b2
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,8 @@
+import pytest
+from unittest.mock import MagicMock
+from telegram.ext import Application
+
+
+@pytest.fixture
+def mock_application():
+ return MagicMock(spec=Application)
diff --git a/tests/test_database/__init__.py b/tests/test_database/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_database/test_database.py b/tests/test_database/test_database.py
new file mode 100644
index 0000000..0c83913
--- /dev/null
+++ b/tests/test_database/test_database.py
@@ -0,0 +1,312 @@
+import pytest
+import os
+
+from bot.database.database import Database
+from bot.database.user import User
+from bot.database.product import Product
+from bot.database.order import Order
+
+
+@pytest.fixture
+def db_fixture():
+ return Database(":memory:")
+
+
+def test_fix_path_creates_directory_and_file(tmp_path, db_fixture):
+ test_path = tmp_path / "test_dir" / "test_file.db"
+ db_fixture.fix_path(str(test_path))
+ assert os.path.exists(test_path)
+ assert os.path.isfile(test_path)
+
+
+def test_insert_user(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ db_fixture.insert_user(user)
+ assert db_fixture.get_user_by_id(1) == user
+
+
+def test_insert_product(db_fixture):
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ db_fixture.insert_product(product)
+ retrieved_product = db_fixture.get_product_by_name("Test Product")
+ assert retrieved_product.name == product.name
+ assert retrieved_product.description == product.description
+ assert retrieved_product.price == product.price
+
+
+def test_insert_order(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ assert db_fixture.get_order_by_date("2023-01-01T00:00:00") is not None
+
+
+def test_delete_user(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ db_fixture.insert_user(user)
+ db_fixture.delete_user(user)
+ assert db_fixture.get_user_by_id(1) is None
+
+
+def test_delete_product(db_fixture):
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ db_fixture.insert_product(product)
+ db_fixture.delete_product(product)
+ assert db_fixture.get_product_by_name("Test Product") is None
+
+
+def test_delete_order(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-02T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ db_fixture.delete_order(order)
+ assert db_fixture.get_order_by_date("2023-01-02T00:00:00") is None
+
+
+def test_update_order(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-03T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ order.status = "completed"
+ db_fixture.update_order(order)
+ updated_order = db_fixture.get_order_by_date("2023-01-03T00:00:00")
+ assert updated_order.status == "completed"
+
+
+def test_insert_user_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.insert_user(None)
+
+
+def test_insert_product_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.insert_product(None)
+
+
+def test_insert_order_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.insert_order(None)
+
+
+def test_update_user_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.update_user(None)
+
+
+def test_update_product_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.update_product(None)
+
+
+def test_update_order_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.update_order(None)
+
+
+def test_delete_user_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.delete_user(None)
+
+
+def test_delete_product_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.delete_product(None)
+
+
+def test_delete_order_with_invalid_data(db_fixture):
+ with pytest.raises(ValueError):
+ db_fixture.delete_order(None)
+
+
+def test_get_all_products(db_fixture):
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ db_fixture.insert_product(product)
+ products = db_fixture.get_all_products()
+ assert len(products) == 1
+ assert products[0].name == "Test Product"
+
+
+def test_get_all_users(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ db_fixture.insert_user(user)
+ users = db_fixture.get_all_users()
+ assert len(users) == 1
+ assert users[0].name == "Test User"
+
+
+def test_get_all_orders(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ orders = db_fixture.get_all_orders()
+ assert len(orders) == 1
+ assert orders[0].date == "2023-01-01T00:00:00"
+
+
+def test_get_user_by_id(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ db_fixture.insert_user(user)
+ retrieved_user = db_fixture.get_user_by_id(1)
+ assert retrieved_user is not None
+ assert retrieved_user.name == "Test User"
+
+
+def test_get_product_by_name(db_fixture):
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ db_fixture.insert_product(product)
+ retrieved_product = db_fixture.get_product_by_name("Test Product")
+ assert retrieved_product is not None
+ assert retrieved_product.name == "Test Product"
+
+
+def test_get_order_by_date(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ retrieved_order = db_fixture.get_order_by_date("2023-01-01T00:00:00")
+ assert retrieved_order is not None
+ assert retrieved_order.date == "2023-01-01T00:00:00"
+
+
+def test_get_orders_by_customer_id(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ orders = db_fixture.get_orders_by_customer_id(1)
+ assert len(orders) == 1
+ assert orders[0].customer_id == 1
+
+
+def test_get_orders_queue(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="размещён",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ orders = db_fixture.get_orders_queue()
+ assert len(orders) == 1
+ assert orders[0].status == "размещён"
+
+
+def test_update_user_with_valid_data(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ db_fixture.insert_user(user)
+ user.name = "Updated User"
+ db_fixture.update_user(user)
+ updated_user = db_fixture.get_user_by_id(1)
+ assert updated_user.name == "Updated User"
+
+
+def test_update_product_with_valid_data(db_fixture):
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ db_fixture.insert_product(product)
+ product.description = "Updated description"
+ db_fixture.update_product(product)
+ updated_product = db_fixture.get_product_by_name("Test Product")
+ assert updated_product.description == "Updated description"
+
+
+def test_update_order_with_valid_data(db_fixture):
+ user = User(id=1, name="Test User", type="customer")
+ product = Product(
+ name="Test Product", description="A product for testing", price=100
+ )
+ order = Order(
+ date="2023-01-01T00:00:00",
+ product="Test Product",
+ customer_id=1,
+ barman_id=2,
+ status="placed",
+ )
+ db_fixture.insert_user(user)
+ db_fixture.insert_product(product)
+ db_fixture.insert_order(order)
+ order.status = "completed"
+ db_fixture.update_order(order)
+ updated_order = db_fixture.get_order_by_date("2023-01-01T00:00:00")
+ assert updated_order.status == "completed"
diff --git a/tests/test_database/test_order.py b/tests/test_database/test_order.py
new file mode 100644
index 0000000..ce5e9c6
--- /dev/null
+++ b/tests/test_database/test_order.py
@@ -0,0 +1,58 @@
+import pytest
+from bot.database.order import Order
+
+
+@pytest.fixture
+def order():
+ return Order(
+ date="2023-01-01",
+ product="Coffee",
+ customer_id=1,
+ barman_id=2,
+ status="pending",
+ )
+
+
+def test_get_order_date(order):
+ assert order.get_order_date() == "2023-01-01"
+
+
+def test_get_order_product(order):
+ assert order.get_order_product() == "Coffee"
+
+
+def test_get_order_customer_id(order):
+ assert order.get_order_customer_id() == 1
+
+
+def test_get_order_barman_id(order):
+ assert order.get_order_barman_id() == 2
+
+
+def test_get_order_status(order):
+ assert order.get_order_status() == "pending"
+
+
+def test_set_order_date(order):
+ order.set_order_date("2023-02-01")
+ assert order.get_order_date() == "2023-02-01"
+
+
+def test_set_order_product(order):
+ order.set_order_product("Tea")
+ assert order.get_order_product() == "Tea"
+
+
+def test_set_order_customer_id(order):
+ order.set_order_customer_id(3)
+ assert order.get_order_customer_id() == 3
+
+
+def test_set_order_barman_id(order):
+ order.set_order_barman_id(4)
+ assert order.get_order_barman_id() == 4
+
+
+def test_set_order_status(order):
+ order.set_order_status("completed")
+ assert order.get_order_status() == "completed"
diff --git a/tests/test_database/test_product.py b/tests/test_database/test_product.py
new file mode 100644
index 0000000..e4867c5
--- /dev/null
+++ b/tests/test_database/test_product.py
@@ -0,0 +1,30 @@
+import pytest
+from bot.database.product import Product
+
+
+@pytest.fixture
+def product():
+ return Product(
+ name="Test Product", description="A product for testing", price=100.0
+ )
+
+
+def test_create_product(product):
+ assert product.get_name() == "Test Product"
+ assert product.get_description() == "A product for testing"
+ assert product.get_price() == 100.0
+
+
+def test_update_product_name(product):
+ product.set_name("Updated Product")
+ assert product.get_name() == "Updated Product"
+
+
+def test_update_product_description(product):
+ product.set_description("An updated product description")
+ assert product.get_description() == "An updated product description"
+
+
+def test_update_product_price(product):
+ product.set_price(150.0)
+ assert product.get_price() == 150.0
diff --git a/tests/test_database/test_user.py b/tests/test_database/test_user.py
new file mode 100644
index 0000000..bbbb1e6
--- /dev/null
+++ b/tests/test_database/test_user.py
@@ -0,0 +1,38 @@
+import pytest
+from bot.database.user import User
+
+
+@pytest.fixture
+def user():
+ return User(id=1, name="Test User", type="customer")
+
+
+def test_create_user(user):
+ assert user.get_user_id() == 1
+ assert user.get_user_name() == "Test User"
+ assert user.get_user_type() == "customer"
+
+
+def test_update_user_name(user):
+ user.set_user_name("Updated User")
+ assert user.get_user_name() == "Updated User"
+
+
+def test_update_user_id(user):
+ user.set_user_id(2)
+ assert user.get_user_id() == 2
+
+
+def test_update_user_type(user):
+ user.set_user_type("admin")
+ assert user.get_user_type() == "admin"
+
+
+def test_invalid_user_type():
+ with pytest.raises(ValueError):
+ User(id=1, name="Invalid User", type="invalid")
+
+
+def test_set_invalid_user_type(user):
+ with pytest.raises(ValueError):
+ user.set_user_type("invalid")
diff --git a/tests/test_handlers/__init__.py b/tests/test_handlers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_handlers/test_callback_handler.py b/tests/test_handlers/test_callback_handler.py
new file mode 100644
index 0000000..7a2a793
--- /dev/null
+++ b/tests/test_handlers/test_callback_handler.py
@@ -0,0 +1,37 @@
+# # tests/test_callback_handler.py
+#
+# import pytest
+# from unittest.mock import AsyncMock, MagicMock
+# from telegram import Update, User, CallbackQuery
+# from bot.handlers.callback_handler import callback_handler
+# from bot.database import Database, User as UserDB
+# from bot.roles import role_associations
+# from bot.settings import load_texts
+#
+#
+# @pytest.mark.asyncio
+# async def test_callback_handler():
+# db = MagicMock(Database)
+# role_class = MagicMock()
+# role_obj = MagicMock()
+# role_associations["customer"] = role_class
+# role_class.return_value = role_obj
+#
+# tg_user = User(id=123, first_name="Test", is_bot=False)
+# callback_query = MagicMock(CallbackQuery)
+# callback_query.data = "test_data"
+# callback_query.answer = AsyncMock()
+# callback_query.edit_message_text = AsyncMock()
+# update = MagicMock(Update)
+# update.effective_user = tg_user
+# update.callback_query = callback_query
+# db.insert_user(MagicMock(UserDB(123, "Test", "customer")))
+# db.get_user_by_id.return_value = MagicMock(UserDB(123, "Test", "customer"))
+# load_texts.return_value = {"texts": {}}
+#
+# await callback_handler(update, MagicMock())
+#
+# role_class.assert_called_once_with(db, tg_user.id, {})
+# role_obj.on_button_tap.assert_called_once_with("test_data")
+# callback_query.answer.assert_called_once()
+# callback_query.edit_message_text.assert_called_once()
diff --git a/tests/test_handlers/test_help_handler.py b/tests/test_handlers/test_help_handler.py
new file mode 100644
index 0000000..612912b
--- /dev/null
+++ b/tests/test_handlers/test_help_handler.py
@@ -0,0 +1,23 @@
+import pytest
+from unittest.mock import MagicMock
+from telegram import User as TelegramUser, Update, Message
+from bot.handlers.help_handler import help_handler
+from bot.settings import load_texts
+
+
+@pytest.mark.asyncio
+async def test_help_handler():
+ telegram_user = TelegramUser(id=1, first_name="Test", is_bot=False, language_code="ru")
+
+ message = MagicMock(spec=Message)
+ message.from_user = telegram_user
+ message.text = "/help"
+ update = MagicMock(spec=Update)
+ update.effective_user = telegram_user
+ update.message = message
+
+ context = MagicMock()
+
+ await help_handler(update, context)
+ texts = load_texts(telegram_user.language_code)["texts"]
+ message.reply_text.assert_called_once_with(texts["help"])
diff --git a/tests/test_handlers/test_menu_handler.py b/tests/test_handlers/test_menu_handler.py
new file mode 100644
index 0000000..7ddb692
--- /dev/null
+++ b/tests/test_handlers/test_menu_handler.py
@@ -0,0 +1,72 @@
+import pytest
+from unittest.mock import AsyncMock, MagicMock, patch
+from telegram import User as TelegramUser, Update, Message
+from telegram.constants import ParseMode
+
+from bot.database import Database
+from bot.handlers.menu_handler import menu_handler
+from bot.settings import load_texts
+
+
+@pytest.mark.asyncio
+async def test_menu_handler_customer():
+ telegram_user = TelegramUser(
+ id=1, first_name="Test", is_bot=False, language_code="ru"
+ )
+ message = MagicMock(spec=Message)
+ message.from_user = telegram_user
+ message.text = "/menu"
+ update = MagicMock(spec=Update)
+ update.effective_user = telegram_user
+ update.message = message
+
+ context = MagicMock()
+ context.bot.send_message = AsyncMock()
+
+ database = MagicMock(spec=Database)
+ user = MagicMock()
+ user.type = "customer"
+
+ with patch("bot.handlers.menu_handler.Database", return_value=database):
+ with patch.object(database, "get_user_by_id", return_value=user):
+ with patch("bot.roles.Customer.build_menu", return_value="customer_menu"):
+ await menu_handler(update, context)
+
+ context.bot.send_message.assert_called_once_with(
+ telegram_user.id,
+ load_texts(telegram_user.language_code)["texts"]["menu"],
+ parse_mode=ParseMode.HTML,
+ reply_markup="customer_menu",
+ )
+
+
+@pytest.mark.asyncio
+async def test_menu_handler_barman():
+ telegram_user = TelegramUser(
+ id=1, first_name="Test", is_bot=False, language_code="ru"
+ )
+ message = MagicMock(spec=Message)
+ message.from_user = telegram_user
+ message.text = "/menu"
+ update = MagicMock(spec=Update)
+ update.effective_user = telegram_user
+ update.message = message
+
+ context = MagicMock()
+ context.bot.send_message = AsyncMock()
+
+ database = MagicMock(spec=Database())
+ user = MagicMock()
+ user.type = "barman"
+
+ with patch("bot.handlers.menu_handler.Database", return_value=database):
+ with patch.object(database, "get_user_by_id", return_value=user):
+ with patch("bot.roles.Barman.build_menu", return_value="barman_menu"):
+ await menu_handler(update, context)
+
+ context.bot.send_message.assert_called_once_with(
+ telegram_user.id,
+ load_texts(telegram_user.language_code)["texts"]["menu"],
+ parse_mode=ParseMode.HTML,
+ reply_markup="barman_menu",
+ )
diff --git a/tests/test_handlers/test_start_handler.py b/tests/test_handlers/test_start_handler.py
new file mode 100644
index 0000000..18895fb
--- /dev/null
+++ b/tests/test_handlers/test_start_handler.py
@@ -0,0 +1,34 @@
+import pytest
+from unittest.mock import MagicMock, AsyncMock, patch
+from telegram import User as TelegramUser, Update, Message
+from bot.handlers.start_handler import start_handler
+from bot.database import Database, User
+
+
+@pytest.mark.asyncio
+async def test_start_handler():
+
+ telegram_user = TelegramUser(id=1, first_name="Test", is_bot=False)
+
+ message = MagicMock(spec=Message)
+ message.from_user = telegram_user
+ message.text = "/start"
+ update = MagicMock(spec=Update)
+ update.effective_user = telegram_user
+ update.message = message
+
+ context = MagicMock()
+ context.bot = AsyncMock()
+
+ database = MagicMock(spec=Database)
+ user = User(id=1, name="Test", type="customer")
+ user.language_code = "ru"
+
+ with patch("bot.handlers.start_handler.Database", return_value=database):
+ with patch("bot.handlers.start_handler.User", return_value=user):
+ await start_handler(update, context)
+
+ database.insert_user.assert_called_once_with(user)
+ message.reply_text.assert_called_once_with(
+ 'Привет! Я бот. Нажимай на кнопку "/help" для получения подсказок по командам.'
+ )
diff --git a/tests/test_roles/test_barman.py b/tests/test_roles/test_barman.py
new file mode 100644
index 0000000..acca971
--- /dev/null
+++ b/tests/test_roles/test_barman.py
@@ -0,0 +1,89 @@
+import pytest
+from unittest.mock import MagicMock
+from telegram import InlineKeyboardMarkup
+from bot.roles.barman import Barman
+
+
+@pytest.fixture
+def barman():
+ db = MagicMock()
+ tg_user_id = 12345
+ texts = {
+ "queue_button": "Queue",
+ "queue_text": "Queue Text",
+ "complete_button": "Complete",
+ "back_to_menu_button": "Back to Menu",
+ "back_button": "Back",
+ "menu": "Menu",
+ "show_products_button": "Show Products",
+ "make_order_button": "Make Order",
+ "show_orders_button": "Show Orders",
+ }
+ return Barman(db, tg_user_id, texts)
+
+
+def test_build_menu(barman):
+ menu = barman.build_menu()
+ assert isinstance(menu, InlineKeyboardMarkup)
+
+
+def test_handle_queue_button(barman):
+ barman.db.get_orders_queue.return_value = []
+ text, markup = barman._Barman__handle_queue_button()
+ assert text == "Queue Text"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_pre_complete_order(barman):
+ order = MagicMock()
+ order.date = "2023-10-10 10:10:10"
+ order.product = "Product1"
+ order.customer_id = 12345
+ order.barman_id = 54321
+ order.status = "размещён"
+ barman.db.get_order_by_date.return_value = order
+
+ text, markup = barman._Barman__handle_pre_complete_order(
+ "complete_2023-10-10 10:10:10"
+ )
+ assert "Product1" in text
+ # assert "2023-10-10 10:10" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_complete_order(barman):
+ order = MagicMock()
+ order.date = "2023-10-10 10:10:10"
+ order.product = "Product1"
+ order.customer_id = 12345
+ order.barman_id = 54321
+ order.status = "размещён"
+ barman.db.get_order_by_date.return_value = order
+
+ text, markup = barman._Barman__handle_complete_order(
+ "ccomplete_2023-10-10 10:10:10"
+ )
+ assert "Заказ завершён!!!" in text
+ assert "Product1" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_on_button_tap_queue(barman):
+ barman.db.get_orders_queue.return_value = []
+ text, markup = barman.on_button_tap("Queue")
+ assert text == "Queue Text"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_on_button_tap_complete(barman):
+ order = MagicMock()
+ order.date = "2023-10-10 10:10:10"
+ order.product = "Product1"
+ order.customer_id = 12345
+ order.barman_id = 54321
+ order.status = "размещён"
+ barman.db.get_order_by_date.return_value = order
+
+ text, markup = barman.on_button_tap("complete_2023-10-10 10:10:10")
+ assert "Product1" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
diff --git a/tests/test_roles/test_customer.py b/tests/test_roles/test_customer.py
new file mode 100644
index 0000000..d4155bd
--- /dev/null
+++ b/tests/test_roles/test_customer.py
@@ -0,0 +1,117 @@
+import pytest
+from unittest.mock import MagicMock
+from telegram import InlineKeyboardMarkup
+from bot.roles.customer import Customer
+
+
+@pytest.fixture
+def customer():
+ db = MagicMock()
+ tg_user_id = 12345
+ texts = {
+ "menu": "Menu",
+ "show_products_text": "Show Products",
+ "make_order_text": "Make Order",
+ "show_orders_text": "Show Orders",
+ "show_orders_button": "Show Orders",
+ "show_products_button": "Show Products",
+ "make_order_button": "Make Order",
+ "approve_orders_button": "Approve Order",
+ "back_to_menu_button": "Back to Menu",
+ "back_button": "Back",
+ }
+ return Customer(db, tg_user_id, texts)
+
+
+def test_build_menu(customer):
+ menu = customer.build_menu()
+ assert isinstance(menu, InlineKeyboardMarkup)
+
+
+def test_handle_show_products_button(customer):
+ text, markup = customer._Customer__handle_show_products_button()
+ assert text == "Show Products"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_make_order_button(customer):
+ text, markup = customer._Customer__handle_make_order_button()
+ assert text == "Make Order"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_show_orders_button(customer):
+ text, markup = customer._Customer__handle_show_orders_button()
+ assert text == "Show Orders"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_product_info(customer):
+ product = MagicMock()
+ product.name = "Product1"
+ product.description = "Description1"
+ product.price = 100
+ customer.db.get_product_by_name.return_value = product
+
+ text, markup = customer._Customer__handle_product_info("shown_Product1")
+ assert "Product1" in text
+ assert "Description1" in text
+ assert "100руб." in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_pre_approve_order(customer):
+ text, markup = customer._Customer__handle_pre_approve_order("chose_Product1")
+ assert "Product1" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_approve_order(customer):
+ order = MagicMock()
+ order.date = "2023-10-10 10:10:10"
+ order.product = "Product1"
+ order.customer_id = customer.tg_user_id
+ order.status = "размещён"
+ customer.db.get_order_by_date.return_value = order
+
+ text, markup = customer._Customer__handle_approve_order("nextchose_Product1")
+ assert "Заказ оформлен!" in text
+ assert "Product1" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_handle_order_info(customer):
+ order = MagicMock()
+ order.date = "2023-10-10 10:10:10"
+ order.product = "Product1"
+ order.barman_id = 54321
+ order.status = "размещён"
+ barman = MagicMock()
+ barman.name = "Barman1"
+ customer.db.get_order_by_date.return_value = order
+ customer.db.get_user_by_id.return_value = barman
+
+ text, markup = customer._Customer__handle_order_info("2023-10-10 10:10:10")
+ assert "Product1" in text
+ assert "Barman1" in text
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_back_to_menu(customer):
+ text, markup = customer._Customer__back_to_menu()
+ assert text == "Menu"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_on_button_tap_top_menu(customer):
+ customer.db.get_all_orders.return_value = []
+ text, markup = customer.on_button_tap("Show Products")
+ assert text == "Show Products"
+ assert isinstance(markup, InlineKeyboardMarkup)
+
+
+def test_on_button_tap_invalid(customer):
+ customer.db.get_all_orders.return_value = []
+ text, markup = customer.on_button_tap("Invalid Button")
+ assert text == "Err0r"
+ assert isinstance(markup, InlineKeyboardMarkup)