master-floor/control1-2.py

1302 lines
55 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import sqlite3
from datetime import datetime
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QTextEdit, QComboBox, QPushButton, QTableWidget,
QTableWidgetItem, QTabWidget, QGroupBox, QMessageBox, QFileDialog,
QSplitter, QHeaderView, QFormLayout, QCheckBox, QDialog, QDateEdit)
from PyQt6.QtCore import Qt, pyqtSignal, QDate
from PyQt6.QtGui import QIcon, QAction
import os
class DatabaseManager:
def __init__(self):
self.conn = sqlite3.connect('service_requests_v2.db')
self.create_tables()
def create_tables(self):
cursor = self.conn.cursor()
# Таблица пользователей
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL,
full_name TEXT NOT NULL,
phone TEXT,
email TEXT
)
''')
# Таблица заявок с расширенными полями
cursor.execute('''
CREATE TABLE IF NOT EXISTS service_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_name TEXT NOT NULL,
client_phone TEXT NOT NULL,
client_email TEXT,
equipment_type TEXT NOT NULL,
equipment_model TEXT NOT NULL,
serial_number TEXT,
problem_description TEXT NOT NULL,
status TEXT DEFAULT 'Новая',
priority TEXT DEFAULT 'Средний',
request_type TEXT DEFAULT 'Ремонт',
assigned_operator TEXT,
assigned_master TEXT,
observer_group TEXT,
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_date TIMESTAMP,
deadline DATE,
marked_for_deletion BOOLEAN DEFAULT 0,
duplicate_of INTEGER
)
''')
# Таблица истории заявок
cursor.execute('''
CREATE TABLE IF NOT EXISTS request_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id INTEGER,
user_id INTEGER,
action TEXT,
details TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (request_id) REFERENCES service_requests (id)
)
''')
# Таблица вложений
cursor.execute('''
CREATE TABLE IF NOT EXISTS attachments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id INTEGER,
filename TEXT,
filepath TEXT,
file_type TEXT,
uploaded_by INTEGER,
upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (request_id) REFERENCES service_requests (id)
)
''')
# Таблица отчетов
cursor.execute('''
CREATE TABLE IF NOT EXISTS reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id INTEGER,
master_id INTEGER,
work_description TEXT,
parts_used TEXT,
time_spent REAL,
labor_cost REAL,
parts_cost REAL,
total_cost REAL,
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (request_id) REFERENCES service_requests (id)
)
''')
# Таблица заказов запчастей
cursor.execute('''
CREATE TABLE IF NOT EXISTS part_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id INTEGER,
part_name TEXT,
part_number TEXT,
quantity INTEGER,
supplier TEXT,
estimated_cost REAL,
status TEXT DEFAULT 'Заказан',
ordered_by INTEGER,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expected_date DATE,
FOREIGN KEY (request_id) REFERENCES service_requests (id)
)
''')
# Таблица МТР (материально-технических ресурсов)
cursor.execute('''
CREATE TABLE IF NOT EXISTS material_needs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
request_id INTEGER,
material_name TEXT,
material_type TEXT,
quantity INTEGER,
unit TEXT,
urgency TEXT DEFAULT 'Обычная',
status TEXT DEFAULT 'Требуется',
estimated_cost REAL,
notes TEXT,
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (request_id) REFERENCES service_requests (id)
)
''')
# Создаем тестовых пользователей для варианта 2
self.create_test_users()
self.conn.commit()
def create_test_users(self):
cursor = self.conn.cursor()
test_users = [
('operator1', '123', 'operator', 'Петр Петров', '+79167654321', 'operator@company.ru'),
('master1', '123', 'master', 'Сергей Сергеев', '+79169998877', 'master@company.ru'),
('admin1', '123', 'admin', 'Администратор Системы', '+79160001122', 'admin@company.ru')
]
for user in test_users:
try:
cursor.execute(
'INSERT INTO users (username, password, role, full_name, phone, email) VALUES (?, ?, ?, ?, ?, ?)',
user
)
except sqlite3.IntegrityError:
pass # Пользователь уже существует
self.conn.commit()
class ServiceRequestAppV2(QMainWindow):
def __init__(self):
super().__init__()
self.db = DatabaseManager()
self.current_user = None
self.init_ui()
def init_ui(self):
self.setWindowTitle('Система учета заявок на ремонт оргтехники - Вариант 2')
self.setGeometry(100, 100, 1400, 800)
# Центральный виджет
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Основной layout
layout = QVBoxLayout(central_widget)
# Панель входа
self.login_widget = self.create_login_widget()
layout.addWidget(self.login_widget)
# Основной интерфейс (скрыт до входа)
self.main_tabs = QTabWidget()
self.main_tabs.setVisible(False)
layout.addWidget(self.main_tabs)
def create_login_widget(self):
widget = QWidget()
layout = QVBoxLayout(widget)
layout.addWidget(QLabel('Вход в систему - Вариант 2'))
form_layout = QFormLayout()
self.username_input = QLineEdit()
self.password_input = QLineEdit()
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
form_layout.addRow('Логин:', self.username_input)
form_layout.addRow('Пароль:', self.password_input)
layout.addLayout(form_layout)
login_btn = QPushButton('Войти')
login_btn.clicked.connect(self.login)
layout.addWidget(login_btn)
# Подсказка с тестовыми пользователями
hint = QLabel('Тестовые пользователи: operator1/123, master1/123, admin1/123')
hint.setStyleSheet('color: gray; font-size: 10px;')
layout.addWidget(hint)
return widget
def login(self):
username = self.username_input.text()
password = self.password_input.text()
cursor = self.db.conn.cursor()
cursor.execute(
'SELECT * FROM users WHERE username = ? AND password = ?',
(username, password)
)
user = cursor.fetchone()
if user:
self.current_user = {
'id': user[0],
'username': user[1],
'role': user[3],
'full_name': user[4]
}
self.show_main_interface()
else:
QMessageBox.warning(self, 'Ошибка', 'Неверный логин или пароль')
def show_main_interface(self):
self.login_widget.setVisible(False)
self.main_tabs.setVisible(True)
# Очищаем предыдущие вкладки
self.main_tabs.clear()
role = self.current_user['role']
if role == 'operator':
self.setup_operator_interface()
elif role == 'master':
self.setup_master_interface()
elif role == 'admin':
self.setup_admin_interface()
def setup_operator_interface(self):
# Вкладка регистрации заявок
register_tab = QWidget()
layout = QVBoxLayout(register_tab)
layout.addWidget(QLabel('Регистрация новой заявки'))
form_layout = QFormLayout()
self.op_client_name = QLineEdit()
self.op_client_phone = QLineEdit()
self.op_client_email = QLineEdit()
self.op_equipment_type = QComboBox()
self.op_equipment_type.addItems(['Принтер', 'Копир', 'Сканер', 'МФУ', 'Компьютер', 'Монитор', 'Телефон', 'Другое'])
self.op_equipment_model = QLineEdit()
self.op_serial_number = QLineEdit()
self.op_problem_description = QTextEdit()
form_layout.addRow('ФИО клиента:', self.op_client_name)
form_layout.addRow('Телефон:', self.op_client_phone)
form_layout.addRow('Email:', self.op_client_email)
form_layout.addRow('Тип оборудования:', self.op_equipment_type)
form_layout.addRow('Модель:', self.op_equipment_model)
form_layout.addRow('Серийный номер:', self.op_serial_number)
form_layout.addRow('Описание проблемы:', self.op_problem_description)
layout.addLayout(form_layout)
submit_btn = QPushButton('Зарегистрировать заявку')
submit_btn.clicked.connect(self.register_service_request)
layout.addWidget(submit_btn)
self.main_tabs.addTab(register_tab, 'Регистрация заявки')
# Вкладка управления заявками
management_tab = QWidget()
layout = QVBoxLayout(management_tab)
# Панель фильтров
filter_layout = QHBoxLayout()
filter_layout.addWidget(QLabel('Статус:'))
self.status_filter = QComboBox()
self.status_filter.addItems(['Все', 'Новая', 'В работе', 'Ожидает запчасти', 'Выполнена', 'Отменена'])
filter_layout.addWidget(self.status_filter)
filter_layout.addWidget(QLabel('Приоритет:'))
self.priority_filter = QComboBox()
self.priority_filter.addItems(['Все', 'Низкий', 'Средний', 'Высокий', 'Критичный'])
filter_layout.addWidget(self.priority_filter)
filter_btn = QPushButton('Применить фильтры')
filter_btn.clicked.connect(self.load_operator_requests)
filter_layout.addWidget(filter_btn)
layout.addLayout(filter_layout)
self.operator_requests_table = QTableWidget()
self.operator_requests_table.setColumnCount(10)
self.operator_requests_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Тип', 'Дата', 'Оператор', 'Помечена на удаление'
])
layout.addWidget(self.operator_requests_table)
# Панель управления заявкой
control_group = QGroupBox('Управление заявкой')
control_layout = QHBoxLayout(control_group)
self.status_combo = QComboBox()
self.status_combo.addItems(['Новая', 'В работе', 'Ожидает запчасти', 'Выполнена', 'Отменена'])
self.priority_combo = QComboBox()
self.priority_combo.addItems(['Низкий', 'Средний', 'Высокий', 'Критичный'])
self.type_combo = QComboBox()
self.type_combo.addItems(['Ремонт', 'Обслуживание', 'Консультация', 'Диагностика'])
self.observer_group = QLineEdit()
self.observer_group.setPlaceholderText('Группа наблюдателей')
mark_delete_btn = QPushButton('Пометить на удаление')
mark_delete_btn.clicked.connect(self.mark_request_for_deletion)
update_btn = QPushButton('Обновить заявку')
update_btn.clicked.connect(self.update_request_operator)
control_layout.addWidget(QLabel('Статус:'))
control_layout.addWidget(self.status_combo)
control_layout.addWidget(QLabel('Приоритет:'))
control_layout.addWidget(self.priority_combo)
control_layout.addWidget(QLabel('Тип:'))
control_layout.addWidget(self.type_combo)
control_layout.addWidget(self.observer_group)
control_layout.addWidget(update_btn)
control_layout.addWidget(mark_delete_btn)
layout.addWidget(control_group)
self.main_tabs.addTab(management_tab, 'Управление заявками')
# Вкладка архива
archive_tab = QWidget()
layout = QVBoxLayout(archive_tab)
search_layout = QHBoxLayout()
self.archive_search = QLineEdit()
self.archive_search.setPlaceholderText('Поиск в архиве...')
search_btn = QPushButton('Найти')
search_btn.clicked.connect(self.search_archive)
search_layout.addWidget(self.archive_search)
search_layout.addWidget(search_btn)
layout.addLayout(search_layout)
self.archive_table = QTableWidget()
self.archive_table.setColumnCount(9)
self.archive_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Дата создания', 'Дата завершения', 'Мастер', 'Тип'
])
layout.addWidget(self.archive_table)
self.main_tabs.addTab(archive_tab, 'Архив')
self.load_operator_requests()
self.load_archive()
def setup_master_interface(self):
# Вкладка назначенных заявок
requests_tab = QWidget()
layout = QVBoxLayout(requests_tab)
self.master_requests_table = QTableWidget()
self.master_requests_table.setColumnCount(8)
self.master_requests_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Дата', 'Срок'
])
layout.addWidget(self.master_requests_table)
# Панель управления для мастера
control_group = QGroupBox('Управление ремонтом')
control_layout = QVBoxLayout(control_group)
# Смена статуса
status_layout = QHBoxLayout()
status_layout.addWidget(QLabel('Статус:'))
self.master_status_combo = QComboBox()
self.master_status_combo.addItems(['В работе', 'Ожидает запчасти', 'Выполнена', 'Отменена'])
status_layout.addWidget(self.master_status_combo)
update_status_btn = QPushButton('Обновить статус')
update_status_btn.clicked.connect(self.update_request_master)
status_layout.addWidget(update_status_btn)
control_layout.addLayout(status_layout)
# Заказ запчастей
parts_group = QGroupBox('Заказ запчастей')
parts_layout = QFormLayout(parts_group)
self.part_name = QLineEdit()
self.part_number = QLineEdit()
self.part_quantity = QLineEdit()
self.part_quantity.setText('1')
self.part_supplier = QLineEdit()
self.part_cost = QLineEdit()
parts_layout.addRow('Название запчасти:', self.part_name)
parts_layout.addRow('Номер запчасти:', self.part_number)
parts_layout.addRow('Количество:', self.part_quantity)
parts_layout.addRow('Поставщик:', self.part_supplier)
parts_layout.addRow('Примерная стоимость:', self.part_cost)
order_parts_btn = QPushButton('Заказать запчасти')
order_parts_btn.clicked.connect(self.order_parts)
parts_layout.addRow(order_parts_btn)
control_layout.addWidget(parts_group)
# Прикрепление файлов
file_layout = QHBoxLayout()
attach_photo_btn = QPushButton('Прикрепить фото')
attach_photo_btn.clicked.connect(self.attach_photo)
attach_file_btn = QPushButton('Прикрепить файл')
attach_file_btn.clicked.connect(self.attach_file)
file_layout.addWidget(attach_photo_btn)
file_layout.addWidget(attach_file_btn)
control_layout.addLayout(file_layout)
# Создание отчета
report_btn = QPushButton('Создать отчет о выполненной работе')
report_btn.clicked.connect(self.create_report)
control_layout.addWidget(report_btn)
# История заявки
history_btn = QPushButton('Просмотреть историю заявки')
history_btn.clicked.connect(self.show_request_history)
control_layout.addWidget(history_btn)
layout.addWidget(control_group)
self.main_tabs.addTab(requests_tab, 'Мои заявки')
# Вкладка заказанных запчастей
parts_tab = QWidget()
layout = QVBoxLayout(parts_tab)
self.parts_table = QTableWidget()
self.parts_table.setColumnCount(8)
self.parts_table.setHorizontalHeaderLabels([
'ID', 'Заявка', 'Запчасть', 'Номер', 'Кол-во', 'Статус', 'Дата заказа', 'Поставщик'
])
layout.addWidget(self.parts_table)
self.main_tabs.addTab(parts_tab, 'Заказанные запчасти')
self.load_master_requests()
self.load_master_parts()
def setup_admin_interface(self):
# Вкладка управления пользователями
users_tab = QWidget()
layout = QVBoxLayout(users_tab)
self.users_table = QTableWidget()
self.users_table.setColumnCount(6)
self.users_table.setHorizontalHeaderLabels(['ID', 'Логин', 'ФИО', 'Роль', 'Телефон', 'Email'])
layout.addWidget(self.users_table)
add_user_btn = QPushButton('Добавить пользователя')
add_user_btn.clicked.connect(self.show_add_user_dialog)
layout.addWidget(add_user_btn)
self.main_tabs.addTab(users_tab, 'Пользователи')
# Вкладка всех заявок
requests_tab = QWidget()
layout = QVBoxLayout(requests_tab)
self.admin_requests_table = QTableWidget()
self.admin_requests_table.setColumnCount(11)
self.admin_requests_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Приоритет', 'Тип', 'Дата', 'Оператор', 'Мастер', 'Помечена на удаление'
])
layout.addWidget(self.admin_requests_table)
delete_btn = QPushButton('Удалить выбранные заявки')
delete_btn.clicked.connect(self.delete_marked_requests)
layout.addWidget(delete_btn)
self.main_tabs.addTab(requests_tab, 'Все заявки')
# Вкладка распределения заявок
distribution_tab = QWidget()
layout = QVBoxLayout(distribution_tab)
self.distribution_table = QTableWidget()
self.distribution_table.setColumnCount(9)
self.distribution_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Статус', 'Оператор', 'Мастер', 'Группа наблюдателей', 'Срок исполнения', 'Приоритет'
])
layout.addWidget(self.distribution_table)
self.main_tabs.addTab(distribution_tab, 'Распределение заявок')
# Вкладка МТР (материально-технических ресурсов)
materials_tab = QWidget()
layout = QVBoxLayout(materials_tab)
# Панель управления МТР
mtr_control_layout = QHBoxLayout()
consolidate_btn = QPushButton('Консолидировать потребности в МТР')
consolidate_btn.clicked.connect(self.consolidate_material_needs)
generate_report_btn = QPushButton('Сформировать отчет по МТР')
generate_report_btn.clicked.connect(self.generate_mtr_report)
mtr_control_layout.addWidget(consolidate_btn)
mtr_control_layout.addWidget(generate_report_btn)
layout.addLayout(mtr_control_layout)
self.materials_table = QTableWidget()
self.materials_table.setColumnCount(10)
self.materials_table.setHorizontalHeaderLabels([
'ID', 'Заявка', 'Материал', 'Тип', 'Кол-во', 'Ед.изм', 'Срочность', 'Статус', 'Примерная стоимость', 'Примечания'
])
layout.addWidget(self.materials_table)
self.main_tabs.addTab(materials_tab, 'МТР')
# Вкладка архива
archive_tab = QWidget()
layout = QVBoxLayout(archive_tab)
search_layout = QHBoxLayout()
self.admin_archive_search = QLineEdit()
self.admin_archive_search.setPlaceholderText('Поиск в архиве...')
admin_search_btn = QPushButton('Найти')
admin_search_btn.clicked.connect(self.search_admin_archive)
search_layout.addWidget(self.admin_archive_search)
search_layout.addWidget(admin_search_btn)
layout.addLayout(search_layout)
self.admin_archive_table = QTableWidget()
self.admin_archive_table.setColumnCount(10)
self.admin_archive_table.setHorizontalHeaderLabels([
'ID', 'Клиент', 'Оборудование', 'Модель', 'Статус', 'Дата создания', 'Дата завершения', 'Мастер', 'Тип', 'Стоимость'
])
layout.addWidget(self.admin_archive_table)
self.main_tabs.addTab(archive_tab, 'Архив')
self.load_users()
self.load_admin_requests()
self.load_distribution()
self.load_materials()
self.load_admin_archive()
def register_service_request(self):
cursor = self.db.conn.cursor()
cursor.execute('''
INSERT INTO service_requests
(client_name, client_phone, client_email, equipment_type, equipment_model, serial_number, problem_description, assigned_operator)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
self.op_client_name.text(),
self.op_client_phone.text(),
self.op_client_email.text(),
self.op_equipment_type.currentText(),
self.op_equipment_model.text(),
self.op_serial_number.text(),
self.op_problem_description.toPlainText(),
self.current_user['full_name']
))
self.db.conn.commit()
# Запись в историю
request_id = cursor.lastrowid
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Создание заявки', 'Заявка зарегистрирована оператором'))
self.db.conn.commit()
QMessageBox.information(self, 'Успех', 'Заявка успешно зарегистрирована!')
# Очищаем форму
self.op_client_name.clear()
self.op_client_phone.clear()
self.op_client_email.clear()
self.op_equipment_model.clear()
self.op_serial_number.clear()
self.op_problem_description.clear()
def load_operator_requests(self):
cursor = self.db.conn.cursor()
status_filter = self.status_filter.currentText()
priority_filter = self.priority_filter.currentText()
query = '''
SELECT id, client_name, equipment_type, equipment_model, status, priority, request_type,
created_date, assigned_operator, marked_for_deletion
FROM service_requests
WHERE 1=1
'''
params = []
if status_filter != 'Все':
query += ' AND status = ?'
params.append(status_filter)
if priority_filter != 'Все':
query += ' AND priority = ?'
params.append(priority_filter)
query += ' ORDER BY created_date DESC'
cursor.execute(query, params)
requests = cursor.fetchall()
self.operator_requests_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
item = QTableWidgetItem(str(value) if value is not None else '')
# Помечаем заявки на удаление
if col == 9 and value == 1:
item.setBackground(Qt.GlobalColor.yellow)
self.operator_requests_table.setItem(row, col, item)
def load_master_requests(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT id, client_name, equipment_type, equipment_model, status, priority, created_date, deadline
FROM service_requests
WHERE status != 'Выполнена' AND status != 'Отменена'
ORDER BY
CASE priority
WHEN 'Критичный' THEN 1
WHEN 'Высокий' THEN 2
WHEN 'Средний' THEN 3
WHEN 'Низкий' THEN 4
END,
created_date
''')
requests = cursor.fetchall()
self.master_requests_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
item = QTableWidgetItem(str(value) if value is not None else '')
# Подсвечиваем просроченные заявки
if col == 7 and value:
deadline = QDate.fromString(value, 'yyyy-MM-dd')
if deadline < QDate.currentDate():
item.setBackground(Qt.GlobalColor.red)
self.master_requests_table.setItem(row, col, item)
def load_master_parts(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT po.id, sr.id, po.part_name, po.part_number, po.quantity, po.status, po.order_date, po.supplier
FROM part_orders po
JOIN service_requests sr ON po.request_id = sr.id
WHERE sr.assigned_master = ? OR ? = 'admin1'
ORDER BY po.order_date DESC
''', (self.current_user['full_name'], self.current_user['username']))
parts = cursor.fetchall()
self.parts_table.setRowCount(len(parts))
for row, part in enumerate(parts):
for col, value in enumerate(part):
self.parts_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def load_admin_requests(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT id, client_name, equipment_type, equipment_model, status, priority, request_type,
created_date, assigned_operator, assigned_master, marked_for_deletion
FROM service_requests
ORDER BY created_date DESC
''')
requests = cursor.fetchall()
self.admin_requests_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
item = QTableWidgetItem(str(value) if value is not None else '')
# Помечаем заявки на удаление
if col == 10 and value == 1:
item.setBackground(Qt.GlobalColor.yellow)
self.admin_requests_table.setItem(row, col, item)
def load_distribution(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT id, client_name, equipment_type, status, assigned_operator, assigned_master,
observer_group, deadline, priority
FROM service_requests
WHERE status != 'Выполнена' AND status != 'Отменена'
ORDER BY priority, created_date
''')
distributions = cursor.fetchall()
self.distribution_table.setRowCount(len(distributions))
for row, dist in enumerate(distributions):
for col, value in enumerate(dist):
item = QTableWidgetItem(str(value) if value is not None else '')
self.distribution_table.setItem(row, col, item)
def load_materials(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT mn.id, sr.id, mn.material_name, mn.material_type, mn.quantity, mn.unit,
mn.urgency, mn.status, mn.estimated_cost, mn.notes
FROM material_needs mn
JOIN service_requests sr ON mn.request_id = sr.id
ORDER BY mn.urgency DESC, mn.created_date
''')
materials = cursor.fetchall()
self.materials_table.setRowCount(len(materials))
for row, material in enumerate(materials):
for col, value in enumerate(material):
self.materials_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def load_archive(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master, request_type
FROM service_requests
WHERE status = 'Выполнена'
ORDER BY completed_date DESC
''')
requests = cursor.fetchall()
self.archive_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
self.archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def load_admin_archive(self):
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status,
sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type,
COALESCE(r.total_cost, 0)
FROM service_requests sr
LEFT JOIN reports r ON sr.id = r.request_id
WHERE sr.status = 'Выполнена'
ORDER BY sr.completed_date DESC
''')
requests = cursor.fetchall()
self.admin_archive_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
self.admin_archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def load_users(self):
cursor = self.db.conn.cursor()
cursor.execute('SELECT id, username, full_name, role, phone, email FROM users')
users = cursor.fetchall()
self.users_table.setRowCount(len(users))
for row, user in enumerate(users):
for col, value in enumerate(user):
self.users_table.setItem(row, col, QTableWidgetItem(str(value)))
def update_request_operator(self):
current_row = self.operator_requests_table.currentRow()
if current_row >= 0:
request_id = self.operator_requests_table.item(current_row, 0).text()
cursor = self.db.conn.cursor()
cursor.execute('''
UPDATE service_requests
SET status = ?, priority = ?, request_type = ?, observer_group = ?, assigned_operator = ?
WHERE id = ?
''', (
self.status_combo.currentText(),
self.priority_combo.currentText(),
self.type_combo.currentText(),
self.observer_group.text(),
self.current_user['full_name'],
request_id
))
# Запись в историю
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Изменение заявки',
f'Оператор изменил статус на {self.status_combo.currentText()}, приоритет на {self.priority_combo.currentText()}'))
self.db.conn.commit()
self.load_operator_requests()
QMessageBox.information(self, 'Успех', 'Заявка обновлена!')
def mark_request_for_deletion(self):
current_row = self.operator_requests_table.currentRow()
if current_row >= 0:
request_id = self.operator_requests_table.item(current_row, 0).text()
cursor = self.db.conn.cursor()
cursor.execute('''
UPDATE service_requests
SET marked_for_deletion = 1
WHERE id = ?
''', (request_id,))
self.db.conn.commit()
self.load_operator_requests()
QMessageBox.information(self, 'Успех', 'Заявка помечена на удаление!')
def update_request_master(self):
current_row = self.master_requests_table.currentRow()
if current_row >= 0:
request_id = self.master_requests_table.item(current_row, 0).text()
cursor = self.db.conn.cursor()
cursor.execute('''
UPDATE service_requests
SET status = ?, assigned_master = ?
WHERE id = ?
''', (
self.master_status_combo.currentText(),
self.current_user['full_name'],
request_id
))
# Запись в историю
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Изменение статуса',
f'Мастер изменил статус на {self.master_status_combo.currentText()}'))
self.db.conn.commit()
self.load_master_requests()
QMessageBox.information(self, 'Успех', 'Статус заявки обновлен!')
def order_parts(self):
current_row = self.master_requests_table.currentRow()
if current_row >= 0:
request_id = self.master_requests_table.item(current_row, 0).text()
cursor = self.db.conn.cursor()
cursor.execute('''
INSERT INTO part_orders (request_id, part_name, part_number, quantity, supplier, estimated_cost, ordered_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
request_id,
self.part_name.text(),
self.part_number.text(),
int(self.part_quantity.text()),
self.part_supplier.text(),
float(self.part_cost.text() or 0),
self.current_user['id']
))
self.db.conn.commit()
# Обновляем статус заявки
cursor.execute('''
UPDATE service_requests
SET status = 'Ожидает запчасти'
WHERE id = ?
''', (request_id,))
# Запись в историю
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Заказ запчастей',
f'Заказана запчасть: {self.part_name.text()}, количество: {self.part_quantity.text()}'))
self.db.conn.commit()
# Очищаем форму
self.part_name.clear()
self.part_number.clear()
self.part_quantity.setText('1')
self.part_supplier.clear()
self.part_cost.clear()
self.load_master_requests()
self.load_master_parts()
QMessageBox.information(self, 'Успех', 'Запчасти заказаны!')
def attach_photo(self):
self.attach_file(file_type='photo')
def attach_file(self, file_type='document'):
current_row = self.master_requests_table.currentRow()
if current_row >= 0:
request_id = self.master_requests_table.item(current_row, 0).text()
file_path, _ = QFileDialog.getOpenFileName(self, 'Выберите файл')
if file_path:
filename = os.path.basename(file_path)
cursor = self.db.conn.cursor()
cursor.execute('''
INSERT INTO attachments (request_id, filename, filepath, file_type, uploaded_by)
VALUES (?, ?, ?, ?, ?)
''', (
request_id,
filename,
file_path,
file_type,
self.current_user['id']
))
# Запись в историю
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Прикрепление файла',
f'Прикреплен файл: {filename}'))
self.db.conn.commit()
QMessageBox.information(self, 'Успех', 'Файл прикреплен!')
def create_report(self):
current_row = self.master_requests_table.currentRow()
if current_row >= 0:
request_id = self.master_requests_table.item(current_row, 0).text()
# Диалог для ввода отчета
report_dialog = QDialog(self)
report_dialog.setWindowTitle('Создание отчета о выполненной работе')
report_dialog.setModal(True)
report_dialog.resize(500, 400)
layout = QVBoxLayout(report_dialog)
form_layout = QFormLayout()
work_description = QTextEdit()
parts_used = QLineEdit()
time_spent = QLineEdit()
labor_cost = QLineEdit()
parts_cost = QLineEdit()
form_layout.addRow('Описание выполненной работы:', work_description)
form_layout.addRow('Использованные запчасти:', parts_used)
form_layout.addRow('Затраченное время (часы):', time_spent)
form_layout.addRow('Стоимость работы:', labor_cost)
form_layout.addRow('Стоимость запчастей:', parts_cost)
layout.addLayout(form_layout)
buttons_layout = QHBoxLayout()
save_btn = QPushButton('Сохранить отчет')
cancel_btn = QPushButton('Отмена')
buttons_layout.addWidget(save_btn)
buttons_layout.addWidget(cancel_btn)
layout.addLayout(buttons_layout)
def save_report():
total = float(labor_cost.text() or 0) + float(parts_cost.text() or 0)
cursor = self.db.conn.cursor()
cursor.execute('''
INSERT INTO reports (request_id, master_id, work_description, parts_used, time_spent, labor_cost, parts_cost, total_cost)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
request_id,
self.current_user['id'],
work_description.toPlainText(),
parts_used.text(),
float(time_spent.text() or 0),
float(labor_cost.text() or 0),
float(parts_cost.text() or 0),
total
))
# Обновляем статус заявки на выполненную
cursor.execute('''
UPDATE service_requests
SET status = 'Выполнена', completed_date = CURRENT_TIMESTAMP
WHERE id = ?
''', (request_id,))
# Запись в историю
cursor.execute('''
INSERT INTO request_history (request_id, user_id, action, details)
VALUES (?, ?, ?, ?)
''', (request_id, self.current_user['id'], 'Создание отчета',
'Отчет о выполненной работе создан'))
self.db.conn.commit()
report_dialog.accept()
self.load_master_requests()
QMessageBox.information(self, 'Успех', 'Отчет создан!')
save_btn.clicked.connect(save_report)
cancel_btn.clicked.connect(report_dialog.reject)
report_dialog.exec()
def show_request_history(self):
current_row = self.master_requests_table.currentRow()
if current_row >= 0:
request_id = self.master_requests_table.item(current_row, 0).text()
cursor = self.db.conn.cursor()
cursor.execute('''
SELECT h.timestamp, u.full_name, h.action, h.details
FROM request_history h
JOIN users u ON h.user_id = u.id
WHERE h.request_id = ?
ORDER BY h.timestamp DESC
''', (request_id,))
history = cursor.fetchall()
history_dialog = QDialog(self)
history_dialog.setWindowTitle(f'История заявки #{request_id}')
history_dialog.setModal(True)
history_dialog.resize(600, 400)
layout = QVBoxLayout(history_dialog)
history_table = QTableWidget()
history_table.setColumnCount(4)
history_table.setHorizontalHeaderLabels(['Дата', 'Пользователь', 'Действие', 'Детали'])
history_table.setRowCount(len(history))
for row, record in enumerate(history):
for col, value in enumerate(record):
history_table.setItem(row, col, QTableWidgetItem(str(value)))
layout.addWidget(history_table)
close_btn = QPushButton('Закрыть')
close_btn.clicked.connect(history_dialog.accept)
layout.addWidget(close_btn)
history_dialog.exec()
def search_archive(self):
search_text = self.archive_search.text()
cursor = self.db.conn.cursor()
if search_text:
cursor.execute('''
SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master, request_type
FROM service_requests
WHERE status = 'Выполнена' AND
(client_name LIKE ? OR equipment_type LIKE ? OR equipment_model LIKE ? OR assigned_master LIKE ?)
ORDER BY completed_date DESC
''', (f'%{search_text}%', f'%{search_text}%', f'%{search_text}%', f'%{search_text}%'))
else:
cursor.execute('''
SELECT id, client_name, equipment_type, equipment_model, status, created_date, completed_date, assigned_master, request_type
FROM service_requests
WHERE status = 'Выполнена'
ORDER BY completed_date DESC
''')
requests = cursor.fetchall()
self.archive_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
self.archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def search_admin_archive(self):
search_text = self.admin_archive_search.text()
cursor = self.db.conn.cursor()
if search_text:
cursor.execute('''
SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status,
sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type,
COALESCE(r.total_cost, 0)
FROM service_requests sr
LEFT JOIN reports r ON sr.id = r.request_id
WHERE sr.status = 'Выполнена' AND
(sr.client_name LIKE ? OR sr.equipment_type LIKE ? OR sr.equipment_model LIKE ? OR sr.assigned_master LIKE ?)
ORDER BY sr.completed_date DESC
''', (f'%{search_text}%', f'%{search_text}%', f'%{search_text}%', f'%{search_text}%'))
else:
cursor.execute('''
SELECT sr.id, sr.client_name, sr.equipment_type, sr.equipment_model, sr.status,
sr.created_date, sr.completed_date, sr.assigned_master, sr.request_type,
COALESCE(r.total_cost, 0)
FROM service_requests sr
LEFT JOIN reports r ON sr.id = r.request_id
WHERE sr.status = 'Выполнена'
ORDER BY sr.completed_date DESC
''')
requests = cursor.fetchall()
self.admin_archive_table.setRowCount(len(requests))
for row, request in enumerate(requests):
for col, value in enumerate(request):
self.admin_archive_table.setItem(row, col, QTableWidgetItem(str(value) if value is not None else ''))
def show_add_user_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle('Добавить пользователя')
dialog.setModal(True)
layout = QVBoxLayout(dialog)
form_layout = QFormLayout()
username = QLineEdit()
password = QLineEdit()
password.setEchoMode(QLineEdit.EchoMode.Password)
full_name = QLineEdit()
phone = QLineEdit()
email = QLineEdit()
role = QComboBox()
role.addItems(['operator', 'master', 'admin'])
form_layout.addRow('Логин:', username)
form_layout.addRow('Пароль:', password)
form_layout.addRow('ФИО:', full_name)
form_layout.addRow('Телефон:', phone)
form_layout.addRow('Email:', email)
form_layout.addRow('Роль:', role)
layout.addLayout(form_layout)
buttons_layout = QHBoxLayout()
save_btn = QPushButton('Сохранить')
cancel_btn = QPushButton('Отмена')
buttons_layout.addWidget(save_btn)
buttons_layout.addWidget(cancel_btn)
layout.addLayout(buttons_layout)
def save_user():
cursor = self.db.conn.cursor()
try:
cursor.execute('''
INSERT INTO users (username, password, role, full_name, phone, email)
VALUES (?, ?, ?, ?, ?, ?)
''', (
username.text(),
password.text(),
role.currentText(),
full_name.text(),
phone.text(),
email.text()
))
self.db.conn.commit()
dialog.accept()
self.load_users()
QMessageBox.information(self, 'Успех', 'Пользователь добавлен!')
except sqlite3.IntegrityError:
QMessageBox.warning(self, 'Ошибка', 'Пользователь с таким логином уже существует!')
save_btn.clicked.connect(save_user)
cancel_btn.clicked.connect(dialog.reject)
dialog.exec()
def delete_marked_requests(self):
cursor = self.db.conn.cursor()
cursor.execute('SELECT COUNT(*) FROM service_requests WHERE marked_for_deletion = 1')
count = cursor.fetchone()[0]
if count == 0:
QMessageBox.information(self, 'Информация', 'Нет заявок, помеченных на удаление')
return
reply = QMessageBox.question(self, 'Подтверждение',
f'Вы уверены, что хотите удалить {count} заявок, помеченных на удаление?',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
cursor.execute('DELETE FROM service_requests WHERE marked_for_deletion = 1')
self.db.conn.commit()
self.load_admin_requests()
QMessageBox.information(self, 'Успех', f'Удалено {count} заявок')
def consolidate_material_needs(self):
cursor = self.db.conn.cursor()
# Создаем консолидированный список потребностей в МТР
cursor.execute('''
SELECT material_name, material_type, SUM(quantity), unit, urgency, SUM(estimated_cost)
FROM material_needs
WHERE status = 'Требуется'
GROUP BY material_name, material_type, unit, urgency
ORDER BY urgency, material_type, material_name
''')
consolidated = cursor.fetchall()
dialog = QDialog(self)
dialog.setWindowTitle('Консолидированные потребности в МТР')
dialog.setModal(True)
dialog.resize(700, 500)
layout = QVBoxLayout(dialog)
table = QTableWidget()
table.setColumnCount(6)
table.setHorizontalHeaderLabels(['Материал', 'Тип', 'Количество', 'Ед.изм', 'Срочность', 'Общая стоимость'])
table.setRowCount(len(consolidated))
for row, record in enumerate(consolidated):
for col, value in enumerate(record):
table.setItem(row, col, QTableWidgetItem(str(value)))
layout.addWidget(table)
close_btn = QPushButton('Закрыть')
close_btn.clicked.connect(dialog.accept)
layout.addWidget(close_btn)
dialog.exec()
def generate_mtr_report(self):
cursor = self.db.conn.cursor()
# Формируем отчет по МТР
cursor.execute('''
SELECT
mn.material_name,
mn.material_type,
SUM(mn.quantity) as total_quantity,
mn.unit,
mn.urgency,
COUNT(DISTINCT mn.request_id) as request_count,
SUM(mn.estimated_cost) as total_cost
FROM material_needs mn
WHERE mn.status = 'Требуется'
GROUP BY mn.material_name, mn.material_type, mn.unit, mn.urgency
ORDER BY mn.urgency DESC, total_cost DESC
''')
report_data = cursor.fetchall()
dialog = QDialog(self)
dialog.setWindowTitle('Отчет по материально-техническим ресурсам')
dialog.setModal(True)
dialog.resize(800, 600)
layout = QVBoxLayout(dialog)
layout.addWidget(QLabel('Отчет по потребностям в МТР'))
table = QTableWidget()
table.setColumnCount(7)
table.setHorizontalHeaderLabels(['Материал', 'Тип', 'Общее кол-во', 'Ед.изм', 'Срочность', 'Кол-во заявок', 'Общая стоимость'])
table.setRowCount(len(report_data))
total_cost = 0
for row, record in enumerate(report_data):
for col, value in enumerate(record):
table.setItem(row, col, QTableWidgetItem(str(value)))
total_cost += float(record[6] or 0)
layout.addWidget(table)
layout.addWidget(QLabel(f'Общая стоимость всех МТР: {total_cost:.2f} руб.'))
close_btn = QPushButton('Закрыть')
close_btn.clicked.connect(dialog.accept)
layout.addWidget(close_btn)
dialog.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = ServiceRequestAppV2()
window.show()
sys.exit(app.exec())