mirror of
https://github.com/artagaz/bd-semestovaya.git
synced 2026-06-22 02:01:51 +07:00
600 lines
24 KiB
Python
600 lines
24 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
import sys
|
||
import json
|
||
from datetime import date
|
||
|
||
try:
|
||
from PySide6.QtWidgets import (
|
||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||
QTableWidget, QTableWidgetItem, QPushButton, QLabel, QLineEdit,
|
||
QComboBox, QDialog, QFormLayout, QMessageBox, QHeaderView,
|
||
QTabWidget, QStatusBar, QGroupBox, QCheckBox, QTextEdit,
|
||
QDateEdit, QSpinBox
|
||
)
|
||
from PySide6.QtCore import Qt, QDate, QTimer
|
||
from PySide6.QtGui import QColor, QBrush, QFont
|
||
QT_AVAILABLE = True
|
||
except ImportError:
|
||
QT_AVAILABLE = False
|
||
|
||
try:
|
||
import oracledb
|
||
ORACLE_AVAILABLE = True
|
||
except ImportError:
|
||
ORACLE_AVAILABLE = False
|
||
|
||
|
||
# =====================================================================
|
||
# Работа с БД
|
||
# =====================================================================
|
||
class DB:
|
||
conn = None
|
||
|
||
@classmethod
|
||
def connect(cls, user, pwd, host, port, service, role):
|
||
dsn = f"{host}:{port}/{service}"
|
||
kwargs = {"user": user, "password": pwd, "dsn": dsn}
|
||
if role == "SYSDBA":
|
||
kwargs["mode"] = oracledb.SYSDBA
|
||
elif role == "SYSOPER":
|
||
kwargs["mode"] = oracledb.SYSOPER
|
||
cls.conn = oracledb.connect(**kwargs)
|
||
|
||
@classmethod
|
||
def query(cls, sql, params=None):
|
||
if cls.conn is None:
|
||
return []
|
||
with cls.conn.cursor() as cur:
|
||
cur.execute(sql, params or {})
|
||
return cur.fetchall()
|
||
|
||
@classmethod
|
||
def execute(cls, sql, params=None):
|
||
if cls.conn is None:
|
||
return
|
||
with cls.conn.cursor() as cur:
|
||
cur.execute(sql, params or {})
|
||
cls.conn.commit()
|
||
|
||
|
||
# =====================================================================
|
||
# Диалог подключения
|
||
# =====================================================================
|
||
class ConnectDialog(QDialog):
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
self.setWindowTitle("Подключение к Oracle")
|
||
self.setFixedSize(350, 300)
|
||
layout = QFormLayout(self)
|
||
|
||
self.user = QLineEdit("student")
|
||
self.pwd = QLineEdit()
|
||
self.pwd.setEchoMode(QLineEdit.Password)
|
||
self.host = QLineEdit("localhost")
|
||
self.port = QLineEdit("1521")
|
||
self.service = QLineEdit("XEPDB1")
|
||
self.role = QComboBox()
|
||
self.role.addItems(["NORMAL", "SYSDBA", "SYSOPER"])
|
||
|
||
layout.addRow("Пользователь:", self.user)
|
||
layout.addRow("Пароль:", self.pwd)
|
||
layout.addRow("Хост:", self.host)
|
||
layout.addRow("Порт:", self.port)
|
||
layout.addRow("Service Name:", self.service)
|
||
layout.addRow("Role:", self.role)
|
||
|
||
btn = QPushButton("Подключиться")
|
||
btn.clicked.connect(self.do_connect)
|
||
layout.addRow(btn)
|
||
|
||
def do_connect(self):
|
||
try:
|
||
DB.connect(
|
||
self.user.text(), self.pwd.text(), self.host.text(),
|
||
self.port.text(), self.service.text(), self.role.currentText()
|
||
)
|
||
self.accept()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
|
||
# =====================================================================
|
||
# Диалог редактирования записи
|
||
# =====================================================================
|
||
class RecordDialog(QDialog):
|
||
def __init__(self, title, fields, values=None, parent=None):
|
||
super().__init__(parent)
|
||
self.setWindowTitle(title)
|
||
self.setFixedSize(400, 350)
|
||
self.layout = QFormLayout(self)
|
||
self.fields = {}
|
||
values = values or {}
|
||
|
||
for col, label in fields:
|
||
edit = QLineEdit(str(values.get(col, "")))
|
||
self.layout.addRow(label + ":", edit)
|
||
self.fields[col] = edit
|
||
|
||
btn = QPushButton("Сохранить")
|
||
btn.clicked.connect(self.accept)
|
||
self.layout.addRow(btn)
|
||
|
||
def get_data(self):
|
||
return {k: v.text() for k, v in self.fields.items()}
|
||
|
||
|
||
# =====================================================================
|
||
# Диалог деталей операции
|
||
# =====================================================================
|
||
class LogDetailDialog(QDialog):
|
||
def __init__(self, log_id, table, op_type, old_data, new_data, parent=None):
|
||
super().__init__(parent)
|
||
self.setWindowTitle(f"Детали операции #{log_id}")
|
||
self.resize(500, 400)
|
||
layout = QVBoxLayout(self)
|
||
|
||
info = QLabel(f"<b>Таблица:</b> {table} <b>Операция:</b> {op_type}")
|
||
layout.addWidget(info)
|
||
|
||
def make_box(title, data, color):
|
||
if not data:
|
||
return
|
||
box = QGroupBox(title)
|
||
v = QVBoxLayout(box)
|
||
te = QTextEdit()
|
||
te.setReadOnly(True)
|
||
te.setStyleSheet(f"background-color: {color};")
|
||
try:
|
||
te.setPlainText(json.dumps(json.loads(data), ensure_ascii=False, indent=2))
|
||
except Exception:
|
||
te.setPlainText(str(data))
|
||
v.addWidget(te)
|
||
layout.addWidget(box)
|
||
|
||
make_box("До операции (old_data)", old_data, "#ffe6e6")
|
||
make_box("После операции (new_data)", new_data, "#e6ffe6")
|
||
|
||
btn = QPushButton("Закрыть")
|
||
btn.clicked.connect(self.accept)
|
||
layout.addWidget(btn)
|
||
|
||
|
||
# =====================================================================
|
||
# Главное окно
|
||
# =====================================================================
|
||
class MainWindow(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.setWindowTitle("Магазин автозапчастей")
|
||
self.resize(1150, 750)
|
||
|
||
self.tabs = QTabWidget()
|
||
self.setCentralWidget(self.tabs)
|
||
|
||
self.build_table_tab("Suppliers", "suppliers",
|
||
["id", "name", "category", "contact_info", "has_guarantee", "contract_id"],
|
||
[("name", "Название"), ("category", "Категория"),
|
||
("contact_info", "Контакты"), ("has_guarantee", "Гарантия (Y/N)"),
|
||
("contract_id", "Договор")])
|
||
|
||
self.build_table_tab("Products", "products",
|
||
["id", "name", "article"],
|
||
[("name", "Название"), ("article", "Артикул")])
|
||
|
||
self.build_orders_tab()
|
||
self.build_log_tab()
|
||
self.build_report_tab()
|
||
|
||
self.status = QStatusBar()
|
||
self.setStatusBar(self.status)
|
||
self.status.showMessage("Не подключено")
|
||
|
||
QTimer.singleShot(0, self.show_connect)
|
||
|
||
# -----------------------------------------------------------------
|
||
# Универсальная вкладка таблицы
|
||
# -----------------------------------------------------------------
|
||
def build_table_tab(self, title, table, columns, fields):
|
||
w = QWidget()
|
||
v = QVBoxLayout(w)
|
||
|
||
table_widget = QTableWidget()
|
||
table_widget.setColumnCount(len(columns))
|
||
table_widget.setHorizontalHeaderLabels(columns)
|
||
table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
table_widget.setSelectionBehavior(QTableWidget.SelectRows)
|
||
v.addWidget(table_widget)
|
||
setattr(self, f"tw_{table}", table_widget)
|
||
|
||
h = QHBoxLayout()
|
||
btn_refresh = QPushButton("🔄 Обновить")
|
||
btn_refresh.clicked.connect(lambda: self.load_table(table, columns))
|
||
btn_add = QPushButton("➕ Добавить")
|
||
btn_add.clicked.connect(lambda: self.add_record(table, fields))
|
||
btn_edit = QPushButton("✏️ Изменить")
|
||
btn_edit.clicked.connect(lambda: self.edit_record(table, columns, fields))
|
||
btn_del = QPushButton("🗑️ Удалить")
|
||
btn_del.clicked.connect(lambda: self.delete_record(table, columns))
|
||
|
||
for b in (btn_refresh, btn_add, btn_edit, btn_del):
|
||
h.addWidget(b)
|
||
v.addLayout(h)
|
||
|
||
self.tabs.addTab(w, title)
|
||
QTimer.singleShot(100, lambda: self.load_table(table, columns))
|
||
|
||
def load_table(self, table, columns):
|
||
tw = getattr(self, f"tw_{table}")
|
||
tw.setRowCount(0)
|
||
try:
|
||
rows = DB.query(f"SELECT {','.join(columns)} FROM {table}")
|
||
tw.setRowCount(len(rows))
|
||
for i, row in enumerate(rows):
|
||
for j, val in enumerate(row):
|
||
tw.setItem(i, j, QTableWidgetItem(str(val) if val is not None else ""))
|
||
self.status.showMessage(f"{table}: загружено {len(rows)} записей")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def add_record(self, table, fields):
|
||
dlg = RecordDialog(f"Добавить {table}", fields)
|
||
if dlg.exec() != QDialog.Accepted:
|
||
return
|
||
data = dlg.get_data()
|
||
cols = ", ".join(data.keys())
|
||
ph = ", ".join([f":{k}" for k in data.keys()])
|
||
try:
|
||
DB.execute(f"INSERT INTO {table} ({cols}) VALUES ({ph})", data)
|
||
self.load_table(table, [c for c, _ in fields] + ["id"])
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def edit_record(self, table, columns, fields):
|
||
tw = getattr(self, f"tw_{table}")
|
||
row = tw.currentRow()
|
||
if row < 0:
|
||
QMessageBox.warning(self, "Внимание", "Выберите запись")
|
||
return
|
||
pk = tw.item(row, 0).text()
|
||
current = {col: tw.item(row, i).text() for i, col in enumerate(columns)}
|
||
dlg = RecordDialog(f"Изменить {table}", fields, current)
|
||
if dlg.exec() != QDialog.Accepted:
|
||
return
|
||
data = dlg.get_data()
|
||
sets = ", ".join([f"{k} = :{k}" for k in data.keys()])
|
||
data["id"] = pk
|
||
try:
|
||
DB.execute(f"UPDATE {table} SET {sets} WHERE id = :id", data)
|
||
self.load_table(table, columns)
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def delete_record(self, table, columns):
|
||
tw = getattr(self, f"tw_{table}")
|
||
row = tw.currentRow()
|
||
if row < 0:
|
||
QMessageBox.warning(self, "Внимание", "Выберите запись")
|
||
return
|
||
pk = tw.item(row, 0).text()
|
||
if QMessageBox.question(self, "Подтверждение", f"Удалить запись {pk}?") == QMessageBox.Yes:
|
||
try:
|
||
DB.execute(f"DELETE FROM {table} WHERE id = :id", {"id": pk})
|
||
self.load_table(table, columns)
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
# -----------------------------------------------------------------
|
||
# Orders (отдельно из-за DateEdit и ComboBox)
|
||
# -----------------------------------------------------------------
|
||
def build_orders_tab(self):
|
||
w = QWidget()
|
||
v = QVBoxLayout(w)
|
||
|
||
self.tw_orders = QTableWidget()
|
||
self.tw_orders.setColumnCount(3)
|
||
self.tw_orders.setHorizontalHeaderLabels(["id", "supplier_id", "order_date"])
|
||
self.tw_orders.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
self.tw_orders.setSelectionBehavior(QTableWidget.SelectRows)
|
||
v.addWidget(self.tw_orders)
|
||
|
||
h = QHBoxLayout()
|
||
btn_refresh = QPushButton("🔄 Обновить")
|
||
btn_refresh.clicked.connect(self.load_orders)
|
||
btn_add = QPushButton("➕ Добавить")
|
||
btn_add.clicked.connect(self.add_order)
|
||
btn_edit = QPushButton("✏️ Изменить")
|
||
btn_edit.clicked.connect(self.edit_order)
|
||
btn_del = QPushButton("🗑️ Удалить")
|
||
btn_del.clicked.connect(self.delete_order)
|
||
for b in (btn_refresh, btn_add, btn_edit, btn_del):
|
||
h.addWidget(b)
|
||
v.addLayout(h)
|
||
|
||
self.tabs.addTab(w, "Purchase Orders")
|
||
QTimer.singleShot(150, self.load_orders)
|
||
|
||
def load_orders(self):
|
||
self.tw_orders.setRowCount(0)
|
||
try:
|
||
rows = DB.query("SELECT id, supplier_id, order_date FROM purchase_orders")
|
||
self.tw_orders.setRowCount(len(rows))
|
||
for i, (oid, sid, odate) in enumerate(rows):
|
||
self.tw_orders.setItem(i, 0, QTableWidgetItem(str(oid)))
|
||
self.tw_orders.setItem(i, 1, QTableWidgetItem(str(sid)))
|
||
self.tw_orders.setItem(i, 2, QTableWidgetItem(str(odate) if odate else ""))
|
||
self.status.showMessage(f"purchase_orders: загружено {len(rows)} записей")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def _supplier_combo(self):
|
||
cb = QComboBox()
|
||
self.supplier_map = {}
|
||
for sid, sname in DB.query("SELECT id, name FROM suppliers"):
|
||
txt = f"{sid} — {sname}"
|
||
cb.addItem(txt)
|
||
self.supplier_map[txt] = sid
|
||
return cb
|
||
|
||
def add_order(self):
|
||
dlg = QDialog(self)
|
||
dlg.setWindowTitle("Добавить заказ")
|
||
dlg.setFixedSize(350, 200)
|
||
f = QFormLayout(dlg)
|
||
cb = self._supplier_combo()
|
||
de = QDateEdit(QDate.currentDate())
|
||
de.setCalendarPopup(True)
|
||
f.addRow("Поставщик:", cb)
|
||
f.addRow("Дата:", de)
|
||
btn = QPushButton("Сохранить")
|
||
btn.clicked.connect(dlg.accept)
|
||
f.addRow(btn)
|
||
if dlg.exec() != QDialog.Accepted:
|
||
return
|
||
try:
|
||
DB.execute("INSERT INTO purchase_orders (supplier_id, order_date) VALUES (:sid, :od)",
|
||
{"sid": self.supplier_map[cb.currentText()], "od": de.date().toPython()})
|
||
self.load_orders()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def edit_order(self):
|
||
row = self.tw_orders.currentRow()
|
||
if row < 0:
|
||
QMessageBox.warning(self, "Внимание", "Выберите запись")
|
||
return
|
||
pk = self.tw_orders.item(row, 0).text()
|
||
dlg = QDialog(self)
|
||
dlg.setWindowTitle("Изменить заказ")
|
||
dlg.setFixedSize(350, 200)
|
||
f = QFormLayout(dlg)
|
||
cb = self._supplier_combo()
|
||
de = QDateEdit()
|
||
de.setCalendarPopup(True)
|
||
# установить текущие значения
|
||
cur_sid = self.tw_orders.item(row, 1).text()
|
||
cur_date = self.tw_orders.item(row, 2).text()
|
||
for i in range(cb.count()):
|
||
if cb.itemText(i).startswith(cur_sid + " "):
|
||
cb.setCurrentIndex(i)
|
||
break
|
||
de.setDate(QDate.fromString(cur_date, "yyyy-MM-dd"))
|
||
f.addRow("Поставщик:", cb)
|
||
f.addRow("Дата:", de)
|
||
btn = QPushButton("Сохранить")
|
||
btn.clicked.connect(dlg.accept)
|
||
f.addRow(btn)
|
||
if dlg.exec() != QDialog.Accepted:
|
||
return
|
||
try:
|
||
DB.execute("UPDATE purchase_orders SET supplier_id = :sid, order_date = :od WHERE id = :id",
|
||
{"sid": self.supplier_map[cb.currentText()],
|
||
"od": de.date().toPython(),
|
||
"id": pk})
|
||
self.load_orders()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def delete_order(self):
|
||
row = self.tw_orders.currentRow()
|
||
if row < 0:
|
||
QMessageBox.warning(self, "Внимание", "Выберите запись")
|
||
return
|
||
pk = self.tw_orders.item(row, 0).text()
|
||
if QMessageBox.question(self, "Подтверждение", f"Удалить заказ {pk}?") == QMessageBox.Yes:
|
||
try:
|
||
DB.execute("DELETE FROM purchase_orders WHERE id = :id", {"id": pk})
|
||
self.load_orders()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
# -----------------------------------------------------------------
|
||
# Журнал операций
|
||
# -----------------------------------------------------------------
|
||
def build_log_tab(self):
|
||
w = QWidget()
|
||
v = QVBoxLayout(w)
|
||
|
||
filt = QGroupBox("Фильтры")
|
||
fh = QHBoxLayout(filt)
|
||
self.log_type = QComboBox()
|
||
self.log_type.addItems(["Все", "INSERT", "UPDATE", "DELETE"])
|
||
self.log_from = QLineEdit("2025-01-01")
|
||
self.log_to = QLineEdit("2026-12-31")
|
||
fh.addWidget(QLabel("Тип:"))
|
||
fh.addWidget(self.log_type)
|
||
fh.addWidget(QLabel("С:"))
|
||
fh.addWidget(self.log_from)
|
||
fh.addWidget(QLabel("По:"))
|
||
fh.addWidget(self.log_to)
|
||
btn_load = QPushButton("Показать")
|
||
btn_load.clicked.connect(self.load_log)
|
||
btn_undo = QPushButton("↩️ Отменить выбранное")
|
||
btn_undo.clicked.connect(self.undo_selected)
|
||
fh.addWidget(btn_load)
|
||
fh.addWidget(btn_undo)
|
||
fh.addStretch()
|
||
v.addWidget(filt)
|
||
|
||
self.tw_log = QTableWidget()
|
||
self.tw_log.setColumnCount(8)
|
||
self.tw_log.setHorizontalHeaderLabels(
|
||
["log_id", "table_name", "op_type", "date", "record_pk", "old_data", "new_data", "undone"]
|
||
)
|
||
self.tw_log.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
self.tw_log.setSelectionBehavior(QTableWidget.SelectRows)
|
||
self.tw_log.doubleClicked.connect(self.log_double_click)
|
||
v.addWidget(self.tw_log)
|
||
|
||
# цветовые теги
|
||
self.tw_log.tag_colors = {
|
||
"INSERT": QColor("#d4edda"),
|
||
"UPDATE": QColor("#fff3cd"),
|
||
"DELETE": QColor("#f8d7da")
|
||
}
|
||
|
||
self.tabs.addTab(w, "Журнал операций")
|
||
|
||
def load_log(self):
|
||
self.tw_log.setRowCount(0)
|
||
try:
|
||
op = None if self.log_type.currentText() == "Все" else self.log_type.currentText()
|
||
sql = """SELECT log_id, table_name, operation_type, operation_date, record_pk,
|
||
old_data, new_data, is_undone
|
||
FROM operation_log
|
||
WHERE (:op IS NULL OR operation_type = :op)
|
||
AND operation_date BETWEEN TO_TIMESTAMP(:d1, 'YYYY-MM-DD')
|
||
AND TO_TIMESTAMP(:d2 || ' 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
|
||
ORDER BY log_id DESC"""
|
||
rows = DB.query(sql, {"op": op, "d1": self.log_from.text(), "d2": self.log_to.text()})
|
||
self.tw_log.setRowCount(len(rows))
|
||
for i, (lid, tn, ot, od, pk, old_d, new_d, und) in enumerate(rows):
|
||
vals = [lid, tn, ot, str(od) if od else "", pk,
|
||
str(old_d)[:60] + "..." if old_d and len(str(old_d)) > 60 else str(old_d or ""),
|
||
str(new_d)[:60] + "..." if new_d and len(str(new_d)) > 60 else str(new_d or ""),
|
||
und]
|
||
for j, val in enumerate(vals):
|
||
item = QTableWidgetItem(str(val))
|
||
if ot in self.tw_log.tag_colors:
|
||
item.setBackground(QBrush(self.tw_log.tag_colors[ot]))
|
||
self.tw_log.setItem(i, j, item)
|
||
self.status.showMessage(f"Журнал: найдено {len(rows)} записей")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def undo_selected(self):
|
||
row = self.tw_log.currentRow()
|
||
if row < 0:
|
||
QMessageBox.warning(self, "Внимание", "Выберите запись")
|
||
return
|
||
lid = self.tw_log.item(row, 0).text()
|
||
if QMessageBox.question(self, "Подтверждение", f"Отменить операцию {lid}?") == QMessageBox.Yes:
|
||
try:
|
||
DB.execute("BEGIN pkg_log_manager.undo_operation(:id); END;", {"id": lid})
|
||
self.load_log()
|
||
self.refresh_all_tables()
|
||
QMessageBox.information(self, "Успех", "Операция отменена")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
def log_double_click(self):
|
||
row = self.tw_log.currentRow()
|
||
if row < 0:
|
||
return
|
||
lid = self.tw_log.item(row, 0).text()
|
||
tn = self.tw_log.item(row, 1).text()
|
||
ot = self.tw_log.item(row, 2).text()
|
||
try:
|
||
old_d, new_d = DB.query("SELECT old_data, new_data FROM operation_log WHERE log_id = :id", {"id": lid})[0]
|
||
LogDetailDialog(int(lid), tn, ot, old_d, new_d, self).exec()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
# -----------------------------------------------------------------
|
||
# Отчёт
|
||
# -----------------------------------------------------------------
|
||
def build_report_tab(self):
|
||
w = QWidget()
|
||
v = QVBoxLayout(w)
|
||
|
||
opts = QGroupBox("Параметры сортировки")
|
||
oh = QHBoxLayout(opts)
|
||
self.chk_r1 = QCheckBox("По таблице (флаг 1)")
|
||
self.chk_r2 = QCheckBox("По типу операции (флаг 2)")
|
||
self.chk_r3 = QCheckBox("По количеству (флаг 3)")
|
||
btn = QPushButton("Сформировать отчёт")
|
||
btn.clicked.connect(self.load_report)
|
||
oh.addWidget(self.chk_r1)
|
||
oh.addWidget(self.chk_r2)
|
||
oh.addWidget(self.chk_r3)
|
||
oh.addWidget(btn)
|
||
oh.addStretch()
|
||
v.addWidget(opts)
|
||
|
||
self.tw_rep = QTableWidget()
|
||
self.tw_rep.setColumnCount(3)
|
||
self.tw_rep.setHorizontalHeaderLabels(["Таблица", "Тип операции", "Количество"])
|
||
self.tw_rep.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
v.addWidget(self.tw_rep)
|
||
self.tabs.addTab(w, "Отчёт")
|
||
|
||
def load_report(self):
|
||
self.tw_rep.setRowCount(0)
|
||
try:
|
||
parts = []
|
||
if self.chk_r1.isChecked():
|
||
parts.append("table_name")
|
||
if self.chk_r2.isChecked():
|
||
parts.append("operation_type")
|
||
if self.chk_r3.isChecked():
|
||
parts.append("op_count")
|
||
order = "ORDER BY " + ", ".join(parts) if parts else ""
|
||
sql = f"SELECT table_name, operation_type, COUNT(*) AS op_count FROM operation_log GROUP BY table_name, operation_type {order}"
|
||
rows = DB.query(sql)
|
||
self.tw_rep.setRowCount(len(rows))
|
||
for i, (tn, ot, cnt) in enumerate(rows):
|
||
for j, val in enumerate((tn, ot, str(cnt))):
|
||
self.tw_rep.setItem(i, j, QTableWidgetItem(val))
|
||
self.status.showMessage(f"Отчёт: сформировано {len(rows)} строк")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", str(e))
|
||
|
||
# -----------------------------------------------------------------
|
||
# Служебные
|
||
# -----------------------------------------------------------------
|
||
def show_connect(self):
|
||
if DB.conn is None:
|
||
dlg = ConnectDialog(self)
|
||
if dlg.exec() == QDialog.Accepted:
|
||
self.status.showMessage("Подключено")
|
||
self.refresh_all_tables()
|
||
self.load_log()
|
||
|
||
def refresh_all_tables(self):
|
||
self.load_table("suppliers", ["id", "name", "category", "contact_info", "has_guarantee", "contract_id"])
|
||
self.load_table("products", ["id", "name", "article"])
|
||
self.load_orders()
|
||
|
||
def closeEvent(self, event):
|
||
if DB.conn:
|
||
DB.conn.close()
|
||
event.accept()
|
||
|
||
|
||
# =====================================================================
|
||
# Точка входа
|
||
# =====================================================================
|
||
if __name__ == "__main__":
|
||
if not QT_AVAILABLE:
|
||
print("Ошибка: PySide6 не установлен. Установите: pip install pyside6")
|
||
sys.exit(1)
|
||
if not ORACLE_AVAILABLE:
|
||
print("Ошибка: oracledb не установлен. Установите: pip install oracledb")
|
||
sys.exit(1)
|
||
|
||
app = QApplication(sys.argv)
|
||
app.setStyle("Fusion")
|
||
win = MainWindow()
|
||
win.show()
|
||
sys.exit(app.exec())
|