Manager + Client fixes
This commit is contained in:
parent
c0727d7b59
commit
2fff742651
2 changed files with 641 additions and 37 deletions
255
src/db.py
255
src/db.py
|
|
@ -118,31 +118,260 @@ def get_items(*, cursor):
|
||||||
|
|
||||||
|
|
||||||
@do_request(autocommit=True)
|
@do_request(autocommit=True)
|
||||||
def create_request(item_id: int, user: User, **kwargs):
|
def create_request(item_id: int, quantity: int, user: User,
|
||||||
|
date_from: str = None, date_to: str = None,
|
||||||
|
notes: str = None, *, cursor):
|
||||||
"""
|
"""
|
||||||
Создать заявку/заказ
|
Создать новую заявку/заказ
|
||||||
|
|
||||||
TODO: Адаптировать под конкретную предметную область
|
|
||||||
Args:
|
Args:
|
||||||
item_id: ID элемента (товар, номер, услуга)
|
item_id: ID товара/услуги
|
||||||
user: Пользователь создающий заявку
|
quantity: Количество
|
||||||
**kwargs: Дополнительные параметры (даты, количество, etc.)
|
user: Объект пользователя
|
||||||
|
date_from: Дата начала (YYYY-MM-DD) или None
|
||||||
|
date_to: Дата окончания (YYYY-MM-DD) или None
|
||||||
|
notes: Дополнительные заметки
|
||||||
|
cursor: Курсор БД (автоматически через декоратор)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True если успешно, False если ошибка
|
||||||
"""
|
"""
|
||||||
# TODO: Реализовать вашу логику создания заявки
|
try:
|
||||||
pass
|
# 1. Проверяем существование товара и его доступность
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, price, quantity, status
|
||||||
|
FROM items
|
||||||
|
WHERE id = %s;
|
||||||
|
""", (item_id,))
|
||||||
|
|
||||||
|
item = cursor.fetchone()
|
||||||
|
|
||||||
|
if not item:
|
||||||
|
print(f"Error: Item with id={item_id} not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
item_db_id, item_name, item_price, item_quantity, item_status = item
|
||||||
|
|
||||||
|
# 2. Проверяем статус товара
|
||||||
|
if item_status not in ['available', 'reserved']:
|
||||||
|
print(f"Error: Item '{item_name}' is not available (status: {item_status})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 3. Проверяем количество (если товар физический)
|
||||||
|
if item_quantity < quantity:
|
||||||
|
print(f"Error: Insufficient quantity. Available: {item_quantity}, requested: {quantity}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 4. Вычисляем total_price
|
||||||
|
total_price = float(item_price) * quantity
|
||||||
|
|
||||||
|
# 5. Создаём заявку
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO requests (user_id, item_id, quantity, date_from, date_to,
|
||||||
|
status, total_price, notes)
|
||||||
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
RETURNING id;
|
||||||
|
""", (user.id, item_id, quantity, date_from, date_to,
|
||||||
|
'pending', total_price, notes))
|
||||||
|
|
||||||
|
request_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
print(f"Success: Request #{request_id} created for user {user.name}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating request: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@do_request()
|
@do_request()
|
||||||
def get_user_requests(user_id: int, *, cursor):
|
def get_user_requests(user_id: int, *, cursor):
|
||||||
"""
|
"""
|
||||||
Получить заявки/заказы пользователя
|
Получить все заявки пользователя
|
||||||
|
|
||||||
TODO: Заменить на вашу логику
|
Args:
|
||||||
|
user_id: ID пользователя
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples или None
|
||||||
"""
|
"""
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT *
|
SELECT
|
||||||
FROM requests
|
r.id,
|
||||||
WHERE user_id = %s;
|
r.created_at,
|
||||||
|
i.name AS item_name,
|
||||||
|
r.quantity,
|
||||||
|
r.total_price,
|
||||||
|
r.date_from,
|
||||||
|
r.date_to,
|
||||||
|
r.status,
|
||||||
|
r.notes
|
||||||
|
FROM requests r
|
||||||
|
JOIN items i ON r.item_id = i.id
|
||||||
|
WHERE r.user_id = %s
|
||||||
|
ORDER BY r.created_at DESC;
|
||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@do_request(autocommit=True)
|
||||||
|
def update_request_status(request_id: int, new_status: str, *, cursor):
|
||||||
|
"""
|
||||||
|
Обновить статус заявки (для менеджера/администратора)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request_id: ID заявки
|
||||||
|
new_status: Новый статус ('pending', 'approved', 'rejected', 'completed', 'cancelled')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True если успешно, False если ошибка
|
||||||
|
"""
|
||||||
|
valid_statuses = ['pending', 'approved', 'rejected', 'completed', 'cancelled']
|
||||||
|
|
||||||
|
if new_status not in valid_statuses:
|
||||||
|
print(f"Error: Invalid status '{new_status}'. Must be one of: {valid_statuses}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE requests
|
||||||
|
SET status = %s, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = %s;
|
||||||
|
""", (new_status, request_id))
|
||||||
|
|
||||||
|
if cursor.rowcount == 0:
|
||||||
|
print(f"Error: Request #{request_id} not found")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Success: Request #{request_id} status updated to '{new_status}'")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error updating request status: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@do_request()
|
||||||
|
def get_all_requests(status_filter: str = None, *, cursor):
|
||||||
|
"""
|
||||||
|
Получить все заявки (для администратора/менеджера)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status_filter: Фильтр по статусу (опционально)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples
|
||||||
|
"""
|
||||||
|
if status_filter:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.created_at,
|
||||||
|
u.name AS user_name,
|
||||||
|
u.email AS user_email,
|
||||||
|
i.name AS item_name,
|
||||||
|
r.quantity,
|
||||||
|
r.total_price,
|
||||||
|
r.date_from,
|
||||||
|
r.date_to,
|
||||||
|
r.status
|
||||||
|
FROM requests r
|
||||||
|
JOIN users u ON r.user_id = u.id
|
||||||
|
JOIN items i ON r.item_id = i.id
|
||||||
|
WHERE r.status = %s
|
||||||
|
ORDER BY r.created_at DESC;
|
||||||
|
""", (status_filter,))
|
||||||
|
else:
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
r.id,
|
||||||
|
r.created_at,
|
||||||
|
u.name AS user_name,
|
||||||
|
u.email AS user_email,
|
||||||
|
i.name AS item_name,
|
||||||
|
r.quantity,
|
||||||
|
r.total_price,
|
||||||
|
r.date_from,
|
||||||
|
r.date_to,
|
||||||
|
r.status
|
||||||
|
FROM requests r
|
||||||
|
JOIN users u ON r.user_id = u.id
|
||||||
|
JOIN items i ON r.item_id = i.id
|
||||||
|
ORDER BY r.created_at DESC;
|
||||||
|
""")
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@do_request()
|
||||||
|
def search_items(search_term: str, *, cursor):
|
||||||
|
"""
|
||||||
|
Поиск товаров по названию или описанию
|
||||||
|
|
||||||
|
Args:
|
||||||
|
search_term: Поисковый запрос
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples
|
||||||
|
"""
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
i.id,
|
||||||
|
i.name,
|
||||||
|
i.description,
|
||||||
|
i.price,
|
||||||
|
i.quantity,
|
||||||
|
i.status,
|
||||||
|
c.name AS category_name
|
||||||
|
FROM items i
|
||||||
|
LEFT JOIN categories c ON i.category_id = c.id
|
||||||
|
WHERE i.name ILIKE %s OR i.description ILIKE %s
|
||||||
|
ORDER BY i.name;
|
||||||
|
""", (f'%{search_term}%', f'%{search_term}%'))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@do_request()
|
||||||
|
def get_items_by_category(category_id: int, *, cursor):
|
||||||
|
"""
|
||||||
|
Получить товары по категории
|
||||||
|
|
||||||
|
Args:
|
||||||
|
category_id: ID категории
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples
|
||||||
|
"""
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT
|
||||||
|
i.id,
|
||||||
|
i.name,
|
||||||
|
i.description,
|
||||||
|
i.price,
|
||||||
|
i.quantity,
|
||||||
|
i.status
|
||||||
|
FROM items i
|
||||||
|
WHERE i.category_id = %s AND i.status = 'available'
|
||||||
|
ORDER BY i.name;
|
||||||
|
""", (category_id,))
|
||||||
|
|
||||||
|
return cursor.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
@do_request()
|
||||||
|
def get_all_categories(*, cursor):
|
||||||
|
"""
|
||||||
|
Получить все категории
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of tuples (id, name)
|
||||||
|
"""
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, name, description
|
||||||
|
FROM categories
|
||||||
|
ORDER BY name;
|
||||||
|
""")
|
||||||
|
|
||||||
return cursor.fetchall()
|
return cursor.fetchall()
|
||||||
423
src/windows.py
423
src/windows.py
|
|
@ -1,5 +1,5 @@
|
||||||
import csv
|
import csv
|
||||||
from .db import auth, get_items, get_user_requests
|
from .db import auth, get_items, create_request
|
||||||
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 (
|
||||||
|
|
@ -9,12 +9,14 @@ from PyQt6.QtWidgets import (
|
||||||
QGroupBox,
|
QGroupBox,
|
||||||
QFormLayout,
|
QFormLayout,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
|
QHBoxLayout,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QTabWidget,
|
QTabWidget,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QDateEdit,
|
QDateEdit,
|
||||||
QTableView
|
QTableView,
|
||||||
|
QLabel
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import QStandardItemModel, QStandardItem
|
from PyQt6.QtGui import QStandardItemModel, QStandardItem
|
||||||
from PyQt6.QtSql import QSqlTableModel
|
from PyQt6.QtSql import QSqlTableModel
|
||||||
|
|
@ -146,7 +148,7 @@ class LoginWindow(BaseWindow):
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
self.setWindowTitle("Login")
|
self.setWindowTitle("Login")
|
||||||
self.setFixedSize(300, 200)
|
self.setFixedSize(260, 150)
|
||||||
|
|
||||||
|
|
||||||
class AdminWindow(BaseWindow):
|
class AdminWindow(BaseWindow):
|
||||||
|
|
@ -210,7 +212,7 @@ class AdminWindow(BaseWindow):
|
||||||
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")
|
||||||
|
|
||||||
# TODO: Изменить название поля (checkin/checkout → order_date/delivery_date)
|
# TODO: Изменить название поля (checkin/checkout -> order_date/delivery_date)
|
||||||
if hasattr(tab, '_name'):
|
if hasattr(tab, '_name'):
|
||||||
# Пример фильтра
|
# Пример фильтра
|
||||||
tab.model.setFilter(
|
tab.model.setFilter(
|
||||||
|
|
@ -324,17 +326,343 @@ class ManagerWindow(BaseWindow):
|
||||||
"""
|
"""
|
||||||
Панель менеджера
|
Панель менеджера
|
||||||
|
|
||||||
TODO: Реализовать если требуется в задании
|
Функционал:
|
||||||
Обычно: просмотр данных + поиск/фильтрация (без удаления)
|
- Просмотр всех заявок
|
||||||
|
- Фильтрация заявок по статусу
|
||||||
|
- Одобрение/отклонение заявок
|
||||||
|
- Поиск товаров
|
||||||
|
- Просмотр товаров (без редактирования)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _define_widgets(self):
|
def _define_widgets(self):
|
||||||
# TODO: Похож на AdminWindow, но с ограниченными правами
|
self.root = QWidget()
|
||||||
pass
|
self.tabs = QTabWidget()
|
||||||
|
|
||||||
|
# Таб 1: Управление заявками
|
||||||
|
self._define_requests_tab()
|
||||||
|
|
||||||
|
# Таб 2: Просмотр товаров
|
||||||
|
self._define_items_tab()
|
||||||
|
|
||||||
|
def _define_requests_tab(self):
|
||||||
|
"""Таб управления заявками"""
|
||||||
|
self.requests_widget = QWidget()
|
||||||
|
|
||||||
|
# Фильтр по статусу
|
||||||
|
self.filter_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
self.filter_label = QLabel("Filter by status:")
|
||||||
|
self.status_filter = QComboBox()
|
||||||
|
self.status_filter.addItems([
|
||||||
|
"All",
|
||||||
|
"Pending",
|
||||||
|
"Approved",
|
||||||
|
"Rejected",
|
||||||
|
"Completed",
|
||||||
|
"Cancelled"
|
||||||
|
])
|
||||||
|
|
||||||
|
self.filter_button = QPushButton("Apply Filter")
|
||||||
|
self.refresh_button = QPushButton("Refresh")
|
||||||
|
|
||||||
|
self.filter_layout.addWidget(self.filter_label)
|
||||||
|
self.filter_layout.addWidget(self.status_filter)
|
||||||
|
self.filter_layout.addWidget(self.filter_button)
|
||||||
|
self.filter_layout.addWidget(self.refresh_button)
|
||||||
|
self.filter_layout.addStretch()
|
||||||
|
|
||||||
|
# Таблица заявок
|
||||||
|
self.requests_table = QTableView()
|
||||||
|
self.requests_model = QStandardItemModel()
|
||||||
|
self.requests_model.setHorizontalHeaderLabels([
|
||||||
|
"ID", "Date", "User", "Email", "Item",
|
||||||
|
"Qty", "Price", "From", "To", "Status"
|
||||||
|
])
|
||||||
|
self.requests_table.setModel(self.requests_model)
|
||||||
|
|
||||||
|
# Кнопки управления
|
||||||
|
self.requests_buttons = QHBoxLayout()
|
||||||
|
|
||||||
|
self.approve_button = QPushButton("✓ Approve")
|
||||||
|
self.approve_button.setStyleSheet("background-color: #90EE90;")
|
||||||
|
|
||||||
|
self.reject_button = QPushButton("✗ Reject")
|
||||||
|
self.reject_button.setStyleSheet("background-color: #FFB6C6;")
|
||||||
|
|
||||||
|
self.complete_button = QPushButton("✓ Complete")
|
||||||
|
self.complete_button.setStyleSheet("background-color: #87CEEB;")
|
||||||
|
|
||||||
|
self.view_details_button = QPushButton("View Details")
|
||||||
|
|
||||||
|
self.requests_buttons.addWidget(self.approve_button)
|
||||||
|
self.requests_buttons.addWidget(self.reject_button)
|
||||||
|
self.requests_buttons.addWidget(self.complete_button)
|
||||||
|
self.requests_buttons.addWidget(self.view_details_button)
|
||||||
|
self.requests_buttons.addStretch()
|
||||||
|
|
||||||
|
def _define_items_tab(self):
|
||||||
|
"""Таб просмотра товаров"""
|
||||||
|
self.items_widget = QWidget()
|
||||||
|
|
||||||
|
# Поиск
|
||||||
|
self.search_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
self.search_label = QLabel("Search:")
|
||||||
|
self.search_input = QLineEdit()
|
||||||
|
self.search_input.setPlaceholderText("Enter item name...")
|
||||||
|
self.search_button = QPushButton("Search")
|
||||||
|
self.show_all_button = QPushButton("Show All")
|
||||||
|
|
||||||
|
self.search_layout.addWidget(self.search_label)
|
||||||
|
self.search_layout.addWidget(self.search_input)
|
||||||
|
self.search_layout.addWidget(self.search_button)
|
||||||
|
self.search_layout.addWidget(self.show_all_button)
|
||||||
|
self.search_layout.addStretch()
|
||||||
|
|
||||||
|
# Таблица товаров
|
||||||
|
self.items_table = QTableView()
|
||||||
|
self.items_model = QSqlTableModel(db=self._db)
|
||||||
|
self.items_model.setTable("items")
|
||||||
|
self.items_model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit)
|
||||||
|
self.items_model.select()
|
||||||
|
self.items_table.setModel(self.items_model)
|
||||||
|
|
||||||
|
# Только для чтения
|
||||||
|
self.items_table.setEditTriggers(QTableView.EditTrigger.NoEditTriggers)
|
||||||
|
|
||||||
|
def _tune_layouts(self):
|
||||||
|
"""Компоновка"""
|
||||||
|
# Requests tab layout
|
||||||
|
requests_layout = QVBoxLayout()
|
||||||
|
requests_layout.addLayout(self.filter_layout)
|
||||||
|
requests_layout.addWidget(self.requests_table)
|
||||||
|
requests_layout.addLayout(self.requests_buttons)
|
||||||
|
self.requests_widget.setLayout(requests_layout)
|
||||||
|
|
||||||
|
# Items tab layout
|
||||||
|
items_layout = QVBoxLayout()
|
||||||
|
items_layout.addLayout(self.search_layout)
|
||||||
|
items_layout.addWidget(self.items_table)
|
||||||
|
self.items_widget.setLayout(items_layout)
|
||||||
|
|
||||||
|
# Add tabs
|
||||||
|
self.tabs.addTab(self.requests_widget, "Manage Requests")
|
||||||
|
self.tabs.addTab(self.items_widget, "View Items")
|
||||||
|
|
||||||
|
# Root layout
|
||||||
|
root_layout = QVBoxLayout()
|
||||||
|
root_layout.addWidget(self.tabs)
|
||||||
|
self.root.setLayout(root_layout)
|
||||||
|
|
||||||
|
self.setCentralWidget(self.root)
|
||||||
|
|
||||||
|
def _connect_slots(self):
|
||||||
|
"""Подключение сигналов"""
|
||||||
|
# Requests tab
|
||||||
|
self.filter_button.clicked.connect(self._on_filter_requests)
|
||||||
|
self.refresh_button.clicked.connect(self._on_refresh_requests)
|
||||||
|
self.approve_button.clicked.connect(self._on_approve_request)
|
||||||
|
self.reject_button.clicked.connect(self._on_reject_request)
|
||||||
|
self.complete_button.clicked.connect(self._on_complete_request)
|
||||||
|
self.view_details_button.clicked.connect(self._on_view_details)
|
||||||
|
|
||||||
|
# Items tab
|
||||||
|
self.search_button.clicked.connect(self._on_search_items)
|
||||||
|
self.show_all_button.clicked.connect(self._on_show_all_items)
|
||||||
|
self.search_input.returnPressed.connect(self._on_search_items)
|
||||||
|
|
||||||
|
def _load_requests(self, status_filter=None):
|
||||||
|
"""Загрузить заявки из БД"""
|
||||||
|
from .db import get_all_requests
|
||||||
|
|
||||||
|
# Конвертируем фильтр в формат БД
|
||||||
|
status_map = {
|
||||||
|
"All": None,
|
||||||
|
"Pending": "pending",
|
||||||
|
"Approved": "approved",
|
||||||
|
"Rejected": "rejected",
|
||||||
|
"Completed": "completed",
|
||||||
|
"Cancelled": "cancelled"
|
||||||
|
}
|
||||||
|
|
||||||
|
db_status = status_map.get(status_filter, None)
|
||||||
|
requests = get_all_requests(status_filter=db_status)
|
||||||
|
|
||||||
|
# Очистить модель
|
||||||
|
self.requests_model.removeRows(0, self.requests_model.rowCount())
|
||||||
|
|
||||||
|
if not requests:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Заполнить модель
|
||||||
|
for req in requests:
|
||||||
|
row = [QStandardItem(str(field) if field else "") for field in req]
|
||||||
|
self.requests_model.appendRow(row)
|
||||||
|
|
||||||
|
# Автоширина колонок
|
||||||
|
self.requests_table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
def _on_filter_requests(self):
|
||||||
|
"""Применить фильтр"""
|
||||||
|
status = self.status_filter.currentText()
|
||||||
|
self._load_requests(status_filter=status)
|
||||||
|
|
||||||
|
def _on_refresh_requests(self):
|
||||||
|
"""Обновить список заявок"""
|
||||||
|
self._load_requests()
|
||||||
|
|
||||||
|
def _get_selected_request_id(self):
|
||||||
|
"""Получить ID выбранной заявки"""
|
||||||
|
index = self.requests_table.currentIndex()
|
||||||
|
|
||||||
|
if not index.isValid():
|
||||||
|
QMessageBox.warning(self, "No Selection",
|
||||||
|
"Please select a request first")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ID в первой колонке (индекс 0)
|
||||||
|
request_id = self.requests_model.item(index.row(), 0).text()
|
||||||
|
return int(request_id) if request_id else None
|
||||||
|
|
||||||
|
def _on_approve_request(self):
|
||||||
|
"""Одобрить заявку"""
|
||||||
|
from .db import update_request_status
|
||||||
|
|
||||||
|
request_id = self._get_selected_request_id()
|
||||||
|
if not request_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
confirm = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Confirm Approval",
|
||||||
|
f"Approve request #{request_id}?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirm == QMessageBox.StandardButton.Yes:
|
||||||
|
success = update_request_status(request_id, 'approved')
|
||||||
|
|
||||||
|
if success:
|
||||||
|
QMessageBox.information(self, "Success",
|
||||||
|
f"Request #{request_id} approved!")
|
||||||
|
self._on_refresh_requests()
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"Failed to approve request")
|
||||||
|
|
||||||
|
def _on_reject_request(self):
|
||||||
|
"""Отклонить заявку"""
|
||||||
|
from .db import update_request_status
|
||||||
|
|
||||||
|
request_id = self._get_selected_request_id()
|
||||||
|
if not request_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
confirm = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Confirm Rejection",
|
||||||
|
f"Reject request #{request_id}?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirm == QMessageBox.StandardButton.Yes:
|
||||||
|
success = update_request_status(request_id, 'rejected')
|
||||||
|
|
||||||
|
if success:
|
||||||
|
QMessageBox.information(self, "Success",
|
||||||
|
f"Request #{request_id} rejected")
|
||||||
|
self._on_refresh_requests()
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"Failed to reject request")
|
||||||
|
|
||||||
|
def _on_complete_request(self):
|
||||||
|
"""Завершить заявку"""
|
||||||
|
from .db import update_request_status
|
||||||
|
|
||||||
|
request_id = self._get_selected_request_id()
|
||||||
|
if not request_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
confirm = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Confirm Completion",
|
||||||
|
f"Mark request #{request_id} as completed?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
|
)
|
||||||
|
|
||||||
|
if confirm == QMessageBox.StandardButton.Yes:
|
||||||
|
success = update_request_status(request_id, 'completed')
|
||||||
|
|
||||||
|
if success:
|
||||||
|
QMessageBox.information(self, "Success",
|
||||||
|
f"Request #{request_id} completed!")
|
||||||
|
self._on_refresh_requests()
|
||||||
|
else:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"Failed to complete request")
|
||||||
|
|
||||||
|
def _on_view_details(self):
|
||||||
|
"""Просмотр деталей заявки"""
|
||||||
|
index = self.requests_table.currentIndex()
|
||||||
|
|
||||||
|
if not index.isValid():
|
||||||
|
QMessageBox.warning(self, "No Selection",
|
||||||
|
"Please select a request first")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Собираем данные из строки
|
||||||
|
row = index.row()
|
||||||
|
details = []
|
||||||
|
|
||||||
|
for col in range(self.requests_model.columnCount()):
|
||||||
|
header = self.requests_model.horizontalHeaderItem(col).text()
|
||||||
|
value = self.requests_model.item(row, col).text()
|
||||||
|
details.append(f"{header}: {value}")
|
||||||
|
|
||||||
|
# Показываем диалог с деталями
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Request Details",
|
||||||
|
"\n".join(details)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_search_items(self):
|
||||||
|
"""Поиск товаров"""
|
||||||
|
from .db import search_items
|
||||||
|
|
||||||
|
search_term = self.search_input.text().strip()
|
||||||
|
|
||||||
|
if not search_term:
|
||||||
|
QMessageBox.warning(self, "Empty Search",
|
||||||
|
"Please enter a search term")
|
||||||
|
return
|
||||||
|
|
||||||
|
results = search_items(search_term)
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
QMessageBox.information(self, "No Results",
|
||||||
|
f"No items found matching '{search_term}'")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Применяем фильтр к модели
|
||||||
|
filter_str = f"name ILIKE '%{search_term}%' OR description ILIKE '%{search_term}%'"
|
||||||
|
self.items_model.setFilter(filter_str)
|
||||||
|
self.items_model.select()
|
||||||
|
|
||||||
|
def _on_show_all_items(self):
|
||||||
|
"""Показать все товары"""
|
||||||
|
self.search_input.clear()
|
||||||
|
self.items_model.setFilter("")
|
||||||
|
self.items_model.select()
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
|
"""Настройки окна"""
|
||||||
self.setWindowTitle("Manager Panel")
|
self.setWindowTitle("Manager Panel")
|
||||||
self.setFixedSize(1000, 600)
|
self.setFixedSize(1100, 700)
|
||||||
|
|
||||||
|
# Загрузить заявки при открытии
|
||||||
|
self._load_requests()
|
||||||
|
|
||||||
|
|
||||||
class ClientWindow(BaseWindow):
|
class ClientWindow(BaseWindow):
|
||||||
|
|
@ -481,48 +809,95 @@ class ClientWindow(BaseWindow):
|
||||||
|
|
||||||
def _on_submit_request(self):
|
def _on_submit_request(self):
|
||||||
"""
|
"""
|
||||||
Обработка создания заявки
|
Обработка создания заявки (исправленная версия)
|
||||||
|
|
||||||
TODO: Реализовать логику создания заявки
|
|
||||||
"""
|
"""
|
||||||
item = self.item_combo.currentText()
|
from .db import create_request, get_items
|
||||||
|
|
||||||
|
item_text = self.item_combo.currentText()
|
||||||
date_from = self.date_from.date().toString("yyyy-MM-dd")
|
date_from = self.date_from.date().toString("yyyy-MM-dd")
|
||||||
date_to = self.date_to.date().toString("yyyy-MM-dd")
|
date_to = self.date_to.date().toString("yyyy-MM-dd")
|
||||||
|
|
||||||
# Валидация
|
# Валидация: проверка наличия товаров
|
||||||
if item == "No items available":
|
if item_text == "No items available":
|
||||||
QMessageBox.warning(self, "No Items",
|
QMessageBox.warning(self, "No Items",
|
||||||
"There are no items available at this moment")
|
"There are no items available at this moment")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Валидация: даты
|
||||||
if not date_from or not date_to:
|
if not date_from or not date_to:
|
||||||
QMessageBox.critical(self, "Input Error",
|
QMessageBox.critical(self, "Input Error",
|
||||||
"Please select valid dates")
|
"Please select valid dates")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Проверка дат
|
# Валидация: даты не в прошлом
|
||||||
if self.date_from.date() < QDate.currentDate() or \
|
if self.date_from.date() < QDate.currentDate() or \
|
||||||
self.date_to.date() < QDate.currentDate():
|
self.date_to.date() < QDate.currentDate():
|
||||||
QMessageBox.warning(self, "Invalid Date",
|
QMessageBox.warning(self, "Invalid Date",
|
||||||
"Cannot select past dates")
|
"Cannot select past dates")
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: Вызвать функцию создания заявки из db.py
|
# Валидация: date_to >= date_from
|
||||||
# success = create_request(item_id, self._user, date_from=date_from, date_to=date_to)
|
if self.date_to.date() < self.date_from.date():
|
||||||
|
QMessageBox.warning(self, "Invalid Date Range",
|
||||||
|
"End date must be after start date")
|
||||||
|
return
|
||||||
|
|
||||||
# Заглушка
|
# Получить item_id из выбранного товара
|
||||||
success = True
|
items = get_items()
|
||||||
|
if not items:
|
||||||
|
QMessageBox.critical(self, "Error", "Failed to load items")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Найти item_id по названию
|
||||||
|
item_id = None
|
||||||
|
for item in items:
|
||||||
|
if str(item[1]) == item_text: # item[1] = name
|
||||||
|
item_id = item[0] # item[0] = id
|
||||||
|
break
|
||||||
|
|
||||||
|
# КРИТИЧНО: Проверка ДО использования
|
||||||
|
if item_id is None:
|
||||||
|
QMessageBox.critical(self, "Error",
|
||||||
|
"Failed to identify selected item")
|
||||||
|
return # ← ВЫХОД, дальше item_id гарантированно НЕ None
|
||||||
|
|
||||||
|
# Теперь item_id точно не None, безопасно использовать
|
||||||
|
quantity = 1 # TODO: Добавить поле для quantity в форме
|
||||||
|
|
||||||
|
# Создать заявку
|
||||||
|
success = create_request(
|
||||||
|
item_id=item_id, # ← Теперь это безопасно
|
||||||
|
quantity=quantity,
|
||||||
|
user=self._user,
|
||||||
|
date_from=date_from,
|
||||||
|
date_to=date_to,
|
||||||
|
notes=None
|
||||||
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
QMessageBox.information(
|
QMessageBox.information(
|
||||||
self,
|
self,
|
||||||
"Success",
|
"Success",
|
||||||
f"Request created successfully!\nItem: {item}\nPeriod: {date_from} - {date_to}"
|
f"Request created successfully!\n"
|
||||||
|
f"Item: {item_text}\n"
|
||||||
|
f"Period: {date_from} → {date_to}\n"
|
||||||
|
f"Status: Pending approval"
|
||||||
)
|
)
|
||||||
self.history_model.select() # Обновить историю
|
self.history_model.select()
|
||||||
|
|
||||||
|
# Очистить форму
|
||||||
|
self.date_from.setDate(QDate.currentDate())
|
||||||
|
self.date_to.setDate(QDate.currentDate().addDays(1))
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "Error",
|
QMessageBox.critical(
|
||||||
"Failed to create request. Please try again later.")
|
self,
|
||||||
|
"Error",
|
||||||
|
"Failed to create request.\n"
|
||||||
|
"Possible reasons:\n"
|
||||||
|
"- Item is unavailable\n"
|
||||||
|
"- Insufficient quantity\n"
|
||||||
|
"- Database error"
|
||||||
|
)
|
||||||
|
|
||||||
def _apply_window_settings(self):
|
def _apply_window_settings(self):
|
||||||
self.setWindowTitle("Client Panel")
|
self.setWindowTitle("Client Panel")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue