commit 590975b1f0771407dfc42884b9287ea8e6049d45 Author: CinkaFox Date: Sun Apr 5 16:14:54 2026 +0300 - init: inti repo 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