template creation
This commit is contained in:
parent
3c2137da2b
commit
c0727d7b59
6 changed files with 464 additions and 293 deletions
65
composer.py
65
composer.py
|
|
@ -1,12 +1,15 @@
|
||||||
from src.windows import LoginWindow, AdminWindow, ClientWindow
|
from src.windows import LoginWindow, AdminWindow, ClientWindow, ManagerWindow
|
||||||
from src.objects import User, Rights
|
from src.objects import User, Rights
|
||||||
from src.db import DB_AUTH_HARDCODED as config
|
from src.db import DB_CONFIG
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal
|
from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal
|
||||||
from PyQt6.QtSql import QSqlDatabase
|
from PyQt6.QtSql import QSqlDatabase
|
||||||
|
|
||||||
|
|
||||||
class Composer(QObject):
|
class Composer(QObject):
|
||||||
|
"""Управляет навигацией между окнами приложения"""
|
||||||
|
|
||||||
render_request = pyqtSignal(User)
|
render_request = pyqtSignal(User)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -17,56 +20,64 @@ class Composer(QObject):
|
||||||
self.render_request.connect(self._render)
|
self.render_request.connect(self._render)
|
||||||
|
|
||||||
def _init_db(self):
|
def _init_db(self):
|
||||||
|
"""Инициализация подключения к базе данных"""
|
||||||
self._db = QSqlDatabase("QPSQL")
|
self._db = QSqlDatabase("QPSQL")
|
||||||
self._db.setDatabaseName(config['dbname'])
|
self._db.setDatabaseName(DB_CONFIG['dbname'])
|
||||||
self._db.setPort(config['port'])
|
self._db.setPort(DB_CONFIG['port'])
|
||||||
self._db.setHostName(config['host'])
|
self._db.setHostName(DB_CONFIG['host'])
|
||||||
self._db.setUserName(config['user'])
|
self._db.setUserName(DB_CONFIG['user'])
|
||||||
self._db.setPassword(config['password'])
|
self._db.setPassword(DB_CONFIG['password'])
|
||||||
self._db.open()
|
|
||||||
|
if not self._db.open():
|
||||||
|
raise Exception(f"Не получилось подключиться к базе данных: {self._db.lastError().text()}")
|
||||||
|
|
||||||
@pyqtSlot(User)
|
@pyqtSlot(User)
|
||||||
def _render(self, user: User):
|
def _render(self, user: User):
|
||||||
|
"""Маршрутизация пользователя на основе его роли"""
|
||||||
match user.rights:
|
match user.rights:
|
||||||
case Rights.ADMIN:
|
case Rights.ADMIN:
|
||||||
self._admin_fabric()
|
self._admin_fabric()
|
||||||
case Rights.MANAGER:
|
case Rights.MANAGER:
|
||||||
pass
|
self._manager_fabric()
|
||||||
case Rights.CLIENT:
|
case Rights.CLIENT:
|
||||||
self._client_fabric(user)
|
self._client_fabric(user)
|
||||||
|
case Rights.GUEST:
|
||||||
|
self._guest_fabric()
|
||||||
|
|
||||||
def _login_fabric(self):
|
def _login_fabric(self):
|
||||||
|
"""Создание и отображение окна входа"""
|
||||||
self.wlogin = LoginWindow(self, self._db)
|
self.wlogin = LoginWindow(self, self._db)
|
||||||
|
self._switch_window(self.wlogin)
|
||||||
if self._current:
|
|
||||||
self._current.close()
|
|
||||||
|
|
||||||
self.wlogin.show()
|
|
||||||
|
|
||||||
self._current = self.wlogin
|
|
||||||
|
|
||||||
def _admin_fabric(self):
|
def _admin_fabric(self):
|
||||||
|
"""Создание и отображение панели администратора"""
|
||||||
self.wadmin = AdminWindow(self, self._db)
|
self.wadmin = AdminWindow(self, self._db)
|
||||||
|
self._switch_window(self.wadmin)
|
||||||
|
|
||||||
if self._current:
|
def _manager_fabric(self):
|
||||||
self._current.close()
|
"""Создание и отображение панели менеджера"""
|
||||||
|
self.wmanager = ManagerWindow(self, self._db)
|
||||||
self.wadmin.show()
|
self._switch_window(self.wmanager)
|
||||||
|
|
||||||
self._current = self.wadmin
|
|
||||||
|
|
||||||
def _client_fabric(self, user: User):
|
def _client_fabric(self, user: User):
|
||||||
|
"""Создание и отображение панели клиента"""
|
||||||
self.wclient = ClientWindow(self, self._db, user)
|
self.wclient = ClientWindow(self, self._db, user)
|
||||||
|
self._switch_window(self.wclient)
|
||||||
|
|
||||||
|
def _guest_fabric(self):
|
||||||
|
"""Создание и отображение панели гостя"""
|
||||||
|
# TODO: Реализовать GuestWindow если нужен режим гостя
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _switch_window(self, new_window):
|
||||||
|
"""Переключение между окнами"""
|
||||||
if self._current:
|
if self._current:
|
||||||
self._current.close()
|
self._current.close()
|
||||||
|
new_window.show()
|
||||||
self.wclient.show()
|
self._current = new_window
|
||||||
|
|
||||||
self._current = self.wclient
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
"""Запуск приложения"""
|
||||||
import sys
|
import sys
|
||||||
self._login_fabric()
|
self._login_fabric()
|
||||||
self._current.show()
|
|
||||||
sys.exit(self._app.exec())
|
sys.exit(self._app.exec())
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Этот скрипт я написал для быстрой сборки кода в 1 файл.
|
||||||
|
# Это очень удобно что бы отправлять код на анализ ИИ
|
||||||
|
# Работает только в оболочке bash для *nix систем
|
||||||
|
|
||||||
OUTPUT="all_code.txt"
|
OUTPUT="all_code.txt"
|
||||||
ROOT="."
|
ROOT="."
|
||||||
|
|
||||||
|
|
|
||||||
173
src/db.py
173
src/db.py
|
|
@ -1,19 +1,27 @@
|
||||||
import psycopg2 as pg
|
import psycopg2 as pg
|
||||||
|
|
||||||
from .objects import User, Rights
|
from .objects import User, Rights
|
||||||
|
|
||||||
DB_AUTH_HARDCODED = {
|
DB_CONFIG = {
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"port": 5432,
|
"port": 5432,
|
||||||
"dbname": "examdb",
|
"dbname": "example", # TODO: Заменить на название из задания
|
||||||
"user": "postgres",
|
"user": "example_user",
|
||||||
"password": "213k2010###"
|
"password": "example_password123" # TODO: Заменить на реальный пароль
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_connection():
|
def get_connection():
|
||||||
return pg.connect(**DB_AUTH_HARDCODED)
|
"""Получить подключение к базе данных"""
|
||||||
|
return pg.connect(**DB_CONFIG)
|
||||||
|
|
||||||
|
|
||||||
def do_request(autocommit=False):
|
def do_request(autocommit=False):
|
||||||
|
"""
|
||||||
|
Декоратор для выполнения SQL запросов с автоматическим управлением соединением
|
||||||
|
|
||||||
|
Args:
|
||||||
|
autocommit: Если True, автоматически фиксирует транзакцию
|
||||||
|
"""
|
||||||
|
|
||||||
def upper_wrapper(func):
|
def upper_wrapper(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
|
|
@ -21,7 +29,6 @@ def do_request(autocommit=False):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
kwargs['cursor'] = cursor
|
kwargs['cursor'] = cursor
|
||||||
|
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
|
|
||||||
if autocommit:
|
if autocommit:
|
||||||
|
|
@ -30,43 +37,57 @@ def do_request(autocommit=False):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: Can't request query to DB: {e}")
|
print(f"Error: Database request failed: {e}")
|
||||||
|
conn.rollback()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
return upper_wrapper
|
return upper_wrapper
|
||||||
|
|
||||||
|
|
||||||
@do_request()
|
@do_request()
|
||||||
def auth(login: str, password: str, *, cursor) -> User | None:
|
def auth(login: str, password: str, *, cursor) -> User | None:
|
||||||
|
"""
|
||||||
|
Аутентификация пользователя
|
||||||
|
|
||||||
|
Args:
|
||||||
|
login: Имя пользователя
|
||||||
|
password: Пароль
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User объект или None если аутентификация не удалась
|
||||||
|
"""
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT id, name, rights
|
SELECT id, name, rights
|
||||||
FROM users
|
FROM users
|
||||||
WHERE name = %s
|
WHERE name = %s AND password = %s;
|
||||||
AND password = %s;
|
|
||||||
""", (login, password))
|
""", (login, password))
|
||||||
|
|
||||||
user = cursor.fetchone()
|
user = cursor.fetchone()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
print("Warning: Login Forbidden: Can't find such user!")
|
print("Warning: Authentication failed - user not found")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
rights = None
|
# Маппинг строки роли на Enum
|
||||||
|
rights_mapping = {
|
||||||
|
"admin": Rights.ADMIN,
|
||||||
|
"manager": Rights.MANAGER,
|
||||||
|
"client": Rights.CLIENT,
|
||||||
|
"customer": Rights.CLIENT, # Альтернативное название
|
||||||
|
"guest": Rights.GUEST,
|
||||||
|
}
|
||||||
|
|
||||||
match user[2]:
|
rights = rights_mapping.get(user[2])
|
||||||
case "admin":
|
|
||||||
rights = Rights.ADMIN
|
if not rights:
|
||||||
case "customer":
|
print(f"Warning: Unknown user role: {user[2]}")
|
||||||
rights = Rights.CLIENT
|
return None
|
||||||
case "manager":
|
|
||||||
rights = Rights.MANAGER
|
|
||||||
case _:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return User(
|
return User(
|
||||||
id=user[0],
|
id=user[0],
|
||||||
|
|
@ -74,80 +95,54 @@ def auth(login: str, password: str, *, cursor) -> User | None:
|
||||||
rights=rights
|
rights=rights
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Ниже функции для работы с предметной областью
|
||||||
|
# Заменить на ваши функции (например: get_products, add_product, etc.)
|
||||||
|
|
||||||
@do_request()
|
@do_request()
|
||||||
def get_free_numbers(*, cursor):
|
def get_items(*, cursor):
|
||||||
|
"""
|
||||||
|
Получить список доступных элементов
|
||||||
|
|
||||||
|
Например: get_products(), get_available_rooms(), etc.
|
||||||
|
"""
|
||||||
|
# Пример запроса - заменить на реальный
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM rooms
|
FROM items
|
||||||
WHERE status = 'free';
|
WHERE status = 'available';
|
||||||
""")
|
""")
|
||||||
|
|
||||||
free = cursor.fetchall()
|
items = cursor.fetchall()
|
||||||
|
return items if items else None
|
||||||
|
|
||||||
if not free:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return free
|
|
||||||
|
|
||||||
@do_request(autocommit=True)
|
@do_request(autocommit=True)
|
||||||
def update_number_status(number: str, checkin: str,
|
def create_request(item_id: int, user: User, **kwargs):
|
||||||
checkout: str, user: User,
|
"""
|
||||||
*, cursor):
|
Создать заявку/заказ
|
||||||
|
|
||||||
|
TODO: Адаптировать под конкретную предметную область
|
||||||
|
Args:
|
||||||
|
item_id: ID элемента (товар, номер, услуга)
|
||||||
|
user: Пользователь создающий заявку
|
||||||
|
**kwargs: Дополнительные параметры (даты, количество, etc.)
|
||||||
|
"""
|
||||||
|
# TODO: Реализовать вашу логику создания заявки
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@do_request()
|
||||||
|
def get_user_requests(user_id: int, *, cursor):
|
||||||
|
"""
|
||||||
|
Получить заявки/заказы пользователя
|
||||||
|
|
||||||
|
TODO: Заменить на вашу логику
|
||||||
|
"""
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT password
|
SELECT *
|
||||||
FROM users
|
FROM requests
|
||||||
WHERE id = %s
|
WHERE user_id = %s;
|
||||||
""", (user.id,))
|
""", (user_id,))
|
||||||
|
|
||||||
password = cursor.fetchone()
|
return cursor.fetchall()
|
||||||
|
|
||||||
if not password:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT id
|
|
||||||
FROM guests
|
|
||||||
WHERE name = %s
|
|
||||||
AND PHONE = %s
|
|
||||||
""", (user.name, password[0]))
|
|
||||||
|
|
||||||
guest = cursor.fetchone()
|
|
||||||
|
|
||||||
if not guest:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT id
|
|
||||||
FROM rooms
|
|
||||||
WHERE number = %s;
|
|
||||||
""", (number,))
|
|
||||||
|
|
||||||
number_id = cursor.fetchone()
|
|
||||||
|
|
||||||
if not number_id:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT guest, room
|
|
||||||
FROM bookings
|
|
||||||
WHERE guest = %s
|
|
||||||
AND room = %s;
|
|
||||||
""", (guest[0], number_id[0]))
|
|
||||||
|
|
||||||
request_exists = cursor.fetchone()
|
|
||||||
|
|
||||||
if request_exists:
|
|
||||||
return False
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
INSERT INTO bookings(guest, room, checkin, checkout, status)
|
|
||||||
VALUES (%s, %s, %s, %s, 'active');
|
|
||||||
""", (guest[0], number_id[0], checkin, checkout))
|
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
UPDATE rooms
|
|
||||||
SET status = 'booked'
|
|
||||||
WHERE number = %s;
|
|
||||||
""", (number,))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
@ -2,16 +2,20 @@ from enum import Enum, auto
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
class SignalCode(Enum):
|
class SignalCode(Enum):
|
||||||
SIGFALSE = auto()
|
"""Коды сигналов для обработки ошибок"""
|
||||||
SIGERR = auto()
|
SIGFALSE = auto() # Неверные данные
|
||||||
|
SIGERR = auto() # Ошибка валидации
|
||||||
|
|
||||||
class Rights(Enum):
|
class Rights(Enum):
|
||||||
ADMIN = auto()
|
"""Роли пользователей в системе"""
|
||||||
CLIENT = auto()
|
ADMIN = auto() # Полный доступ
|
||||||
MANAGER = auto()
|
MANAGER = auto() # Просмотр + частичное редактирование
|
||||||
|
CLIENT = auto() # Ограниченный доступ
|
||||||
|
GUEST = auto() # Только просмотр
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class User:
|
class User:
|
||||||
|
"""Модель пользователя"""
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
rights: Rights
|
rights: Rights
|
||||||
48
src/utils.py
48
src/utils.py
|
|
@ -9,17 +9,38 @@ from PyQt6.QtWidgets import (
|
||||||
)
|
)
|
||||||
from PyQt6.QtSql import QSqlTableModel
|
from PyQt6.QtSql import QSqlTableModel
|
||||||
|
|
||||||
|
|
||||||
class TabWidgetCustom(QWidget):
|
class TabWidgetCustom(QWidget):
|
||||||
def __init__(self, name: str, db):
|
"""
|
||||||
|
Универсальный виджет для CRUD операций с таблицей БД
|
||||||
|
|
||||||
|
Функционал:
|
||||||
|
- Отображение данных в QTableView
|
||||||
|
- Добавление/удаление строк
|
||||||
|
- Применение/отмена изменений
|
||||||
|
- Экспорт в CSV
|
||||||
|
- Опциональная фильтрация по датам
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, table_name: str, db, show_date_filter=False):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
table_name: Название таблицы в БД
|
||||||
|
db: QSqlDatabase объект
|
||||||
|
show_date_filter: Показывать ли фильтр по датам
|
||||||
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._name = name
|
self._name = table_name
|
||||||
self._db = db
|
self._db = db
|
||||||
|
self._show_date_filter = show_date_filter
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
|
"""Настройка UI виджета"""
|
||||||
self.root = QVBoxLayout(self)
|
self.root = QVBoxLayout(self)
|
||||||
|
|
||||||
if self._name in ("bookings", "payments"):
|
# Опциональный фильтр по датам
|
||||||
|
if self._show_date_filter:
|
||||||
self.header = QHBoxLayout()
|
self.header = QHBoxLayout()
|
||||||
|
|
||||||
self.from_date = QDateEdit()
|
self.from_date = QDateEdit()
|
||||||
|
|
@ -28,27 +49,29 @@ class TabWidgetCustom(QWidget):
|
||||||
self.to_date = QDateEdit()
|
self.to_date = QDateEdit()
|
||||||
self.to_date.setCalendarPopup(True)
|
self.to_date.setCalendarPopup(True)
|
||||||
|
|
||||||
self.button_filter = QPushButton("Фильтровать")
|
self.button_filter = QPushButton("Filter")
|
||||||
self.button_all = QPushButton("Показать всё")
|
self.button_all = QPushButton("Show All")
|
||||||
|
|
||||||
self.header.addWidget(QLabel("С:"))
|
self.header.addWidget(QLabel("From:"))
|
||||||
self.header.addWidget(self.from_date)
|
self.header.addWidget(self.from_date)
|
||||||
self.header.addWidget(QLabel("По:"))
|
self.header.addWidget(QLabel("To:"))
|
||||||
self.header.addWidget(self.to_date)
|
self.header.addWidget(self.to_date)
|
||||||
self.header.addWidget(self.button_filter)
|
self.header.addWidget(self.button_filter)
|
||||||
self.header.addWidget(self.button_all)
|
self.header.addWidget(self.button_all)
|
||||||
|
|
||||||
self.root.addLayout(self.header)
|
self.root.addLayout(self.header)
|
||||||
|
|
||||||
|
# Таблица
|
||||||
self.view = QTableView()
|
self.view = QTableView()
|
||||||
|
|
||||||
|
# Панель кнопок
|
||||||
self.btoolbar = QHBoxLayout()
|
self.btoolbar = QHBoxLayout()
|
||||||
|
|
||||||
self.button_add = QPushButton("+ Добавить")
|
self.button_add = QPushButton("+ Add")
|
||||||
self.button_del = QPushButton("- Удалить")
|
self.button_del = QPushButton("- Delete")
|
||||||
self.button_ok = QPushButton("\\_/ Применить")
|
self.button_ok = QPushButton("✓ Apply")
|
||||||
self.button_deny = QPushButton("-- Отменить")
|
self.button_deny = QPushButton("✗ Cancel")
|
||||||
self.button_csv = QPushButton("Выгрузить отчёт в CSV")
|
self.button_csv = QPushButton("Export CSV")
|
||||||
|
|
||||||
self.btoolbar.addWidget(self.button_add)
|
self.btoolbar.addWidget(self.button_add)
|
||||||
self.btoolbar.addWidget(self.button_del)
|
self.btoolbar.addWidget(self.button_del)
|
||||||
|
|
@ -62,6 +85,7 @@ class TabWidgetCustom(QWidget):
|
||||||
self._setup_db()
|
self._setup_db()
|
||||||
|
|
||||||
def _setup_db(self):
|
def _setup_db(self):
|
||||||
|
"""Настройка модели БД"""
|
||||||
self.model = QSqlTableModel(db=self._db)
|
self.model = QSqlTableModel(db=self._db)
|
||||||
self.model.setTable(self._name)
|
self.model.setTable(self._name)
|
||||||
self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit)
|
self.model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit)
|
||||||
|
|
|
||||||
453
src/windows.py
453
src/windows.py
|
|
@ -1,5 +1,5 @@
|
||||||
import csv
|
import csv
|
||||||
from .db import auth, get_free_numbers, update_number_status
|
from .db import auth, get_items, get_user_requests
|
||||||
from .objects import User, SignalCode
|
from .objects import User, SignalCode
|
||||||
from .utils import TabWidgetCustom
|
from .utils import TabWidgetCustom
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
|
|
@ -20,10 +20,20 @@ from PyQt6.QtGui import QStandardItemModel, QStandardItem
|
||||||
from PyQt6.QtSql import QSqlTableModel
|
from PyQt6.QtSql import QSqlTableModel
|
||||||
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QDate
|
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QDate
|
||||||
|
|
||||||
FIW = 160 # Fixed Input Width
|
FIW = 160 # Fixed Input Width
|
||||||
NO_NUMBERS_CONST = "Нет свободных номеров"
|
|
||||||
|
|
||||||
class BaseWindow(QMainWindow):
|
class BaseWindow(QMainWindow):
|
||||||
|
"""
|
||||||
|
Базовый класс для всех окон приложения
|
||||||
|
|
||||||
|
Определяет общую структуру окна:
|
||||||
|
1. _define_widgets() - создание виджетов
|
||||||
|
2. _tune_layouts() - настройка layout'ов
|
||||||
|
3. _connect_slots() - подключение сигналов
|
||||||
|
4. _apply_window_settings() - настройка окна (заголовок, размер)
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, composer, db, user=None):
|
def __init__(self, composer, db, user=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._composer = composer
|
self._composer = composer
|
||||||
|
|
@ -35,59 +45,71 @@ class BaseWindow(QMainWindow):
|
||||||
self._apply_window_settings()
|
self._apply_window_settings()
|
||||||
|
|
||||||
def _define_widgets(self):
|
def _define_widgets(self):
|
||||||
|
"""Определение виджетов (переопределить в наследниках)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _tune_layouts(self):
|
def _tune_layouts(self):
|
||||||
|
"""Настройка компоновки (переопределить в наследниках)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _connect_slots(self):
|
def _connect_slots(self):
|
||||||
|
"""Подключение сигналов к слотам (переопределить в наследниках)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
|
"""Настройка параметров окна (переопределить в наследниках)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class LoginWindow(BaseWindow):
|
class LoginWindow(BaseWindow):
|
||||||
|
"""Окно авторизации"""
|
||||||
|
|
||||||
login_success = pyqtSignal(User)
|
login_success = pyqtSignal(User)
|
||||||
login_forbidden = pyqtSignal(SignalCode)
|
login_forbidden = pyqtSignal(SignalCode)
|
||||||
|
|
||||||
def _define_widgets(self):
|
def _define_widgets(self):
|
||||||
self.root = QWidget()
|
self.root = QWidget()
|
||||||
|
|
||||||
self.auth_form = QGroupBox("Логин")
|
self.auth_form = QGroupBox("Authorization")
|
||||||
self.auth_form.setAlignment(Qt.AlignmentFlag.AlignHCenter |
|
self.auth_form.setAlignment(Qt.AlignmentFlag.AlignHCenter |
|
||||||
Qt.AlignmentFlag.AlignVCenter)
|
Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
|
||||||
self.login_line = QLineEdit()
|
self.login_line = QLineEdit()
|
||||||
self.login_line.setFixedWidth(FIW)
|
self.login_line.setFixedWidth(FIW)
|
||||||
|
self.login_line.setPlaceholderText("Username")
|
||||||
|
|
||||||
self.password_line = QLineEdit()
|
self.password_line = QLineEdit()
|
||||||
self.password_line.setFixedWidth(FIW)
|
self.password_line.setFixedWidth(FIW)
|
||||||
self.password_line.setEchoMode(QLineEdit.EchoMode.Password)
|
self.password_line.setEchoMode(QLineEdit.EchoMode.Password)
|
||||||
|
self.password_line.setPlaceholderText("Password")
|
||||||
|
|
||||||
self.auth_button = QPushButton("Авторизоваться")
|
self.auth_button = QPushButton("Login")
|
||||||
|
self.guest_button = QPushButton("Continue as Guest") # Опционально
|
||||||
|
|
||||||
def _tune_layouts(self):
|
def _tune_layouts(self):
|
||||||
self.root_l = QVBoxLayout()
|
self.root_l = QVBoxLayout()
|
||||||
|
|
||||||
self.form = QFormLayout()
|
self.form = QFormLayout()
|
||||||
self.form.addRow("Логин:", self.login_line)
|
self.form.addRow("Login:", self.login_line)
|
||||||
self.form.addRow("Пароль:", self.password_line)
|
self.form.addRow("Password:", self.password_line)
|
||||||
|
|
||||||
self.auth_form.setLayout(self.form)
|
self.auth_form.setLayout(self.form)
|
||||||
|
|
||||||
self.root_l.addWidget(self.auth_form)
|
self.root_l.addWidget(self.auth_form)
|
||||||
self.root_l.addWidget(self.auth_button)
|
self.root_l.addWidget(self.auth_button)
|
||||||
|
# self.root_l.addWidget(self.guest_button) # Опционально
|
||||||
|
|
||||||
self.root.setLayout(self.root_l)
|
self.root.setLayout(self.root_l)
|
||||||
|
|
||||||
self.setCentralWidget(self.root)
|
self.setCentralWidget(self.root)
|
||||||
|
|
||||||
def _connect_slots(self):
|
def _connect_slots(self):
|
||||||
self.auth_button.clicked.connect(self._on_auth_button_clicked)
|
self.auth_button.clicked.connect(self._on_auth_button_clicked)
|
||||||
self.login_success.connect(self._on_login_success)
|
self.login_success.connect(self._on_login_success)
|
||||||
self.login_forbidden.connect(self._on_login_forbidden)
|
self.login_forbidden.connect(self._on_login_forbidden)
|
||||||
|
# self.guest_button.clicked.connect(self._on_guest_button_clicked) # Опционально
|
||||||
|
|
||||||
def _on_auth_button_clicked(self):
|
def _on_auth_button_clicked(self):
|
||||||
|
"""Обработка нажатия кнопки входа"""
|
||||||
login = self.login_line.text()
|
login = self.login_line.text()
|
||||||
password = self.password_line.text()
|
password = self.password_line.text()
|
||||||
|
|
||||||
|
|
@ -101,49 +123,53 @@ class LoginWindow(BaseWindow):
|
||||||
self.login_forbidden.emit(SignalCode.SIGFALSE)
|
self.login_forbidden.emit(SignalCode.SIGFALSE)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Login OK: {user.name} {user.rights}")
|
print(f"Login successful: {user.name} ({user.rights.name})")
|
||||||
|
|
||||||
self.login_success.emit(user)
|
self.login_success.emit(user)
|
||||||
|
|
||||||
@pyqtSlot(User)
|
@pyqtSlot(User)
|
||||||
def _on_login_success(self, user: User):
|
def _on_login_success(self, user: User):
|
||||||
|
"""Успешная авторизация - переход к соответствующей панели"""
|
||||||
self._composer.render_request.emit(user)
|
self._composer.render_request.emit(user)
|
||||||
|
|
||||||
@pyqtSlot(SignalCode)
|
@pyqtSlot(SignalCode)
|
||||||
def _on_login_forbidden(self, code: SignalCode):
|
def _on_login_forbidden(self, code: SignalCode):
|
||||||
|
"""Обработка ошибок авторизации"""
|
||||||
match code:
|
match code:
|
||||||
case SignalCode.SIGFALSE:
|
case SignalCode.SIGFALSE:
|
||||||
QMessageBox().information(self,
|
QMessageBox.warning(self,
|
||||||
"Предупреждение!",
|
"Authentication Failed",
|
||||||
"Вы ввели некорректные данные")
|
"Invalid username or password")
|
||||||
case SignalCode.SIGERR:
|
case SignalCode.SIGERR:
|
||||||
QMessageBox().information(self,
|
QMessageBox.critical(self,
|
||||||
"Ошибка!",
|
"Input Error",
|
||||||
"Вы ввели неприемлемые данные")
|
"Please enter both username and password")
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
self.setWindowTitle("Авторизация")
|
self.setWindowTitle("Login")
|
||||||
self.setFixedSize(260,180)
|
self.setFixedSize(300, 200)
|
||||||
|
|
||||||
|
|
||||||
class AdminWindow(BaseWindow):
|
class AdminWindow(BaseWindow):
|
||||||
|
"""
|
||||||
|
Панель администратора
|
||||||
|
|
||||||
|
TODO: Добавить табы для каждой таблицы из задания
|
||||||
|
Например: products, orders, users, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
def _define_widgets(self):
|
def _define_widgets(self):
|
||||||
self.root = QWidget()
|
self.root = QWidget()
|
||||||
|
|
||||||
self.tabs = QTabWidget()
|
self.tabs = QTabWidget()
|
||||||
|
|
||||||
self.users_tab = TabWidgetCustom("users", self._db)
|
# TODO: Заменить на ваши таблицы
|
||||||
self.guests_tab = TabWidgetCustom("guests", self._db)
|
# Пример:
|
||||||
self.rooms_tab = TabWidgetCustom("rooms", self._db)
|
# self.products_tab = TabWidgetCustom("products", self._db)
|
||||||
self.bookings_tab = TabWidgetCustom("bookings", self._db)
|
# self.orders_tab = TabWidgetCustom("orders", self._db, show_date_filter=True)
|
||||||
self.staff_tab = TabWidgetCustom("staff", self._db)
|
# self.users_tab = TabWidgetCustom("users", self._db)
|
||||||
self.payments_tab = TabWidgetCustom("payments", self._db)
|
|
||||||
|
|
||||||
self.tabs.addTab(self.users_tab, "Пользователи")
|
self.example_tab = TabWidgetCustom("items", self._db) # TODO: Заменить
|
||||||
self.tabs.addTab(self.guests_tab, "Постояльцы")
|
|
||||||
self.tabs.addTab(self.rooms_tab, "Номера")
|
self.tabs.addTab(self.example_tab, "Items") # TODO: Адаптировать названия
|
||||||
self.tabs.addTab(self.bookings_tab, "Бронирования")
|
|
||||||
self.tabs.addTab(self.staff_tab, "Персонал")
|
|
||||||
self.tabs.addTab(self.payments_tab, "Платежи")
|
|
||||||
|
|
||||||
def _tune_layouts(self):
|
def _tune_layouts(self):
|
||||||
self.root_l = QVBoxLayout()
|
self.root_l = QVBoxLayout()
|
||||||
|
|
@ -152,245 +178,352 @@ class AdminWindow(BaseWindow):
|
||||||
self.setCentralWidget(self.root)
|
self.setCentralWidget(self.root)
|
||||||
|
|
||||||
def _connect_slots(self):
|
def _connect_slots(self):
|
||||||
|
"""Подключение обработчиков для каждого таба"""
|
||||||
for i in range(self.tabs.count()):
|
for i in range(self.tabs.count()):
|
||||||
tab = self.tabs.widget(i)
|
tab = self.tabs.widget(i)
|
||||||
|
|
||||||
|
# Фильтрация по датам (если доступна)
|
||||||
try:
|
try:
|
||||||
tab.button_all.clicked.connect(lambda _, t=tab: self._select_all(t))
|
tab.button_all.clicked.connect(lambda _, t=tab: self._select_all(t))
|
||||||
tab.button_filter.clicked.connect(lambda _, t=tab: self._select_filter(t))
|
tab.button_filter.clicked.connect(lambda _, t=tab: self._select_filter(t))
|
||||||
except Exception as e:
|
except AttributeError:
|
||||||
pass
|
pass # Нет фильтра по датам
|
||||||
|
|
||||||
|
# CRUD операции
|
||||||
tab.button_add.clicked.connect(lambda _, t=tab: self._add_row(t))
|
tab.button_add.clicked.connect(lambda _, t=tab: self._add_row(t))
|
||||||
tab.button_del.clicked.connect(lambda _, t=tab: self._del_row(t))
|
tab.button_del.clicked.connect(lambda _, t=tab: self._del_row(t))
|
||||||
tab.button_csv.clicked.connect(lambda _, t=tab: self._csv_row(t))
|
tab.button_csv.clicked.connect(lambda _, t=tab: self._csv_export(t))
|
||||||
tab.button_ok.clicked.connect(lambda _, t=tab: self._apply_changes(t))
|
tab.button_ok.clicked.connect(lambda _, t=tab: self._apply_changes(t))
|
||||||
tab.button_deny.clicked.connect(lambda _, t=tab: self._revert_changes(t))
|
tab.button_deny.clicked.connect(lambda _, t=tab: self._revert_changes(t))
|
||||||
|
|
||||||
def _select_all(self, tab):
|
def _select_all(self, tab):
|
||||||
|
"""Сброс фильтра - показать все записи"""
|
||||||
tab.model.setFilter("")
|
tab.model.setFilter("")
|
||||||
tab.model.select()
|
tab.model.select()
|
||||||
|
|
||||||
def _select_filter(self, tab):
|
def _select_filter(self, tab):
|
||||||
|
"""
|
||||||
|
Фильтрация по датам
|
||||||
|
|
||||||
|
TODO: Адаптировать под поля из задания
|
||||||
|
"""
|
||||||
date_from = tab.from_date.date().toString("yyyy-MM-dd")
|
date_from = tab.from_date.date().toString("yyyy-MM-dd")
|
||||||
date_to = tab.to_date.date().toString("yyyy-MM-dd")
|
date_to = tab.to_date.date().toString("yyyy-MM-dd")
|
||||||
|
|
||||||
if tab._name == "bookings":
|
# TODO: Изменить название поля (checkin/checkout → order_date/delivery_date)
|
||||||
tab.model.setFilter(f"DATE(checkin) >= \'{date_from}\' AND DATE(checkout) <= \'{date_to}\'")
|
if hasattr(tab, '_name'):
|
||||||
tab.model.select()
|
# Пример фильтра
|
||||||
elif tab._name == "payments":
|
tab.model.setFilter(
|
||||||
tab.model.setFilter(f"date <= \'{date_from}\' AND date <= \'{date_to}\'")
|
f"DATE(date_field) >= '{date_from}' AND DATE(date_field) <= '{date_to}'"
|
||||||
|
)
|
||||||
tab.model.select()
|
tab.model.select()
|
||||||
|
|
||||||
def _add_row(self, tab):
|
def _add_row(self, tab):
|
||||||
|
"""Добавление новой строки"""
|
||||||
row = tab.model.rowCount()
|
row = tab.model.rowCount()
|
||||||
tab.model.insertRow(row)
|
tab.model.insertRow(row)
|
||||||
tab.view.selectRow(row)
|
tab.view.selectRow(row)
|
||||||
tab.view.edit(tab.model.index(row, 0))
|
tab.view.edit(tab.model.index(row, 0))
|
||||||
|
|
||||||
def _del_row(self, tab):
|
def _del_row(self, tab):
|
||||||
|
"""Удаление выбранной строки"""
|
||||||
idx = tab.view.currentIndex()
|
idx = tab.view.currentIndex()
|
||||||
|
|
||||||
if not idx.isValid():
|
if not idx.isValid():
|
||||||
print("Error: Fatal: row index is not valid!")
|
QMessageBox.warning(self, "Selection Error",
|
||||||
|
"Please select a row to delete")
|
||||||
return
|
return
|
||||||
|
|
||||||
msg = QMessageBox.question(self, "Подтверждение",
|
confirm = QMessageBox.question(
|
||||||
"Вы уверены что хотите удалить выбранную строку ?"
|
self,
|
||||||
"Изменения будут необратимы")
|
"Confirm Deletion",
|
||||||
|
"Are you sure you want to delete this record?\nThis action cannot be undone.",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
if msg == QMessageBox.StandardButton.Yes:
|
if confirm == QMessageBox.StandardButton.Yes:
|
||||||
tab.model.removeRow(idx.row())
|
tab.model.removeRow(idx.row())
|
||||||
tab.model.submitAll()
|
tab.model.submitAll()
|
||||||
|
|
||||||
def _apply_changes(self, tab):
|
def _apply_changes(self, tab):
|
||||||
|
"""Применить изменения к БД"""
|
||||||
if not tab.model.isDirty():
|
if not tab.model.isDirty():
|
||||||
return
|
return
|
||||||
|
|
||||||
msg = QMessageBox.question(self, "Подтверждение",
|
confirm = QMessageBox.question(
|
||||||
"Применить изменения ?")
|
self,
|
||||||
|
"Apply Changes",
|
||||||
|
"Do you want to save all changes?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
if msg == QMessageBox.StandardButton.Yes:
|
if confirm == QMessageBox.StandardButton.Yes:
|
||||||
if not tab.model.submitAll():
|
if not tab.model.submitAll():
|
||||||
QMessageBox.critical(self, "Ошибка БД",
|
QMessageBox.critical(
|
||||||
"Не получилось применить изменения,"
|
self,
|
||||||
f"\n{tab.model.lastError().text()}")
|
"Database Error",
|
||||||
|
f"Failed to save changes:\n{tab.model.lastError().text()}"
|
||||||
|
)
|
||||||
tab.model.revertAll()
|
tab.model.revertAll()
|
||||||
|
|
||||||
def _revert_changes(self, tab):
|
def _revert_changes(self, tab):
|
||||||
|
"""Отменить несохранённые изменения"""
|
||||||
if not tab.model.isDirty():
|
if not tab.model.isDirty():
|
||||||
return
|
return
|
||||||
|
|
||||||
tab.model.revertAll()
|
tab.model.revertAll()
|
||||||
|
QMessageBox.information(self, "Changes Reverted",
|
||||||
|
"All unsaved changes have been discarded")
|
||||||
|
|
||||||
def _csv_row(self, tab):
|
def _csv_export(self, tab):
|
||||||
path = f"{tab._name}.csv"
|
"""Экспорт таблицы в CSV"""
|
||||||
|
filename = f"{tab._name}.csv"
|
||||||
|
|
||||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
try:
|
||||||
writer = csv.writer(f)
|
with open(filename, "w", newline="", encoding="utf-8") as f:
|
||||||
header = [tab.model.headerData(i, Qt.Orientation.Horizontal) for i in range(tab.model.columnCount())]
|
writer = csv.writer(f)
|
||||||
|
|
||||||
writer.writerow(header)
|
# Заголовки
|
||||||
|
headers = [
|
||||||
|
tab.model.headerData(i, Qt.Orientation.Horizontal)
|
||||||
|
for i in range(tab.model.columnCount())
|
||||||
|
]
|
||||||
|
writer.writerow(headers)
|
||||||
|
|
||||||
for row in range(tab.model.rowCount()):
|
# Данные
|
||||||
row_data = []
|
for row in range(tab.model.rowCount()):
|
||||||
for col in range(tab.model.columnCount()):
|
row_data = []
|
||||||
value = tab.model.data(tab.model.index(row, col))
|
for col in range(tab.model.columnCount()):
|
||||||
|
value = tab.model.data(tab.model.index(row, col))
|
||||||
|
|
||||||
if hasattr(value, "toString"):
|
if hasattr(value, "toString"):
|
||||||
value = value.toString()
|
value = value.toString()
|
||||||
|
|
||||||
row_data.append(value)
|
row_data.append(value)
|
||||||
|
|
||||||
writer.writerow(row_data)
|
writer.writerow(row_data)
|
||||||
|
|
||||||
QMessageBox.information(self, "Информация",
|
QMessageBox.information(
|
||||||
"Отчёт был сохранён в корневую директорию приложения")
|
self,
|
||||||
|
"Export Successful",
|
||||||
|
f"Data exported to:\n{filename}"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(
|
||||||
|
self,
|
||||||
|
"Export Failed",
|
||||||
|
f"Failed to export data:\n{str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
self.setWindowTitle("Панель Администратора")
|
self.setWindowTitle("Admin Panel")
|
||||||
self.setFixedSize(1200,800)
|
self.setFixedSize(1200, 800)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerWindow(BaseWindow):
|
||||||
|
"""
|
||||||
|
Панель менеджера
|
||||||
|
|
||||||
|
TODO: Реализовать если требуется в задании
|
||||||
|
Обычно: просмотр данных + поиск/фильтрация (без удаления)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _define_widgets(self):
|
||||||
|
# TODO: Похож на AdminWindow, но с ограниченными правами
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _apply_window_settings(self):
|
||||||
|
self.setWindowTitle("Manager Panel")
|
||||||
|
self.setFixedSize(1000, 600)
|
||||||
|
|
||||||
|
|
||||||
class ClientWindow(BaseWindow):
|
class ClientWindow(BaseWindow):
|
||||||
|
"""
|
||||||
|
Панель клиента
|
||||||
|
|
||||||
|
TODO: Адаптировать под задание
|
||||||
|
Обычная структура:
|
||||||
|
- Таб 1: Создание заявки/заказа
|
||||||
|
- Таб 2: Просмотр доступных товаров/услуг
|
||||||
|
- Таб 3: История заявок/заказов пользователя
|
||||||
|
"""
|
||||||
|
|
||||||
def _define_widgets(self):
|
def _define_widgets(self):
|
||||||
self.root = QTabWidget()
|
self.root = QTabWidget()
|
||||||
|
|
||||||
self._define_request_box()
|
self._define_request_form()
|
||||||
self._define_free_rooms()
|
self._define_available_items()
|
||||||
self._define_client_bookings()
|
self._define_user_requests()
|
||||||
|
|
||||||
def _tune_layouts(self):
|
def _tune_layouts(self):
|
||||||
self.root_l = QVBoxLayout()
|
self.root_l = QVBoxLayout()
|
||||||
|
|
||||||
self._tune_request_box()
|
self._tune_request_form()
|
||||||
self._tune_free_rooms()
|
self._tune_available_items()
|
||||||
self._tune_client_bookings()
|
self._tune_user_requests()
|
||||||
|
|
||||||
self.root.addTab(self.rb_widget, "Окно заявки")
|
# TODO: Адаптировать названия табов под задание
|
||||||
self.root.addTab(self.fr_widget, "Свободные номера")
|
self.root.addTab(self.request_widget, "New Request")
|
||||||
self.root.addTab(self.cl_widget, "Ваши заявки")
|
self.root.addTab(self.items_widget, "Available Items")
|
||||||
|
self.root.addTab(self.history_widget, "My Requests")
|
||||||
|
|
||||||
self.root.setLayout(self.root_l)
|
self.root.setLayout(self.root_l)
|
||||||
|
|
||||||
self.setCentralWidget(self.root)
|
self.setCentralWidget(self.root)
|
||||||
|
|
||||||
def _define_request_box(self):
|
def _define_request_form(self):
|
||||||
self.rb_widget = QWidget()
|
"""
|
||||||
self.req_box = QGroupBox("Создание заявки")
|
Форма создания заявки
|
||||||
self.req_box.setAlignment(Qt.AlignmentFlag.AlignVCenter |
|
|
||||||
Qt.AlignmentFlag.AlignHCenter)
|
|
||||||
|
|
||||||
self.room_combo = QComboBox()
|
TODO: Адаптировать под задание
|
||||||
|
"""
|
||||||
|
self.request_widget = QWidget()
|
||||||
|
self.request_box = QGroupBox("Create Request")
|
||||||
|
self.request_box.setAlignment(Qt.AlignmentFlag.AlignVCenter |
|
||||||
|
Qt.AlignmentFlag.AlignHCenter)
|
||||||
|
|
||||||
free_rooms = get_free_numbers()
|
# TODO: Заменить на реальные поля из задания
|
||||||
|
self.item_combo = QComboBox()
|
||||||
|
|
||||||
if free_rooms:
|
# Загрузка доступных элементов
|
||||||
for room in free_rooms:
|
items = get_items()
|
||||||
number = room[1]
|
if items:
|
||||||
self.room_combo.addItem(str(number))
|
for item in items:
|
||||||
|
# TODO: Адаптировать под структуру данных
|
||||||
|
self.item_combo.addItem(str(item[1])) # Предполагаем что item[1] - название
|
||||||
else:
|
else:
|
||||||
self.room_combo.addItem(NO_NUMBERS_CONST)
|
self.item_combo.addItem("No items available")
|
||||||
|
|
||||||
self.checkin = QDateEdit()
|
# TODO: Добавить дополнительные поля (даты, количество, etc.)
|
||||||
self.checkin.setCalendarPopup(True)
|
self.date_from = QDateEdit()
|
||||||
|
self.date_from.setCalendarPopup(True)
|
||||||
|
|
||||||
self.checkout = QDateEdit()
|
self.date_to = QDateEdit()
|
||||||
self.checkout.setCalendarPopup(True)
|
self.date_to.setCalendarPopup(True)
|
||||||
|
|
||||||
self.book_button = QPushButton("Забронировать")
|
self.submit_button = QPushButton("Submit Request")
|
||||||
|
|
||||||
def _tune_request_box(self):
|
def _tune_request_form(self):
|
||||||
self.req_l = QVBoxLayout()
|
"""Компоновка формы заявки"""
|
||||||
self.form_l = QFormLayout()
|
req_layout = QVBoxLayout()
|
||||||
|
form_layout = QFormLayout()
|
||||||
|
|
||||||
self.form_l.addRow("Комната:", self.room_combo)
|
# TODO: Адаптировать названия полей
|
||||||
self.form_l.addRow("Заезд:", self.checkin)
|
form_layout.addRow("Item:", self.item_combo)
|
||||||
self.form_l.addRow("Выезд:", self.checkout)
|
form_layout.addRow("From:", self.date_from)
|
||||||
|
form_layout.addRow("To:", self.date_to)
|
||||||
|
|
||||||
self.req_box.setLayout(self.form_l)
|
self.request_box.setLayout(form_layout)
|
||||||
|
|
||||||
self.req_l.addWidget(self.req_box)
|
req_layout.addWidget(self.request_box)
|
||||||
self.req_l.addWidget(self.book_button)
|
req_layout.addWidget(self.submit_button)
|
||||||
|
|
||||||
self.rb_widget.setLayout(self.req_l)
|
self.request_widget.setLayout(req_layout)
|
||||||
|
|
||||||
def _define_free_rooms(self):
|
def _define_available_items(self):
|
||||||
self.fr_widget = QWidget()
|
"""
|
||||||
self.fr_table = QTableView()
|
Таб со списком доступных элементов
|
||||||
|
|
||||||
free_rooms = get_free_numbers()
|
TODO: Адаптировать под задание
|
||||||
|
"""
|
||||||
|
self.items_widget = QWidget()
|
||||||
|
self.items_table = QTableView()
|
||||||
|
|
||||||
self.fr_model = QStandardItemModel()
|
items = get_items()
|
||||||
self.fr_model.setHorizontalHeaderLabels(["Номер Комнаты"])
|
|
||||||
|
|
||||||
if free_rooms:
|
self.items_model = QStandardItemModel()
|
||||||
for room in free_rooms:
|
# TODO: Адаптировать заголовки
|
||||||
item = room[1]
|
self.items_model.setHorizontalHeaderLabels(["ID", "Name", "Status"])
|
||||||
self.fr_model.appendRow(QStandardItem(str(item)))
|
|
||||||
|
|
||||||
self.fr_table.setModel(self.fr_model)
|
if items:
|
||||||
|
for item in items:
|
||||||
|
# TODO: Адаптировать под структуру данных
|
||||||
|
row = [QStandardItem(str(field)) for field in item]
|
||||||
|
self.items_model.appendRow(row)
|
||||||
|
|
||||||
def _tune_free_rooms(self):
|
self.items_table.setModel(self.items_model)
|
||||||
self.f_rooms_l = QVBoxLayout()
|
|
||||||
self.f_rooms_l.addWidget(self.fr_table)
|
|
||||||
|
|
||||||
self.fr_widget.setLayout(self.f_rooms_l)
|
def _tune_available_items(self):
|
||||||
|
"""Компоновка таба доступных элементов"""
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.items_table)
|
||||||
|
self.items_widget.setLayout(layout)
|
||||||
|
|
||||||
def _define_client_bookings(self):
|
def _define_user_requests(self):
|
||||||
self.cl_widget = QWidget()
|
"""
|
||||||
self.cl_table = QTableView()
|
Таб с историей заявок пользователя
|
||||||
|
|
||||||
self.cl_model = QSqlTableModel(db=self._db)
|
TODO: Адаптировать под задание
|
||||||
self.cl_model.setTable("bookings")
|
"""
|
||||||
self.cl_model.select()
|
self.history_widget = QWidget()
|
||||||
|
self.history_table = QTableView()
|
||||||
|
|
||||||
self.cl_table.setModel(self.cl_model)
|
self.history_model = QSqlTableModel(db=self._db)
|
||||||
|
# TODO: Заменить на реальную таблицу
|
||||||
|
self.history_model.setTable("requests")
|
||||||
|
|
||||||
def _tune_client_bookings(self):
|
# TODO: Добавить фильтр по user_id
|
||||||
self.c_bookings_l = QVBoxLayout()
|
if self._user:
|
||||||
self.c_bookings_l.addWidget(self.cl_table)
|
self.history_model.setFilter(f"user_id = {self._user.id}")
|
||||||
|
|
||||||
self.cl_widget.setLayout(self.c_bookings_l)
|
self.history_model.select()
|
||||||
|
|
||||||
|
self.history_table.setModel(self.history_model)
|
||||||
|
|
||||||
|
def _tune_user_requests(self):
|
||||||
|
"""Компоновка таба истории"""
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.history_table)
|
||||||
|
self.history_widget.setLayout(layout)
|
||||||
|
|
||||||
def _connect_slots(self):
|
def _connect_slots(self):
|
||||||
self.book_button.clicked.connect(self._book_button_handler)
|
"""Подключение обработчиков"""
|
||||||
|
self.submit_button.clicked.connect(self._on_submit_request)
|
||||||
|
|
||||||
def _book_button_handler(self):
|
def _on_submit_request(self):
|
||||||
room = self.room_combo.currentText()
|
"""
|
||||||
checkin = self.checkin.date().toString("yyyy-MM-dd")
|
Обработка создания заявки
|
||||||
checkout = self.checkout.date().toString("yyyy-MM-dd")
|
|
||||||
|
|
||||||
if room == NO_NUMBERS_CONST:
|
TODO: Реализовать логику создания заявки
|
||||||
QMessageBox.information(self, "Информация",
|
"""
|
||||||
"На данный момент у нас нет свободных номеров,"
|
item = self.item_combo.currentText()
|
||||||
"приносим свои извинения за доставленные неудобства")
|
date_from = self.date_from.date().toString("yyyy-MM-dd")
|
||||||
|
date_to = self.date_to.date().toString("yyyy-MM-dd")
|
||||||
|
|
||||||
|
# Валидация
|
||||||
|
if item == "No items available":
|
||||||
|
QMessageBox.warning(self, "No Items",
|
||||||
|
"There are no items available at this moment")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not checkin or not checkout:
|
if not date_from or not date_to:
|
||||||
QMessageBox.critical(self, "Информация",
|
QMessageBox.critical(self, "Input Error",
|
||||||
"Пожалуйста, введите корректную дату прибытия и отбытия")
|
"Please select valid dates")
|
||||||
return
|
return
|
||||||
|
|
||||||
current_date = QDate.currentDate()
|
# Проверка дат
|
||||||
|
if self.date_from.date() < QDate.currentDate() or \
|
||||||
if self.checkin.date() < current_date or self.checkout.date() < current_date:
|
self.date_to.date() < QDate.currentDate():
|
||||||
QMessageBox.critical(self, "Предупреждение",
|
QMessageBox.warning(self, "Invalid Date",
|
||||||
"Вы не можете выбрать прошедшую дату!")
|
"Cannot select past dates")
|
||||||
return
|
return
|
||||||
|
|
||||||
status = update_number_status(room, checkin, checkout, self._user)
|
# TODO: Вызвать функцию создания заявки из db.py
|
||||||
|
# success = create_request(item_id, self._user, date_from=date_from, date_to=date_to)
|
||||||
|
|
||||||
if not status:
|
# Заглушка
|
||||||
QMessageBox.critical(self, "Ошибка!",
|
success = True
|
||||||
"Не получилось забронировать ваш номер, попробуйте позже")
|
|
||||||
return
|
if success:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Success",
|
||||||
|
f"Request created successfully!\nItem: {item}\nPeriod: {date_from} - {date_to}"
|
||||||
|
)
|
||||||
|
self.history_model.select() # Обновить историю
|
||||||
else:
|
else:
|
||||||
QMessageBox.information(self, "Успешно!",
|
QMessageBox.critical(self, "Error",
|
||||||
f"Вы забронировали номер {room} на даты"
|
"Failed to create request. Please try again later.")
|
||||||
f"\nС {checkin} по {checkout}, будем рады вас видеть!")
|
|
||||||
|
|
||||||
self.cl_model.select()
|
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
self.setWindowTitle("Панель Пользователя")
|
self.setWindowTitle("Client Panel")
|
||||||
self.setFixedSize(800,400)
|
self.setFixedSize(900, 600)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue