From 590975b1f0771407dfc42884b9287ea8e6049d45 Mon Sep 17 00:00:00 2001 From: CinkaFox Date: Sun, 5 Apr 2026 16:14:54 +0300 Subject: [PATCH] - init: inti repo --- .gitignore | 1 + .idea/.gitignore | 10 ++ .idea/.name | 1 + .idea/QtSettings.xml | 18 +++ .idea/dataSources.xml | 12 ++ .idea/editor.xml | 345 ++++++++++++++++++++++++++++++++++++++++++ .idea/encodings.xml | 4 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/untitled.iml | 2 + .idea/vcs.xml | 6 + CMakeLists.txt | 85 +++++++++++ SPEC.md | 78 ++++++++++ database.cpp | 306 +++++++++++++++++++++++++++++++++++++ database.h | 77 ++++++++++ loginwindow.cpp | 107 +++++++++++++ loginwindow.h | 38 +++++ main.cpp | 28 ++++ mainwindow.cpp | 84 ++++++++++ mainwindow.h | 43 ++++++ printwidget.cpp | 145 ++++++++++++++++++ printwidget.h | 31 ++++ routeswidget.cpp | 160 ++++++++++++++++++++ routeswidget.h | 54 +++++++ ticketswidget.cpp | 136 +++++++++++++++++ ticketswidget.h | 37 +++++ userswidget.cpp | 98 ++++++++++++ userswidget.h | 27 ++++ 28 files changed, 1948 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/QtSettings.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/editor.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/untitled.iml create mode 100644 .idea/vcs.xml create mode 100644 CMakeLists.txt create mode 100644 SPEC.md create mode 100644 database.cpp create mode 100644 database.h create mode 100644 loginwindow.cpp create mode 100644 loginwindow.h create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 printwidget.cpp create mode 100644 printwidget.h create mode 100644 routeswidget.cpp create mode 100644 routeswidget.h create mode 100644 ticketswidget.cpp create mode 100644 ticketswidget.h create mode 100644 userswidget.cpp create mode 100644 userswidget.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f367ff8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cmake-build-debug diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7fecb8f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..ef9e8d2 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +StationManager \ No newline at end of file diff --git a/.idea/QtSettings.xml b/.idea/QtSettings.xml new file mode 100644 index 0000000..ec9483f --- /dev/null +++ b/.idea/QtSettings.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..707d812 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/cmake-build-debug/station.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..8d0e15e --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,345 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..aeb7613 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/untitled.iml b/.idea/untitled.iml new file mode 100644 index 0000000..4c94235 --- /dev/null +++ b/.idea/untitled.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0113112 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,85 @@ +cmake_minimum_required(VERSION 4.2) +project(StationManager) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +set(CMAKE_PREFIX_PATH "C:/Qt/6.11.0/mingw_64") + +find_package(Qt6 COMPONENTS + Core + Gui + Widgets + Sql + REQUIRED) + +set(SOURCES + main.cpp + database.cpp + loginwindow.cpp + mainwindow.cpp + routeswidget.cpp + ticketswidget.cpp + printwidget.cpp + userswidget.cpp +) + +set(HEADERS + database.h + loginwindow.h + mainwindow.h + routeswidget.h + ticketswidget.h + printwidget.h + userswidget.h + database.cpp +) + +add_executable(StationManager ${SOURCES} ${HEADERS}) +target_link_libraries(StationManager + Qt::Core + Qt::Gui + Qt::Widgets + Qt::Sql +) + +if (WIN32 AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) + set(DEBUG_SUFFIX) + if (MSVC AND CMAKE_BUILD_TYPE MATCHES "Debug") + set(DEBUG_SUFFIX "d") + endif () + set(QT_INSTALL_PATH "${CMAKE_PREFIX_PATH}") + message(STATUS "CMAKE PATH ${CMAKE_PREFIX_PATH}") + if (NOT EXISTS "${QT_INSTALL_PATH}/bin") + set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..") + if (NOT EXISTS "${QT_INSTALL_PATH}/bin") + set(QT_INSTALL_PATH "${QT_INSTALL_PATH}/..") + endif () + endif () + if (EXISTS "${QT_INSTALL_PATH}/plugins/sqldrivers/qsqlite.dll") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/plugins/sqldrivers/") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${QT_INSTALL_PATH}/plugins/sqldrivers/qsqlite.dll" + "$/plugins/sqldrivers/") + endif () + if (EXISTS "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/plugins/platforms/") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${QT_INSTALL_PATH}/plugins/platforms/qwindows${DEBUG_SUFFIX}.dll" + "$/plugins/platforms/") + endif () + foreach (QT_LIB Core Gui Widgets Sql) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${QT_INSTALL_PATH}/bin/Qt6${QT_LIB}${DEBUG_SUFFIX}.dll" + "$") + endforeach (QT_LIB) +endif () diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..a86c18e --- /dev/null +++ b/SPEC.md @@ -0,0 +1,78 @@ +# Система учета работы вокзала - Спецификация + +## 1. Обзор + +**Название:** StationManager +**Тип:** Десктопное приложение (Qt6 + SQLite) +**Назначение:** Учет продажи билетов на железнодорожном вокзале + +## 2. Функциональность + +### 2.1 Авторизация +- Форма входа с логином и паролем +- Хранение пользователей в БД +- Роли: Администратор, Кассир +- Админ может добавлять пользователей + +### 2.2 Продажа билетов +- Выбор рейса из списка +- Ввод данных пассажира (ФИО, номер документа) +- Выбор количества билетов +- Автоматический расчет стоимости +- Сохранение в базу данных + +### 2.3 Управление рейсами +- Добавление новых рейсов (маршрут, дата/время, цена, количество мест) +- Просмотр списка рейсов +- Редактирование/удаление рейсов + +### 2.4 Печать билетов +- Фильтрация по номеру рейса +- Отображение списка проданных билетов +- Экспорт в текстовый файл для печати + +## 3. Архитектура БД + +### Таблица users +| Поле | Тип | Описание | +|------|-----|----------| +| id | INTEGER PK | ID пользователя | +| username | TEXT | Логин | +| password | TEXT | Пароль (хеш) | +| role | TEXT | Роль (admin/cashier) | + +### Таблица routes +| Поле | Тип | Описание | +|------|-----|----------| +| id | INTEGER PK | ID рейса | +| route_name | TEXT | Название маршрута | +| departure_time | TEXT | Дата/время отправления | +| price | REAL | Цена билета | +| total_seats | INTEGER | Всего мест | +| available_seats | INTEGER | Свободных мест | + +### Таблица tickets +| Поле | Тип | Описание | +|------|-----|----------| +| id | INTEGER PK | ID билета | +| route_id | INTEGER FK | ID рейса | +| passenger_name | TEXT | ФИО пассажира | +| document_number | TEXT | Номер документа | +| seat_count | INTEGER | Количество билетов | +| total_price | REAL | Общая стоимость | +| seller_id | INTEGER FK | ID кассира | +| sale_date | TEXT | Дата продажи | + +## 4. Интерфейс + +### Окна: +1. **LoginWindow** - авторизация +2. **MainWindow** - главное окно с меню +3. **RoutesWidget** - управление рейсами +4. **TicketsWidget** - продажа билетов +5. **PrintWidget** - печать списка билетов +6. **UsersWidget** - управление пользователями (admin) + +### Навигация: +- Боковое меню с переключением между виджетами +- Строка состояния с информацией о текущем пользователе diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..b8829b2 --- /dev/null +++ b/database.cpp @@ -0,0 +1,306 @@ +#include "database.h" +#include +#include +#include +#include +#include +#include + +Database& Database::instance() { + static Database instance; + return instance; +} + +bool Database::initialize() { + m_db = QSqlDatabase::addDatabase("QSQLITE", "StationDB"); + QString dbPath = QDir::currentPath() + "/station.db"; + m_db.setDatabaseName(dbPath); + + if (!m_db.open()) { + qDebug() << QDir::currentPath() + "/station.db"; + qDebug() << "Database open error:" << m_db.lastError().text(); + return false; + } + + QSqlQuery query(m_db); + + query.exec("CREATE TABLE IF NOT EXISTS users (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "username TEXT UNIQUE NOT NULL, " + "password TEXT NOT NULL, " + "role TEXT NOT NULL)"); + + query.exec("CREATE TABLE IF NOT EXISTS routes (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "route_name TEXT NOT NULL, " + "departure_time TEXT NOT NULL, " + "price REAL NOT NULL, " + "total_seats INTEGER NOT NULL, " + "available_seats INTEGER NOT NULL)"); + + query.exec("CREATE TABLE IF NOT EXISTS tickets (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "route_id INTEGER NOT NULL, " + "passenger_name TEXT NOT NULL, " + "document_number TEXT NOT NULL, " + "seat_count INTEGER NOT NULL, " + "total_price REAL NOT NULL, " + "seller_id INTEGER NOT NULL, " + "sale_date TEXT NOT NULL, " + "FOREIGN KEY (route_id) REFERENCES routes(id), " + "FOREIGN KEY (seller_id) REFERENCES users(id))"); + + query.exec("SELECT COUNT(*) FROM users WHERE role = 'admin'"); + if (query.next() && query.value(0).toInt() == 0) { + addUser("admin", "admin123", "admin"); + } + + return true; +} + +QString Database::hashPassword(const QString& password) { + QByteArray hash = QCryptographicHash::hash( + password.toUtf8(), QCryptographicHash::Sha256); + return hash.toHex(); +} + +bool Database::authenticate(const QString& username, const QString& password) { + QSqlQuery query(m_db); + query.prepare("SELECT password FROM users WHERE username = ?"); + query.addBindValue(username); + + if (query.exec() && query.next()) { + return query.value(0).toString() == hashPassword(password); + } + return false; +} + +User Database::getUser(const QString& username) { + User user; + QSqlQuery query(m_db); + query.prepare("SELECT id, username, password, role FROM users WHERE username = ?"); + query.addBindValue(username); + + if (query.exec() && query.next()) { + user.id = query.value(0).toInt(); + user.username = query.value(1).toString(); + user.password = query.value(2).toString(); + user.role = query.value(3).toString(); + } + return user; +} + +QVector Database::getAllUsers() { + QVector users; + QSqlQuery query("SELECT id, username, password, role FROM users", m_db); + + while (query.next()) { + User user; + user.id = query.value(0).toInt(); + user.username = query.value(1).toString(); + user.password = query.value(2).toString(); + user.role = query.value(3).toString(); + users.append(user); + } + return users; +} + +bool Database::addUser(const QString& username, const QString& password, const QString& role) { + QSqlQuery query(m_db); + query.prepare("INSERT INTO users (username, password, role) VALUES (?, ?, ?)"); + query.addBindValue(username); + query.addBindValue(hashPassword(password)); + query.addBindValue(role); + return query.exec(); +} + +bool Database::deleteUser(int id) { + QSqlQuery query(m_db); + query.prepare("DELETE FROM users WHERE id = ? AND role != 'admin'"); + query.addBindValue(id); + return query.exec(); +} + +QVector Database::getAllRoutes() { + QVector routes; + QSqlQuery query("SELECT id, route_name, departure_time, price, total_seats, available_seats FROM routes", m_db); + + while (query.next()) { + Route route; + route.id = query.value(0).toInt(); + route.routeName = query.value(1).toString(); + route.departureTime = query.value(2).toString(); + route.price = query.value(3).toDouble(); + route.totalSeats = query.value(4).toInt(); + route.availableSeats = query.value(5).toInt(); + routes.append(route); + } + return routes; +} + +Route Database::getRoute(int id) { + Route route; + QSqlQuery query(m_db); + query.prepare("SELECT id, route_name, departure_time, price, total_seats, available_seats FROM routes WHERE id = ?"); + query.addBindValue(id); + + if (query.exec() && query.next()) { + route.id = query.value(0).toInt(); + route.routeName = query.value(1).toString(); + route.departureTime = query.value(2).toString(); + route.price = query.value(3).toDouble(); + route.totalSeats = query.value(4).toInt(); + route.availableSeats = query.value(5).toInt(); + } + return route; +} + +bool Database::addRoute(const QString& routeName, const QString& departureTime, + double price, int totalSeats) { + QSqlQuery query(m_db); + query.prepare("INSERT INTO routes (route_name, departure_time, price, total_seats, available_seats) " + "VALUES (?, ?, ?, ?, ?)"); + query.addBindValue(routeName); + query.addBindValue(departureTime); + query.addBindValue(price); + query.addBindValue(totalSeats); + query.addBindValue(totalSeats); + return query.exec(); +} + +bool Database::updateRoute(const Route& route) { + QSqlQuery query(m_db); + query.prepare("UPDATE routes SET route_name = ?, departure_time = ?, price = ?, " + "total_seats = ?, available_seats = ? WHERE id = ?"); + query.addBindValue(route.routeName); + query.addBindValue(route.departureTime); + query.addBindValue(route.price); + query.addBindValue(route.totalSeats); + query.addBindValue(route.availableSeats); + query.addBindValue(route.id); + return query.exec(); +} + +bool Database::deleteRoute(int id) { + QSqlQuery query(m_db); + query.prepare("DELETE FROM routes WHERE id = ?"); + query.addBindValue(id); + return query.exec(); +} + +bool Database::updateAvailableSeats(int routeId, int change) { + QSqlQuery query(m_db); + query.prepare("UPDATE routes SET available_seats = available_seats + ? WHERE id = ?"); + query.addBindValue(change); + query.addBindValue(routeId); + return query.exec(); +} + +QVector Database::getTicketsByRoute(int routeId) { + QVector tickets; + QSqlQuery query(m_db); + query.prepare("SELECT t.id, t.route_id, r.route_name, t.passenger_name, " + "t.document_number, t.seat_count, t.total_price, t.seller_id, " + "u.username, t.sale_date " + "FROM tickets t " + "JOIN routes r ON t.route_id = r.id " + "JOIN users u ON t.seller_id = u.id " + "WHERE t.route_id = :route_id"); + query.bindValue(":route_id", routeId); + + query.exec(); + + while (query.next()) { + Ticket ticket; + ticket.id = query.value(0).toInt(); + ticket.routeId = query.value(1).toInt(); + ticket.routeName = query.value(2).toString(); + ticket.passengerName = query.value(3).toString(); + ticket.documentNumber = query.value(4).toString(); + ticket.seatCount = query.value(5).toInt(); + ticket.totalPrice = query.value(6).toDouble(); + ticket.sellerId = query.value(7).toInt(); + ticket.sellerName = query.value(8).toString(); + ticket.saleDate = query.value(9).toString(); + tickets.append(ticket); + } + return tickets; +} + +QVector Database::getAllTickets() { + QVector tickets; + QSqlQuery query("SELECT t.id, t.route_id, r.route_name, t.passenger_name, " + "t.document_number, t.seat_count, t.total_price, t.seller_id, " + "u.username, t.sale_date " + "FROM tickets t " + "JOIN routes r ON t.route_id = r.id " + "JOIN users u ON t.seller_id = u.id " + "ORDER BY t.sale_date DESC",m_db); + + query.exec(); + + while (query.next()) { + Ticket ticket; + ticket.id = query.value(0).toInt(); + ticket.routeId = query.value(1).toInt(); + ticket.routeName = query.value(2).toString(); + ticket.passengerName = query.value(3).toString(); + ticket.documentNumber = query.value(4).toString(); + ticket.seatCount = query.value(5).toInt(); + ticket.totalPrice = query.value(6).toDouble(); + ticket.sellerId = query.value(7).toInt(); + ticket.sellerName = query.value(8).toString(); + ticket.saleDate = query.value(9).toString(); + tickets.append(ticket); + } + return tickets; +} + +bool Database::addTicket(int routeId, const QString& passengerName, + const QString& documentNumber, int seatCount, int sellerId) { + Route route = getRoute(routeId); + double totalPrice = route.price * seatCount; + + QSqlQuery query(m_db); + query.prepare("INSERT INTO tickets (route_id, passenger_name, document_number, " + "seat_count, total_price, seller_id, sale_date) VALUES (?, ?, ?, ?, ?, ?, ?)"); + query.addBindValue(routeId); + query.addBindValue(passengerName); + query.addBindValue(documentNumber); + query.addBindValue(seatCount); + query.addBindValue(totalPrice); + query.addBindValue(sellerId); + query.addBindValue(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); + + if (query.exec()) { + return updateAvailableSeats(routeId, -seatCount); + } + return false; +} + +bool Database::deleteTicket(int id) { + QSqlQuery query(m_db); + query.prepare("SELECT route_id, seat_count FROM tickets WHERE id = ?"); + query.addBindValue(id); + + if (query.exec() && query.next()) { + int routeId = query.value(0).toInt(); + int seatCount = query.value(1).toInt(); + + query.prepare("DELETE FROM tickets WHERE id = ?"); + query.addBindValue(id); + + if (query.exec()) { + return updateAvailableSeats(routeId, seatCount); + } + } + return false; +} + +int Database::getLastTicketId() { + QSqlQuery query("SELECT last_insert_rowid()", m_db); + if (query.next()) { + return query.value(0).toInt(); + } + return -1; +} diff --git a/database.h b/database.h new file mode 100644 index 0000000..ed2a864 --- /dev/null +++ b/database.h @@ -0,0 +1,77 @@ +#ifndef DATABASE_H +#define DATABASE_H + +#include +#include +#include +#include +#include +#include + +struct User { + int id; + QString username; + QString password; + QString role; +}; + +struct Route { + int id; + QString routeName; + QString departureTime; + double price; + int totalSeats; + int availableSeats; +}; + +struct Ticket { + int id; + int routeId; + QString routeName; + QString passengerName; + QString documentNumber; + int seatCount; + double totalPrice; + int sellerId; + QString sellerName; + QString saleDate; +}; + +class Database { +public: + static Database& instance(); + bool initialize(); + + bool authenticate(const QString& username, const QString& password); + User getUser(const QString& username); + QVector getAllUsers(); + bool addUser(const QString& username, const QString& password, const QString& role); + bool deleteUser(int id); + + QVector getAllRoutes(); + Route getRoute(int id); + bool addRoute(const QString& routeName, const QString& departureTime, + double price, int totalSeats); + bool updateRoute(const Route& route); + bool deleteRoute(int id); + bool updateAvailableSeats(int routeId, int change); + + QVector getTicketsByRoute(int routeId); + QVector getAllTickets(); + bool addTicket(int routeId, const QString& passengerName, + const QString& documentNumber, int seatCount, + int sellerId); + bool deleteTicket(int id); + int getLastTicketId(); + +private: + Database() = default; + ~Database() = default; + Database(const Database&) = delete; + Database& operator=(const Database&) = delete; + + QSqlDatabase m_db; + QString hashPassword(const QString& password); +}; + +#endif diff --git a/loginwindow.cpp b/loginwindow.cpp new file mode 100644 index 0000000..0e4f23a --- /dev/null +++ b/loginwindow.cpp @@ -0,0 +1,107 @@ +#include "loginwindow.h" +#include "database.h" +#include + +LoginWindow::LoginWindow(QWidget* parent) + : QDialog(parent), isRegistering(false) +{ + setWindowTitle("Авторизация - Система вокзала"); + setFixedSize(400, 280); + setModal(true); + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(15); + mainLayout->setContentsMargins(30, 30, 30, 30); + + QLabel* titleLabel = new QLabel("Система учета вокзала", this); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setStyleSheet("font-size: 18px; font-weight: bold;"); + mainLayout->addWidget(titleLabel); + + QLabel* userLabel = new QLabel("Логин:", this); + m_usernameEdit = new QLineEdit(this); + m_usernameEdit->setPlaceholderText("Введите логин"); + mainLayout->addWidget(userLabel); + mainLayout->addWidget(m_usernameEdit); + + QLabel* passLabel = new QLabel("Пароль:", this); + m_passwordEdit = new QLineEdit(this); + m_passwordEdit->setPlaceholderText("Введите пароль"); + m_passwordEdit->setEchoMode(QLineEdit::Password); + mainLayout->addWidget(passLabel); + mainLayout->addWidget(m_passwordEdit); + + m_messageLabel = new QLabel(this); + m_messageLabel->setAlignment(Qt::AlignCenter); + m_messageLabel->setStyleSheet("color: red;"); + mainLayout->addWidget(m_messageLabel); + + QHBoxLayout* btnLayout = new QHBoxLayout(); + m_loginBtn = new QPushButton("Войти", this); + m_registerBtn = new QPushButton("Регистрация", this); + btnLayout->addWidget(m_loginBtn); + btnLayout->addWidget(m_registerBtn); + mainLayout->addLayout(btnLayout); + + connect(m_loginBtn, &QPushButton::clicked, this, &LoginWindow::onLoginClicked); + connect(m_registerBtn, &QPushButton::clicked, this, &LoginWindow::onRegisterClicked); +} + +void LoginWindow::onLoginClicked() { + QString username = m_usernameEdit->text().trimmed(); + QString password = m_passwordEdit->text(); + + if (username.isEmpty() || password.isEmpty()) { + m_messageLabel->setText("Заполните все поля!"); + return; + } + + if (Database::instance().authenticate(username, password)) { + User user = Database::instance().getUser(username); + m_username = user.username; + m_role = user.role; + accept(); + } else { + m_messageLabel->setText("Неверный логин или пароль!"); + m_passwordEdit->clear(); + } +} + +void LoginWindow::onRegisterClicked() { + if (!isRegistering) { + isRegistering = true; + m_loginBtn->setText("Зарегистрироваться"); + m_registerBtn->setText("Отмена"); + m_messageLabel->setText("Регистрация нового пользователя"); + m_messageLabel->setStyleSheet("color: green;"); + } else { + QString username = m_usernameEdit->text().trimmed(); + QString password = m_passwordEdit->text(); + + if (username.isEmpty() || password.isEmpty()) { + m_messageLabel->setText("Заполните все поля!"); + m_messageLabel->setStyleSheet("color: red;"); + return; + } + + if (password.length() < 4) { + m_messageLabel->setText("Пароль должен быть >= 4 символов!"); + m_messageLabel->setStyleSheet("color: red;"); + return; + } + + if (Database::instance().addUser(username, password, "cashier")) { + QMessageBox::information(this, "Успех", "Пользователь зарегистрирован!"); + isRegistering = false; + m_loginBtn->setText("Войти"); + m_registerBtn->setText("Регистрация"); + m_messageLabel->setText(""); + m_messageLabel->setStyleSheet("color: red;"); + m_usernameEdit->clear(); + m_passwordEdit->clear(); + } else { + m_messageLabel->setText("Ошибка: пользователь уже существует!"); + m_messageLabel->setStyleSheet("color: red;"); + } + } +} diff --git a/loginwindow.h b/loginwindow.h new file mode 100644 index 0000000..a2aa3af --- /dev/null +++ b/loginwindow.h @@ -0,0 +1,38 @@ +#ifndef LOGINWINDOW_H +#define LOGINWINDOW_H + +#include +#include +#include +#include +#include +#include + +class LoginWindow : public QDialog { + Q_OBJECT + +public: + explicit LoginWindow(QWidget* parent = nullptr); + QString getUsername() const { return m_username; } + QString getRole() const { return m_role; } + +signals: + void loginSuccessful(); + +private slots: + void onLoginClicked(); + void onRegisterClicked(); + +private: + QLineEdit* m_usernameEdit; + QLineEdit* m_passwordEdit; + QPushButton* m_loginBtn; + QPushButton* m_registerBtn; + QLabel* m_messageLabel; + QString m_username; + QString m_role; + + bool isRegistering; +}; + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..6b333b6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include "database.h" +#include "loginwindow.h" +#include "mainwindow.h" + +int main(int argc, char* argv[]) { + QApplication a(argc, argv); + a.setApplicationName("StationManager"); + a.setOrganizationName("Station"); + + if (!Database::instance().initialize()) { + QMessageBox::critical(nullptr, "Ошибка", + "Не удалось инициализировать базу данных!"); + return 1; + } + + LoginWindow login; + if (login.exec() != QDialog::Accepted) { + return 0; + } + + MainWindow w(login.getUsername(), login.getRole()); + w.show(); + + return QApplication::exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..68bf325 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,84 @@ +#include "mainwindow.h" +#include "routeswidget.h" +#include "ticketswidget.h" +#include "printwidget.h" +#include "userswidget.h" +#include "database.h" +#include +#include +#include +#include + +MainWindow::MainWindow(const QString& username, const QString& role, QWidget* parent) + : QMainWindow(parent), m_username(username), m_role(role) +{ + setWindowTitle("Система учета вокзала"); + setMinimumSize(900, 600); + + User user = Database::instance().getUser(username); + m_userId = user.id; + + QMenuBar* menuBar = this->menuBar(); + QMenu* menu = menuBar->addMenu("Меню"); + + QAction* routesAction = menu->addAction("Управление рейсами"); + QAction* ticketsAction = menu->addAction("Продажа билетов"); + QAction* printAction = menu->addAction("Печать билетов"); + menu->addSeparator(); + QAction* logoutAction = menu->addAction("Выйти"); + + connect(routesAction, &QAction::triggered, this, &MainWindow::showRoutes); + connect(ticketsAction, &QAction::triggered, this, &MainWindow::showTickets); + connect(printAction, &QAction::triggered, this, &MainWindow::showPrint); + connect(logoutAction, &QAction::triggered, this, &MainWindow::logout); + + m_stack = new QStackedWidget(this); + setCentralWidget(m_stack); + + m_routesWidget = new RoutesWidget(this); + m_ticketsWidget = new TicketsWidget(this); + m_printWidget = new PrintWidget(this); + m_usersWidget = new UsersWidget(this); + + m_stack->addWidget(m_routesWidget); + m_stack->addWidget(m_ticketsWidget); + m_stack->addWidget(m_printWidget); + m_stack->addWidget(m_usersWidget); + + m_statusLabel = new QLabel(QString("Пользователь: %1 | Роль: %2").arg(username, role)); + statusBar()->addWidget(m_statusLabel); + + if (role == "admin") { + QMenu* userMenu = new QMenu("Пользователи", menuBar); + QAction* usersAction = menu->insertMenu(printAction, userMenu); + QAction* manageUsersAction = userMenu->addAction("Управление пользователями"); + connect(manageUsersAction, &QAction::triggered, this, &MainWindow::showUsers); + } else { + m_usersWidget->setVisible(false); + } + + showRoutes(); +} + +void MainWindow::showRoutes() { + m_stack->setCurrentWidget(m_routesWidget); +} + +void MainWindow::showTickets() { + m_ticketsWidget->refreshRoutes(); + m_stack->setCurrentWidget(m_ticketsWidget); +} + +void MainWindow::showPrint() { + m_printWidget->refreshRoutes(); + m_stack->setCurrentWidget(m_printWidget); +} + +void MainWindow::showUsers() { + m_usersWidget->refreshUsers(); + m_stack->setCurrentWidget(m_usersWidget); +} + +void MainWindow::logout() { + QApplication::quit(); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..d35f37d --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include + +class RoutesWidget; +class TicketsWidget; +class PrintWidget; +class UsersWidget; + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit MainWindow(const QString& username, const QString& role, QWidget* parent = nullptr); + QString currentUser() const { return m_username; } + int currentUserId() const { return m_userId; } + QString currentRole() const { return m_role; } + +public slots: + void showRoutes(); + void showTickets(); + void showPrint(); + void showUsers(); + void logout(); + +private: + QStackedWidget* m_stack; + RoutesWidget* m_routesWidget; + TicketsWidget* m_ticketsWidget; + PrintWidget* m_printWidget; + UsersWidget* m_usersWidget; + QLabel* m_statusLabel; + QString m_username; + QString m_role; + int m_userId; +}; + +#endif diff --git a/printwidget.cpp b/printwidget.cpp new file mode 100644 index 0000000..3e23ad4 --- /dev/null +++ b/printwidget.cpp @@ -0,0 +1,145 @@ +#include "printwidget.h" +#include "database.h" +#include +#include +#include +#include +#include +#include + +PrintWidget::PrintWidget(QWidget* parent) : QWidget(parent) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + QLabel* title = new QLabel("Печать списка билетов", this); + title->setStyleSheet("font-size: 20px; font-weight: bold;"); + mainLayout->addWidget(title); + + QHBoxLayout* filterLayout = new QHBoxLayout(); + QLabel* routeLabel = new QLabel("Выберите рейс:", this); + m_routeCombo = new QComboBox(this); + filterLayout->addWidget(routeLabel); + filterLayout->addWidget(m_routeCombo); + filterLayout->addStretch(); + mainLayout->addLayout(filterLayout); + + m_table = new QTableWidget(this); + m_table->setColumnCount(8); + m_table->setHorizontalHeaderLabels({"ID", "Маршрут", "Время", "Пассажир", "Документ", "Билетов", "Сумма", "Кассир"}); + m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + mainLayout->addWidget(m_table); + + m_totalLabel = new QLabel("Итого: 0 билетов на сумму 0.00 руб.", this); + m_totalLabel->setStyleSheet("font-weight: bold; font-size: 14px;"); + mainLayout->addWidget(m_totalLabel); + + QHBoxLayout* btnLayout = new QHBoxLayout(); + QPushButton* refreshBtn = new QPushButton("Обновить", this); + QPushButton* printBtn = new QPushButton("Экспорт в файл", this); + btnLayout->addWidget(refreshBtn); + btnLayout->addWidget(printBtn); + btnLayout->addStretch(); + mainLayout->addLayout(btnLayout); + + connect(m_routeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &PrintWidget::onRouteChanged); + connect(refreshBtn, &QPushButton::clicked, this, [this]() { + refreshRoutes(); + onRouteChanged(m_routeCombo->currentIndex()); + }); + connect(printBtn, &QPushButton::clicked, this, &PrintWidget::onPrintClicked); +} + +void PrintWidget::refreshRoutes() { + m_routeCombo->clear(); + QVector routes = Database::instance().getAllRoutes(); + for (const auto& route : routes) { + m_routeCombo->addItem(QString("%1 - %2").arg(route.routeName).arg(route.departureTime), route.id); + } +} + +void PrintWidget::onRouteChanged(int index) { + if (index < 0) return; + int routeId = m_routeCombo->currentData().toInt(); + loadTickets(routeId); +} + +void PrintWidget::loadTickets(int routeId) { + QVector tickets = Database::instance().getTicketsByRoute(routeId); + m_table->setRowCount(tickets.size()); + qDebug() << "TICKET COUNT:" + std::to_string(tickets.length()) + " " + std::to_string(routeId); + + double total = 0; + int totalSeats = 0; + + for (int i = 0; i < tickets.size(); ++i) { + const Ticket& ticket = tickets[i]; + m_table->setItem(i, 0, new QTableWidgetItem(QString::number(ticket.id))); + m_table->setItem(i, 1, new QTableWidgetItem(ticket.routeName)); + m_table->setItem(i, 2, new QTableWidgetItem(ticket.saleDate)); + m_table->setItem(i, 3, new QTableWidgetItem(ticket.passengerName)); + m_table->setItem(i, 4, new QTableWidgetItem(ticket.documentNumber)); + m_table->setItem(i, 5, new QTableWidgetItem(QString::number(ticket.seatCount))); + m_table->setItem(i, 6, new QTableWidgetItem(QString::number(ticket.totalPrice, 'f', 2))); + m_table->setItem(i, 7, new QTableWidgetItem(ticket.sellerName)); + + total += ticket.totalPrice; + totalSeats += ticket.seatCount; + } + + m_totalLabel->setText(QString("Итого: %1 билетов на сумму %2 руб.") + .arg(totalSeats) + .arg(total, 0, 'f', 2)); +} + +void PrintWidget::onPrintClicked() { + if (m_table->rowCount() == 0) { + QMessageBox::warning(this, "Внимание", "Нет данных для печати!"); + return; + } + + QString fileName = QFileDialog::getSaveFileName( + this, "Сохранить список билетов", + QString("tickets_%1.txt").arg(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")), + "Text Files (*.txt)"); + + if (fileName.isEmpty()) return; + + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Ошибка", "Не удалось создать файл!"); + return; + } + + QTextStream out(&file); + out << "===========================================\n"; + out << " СПИСОК ПРОДАННЫХ БИЛЕТОВ\n"; + out << "===========================================\n"; + out << "Рейс: " << m_routeCombo->currentText() << "\n"; + out << "Дата формирования: " << QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss") << "\n"; + out << "===========================================\n\n"; + + int totalSeats = 0; + double totalSum = 0; + + for (int i = 0; i < m_table->rowCount(); ++i) { + out << QString("Билет #%1\n").arg(m_table->item(i, 0)->text()); + out << QString(" Пассажир: %1\n").arg(m_table->item(i, 3)->text()); + out << QString(" Документ: %1\n").arg(m_table->item(i, 4)->text()); + out << QString(" Количество: %1 шт.\n").arg(m_table->item(i, 5)->text()); + out << QString(" Сумма: %1 руб.\n").arg(m_table->item(i, 6)->text()); + out << QString(" Продал: %1 (%2)\n").arg(m_table->item(i, 7)->text()).arg(m_table->item(i, 2)->text()); + out << "-----------------------------------------\n"; + + totalSeats += m_table->item(i, 5)->text().toInt(); + totalSum += m_table->item(i, 6)->text().toDouble(); + } + + out << "\n===========================================\n"; + out << QString("ИТОГО: %1 билетов на сумму %2 руб.\n").arg(totalSeats).arg(totalSum, 0, 'f', 2); + out << "===========================================\n"; + + file.close(); + QMessageBox::information(this, "Успех", QString("Файл сохранен:\n%1").arg(fileName)); +} diff --git a/printwidget.h b/printwidget.h new file mode 100644 index 0000000..2638771 --- /dev/null +++ b/printwidget.h @@ -0,0 +1,31 @@ +#ifndef PRINTWIDGET_H +#define PRINTWIDGET_H + +#include +#include +#include +#include +#include +#include +#include + +class PrintWidget : public QWidget { + Q_OBJECT + +public: + explicit PrintWidget(QWidget* parent = nullptr); + void refreshRoutes(); + +private slots: + void onRouteChanged(int index); + void onPrintClicked(); + +private: + void loadTickets(int routeId); + + QComboBox* m_routeCombo; + QTableWidget* m_table; + QLabel* m_totalLabel; +}; + +#endif diff --git a/routeswidget.cpp b/routeswidget.cpp new file mode 100644 index 0000000..513297f --- /dev/null +++ b/routeswidget.cpp @@ -0,0 +1,160 @@ +#include "routeswidget.h" +#include "database.h" +#include +#include +#include +#include + +RoutesWidget::RoutesWidget(QWidget* parent) : QWidget(parent) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + QLabel* title = new QLabel("Управление рейсами", this); + title->setStyleSheet("font-size: 20px; font-weight: bold;"); + mainLayout->addWidget(title); + + m_table = new QTableWidget(this); + m_table->setColumnCount(6); + m_table->setHorizontalHeaderLabels({"ID", "Маршрут", "Время отправления", "Цена", "Всего мест", "Свободно"}); + m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + mainLayout->addWidget(m_table); + + QHBoxLayout* btnLayout = new QHBoxLayout(); + m_addBtn = new QPushButton("Добавить", this); + m_editBtn = new QPushButton("Редактировать", this); + m_deleteBtn = new QPushButton("Удалить", this); + btnLayout->addWidget(m_addBtn); + btnLayout->addWidget(m_editBtn); + btnLayout->addWidget(m_deleteBtn); + btnLayout->addStretch(); + mainLayout->addLayout(btnLayout); + + connect(m_addBtn, &QPushButton::clicked, this, &RoutesWidget::onAddClicked); + connect(m_editBtn, &QPushButton::clicked, this, &RoutesWidget::onEditClicked); + connect(m_deleteBtn, &QPushButton::clicked, this, &RoutesWidget::onDeleteClicked); + + loadRoutes(); +} + +void RoutesWidget::loadRoutes() { + QVector routes = Database::instance().getAllRoutes(); + m_table->setRowCount(routes.size()); + + for (int i = 0; i < routes.size(); ++i) { + const Route& route = routes[i]; + m_table->setItem(i, 0, new QTableWidgetItem(QString::number(route.id))); + m_table->setItem(i, 1, new QTableWidgetItem(route.routeName)); + m_table->setItem(i, 2, new QTableWidgetItem(route.departureTime)); + m_table->setItem(i, 3, new QTableWidgetItem(QString::number(route.price, 'f', 2))); + m_table->setItem(i, 4, new QTableWidgetItem(QString::number(route.totalSeats))); + m_table->setItem(i, 5, new QTableWidgetItem(QString::number(route.availableSeats))); + } +} + +void RoutesWidget::onAddClicked() { + RouteDialog dialog; + if (dialog.exec() == QDialog::Accepted) { + if (Database::instance().addRoute( + dialog.getRouteName(), + dialog.getDepartureTime(), + dialog.getPrice(), + dialog.getTotalSeats())) { + loadRoutes(); + QMessageBox::information(this, "Успех", "Рейс добавлен!"); + } + } +} + +void RoutesWidget::onEditClicked() { + int row = m_table->currentRow(); + if (row < 0) { + QMessageBox::warning(this, "Ошибка", "Выберите рейс для редактирования!"); + return; + } + + int id = m_table->item(row, 0)->text().toInt(); + Route route = Database::instance().getRoute(id); + + RouteDialog dialog(&route, this); + if (dialog.exec() == QDialog::Accepted) { + route.routeName = dialog.getRouteName(); + route.departureTime = dialog.getDepartureTime(); + route.price = dialog.getPrice(); + + if (route.totalSeats != dialog.getTotalSeats()) { + int diff = dialog.getTotalSeats() - route.totalSeats; + route.totalSeats = dialog.getTotalSeats(); + route.availableSeats += diff; + if (route.availableSeats < 0) route.availableSeats = 0; + } + + if (Database::instance().updateRoute(route)) { + loadRoutes(); + QMessageBox::information(this, "Успех", "Рейс обновлен!"); + } + } +} + +void RoutesWidget::onDeleteClicked() { + int row = m_table->currentRow(); + if (row < 0) { + QMessageBox::warning(this, "Ошибка", "Выберите рейс для удаления!"); + return; + } + + int id = m_table->item(row, 0)->text().toInt(); + + QMessageBox::StandardButton reply = QMessageBox::question( + this, "Подтверждение", "Удалить выбранный рейс?", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + if (Database::instance().deleteRoute(id)) { + loadRoutes(); + QMessageBox::information(this, "Успех", "Рейс удален!"); + } + } +} + +RouteDialog::RouteDialog(Route* route, QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(route ? "Редактирование рейса" : "Добавление рейса"); + setFixedSize(400, 250); + + QFormLayout* form = new QFormLayout(this); + + m_nameEdit = new QLineEdit(this); + m_dateTimeEdit = new QDateTimeEdit(this); + m_dateTimeEdit->setCalendarPopup(true); + m_dateTimeEdit->setDateTime(QDateTime::currentDateTime().addDays(1)); + m_priceEdit = new QLineEdit(this); + m_priceEdit->setPlaceholderText("0.00"); + m_seatsEdit = new QLineEdit(this); + m_seatsEdit->setPlaceholderText("100"); + + if (route) { + m_nameEdit->setText(route->routeName); + m_dateTimeEdit->setDateTime(QDateTime::fromString(route->departureTime, "yyyy-MM-dd HH:mm:ss")); + m_priceEdit->setText(QString::number(route->price, 'f', 2)); + m_seatsEdit->setText(QString::number(route->totalSeats)); + } + + form->addRow("Маршрут:", m_nameEdit); + form->addRow("Время отправления:", m_dateTimeEdit); + form->addRow("Цена билета:", m_priceEdit); + form->addRow("Всего мест:", m_seatsEdit); + + QHBoxLayout* btnLayout = new QHBoxLayout(); + QPushButton* okBtn = new QPushButton("ОК", this); + QPushButton* cancelBtn = new QPushButton("Отмена", this); + btnLayout->addStretch(); + btnLayout->addWidget(okBtn); + btnLayout->addWidget(cancelBtn); + form->addRow(btnLayout); + + connect(okBtn, &QPushButton::clicked, this, &QDialog::accept); + connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject); +} diff --git a/routeswidget.h b/routeswidget.h new file mode 100644 index 0000000..34f2c46 --- /dev/null +++ b/routeswidget.h @@ -0,0 +1,54 @@ +#ifndef ROUTESWIDGET_H +#define ROUTESWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Route; + +class RoutesWidget : public QWidget { + Q_OBJECT + +public: + explicit RoutesWidget(QWidget* parent = nullptr); + +private slots: + void loadRoutes(); + void onAddClicked(); + void onEditClicked(); + void onDeleteClicked(); + +private: + void showAddEditDialog(Route* route = nullptr); + + QTableWidget* m_table; + QPushButton* m_addBtn; + QPushButton* m_editBtn; + QPushButton* m_deleteBtn; +}; + +class RouteDialog : public QDialog { + Q_OBJECT +public: + explicit RouteDialog(Route* route = nullptr, QWidget* parent = nullptr); + QString getRouteName() const { return m_nameEdit->text(); } + QString getDepartureTime() const { return m_dateTimeEdit->text(); } + double getPrice() const { return m_priceEdit->text().toDouble(); } + int getTotalSeats() const { return m_seatsEdit->text().toInt(); } + +private: + QLineEdit* m_nameEdit; + QDateTimeEdit* m_dateTimeEdit; + QLineEdit* m_priceEdit; + QLineEdit* m_seatsEdit; +}; + +#endif diff --git a/ticketswidget.cpp b/ticketswidget.cpp new file mode 100644 index 0000000..1fc6796 --- /dev/null +++ b/ticketswidget.cpp @@ -0,0 +1,136 @@ +#include "ticketswidget.h" +#include "database.h" +#include "mainwindow.h" +#include +#include +#include +#include + +TicketsWidget::TicketsWidget(QWidget* parent) : QWidget(parent) { + auto* mainLayout = new QVBoxLayout(this); + + auto* title = new QLabel("Продажа билетов", this); + title->setStyleSheet("font-size: 20px; font-weight: bold;"); + mainLayout->addWidget(title); + + auto* sellGroup = new QGroupBox("Новый билет", this); + auto* sellForm = new QFormLayout(sellGroup); + + m_routeCombo = new QComboBox(this); + m_routeInfo = new QLabel("Выберите рейс", this); + m_passengerName = new QLineEdit(this); + m_passengerName->setPlaceholderText("Иванов Иван Иванович"); + m_documentNumber = new QLineEdit(this); + m_documentNumber->setPlaceholderText("Серия и номер паспорта"); + m_seatCount = new QSpinBox(this); + m_seatCount->setMinimum(1); + m_seatCount->setMaximum(10); + m_totalPrice = new QLabel("0.00 руб.", this); + m_sellBtn = new QPushButton("Продать билет", this); + + sellForm->addRow("Рейс:", m_routeCombo); + sellForm->addRow("", m_routeInfo); + sellForm->addRow("ФИО пассажира:", m_passengerName); + sellForm->addRow("Документ:", m_documentNumber); + sellForm->addRow("Количество билетов:", m_seatCount); + sellForm->addRow("Итого:", m_totalPrice); + sellForm->addRow("", m_sellBtn); + + mainLayout->addWidget(sellGroup); + + connect(m_routeCombo, QOverload::of(&QComboBox::currentIndexChanged), + this, &TicketsWidget::onRouteChanged); + connect(m_seatCount, QOverload::of(&QSpinBox::valueChanged), + this, &TicketsWidget::onRouteChanged); + connect(m_sellBtn, &QPushButton::clicked, this, &TicketsWidget::onSellClicked); + + auto* ticketsLabel = new QLabel("Проданные билеты на выбранный рейс", this); + ticketsLabel->setStyleSheet("font-weight: bold;"); + mainLayout->addWidget(ticketsLabel); + + m_ticketsTable = new QTableWidget(this); + m_ticketsTable->setColumnCount(6); + m_ticketsTable->setHorizontalHeaderLabels({"ID", "Пассажир", "Документ", "Билетов", "Сумма", "Продано"}); + m_ticketsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_ticketsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_ticketsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mainLayout->addWidget(m_ticketsTable); + + refreshRoutes(); +} + +void TicketsWidget::refreshRoutes() { + m_routeCombo->clear(); + QVector routes = Database::instance().getAllRoutes(); + for (const auto& route : routes) { + m_routeCombo->addItem(QString("%1 - %2").arg(route.routeName).arg(route.departureTime), route.id); + } +} + +void TicketsWidget::onRouteChanged(int index) { + if (index < 0) return; + + int routeId = m_routeCombo->currentData().toInt(); + Route route = Database::instance().getRoute(routeId); + + m_routeInfo->setText(QString("Цена: %1 руб. | Свободно: %2 мест") + .arg(route.price, 0, 'f', 2) + .arg(route.availableSeats)); + + m_seatCount->setMaximum(qMin(10, route.availableSeats)); + + double total = route.price * m_seatCount->value(); + m_totalPrice->setText(QString("%1 руб.").arg(total, 0, 'f', 2)); + + QVector tickets = Database::instance().getTicketsByRoute(routeId); + m_ticketsTable->setRowCount(tickets.size()); + + for (int i = 0; i < tickets.size(); ++i) { + const Ticket& ticket = tickets[i]; + m_ticketsTable->setItem(i, 0, new QTableWidgetItem(QString::number(ticket.id))); + m_ticketsTable->setItem(i, 1, new QTableWidgetItem(ticket.passengerName)); + m_ticketsTable->setItem(i, 2, new QTableWidgetItem(ticket.documentNumber)); + m_ticketsTable->setItem(i, 3, new QTableWidgetItem(QString::number(ticket.seatCount))); + m_ticketsTable->setItem(i, 4, new QTableWidgetItem(QString::number(ticket.totalPrice, 'f', 2))); + m_ticketsTable->setItem(i, 5, new QTableWidgetItem(ticket.saleDate)); + } +} + +void TicketsWidget::onSellClicked() { + int routeId = m_routeCombo->currentData().toInt(); + Route route = Database::instance().getRoute(routeId); + + QString passengerName = m_passengerName->text().trimmed(); + QString documentNumber = m_documentNumber->text().trimmed(); + int seatCount = m_seatCount->value(); + + if (passengerName.isEmpty() || documentNumber.isEmpty()) { + QMessageBox::warning(this, "Ошибка", "Заполните все поля!"); + return; + } + + if (seatCount > route.availableSeats) { + QMessageBox::warning(this, "Ошибка", + QString("Недостаточно мест! Доступно: %1").arg(route.availableSeats)); + return; + } + + MainWindow* mainWin = qobject_cast(window()); + int sellerId = mainWin ? mainWin->currentUserId() : 1; + + if (Database::instance().addTicket(routeId, passengerName, documentNumber, seatCount, sellerId)) { + QMessageBox::information(this, "Успех", + QString("Продано %1 билет(а) на сумму %2 руб.") + .arg(seatCount) + .arg(route.price * seatCount, 0, 'f', 2)); + + m_passengerName->clear(); + m_documentNumber->clear(); + m_seatCount->setValue(1); + + refreshRoutes(); + onRouteChanged(m_routeCombo->currentIndex()); + } else { + QMessageBox::critical(this, "Ошибка", "Не удалось продать билет!"); + } +} diff --git a/ticketswidget.h b/ticketswidget.h new file mode 100644 index 0000000..74108e6 --- /dev/null +++ b/ticketswidget.h @@ -0,0 +1,37 @@ +#ifndef TICKETSWIDGET_H +#define TICKETSWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TicketsWidget : public QWidget { + Q_OBJECT + +public: + explicit TicketsWidget(QWidget* parent = nullptr); + void refreshRoutes(); + +private slots: + void onRouteChanged(int index); + void onSellClicked(); + +private: + QComboBox* m_routeCombo; + QLabel* m_routeInfo; + QLineEdit* m_passengerName; + QLineEdit* m_documentNumber; + QSpinBox* m_seatCount; + QLabel* m_totalPrice; + QPushButton* m_sellBtn; + QTableWidget* m_ticketsTable; +}; + +#endif diff --git a/userswidget.cpp b/userswidget.cpp new file mode 100644 index 0000000..3376e1c --- /dev/null +++ b/userswidget.cpp @@ -0,0 +1,98 @@ +#include "userswidget.h" +#include "database.h" +#include +#include +#include +#include +#include + +UsersWidget::UsersWidget(QWidget* parent) : QWidget(parent) { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + QLabel* title = new QLabel("Управление пользователями", this); + title->setStyleSheet("font-size: 20px; font-weight: bold;"); + mainLayout->addWidget(title); + + m_table = new QTableWidget(this); + m_table->setColumnCount(3); + m_table->setHorizontalHeaderLabels({"ID", "Логин", "Роль"}); + m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + mainLayout->addWidget(m_table); + + QHBoxLayout* btnLayout = new QHBoxLayout(); + m_addBtn = new QPushButton("Добавить пользователя", this); + m_deleteBtn = new QPushButton("Удалить пользователя", this); + btnLayout->addWidget(m_addBtn); + btnLayout->addWidget(m_deleteBtn); + btnLayout->addStretch(); + mainLayout->addLayout(btnLayout); + + connect(m_addBtn, &QPushButton::clicked, this, &UsersWidget::onAddClicked); + connect(m_deleteBtn, &QPushButton::clicked, this, &UsersWidget::onDeleteClicked); + + refreshUsers(); +} + +void UsersWidget::refreshUsers() { + QVector users = Database::instance().getAllUsers(); + m_table->setRowCount(users.size()); + + for (int i = 0; i < users.size(); ++i) { + const User& user = users[i]; + m_table->setItem(i, 0, new QTableWidgetItem(QString::number(user.id))); + m_table->setItem(i, 1, new QTableWidgetItem(user.username)); + m_table->setItem(i, 2, new QTableWidgetItem(user.role == "admin" ? "Администратор" : "Кассир")); + } +} + +void UsersWidget::onAddClicked() { + QString username = QInputDialog::getText(this, "Новый пользователь", "Логин:"); + if (username.isEmpty()) return; + + QString password = QInputDialog::getText(this, "Новый пользователь", "Пароль:", QLineEdit::Password); + if (password.isEmpty()) return; + + QStringList roles = {"cashier", "admin"}; + QString role = QInputDialog::getItem(this, "Новый пользователь", "Роль:", roles, 0, false); + if (role.isEmpty()) return; + + if (Database::instance().addUser(username, password, role)) { + QMessageBox::information(this, "Успех", "Пользователь добавлен!"); + refreshUsers(); + } else { + QMessageBox::critical(this, "Ошибка", "Пользователь с таким логином уже существует!"); + } +} + +void UsersWidget::onDeleteClicked() { + int row = m_table->currentRow(); + if (row < 0) { + QMessageBox::warning(this, "Ошибка", "Выберите пользователя!"); + return; + } + + QString username = m_table->item(row, 1)->text(); + QString role = m_table->item(row, 2)->text(); + + if (role == "Администратор") { + QMessageBox::warning(this, "Ошибка", "Нельзя удалить администратора!"); + return; + } + + int id = m_table->item(row, 0)->text().toInt(); + + QMessageBox::StandardButton reply = QMessageBox::question( + this, "Подтверждение", + QString("Удалить пользователя '%1'?").arg(username), + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + if (Database::instance().deleteUser(id)) { + QMessageBox::information(this, "Успех", "Пользователь удален!"); + refreshUsers(); + } + } +} diff --git a/userswidget.h b/userswidget.h new file mode 100644 index 0000000..4f0b6d0 --- /dev/null +++ b/userswidget.h @@ -0,0 +1,27 @@ +#ifndef USERSWIDGET_H +#define USERSWIDGET_H + +#include +#include +#include +#include +#include + +class UsersWidget : public QWidget { + Q_OBJECT + +public: + explicit UsersWidget(QWidget* parent = nullptr); + void refreshUsers(); + +private slots: + void onAddClicked(); + void onDeleteClicked(); + +private: + QTableWidget* m_table; + QPushButton* m_addBtn; + QPushButton* m_deleteBtn; +}; + +#endif