From cb711ce04e94b49e1c42ed59d07acc20c648fbca Mon Sep 17 00:00:00 2001 From: MarcusStill <69536339+MarcusStill@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:42:56 +0000 Subject: [PATCH] Refactor codebase to comply with PEP8 standards Applied `black` and `isort` for consistent code formatting and import sorting across the repository. Safely applied `ruff` fixes for minor linting errors, ensuring no logic was changed. Protected test files mocking `sys.modules` from being broken by formatters by utilizing `# isort: split` and `# noqa: E402`. Added missing `ruff: noqa: F403, F405` to autogenerated `libfptr10.py` to suppress wildcard import errors. No functional logic was altered, and all tests pass successfully. --- db/models/sale.py | 2 +- design/logic/authorization.py | 2 +- design/logic/client.py | 2 +- design/logic/main_form.py | 4 +- design/logic/pay.py | 4 +- design/logic/progress_dialog.py | 36 +- design/logic/sale.py | 2 +- design/logic/slip.py | 2 +- main.py | 394 +- modules/config.py | 10 +- modules/libfptr10.py | 899 ++- modules/otchet.py | 305 +- modules/payment_equipment.py | 327 +- modules/progress_window.py | 3 +- modules/sale_logic.py | 36 +- modules/system.py | 39 +- modules/worker.py | 123 +- patch.diff | 11457 ++++++++++++++++++++++++++++++ tests/test_auth_logic.py | 48 +- tests/test_config.py | 12 +- tests/test_logger.py | 21 +- tests/test_otchet.py | 114 +- tests/test_payment_equipment.py | 312 +- tests/test_progress_window.py | 31 +- tests/test_sale_logic.py | 75 +- tests/test_system.py | 129 +- tests/test_windows.py | 23 +- tests/test_worker.py | 580 +- 28 files changed, 13623 insertions(+), 1369 deletions(-) create mode 100644 patch.diff diff --git a/db/models/sale.py b/db/models/sale.py index a0035ee..4e59786 100644 --- a/db/models/sale.py +++ b/db/models/sale.py @@ -37,7 +37,7 @@ class Sale(Base): datetime_return: Mapped[DateTime | None] = mapped_column( DateTime, comment="Дата и время возврата продажи" ) - bank_return: Mapped[str | None | None] = mapped_column( + bank_return: Mapped[str | None] = mapped_column( Text, comment="Банковский чек возврата" ) partial_return: Mapped[int] = mapped_column( diff --git a/design/logic/authorization.py b/design/logic/authorization.py index 6238708..6242cff 100644 --- a/design/logic/authorization.py +++ b/design/logic/authorization.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import QFrame, QLabel, QLineEdit, QPushButton -class Ui_Dialog(object): +class Ui_Dialog: def setupUi(self, Dialog): if not Dialog.objectName(): Dialog.setObjectName("Dialog") diff --git a/design/logic/client.py b/design/logic/client.py index ddc3b51..a12ecad 100644 --- a/design/logic/client.py +++ b/design/logic/client.py @@ -13,7 +13,7 @@ ) -class Ui_Dialog_Client(object): +class Ui_Dialog_Client: def setupUi(self, Dialog_Client): if not Dialog_Client.objectName(): Dialog_Client.setObjectName("Dialog_Client") diff --git a/design/logic/main_form.py b/design/logic/main_form.py index b3bfd74..f0088d0 100644 --- a/design/logic/main_form.py +++ b/design/logic/main_form.py @@ -15,14 +15,14 @@ QPushButton, QRadioButton, QStatusBar, - QTabWidget, QTableWidget, QTableWidgetItem, + QTabWidget, QWidget, ) -class Ui_MainWindow(object): +class Ui_MainWindow: def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName("MainWindow") diff --git a/design/logic/pay.py b/design/logic/pay.py index 3926a8f..8d243c4 100644 --- a/design/logic/pay.py +++ b/design/logic/pay.py @@ -1,9 +1,9 @@ from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt from PySide6.QtGui import QFont -from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QCheckBox +from PySide6.QtWidgets import QCheckBox, QGroupBox, QLabel, QPushButton -class Ui_Dialog_Pay(object): +class Ui_Dialog_Pay: def setupUi(self, Dialog_Pay): if not Dialog_Pay.objectName(): Dialog_Pay.setObjectName("Dialog_Pay") diff --git a/design/logic/progress_dialog.py b/design/logic/progress_dialog.py index e7b5b3e..7d2596a 100644 --- a/design/logic/progress_dialog.py +++ b/design/logic/progress_dialog.py @@ -1,23 +1,23 @@ -from PySide6.QtCore import (QCoreApplication, QMetaObject, QSize, Qt) -from PySide6.QtWidgets import (QLabel, QProgressBar, - QVBoxLayout) +from PySide6.QtCore import QCoreApplication, QMetaObject, QSize, Qt +from PySide6.QtWidgets import QLabel, QProgressBar, QVBoxLayout -class Ui_ProgressDialog(object): + +class Ui_ProgressDialog: def setupUi(self, ProgressDialog): if not ProgressDialog.objectName(): - ProgressDialog.setObjectName(u"ProgressDialog") + ProgressDialog.setObjectName("ProgressDialog") ProgressDialog.resize(300, 127) ProgressDialog.setMinimumSize(QSize(300, 100)) self.verticalLayout = QVBoxLayout(ProgressDialog) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.label_status = QLabel(ProgressDialog) - self.label_status.setObjectName(u"label_status") + self.label_status.setObjectName("label_status") self.label_status.setAlignment(Qt.AlignmentFlag.AlignCenter) self.verticalLayout.addWidget(self.label_status) self.progressBar = QProgressBar(ProgressDialog) - self.progressBar.setObjectName(u"progressBar") + self.progressBar.setObjectName("progressBar") self.progressBar.setMinimum(0) self.progressBar.setMaximum(100) self.progressBar.setValue(0) @@ -25,14 +25,26 @@ def setupUi(self, ProgressDialog): self.verticalLayout.addWidget(self.progressBar) - self.retranslateUi(ProgressDialog) QMetaObject.connectSlotsByName(ProgressDialog) + # setupUi def retranslateUi(self, ProgressDialog): - ProgressDialog.setWindowTitle(QCoreApplication.translate("ProgressDialog", u"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", None)) - self.label_status.setText(QCoreApplication.translate("ProgressDialog", u"\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", None)) - # retranslateUi + ProgressDialog.setWindowTitle( + QCoreApplication.translate( + "ProgressDialog", + "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", + None, + ) + ) + self.label_status.setText( + QCoreApplication.translate( + "ProgressDialog", + "\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", + None, + ) + ) + # retranslateUi diff --git a/design/logic/sale.py b/design/logic/sale.py index ab9ae05..56b8bb8 100644 --- a/design/logic/sale.py +++ b/design/logic/sale.py @@ -15,7 +15,7 @@ ) -class Ui_Dialog_Sale(object): +class Ui_Dialog_Sale: def setupUi(self, Dialog_Sale): if not Dialog_Sale.objectName(): Dialog_Sale.setObjectName("Dialog_Sale") diff --git a/design/logic/slip.py b/design/logic/slip.py index 16b50c0..9e2b262 100644 --- a/design/logic/slip.py +++ b/design/logic/slip.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QTabWidget, QTextEdit, QWidget -class Ui_Dialog_Slip(object): +class Ui_Dialog_Slip: def setupUi(self, Dialog_Slip): if not Dialog_Slip.objectName(): Dialog_Slip.setObjectName("Dialog_Slip") diff --git a/main.py b/main.py index c7f54d0..796ae55 100644 --- a/main.py +++ b/main.py @@ -4,46 +4,43 @@ import sys from datetime import date, timedelta from pathlib import Path -from typing import Any, Optional, Type +from typing import Any from PySide6 import QtCore, QtWidgets -from PySide6.QtCore import Qt -from PySide6.QtCore import Signal, QThread +from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QColor, QPixmap from PySide6.QtWidgets import ( QCheckBox, + QDialog, QHBoxLayout, + QMessageBox, QTableWidgetItem, + QWidget, ) -from PySide6.QtWidgets import QDialog, QWidget -from PySide6.QtWidgets import QMessageBox -from sqlalchemy import and_, select, update, desc +from sqlalchemy import and_, desc, select, update from sqlalchemy.orm import Session -from db.models import Client -from db.models import Sale -from db.models import Ticket +from db.models import Client, Sale, Ticket from design.logic.authorization import Ui_Dialog from design.logic.client import Ui_Dialog_Client from design.logic.main_form import Ui_MainWindow from design.logic.pay import Ui_Dialog_Pay from design.logic.sale import Ui_Dialog_Sale from design.logic.slip import Ui_Dialog_Slip -from modules import otchet +from modules import otchet, windows from modules import payment_equipment as pq -from modules import windows from modules.auth_logic import perform_pre_sale_checks from modules.logger import logger, logger_wraps from modules.progress_window import ProgressWindow from modules.sale_logic import ( - calculate_age, - calculate_ticket_type, - calculated_ticket_price, calculate_adult_price, + calculate_age, calculate_child_price, - calculate_discounted_price, calculate_discount, + calculate_discounted_price, calculate_itog, + calculate_ticket_type, + calculated_ticket_price, convert_sale_dict_values, generating_parts_for_partial_returns, get_talent_based_on_time, @@ -67,6 +64,7 @@ def handle_uncaught_exception(exc_type, exc_value, exc_traceback): "Неперехваченное исключение" ) + sys.excepthook = handle_uncaught_exception @@ -101,7 +99,7 @@ def starting_the_main_form(self) -> None: windows.info_window( "База данных недоступна", "Не удалось подключиться к серверу базы данных.\n Приложение будет закрыто.", - "" + "", ) sys.exit(1) login: str = self.ui.lineEdit.text() @@ -134,8 +132,11 @@ def perform_login_checks(login: str, password: str) -> bool: auth.close() return True else: - windows.info_window("Пользователь не найден", "Проверьте правильность ввода логина и пароля.", - "Убедитесь, что используете правильный логин и пароль для входа в систему.") + windows.info_window( + "Пользователь не найден", + "Проверьте правильность ввода логина и пароля.", + "Убедитесь, что используете правильный логин и пароль для входа в систему.", + ) return False def check_kkt_connection(self): @@ -168,15 +169,21 @@ def check_kkt_connection(self): break except (ConnectionError, TimeoutError, Exception) as e: - error_type = "сетевая" if isinstance(e, (ConnectionError, TimeoutError)) else "неизвестная" - logger.warning(f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}") + error_type = ( + "сетевая" + if isinstance(e, (ConnectionError, TimeoutError)) + else "неизвестная" + ) + logger.warning( + f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}" + ) if not connected: logger.error("Все 3 попытки подключения к ККТ не удались") windows.info_window( "Внимание", "Кассовый аппарат не подключен", - "Программа продолжит работу без фискальных операций." + "Программа продолжит работу без фискальных операций.", ) except (ConnectionError, TimeoutError) as e: logger.error(f"Ошибка при проверке подключения к ККТ: {e}") @@ -225,7 +232,6 @@ def show_main_window(): window.exec_() - class ClientForm(QDialog): """Форма с данными клиента""" @@ -320,7 +326,7 @@ def client_save(self): gender=str(self.ui.comboBox.currentText()), phone=self.ui.lineEdit_4.text(), email=self.ui.lineEdit_5.text(), - privilege=str((self.ui.comboBox_2.currentText())), + privilege=str(self.ui.comboBox_2.currentText()), ) ) session.commit() @@ -396,7 +402,7 @@ def edit_client_in_sale(self) -> None: client_id: str = self.ui.tableWidget.item(row_number, 5).text() # При типизации указываем, что search_client может быть либо экземпляром Client, либо None with Session(system.engine) as session: - search_client: Optional[Client] = ( + search_client: Client | None = ( session.query(Client).filter_by(id=client_id).first() ) # Проверяем, найден ли клиент @@ -508,9 +514,10 @@ def tracking_button_pressing(self, event) -> None: None """ logger.info("Запуск функции tracking_button_pressing") - if event.key() == QtCore.Qt.Key_Delete: - self.deleting_selected_record() - elif event.key() == QtCore.Qt.Key_Backspace: + if ( + event.key() == QtCore.Qt.Key_Delete + or event.key() == QtCore.Qt.Key_Backspace + ): self.deleting_selected_record() def deleting_selected_record(self) -> None: @@ -533,23 +540,33 @@ def deleting_selected_record(self) -> None: if current_row == system.sale_checkbox_row: # Удаляется исключенный взрослый logger.debug(f"Удаляется исключенный взрослый row={current_row}") - logger.debug(f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") + logger.debug( + f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" + ) # Сбросить флаги system.sale_checkbox_row = None system.exclude_from_sale = 0 system.sale_dict["detail"][4] = 0 - logger.debug(f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") + logger.debug( + f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" + ) elif current_row < system.sale_checkbox_row: # Удаляется строка выше исключенного - logger.debug(f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}") - logger.debug(f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}") + logger.debug( + f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}" + ) + logger.debug( + f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}" + ) # Уменьшить индекс system.sale_checkbox_row -= 1 - logger.debug(f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}") + logger.debug( + f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}" + ) # Удалить строку self.ui.tableWidget_2.removeRow(current_row) @@ -775,7 +792,7 @@ def sale_search_clients(self) -> None: query: str = "%" + self.ui.lineEdit.text() + "%" self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client).filter(Client.last_name.ilike(query)).all() ) for client in search: @@ -791,12 +808,12 @@ def sale_search_clients(self) -> None: elif index == 0: # Поиск по фамилии и имени self.ui.tableWidget.setRowCount(0) - search: list[Type[Client]] = self.ui.lineEdit.text().title() + search: list[type[Client]] = self.ui.lineEdit.text().title() # Разбиваем поисковую фразу на две lst: Any = search.split() if len(lst) == 2: with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter( and_( @@ -826,7 +843,7 @@ def sale_search_clients(self) -> None: # Поиск инвалидов self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter(Client.privilege.ilike("%" + "и")) .all() @@ -845,7 +862,7 @@ def sale_search_clients(self) -> None: # Поиск многодетных self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter(Client.privilege.ilike("%" + "м")) .all() @@ -897,11 +914,11 @@ def adding_client_to_sale(self, *args, **kwargs) -> None: self.ui.tableWidget_3.setColumnWidth(3, 7) # Если продажа новая - обновляем статус system.sale_status = 0 - logger.warning(f"Статус продажи - новая") + logger.warning("Статус продажи - новая") row_number: int = self.ui.tableWidget.currentRow() res: str = self.ui.tableWidget.item(row_number, 5).text() with Session(system.engine) as session: - search_client: Type[Client] = ( + search_client: type[Client] = ( session.query(Client).filter_by(id=res).first() ) # Вычисляем возраст клиента @@ -1141,12 +1158,16 @@ def sale_update(self) -> None: # Рассчитываем цену со скидкой if adult_with_discount_count > 0: - price_adult_with_discount = int(system.sale_dict["price_adult"] * (1 - system.sale_discount / 100)) + price_adult_with_discount = int( + system.sale_dict["price_adult"] * (1 - system.sale_discount / 100) + ) else: price_adult_with_discount = 0 if child_with_discount_count > 0: - price_child_with_discount = int(system.sale_dict["price_child"] * (1 - system.sale_discount / 100)) + price_child_with_discount = int( + system.sale_dict["price_child"] * (1 - system.sale_discount / 100) + ) else: price_child_with_discount = 0 @@ -1166,8 +1187,12 @@ def sale_update(self) -> None: ) logger.debug(f"detail ИТОГО: {system.sale_dict['detail']}") - logger.debug(f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}") - logger.debug(f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}") + logger.debug( + f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}" + ) + logger.debug( + f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}" + ) # Считаем без исключённых взрослых adult_count_in_sale = 0 @@ -1186,7 +1211,9 @@ def sale_update(self) -> None: # Синхронизируем system.sale_dict["kol_adult"] = adult_count_in_sale - logger.debug(f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}") + logger.debug( + f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}" + ) # Считаем итог itog: int = calculate_itog() @@ -1225,7 +1252,7 @@ def sale_update(self) -> None: system.sale_tickets = tickets # Проверяем есть ли в продаже взрослый - if system.sale_dict['kol_adult'] >= 1 or system.exclude_from_sale == 1: + if system.sale_dict["kol_adult"] >= 1 or system.exclude_from_sale == 1: self.ui.pushButton_5.setEnabled(True) else: self.ui.pushButton_5.setEnabled(False) @@ -1244,15 +1271,18 @@ def sale_update(self) -> None: # Если есть исключённый if row == system.sale_checkbox_row: checkbox.setEnabled(True) - logger.debug(f"Checkbox row={row} enabled (исключенный)") + logger.debug( + f"Checkbox row={row} enabled (исключенный)" + ) else: checkbox.setEnabled(False) logger.debug(f"Checkbox row={row} disabled") else: # Нет исключённых - все доступны checkbox.setEnabled(True) - logger.debug(f"Checkbox row={row} enabled (нет исключенных)") - + logger.debug( + f"Checkbox row={row} enabled (нет исключенных)" + ) def apply_discounts(self, row: int, price: int, type_ticket: str) -> None: """ @@ -1288,7 +1318,9 @@ def apply_discounts(self, row: int, price: int, type_ticket: str) -> None: if type_ticket == "взрослый" and system.exclude_from_sale == 1: checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) if checkbox.isChecked() and row == system.sale_checkbox_row: - logger.info(f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок") + logger.info( + f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок" + ) return # НЕ применяем скидки из checkBox_2! # Иначе проверяем активен ли checkbox со скидкой и размер > 0 @@ -1299,9 +1331,11 @@ def apply_discounts(self, row: int, price: int, type_ticket: str) -> None: # Используем другое имя переменной для цены по акции promo_price = calculate_discount(price, system.sale_discount) # Если checkbox в акт - применяем к этой строке скидку - if (self.ui.tableWidget_2.cellWidget(row, 7) - .findChild(QCheckBox) - .isChecked()): # Если False → применить скидку + if ( + self.ui.tableWidget_2.cellWidget(row, 7) + .findChild(QCheckBox) + .isChecked() + ): # Если False → применить скидку # Применяем скидку ТОЛЬКО если checkbox НЕ активен if type_ticket == "взрослый": system.count_number_of_visitors["kol_sale_adult"] += 1 @@ -1481,7 +1515,7 @@ def apply_extended_many_children_discount(self) -> None: self.ui.checkBox_2.setChecked(True) self.ui.comboBox_2.setCurrentIndex(10) # Добавляем информацию о скидке - system.sale_dict['detail'][4] = 50 + system.sale_dict["detail"][4] = 50 # Разрешаем выбор времени посещения self.ui.comboBox.setEnabled(True) @@ -1544,7 +1578,9 @@ def adult_exclusion(self, row: int) -> None: None """ logger.info("Запуск функции adult_exclusion") - logger.debug(f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") + logger.debug( + f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" + ) # Получаем checkbox для проверки checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) @@ -1577,14 +1613,16 @@ def adult_exclusion(self, row: int) -> None: # Проверка - единственный взрослый? if adult_count == 1 and child_count == 0: - logger.warning("Невозможно исключить единственного взрослого в продаже!") + logger.warning( + "Невозможно исключить единственного взрослого в продаже!" + ) QMessageBox.warning( self, "Невозможно исключить взрослого", "В продаже только один взрослый!\n" "Нельзя исключить единственного взрослого из продажи.\n" - "Добавьте ребенка или других взрослых." + "Добавьте ребенка или других взрослых.", ) checkbox.blockSignals(True) @@ -1596,24 +1634,30 @@ def adult_exclusion(self, row: int) -> None: # Первое исключение взрослого logger.info("Исключаем взрослого из продажи") - system.sale_dict["detail"][4] = system.sale_discount if system.sale_discount else 0 - logger.debug(f"Установлен флаг исключения: detail[4] = 100") + system.sale_dict["detail"][4] = ( + system.sale_discount if system.sale_discount else 0 + ) + logger.debug("Установлен флаг исключения: detail[4] = 100") system.sale_checkbox_row = row system.exclude_from_sale = 1 self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("н")) - logger.debug(f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") + logger.debug( + f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" + ) elif system.sale_checkbox_row == row: logger.debug(f"Взрослый row={row} уже исключен, пропускаем") return else: - logger.warning(f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}") + logger.warning( + f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}" + ) QMessageBox.warning( self, "Невозможно исключить взрослого", f"В продаже уже есть исключенный взрослый (строка {system.sale_checkbox_row})!\n" - "Можно исключить только одного взрослого." + "Можно исключить только одного взрослого.", ) # СНЯТЬ checkbox @@ -1629,19 +1673,27 @@ def adult_exclusion(self, row: int) -> None: if row == system.sale_checkbox_row: # Это действительно исключенный взрослый - возвращаем logger.info(f"Возвращаем взрослого row={row} в продажу") - logger.debug(f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}") + logger.debug( + f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}" + ) self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("-")) if self.ui.checkBox_2.isChecked(): system.sale_dict["detail"][4] = system.sale_discount else: system.sale_dict["detail"][4] = 0 - logger.debug(f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}") + logger.debug( + f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}" + ) system.sale_checkbox_row = None system.exclude_from_sale = 0 - logger.debug(f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") + logger.debug( + f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" + ) else: # Это другая строка, не исключенный взрослый - logger.debug(f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем") + logger.debug( + f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем" + ) def generate_ticket_list( self, @@ -1698,7 +1750,7 @@ def save_sale(self) -> None: pc_name=system.pc_name, datetime=dt.datetime.now(), ) - logger.debug(f"Объект Sale создан") + logger.debug("Объект Sale создан") with Session(system.engine) as session: session.add(add_sale) @@ -1731,7 +1783,9 @@ def save_sale(self) -> None: session.add(add_ticket) session.commit() - logger.info(f"Продажа {system.sale_id} и все билеты успешно сохранены") + logger.info( + f"Продажа {system.sale_id} и все билеты успешно сохранены" + ) self.close() else: @@ -1744,9 +1798,7 @@ def save_sale(self) -> None: logger.warning(f"Ошибка сохранения продажи: {e}") system.sale_id = None windows.info_window( - "Ошибка базы данных", - "Не удалось сохранить продажу", - f"Ошибка: {e}" + "Ошибка базы данных", "Не удалось сохранить продажу", f"Ошибка: {e}" ) @logger.catch() @@ -1775,17 +1827,19 @@ def sale_transaction(self, payment_type, print_check) -> None: system, pq, Session, - self # передаем self, чтобы вызвать print_saved_tickets + self, # передаем self, чтобы вызвать print_saved_tickets ) # Подключение сигналов self.worker.progress_updated.connect(self.progress_window.update_status) self.worker.finished.connect(self.on_transaction_finished) - self.worker.save_sale_signal.connect(self.save_sale, Qt.BlockingQueuedConnection) + self.worker.save_sale_signal.connect( + self.save_sale, Qt.BlockingQueuedConnection + ) self.worker.error_signal.connect(self.handle_error) # Обработка ошибок self.worker.info_signal.connect(self.handle_info) self.worker.print_ticket_signal.connect(self.print_saved_tickets) - #self.worker.close_window_signal.connect(self.close) + # self.worker.close_window_signal.connect(self.close) self.worker.close_window_signal.connect(self.progress_window.close) # Поток @@ -1799,7 +1853,6 @@ def sale_transaction(self, payment_type, print_check) -> None: self.thread.start() - def handle_error(self, title: str, text: str, details: str): """ Обрабатывает ошибку, закрывая окно прогресса и отображая сообщение с ошибкой. @@ -1813,7 +1866,7 @@ def handle_error(self, title: str, text: str, details: str): None: Функция ничего не возвращает, но закрывает окно прогресса и отображает сообщение об ошибке. """ # Закрытие окна прогресса перед отображением ошибки - if hasattr(self, 'progress_window') and self.progress_window: + if hasattr(self, "progress_window") and self.progress_window: self.progress_window.close() # Создание окна с ошибкой @@ -1848,19 +1901,18 @@ def on_transaction_finished(self): None: Эта функция ничего не возвращает, но закрывает окно прогресса и логирует завершение транзакции. """ logger.info("Запуск функции on_transaction_finished") - if hasattr(self, '_transaction_finished') and self._transaction_finished: + if hasattr(self, "_transaction_finished") and self._transaction_finished: return self._transaction_finished = True logger.info("Завершение транзакции") # Закрываем окно прогресса - if hasattr(self, 'progress_window') and self.progress_window: + if hasattr(self, "progress_window") and self.progress_window: self.progress_window.close() self.progress_window = None if self.main_window: self.main_window.main_button_all_sales() - @logger.catch() def sale_return(self): """ @@ -1893,7 +1945,9 @@ def sale_return(self): if sale.status == 5: logger.debug("Требуется частичный возврат.") partial_return: int = int(sale.partial_return) - new_tickets: dict | dict = generating_parts_for_partial_returns(tickets, partial_return) + new_tickets: dict = generating_parts_for_partial_returns( + tickets, partial_return + ) logger.debug(f"Список билетов: {new_tickets}") # 1 - карта, 2 - наличные if sale.payment_type == 1: @@ -1917,7 +1971,7 @@ def sale_return(self): logger.debug("Продажа оплачена. Запускаем возврат") if payment_type == 102: state_check = pq.check_open( - tickets, payment_type, system.user, 2, 1,None + tickets, payment_type, system.user, 2, 1, None ) elif payment_type == 101: logger.debug( @@ -1932,7 +1986,9 @@ def sale_return(self): amount=price, progress_signal=None, operation_type=2, # возврат - error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) + error_callback=lambda title, msg, code: windows.info_window( + title, msg, str(code) + ), ) elif sale.status == 5: bank, payment = pq.universal_terminal_operation( @@ -1940,7 +1996,9 @@ def sale_return(self): amount=partial_return, progress_signal=None, operation_type=2, # возврат - error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) + error_callback=lambda title, msg, code: windows.info_window( + title, msg, str(code) + ), ) if bank == 1: check = pq.read_pinpad_file(remove_newline=False) @@ -2026,7 +2084,9 @@ def sale_return(self): amount=price, progress_signal=None, operation_type=2, # возврат - error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) + error_callback=lambda title, msg, code: windows.info_window( + title, msg, str(code) + ), ) if bank == 1: @@ -2102,7 +2162,9 @@ def sale_canceling(self): amount=price, progress_signal=None, operation_type=3, # отмена - error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) + error_callback=lambda title, msg, code: windows.info_window( + title, msg, str(code) + ), ) if bank == 1: check = pq.read_pinpad_file(remove_newline=False) @@ -2287,7 +2349,7 @@ def is_printing_enabled(self) -> bool: Во всех остальных случаях возвращает False. """ # Проверяем наличие атрибута и что он не None - if not hasattr(system, 'ticket_print') or system.ticket_print is None: + if not hasattr(system, "ticket_print") or system.ticket_print is None: return False # Приводим к строке и сравниваем с 'on' @@ -2300,7 +2362,9 @@ def print_saved_tickets(self): """ logger.info("Запуск функции print_saved_tickets") if not self.is_printing_enabled(): - logger.info("Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)") + logger.info( + "Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)" + ) return try: self.sale_generate_saved_tickets() @@ -2361,9 +2425,7 @@ def open_pay_form(self, txt): # Оплата только для новых продаж if system.sale_status != 0: windows.info_window( - "Внимание", - "Оплата картой доступна только для новых продаж.", - "" + "Внимание", "Оплата картой доступна только для новых продаж.", "" ) return @@ -2371,8 +2433,7 @@ def open_pay_form(self, txt): logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") # Создаем тестовый слип-чек slip = "DEBUG_SLIP\nДата: {}\nСумма: {}\nОдобрено".format( - dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), - txt + dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt ) # Сохраняем тестовые данные self.save_debug_payment(Payment.Card, slip) @@ -2388,9 +2449,7 @@ def open_pay_form(self, txt): # Оплата только для новых продаж if system.sale_status != 0: windows.info_window( - "Внимание", - "Оплата наличными доступна только для новых продаж.", - "" + "Внимание", "Оплата наличными доступна только для новых продаж.", "" ) return logger.info("Оплата наличными") @@ -2402,14 +2461,13 @@ def open_pay_form(self, txt): windows.info_window( "Внимание", "Оплата картой доступна только для новых или не фискализированных продаж.", - "" + "", ) return if dev_mode: logger.info("РЕЖИМ ОТЛАДКИ: Имитация offline оплаты") slip = "DEBUG_SLIP_OFFLINE\nДата: {}\nСумма: {}\nОффлайн".format( - dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), - txt + dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt ) self.save_debug_payment(Payment.Offline, slip) self.accept() @@ -2417,12 +2475,12 @@ def open_pay_form(self, txt): user_choice = windows.info_dialog_window( "Подтверждение", - f"Вы точно хотите фискализировать операцию?\n\n" - f"Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" - f"Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" - f"Операция считается успешной, если в банковском слип-чеке:\n" - f" - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" - f" - указано слово 'ОДОБРЕНО'.", + "Вы точно хотите фискализировать операцию?\n\n" + "Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" + "Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" + "Операция считается успешной, если в банковском слип-чеке:\n" + " - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" + " - указано слово 'ОДОБРЕНО'.", ) if user_choice == 1: logger.info("Оплата банковской картой offline") @@ -2435,10 +2493,12 @@ def open_pay_form(self, txt): def save_debug_payment(payment_type, slip_text): """Сохранение тестового платежа в режиме отладки""" try: - logger.debug(f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}") + logger.debug( + f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}" + ) # ... код для сохранения в БД ... except Exception as e: - logger.error(f"Ошибка сохранения тестового платежа: {str(e)}") + logger.error(f"Ошибка сохранения тестового платежа: {e!s}") class PayForm(QDialog): @@ -2515,7 +2575,9 @@ def get_slip(self) -> None: None: Функция не возвращает значений, отображает данные слип-чека в форме. """ logger.info("Запуск функции get_slip") - card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data(system.sale_id) + card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data( + system.sale_id + ) slip: SlipForm = SlipForm() slip.ui.label_5.setText(card_tail) @@ -2663,7 +2725,7 @@ def main_search_clients(self) -> None: # Поиск по номеру телефона self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client).filter(Client.phone.ilike(user_filter)).all() ) for client in search: @@ -2682,7 +2744,7 @@ def main_search_clients(self) -> None: # Поиск по фамилии self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter(Client.last_name.ilike(user_filter)) .all() @@ -2706,10 +2768,10 @@ def main_search_clients(self) -> None: # Разбиваем поисковую фразу на две lst: Any = user_filter.split() # Инициализация переменной search пустым списком, чтобы избежать ошибки - search: list[Type[Client]] = [] + search: list[type[Client]] = [] if len(lst) == 2: with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter( and_( @@ -2741,7 +2803,7 @@ def main_search_clients(self) -> None: # Поиск инвалидов self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter(Client.privilege.ilike("%" + "и")) .all() @@ -2762,7 +2824,7 @@ def main_search_clients(self) -> None: # Поиск многодетных self.ui.tableWidget.setRowCount(0) with Session(system.engine) as session: - search: list[Type[Client]] = ( + search: list[type[Client]] = ( session.query(Client) .filter(Client.privilege.ilike("%" + "м")) .all() @@ -2804,7 +2866,7 @@ def main_edit_client(self) -> None: logger.error("Ячейка пуста или не существует.") return with Session(system.engine) as session: - search_client: Type[Client] = ( + search_client: type[Client] = ( session.query(Client).filter_by(id=client_id).first() ) if search_client is None: @@ -2834,7 +2896,7 @@ def main_edit_client(self) -> None: client.show() # Сохраняем параметры данных об уже существующем клиенте system.client_update = 1 - logger.info(f"Обновляем информацию о клиенте") + logger.info("Обновляем информацию о клиенте") client.exec_() def main_filter_clear(self) -> None: @@ -2972,7 +3034,7 @@ def main_search_selected_sale(self) -> None: sale.ui.pushButton_7.setEnabled(False) sale.ui.pushButton_8.setEnabled(False) # Если продажа требует повторный возврат по банковскому терминалу - elif sale_status == 3: + elif sale_status == 3 or sale_status == 5: # Кнопка сохранить sale.ui.pushButton_3.setEnabled(False) # Кнопка оплатить @@ -2987,37 +3049,7 @@ def main_search_selected_sale(self) -> None: sale.ui.pushButton_7.setEnabled(False) sale.ui.pushButton_8.setEnabled(False) # Если продажа требует частичный возврат - elif sale_status == 5: - # Кнопка сохранить - sale.ui.pushButton_3.setEnabled(False) - # Кнопка оплатить - sale.ui.pushButton_5.setEnabled(False) - # Кнопка обновить - sale.ui.pushButton_10.setEnabled(False) - # Кнопка возврат - sale.ui.pushButton_6.setEnabled(True) - # Кнопка отмены платежа по банковской карте - sale.ui.pushButton_14.setEnabled(False) - # Кнопки просмотра и печати билетов - sale.ui.pushButton_7.setEnabled(False) - sale.ui.pushButton_8.setEnabled(False) - # Если продажа требует частичный возврат - elif sale_status == 7: - # Кнопка сохранить - sale.ui.pushButton_3.setEnabled(False) - # Кнопка оплатить - sale.ui.pushButton_5.setEnabled(False) - # Кнопка обновить - sale.ui.pushButton_10.setEnabled(False) - # Кнопка возврат - sale.ui.pushButton_6.setEnabled(False) - # Кнопка отмены платежа по банковской карте - sale.ui.pushButton_14.setEnabled(False) - # Кнопки просмотра и печати билетов - sale.ui.pushButton_7.setEnabled(False) - sale.ui.pushButton_8.setEnabled(False) - # Если продажа отменена - elif sale_status == 8: + elif sale_status == 7 or sale_status == 8: # Кнопка сохранить sale.ui.pushButton_3.setEnabled(False) # Кнопка оплатить @@ -3159,16 +3191,30 @@ def main_button_all_sales(self) -> None: else: # Определение периода для 1, 3 и 7 дней без учета времени if self.ui.radioButton.isChecked(): - filter_start = dt.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) - filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) + filter_start = dt.datetime.today().replace( + hour=0, minute=0, second=0, microsecond=0 + ) + filter_end = dt.datetime.today().replace( + hour=23, minute=59, second=59, microsecond=999999 + ) elif self.ui.radioButton_2.isChecked(): - filter_start = (dt.datetime.today() - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0) - filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) + filter_start = (dt.datetime.today() - timedelta(days=3)).replace( + hour=0, minute=0, second=0, microsecond=0 + ) + filter_end = dt.datetime.today().replace( + hour=23, minute=59, second=59, microsecond=999999 + ) elif self.ui.radioButton_3.isChecked(): - filter_start = (dt.datetime.today() - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0) - filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) + filter_start = (dt.datetime.today() - timedelta(days=7)).replace( + hour=0, minute=0, second=0, microsecond=0 + ) + filter_end = dt.datetime.today().replace( + hour=23, minute=59, second=59, microsecond=999999 + ) # Определяем, какие статусы фильтровать - sale_status_filter = [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] + sale_status_filter = ( + [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] + ) # Генерация запроса with Session(system.engine) as session: @@ -3314,7 +3360,7 @@ def main_open_sale(self) -> None: system.sale_dict.update(sale_initial_values) # сбрасываем id и статус продажи system.sale_id = None - system.sale_status = 0 # Т.е. новая продажа + system.sale_status = 0 # Т.е. новая продажа system.sale_tickets = None sale.exec_() @@ -3389,30 +3435,36 @@ def fetch_stat_data(self, dt1: str, dt2: str): logger.info("Запуск функции fetch_stat_data") with Session(system.engine) as session: sales = session.execute( - select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) - .where(and_(Sale.status == "1", Sale.datetime.between(dt1, dt2))) + select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( + and_(Sale.status == "1", Sale.datetime.between(dt1, dt2)) + ) ).all() sales_return = session.execute( - select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) - .where(and_( - Sale.datetime_return.between(dt1, dt2), - Sale.status.in_([2, 3, 4, 5, 6]) - )) + select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( + and_( + Sale.datetime_return.between(dt1, dt2), + Sale.status.in_([2, 3, 4, 5, 6]), + ) + ) ).all() tickets = session.execute( select( - Ticket.ticket_type, Ticket.arrival_time, Ticket.description, Sale.status, Sale.id, Ticket.price + Ticket.ticket_type, + Ticket.arrival_time, + Ticket.description, + Sale.status, + Sale.id, + Ticket.price, ).where( and_( Sale.id == Ticket.id_sale, Sale.status == "1", Ticket.datetime.between(dt1, dt2), - ) + ) ) ).all() return sales, sales_return, tickets - def fill_sales_table(self, data): """ Заполнение таблицы продаж данными. @@ -3431,9 +3483,9 @@ def fill_sales_table(self, data): for row, row_data in enumerate(data): for col, value in enumerate(row_data): if value is not None: - self.ui.tableWidget_4.setItem(row, col, QTableWidgetItem(str(value))) - - + self.ui.tableWidget_4.setItem( + row, col, QTableWidgetItem(str(value)) + ) def fill_ticket_table(self, data): """ @@ -3506,7 +3558,7 @@ def main_otchet_kassira(self) -> None: if os.path.exists(path): os.remove(path) # Проверяем, есть ли данные о продажах в system.sales_data_summary - if not hasattr(system, 'sales_data_summary') or not system.sales_data_summary: + if not hasattr(system, "sales_data_summary") or not system.sales_data_summary: logger.error("Нет данных о продажах для отчета кассира") return # Получаем имя текущего компьютера @@ -3529,14 +3581,16 @@ def main_otchet_kassira(self) -> None: str(total_sales_card), str(total_sales_cash), str(total_returns_card), - str(total_returns_cash) + str(total_returns_cash), ] # Получаем даты для отчета dt1, dt2 = self.get_date_range_from_ui() # Получаем информацию о кассире - kassir = system.user # Предполагаем, что данные о кассире хранятся в system.user + kassir = ( + system.user + ) # Предполагаем, что данные о кассире хранятся в system.user # Вызываем функцию для формирования отчета otchet.otchet_kassira(values, dt1, dt2, kassir) diff --git a/modules/config.py b/modules/config.py index 3cb0a17..61938a7 100644 --- a/modules/config.py +++ b/modules/config.py @@ -2,11 +2,10 @@ import os from configparser import ( ConfigParser, - NoSectionError, - NoOptionError, MissingSectionHeaderError, + NoOptionError, + NoSectionError, ) -from typing import List, Optional logger = logging.getLogger(__name__) @@ -18,7 +17,7 @@ def __init__(self, config_file="config.ini"): # Инициализируем список ПК self.pcs = self._get_pc_list() - def _get_pc_list(self) -> List[str]: + def _get_pc_list(self) -> list[str]: """Возвращает список имен ПК из конфигурации""" pc_count = int(self.get("kol")) pc_list = [] @@ -122,7 +121,6 @@ def load_config(self): ) config_data[key] = config.get(section, key) - return config_data except (NoSectionError, NoOptionError) as e: logger.error(f"Ошибка в конфигурационном файле: {e}") @@ -136,6 +134,6 @@ def load_config(self): logger.error(f"Неизвестная ошибка при чтении файла конфигурации: {e}") raise RuntimeError(f"Неизвестная ошибка: {e}") - def get(self, key: str) -> Optional[str]: + def get(self, key: str) -> str | None: value = self.config_data.get(key) return value diff --git a/modules/libfptr10.py b/modules/libfptr10.py index ced442d..b0c9fc1 100644 --- a/modules/libfptr10.py +++ b/modules/libfptr10.py @@ -1,27 +1,25 @@ -# -*- coding: utf-8 -*- # AUTO GENERATED FILE import ctypes -import sys -import json import datetime +import json import os import platform -import warnings +import sys if sys.version_info[0] == 3: - if platform.system() == 'Windows': + if platform.system() == "Windows": from winreg import * TEXT = str RANGE = range else: - if platform.system() == 'Windows': + if platform.system() == "Windows": from _winreg import * TEXT = basestring RANGE = xrange -class IFptr(object): +class IFptr: ( LIBFPTR_PARAM_TEXT, LIBFPTR_PARAM_TEXT_WRAP, @@ -1672,231 +1670,294 @@ class IFptr(object): DEFAULT_BUFF_SIZE = 512 CREATE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(ctypes.c_void_p)) - CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.POINTER(ctypes.c_void_p), - ctypes.c_wchar_p) + CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.POINTER(ctypes.c_void_p), ctypes.c_wchar_p + ) DESTROY_METHOD = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_void_p)) GET_VERSION_METHOD = ctypes.CFUNCTYPE(ctypes.c_char_p) - METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p) - - IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p) - - GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p) - GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_int) - GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_int) - RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p) - - SET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p) - GET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_int) - - SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_wchar_p) - GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_wchar_p, - ctypes.c_int) - - SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) - GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_int, - ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) - - SET_INT_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_uint) - GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, - ctypes.c_void_p, - ctypes.c_int) - - SET_BOOL_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_int) - GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_int) - - SET_DOUBLE_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_double) - GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, - ctypes.c_void_p, - ctypes.c_int) - - SET_STRING_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_wchar_p) - GET_STRING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_wchar_p, ctypes.c_int) - - SET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, - ctypes.c_int, - ctypes.c_int) - GET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, - ctypes.c_void_p, - ctypes.c_int, - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int), - ctypes.POINTER(ctypes.c_int)) - IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_int) - - LOG_WRITE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p, - ctypes.c_int, - ctypes.c_wchar_p) - - SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_int, - ctypes.c_void_p) - - CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, - ctypes.c_void_p, - ctypes.c_wchar_p) + METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) + + IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) + + GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) + GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int + ) + GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int + ) + RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, ctypes.c_void_p) + + SET_SETTINGS_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p + ) + GET_SETTINGS_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int + ) + + SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p + ) + GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int + ) + + SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( + None, + ctypes.c_void_p, + ctypes.c_int, + ctypes.POINTER(ctypes.c_ubyte), + ctypes.c_int, + ) + GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, + ctypes.c_void_p, + ctypes.c_int, + ctypes.POINTER(ctypes.c_ubyte), + ctypes.c_int, + ) + + SET_INT_METHOD = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.c_int, ctypes.c_uint + ) + GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, ctypes.c_void_p, ctypes.c_int) + + SET_BOOL_METHOD = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.c_int, ctypes.c_int + ) + GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_int) + + SET_DOUBLE_METHOD = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.c_int, ctypes.c_double + ) + GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_void_p, ctypes.c_int) + + SET_STRING_METHOD = ctypes.CFUNCTYPE( + None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p + ) + GET_STRING_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_int + ) + + SET_DATETIME_METHOD = ctypes.CFUNCTYPE( + None, + ctypes.c_void_p, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ctypes.c_int, + ) + GET_DATETIME_METHOD = ctypes.CFUNCTYPE( + None, + ctypes.c_void_p, + ctypes.c_int, + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ) + IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_int + ) + + LOG_WRITE_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int, ctypes.c_wchar_p + ) + + SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p + ) + + CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE( + ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p + ) def __init__(self, lib_path="", fptr_id=""): assert sys.version_info >= (2, 6) self.lib_path = lib_path try: - if platform.system() == 'Windows': + if platform.system() == "Windows": if len(self.lib_path) == 0: - rk = OpenKey(HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT") + rk = OpenKey( + HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT" + ) rv = QueryValueEx(rk, "INSTALL_DIR")[0] - self.lib_path = os.path.join(rv, 'bin', 'fptr10.dll') + self.lib_path = os.path.join(rv, "bin", "fptr10.dll") else: - if not self.lib_path.endswith('fptr10.dll'): - self.lib_path = os.path.join(self.lib_path, 'fptr10.dll') + if not self.lib_path.endswith("fptr10.dll"): + self.lib_path = os.path.join(self.lib_path, "fptr10.dll") try: self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) except OSError: - ctypes.CDLL(os.path.join(os.path.dirname(self.lib_path), 'msvcp140.dll'), mode=ctypes.RTLD_LOCAL) + ctypes.CDLL( + os.path.join(os.path.dirname(self.lib_path), "msvcp140.dll"), + mode=ctypes.RTLD_LOCAL, + ) self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) - elif platform.system() == 'Darwin': - if not self.lib_path.endswith('fptr10.framework/fptr10'): - self.lib_path = os.path.join(self.lib_path, 'fptr10.framework/fptr10') + elif platform.system() == "Darwin": + if not self.lib_path.endswith("fptr10.framework/fptr10"): + self.lib_path = os.path.join( + self.lib_path, "fptr10.framework/fptr10" + ) try: self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) except OSError: self.lib_path = lib_path - if not self.lib_path.endswith('libfptr10.dylib'): - self.lib_path = os.path.join(self.lib_path, 'libfptr10.dylib') + if not self.lib_path.endswith("libfptr10.dylib"): + self.lib_path = os.path.join(self.lib_path, "libfptr10.dylib") self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) else: - if not self.lib_path.endswith('libfptr10.so'): - self.lib_path = os.path.join(self.lib_path, 'libfptr10.so') + if not self.lib_path.endswith("libfptr10.so"): + self.lib_path = os.path.join(self.lib_path, "libfptr10.so") self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) except OSError: raise Exception( - 'Driver library not found in {}'.format( - '\"' + - os.path.dirname( - self.lib_path) + - '\"' if len(lib_path) != 0 else 'search folders')) + "Driver library not found in {}".format( + '"' + os.path.dirname(self.lib_path) + '"' + if len(lib_path) != 0 + else "search folders" + ) + ) self.interface = ctypes.c_void_p(0) if fptr_id: - _create = self.CREATE_WITH_ID_METHOD(('libfptr_create_with_id', self.library)) - create_r = _create(ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id)) + _create = self.CREATE_WITH_ID_METHOD( + ("libfptr_create_with_id", self.library) + ) + create_r = _create( + ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id) + ) else: - _create = self.CREATE_METHOD(('libfptr_create', self.library)) + _create = self.CREATE_METHOD(("libfptr_create", self.library)) create_r = _create(ctypes.pointer(self.interface)) if create_r == -2: - raise AttributeError('Invalid [id] format') + raise AttributeError("Invalid [id] format") elif create_r != 0: - raise Exception('Can`t create driver handle') - - self._setByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_param_bytearray', self.library)) - self._setUserByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_user_param_bytearray', self.library)) + raise Exception("Can`t create driver handle") + + self._setByteArray = self.SET_BYTEARRAY_METHOD( + ("libfptr_set_param_bytearray", self.library) + ) + self._setUserByteArray = self.SET_BYTEARRAY_METHOD( + ("libfptr_set_user_param_bytearray", self.library) + ) self._setNonPrintableByteArray = self.SET_BYTEARRAY_METHOD( - ('libfptr_set_non_printable_param_bytearray', self.library)) - self._getByteArray = self.GET_BYTEARRAY_METHOD(('libfptr_get_param_bytearray', self.library)) - - self._setInt = self.SET_INT_METHOD(('libfptr_set_param_int', self.library)) - self._setUserInt = self.SET_INT_METHOD(('libfptr_set_user_param_int', self.library)) - self._setNonPrintableInt = self.SET_INT_METHOD(('libfptr_set_non_printable_param_int', self.library)) - self._getInt = self.GET_INT_METHOD(('libfptr_get_param_int', self.library)) - - self._setBool = self.SET_BOOL_METHOD(('libfptr_set_param_bool', self.library)) - self._setUserBool = self.SET_BOOL_METHOD(('libfptr_set_user_param_bool', self.library)) - self._setNonPrintableBool = self.SET_BOOL_METHOD(('libfptr_set_non_printable_param_bool', self.library)) - self._getBool = self.GET_BOOL_METHOD(('libfptr_get_param_bool', self.library)) - - self._setDouble = self.SET_DOUBLE_METHOD(('libfptr_set_param_double', self.library)) - self._setUserDouble = self.SET_DOUBLE_METHOD(('libfptr_set_user_param_double', self.library)) - self._setNonPrintableDouble = self.SET_DOUBLE_METHOD(('libfptr_set_non_printable_param_double', self.library)) - self._getDouble = self.GET_DOUBLE_METHOD(('libfptr_get_param_double', self.library)) - - self._setDateTime = self.SET_DATETIME_METHOD(('libfptr_set_param_datetime', self.library)) - self._setUserDateTime = self.SET_DATETIME_METHOD(('libfptr_set_user_param_datetime', self.library)) + ("libfptr_set_non_printable_param_bytearray", self.library) + ) + self._getByteArray = self.GET_BYTEARRAY_METHOD( + ("libfptr_get_param_bytearray", self.library) + ) + + self._setInt = self.SET_INT_METHOD(("libfptr_set_param_int", self.library)) + self._setUserInt = self.SET_INT_METHOD( + ("libfptr_set_user_param_int", self.library) + ) + self._setNonPrintableInt = self.SET_INT_METHOD( + ("libfptr_set_non_printable_param_int", self.library) + ) + self._getInt = self.GET_INT_METHOD(("libfptr_get_param_int", self.library)) + + self._setBool = self.SET_BOOL_METHOD(("libfptr_set_param_bool", self.library)) + self._setUserBool = self.SET_BOOL_METHOD( + ("libfptr_set_user_param_bool", self.library) + ) + self._setNonPrintableBool = self.SET_BOOL_METHOD( + ("libfptr_set_non_printable_param_bool", self.library) + ) + self._getBool = self.GET_BOOL_METHOD(("libfptr_get_param_bool", self.library)) + + self._setDouble = self.SET_DOUBLE_METHOD( + ("libfptr_set_param_double", self.library) + ) + self._setUserDouble = self.SET_DOUBLE_METHOD( + ("libfptr_set_user_param_double", self.library) + ) + self._setNonPrintableDouble = self.SET_DOUBLE_METHOD( + ("libfptr_set_non_printable_param_double", self.library) + ) + self._getDouble = self.GET_DOUBLE_METHOD( + ("libfptr_get_param_double", self.library) + ) + + self._setDateTime = self.SET_DATETIME_METHOD( + ("libfptr_set_param_datetime", self.library) + ) + self._setUserDateTime = self.SET_DATETIME_METHOD( + ("libfptr_set_user_param_datetime", self.library) + ) self._setNonPrintableDateTime = self.SET_DATETIME_METHOD( - ('libfptr_set_non_printable_param_datetime', self.library)) - self._getDateTime = self.GET_DATETIME_METHOD(('libfptr_get_param_datetime', self.library)) - - self._setString = self.SET_STRING_METHOD(('libfptr_set_param_str', self.library)) - self._setUserString = self.SET_STRING_METHOD(('libfptr_set_user_param_str', self.library)) - self._setNonPrintableString = self.SET_STRING_METHOD(('libfptr_set_non_printable_param_str', self.library)) - self._getString = self.GET_STRING_METHOD(('libfptr_get_param_str', self.library)) - - self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD(('libfptr_is_param_available', self.library)) - - self._setSettings = self.SET_SETTINGS_METHOD(('libfptr_set_settings', self.library)) - self._getSettings = self.GET_SETTINGS_METHOD(('libfptr_get_settings', self.library)) - - self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD(('libfptr_get_single_setting', self.library)) - self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD(('libfptr_set_single_setting', self.library)) - - self._getVersion = self.GET_VERSION_METHOD(('libfptr_get_version_string', self.library)) - self._isOpened = self.IS_OPENED_METHOD(('libfptr_is_opened', self.library)) - self._errorCode = self.GET_ERROR_CODE_METHOD(('libfptr_error_code', self.library)) - self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD(('libfptr_error_description', self.library)) - self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD(('libfptr_error_recommendation', self.library)) - self._resetError = self.RESET_ERROR_METHOD(('libfptr_reset_error', self.library)) - self._logWrite = self.LOG_WRITE_METHOD(('libfptr_log_write_ex', self.library)) - self._showProperties = self.SHOW_PROPERTIES_METHOD(('libfptr_show_properties', self.library)) - self._changeLabel = self.CHANGE_LABEL_METHOD(('libfptr_change_label', self.library)) + ("libfptr_set_non_printable_param_datetime", self.library) + ) + self._getDateTime = self.GET_DATETIME_METHOD( + ("libfptr_get_param_datetime", self.library) + ) + + self._setString = self.SET_STRING_METHOD( + ("libfptr_set_param_str", self.library) + ) + self._setUserString = self.SET_STRING_METHOD( + ("libfptr_set_user_param_str", self.library) + ) + self._setNonPrintableString = self.SET_STRING_METHOD( + ("libfptr_set_non_printable_param_str", self.library) + ) + self._getString = self.GET_STRING_METHOD( + ("libfptr_get_param_str", self.library) + ) + + self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD( + ("libfptr_is_param_available", self.library) + ) + + self._setSettings = self.SET_SETTINGS_METHOD( + ("libfptr_set_settings", self.library) + ) + self._getSettings = self.GET_SETTINGS_METHOD( + ("libfptr_get_settings", self.library) + ) + + self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD( + ("libfptr_get_single_setting", self.library) + ) + self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD( + ("libfptr_set_single_setting", self.library) + ) + + self._getVersion = self.GET_VERSION_METHOD( + ("libfptr_get_version_string", self.library) + ) + self._isOpened = self.IS_OPENED_METHOD(("libfptr_is_opened", self.library)) + self._errorCode = self.GET_ERROR_CODE_METHOD( + ("libfptr_error_code", self.library) + ) + self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD( + ("libfptr_error_description", self.library) + ) + self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD( + ("libfptr_error_recommendation", self.library) + ) + self._resetError = self.RESET_ERROR_METHOD( + ("libfptr_reset_error", self.library) + ) + self._logWrite = self.LOG_WRITE_METHOD(("libfptr_log_write_ex", self.library)) + self._showProperties = self.SHOW_PROPERTIES_METHOD( + ("libfptr_show_properties", self.library) + ) + self._changeLabel = self.CHANGE_LABEL_METHOD( + ("libfptr_change_label", self.library) + ) def __del__(self): - destroy = self.DESTROY_METHOD(('libfptr_destroy', self.library)) + destroy = self.DESTROY_METHOD(("libfptr_destroy", self.library)) destroy(ctypes.pointer(self.interface)) def version(self): @@ -1957,7 +2018,7 @@ def getSettingsStr(self): def setSingleSetting(self, key, value): if isinstance(value, bool): - value = '1' if value else '0' + value = "1" if value else "0" elif isinstance(value, int): value = str(value) self._setSingleSetting(self.interface, key, value) @@ -1973,77 +2034,149 @@ def getSingleSetting(self, key): def setParam(self, paramId, param): if isinstance(param, bool): self._setBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) - elif isinstance(param, int) or (sys.version_info < (3, 0) and isinstance(param, long)): + elif isinstance(param, int) or ( + sys.version_info < (3, 0) and isinstance(param, long) + ): if param > 4294967295: - raise ValueError("Invalid 'param' value {0}".format(param)) + raise ValueError(f"Invalid 'param' value {param}") if param < 0: - self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) else: - self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) + self._setInt( + self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) + ) elif isinstance(param, float): - self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) elif isinstance(param, TEXT): - self._setString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) - elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): - self._setByteArray(self.interface, ctypes.c_int(paramId), - (ctypes.c_ubyte * len(param))(*param), len(param)) + self._setString( + self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) + ) + elif ( + isinstance(param, list) + or isinstance(param, bytearray) + or isinstance(param, bytes) + ): + self._setByteArray( + self.interface, + ctypes.c_int(paramId), + (ctypes.c_ubyte * len(param))(*param), + len(param), + ) elif isinstance(param, datetime.datetime): - self._setDateTime(self.interface, ctypes.c_int(paramId), param.date().year, - param.date().month, - param.date().day, - param.time().hour, param.time().minute, param.time().second) + self._setDateTime( + self.interface, + ctypes.c_int(paramId), + param.date().year, + param.date().month, + param.date().day, + param.time().hour, + param.time().minute, + param.time().second, + ) else: - raise TypeError("Invalid 'param' type {0}".format(type(param))) + raise TypeError(f"Invalid 'param' type {type(param)}") def setUserParam(self, paramId, param): if isinstance(param, bool): - self._setUserBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) + self._setUserBool( + self.interface, ctypes.c_int(paramId), ctypes.c_int(param) + ) elif isinstance(param, int): if param > 4294967295: - raise ValueError("Invalid 'param' value {0}".format(param)) + raise ValueError(f"Invalid 'param' value {param}") if param < 0: - self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) else: - self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) + self._setInt( + self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) + ) elif isinstance(param, float): - self._setUserDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setUserDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) elif isinstance(param, TEXT): - self._setUserString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) - elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): - self._setUserByteArray(self.interface, ctypes.c_int(paramId), - (ctypes.c_ubyte * len(param))(*param), len(param)) + self._setUserString( + self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) + ) + elif ( + isinstance(param, list) + or isinstance(param, bytearray) + or isinstance(param, bytes) + ): + self._setUserByteArray( + self.interface, + ctypes.c_int(paramId), + (ctypes.c_ubyte * len(param))(*param), + len(param), + ) elif isinstance(param, datetime.datetime): - self._setUserDateTime(self.interface, ctypes.c_int(paramId), param.date().year, - param.date().month, - param.date().day, - param.time().hour, param.time().minute, param.time().second) + self._setUserDateTime( + self.interface, + ctypes.c_int(paramId), + param.date().year, + param.date().month, + param.date().day, + param.time().hour, + param.time().minute, + param.time().second, + ) else: - raise TypeError("Invalid 'param' type {0}".format(type(param))) + raise TypeError(f"Invalid 'param' type {type(param)}") def setNonPrintableParam(self, paramId, param): if isinstance(param, bool): - self._setNonPrintableBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) + self._setNonPrintableBool( + self.interface, ctypes.c_int(paramId), ctypes.c_int(param) + ) elif isinstance(param, int): if param > 4294967295: - raise ValueError("Invalid 'param' value {0}".format(param)) + raise ValueError(f"Invalid 'param' value {param}") if param < 0: - self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) else: - self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) + self._setInt( + self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) + ) elif isinstance(param, float): - self._setNonPrintableDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) + self._setNonPrintableDouble( + self.interface, ctypes.c_int(paramId), ctypes.c_double(param) + ) elif isinstance(param, TEXT): - self._setNonPrintableString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) - elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): - self._setNonPrintableByteArray(self.interface, ctypes.c_int(paramId), - (ctypes.c_ubyte * len(param))(*param), len(param)) + self._setNonPrintableString( + self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) + ) + elif ( + isinstance(param, list) + or isinstance(param, bytearray) + or isinstance(param, bytes) + ): + self._setNonPrintableByteArray( + self.interface, + ctypes.c_int(paramId), + (ctypes.c_ubyte * len(param))(*param), + len(param), + ) elif isinstance(param, datetime.datetime): - self._setNonPrintableDateTime(self.interface, ctypes.c_int(paramId), param.date().year, - param.date().month, - param.date().day, - param.time().hour, param.time().minute, param.time().second) + self._setNonPrintableDateTime( + self.interface, + ctypes.c_int(paramId), + param.date().year, + param.date().month, + param.date().day, + param.time().hour, + param.time().minute, + param.time().second, + ) else: - raise TypeError("Invalid 'param' type {0}".format(type(param))) + raise TypeError(f"Invalid 'param' type {type(param)}") def getParamInt(self, paramId): value = self._getInt(self.interface, ctypes.c_int(paramId)) @@ -2059,13 +2192,20 @@ def getParamDouble(self, paramId): def getParamByteArray(self, paramId): value = (ctypes.c_ubyte * self.DEFAULT_BUFF_SIZE)() - size = self._getByteArray(self.interface, ctypes.c_int(paramId), - ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), - self.DEFAULT_BUFF_SIZE) + size = self._getByteArray( + self.interface, + ctypes.c_int(paramId), + ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), + self.DEFAULT_BUFF_SIZE, + ) if size > self.DEFAULT_BUFF_SIZE: value = (ctypes.c_ubyte * size)() - size = self._getByteArray(self.interface, ctypes.c_int(paramId), - ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), size) + size = self._getByteArray( + self.interface, + ctypes.c_int(paramId), + ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), + size, + ) return value[:size] def getParamDateTime(self, paramId): @@ -2075,16 +2215,25 @@ def getParamDateTime(self, paramId): hour = ctypes.c_int(0) minute = ctypes.c_int(0) second = ctypes.c_int(0) - self._getDateTime(self.interface, ctypes.c_int(paramId), ctypes.pointer(year), - ctypes.pointer(month), - ctypes.pointer(day), - ctypes.pointer(hour), ctypes.pointer(minute), ctypes.pointer(second)) - return datetime.datetime(year.value, month.value, day.value, hour.value, minute.value, - second.value) + self._getDateTime( + self.interface, + ctypes.c_int(paramId), + ctypes.pointer(year), + ctypes.pointer(month), + ctypes.pointer(day), + ctypes.pointer(hour), + ctypes.pointer(minute), + ctypes.pointer(second), + ) + return datetime.datetime( + year.value, month.value, day.value, hour.value, minute.value, second.value + ) def getParamString(self, paramId): value = ctypes.create_unicode_buffer(self.DEFAULT_BUFF_SIZE) - size = self._getString(self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE) + size = self._getString( + self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE + ) if size > self.DEFAULT_BUFF_SIZE: value = ctypes.create_unicode_buffer(size) self._getString(self.interface, ctypes.c_int(paramId), value, size) @@ -2095,485 +2244,495 @@ def isParamAvailable(self, paramId): return value > 0 def applySingleSettings(self): - _method = self.METHOD(('libfptr_apply_single_settings', self.library)) + _method = self.METHOD(("libfptr_apply_single_settings", self.library)) return _method(self.interface) def open(self): - _method = self.METHOD(('libfptr_open', self.library)) + _method = self.METHOD(("libfptr_open", self.library)) return _method(self.interface) def close(self): - _method = self.METHOD(('libfptr_close', self.library)) + _method = self.METHOD(("libfptr_close", self.library)) return _method(self.interface) def resetParams(self): - _method = self.METHOD(('libfptr_reset_params', self.library)) + _method = self.METHOD(("libfptr_reset_params", self.library)) return _method(self.interface) def runCommand(self): - _method = self.METHOD(('libfptr_run_command', self.library)) + _method = self.METHOD(("libfptr_run_command", self.library)) return _method(self.interface) def beep(self): - _method = self.METHOD(('libfptr_beep', self.library)) + _method = self.METHOD(("libfptr_beep", self.library)) return _method(self.interface) def openDrawer(self): - _method = self.METHOD(('libfptr_open_drawer', self.library)) + _method = self.METHOD(("libfptr_open_drawer", self.library)) return _method(self.interface) def cut(self): - _method = self.METHOD(('libfptr_cut', self.library)) + _method = self.METHOD(("libfptr_cut", self.library)) return _method(self.interface) def devicePoweroff(self): - _method = self.METHOD(('libfptr_device_poweroff', self.library)) + _method = self.METHOD(("libfptr_device_poweroff", self.library)) return _method(self.interface) def deviceReboot(self): - _method = self.METHOD(('libfptr_device_reboot', self.library)) + _method = self.METHOD(("libfptr_device_reboot", self.library)) return _method(self.interface) def openShift(self): - _method = self.METHOD(('libfptr_open_shift', self.library)) + _method = self.METHOD(("libfptr_open_shift", self.library)) return _method(self.interface) def resetSummary(self): - _method = self.METHOD(('libfptr_reset_summary', self.library)) + _method = self.METHOD(("libfptr_reset_summary", self.library)) return _method(self.interface) def initDevice(self): - _method = self.METHOD(('libfptr_init_device', self.library)) + _method = self.METHOD(("libfptr_init_device", self.library)) return _method(self.interface) def queryData(self): - _method = self.METHOD(('libfptr_query_data', self.library)) + _method = self.METHOD(("libfptr_query_data", self.library)) return _method(self.interface) def cashIncome(self): - _method = self.METHOD(('libfptr_cash_income', self.library)) + _method = self.METHOD(("libfptr_cash_income", self.library)) return _method(self.interface) def cashOutcome(self): - _method = self.METHOD(('libfptr_cash_outcome', self.library)) + _method = self.METHOD(("libfptr_cash_outcome", self.library)) return _method(self.interface) def openReceipt(self): - _method = self.METHOD(('libfptr_open_receipt', self.library)) + _method = self.METHOD(("libfptr_open_receipt", self.library)) return _method(self.interface) def cancelReceipt(self): - _method = self.METHOD(('libfptr_cancel_receipt', self.library)) + _method = self.METHOD(("libfptr_cancel_receipt", self.library)) return _method(self.interface) def closeReceipt(self): - _method = self.METHOD(('libfptr_close_receipt', self.library)) + _method = self.METHOD(("libfptr_close_receipt", self.library)) return _method(self.interface) def checkDocumentClosed(self): - _method = self.METHOD(('libfptr_check_document_closed', self.library)) + _method = self.METHOD(("libfptr_check_document_closed", self.library)) return _method(self.interface) def receiptTotal(self): - _method = self.METHOD(('libfptr_receipt_total', self.library)) + _method = self.METHOD(("libfptr_receipt_total", self.library)) return _method(self.interface) def receiptTax(self): - _method = self.METHOD(('libfptr_receipt_tax', self.library)) + _method = self.METHOD(("libfptr_receipt_tax", self.library)) return _method(self.interface) def registration(self): - _method = self.METHOD(('libfptr_registration', self.library)) + _method = self.METHOD(("libfptr_registration", self.library)) return _method(self.interface) def payment(self): - _method = self.METHOD(('libfptr_payment', self.library)) + _method = self.METHOD(("libfptr_payment", self.library)) return _method(self.interface) def report(self): - _method = self.METHOD(('libfptr_report', self.library)) + _method = self.METHOD(("libfptr_report", self.library)) return _method(self.interface) def printText(self): - _method = self.METHOD(('libfptr_print_text', self.library)) + _method = self.METHOD(("libfptr_print_text", self.library)) return _method(self.interface) def printCliche(self): - _method = self.METHOD(('libfptr_print_cliche', self.library)) + _method = self.METHOD(("libfptr_print_cliche", self.library)) return _method(self.interface) def beginNonfiscalDocument(self): - _method = self.METHOD(('libfptr_begin_nonfiscal_document', self.library)) + _method = self.METHOD(("libfptr_begin_nonfiscal_document", self.library)) return _method(self.interface) def endNonfiscalDocument(self): - _method = self.METHOD(('libfptr_end_nonfiscal_document', self.library)) + _method = self.METHOD(("libfptr_end_nonfiscal_document", self.library)) return _method(self.interface) def printBarcode(self): - _method = self.METHOD(('libfptr_print_barcode', self.library)) + _method = self.METHOD(("libfptr_print_barcode", self.library)) return _method(self.interface) def printPicture(self): - _method = self.METHOD(('libfptr_print_picture', self.library)) + _method = self.METHOD(("libfptr_print_picture", self.library)) return _method(self.interface) def printPictureByNumber(self): - _method = self.METHOD(('libfptr_print_picture_by_number', self.library)) + _method = self.METHOD(("libfptr_print_picture_by_number", self.library)) return _method(self.interface) def uploadPictureFromFile(self): - _method = self.METHOD(('libfptr_upload_picture_from_file', self.library)) + _method = self.METHOD(("libfptr_upload_picture_from_file", self.library)) return _method(self.interface) def clearPictures(self): - _method = self.METHOD(('libfptr_clear_pictures', self.library)) + _method = self.METHOD(("libfptr_clear_pictures", self.library)) return _method(self.interface) def writeDeviceSettingRaw(self): - _method = self.METHOD(('libfptr_write_device_setting_raw', self.library)) + _method = self.METHOD(("libfptr_write_device_setting_raw", self.library)) return _method(self.interface) def readDeviceSettingRaw(self): - _method = self.METHOD(('libfptr_read_device_setting_raw', self.library)) + _method = self.METHOD(("libfptr_read_device_setting_raw", self.library)) return _method(self.interface) def commitSettings(self): - _method = self.METHOD(('libfptr_commit_settings', self.library)) + _method = self.METHOD(("libfptr_commit_settings", self.library)) return _method(self.interface) def initSettings(self): - _method = self.METHOD(('libfptr_init_settings', self.library)) + _method = self.METHOD(("libfptr_init_settings", self.library)) return _method(self.interface) def resetSettings(self): - _method = self.METHOD(('libfptr_reset_settings', self.library)) + _method = self.METHOD(("libfptr_reset_settings", self.library)) return _method(self.interface) def writeDateTime(self): - _method = self.METHOD(('libfptr_write_date_time', self.library)) + _method = self.METHOD(("libfptr_write_date_time", self.library)) return _method(self.interface) def writeLicense(self): - _method = self.METHOD(('libfptr_write_license', self.library)) + _method = self.METHOD(("libfptr_write_license", self.library)) return _method(self.interface) def fnOperation(self): - _method = self.METHOD(('libfptr_fn_operation', self.library)) + _method = self.METHOD(("libfptr_fn_operation", self.library)) return _method(self.interface) def fnQueryData(self): - _method = self.METHOD(('libfptr_fn_query_data', self.library)) + _method = self.METHOD(("libfptr_fn_query_data", self.library)) return _method(self.interface) def fnWriteAttributes(self): - _method = self.METHOD(('libfptr_fn_write_attributes', self.library)) + _method = self.METHOD(("libfptr_fn_write_attributes", self.library)) return _method(self.interface) def externalDevicePowerOn(self): - _method = self.METHOD(('libfptr_external_device_power_on', self.library)) + _method = self.METHOD(("libfptr_external_device_power_on", self.library)) return _method(self.interface) def externalDevicePowerOff(self): - _method = self.METHOD(('libfptr_external_device_power_off', self.library)) + _method = self.METHOD(("libfptr_external_device_power_off", self.library)) return _method(self.interface) def externalDeviceWriteData(self): - _method = self.METHOD(('libfptr_external_device_write_data', self.library)) + _method = self.METHOD(("libfptr_external_device_write_data", self.library)) return _method(self.interface) def externalDeviceReadData(self): - _method = self.METHOD(('libfptr_external_device_read_data', self.library)) + _method = self.METHOD(("libfptr_external_device_read_data", self.library)) return _method(self.interface) def operatorLogin(self): - _method = self.METHOD(('libfptr_operator_login', self.library)) + _method = self.METHOD(("libfptr_operator_login", self.library)) return _method(self.interface) def processJson(self): - _method = self.METHOD(('libfptr_process_json', self.library)) + _method = self.METHOD(("libfptr_process_json", self.library)) return _method(self.interface) def readDeviceSetting(self): - _method = self.METHOD(('libfptr_read_device_setting', self.library)) + _method = self.METHOD(("libfptr_read_device_setting", self.library)) return _method(self.interface) def writeDeviceSetting(self): - _method = self.METHOD(('libfptr_write_device_setting', self.library)) + _method = self.METHOD(("libfptr_write_device_setting", self.library)) return _method(self.interface) def beginReadRecords(self): - _method = self.METHOD(('libfptr_begin_read_records', self.library)) + _method = self.METHOD(("libfptr_begin_read_records", self.library)) return _method(self.interface) def readNextRecord(self): - _method = self.METHOD(('libfptr_read_next_record', self.library)) + _method = self.METHOD(("libfptr_read_next_record", self.library)) return _method(self.interface) def endReadRecords(self): - _method = self.METHOD(('libfptr_end_read_records', self.library)) + _method = self.METHOD(("libfptr_end_read_records", self.library)) return _method(self.interface) def userMemoryOperation(self): - _method = self.METHOD(('libfptr_user_memory_operation', self.library)) + _method = self.METHOD(("libfptr_user_memory_operation", self.library)) return _method(self.interface) def continuePrint(self): - _method = self.METHOD(('libfptr_continue_print', self.library)) + _method = self.METHOD(("libfptr_continue_print", self.library)) return _method(self.interface) def initMgm(self): - _method = self.METHOD(('libfptr_init_mgm', self.library)) + _method = self.METHOD(("libfptr_init_mgm", self.library)) return _method(self.interface) def utilFormTlv(self): - _method = self.METHOD(('libfptr_util_form_tlv', self.library)) + _method = self.METHOD(("libfptr_util_form_tlv", self.library)) return _method(self.interface) def utilFormNomenclature(self): - _method = self.METHOD(('libfptr_util_form_nomenclature', self.library)) + _method = self.METHOD(("libfptr_util_form_nomenclature", self.library)) return _method(self.interface) def utilMapping(self): - _method = self.METHOD(('libfptr_util_mapping', self.library)) + _method = self.METHOD(("libfptr_util_mapping", self.library)) return _method(self.interface) def readModelFlags(self): - _method = self.METHOD(('libfptr_read_model_flags', self.library)) + _method = self.METHOD(("libfptr_read_model_flags", self.library)) return _method(self.interface) def lineFeed(self): - _method = self.METHOD(('libfptr_line_feed', self.library)) + _method = self.METHOD(("libfptr_line_feed", self.library)) return _method(self.interface) def flashFirmware(self): - _method = self.METHOD(('libfptr_flash_firmware', self.library)) + _method = self.METHOD(("libfptr_flash_firmware", self.library)) return _method(self.interface) def softLockInit(self): - _method = self.METHOD(('libfptr_soft_lock_init', self.library)) + _method = self.METHOD(("libfptr_soft_lock_init", self.library)) return _method(self.interface) def softLockQuerySessionCode(self): - _method = self.METHOD(('libfptr_soft_lock_query_session_code', self.library)) + _method = self.METHOD(("libfptr_soft_lock_query_session_code", self.library)) return _method(self.interface) def softLockValidate(self): - _method = self.METHOD(('libfptr_soft_lock_validate', self.library)) + _method = self.METHOD(("libfptr_soft_lock_validate", self.library)) return _method(self.interface) def utilCalcTax(self): - _method = self.METHOD(('libfptr_util_calc_tax', self.library)) + _method = self.METHOD(("libfptr_util_calc_tax", self.library)) return _method(self.interface) def downloadPicture(self): - _method = self.METHOD(('libfptr_download_picture', self.library)) + _method = self.METHOD(("libfptr_download_picture", self.library)) return _method(self.interface) def bluetoothRemovePairedDevices(self): - _method = self.METHOD(('libfptr_bluetooth_remove_paired_devices', self.library)) + _method = self.METHOD(("libfptr_bluetooth_remove_paired_devices", self.library)) return _method(self.interface) def utilTagInfo(self): - _method = self.METHOD(('libfptr_util_tag_info', self.library)) + _method = self.METHOD(("libfptr_util_tag_info", self.library)) return _method(self.interface) def utilContainerVersions(self): - _method = self.METHOD(('libfptr_util_container_versions', self.library)) + _method = self.METHOD(("libfptr_util_container_versions", self.library)) return _method(self.interface) def activateLicenses(self): - _method = self.METHOD(('libfptr_activate_licenses', self.library)) + _method = self.METHOD(("libfptr_activate_licenses", self.library)) return _method(self.interface) def removeLicenses(self): - _method = self.METHOD(('libfptr_remove_licenses', self.library)) + _method = self.METHOD(("libfptr_remove_licenses", self.library)) return _method(self.interface) def enterKeys(self): - _method = self.METHOD(('libfptr_enter_keys', self.library)) + _method = self.METHOD(("libfptr_enter_keys", self.library)) return _method(self.interface) def validateKeys(self): - _method = self.METHOD(('libfptr_validate_keys', self.library)) + _method = self.METHOD(("libfptr_validate_keys", self.library)) return _method(self.interface) def enterSerialNumber(self): - _method = self.METHOD(('libfptr_enter_serial_number', self.library)) + _method = self.METHOD(("libfptr_enter_serial_number", self.library)) return _method(self.interface) def getSerialNumberRequest(self): - _method = self.METHOD(('libfptr_get_serial_number_request', self.library)) + _method = self.METHOD(("libfptr_get_serial_number_request", self.library)) return _method(self.interface) def uploadPixelBuffer(self): - _method = self.METHOD(('libfptr_upload_pixel_buffer', self.library)) + _method = self.METHOD(("libfptr_upload_pixel_buffer", self.library)) return _method(self.interface) def downloadPixelBuffer(self): - _method = self.METHOD(('libfptr_download_pixel_buffer', self.library)) + _method = self.METHOD(("libfptr_download_pixel_buffer", self.library)) return _method(self.interface) def printPixelBuffer(self): - _method = self.METHOD(('libfptr_print_pixel_buffer', self.library)) + _method = self.METHOD(("libfptr_print_pixel_buffer", self.library)) return _method(self.interface) def utilConvertTagValue(self): - _method = self.METHOD(('libfptr_util_convert_tag_value', self.library)) + _method = self.METHOD(("libfptr_util_convert_tag_value", self.library)) return _method(self.interface) def parseMarkingCode(self): - _method = self.METHOD(('libfptr_parse_marking_code', self.library)) + _method = self.METHOD(("libfptr_parse_marking_code", self.library)) return _method(self.interface) def callScript(self): - _method = self.METHOD(('libfptr_call_script', self.library)) + _method = self.METHOD(("libfptr_call_script", self.library)) return _method(self.interface) def setHeaderLines(self): - _method = self.METHOD(('libfptr_set_header_lines', self.library)) + _method = self.METHOD(("libfptr_set_header_lines", self.library)) return _method(self.interface) def setFooterLines(self): - _method = self.METHOD(('libfptr_set_footer_lines', self.library)) + _method = self.METHOD(("libfptr_set_footer_lines", self.library)) return _method(self.interface) def uploadPictureCliche(self): - _method = self.METHOD(('libfptr_upload_picture_cliche', self.library)) + _method = self.METHOD(("libfptr_upload_picture_cliche", self.library)) return _method(self.interface) def uploadPictureMemory(self): - _method = self.METHOD(('libfptr_upload_picture_memory', self.library)) + _method = self.METHOD(("libfptr_upload_picture_memory", self.library)) return _method(self.interface) def uploadPixelBufferCliche(self): - _method = self.METHOD(('libfptr_upload_pixel_buffer_cliche', self.library)) + _method = self.METHOD(("libfptr_upload_pixel_buffer_cliche", self.library)) return _method(self.interface) def uploadPixelBufferMemory(self): - _method = self.METHOD(('libfptr_upload_pixel_buffer_memory', self.library)) + _method = self.METHOD(("libfptr_upload_pixel_buffer_memory", self.library)) return _method(self.interface) def execDriverScript(self): - _method = self.METHOD(('libfptr_exec_driver_script', self.library)) + _method = self.METHOD(("libfptr_exec_driver_script", self.library)) return _method(self.interface) def uploadDriverScript(self): - _method = self.METHOD(('libfptr_upload_driver_script', self.library)) + _method = self.METHOD(("libfptr_upload_driver_script", self.library)) return _method(self.interface) def execDriverScriptById(self): - _method = self.METHOD(('libfptr_exec_driver_script_by_id', self.library)) + _method = self.METHOD(("libfptr_exec_driver_script_by_id", self.library)) return _method(self.interface) def writeUniversalCountersSettings(self): - _method = self.METHOD(('libfptr_write_universal_counters_settings', self.library)) + _method = self.METHOD( + ("libfptr_write_universal_counters_settings", self.library) + ) return _method(self.interface) def readUniversalCountersSettings(self): - _method = self.METHOD(('libfptr_read_universal_counters_settings', self.library)) + _method = self.METHOD( + ("libfptr_read_universal_counters_settings", self.library) + ) return _method(self.interface) def queryUniversalCountersState(self): - _method = self.METHOD(('libfptr_query_universal_counters_state', self.library)) + _method = self.METHOD(("libfptr_query_universal_counters_state", self.library)) return _method(self.interface) def resetUniversalCounters(self): - _method = self.METHOD(('libfptr_reset_universal_counters', self.library)) + _method = self.METHOD(("libfptr_reset_universal_counters", self.library)) return _method(self.interface) def cacheUniversalCounters(self): - _method = self.METHOD(('libfptr_cache_universal_counters', self.library)) + _method = self.METHOD(("libfptr_cache_universal_counters", self.library)) return _method(self.interface) def readUniversalCounterSum(self): - _method = self.METHOD(('libfptr_read_universal_counter_sum', self.library)) + _method = self.METHOD(("libfptr_read_universal_counter_sum", self.library)) return _method(self.interface) def readUniversalCounterQuantity(self): - _method = self.METHOD(('libfptr_read_universal_counter_quantity', self.library)) + _method = self.METHOD(("libfptr_read_universal_counter_quantity", self.library)) return _method(self.interface) def clearUniversalCountersCache(self): - _method = self.METHOD(('libfptr_clear_universal_counters_cache', self.library)) + _method = self.METHOD(("libfptr_clear_universal_counters_cache", self.library)) return _method(self.interface) def disableOfdChannel(self): - _method = self.METHOD(('libfptr_disable_ofd_channel', self.library)) + _method = self.METHOD(("libfptr_disable_ofd_channel", self.library)) return _method(self.interface) def enableOfdChannel(self): - _method = self.METHOD(('libfptr_enable_ofd_channel', self.library)) + _method = self.METHOD(("libfptr_enable_ofd_channel", self.library)) return _method(self.interface) def validateJson(self): - _method = self.METHOD(('libfptr_validate_json', self.library)) + _method = self.METHOD(("libfptr_validate_json", self.library)) return _method(self.interface) def reflectionCall(self): - _method = self.METHOD(('libfptr_reflection_call', self.library)) + _method = self.METHOD(("libfptr_reflection_call", self.library)) return _method(self.interface) def getRemoteServerInfo(self): - _method = self.METHOD(('libfptr_get_remote_server_info', self.library)) + _method = self.METHOD(("libfptr_get_remote_server_info", self.library)) return _method(self.interface) def beginMarkingCodeValidation(self): - _method = self.METHOD(('libfptr_begin_marking_code_validation', self.library)) + _method = self.METHOD(("libfptr_begin_marking_code_validation", self.library)) return _method(self.interface) def cancelMarkingCodeValidation(self): - _method = self.METHOD(('libfptr_cancel_marking_code_validation', self.library)) + _method = self.METHOD(("libfptr_cancel_marking_code_validation", self.library)) return _method(self.interface) def getMarkingCodeValidationStatus(self): - _method = self.METHOD(('libfptr_get_marking_code_validation_status', self.library)) + _method = self.METHOD( + ("libfptr_get_marking_code_validation_status", self.library) + ) return _method(self.interface) def acceptMarkingCode(self): - _method = self.METHOD(('libfptr_accept_marking_code', self.library)) + _method = self.METHOD(("libfptr_accept_marking_code", self.library)) return _method(self.interface) def declineMarkingCode(self): - _method = self.METHOD(('libfptr_decline_marking_code', self.library)) + _method = self.METHOD(("libfptr_decline_marking_code", self.library)) return _method(self.interface) def updateFnmKeys(self): - _method = self.METHOD(('libfptr_update_fnm_keys', self.library)) + _method = self.METHOD(("libfptr_update_fnm_keys", self.library)) return _method(self.interface) def writeSalesNotice(self): - _method = self.METHOD(('libfptr_write_sales_notice', self.library)) + _method = self.METHOD(("libfptr_write_sales_notice", self.library)) return _method(self.interface) def checkMarkingCodeValidationsReady(self): - _method = self.METHOD(('libfptr_check_marking_code_validations_ready', self.library)) + _method = self.METHOD( + ("libfptr_check_marking_code_validations_ready", self.library) + ) return _method(self.interface) def clearMarkingCodeValidationResult(self): - _method = self.METHOD(('libfptr_clear_marking_code_validation_result', self.library)) + _method = self.METHOD( + ("libfptr_clear_marking_code_validation_result", self.library) + ) return _method(self.interface) def pingMarkingServer(self): - _method = self.METHOD(('libfptr_ping_marking_server', self.library)) + _method = self.METHOD(("libfptr_ping_marking_server", self.library)) return _method(self.interface) def getMarkingServerStatus(self): - _method = self.METHOD(('libfptr_get_marking_server_status', self.library)) + _method = self.METHOD(("libfptr_get_marking_server_status", self.library)) return _method(self.interface) def isDriverLocked(self): - _method = self.METHOD(('libfptr_is_driver_locked', self.library)) + _method = self.METHOD(("libfptr_is_driver_locked", self.library)) return _method(self.interface) def getLastDocumentJournal(self): - _method = self.METHOD(('libfptr_get_last_document_journal', self.library)) + _method = self.METHOD(("libfptr_get_last_document_journal", self.library)) return _method(self.interface) def findDocumentInJournal(self): - _method = self.METHOD(('libfptr_find_document_in_journal', self.library)) + _method = self.METHOD(("libfptr_find_document_in_journal", self.library)) return _method(self.interface) def runFnCommand(self): - _method = self.METHOD(('libfptr_run_fn_command', self.library)) + _method = self.METHOD(("libfptr_run_fn_command", self.library)) return _method(self.interface) diff --git a/modules/otchet.py b/modules/otchet.py index 965f03a..ba46312 100644 --- a/modules/otchet.py +++ b/modules/otchet.py @@ -1,9 +1,9 @@ from datetime import datetime -from typing import Any, Dict, List, Tuple, Union +from typing import Any from reportlab.lib import colors from reportlab.lib.pagesizes import A4, landscape, letter -from reportlab.lib.units import mm, inch +from reportlab.lib.units import inch, mm from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas @@ -17,7 +17,7 @@ config = Config() -def get_ticket_type(age:str) -> str: +def get_ticket_type(age: str) -> str: """ Определяет тип билета на основе возраста клиента. @@ -45,7 +45,8 @@ def get_ticket_type(age:str) -> str: else: return "взрослый" -def generate_saved_tickets(values: List[tuple]) -> None: + +def generate_saved_tickets(values: list[tuple]) -> None: """ Генерирует PDF-файл с билетами для клиентов на основе переданных данных. @@ -72,32 +73,79 @@ def generate_saved_tickets(values: List[tuple]) -> None: pdfmetrics.registerFont(TTFont("DejaVuSerif", "files/DejaVuSerif.ttf")) c = canvas.Canvas(path, pagesize=(landscape(letter))) for client in values: - (last_name, name, ticket_flag, price, category, client_id, age, hours, talents, date_time) = client + ( + last_name, + name, + ticket_flag, + price, + category, + client_id, + age, + hours, + talents, + date_time, + ) = client ticket_type = get_ticket_type(age) if ticket_type == "бесплатный" or category == "н": continue # Пропускаем бесплатных и "не идущих" c.setFont("DejaVuSerif", 12) # x - расстояние мм от левого края страницы # y - расстояние мм от нижнего края страницы - c.drawString(coordinates["name"]["x"] * mm,coordinates["name"]["y"] * mm, name) - c.drawString(coordinates["surname"]["x"] * mm, coordinates["surname"]["y"] * mm, last_name) - c.drawString(coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age)) + c.drawString(coordinates["name"]["x"] * mm, coordinates["name"]["y"] * mm, name) + c.drawString( + coordinates["surname"]["x"] * mm, + coordinates["surname"]["y"] * mm, + last_name, + ) + c.drawString( + coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age) + ) duration_text = f"{hours} ч. пребывания" if hours == 3: duration_text += "-весь день" - c.drawString(coordinates["duration"]["x"] * mm, coordinates["duration"]["y"] * mm, duration_text) + c.drawString( + coordinates["duration"]["x"] * mm, + coordinates["duration"]["y"] * mm, + duration_text, + ) if isinstance(date_time, str): date_time = datetime.strptime(date_time, "%Y-%m-%d") - c.drawString(coordinates["date"]["x"] * mm, coordinates["date"]["y"] * mm, date_time.strftime('%d.%m.%Y')) - c.drawString(coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость") - c.drawString(coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД") - c.drawString(coordinates["place"]["x"] * mm, coordinates["place"]["y"] * mm, "МАСТЕРСЛАВЛЬ") - c.drawString(coordinates["price"]["x"] * mm, coordinates["price"]["y"] * mm, f"{price} руб.") - c.drawString(coordinates["ticket_type"]["x"] * mm, coordinates["ticket_type"]["y"] * mm, ticket_type) - c.drawString(coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category) + c.drawString( + coordinates["date"]["x"] * mm, + coordinates["date"]["y"] * mm, + date_time.strftime("%d.%m.%Y"), + ) + c.drawString( + coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость" + ) + c.drawString( + coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД" + ) + c.drawString( + coordinates["place"]["x"] * mm, + coordinates["place"]["y"] * mm, + "МАСТЕРСЛАВЛЬ", + ) + c.drawString( + coordinates["price"]["x"] * mm, + coordinates["price"]["y"] * mm, + f"{price} руб.", + ) + c.drawString( + coordinates["ticket_type"]["x"] * mm, + coordinates["ticket_type"]["y"] * mm, + ticket_type, + ) + c.drawString( + coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category + ) c.setFont("DejaVuSerif", 24) talents_str = "0" if ticket_type == "взрослый" else str(talents) - c.drawString(coordinates["talents"]["x"] * mm, coordinates["talents"]["y"] * mm, talents_str) + c.drawString( + coordinates["talents"]["x"] * mm, + coordinates["talents"]["y"] * mm, + talents_str, + ) c.drawImage( img_file, coordinates["qr_code"]["x"], @@ -134,13 +182,30 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: """ logger.info("Запуск функции generate_ticket_report_table") type_ticket = [ - "Взрослый, 1 ч.", "Взрослый, 2 ч.", "Взрослый, 3 ч.", - "Детский, 1 ч.", "Детский, 2 ч.", "Детский, 3 ч.", - "Многодетный взрослый, 1 ч.", "Многодетный взрослый, 2 ч.", "Многодетный взрослый, 3 ч.", - "Многодетный детский, 1 ч.", "Многодетный детский, 2 ч.", "Многодетный детский, 3 ч.", - "Инвалид, 3 ч.", "Сопровождающий, 3 ч.", + "Взрослый, 1 ч.", + "Взрослый, 2 ч.", + "Взрослый, 3 ч.", + "Детский, 1 ч.", + "Детский, 2 ч.", + "Детский, 3 ч.", + "Многодетный взрослый, 1 ч.", + "Многодетный взрослый, 2 ч.", + "Многодетный взрослый, 3 ч.", + "Многодетный детский, 1 ч.", + "Многодетный детский, 2 ч.", + "Многодетный детский, 3 ч.", + "Инвалид, 3 ч.", + "Сопровождающий, 3 ч.", + ] + data = [ + [ + "№\n п/п", + "Тип\nбилета", + "Цена,\n руб.", + "Количество,\n шт.", + "Стоимость,\n руб.", + ] ] - data = [["№\n п/п", "Тип\nбилета", "Цена,\n руб.", "Количество,\n шт.", "Стоимость,\n руб."]] # Счётчики для агрегатов total = { "adult": {"count": 0, "sum": 0}, @@ -182,23 +247,57 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: # Агрегированные строки data += [ - ["", "Всего взрослых билетов", "", total["adult"]["count"], total["adult"]["sum"]], - ["", "Всего детских билетов", "", total["child"]["count"], total["child"]["sum"]], - ["", "Всего многодетных взрослых билетов", "", total["many_adult"]["count"], total["many_adult"]["sum"]], - ["", "Всего многодетных детских билетов", "", total["many_child"]["count"], total["many_child"]["sum"]], + [ + "", + "Всего взрослых билетов", + "", + total["adult"]["count"], + total["adult"]["sum"], + ], + [ + "", + "Всего детских билетов", + "", + total["child"]["count"], + total["child"]["sum"], + ], + [ + "", + "Всего многодетных взрослых билетов", + "", + total["many_adult"]["count"], + total["many_adult"]["sum"], + ], + [ + "", + "Всего многодетных детских билетов", + "", + total["many_child"]["count"], + total["many_child"]["sum"], + ], ["", "Инвалид, 3 ч.", "", total["disabled"]["count"], "0"], ["", "Сопровождающий, 3 ч.", "", total["maintainer"]["count"], "0"], - ["", "Итого билетов", "", - total["adult"]["count"] + total["child"]["count"] + - total["many_adult"]["count"] + total["many_child"]["count"] + - total["disabled"]["count"] + total["maintainer"]["count"], - total["adult"]["sum"] + total["child"]["sum"] + - total["many_adult"]["sum"] + total["many_child"]["sum"]], + [ + "", + "Итого билетов", + "", + total["adult"]["count"] + + total["child"]["count"] + + total["many_adult"]["count"] + + total["many_child"]["count"] + + total["disabled"]["count"] + + total["maintainer"]["count"], + total["adult"]["sum"] + + total["child"]["sum"] + + total["many_adult"]["sum"] + + total["many_child"]["sum"], + ], ] return data -def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: + +def otchet_administratora(date_1: str, date_2: str, values: dict) -> None: """ Функция формирует отчет администратора в формате PDF. @@ -253,15 +352,22 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: table_width = sum(custom_widths) if table_width > (page_width - left_margin - right_margin): # Корректировка ширины второго столбца - remaining_width = page_width - left_margin - right_margin - (sum(custom_widths) - custom_widths[1]) + remaining_width = ( + page_width + - left_margin + - right_margin + - (sum(custom_widths) - custom_widths[1]) + ) custom_widths[1] = remaining_width t = Table(data, colWidths=custom_widths, rowHeights=[0.3 * inch] * len(data)) t.setStyle( - TableStyle([ - ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), - ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), - ("BOX", (0, 0), (-1, -1), 0.25, colors.black), - ]) + TableStyle( + [ + ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), + ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), + ("BOX", (0, 0), (-1, -1), 0.25, colors.black), + ] + ) ) t.wrapOn(c, left_margin, 180 * mm) t.drawOn(c, left_margin, 60 * mm) @@ -281,6 +387,7 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: c.showPage() c.save() + def safe_int(value: Any) -> int: """ Преобразует значение в целое число, если это возможно. Если значение None, строка 'None', @@ -295,14 +402,22 @@ def safe_int(value: Any) -> int: if isinstance(value, (list, dict, set, tuple)): # Проверка на неподобающие типы return 0 try: - if value not in [None, 'None']: - return int(float(value)) # Преобразование строки с плавающей точкой в целое число + if value not in [None, "None"]: + return int( + float(value) + ) # Преобразование строки с плавающей точкой в целое число else: return 0 except (ValueError, TypeError): # Обработка ошибок для некорректных значений return 0 -def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m-%d %H:%M:%S", output_format: str = "%d-%m-%Y") -> Tuple[str, str]: + +def format_date_range( + date1_str: str, + date2_str: str, + input_format: str = "%Y-%m-%d %H:%M:%S", + output_format: str = "%d-%m-%Y", +) -> tuple[str, str]: """ Преобразует строки с датами из одного формата в другой и возвращает кортеж из двух отформатированных строк. @@ -321,7 +436,8 @@ def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m dt2 = date_2.strftime(output_format) return dt1, dt2 -def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: + +def otchet_kassira(val: list[int], date1: str, date2: str, kassir: Any) -> None: """ Формирует PDF-отчет кассира за указанный период с разбивкой по типам оплат и возвратов. @@ -345,7 +461,9 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: logger.info("Запуск функции otchet_kassira") # Проверка на пустой список или недостаточное количество элементов if not val or len(val) < 4: - logger.error("Некорректные данные для отчета: данные не переданы или их недостаточно.") + logger.error( + "Некорректные данные для отчета: данные не переданы или их недостаточно." + ) # Завершаем функцию, если данных недостаточно или они пустые return path = "./otchet.pdf" @@ -423,10 +541,21 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: c.showPage() c.save() + def process_sales_and_returns( - sales: List[Tuple[str, int, Union[int, float]]], - sales_return: List[Tuple[str, int, Union[int, float], int]] -) -> List[Tuple[Union[str, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float]]]: + sales: list[tuple[str, int, int | float]], + sales_return: list[tuple[str, int, int | float, int]], +) -> list[ + tuple[ + str | None, + int | float | None, + int | float | None, + int | float | None, + int | float | None, + int | float | None, + int | float, + ] +]: """ Обрабатывает продажи и возвраты, агрегируя информацию по каждому кассовому аппарату. Функция вычисляет суммы по картам и наличным, а также учитывает возвраты по каждому типу: @@ -462,24 +591,29 @@ def process_sales_and_returns( logger.info("Запуск функции process_sales_and_returns") pcs = { name: { - "card": 0, "cash": 0, - "return": 0, "return_again": 0, + "card": 0, + "cash": 0, + "return": 0, + "return_again": 0, "return_partial": 0, - "return_card": 0, "return_cash": 0, + "return_card": 0, + "return_cash": 0, } for name in system.pcs } type_rm = [1, 2, 3] # 1 - карта, 2 - наличные, 3 - offline - return_fields: dict[int, str] = {2: "return", 4: "return_again", 6: "return_partial"} + return_fields: dict[int, str] = { + 2: "return", + 4: "return_again", + 6: "return_partial", + } for sale in sales: pc_name = sale[0] if pc_name not in pcs: continue - if sale[1] == type_rm[0] or sale[1] == type_rm[2]: - pcs[pc_name]["card"] += sale[2] - elif sale[1] == type_rm[2]: + if sale[1] == type_rm[0] or sale[1] == type_rm[2] or sale[1] == type_rm[2]: pcs[pc_name]["card"] += sale[2] else: pcs[pc_name]["cash"] += sale[2] @@ -501,33 +635,40 @@ def process_sales_and_returns( data = [] for name in system.pcs: stats = pcs[name] - data.append(( - name, - stats["card"], - stats["cash"], - None, - stats["return_card"], - stats["return_cash"], - stats["return_card"] + stats["return_cash"] - )) + data.append( + ( + name, + stats["card"], + stats["cash"], + None, + stats["return_card"], + stats["return_cash"], + stats["return_card"] + stats["return_cash"], + ) + ) total_card = sum(pc["card"] for pc in pcs.values()) total_cash = sum(pc["cash"] for pc in pcs.values()) total_return = sum(pc["return_card"] + pc["return_cash"] for pc in pcs.values()) - data.append(( - "Итого", - total_card, - total_cash, - total_card + total_cash, - None, - None, - total_return - )) + data.append( + ( + "Итого", + total_card, + total_cash, + total_card + total_cash, + None, + None, + total_return, + ) + ) system.sales_data_summary = data return data -def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int]]) -> List[Tuple[str, Dict[str, int]]]: + +def process_ticket_stats( + tickets: list[tuple[int, int, str, object, object, int]], +) -> list[tuple[str, dict[str, int]]]: """ Обрабатывает информацию о билетах, суммируя количество и стоимость проданных билетов по категориям и времени пребывания. Возвращает данные, которые могут быть использованы для отображения статистики @@ -555,10 +696,18 @@ def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int] ) TICKET_TYPE_MAP = { - (0, 1): "Взрослый, 1 ч.", (0, 2): "Взрослый, 2 ч.", (0, 3): "Взрослый, 3 ч.", - (1, 1): "Детский, 1 ч.", (1, 2): "Детский, 2 ч.", (1, 3): "Детский, 3 ч.", - (2, 1): "Многодетный взрослый, 1 ч.", (2, 2): "Многодетный взрослый, 2 ч.", (2, 3): "Многодетный взрослый, 3 ч.", - (3, 1): "Многодетный детский, 1 ч.", (3, 2): "Многодетный детский, 2 ч.", (3, 3): "Многодетный детский, 3 ч.", + (0, 1): "Взрослый, 1 ч.", + (0, 2): "Взрослый, 2 ч.", + (0, 3): "Взрослый, 3 ч.", + (1, 1): "Детский, 1 ч.", + (1, 2): "Детский, 2 ч.", + (1, 3): "Детский, 3 ч.", + (2, 1): "Многодетный взрослый, 1 ч.", + (2, 2): "Многодетный взрослый, 2 ч.", + (2, 3): "Многодетный взрослый, 3 ч.", + (3, 1): "Многодетный детский, 1 ч.", + (3, 2): "Многодетный детский, 2 ч.", + (3, 3): "Многодетный детский, 3 ч.", (4, 3): "Инвалид, 3 ч.", (5, 3): "Сопровождающий, 3 ч.", } diff --git a/modules/payment_equipment.py b/modules/payment_equipment.py index 9b2005a..49695eb 100644 --- a/modules/payment_equipment.py +++ b/modules/payment_equipment.py @@ -3,7 +3,6 @@ import time from contextlib import contextmanager from datetime import datetime, timedelta -from typing import Optional from modules import windows from modules.config import Config @@ -14,10 +13,12 @@ # Получаем параметр available try: - kkt_available = config.get("available") == 'on' + kkt_available = config.get("available") == "on" except (KeyError, ValueError): kkt_available = False - logger.info("Параметр 'available' не найден в конфигурации, ККТ считается отключенным") + logger.info( + "Параметр 'available' не найден в конфигурации, ККТ считается отключенным" + ) # Инициализация драйвера ККТ try: @@ -27,7 +28,7 @@ fptr = None logger.info("ККТ отключен в конфигурации (available ≠ on)") except Exception as e: - logger.warning(f"Не установлен драйвер ККТ: {str(e)}") + logger.warning(f"Не установлен драйвер ККТ: {e!s}") kkt_available = False fptr = None @@ -111,7 +112,6 @@ 4144, 4145, 4146, - 4146, 4202, 4203, 4208, @@ -164,7 +164,7 @@ def fptr_connection(device): device.open() connected = True - if not hasattr(device, 'isOpened') or not device.isOpened(): + if not hasattr(device, "isOpened") or not device.isOpened(): logger.warning("Не удалось открыть соединение с ККТ") yield None return @@ -172,7 +172,7 @@ def fptr_connection(device): yield device except Exception as e: - logger.error(f"Ошибка подключения к ККТ: {str(e)}") + logger.error(f"Ошибка подключения к ККТ: {e!s}") yield None finally: @@ -211,18 +211,24 @@ def run_terminal_command(command_params: str, timeout: int = 300): logger.info(f"Запуск команды: {pinpad_run}") try: process = subprocess.Popen( - pinpad_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - shell=True # Важно для строки команды + pinpad_run, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, # Важно для строки команды ) try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: - logger.warning(f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически.") + logger.warning( + f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически." + ) process.terminate() try: stdout, stderr = process.communicate(timeout=5) except subprocess.TimeoutExpired: - logger.warning("Процесс не завершился после terminate(). Используется kill().") + logger.warning( + "Процесс не завершился после terminate(). Используется kill()." + ) process.kill() stdout, stderr = process.communicate() @@ -296,6 +302,7 @@ def _safe_handle_error(returncode, title, message, error_callback): except Exception as e: logger.exception(f"Ошибка при вызове handle_error для кода {returncode}: {e}") + @logger_wraps() def handle_error(code, title, message, error_callback=None): """Универсальная обработка ошибок терминала. @@ -343,15 +350,15 @@ def process_terminal_error(returncode, error_callback=None): error_handlers = { TERMINAL_USER_CANCEL_CODE: ( "Оплата отменена пользователем", - f"Оплата отменена пользователем. Код возврата: {returncode}." + f"Оплата отменена пользователем. Код возврата: {returncode}.", ), TERMINAL_USER_TIMEOUT: ( "Слишком долгий ввод ПИН-кода", - f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}." + f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}.", ), TERMINAL_INVALID_CURRENCY_CODE: ( "Указан неверный код валюты", - f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}." + f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}.", ), TERMINAL_NO_ADDRESS_TO_CONTACT: ( "Терминал потерял связь с банком", @@ -359,7 +366,7 @@ def process_terminal_error(returncode, error_callback=None): ), TERMINAL_OPERATION_CANCELED: ( "Операция отменена", - f"Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", + "Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", ), TERMINAL_QR_ERROR: ( "Ошибка операции по QR-коду", @@ -377,8 +384,8 @@ def process_terminal_error(returncode, error_callback=None): _safe_handle_error( returncode, "Карта клиента заблокирована", - f"Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", - error_callback + "Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", + error_callback, ) return 0 if returncode in TERMINAL_CARD_LIMIT: @@ -386,7 +393,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Превышен лимит операций", "Превышен лимит операций. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_BIOMETRIC_ERROR: @@ -394,7 +401,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Ошибка в работе с биометрическими данными", f"Ошибка в работе с биометрическими данными. Обратитесь в банк для выяснения причины. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_ERROR_PIN_CODE: @@ -402,7 +409,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Ошибка при вводе ПИН-кода", "ПИН-код не был введен, либо введен неверно, либо вводимый ПИН-код заблокирован. Попробуйте повторить операцию оплаты.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_NO_CONNECTION_BANK: @@ -410,7 +417,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Нет связи с банком", f"Попробуйте повторить операцию через пару минут. При повторении ошибки необходимо обратиться в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_NEED_CASH_COLLECTION: @@ -418,7 +425,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Необходимо произвести инкассацию", f"Необходимо произвести инкассацию. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_LIMIT_OPERATION: @@ -426,7 +433,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Превышен лимит операций", f"Превышен лимит операций. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_DATA_EXCHANGE: @@ -434,7 +441,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Требуется сделать сверку итогов", "Требуется сделать сверку итогов и повторить операцию.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_KLK: @@ -442,7 +449,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Ошибка в работе терминала", f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_CARD_ERROR: @@ -450,7 +457,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Операцию невозможно выполнить для этой карты", f"Необходимо повторить попытку. Если проблема сохраняется – использовать другую карту. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_SERVER_ROUTINE_MAINTENANCE: @@ -458,15 +465,15 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Сервера Сбербанка недоступны", f"Сервера Сбербанка находятся на обслуживании/ремонте/регламентных работах. Попробуйте повторить операцию позже. Если проблема сохраняется - обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}.Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_NO_MONEY: _safe_handle_error( returncode, "Недостаточно средств на карте", - f"Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", - error_callback + "Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", + error_callback, ) return 0 if returncode in TERMINAL_COMMAND_ERROR: @@ -474,7 +481,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Нет нужного варианта связи для операции", f"Нет нужного варианта связи для операции. Обратитесь в службу поддержки ПО. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_PIN_PAD_ERROR: @@ -482,7 +489,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Проблема в работе ПИН-пада.", f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 # Общие ошибки @@ -499,7 +506,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Ошибка терминала", f"Возвращен неизвестный код ошибки. Обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 @@ -519,7 +526,9 @@ def terminal_oplata(amount: float) -> int: @logger_wraps() -def process_terminal_transaction(command_code: str, amount: float, operation_name: str, error_callback=None) -> int: +def process_terminal_transaction( + command_code: str, amount: float, operation_name: str, error_callback=None +) -> int: """ Обрабатывает операцию на банковском терминале. @@ -541,10 +550,10 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam if result is None: logger.error(f"Ошибка при выполнении команды терминала: {command}") _safe_handle_error( - f"Нет ответа от терминала", + "Нет ответа от терминала", f"{operation_name} не была выполнена. Проверьте устройство.", "Код ошибки: отсутствует", - error_callback=error_callback + error_callback=error_callback, ) return 0 @@ -567,11 +576,12 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam def universal_terminal_operation( - payment_type: int, - amount: float, - progress_signal, - operation_type: int = 1, - error_callback=None) -> tuple[int, int]: + payment_type: int, + amount: float, + progress_signal, + operation_type: int = 1, + error_callback=None, +) -> tuple[int, int]: """ Универсальный обработчик терминальных операций для оплаты или возврата. @@ -621,7 +631,9 @@ def universal_terminal_operation( except Exception: pass # Игнорируем ошибки emit - bank = process_terminal_transaction(command_code, amount, operation_name, error_callback) + bank = process_terminal_transaction( + command_code, amount, operation_name, error_callback + ) if bank == 1: if progress_signal is not None: try: @@ -638,12 +650,16 @@ def universal_terminal_operation( return 0, 1 elif payment_type == PAYMENT_OFFLINE: if operation_type != 1: - logger.warning("Оффлайн-режим не поддерживается для возврата или отмены") + logger.warning( + "Оффлайн-режим не поддерживается для возврата или отмены" + ) progress_signal.emit("Оффлайн: только оплата", 100) return 0, 0 logger.info("Запускаем offline оплату по банковскому терминалу") - progress_signal.emit("Запускаем offline оплату по банковскому терминалу...", 35) + progress_signal.emit( + "Запускаем offline оплату по банковскому терминалу...", 35 + ) return 1, 3 # успех, offline else: @@ -652,13 +668,15 @@ def universal_terminal_operation( return 0, 0 except ValueError as ve: logger.error(f"Ошибка: {ve}") - progress_signal.emit(f"Ошибка: {str(ve)}", 100) + progress_signal.emit(f"Ошибка: {ve!s}", 100) return 0, 0 except Exception as exp: logger.error(f"Неизвестная ошибка при проведении операции: {exp}") if progress_signal is not None: try: - progress_signal.emit(f"Неизвестная ошибка при проведении операции: {str(exp)}", 100) + progress_signal.emit( + f"Неизвестная ошибка при проведении операции: {exp!s}", 100 + ) except RuntimeError: pass return 0, 0 @@ -979,7 +997,8 @@ def print_pinpad_check(count: int = 2): count -= 1 -def get_info(hide: bool = False) -> Optional[int]: + +def get_info(hide: bool = False) -> int | None: """ Запрос информации о ККТ. Запрашивает информацию о кассовом аппарате (модель, наименование и версия ПО). @@ -1014,13 +1033,20 @@ def get_info(hide: bool = False) -> Optional[int]: return model except (ConnectionError, AttributeError) as e: logger.error(f"Ошибка при получении данных о ККТ: {e}") - windows.info_window("Ошибка", "Не удалось получить данные о ККТ.", "Проверьте настройки устройства.") + windows.info_window( + "Ошибка", + "Не удалось получить данные о ККТ.", + "Проверьте настройки устройства.", + ) return None except Exception as e: logger.error(f"Неизвестная ошибка: {e}") - windows.info_window("Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова.") + windows.info_window( + "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." + ) return None + def is_kkt_connected() -> bool: """ Проверка физического подключения ККТ. @@ -1046,6 +1072,7 @@ def is_kkt_connected() -> bool: logger.error(f"Ошибка при проверке подключения ККТ: {e}") return False + @logger_wraps() def get_last_document(day: int = 7) -> None: """Запрос информации о последнем чеке в ФН и проверка даты. @@ -1066,7 +1093,9 @@ def get_last_document(day: int = 7) -> None: except (ConnectionError, AttributeError) as e: logger.error(f"Ошибка при работе с ККТ: {e}") windows.info_window( - "Ошибка", "Не удалось получить данные от ККТ.", "Проверьте подключение и настройки." + "Ошибка", + "Не удалось получить данные от ККТ.", + "Проверьте подключение и настройки.", ) except Exception as e: logger.exception(f"Неизвестная ошибка в get_last_document: {e}") @@ -1074,8 +1103,9 @@ def get_last_document(day: int = 7) -> None: "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." ) + def get_last_document_datetime() -> datetime: - """ Получает дату и время последнего зарегистрированного чека из фискального накопителя. + """Получает дату и время последнего зарегистрированного чека из фискального накопителя. Возвращаемое значение: datetime: Дата и время последнего документа в фискальном накопителе. @@ -1085,12 +1115,17 @@ def get_last_document_datetime() -> datetime: """ logger.info("Запуск функции get_last_document_datetime") with fptr_connection(fptr): - fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT) + fptr.setParam( + IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT + ) fptr.fnQueryData() - last_check_datetime: datetime = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME) + last_check_datetime: datetime = fptr.getParamDateTime( + IFptr.LIBFPTR_PARAM_DATE_TIME + ) return last_check_datetime + def check_stale_document(last_check: datetime, max_days: int = 7) -> None: """ Проверяет, не превышает ли возраст последнего чека заданное количество дней. @@ -1108,13 +1143,15 @@ def check_stale_document(last_check: datetime, max_days: int = 7) -> None: if (current_date - last_check) <= timedelta(days=max_days): # Ранний выход, если чек не старый return - info = f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" + info = ( + f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" + ) windows.info_window( "Внимание", "Дата последнего документа, записанного в фискальном накопителе, старше 7 дней.\n\n" "Для корректного проведения платежей по банковскому терминалу " - "необходимо сделать сверку итогов (вкладка \"Касса\" -> \"Сверка итогов\").\n", - info + 'необходимо сделать сверку итогов (вкладка "Касса" -> "Сверка итогов").\n', + info, ) @@ -1402,7 +1439,9 @@ def kassir_reg(user): try: with fptr_connection(fptr): # Параметры для регистрации кассира - fptr.setParam(1021, f"{user.last_name} {user.first_name} {user.middle_name}") + fptr.setParam( + 1021, f"{user.last_name} {user.first_name} {user.middle_name}" + ) fptr.setParam(1203, user.inn) fptr.operatorLogin() logger.info( @@ -1590,11 +1629,11 @@ def register_item(device, name, price, quantity, tax_type=IFptr.LIBFPTR_TAX_VAT2 logger.info("Запуск функции register_item") logger.info(f"В функцию переданы параметры: {name}, {price}, {quantity}") device.setParam(IFptr.LIBFPTR_PARAM_COMMODITY_NAME, name) - logger.info(f"name: {name}") # TODO: убрать + logger.info(f"name: {name}") # TODO: убрать device.setParam(IFptr.LIBFPTR_PARAM_PRICE, price) - logger.info(f"price: {price}") # TODO: убрать + logger.info(f"price: {price}") # TODO: убрать device.setParam(IFptr.LIBFPTR_PARAM_QUANTITY, quantity) - logger.info(f"quantity: {quantity}") # TODO: убрать + logger.info(f"quantity: {quantity}") # TODO: убрать device.setParam(IFptr.LIBFPTR_PARAM_TAX_TYPE, tax_type) device.registration() @@ -1639,7 +1678,9 @@ def register_tickets(device, sale_dict, type_operation): None: Функция не возвращает значений, но выполняет регистрацию билетов в чеке. """ logger.info("Запуск функции register_tickets") - logger.debug(f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}") + logger.debug( + f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}" + ) if type_operation == 1: count_adult_with_discount = sale_dict["detail"][0] @@ -1653,29 +1694,41 @@ def register_tickets(device, sale_dict, type_operation): # Взрослые без акции count_adult_no_discount = sale_dict["kol_adult"] - count_adult_with_discount - logger.debug(f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}") + logger.debug( + f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}" + ) if count_adult_no_discount > 0: adults_no_discount_sum = count_adult_no_discount * sale_dict["price_adult"] - logger.info(f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб") + logger.info( + f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб" + ) # if device is not None: # TODO: для теста без ККТ. + отступ внизу register_item( device, f"Билет взрослый {time} ч.", - sale_dict['price_adult'], + sale_dict["price_adult"], count_adult_no_discount, ) else: logger.debug("Взрослые без акции: не регистрируем (количество = 0)") # Взрослые с акцией - logger.debug(f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}") + logger.debug( + f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}" + ) - if (count_adult_with_discount > 0 - and price_adult_with_discount > 0 - and discount > 0): - adults_with_discount_sum = count_adult_with_discount * price_adult_with_discount - logger.info(f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб") + if ( + count_adult_with_discount > 0 + and price_adult_with_discount > 0 + and discount > 0 + ): + adults_with_discount_sum = ( + count_adult_with_discount * price_adult_with_discount + ) + logger.info( + f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб" + ) # if device is not None: # TODO: для теста без ККТ. + отступ внизу register_item( device, @@ -1690,29 +1743,43 @@ def register_tickets(device, sale_dict, type_operation): # Дети без акции count_child_no_discount = sale_dict["kol_child"] - count_child_with_discount - logger.debug(f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}") + logger.debug( + f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}" + ) if count_child_no_discount > 0: - children_no_discount_sum = count_child_no_discount * sale_dict["price_child"] - logger.info(f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб") + children_no_discount_sum = ( + count_child_no_discount * sale_dict["price_child"] + ) + logger.info( + f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб" + ) # if device is not None: # TODO: для теста без ККТ. + отступ внизу register_item( device, f"Билет детский {time} ч.", - sale_dict['price_child'], + sale_dict["price_child"], count_child_no_discount, ) else: logger.debug("Дети без акции: не регистрируем (количество = 0)") # Дети с акцией - logger.debug(f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}") + logger.debug( + f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}" + ) - if (count_child_with_discount > 0 - and price_child_with_discount > 0 - and discount > 0): - children_with_discount_sum = count_child_with_discount * price_child_with_discount - logger.info(f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб") + if ( + count_child_with_discount > 0 + and price_child_with_discount > 0 + and discount > 0 + ): + children_with_discount_sum = ( + count_child_with_discount * price_child_with_discount + ) + logger.info( + f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб" + ) # if device is not None: # TODO: для теста без ККТ. + отступ внизу register_item( device, @@ -1724,15 +1791,17 @@ def register_tickets(device, sale_dict, type_operation): logger.debug("Дети с акцией: не регистрируем (условия не выполнены)") else: # Для других типов операций - logger.error(f"Другие типы операций.") + logger.error("Другие типы операций.") for item_name, item_data in sale_dict.items(): - if (isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0): + if isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0: # Проверяем наличие количества и цены - logger.debug(f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}") + logger.debug( + f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}" + ) register_item(device, item_name, item_data[0], item_data[1]) else: # Обработка других типов данных - logger.error(f"Ошибка типа данных. Item_data - не список.") + logger.error("Ошибка типа данных. Item_data - не список.") def process_payment(device, payment_type, bank_status, sale_dict, _): @@ -1748,11 +1817,15 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): bool: Возвращает `True`, если оплата прошла успешно, иначе — `False`. """ logger.info("Запуск функции process_payment") - logger.debug(f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}") + logger.debug( + f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}" + ) try: # Проверка payment_type if not isinstance(payment_type, int): - logger.error(f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}") + logger.error( + f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}" + ) return False # Определение суммы @@ -1763,7 +1836,9 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): try: payment_amount = sale_dict["detail"][7] if isinstance(payment_amount, (int, float)) and payment_amount > 0: - logger.debug(f"Сумма получена из sale_dict['detail'][7]: {payment_amount}") + logger.debug( + f"Сумма получена из sale_dict['detail'][7]: {payment_amount}" + ) except (IndexError, TypeError): pass @@ -1775,7 +1850,12 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): if isinstance(item_data, list) and len(item_data) >= 2: price = item_data[0] qty = item_data[1] - if isinstance(price, (int, float)) and isinstance(qty, int) and price > 0 and qty > 0: + if ( + isinstance(price, (int, float)) + and isinstance(qty, int) + and price > 0 + and qty > 0 + ): total += price * qty valid = True if valid and total > 0: @@ -1846,15 +1926,23 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): logger.info("Запуск функции handle_document_errors") # Проверяем, закрыт ли документ while device.checkDocumentClosed() < 0: - logger.warning(f"Не удалось проверить состояние документа: {device.errorDescription()}") + logger.warning( + f"Не удалось проверить состояние документа: {device.errorDescription()}" + ) if retry_count >= max_retries - 1: if on_error: - on_error("Ошибка ККТ", f"Не удалось проверить состояние документа после {max_retries} попыток. " - f"Проверьте соединение с ККТ и не выключайте ПК.") + on_error( + "Ошибка ККТ", + f"Не удалось проверить состояние документа после {max_retries} попыток. " + f"Проверьте соединение с ККТ и не выключайте ПК.", + ) return False if on_error: - on_error("Ошибка ККТ", f"Ошибка: {device.errorDescription()}. Повторная попытка...") + on_error( + "Ошибка ККТ", + f"Ошибка: {device.errorDescription()}. Повторная попытка...", + ) retry_count += 1 time.sleep(5) @@ -1868,12 +1956,17 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): except Exception as e: logger.critical(f"Ошибка при отмене чека: {e}") if on_error: - on_error("Критическая ошибка", "Не удалось отменить чек. Требуется перезагрузка ККТ.") + on_error( + "Критическая ошибка", + "Не удалось отменить чек. Требуется перезагрузка ККТ.", + ) # Пробрасываем исключение наверх raise if on_error: - on_error("Документ не закрыт", "Чек отменен. Требуется сформировать его заново.") + on_error( + "Документ не закрыт", "Чек отменен. Требуется сформировать его заново." + ) return False # Если документ не напечатан — попробовать допечатать @@ -1884,12 +1977,19 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): logger.info("Документ успешно допечатан") return True - logger.warning(f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}") + logger.warning( + f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}" + ) if on_error: - on_error("Ошибка печати", f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.") + on_error( + "Ошибка печати", + f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.", + ) time.sleep(3) - logger.error("Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции.") + logger.error( + "Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции." + ) # Документ закрыт, но не допечатан — допустимо. # Документ будет автоматически допечатан при следующей печатной операции. @@ -1901,7 +2001,15 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): return True -def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_status, on_error=None): +def check_open( + sale_dict, + payment_type, + user, + type_operation, + print_check, + bank_status, + on_error=None, +): """ Проведение операции оплаты. @@ -1941,12 +2049,16 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ setup_fptr(device, user, type_operation, print_check) # Проверка состояния чека - device.setParam(IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE) + device.setParam( + IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE + ) device.queryData() - receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) - document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) + receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) + document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) receipt_type = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE) - logger.debug(f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}") + logger.debug( + f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}" + ) if receipt_type != IFptr.LIBFPTR_RT_CLOSED: logger.error(f"Чек уже открыт! Тип: {receipt_type}") if on_error: @@ -1955,9 +2067,13 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ # Установка типа чека if type_operation == 1: - device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL) # Продажа + device.setParam( + IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL + ) # Продажа elif type_operation == 2: - device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN) # Возврат + device.setParam( + IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN + ) # Возврат else: logger.error(f"Неизвестный тип операции: {type_operation}") return False @@ -1980,11 +2096,12 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ return 1 except Exception as e: - logger.error(f"Критическая ошибка в check_open: {str(e)}") + logger.error(f"Критическая ошибка в check_open: {e!s}") if on_error: - on_error("Ошибка ККТ", f"Критическая ошибка: {str(e)}") + on_error("Ошибка ККТ", f"Критическая ошибка: {e!s}") return 0 + @logger_wraps() def smena_close(user): """Закрытие кассовой смены. diff --git a/modules/progress_window.py b/modules/progress_window.py index c8e6d1e..660ac03 100644 --- a/modules/progress_window.py +++ b/modules/progress_window.py @@ -1,5 +1,4 @@ -from PySide6.QtCore import QPropertyAnimation, QEasingCurve -from PySide6.QtCore import Qt +from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt from PySide6.QtWidgets import QDialog from design.logic.progress_dialog import Ui_ProgressDialog diff --git a/modules/sale_logic.py b/modules/sale_logic.py index 5ba2434..dcadae4 100644 --- a/modules/sale_logic.py +++ b/modules/sale_logic.py @@ -230,10 +230,10 @@ def calculate_itog() -> int: # Проверка на корректность данных if ( - system.sale_dict["detail"][0] < 0 - or system.sale_dict["detail"][2] < 0 - or system.sale_dict["detail"][1] < 0 - or system.sale_dict["detail"][3] < 0 + system.sale_dict["detail"][0] < 0 + or system.sale_dict["detail"][2] < 0 + or system.sale_dict["detail"][1] < 0 + or system.sale_dict["detail"][3] < 0 ): raise ValueError("Некорректные данные для расчета итоговой суммы") @@ -243,7 +243,9 @@ def calculate_itog() -> int: adults_no_discount_sum = adults_no_discount * system.sale_dict["price_adult"] # Взрослые со скидкой (detail[0] × detail[1]) - adults_with_discount_sum = system.sale_dict["detail"][0] * system.sale_dict["detail"][1] + adults_with_discount_sum = ( + system.sale_dict["detail"][0] * system.sale_dict["detail"][1] + ) adult_total = adults_no_discount_sum + adults_with_discount_sum @@ -253,21 +255,33 @@ def calculate_itog() -> int: children_no_discount_sum = children_no_discount * system.sale_dict["price_child"] # Дети со скидкой (detail[2] × detail[3]) - children_with_discount_sum = system.sale_dict["detail"][2] * system.sale_dict["detail"][3] + children_with_discount_sum = ( + system.sale_dict["detail"][2] * system.sale_dict["detail"][3] + ) child_total = children_no_discount_sum + children_with_discount_sum result = adult_total + child_total - logger.debug(f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}") - logger.debug(f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}") + logger.debug( + f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}" + ) + logger.debug( + f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}" + ) logger.debug(f"ИТОГО взрослые: {adult_total}") - logger.debug(f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}") - logger.debug(f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}") + logger.debug( + f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}" + ) + logger.debug( + f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}" + ) logger.debug(f"ИТОГО дети: {child_total}") - logger.debug(f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}") + logger.debug( + f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}" + ) return result diff --git a/modules/system.py b/modules/system.py index 34c8fdf..0c7b82d 100644 --- a/modules/system.py +++ b/modules/system.py @@ -12,7 +12,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session -from db.models import Holiday, Workday, Price, User, Sale +from db.models import Holiday, Price, Sale, User, Workday from modules.config import Config from modules.logger import logger, logger_wraps @@ -24,7 +24,7 @@ class System: def __new__(cls, *args, **kwargs): if not cls._instance: - cls._instance = super(System, cls).__new__(cls, *args, **kwargs) + cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance def __init__(self): @@ -167,7 +167,9 @@ def user_authorization(self, login: str, password: str) -> int: logger.info(f"Успешная авторизация: {kassir.last_name}") return 1 else: - logger.warning(f"Неудачная попытка авторизации для пользователя {login}") + logger.warning( + f"Неудачная попытка авторизации для пользователя {login}" + ) return 0 else: logger.warning(f"Пользователь {login} не найден") @@ -241,7 +243,7 @@ def get_price(self) -> None: db_prices[key] = default_prices[key] else: # Если записей нет, используем дефолтные значения для всех элементов - logger.warning(f"Используем дефолтные значения для прайс-листа") + logger.warning("Используем дефолтные значения для прайс-листа") db_prices = default_prices # Устанавливаем цены, используя данные из db_prices или дефолтные значения @@ -410,24 +412,23 @@ def get_slip_data(self, sale_id: int) -> tuple[str, str, str, str]: query = select(Sale.bank_pay).where(Sale.id == sale_id) load_slip = session.execute(query).scalars().one() try: - qr_match = re.search(r'Номер QR:\s+(\d+)', load_slip) + qr_match = re.search(r"Номер QR:\s+(\d+)", load_slip) if qr_match: - last_digits = re.search(r'Карта:\s+\*+\s*(\d{4})', load_slip) - card_tail = last_digits.group(1) if last_digits else '' + last_digits = re.search(r"Карта:\s+\*+\s*(\d{4})", load_slip) + card_tail = last_digits.group(1) if last_digits else "" else: - last_digits = re.search(r'Карта:\(?E\d*\)?\s+\*+\s*(\d{4})', load_slip) - card_tail = last_digits.group(1) if last_digits else '' - merchant = re.search(r'М:(\d+)', load_slip) - merchant_id = merchant.group(1) if merchant else '' - rrn = re.search(r'RRN:\s+(\d+)', load_slip) - rrn_value = rrn.group(1) if rrn else '' + last_digits = re.search(r"Карта:\(?E\d*\)?\s+\*+\s*(\d{4})", load_slip) + card_tail = last_digits.group(1) if last_digits else "" + merchant = re.search(r"М:(\d+)", load_slip) + merchant_id = merchant.group(1) if merchant else "" + rrn = re.search(r"RRN:\s+(\d+)", load_slip) + rrn_value = rrn.group(1) if rrn else "" except Exception as e: logger.warning(f"Ошибка при разборе чека: {e}") - card_tail = '' - merchant_id = '' - rrn_value = '' - return card_tail, merchant_id, rrn_value, load_slip - + card_tail = "" + merchant_id = "" + rrn_value = "" + return card_tail, merchant_id, rrn_value, load_slip def check_db_connection(self) -> bool: """Проверка подключения к базе данных.""" @@ -439,4 +440,4 @@ def check_db_connection(self) -> bool: return True except Exception as e: logger.error(f"Ошибка подключения к БД: {e}") - return False \ No newline at end of file + return False diff --git a/modules/worker.py b/modules/worker.py index d83a6e7..15f6a25 100644 --- a/modules/worker.py +++ b/modules/worker.py @@ -1,7 +1,15 @@ import datetime as dt from functools import wraps -from PySide6.QtCore import QElapsedTimer, QObject, Signal, QMetaObject, Qt, QTimer, QEventLoop +from PySide6.QtCore import ( + QElapsedTimer, + QEventLoop, + QMetaObject, + QObject, + Qt, + QTimer, + Signal, +) from sqlalchemy import update from db.models import Sale @@ -10,16 +18,19 @@ def with_timer(func): """Декоратор для автоматического создания QElapsedTimer""" + @wraps(func) def wrapper(self, *args, **kwargs): timer = QElapsedTimer() timer.start() return func(self, timer, *args, **kwargs) + return wrapper class BaseWorker(QObject): """Базовый класс с общими методами""" + DEFAULT_DELAY_MS = 15 progress_updated = Signal(str, int) @@ -35,9 +46,10 @@ def log_step(timer, step_name): elapsed = timer.elapsed() logger.debug(f"[TIMER] {step_name} — {elapsed} ms") - def delayed_progress_update(self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS): - if delay_ms < 0: - delay_ms = 0 + def delayed_progress_update( + self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS + ): + delay_ms = max(delay_ms, 0) if delay_ms > 0: # Создаем локальный таймер для обеспечения задержки timer = QTimer(self) @@ -58,9 +70,15 @@ def invoke_main_window_method(self, method_name: str): Qt.QueuedConnection, ) - def emit_error_and_finish(self, title: str, message: str, code: str = "", - timer: QElapsedTimer = None, step_name: str = "", - close_window: bool = True): + def emit_error_and_finish( + self, + title: str, + message: str, + code: str = "", + timer: QElapsedTimer = None, + step_name: str = "", + close_window: bool = True, + ): """ Универсальный метод обработки ошибок :param title: Заголовок ошибки @@ -87,11 +105,13 @@ def emit_error_and_finish(self, title: str, message: str, code: str = "", logger.warning("Попытка emit после удаления объекта — игнорируем") else: raise - except Exception as e: + except Exception: logger.exception("Неожиданная ошибка в emit_error_and_finish") + class PaymentHandler: """Обработчик банковских платежей""" + def __init__(self, worker, pq, payment_type, amount, dev_mode=False): self.worker = worker self.pq = pq @@ -107,7 +127,9 @@ def process_bank_payment(self, timer: QElapsedTimer = None): if self.dev_mode: self.worker.logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") - self.worker.logger.debug(f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}") + self.worker.logger.debug( + f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}" + ) return True, 1 # (success, payment_type) try: @@ -116,7 +138,7 @@ def process_bank_payment(self, timer: QElapsedTimer = None): self.payment_type, self.amount, self.worker.progress_updated, - error_callback=self._handle_terminal_error_callback + error_callback=self._handle_terminal_error_callback, ) self.worker.log_step(timer, "pq.universal_terminal_operation finished") @@ -134,19 +156,21 @@ def process_bank_payment(self, timer: QElapsedTimer = None): def get_mock_slip(self): """Генерация тестового слип-чека для режима отладки""" from datetime import datetime - return (f"DEBUG_SLIP\n" - f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" - f"Сумма: {self.amount} руб.\n" - f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" - f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ") - def _handle_terminal_error_callback(self, title: str, message: str, code: int) -> None: + return ( + f"DEBUG_SLIP\n" + f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" + f"Сумма: {self.amount} руб.\n" + f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" + f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ" + ) + + def _handle_terminal_error_callback( + self, title: str, message: str, code: int + ) -> None: """Callback для обработки ошибок от терминала""" self.worker.emit_error_and_finish( - title=title, - message=message, - code=str(code), - step_name="terminal_error" + title=title, message=message, code=str(code), step_name="terminal_error" ) def _handle_payment_failed(self, timer): @@ -155,7 +179,7 @@ def _handle_payment_failed(self, timer): title="Ошибка", message="Операция оплаты не удалась. Повторите попытку.", timer=timer, - step_name="payment_failed" + step_name="payment_failed", ) def _handle_payment_exception(self, timer, exception): @@ -165,12 +189,13 @@ def _handle_payment_exception(self, timer, exception): message="Не удалось выполнить операцию на терминале.", code=str(exception), timer=timer, - step_name="payment_exception" + step_name="payment_exception", ) class CheckHandler: """Обработчик печати чеков""" + def __init__(self, worker, pq): self.worker = worker self.pq = pq @@ -184,25 +209,28 @@ def print_check(self, sale_dict, payment_type, user, print_check, _, bank_data): type_operation=1, print_check=print_check, bank_status=bank_data, - on_error=self.handle_check_error + on_error=self.handle_check_error, ) return state_check == 1 - def handle_check_error(self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка"): + def handle_check_error( + self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка" + ): """Обработчик ошибок печати чека""" logger.error(f"handle_check_error вызван с title={title}, text={text}") self.worker.emit_error_and_finish( title="Ошибка ККМ", message="Документ не закрылся после 5 попыток. Отмена кассового чека.", code="critical", - step_name="check_error" + step_name="check_error", ) # raise Exception(f"{title}: {text}") class DatabaseHandler: """Обработчик работы с базой данных""" + def __init__(self, Session, engine): self.Session = Session self.engine = engine @@ -211,7 +239,9 @@ def sale_exists(self, sale_id): """Проверяет существование продажи по ID""" try: with self.Session(self.engine) as session: - return session.query(Sale).filter(Sale.id == sale_id).first() is not None + return ( + session.query(Sale).filter(Sale.id == sale_id).first() is not None + ) except Exception as e: logger.error(f"Ошибка проверки существования продажи {sale_id}: {e}") return False @@ -219,17 +249,16 @@ def sale_exists(self, sale_id): def update_sale(self, sale_id, **values): """Общее сохранение данных о продаже""" with self.Session(self.engine) as session: - session.execute( - update(Sale) - .where(Sale.id == sale_id) - .values(**values) - ) + session.execute(update(Sale).where(Sale.id == sale_id).values(**values)) session.commit() class TransactionWorker(BaseWorker): """Основной класс обработки транзакций""" - def __init__(self, payment_type, print_check, system, pq, Session, main_window, parent=None): + + def __init__( + self, payment_type, print_check, system, pq, Session, main_window, parent=None + ): super().__init__(parent) self.payment_type = payment_type self.print_check = print_check @@ -238,7 +267,9 @@ def __init__(self, payment_type, print_check, system, pq, Session, main_window, self.Session = Session self.main_window = main_window # Флаг режима отладки - self.dev_mode = getattr(main_window, 'dev_mode', False) if main_window else False + self.dev_mode = ( + getattr(main_window, "dev_mode", False) if main_window else False + ) # Флаг cleanup self._is_cleaned = False @@ -262,14 +293,16 @@ def __init__(self, payment_type, print_check, system, pq, Session, main_window, @with_timer def process_special_sale(self, timer: QElapsedTimer, progress_percent: int): """Обработка специальной продажи""" - self.delayed_progress_update("Печатаем билеты (особая продажа)...", progress_percent) + self.delayed_progress_update( + "Печатаем билеты (особая продажа)...", progress_percent + ) self.invoke_main_window_method("print_saved_tickets") self.log_step(timer, "print_saved_tickets finished") self.system.sale_status = 0 def process_payment(self, timer: QElapsedTimer): """Обработка платежа""" - payment = 2 # по умолчанию наличные + payment = 2 # по умолчанию наличные bank_status = 0 # Проверяем, что sale_id существует @@ -278,17 +311,19 @@ def process_payment(self, timer: QElapsedTimer): self.emit_error_and_finish( title="Критическая ошибка", message="Продажа не была создана в БД", - code="SALE_ID_NONE" + code="SALE_ID_NONE", ) return None, None # Проверяем существует ли продажа в БД? if not self.db_handler.sale_exists(self.system.sale_id): - logger.warning(f"Продажа {self.system.sale_id} есть в памяти, но отсутствует в БД!") + logger.warning( + f"Продажа {self.system.sale_id} есть в памяти, но отсутствует в БД!" + ) self.emit_error_and_finish( title="Критическая ошибка", message="Продажа не найдена в БД после сохранения", - code="SALE_NOT_IN_DB" + code="SALE_NOT_IN_DB", ) return None, None @@ -318,7 +353,9 @@ def process_payment(self, timer: QElapsedTimer): bank_status = 1 # Обязательно сохраняем банковский слип-чек try: - self.db_handler.update_sale(self.system.sale_id, bank_pay=check, status=9) + self.db_handler.update_sale( + self.system.sale_id, bank_pay=check, status=9 + ) except Exception as e: logger.warning(f"Ошибка сохранения банковского чека: {e}") self.log_step(timer, "save in db finished") @@ -335,7 +372,9 @@ def process_checks(self, timer: QElapsedTimer, payment, bank_data): self.delayed_progress_update("Печатаем кассовый чек...", 60) logger.debug(f"ДО check_open: sale_dict = {self.system.sale_dict}") - logger.debug(f"exclude_from_sale={self.system.exclude_from_sale}, sale_checkbox_row={self.system.sale_checkbox_row}") + logger.debug( + f"exclude_from_sale={self.system.exclude_from_sale}, sale_checkbox_row={self.system.sale_checkbox_row}" + ) try: if not self.check_handler.print_check( @@ -344,7 +383,7 @@ def process_checks(self, timer: QElapsedTimer, payment, bank_data): self.system.user, self.print_check, self.system.sale_dict["detail"][7], - bank_data + bank_data, ): # В случае ошибки внутри print_check должен быть вызван on_error return False @@ -394,7 +433,7 @@ def handle_error(self, error, timer: QElapsedTimer): code=str(error), timer=timer, step_name="transaction_error", - close_window=True + close_window=True, ) self.system.sale_status = 0 diff --git a/patch.diff b/patch.diff new file mode 100644 index 0000000..c0feb1d --- /dev/null +++ b/patch.diff @@ -0,0 +1,11457 @@ +diff --git a/db/models/sale.py b/db/models/sale.py +index a0035ee..4e59786 100644 +--- a/db/models/sale.py ++++ b/db/models/sale.py +@@ -37,7 +37,7 @@ class Sale(Base): + datetime_return: Mapped[DateTime | None] = mapped_column( + DateTime, comment="Дата и время возврата продажи" + ) +- bank_return: Mapped[str | None | None] = mapped_column( ++ bank_return: Mapped[str | None] = mapped_column( + Text, comment="Банковский чек возврата" + ) + partial_return: Mapped[int] = mapped_column( +diff --git a/design/logic/authorization.py b/design/logic/authorization.py +index 6238708..6242cff 100644 +--- a/design/logic/authorization.py ++++ b/design/logic/authorization.py +@@ -3,7 +3,7 @@ from PySide6.QtGui import QPixmap + from PySide6.QtWidgets import QFrame, QLabel, QLineEdit, QPushButton + + +-class Ui_Dialog(object): ++class Ui_Dialog: + def setupUi(self, Dialog): + if not Dialog.objectName(): + Dialog.setObjectName("Dialog") +diff --git a/design/logic/client.py b/design/logic/client.py +index ddc3b51..a12ecad 100644 +--- a/design/logic/client.py ++++ b/design/logic/client.py +@@ -13,7 +13,7 @@ from PySide6.QtWidgets import ( + ) + + +-class Ui_Dialog_Client(object): ++class Ui_Dialog_Client: + def setupUi(self, Dialog_Client): + if not Dialog_Client.objectName(): + Dialog_Client.setObjectName("Dialog_Client") +diff --git a/design/logic/main_form.py b/design/logic/main_form.py +index b3bfd74..f0088d0 100644 +--- a/design/logic/main_form.py ++++ b/design/logic/main_form.py +@@ -15,14 +15,14 @@ from PySide6.QtWidgets import ( + QPushButton, + QRadioButton, + QStatusBar, +- QTabWidget, + QTableWidget, + QTableWidgetItem, ++ QTabWidget, + QWidget, + ) + + +-class Ui_MainWindow(object): ++class Ui_MainWindow: + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName("MainWindow") +diff --git a/design/logic/pay.py b/design/logic/pay.py +index 3926a8f..8d243c4 100644 +--- a/design/logic/pay.py ++++ b/design/logic/pay.py +@@ -1,9 +1,9 @@ + from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt + from PySide6.QtGui import QFont +-from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QCheckBox ++from PySide6.QtWidgets import QCheckBox, QGroupBox, QLabel, QPushButton + + +-class Ui_Dialog_Pay(object): ++class Ui_Dialog_Pay: + def setupUi(self, Dialog_Pay): + if not Dialog_Pay.objectName(): + Dialog_Pay.setObjectName("Dialog_Pay") +diff --git a/design/logic/progress_dialog.py b/design/logic/progress_dialog.py +index e7b5b3e..7d2596a 100644 +--- a/design/logic/progress_dialog.py ++++ b/design/logic/progress_dialog.py +@@ -1,23 +1,23 @@ +-from PySide6.QtCore import (QCoreApplication, QMetaObject, QSize, Qt) +-from PySide6.QtWidgets import (QLabel, QProgressBar, +- QVBoxLayout) ++from PySide6.QtCore import QCoreApplication, QMetaObject, QSize, Qt ++from PySide6.QtWidgets import QLabel, QProgressBar, QVBoxLayout + +-class Ui_ProgressDialog(object): ++ ++class Ui_ProgressDialog: + def setupUi(self, ProgressDialog): + if not ProgressDialog.objectName(): +- ProgressDialog.setObjectName(u"ProgressDialog") ++ ProgressDialog.setObjectName("ProgressDialog") + ProgressDialog.resize(300, 127) + ProgressDialog.setMinimumSize(QSize(300, 100)) + self.verticalLayout = QVBoxLayout(ProgressDialog) +- self.verticalLayout.setObjectName(u"verticalLayout") ++ self.verticalLayout.setObjectName("verticalLayout") + self.label_status = QLabel(ProgressDialog) +- self.label_status.setObjectName(u"label_status") ++ self.label_status.setObjectName("label_status") + self.label_status.setAlignment(Qt.AlignmentFlag.AlignCenter) + + self.verticalLayout.addWidget(self.label_status) + + self.progressBar = QProgressBar(ProgressDialog) +- self.progressBar.setObjectName(u"progressBar") ++ self.progressBar.setObjectName("progressBar") + self.progressBar.setMinimum(0) + self.progressBar.setMaximum(100) + self.progressBar.setValue(0) +@@ -25,14 +25,26 @@ class Ui_ProgressDialog(object): + + self.verticalLayout.addWidget(self.progressBar) + +- + self.retranslateUi(ProgressDialog) + + QMetaObject.connectSlotsByName(ProgressDialog) ++ + # setupUi + + def retranslateUi(self, ProgressDialog): +- ProgressDialog.setWindowTitle(QCoreApplication.translate("ProgressDialog", u"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", None)) +- self.label_status.setText(QCoreApplication.translate("ProgressDialog", u"\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", None)) +- # retranslateUi ++ ProgressDialog.setWindowTitle( ++ QCoreApplication.translate( ++ "ProgressDialog", ++ "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", ++ None, ++ ) ++ ) ++ self.label_status.setText( ++ QCoreApplication.translate( ++ "ProgressDialog", ++ "\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", ++ None, ++ ) ++ ) + ++ # retranslateUi +diff --git a/design/logic/sale.py b/design/logic/sale.py +index ab9ae05..56b8bb8 100644 +--- a/design/logic/sale.py ++++ b/design/logic/sale.py +@@ -15,7 +15,7 @@ from PySide6.QtWidgets import ( + ) + + +-class Ui_Dialog_Sale(object): ++class Ui_Dialog_Sale: + def setupUi(self, Dialog_Sale): + if not Dialog_Sale.objectName(): + Dialog_Sale.setObjectName("Dialog_Sale") +diff --git a/design/logic/slip.py b/design/logic/slip.py +index 16b50c0..9e2b262 100644 +--- a/design/logic/slip.py ++++ b/design/logic/slip.py +@@ -3,7 +3,7 @@ from PySide6.QtGui import QFont + from PySide6.QtWidgets import QLabel, QLineEdit, QTabWidget, QTextEdit, QWidget + + +-class Ui_Dialog_Slip(object): ++class Ui_Dialog_Slip: + def setupUi(self, Dialog_Slip): + if not Dialog_Slip.objectName(): + Dialog_Slip.setObjectName("Dialog_Slip") +diff --git a/main.py b/main.py +index c7f54d0..796ae55 100644 +--- a/main.py ++++ b/main.py +@@ -4,46 +4,43 @@ import subprocess + import sys + from datetime import date, timedelta + from pathlib import Path +-from typing import Any, Optional, Type ++from typing import Any + + from PySide6 import QtCore, QtWidgets +-from PySide6.QtCore import Qt +-from PySide6.QtCore import Signal, QThread ++from PySide6.QtCore import Qt, QThread, Signal + from PySide6.QtGui import QColor, QPixmap + from PySide6.QtWidgets import ( + QCheckBox, ++ QDialog, + QHBoxLayout, ++ QMessageBox, + QTableWidgetItem, ++ QWidget, + ) +-from PySide6.QtWidgets import QDialog, QWidget +-from PySide6.QtWidgets import QMessageBox +-from sqlalchemy import and_, select, update, desc ++from sqlalchemy import and_, desc, select, update + from sqlalchemy.orm import Session + +-from db.models import Client +-from db.models import Sale +-from db.models import Ticket ++from db.models import Client, Sale, Ticket + from design.logic.authorization import Ui_Dialog + from design.logic.client import Ui_Dialog_Client + from design.logic.main_form import Ui_MainWindow + from design.logic.pay import Ui_Dialog_Pay + from design.logic.sale import Ui_Dialog_Sale + from design.logic.slip import Ui_Dialog_Slip +-from modules import otchet ++from modules import otchet, windows + from modules import payment_equipment as pq +-from modules import windows + from modules.auth_logic import perform_pre_sale_checks + from modules.logger import logger, logger_wraps + from modules.progress_window import ProgressWindow + from modules.sale_logic import ( +- calculate_age, +- calculate_ticket_type, +- calculated_ticket_price, + calculate_adult_price, ++ calculate_age, + calculate_child_price, +- calculate_discounted_price, + calculate_discount, ++ calculate_discounted_price, + calculate_itog, ++ calculate_ticket_type, ++ calculated_ticket_price, + convert_sale_dict_values, + generating_parts_for_partial_returns, + get_talent_based_on_time, +@@ -67,6 +64,7 @@ def handle_uncaught_exception(exc_type, exc_value, exc_traceback): + "Неперехваченное исключение" + ) + ++ + sys.excepthook = handle_uncaught_exception + + +@@ -101,7 +99,7 @@ class AuthForm(QDialog): + windows.info_window( + "База данных недоступна", + "Не удалось подключиться к серверу базы данных.\n Приложение будет закрыто.", +- "" ++ "", + ) + sys.exit(1) + login: str = self.ui.lineEdit.text() +@@ -134,8 +132,11 @@ class AuthForm(QDialog): + auth.close() + return True + else: +- windows.info_window("Пользователь не найден", "Проверьте правильность ввода логина и пароля.", +- "Убедитесь, что используете правильный логин и пароль для входа в систему.") ++ windows.info_window( ++ "Пользователь не найден", ++ "Проверьте правильность ввода логина и пароля.", ++ "Убедитесь, что используете правильный логин и пароль для входа в систему.", ++ ) + return False + + def check_kkt_connection(self): +@@ -168,15 +169,21 @@ class AuthForm(QDialog): + break + + except (ConnectionError, TimeoutError, Exception) as e: +- error_type = "сетевая" if isinstance(e, (ConnectionError, TimeoutError)) else "неизвестная" +- logger.warning(f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}") ++ error_type = ( ++ "сетевая" ++ if isinstance(e, (ConnectionError, TimeoutError)) ++ else "неизвестная" ++ ) ++ logger.warning( ++ f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}" ++ ) + + if not connected: + logger.error("Все 3 попытки подключения к ККТ не удались") + windows.info_window( + "Внимание", + "Кассовый аппарат не подключен", +- "Программа продолжит работу без фискальных операций." ++ "Программа продолжит работу без фискальных операций.", + ) + except (ConnectionError, TimeoutError) as e: + logger.error(f"Ошибка при проверке подключения к ККТ: {e}") +@@ -225,7 +232,6 @@ class AuthForm(QDialog): + window.exec_() + + +- + class ClientForm(QDialog): + """Форма с данными клиента""" + +@@ -320,7 +326,7 @@ class ClientForm(QDialog): + gender=str(self.ui.comboBox.currentText()), + phone=self.ui.lineEdit_4.text(), + email=self.ui.lineEdit_5.text(), +- privilege=str((self.ui.comboBox_2.currentText())), ++ privilege=str(self.ui.comboBox_2.currentText()), + ) + ) + session.commit() +@@ -396,7 +402,7 @@ class SaleForm(QDialog): + client_id: str = self.ui.tableWidget.item(row_number, 5).text() + # При типизации указываем, что search_client может быть либо экземпляром Client, либо None + with Session(system.engine) as session: +- search_client: Optional[Client] = ( ++ search_client: Client | None = ( + session.query(Client).filter_by(id=client_id).first() + ) + # Проверяем, найден ли клиент +@@ -508,9 +514,10 @@ class SaleForm(QDialog): + None + """ + logger.info("Запуск функции tracking_button_pressing") +- if event.key() == QtCore.Qt.Key_Delete: +- self.deleting_selected_record() +- elif event.key() == QtCore.Qt.Key_Backspace: ++ if ( ++ event.key() == QtCore.Qt.Key_Delete ++ or event.key() == QtCore.Qt.Key_Backspace ++ ): + self.deleting_selected_record() + + def deleting_selected_record(self) -> None: +@@ -533,23 +540,33 @@ class SaleForm(QDialog): + if current_row == system.sale_checkbox_row: + # Удаляется исключенный взрослый + logger.debug(f"Удаляется исключенный взрослый row={current_row}") +- logger.debug(f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") ++ logger.debug( ++ f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" ++ ) + + # Сбросить флаги + system.sale_checkbox_row = None + system.exclude_from_sale = 0 + system.sale_dict["detail"][4] = 0 + +- logger.debug(f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") ++ logger.debug( ++ f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" ++ ) + elif current_row < system.sale_checkbox_row: + # Удаляется строка выше исключенного +- logger.debug(f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}") +- logger.debug(f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}") ++ logger.debug( ++ f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}" ++ ) ++ logger.debug( ++ f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}" ++ ) + + # Уменьшить индекс + system.sale_checkbox_row -= 1 + +- logger.debug(f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}") ++ logger.debug( ++ f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}" ++ ) + + # Удалить строку + self.ui.tableWidget_2.removeRow(current_row) +@@ -775,7 +792,7 @@ class SaleForm(QDialog): + query: str = "%" + self.ui.lineEdit.text() + "%" + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client).filter(Client.last_name.ilike(query)).all() + ) + for client in search: +@@ -791,12 +808,12 @@ class SaleForm(QDialog): + elif index == 0: + # Поиск по фамилии и имени + self.ui.tableWidget.setRowCount(0) +- search: list[Type[Client]] = self.ui.lineEdit.text().title() ++ search: list[type[Client]] = self.ui.lineEdit.text().title() + # Разбиваем поисковую фразу на две + lst: Any = search.split() + if len(lst) == 2: + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter( + and_( +@@ -826,7 +843,7 @@ class SaleForm(QDialog): + # Поиск инвалидов + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter(Client.privilege.ilike("%" + "и")) + .all() +@@ -845,7 +862,7 @@ class SaleForm(QDialog): + # Поиск многодетных + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter(Client.privilege.ilike("%" + "м")) + .all() +@@ -897,11 +914,11 @@ class SaleForm(QDialog): + self.ui.tableWidget_3.setColumnWidth(3, 7) + # Если продажа новая - обновляем статус + system.sale_status = 0 +- logger.warning(f"Статус продажи - новая") ++ logger.warning("Статус продажи - новая") + row_number: int = self.ui.tableWidget.currentRow() + res: str = self.ui.tableWidget.item(row_number, 5).text() + with Session(system.engine) as session: +- search_client: Type[Client] = ( ++ search_client: type[Client] = ( + session.query(Client).filter_by(id=res).first() + ) + # Вычисляем возраст клиента +@@ -1141,12 +1158,16 @@ class SaleForm(QDialog): + + # Рассчитываем цену со скидкой + if adult_with_discount_count > 0: +- price_adult_with_discount = int(system.sale_dict["price_adult"] * (1 - system.sale_discount / 100)) ++ price_adult_with_discount = int( ++ system.sale_dict["price_adult"] * (1 - system.sale_discount / 100) ++ ) + else: + price_adult_with_discount = 0 + + if child_with_discount_count > 0: +- price_child_with_discount = int(system.sale_dict["price_child"] * (1 - system.sale_discount / 100)) ++ price_child_with_discount = int( ++ system.sale_dict["price_child"] * (1 - system.sale_discount / 100) ++ ) + else: + price_child_with_discount = 0 + +@@ -1166,8 +1187,12 @@ class SaleForm(QDialog): + ) + + logger.debug(f"detail ИТОГО: {system.sale_dict['detail']}") +- logger.debug(f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}") +- logger.debug(f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}") ++ logger.debug( ++ f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}" ++ ) ++ logger.debug( ++ f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}" ++ ) + + # Считаем без исключённых взрослых + adult_count_in_sale = 0 +@@ -1186,7 +1211,9 @@ class SaleForm(QDialog): + + # Синхронизируем + system.sale_dict["kol_adult"] = adult_count_in_sale +- logger.debug(f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}") ++ logger.debug( ++ f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}" ++ ) + + # Считаем итог + itog: int = calculate_itog() +@@ -1225,7 +1252,7 @@ class SaleForm(QDialog): + system.sale_tickets = tickets + + # Проверяем есть ли в продаже взрослый +- if system.sale_dict['kol_adult'] >= 1 or system.exclude_from_sale == 1: ++ if system.sale_dict["kol_adult"] >= 1 or system.exclude_from_sale == 1: + self.ui.pushButton_5.setEnabled(True) + else: + self.ui.pushButton_5.setEnabled(False) +@@ -1244,15 +1271,18 @@ class SaleForm(QDialog): + # Если есть исключённый + if row == system.sale_checkbox_row: + checkbox.setEnabled(True) +- logger.debug(f"Checkbox row={row} enabled (исключенный)") ++ logger.debug( ++ f"Checkbox row={row} enabled (исключенный)" ++ ) + else: + checkbox.setEnabled(False) + logger.debug(f"Checkbox row={row} disabled") + else: + # Нет исключённых - все доступны + checkbox.setEnabled(True) +- logger.debug(f"Checkbox row={row} enabled (нет исключенных)") +- ++ logger.debug( ++ f"Checkbox row={row} enabled (нет исключенных)" ++ ) + + def apply_discounts(self, row: int, price: int, type_ticket: str) -> None: + """ +@@ -1288,7 +1318,9 @@ class SaleForm(QDialog): + if type_ticket == "взрослый" and system.exclude_from_sale == 1: + checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) + if checkbox.isChecked() and row == system.sale_checkbox_row: +- logger.info(f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок") ++ logger.info( ++ f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок" ++ ) + return # НЕ применяем скидки из checkBox_2! + + # Иначе проверяем активен ли checkbox со скидкой и размер > 0 +@@ -1299,9 +1331,11 @@ class SaleForm(QDialog): + # Используем другое имя переменной для цены по акции + promo_price = calculate_discount(price, system.sale_discount) + # Если checkbox в акт - применяем к этой строке скидку +- if (self.ui.tableWidget_2.cellWidget(row, 7) +- .findChild(QCheckBox) +- .isChecked()): # Если False → применить скидку ++ if ( ++ self.ui.tableWidget_2.cellWidget(row, 7) ++ .findChild(QCheckBox) ++ .isChecked() ++ ): # Если False → применить скидку + # Применяем скидку ТОЛЬКО если checkbox НЕ активен + if type_ticket == "взрослый": + system.count_number_of_visitors["kol_sale_adult"] += 1 +@@ -1481,7 +1515,7 @@ class SaleForm(QDialog): + self.ui.checkBox_2.setChecked(True) + self.ui.comboBox_2.setCurrentIndex(10) + # Добавляем информацию о скидке +- system.sale_dict['detail'][4] = 50 ++ system.sale_dict["detail"][4] = 50 + # Разрешаем выбор времени посещения + self.ui.comboBox.setEnabled(True) + +@@ -1544,7 +1578,9 @@ class SaleForm(QDialog): + None + """ + logger.info("Запуск функции adult_exclusion") +- logger.debug(f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") ++ logger.debug( ++ f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" ++ ) + + # Получаем checkbox для проверки + checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) +@@ -1577,14 +1613,16 @@ class SaleForm(QDialog): + + # Проверка - единственный взрослый? + if adult_count == 1 and child_count == 0: +- logger.warning("Невозможно исключить единственного взрослого в продаже!") ++ logger.warning( ++ "Невозможно исключить единственного взрослого в продаже!" ++ ) + + QMessageBox.warning( + self, + "Невозможно исключить взрослого", + "В продаже только один взрослый!\n" + "Нельзя исключить единственного взрослого из продажи.\n" +- "Добавьте ребенка или других взрослых." ++ "Добавьте ребенка или других взрослых.", + ) + + checkbox.blockSignals(True) +@@ -1596,24 +1634,30 @@ class SaleForm(QDialog): + + # Первое исключение взрослого + logger.info("Исключаем взрослого из продажи") +- system.sale_dict["detail"][4] = system.sale_discount if system.sale_discount else 0 +- logger.debug(f"Установлен флаг исключения: detail[4] = 100") ++ system.sale_dict["detail"][4] = ( ++ system.sale_discount if system.sale_discount else 0 ++ ) ++ logger.debug("Установлен флаг исключения: detail[4] = 100") + system.sale_checkbox_row = row + system.exclude_from_sale = 1 + self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("н")) +- logger.debug(f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") ++ logger.debug( ++ f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" ++ ) + + elif system.sale_checkbox_row == row: + logger.debug(f"Взрослый row={row} уже исключен, пропускаем") + return + + else: +- logger.warning(f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}") ++ logger.warning( ++ f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}" ++ ) + QMessageBox.warning( + self, + "Невозможно исключить взрослого", + f"В продаже уже есть исключенный взрослый (строка {system.sale_checkbox_row})!\n" +- "Можно исключить только одного взрослого." ++ "Можно исключить только одного взрослого.", + ) + + # СНЯТЬ checkbox +@@ -1629,19 +1673,27 @@ class SaleForm(QDialog): + if row == system.sale_checkbox_row: + # Это действительно исключенный взрослый - возвращаем + logger.info(f"Возвращаем взрослого row={row} в продажу") +- logger.debug(f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}") ++ logger.debug( ++ f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}" ++ ) + self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("-")) + if self.ui.checkBox_2.isChecked(): + system.sale_dict["detail"][4] = system.sale_discount + else: + system.sale_dict["detail"][4] = 0 +- logger.debug(f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}") ++ logger.debug( ++ f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}" ++ ) + system.sale_checkbox_row = None + system.exclude_from_sale = 0 +- logger.debug(f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") ++ logger.debug( ++ f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" ++ ) + else: + # Это другая строка, не исключенный взрослый +- logger.debug(f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем") ++ logger.debug( ++ f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем" ++ ) + + def generate_ticket_list( + self, +@@ -1698,7 +1750,7 @@ class SaleForm(QDialog): + pc_name=system.pc_name, + datetime=dt.datetime.now(), + ) +- logger.debug(f"Объект Sale создан") ++ logger.debug("Объект Sale создан") + + with Session(system.engine) as session: + session.add(add_sale) +@@ -1731,7 +1783,9 @@ class SaleForm(QDialog): + session.add(add_ticket) + + session.commit() +- logger.info(f"Продажа {system.sale_id} и все билеты успешно сохранены") ++ logger.info( ++ f"Продажа {system.sale_id} и все билеты успешно сохранены" ++ ) + + self.close() + else: +@@ -1744,9 +1798,7 @@ class SaleForm(QDialog): + logger.warning(f"Ошибка сохранения продажи: {e}") + system.sale_id = None + windows.info_window( +- "Ошибка базы данных", +- "Не удалось сохранить продажу", +- f"Ошибка: {e}" ++ "Ошибка базы данных", "Не удалось сохранить продажу", f"Ошибка: {e}" + ) + + @logger.catch() +@@ -1775,17 +1827,19 @@ class SaleForm(QDialog): + system, + pq, + Session, +- self # передаем self, чтобы вызвать print_saved_tickets ++ self, # передаем self, чтобы вызвать print_saved_tickets + ) + + # Подключение сигналов + self.worker.progress_updated.connect(self.progress_window.update_status) + self.worker.finished.connect(self.on_transaction_finished) +- self.worker.save_sale_signal.connect(self.save_sale, Qt.BlockingQueuedConnection) ++ self.worker.save_sale_signal.connect( ++ self.save_sale, Qt.BlockingQueuedConnection ++ ) + self.worker.error_signal.connect(self.handle_error) # Обработка ошибок + self.worker.info_signal.connect(self.handle_info) + self.worker.print_ticket_signal.connect(self.print_saved_tickets) +- #self.worker.close_window_signal.connect(self.close) ++ # self.worker.close_window_signal.connect(self.close) + self.worker.close_window_signal.connect(self.progress_window.close) + + # Поток +@@ -1799,7 +1853,6 @@ class SaleForm(QDialog): + + self.thread.start() + +- + def handle_error(self, title: str, text: str, details: str): + """ + Обрабатывает ошибку, закрывая окно прогресса и отображая сообщение с ошибкой. +@@ -1813,7 +1866,7 @@ class SaleForm(QDialog): + None: Функция ничего не возвращает, но закрывает окно прогресса и отображает сообщение об ошибке. + """ + # Закрытие окна прогресса перед отображением ошибки +- if hasattr(self, 'progress_window') and self.progress_window: ++ if hasattr(self, "progress_window") and self.progress_window: + self.progress_window.close() + + # Создание окна с ошибкой +@@ -1848,19 +1901,18 @@ class SaleForm(QDialog): + None: Эта функция ничего не возвращает, но закрывает окно прогресса и логирует завершение транзакции. + """ + logger.info("Запуск функции on_transaction_finished") +- if hasattr(self, '_transaction_finished') and self._transaction_finished: ++ if hasattr(self, "_transaction_finished") and self._transaction_finished: + return + self._transaction_finished = True + logger.info("Завершение транзакции") + # Закрываем окно прогресса +- if hasattr(self, 'progress_window') and self.progress_window: ++ if hasattr(self, "progress_window") and self.progress_window: + self.progress_window.close() + self.progress_window = None + + if self.main_window: + self.main_window.main_button_all_sales() + +- + @logger.catch() + def sale_return(self): + """ +@@ -1893,7 +1945,9 @@ class SaleForm(QDialog): + if sale.status == 5: + logger.debug("Требуется частичный возврат.") + partial_return: int = int(sale.partial_return) +- new_tickets: dict | dict = generating_parts_for_partial_returns(tickets, partial_return) ++ new_tickets: dict = generating_parts_for_partial_returns( ++ tickets, partial_return ++ ) + logger.debug(f"Список билетов: {new_tickets}") + # 1 - карта, 2 - наличные + if sale.payment_type == 1: +@@ -1917,7 +1971,7 @@ class SaleForm(QDialog): + logger.debug("Продажа оплачена. Запускаем возврат") + if payment_type == 102: + state_check = pq.check_open( +- tickets, payment_type, system.user, 2, 1,None ++ tickets, payment_type, system.user, 2, 1, None + ) + elif payment_type == 101: + logger.debug( +@@ -1932,7 +1986,9 @@ class SaleForm(QDialog): + amount=price, + progress_signal=None, + operation_type=2, # возврат +- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) ++ error_callback=lambda title, msg, code: windows.info_window( ++ title, msg, str(code) ++ ), + ) + elif sale.status == 5: + bank, payment = pq.universal_terminal_operation( +@@ -1940,7 +1996,9 @@ class SaleForm(QDialog): + amount=partial_return, + progress_signal=None, + operation_type=2, # возврат +- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) ++ error_callback=lambda title, msg, code: windows.info_window( ++ title, msg, str(code) ++ ), + ) + if bank == 1: + check = pq.read_pinpad_file(remove_newline=False) +@@ -2026,7 +2084,9 @@ class SaleForm(QDialog): + amount=price, + progress_signal=None, + operation_type=2, # возврат +- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) ++ error_callback=lambda title, msg, code: windows.info_window( ++ title, msg, str(code) ++ ), + ) + + if bank == 1: +@@ -2102,7 +2162,9 @@ class SaleForm(QDialog): + amount=price, + progress_signal=None, + operation_type=3, # отмена +- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) ++ error_callback=lambda title, msg, code: windows.info_window( ++ title, msg, str(code) ++ ), + ) + if bank == 1: + check = pq.read_pinpad_file(remove_newline=False) +@@ -2287,7 +2349,7 @@ class SaleForm(QDialog): + Во всех остальных случаях возвращает False. + """ + # Проверяем наличие атрибута и что он не None +- if not hasattr(system, 'ticket_print') or system.ticket_print is None: ++ if not hasattr(system, "ticket_print") or system.ticket_print is None: + return False + + # Приводим к строке и сравниваем с 'on' +@@ -2300,7 +2362,9 @@ class SaleForm(QDialog): + """ + logger.info("Запуск функции print_saved_tickets") + if not self.is_printing_enabled(): +- logger.info("Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)") ++ logger.info( ++ "Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)" ++ ) + return + try: + self.sale_generate_saved_tickets() +@@ -2361,9 +2425,7 @@ class SaleForm(QDialog): + # Оплата только для новых продаж + if system.sale_status != 0: + windows.info_window( +- "Внимание", +- "Оплата картой доступна только для новых продаж.", +- "" ++ "Внимание", "Оплата картой доступна только для новых продаж.", "" + ) + return + +@@ -2371,8 +2433,7 @@ class SaleForm(QDialog): + logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") + # Создаем тестовый слип-чек + slip = "DEBUG_SLIP\nДата: {}\nСумма: {}\nОдобрено".format( +- dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), +- txt ++ dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt + ) + # Сохраняем тестовые данные + self.save_debug_payment(Payment.Card, slip) +@@ -2388,9 +2449,7 @@ class SaleForm(QDialog): + # Оплата только для новых продаж + if system.sale_status != 0: + windows.info_window( +- "Внимание", +- "Оплата наличными доступна только для новых продаж.", +- "" ++ "Внимание", "Оплата наличными доступна только для новых продаж.", "" + ) + return + logger.info("Оплата наличными") +@@ -2402,14 +2461,13 @@ class SaleForm(QDialog): + windows.info_window( + "Внимание", + "Оплата картой доступна только для новых или не фискализированных продаж.", +- "" ++ "", + ) + return + if dev_mode: + logger.info("РЕЖИМ ОТЛАДКИ: Имитация offline оплаты") + slip = "DEBUG_SLIP_OFFLINE\nДата: {}\nСумма: {}\nОффлайн".format( +- dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), +- txt ++ dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt + ) + self.save_debug_payment(Payment.Offline, slip) + self.accept() +@@ -2417,12 +2475,12 @@ class SaleForm(QDialog): + + user_choice = windows.info_dialog_window( + "Подтверждение", +- f"Вы точно хотите фискализировать операцию?\n\n" +- f"Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" +- f"Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" +- f"Операция считается успешной, если в банковском слип-чеке:\n" +- f" - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" +- f" - указано слово 'ОДОБРЕНО'.", ++ "Вы точно хотите фискализировать операцию?\n\n" ++ "Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" ++ "Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" ++ "Операция считается успешной, если в банковском слип-чеке:\n" ++ " - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" ++ " - указано слово 'ОДОБРЕНО'.", + ) + if user_choice == 1: + logger.info("Оплата банковской картой offline") +@@ -2435,10 +2493,12 @@ class SaleForm(QDialog): + def save_debug_payment(payment_type, slip_text): + """Сохранение тестового платежа в режиме отладки""" + try: +- logger.debug(f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}") ++ logger.debug( ++ f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}" ++ ) + # ... код для сохранения в БД ... + except Exception as e: +- logger.error(f"Ошибка сохранения тестового платежа: {str(e)}") ++ logger.error(f"Ошибка сохранения тестового платежа: {e!s}") + + + class PayForm(QDialog): +@@ -2515,7 +2575,9 @@ class SlipForm(QDialog): + None: Функция не возвращает значений, отображает данные слип-чека в форме. + """ + logger.info("Запуск функции get_slip") +- card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data(system.sale_id) ++ card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data( ++ system.sale_id ++ ) + + slip: SlipForm = SlipForm() + slip.ui.label_5.setText(card_tail) +@@ -2663,7 +2725,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + # Поиск по номеру телефона + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client).filter(Client.phone.ilike(user_filter)).all() + ) + for client in search: +@@ -2682,7 +2744,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + # Поиск по фамилии + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter(Client.last_name.ilike(user_filter)) + .all() +@@ -2706,10 +2768,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + # Разбиваем поисковую фразу на две + lst: Any = user_filter.split() + # Инициализация переменной search пустым списком, чтобы избежать ошибки +- search: list[Type[Client]] = [] ++ search: list[type[Client]] = [] + if len(lst) == 2: + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter( + and_( +@@ -2741,7 +2803,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + # Поиск инвалидов + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter(Client.privilege.ilike("%" + "и")) + .all() +@@ -2762,7 +2824,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + # Поиск многодетных + self.ui.tableWidget.setRowCount(0) + with Session(system.engine) as session: +- search: list[Type[Client]] = ( ++ search: list[type[Client]] = ( + session.query(Client) + .filter(Client.privilege.ilike("%" + "м")) + .all() +@@ -2804,7 +2866,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + logger.error("Ячейка пуста или не существует.") + return + with Session(system.engine) as session: +- search_client: Type[Client] = ( ++ search_client: type[Client] = ( + session.query(Client).filter_by(id=client_id).first() + ) + if search_client is None: +@@ -2834,7 +2896,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + client.show() + # Сохраняем параметры данных об уже существующем клиенте + system.client_update = 1 +- logger.info(f"Обновляем информацию о клиенте") ++ logger.info("Обновляем информацию о клиенте") + client.exec_() + + def main_filter_clear(self) -> None: +@@ -2972,7 +3034,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + sale.ui.pushButton_7.setEnabled(False) + sale.ui.pushButton_8.setEnabled(False) + # Если продажа требует повторный возврат по банковскому терминалу +- elif sale_status == 3: ++ elif sale_status == 3 or sale_status == 5: + # Кнопка сохранить + sale.ui.pushButton_3.setEnabled(False) + # Кнопка оплатить +@@ -2987,37 +3049,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + sale.ui.pushButton_7.setEnabled(False) + sale.ui.pushButton_8.setEnabled(False) + # Если продажа требует частичный возврат +- elif sale_status == 5: +- # Кнопка сохранить +- sale.ui.pushButton_3.setEnabled(False) +- # Кнопка оплатить +- sale.ui.pushButton_5.setEnabled(False) +- # Кнопка обновить +- sale.ui.pushButton_10.setEnabled(False) +- # Кнопка возврат +- sale.ui.pushButton_6.setEnabled(True) +- # Кнопка отмены платежа по банковской карте +- sale.ui.pushButton_14.setEnabled(False) +- # Кнопки просмотра и печати билетов +- sale.ui.pushButton_7.setEnabled(False) +- sale.ui.pushButton_8.setEnabled(False) +- # Если продажа требует частичный возврат +- elif sale_status == 7: +- # Кнопка сохранить +- sale.ui.pushButton_3.setEnabled(False) +- # Кнопка оплатить +- sale.ui.pushButton_5.setEnabled(False) +- # Кнопка обновить +- sale.ui.pushButton_10.setEnabled(False) +- # Кнопка возврат +- sale.ui.pushButton_6.setEnabled(False) +- # Кнопка отмены платежа по банковской карте +- sale.ui.pushButton_14.setEnabled(False) +- # Кнопки просмотра и печати билетов +- sale.ui.pushButton_7.setEnabled(False) +- sale.ui.pushButton_8.setEnabled(False) +- # Если продажа отменена +- elif sale_status == 8: ++ elif sale_status == 7 or sale_status == 8: + # Кнопка сохранить + sale.ui.pushButton_3.setEnabled(False) + # Кнопка оплатить +@@ -3159,16 +3191,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + else: + # Определение периода для 1, 3 и 7 дней без учета времени + if self.ui.radioButton.isChecked(): +- filter_start = dt.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) +- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) ++ filter_start = dt.datetime.today().replace( ++ hour=0, minute=0, second=0, microsecond=0 ++ ) ++ filter_end = dt.datetime.today().replace( ++ hour=23, minute=59, second=59, microsecond=999999 ++ ) + elif self.ui.radioButton_2.isChecked(): +- filter_start = (dt.datetime.today() - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0) +- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) ++ filter_start = (dt.datetime.today() - timedelta(days=3)).replace( ++ hour=0, minute=0, second=0, microsecond=0 ++ ) ++ filter_end = dt.datetime.today().replace( ++ hour=23, minute=59, second=59, microsecond=999999 ++ ) + elif self.ui.radioButton_3.isChecked(): +- filter_start = (dt.datetime.today() - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0) +- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) ++ filter_start = (dt.datetime.today() - timedelta(days=7)).replace( ++ hour=0, minute=0, second=0, microsecond=0 ++ ) ++ filter_end = dt.datetime.today().replace( ++ hour=23, minute=59, second=59, microsecond=999999 ++ ) + # Определяем, какие статусы фильтровать +- sale_status_filter = [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] ++ sale_status_filter = ( ++ [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] ++ ) + + # Генерация запроса + with Session(system.engine) as session: +@@ -3314,7 +3360,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + system.sale_dict.update(sale_initial_values) + # сбрасываем id и статус продажи + system.sale_id = None +- system.sale_status = 0 # Т.е. новая продажа ++ system.sale_status = 0 # Т.е. новая продажа + system.sale_tickets = None + sale.exec_() + +@@ -3389,30 +3435,36 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + logger.info("Запуск функции fetch_stat_data") + with Session(system.engine) as session: + sales = session.execute( +- select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) +- .where(and_(Sale.status == "1", Sale.datetime.between(dt1, dt2))) ++ select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( ++ and_(Sale.status == "1", Sale.datetime.between(dt1, dt2)) ++ ) + ).all() + sales_return = session.execute( +- select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) +- .where(and_( +- Sale.datetime_return.between(dt1, dt2), +- Sale.status.in_([2, 3, 4, 5, 6]) +- )) ++ select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( ++ and_( ++ Sale.datetime_return.between(dt1, dt2), ++ Sale.status.in_([2, 3, 4, 5, 6]), ++ ) ++ ) + ).all() + tickets = session.execute( + select( +- Ticket.ticket_type, Ticket.arrival_time, Ticket.description, Sale.status, Sale.id, Ticket.price ++ Ticket.ticket_type, ++ Ticket.arrival_time, ++ Ticket.description, ++ Sale.status, ++ Sale.id, ++ Ticket.price, + ).where( + and_( + Sale.id == Ticket.id_sale, + Sale.status == "1", + Ticket.datetime.between(dt1, dt2), +- ) ++ ) + ) + ).all() + return sales, sales_return, tickets + +- + def fill_sales_table(self, data): + """ + Заполнение таблицы продаж данными. +@@ -3431,9 +3483,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + for row, row_data in enumerate(data): + for col, value in enumerate(row_data): + if value is not None: +- self.ui.tableWidget_4.setItem(row, col, QTableWidgetItem(str(value))) +- +- ++ self.ui.tableWidget_4.setItem( ++ row, col, QTableWidgetItem(str(value)) ++ ) + + def fill_ticket_table(self, data): + """ +@@ -3506,7 +3558,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + if os.path.exists(path): + os.remove(path) + # Проверяем, есть ли данные о продажах в system.sales_data_summary +- if not hasattr(system, 'sales_data_summary') or not system.sales_data_summary: ++ if not hasattr(system, "sales_data_summary") or not system.sales_data_summary: + logger.error("Нет данных о продажах для отчета кассира") + return + # Получаем имя текущего компьютера +@@ -3529,14 +3581,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): + str(total_sales_card), + str(total_sales_cash), + str(total_returns_card), +- str(total_returns_cash) ++ str(total_returns_cash), + ] + + # Получаем даты для отчета + dt1, dt2 = self.get_date_range_from_ui() + + # Получаем информацию о кассире +- kassir = system.user # Предполагаем, что данные о кассире хранятся в system.user ++ kassir = ( ++ system.user ++ ) # Предполагаем, что данные о кассире хранятся в system.user + + # Вызываем функцию для формирования отчета + otchet.otchet_kassira(values, dt1, dt2, kassir) +diff --git a/modules/config.py b/modules/config.py +index 3cb0a17..61938a7 100644 +--- a/modules/config.py ++++ b/modules/config.py +@@ -2,11 +2,10 @@ import logging + import os + from configparser import ( + ConfigParser, +- NoSectionError, +- NoOptionError, + MissingSectionHeaderError, ++ NoOptionError, ++ NoSectionError, + ) +-from typing import List, Optional + + logger = logging.getLogger(__name__) + +@@ -18,7 +17,7 @@ class Config: + # Инициализируем список ПК + self.pcs = self._get_pc_list() + +- def _get_pc_list(self) -> List[str]: ++ def _get_pc_list(self) -> list[str]: + """Возвращает список имен ПК из конфигурации""" + pc_count = int(self.get("kol")) + pc_list = [] +@@ -122,7 +121,6 @@ class Config: + ) + config_data[key] = config.get(section, key) + +- + return config_data + except (NoSectionError, NoOptionError) as e: + logger.error(f"Ошибка в конфигурационном файле: {e}") +@@ -136,6 +134,6 @@ class Config: + logger.error(f"Неизвестная ошибка при чтении файла конфигурации: {e}") + raise RuntimeError(f"Неизвестная ошибка: {e}") + +- def get(self, key: str) -> Optional[str]: ++ def get(self, key: str) -> str | None: + value = self.config_data.get(key) + return value +diff --git a/modules/libfptr10.py b/modules/libfptr10.py +index ced442d..b0c9fc1 100644 +--- a/modules/libfptr10.py ++++ b/modules/libfptr10.py +@@ -1,27 +1,25 @@ +-# -*- coding: utf-8 -*- + # AUTO GENERATED FILE + + import ctypes +-import sys +-import json + import datetime ++import json + import os + import platform +-import warnings ++import sys + + if sys.version_info[0] == 3: +- if platform.system() == 'Windows': ++ if platform.system() == "Windows": + from winreg import * + TEXT = str + RANGE = range + else: +- if platform.system() == 'Windows': ++ if platform.system() == "Windows": + from _winreg import * + TEXT = basestring + RANGE = xrange + + +-class IFptr(object): ++class IFptr: + ( + LIBFPTR_PARAM_TEXT, + LIBFPTR_PARAM_TEXT_WRAP, +@@ -1672,231 +1670,294 @@ class IFptr(object): + DEFAULT_BUFF_SIZE = 512 + + CREATE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(ctypes.c_void_p)) +- CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.POINTER(ctypes.c_void_p), +- ctypes.c_wchar_p) ++ CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.POINTER(ctypes.c_void_p), ctypes.c_wchar_p ++ ) + DESTROY_METHOD = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_void_p)) + GET_VERSION_METHOD = ctypes.CFUNCTYPE(ctypes.c_char_p) + +- METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p) +- +- IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p) +- +- GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p) +- GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_int) +- GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_int) +- RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p) +- +- SET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p) +- GET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_int) +- +- SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_wchar_p) +- GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_wchar_p, +- ctypes.c_int) +- +- SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) +- GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) +- +- SET_INT_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_uint) +- GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, +- ctypes.c_void_p, +- ctypes.c_int) +- +- SET_BOOL_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_int) +- GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_int) +- +- SET_DOUBLE_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_double) +- GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, +- ctypes.c_void_p, +- ctypes.c_int) +- +- SET_STRING_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_wchar_p) +- GET_STRING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_wchar_p, ctypes.c_int) +- +- SET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, +- ctypes.c_int, +- ctypes.c_int) +- GET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.POINTER(ctypes.c_int), +- ctypes.POINTER(ctypes.c_int), +- ctypes.POINTER(ctypes.c_int), +- ctypes.POINTER(ctypes.c_int), +- ctypes.POINTER(ctypes.c_int), +- ctypes.POINTER(ctypes.c_int)) +- IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_int) +- +- LOG_WRITE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p, +- ctypes.c_int, +- ctypes.c_wchar_p) +- +- SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_int, +- ctypes.c_void_p) +- +- CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, +- ctypes.c_void_p, +- ctypes.c_wchar_p) ++ METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) ++ ++ IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) ++ ++ GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) ++ GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int ++ ) ++ GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int ++ ) ++ RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, ctypes.c_void_p) ++ ++ SET_SETTINGS_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p ++ ) ++ GET_SETTINGS_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int ++ ) ++ ++ SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( ++ None, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p ++ ) ++ GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int ++ ) ++ ++ SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( ++ None, ++ ctypes.c_void_p, ++ ctypes.c_int, ++ ctypes.POINTER(ctypes.c_ubyte), ++ ctypes.c_int, ++ ) ++ GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ++ ctypes.c_void_p, ++ ctypes.c_int, ++ ctypes.POINTER(ctypes.c_ubyte), ++ ctypes.c_int, ++ ) ++ ++ SET_INT_METHOD = ctypes.CFUNCTYPE( ++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_uint ++ ) ++ GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, ctypes.c_void_p, ctypes.c_int) ++ ++ SET_BOOL_METHOD = ctypes.CFUNCTYPE( ++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_int ++ ) ++ GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_int) ++ ++ SET_DOUBLE_METHOD = ctypes.CFUNCTYPE( ++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_double ++ ) ++ GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_void_p, ctypes.c_int) ++ ++ SET_STRING_METHOD = ctypes.CFUNCTYPE( ++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p ++ ) ++ GET_STRING_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_int ++ ) ++ ++ SET_DATETIME_METHOD = ctypes.CFUNCTYPE( ++ None, ++ ctypes.c_void_p, ++ ctypes.c_int, ++ ctypes.c_int, ++ ctypes.c_int, ++ ctypes.c_int, ++ ctypes.c_int, ++ ctypes.c_int, ++ ctypes.c_int, ++ ) ++ GET_DATETIME_METHOD = ctypes.CFUNCTYPE( ++ None, ++ ctypes.c_void_p, ++ ctypes.c_int, ++ ctypes.POINTER(ctypes.c_int), ++ ctypes.POINTER(ctypes.c_int), ++ ctypes.POINTER(ctypes.c_int), ++ ctypes.POINTER(ctypes.c_int), ++ ctypes.POINTER(ctypes.c_int), ++ ctypes.POINTER(ctypes.c_int), ++ ) ++ IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int ++ ) ++ ++ LOG_WRITE_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int, ctypes.c_wchar_p ++ ) ++ ++ SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p ++ ) ++ ++ CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE( ++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p ++ ) + + def __init__(self, lib_path="", fptr_id=""): + assert sys.version_info >= (2, 6) + self.lib_path = lib_path + + try: +- if platform.system() == 'Windows': ++ if platform.system() == "Windows": + if len(self.lib_path) == 0: +- rk = OpenKey(HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT") ++ rk = OpenKey( ++ HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT" ++ ) + rv = QueryValueEx(rk, "INSTALL_DIR")[0] +- self.lib_path = os.path.join(rv, 'bin', 'fptr10.dll') ++ self.lib_path = os.path.join(rv, "bin", "fptr10.dll") + else: +- if not self.lib_path.endswith('fptr10.dll'): +- self.lib_path = os.path.join(self.lib_path, 'fptr10.dll') ++ if not self.lib_path.endswith("fptr10.dll"): ++ self.lib_path = os.path.join(self.lib_path, "fptr10.dll") + try: + self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) + except OSError: +- ctypes.CDLL(os.path.join(os.path.dirname(self.lib_path), 'msvcp140.dll'), mode=ctypes.RTLD_LOCAL) ++ ctypes.CDLL( ++ os.path.join(os.path.dirname(self.lib_path), "msvcp140.dll"), ++ mode=ctypes.RTLD_LOCAL, ++ ) + self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) + +- elif platform.system() == 'Darwin': +- if not self.lib_path.endswith('fptr10.framework/fptr10'): +- self.lib_path = os.path.join(self.lib_path, 'fptr10.framework/fptr10') ++ elif platform.system() == "Darwin": ++ if not self.lib_path.endswith("fptr10.framework/fptr10"): ++ self.lib_path = os.path.join( ++ self.lib_path, "fptr10.framework/fptr10" ++ ) + try: + self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) + except OSError: + self.lib_path = lib_path +- if not self.lib_path.endswith('libfptr10.dylib'): +- self.lib_path = os.path.join(self.lib_path, 'libfptr10.dylib') ++ if not self.lib_path.endswith("libfptr10.dylib"): ++ self.lib_path = os.path.join(self.lib_path, "libfptr10.dylib") + self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) + + else: +- if not self.lib_path.endswith('libfptr10.so'): +- self.lib_path = os.path.join(self.lib_path, 'libfptr10.so') ++ if not self.lib_path.endswith("libfptr10.so"): ++ self.lib_path = os.path.join(self.lib_path, "libfptr10.so") + self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) + except OSError: + raise Exception( +- 'Driver library not found in {}'.format( +- '\"' + +- os.path.dirname( +- self.lib_path) + +- '\"' if len(lib_path) != 0 else 'search folders')) ++ "Driver library not found in {}".format( ++ '"' + os.path.dirname(self.lib_path) + '"' ++ if len(lib_path) != 0 ++ else "search folders" ++ ) ++ ) + + self.interface = ctypes.c_void_p(0) + if fptr_id: +- _create = self.CREATE_WITH_ID_METHOD(('libfptr_create_with_id', self.library)) +- create_r = _create(ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id)) ++ _create = self.CREATE_WITH_ID_METHOD( ++ ("libfptr_create_with_id", self.library) ++ ) ++ create_r = _create( ++ ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id) ++ ) + else: +- _create = self.CREATE_METHOD(('libfptr_create', self.library)) ++ _create = self.CREATE_METHOD(("libfptr_create", self.library)) + create_r = _create(ctypes.pointer(self.interface)) + if create_r == -2: +- raise AttributeError('Invalid [id] format') ++ raise AttributeError("Invalid [id] format") + elif create_r != 0: +- raise Exception('Can`t create driver handle') +- +- self._setByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_param_bytearray', self.library)) +- self._setUserByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_user_param_bytearray', self.library)) ++ raise Exception("Can`t create driver handle") ++ ++ self._setByteArray = self.SET_BYTEARRAY_METHOD( ++ ("libfptr_set_param_bytearray", self.library) ++ ) ++ self._setUserByteArray = self.SET_BYTEARRAY_METHOD( ++ ("libfptr_set_user_param_bytearray", self.library) ++ ) + self._setNonPrintableByteArray = self.SET_BYTEARRAY_METHOD( +- ('libfptr_set_non_printable_param_bytearray', self.library)) +- self._getByteArray = self.GET_BYTEARRAY_METHOD(('libfptr_get_param_bytearray', self.library)) +- +- self._setInt = self.SET_INT_METHOD(('libfptr_set_param_int', self.library)) +- self._setUserInt = self.SET_INT_METHOD(('libfptr_set_user_param_int', self.library)) +- self._setNonPrintableInt = self.SET_INT_METHOD(('libfptr_set_non_printable_param_int', self.library)) +- self._getInt = self.GET_INT_METHOD(('libfptr_get_param_int', self.library)) +- +- self._setBool = self.SET_BOOL_METHOD(('libfptr_set_param_bool', self.library)) +- self._setUserBool = self.SET_BOOL_METHOD(('libfptr_set_user_param_bool', self.library)) +- self._setNonPrintableBool = self.SET_BOOL_METHOD(('libfptr_set_non_printable_param_bool', self.library)) +- self._getBool = self.GET_BOOL_METHOD(('libfptr_get_param_bool', self.library)) +- +- self._setDouble = self.SET_DOUBLE_METHOD(('libfptr_set_param_double', self.library)) +- self._setUserDouble = self.SET_DOUBLE_METHOD(('libfptr_set_user_param_double', self.library)) +- self._setNonPrintableDouble = self.SET_DOUBLE_METHOD(('libfptr_set_non_printable_param_double', self.library)) +- self._getDouble = self.GET_DOUBLE_METHOD(('libfptr_get_param_double', self.library)) +- +- self._setDateTime = self.SET_DATETIME_METHOD(('libfptr_set_param_datetime', self.library)) +- self._setUserDateTime = self.SET_DATETIME_METHOD(('libfptr_set_user_param_datetime', self.library)) ++ ("libfptr_set_non_printable_param_bytearray", self.library) ++ ) ++ self._getByteArray = self.GET_BYTEARRAY_METHOD( ++ ("libfptr_get_param_bytearray", self.library) ++ ) ++ ++ self._setInt = self.SET_INT_METHOD(("libfptr_set_param_int", self.library)) ++ self._setUserInt = self.SET_INT_METHOD( ++ ("libfptr_set_user_param_int", self.library) ++ ) ++ self._setNonPrintableInt = self.SET_INT_METHOD( ++ ("libfptr_set_non_printable_param_int", self.library) ++ ) ++ self._getInt = self.GET_INT_METHOD(("libfptr_get_param_int", self.library)) ++ ++ self._setBool = self.SET_BOOL_METHOD(("libfptr_set_param_bool", self.library)) ++ self._setUserBool = self.SET_BOOL_METHOD( ++ ("libfptr_set_user_param_bool", self.library) ++ ) ++ self._setNonPrintableBool = self.SET_BOOL_METHOD( ++ ("libfptr_set_non_printable_param_bool", self.library) ++ ) ++ self._getBool = self.GET_BOOL_METHOD(("libfptr_get_param_bool", self.library)) ++ ++ self._setDouble = self.SET_DOUBLE_METHOD( ++ ("libfptr_set_param_double", self.library) ++ ) ++ self._setUserDouble = self.SET_DOUBLE_METHOD( ++ ("libfptr_set_user_param_double", self.library) ++ ) ++ self._setNonPrintableDouble = self.SET_DOUBLE_METHOD( ++ ("libfptr_set_non_printable_param_double", self.library) ++ ) ++ self._getDouble = self.GET_DOUBLE_METHOD( ++ ("libfptr_get_param_double", self.library) ++ ) ++ ++ self._setDateTime = self.SET_DATETIME_METHOD( ++ ("libfptr_set_param_datetime", self.library) ++ ) ++ self._setUserDateTime = self.SET_DATETIME_METHOD( ++ ("libfptr_set_user_param_datetime", self.library) ++ ) + self._setNonPrintableDateTime = self.SET_DATETIME_METHOD( +- ('libfptr_set_non_printable_param_datetime', self.library)) +- self._getDateTime = self.GET_DATETIME_METHOD(('libfptr_get_param_datetime', self.library)) +- +- self._setString = self.SET_STRING_METHOD(('libfptr_set_param_str', self.library)) +- self._setUserString = self.SET_STRING_METHOD(('libfptr_set_user_param_str', self.library)) +- self._setNonPrintableString = self.SET_STRING_METHOD(('libfptr_set_non_printable_param_str', self.library)) +- self._getString = self.GET_STRING_METHOD(('libfptr_get_param_str', self.library)) +- +- self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD(('libfptr_is_param_available', self.library)) +- +- self._setSettings = self.SET_SETTINGS_METHOD(('libfptr_set_settings', self.library)) +- self._getSettings = self.GET_SETTINGS_METHOD(('libfptr_get_settings', self.library)) +- +- self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD(('libfptr_get_single_setting', self.library)) +- self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD(('libfptr_set_single_setting', self.library)) +- +- self._getVersion = self.GET_VERSION_METHOD(('libfptr_get_version_string', self.library)) +- self._isOpened = self.IS_OPENED_METHOD(('libfptr_is_opened', self.library)) +- self._errorCode = self.GET_ERROR_CODE_METHOD(('libfptr_error_code', self.library)) +- self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD(('libfptr_error_description', self.library)) +- self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD(('libfptr_error_recommendation', self.library)) +- self._resetError = self.RESET_ERROR_METHOD(('libfptr_reset_error', self.library)) +- self._logWrite = self.LOG_WRITE_METHOD(('libfptr_log_write_ex', self.library)) +- self._showProperties = self.SHOW_PROPERTIES_METHOD(('libfptr_show_properties', self.library)) +- self._changeLabel = self.CHANGE_LABEL_METHOD(('libfptr_change_label', self.library)) ++ ("libfptr_set_non_printable_param_datetime", self.library) ++ ) ++ self._getDateTime = self.GET_DATETIME_METHOD( ++ ("libfptr_get_param_datetime", self.library) ++ ) ++ ++ self._setString = self.SET_STRING_METHOD( ++ ("libfptr_set_param_str", self.library) ++ ) ++ self._setUserString = self.SET_STRING_METHOD( ++ ("libfptr_set_user_param_str", self.library) ++ ) ++ self._setNonPrintableString = self.SET_STRING_METHOD( ++ ("libfptr_set_non_printable_param_str", self.library) ++ ) ++ self._getString = self.GET_STRING_METHOD( ++ ("libfptr_get_param_str", self.library) ++ ) ++ ++ self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD( ++ ("libfptr_is_param_available", self.library) ++ ) ++ ++ self._setSettings = self.SET_SETTINGS_METHOD( ++ ("libfptr_set_settings", self.library) ++ ) ++ self._getSettings = self.GET_SETTINGS_METHOD( ++ ("libfptr_get_settings", self.library) ++ ) ++ ++ self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD( ++ ("libfptr_get_single_setting", self.library) ++ ) ++ self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD( ++ ("libfptr_set_single_setting", self.library) ++ ) ++ ++ self._getVersion = self.GET_VERSION_METHOD( ++ ("libfptr_get_version_string", self.library) ++ ) ++ self._isOpened = self.IS_OPENED_METHOD(("libfptr_is_opened", self.library)) ++ self._errorCode = self.GET_ERROR_CODE_METHOD( ++ ("libfptr_error_code", self.library) ++ ) ++ self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD( ++ ("libfptr_error_description", self.library) ++ ) ++ self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD( ++ ("libfptr_error_recommendation", self.library) ++ ) ++ self._resetError = self.RESET_ERROR_METHOD( ++ ("libfptr_reset_error", self.library) ++ ) ++ self._logWrite = self.LOG_WRITE_METHOD(("libfptr_log_write_ex", self.library)) ++ self._showProperties = self.SHOW_PROPERTIES_METHOD( ++ ("libfptr_show_properties", self.library) ++ ) ++ self._changeLabel = self.CHANGE_LABEL_METHOD( ++ ("libfptr_change_label", self.library) ++ ) + + def __del__(self): +- destroy = self.DESTROY_METHOD(('libfptr_destroy', self.library)) ++ destroy = self.DESTROY_METHOD(("libfptr_destroy", self.library)) + destroy(ctypes.pointer(self.interface)) + + def version(self): +@@ -1957,7 +2018,7 @@ class IFptr(object): + + def setSingleSetting(self, key, value): + if isinstance(value, bool): +- value = '1' if value else '0' ++ value = "1" if value else "0" + elif isinstance(value, int): + value = str(value) + self._setSingleSetting(self.interface, key, value) +@@ -1973,77 +2034,149 @@ class IFptr(object): + def setParam(self, paramId, param): + if isinstance(param, bool): + self._setBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) +- elif isinstance(param, int) or (sys.version_info < (3, 0) and isinstance(param, long)): ++ elif isinstance(param, int) or ( ++ sys.version_info < (3, 0) and isinstance(param, long) ++ ): + if param > 4294967295: +- raise ValueError("Invalid 'param' value {0}".format(param)) ++ raise ValueError(f"Invalid 'param' value {param}") + if param < 0: +- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + else: +- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) ++ self._setInt( ++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) ++ ) + elif isinstance(param, float): +- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + elif isinstance(param, TEXT): +- self._setString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) +- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): +- self._setByteArray(self.interface, ctypes.c_int(paramId), +- (ctypes.c_ubyte * len(param))(*param), len(param)) ++ self._setString( ++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) ++ ) ++ elif ( ++ isinstance(param, list) ++ or isinstance(param, bytearray) ++ or isinstance(param, bytes) ++ ): ++ self._setByteArray( ++ self.interface, ++ ctypes.c_int(paramId), ++ (ctypes.c_ubyte * len(param))(*param), ++ len(param), ++ ) + elif isinstance(param, datetime.datetime): +- self._setDateTime(self.interface, ctypes.c_int(paramId), param.date().year, +- param.date().month, +- param.date().day, +- param.time().hour, param.time().minute, param.time().second) ++ self._setDateTime( ++ self.interface, ++ ctypes.c_int(paramId), ++ param.date().year, ++ param.date().month, ++ param.date().day, ++ param.time().hour, ++ param.time().minute, ++ param.time().second, ++ ) + else: +- raise TypeError("Invalid 'param' type {0}".format(type(param))) ++ raise TypeError(f"Invalid 'param' type {type(param)}") + + def setUserParam(self, paramId, param): + if isinstance(param, bool): +- self._setUserBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) ++ self._setUserBool( ++ self.interface, ctypes.c_int(paramId), ctypes.c_int(param) ++ ) + elif isinstance(param, int): + if param > 4294967295: +- raise ValueError("Invalid 'param' value {0}".format(param)) ++ raise ValueError(f"Invalid 'param' value {param}") + if param < 0: +- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + else: +- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) ++ self._setInt( ++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) ++ ) + elif isinstance(param, float): +- self._setUserDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setUserDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + elif isinstance(param, TEXT): +- self._setUserString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) +- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): +- self._setUserByteArray(self.interface, ctypes.c_int(paramId), +- (ctypes.c_ubyte * len(param))(*param), len(param)) ++ self._setUserString( ++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) ++ ) ++ elif ( ++ isinstance(param, list) ++ or isinstance(param, bytearray) ++ or isinstance(param, bytes) ++ ): ++ self._setUserByteArray( ++ self.interface, ++ ctypes.c_int(paramId), ++ (ctypes.c_ubyte * len(param))(*param), ++ len(param), ++ ) + elif isinstance(param, datetime.datetime): +- self._setUserDateTime(self.interface, ctypes.c_int(paramId), param.date().year, +- param.date().month, +- param.date().day, +- param.time().hour, param.time().minute, param.time().second) ++ self._setUserDateTime( ++ self.interface, ++ ctypes.c_int(paramId), ++ param.date().year, ++ param.date().month, ++ param.date().day, ++ param.time().hour, ++ param.time().minute, ++ param.time().second, ++ ) + else: +- raise TypeError("Invalid 'param' type {0}".format(type(param))) ++ raise TypeError(f"Invalid 'param' type {type(param)}") + + def setNonPrintableParam(self, paramId, param): + if isinstance(param, bool): +- self._setNonPrintableBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) ++ self._setNonPrintableBool( ++ self.interface, ctypes.c_int(paramId), ctypes.c_int(param) ++ ) + elif isinstance(param, int): + if param > 4294967295: +- raise ValueError("Invalid 'param' value {0}".format(param)) ++ raise ValueError(f"Invalid 'param' value {param}") + if param < 0: +- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + else: +- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) ++ self._setInt( ++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) ++ ) + elif isinstance(param, float): +- self._setNonPrintableDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) ++ self._setNonPrintableDouble( ++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) ++ ) + elif isinstance(param, TEXT): +- self._setNonPrintableString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) +- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): +- self._setNonPrintableByteArray(self.interface, ctypes.c_int(paramId), +- (ctypes.c_ubyte * len(param))(*param), len(param)) ++ self._setNonPrintableString( ++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) ++ ) ++ elif ( ++ isinstance(param, list) ++ or isinstance(param, bytearray) ++ or isinstance(param, bytes) ++ ): ++ self._setNonPrintableByteArray( ++ self.interface, ++ ctypes.c_int(paramId), ++ (ctypes.c_ubyte * len(param))(*param), ++ len(param), ++ ) + elif isinstance(param, datetime.datetime): +- self._setNonPrintableDateTime(self.interface, ctypes.c_int(paramId), param.date().year, +- param.date().month, +- param.date().day, +- param.time().hour, param.time().minute, param.time().second) ++ self._setNonPrintableDateTime( ++ self.interface, ++ ctypes.c_int(paramId), ++ param.date().year, ++ param.date().month, ++ param.date().day, ++ param.time().hour, ++ param.time().minute, ++ param.time().second, ++ ) + else: +- raise TypeError("Invalid 'param' type {0}".format(type(param))) ++ raise TypeError(f"Invalid 'param' type {type(param)}") + + def getParamInt(self, paramId): + value = self._getInt(self.interface, ctypes.c_int(paramId)) +@@ -2059,13 +2192,20 @@ class IFptr(object): + + def getParamByteArray(self, paramId): + value = (ctypes.c_ubyte * self.DEFAULT_BUFF_SIZE)() +- size = self._getByteArray(self.interface, ctypes.c_int(paramId), +- ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), +- self.DEFAULT_BUFF_SIZE) ++ size = self._getByteArray( ++ self.interface, ++ ctypes.c_int(paramId), ++ ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), ++ self.DEFAULT_BUFF_SIZE, ++ ) + if size > self.DEFAULT_BUFF_SIZE: + value = (ctypes.c_ubyte * size)() +- size = self._getByteArray(self.interface, ctypes.c_int(paramId), +- ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), size) ++ size = self._getByteArray( ++ self.interface, ++ ctypes.c_int(paramId), ++ ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), ++ size, ++ ) + return value[:size] + + def getParamDateTime(self, paramId): +@@ -2075,16 +2215,25 @@ class IFptr(object): + hour = ctypes.c_int(0) + minute = ctypes.c_int(0) + second = ctypes.c_int(0) +- self._getDateTime(self.interface, ctypes.c_int(paramId), ctypes.pointer(year), +- ctypes.pointer(month), +- ctypes.pointer(day), +- ctypes.pointer(hour), ctypes.pointer(minute), ctypes.pointer(second)) +- return datetime.datetime(year.value, month.value, day.value, hour.value, minute.value, +- second.value) ++ self._getDateTime( ++ self.interface, ++ ctypes.c_int(paramId), ++ ctypes.pointer(year), ++ ctypes.pointer(month), ++ ctypes.pointer(day), ++ ctypes.pointer(hour), ++ ctypes.pointer(minute), ++ ctypes.pointer(second), ++ ) ++ return datetime.datetime( ++ year.value, month.value, day.value, hour.value, minute.value, second.value ++ ) + + def getParamString(self, paramId): + value = ctypes.create_unicode_buffer(self.DEFAULT_BUFF_SIZE) +- size = self._getString(self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE) ++ size = self._getString( ++ self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE ++ ) + if size > self.DEFAULT_BUFF_SIZE: + value = ctypes.create_unicode_buffer(size) + self._getString(self.interface, ctypes.c_int(paramId), value, size) +@@ -2095,485 +2244,495 @@ class IFptr(object): + return value > 0 + + def applySingleSettings(self): +- _method = self.METHOD(('libfptr_apply_single_settings', self.library)) ++ _method = self.METHOD(("libfptr_apply_single_settings", self.library)) + return _method(self.interface) + + def open(self): +- _method = self.METHOD(('libfptr_open', self.library)) ++ _method = self.METHOD(("libfptr_open", self.library)) + return _method(self.interface) + + def close(self): +- _method = self.METHOD(('libfptr_close', self.library)) ++ _method = self.METHOD(("libfptr_close", self.library)) + return _method(self.interface) + + def resetParams(self): +- _method = self.METHOD(('libfptr_reset_params', self.library)) ++ _method = self.METHOD(("libfptr_reset_params", self.library)) + return _method(self.interface) + + def runCommand(self): +- _method = self.METHOD(('libfptr_run_command', self.library)) ++ _method = self.METHOD(("libfptr_run_command", self.library)) + return _method(self.interface) + + def beep(self): +- _method = self.METHOD(('libfptr_beep', self.library)) ++ _method = self.METHOD(("libfptr_beep", self.library)) + return _method(self.interface) + + def openDrawer(self): +- _method = self.METHOD(('libfptr_open_drawer', self.library)) ++ _method = self.METHOD(("libfptr_open_drawer", self.library)) + return _method(self.interface) + + def cut(self): +- _method = self.METHOD(('libfptr_cut', self.library)) ++ _method = self.METHOD(("libfptr_cut", self.library)) + return _method(self.interface) + + def devicePoweroff(self): +- _method = self.METHOD(('libfptr_device_poweroff', self.library)) ++ _method = self.METHOD(("libfptr_device_poweroff", self.library)) + return _method(self.interface) + + def deviceReboot(self): +- _method = self.METHOD(('libfptr_device_reboot', self.library)) ++ _method = self.METHOD(("libfptr_device_reboot", self.library)) + return _method(self.interface) + + def openShift(self): +- _method = self.METHOD(('libfptr_open_shift', self.library)) ++ _method = self.METHOD(("libfptr_open_shift", self.library)) + return _method(self.interface) + + def resetSummary(self): +- _method = self.METHOD(('libfptr_reset_summary', self.library)) ++ _method = self.METHOD(("libfptr_reset_summary", self.library)) + return _method(self.interface) + + def initDevice(self): +- _method = self.METHOD(('libfptr_init_device', self.library)) ++ _method = self.METHOD(("libfptr_init_device", self.library)) + return _method(self.interface) + + def queryData(self): +- _method = self.METHOD(('libfptr_query_data', self.library)) ++ _method = self.METHOD(("libfptr_query_data", self.library)) + return _method(self.interface) + + def cashIncome(self): +- _method = self.METHOD(('libfptr_cash_income', self.library)) ++ _method = self.METHOD(("libfptr_cash_income", self.library)) + return _method(self.interface) + + def cashOutcome(self): +- _method = self.METHOD(('libfptr_cash_outcome', self.library)) ++ _method = self.METHOD(("libfptr_cash_outcome", self.library)) + return _method(self.interface) + + def openReceipt(self): +- _method = self.METHOD(('libfptr_open_receipt', self.library)) ++ _method = self.METHOD(("libfptr_open_receipt", self.library)) + return _method(self.interface) + + def cancelReceipt(self): +- _method = self.METHOD(('libfptr_cancel_receipt', self.library)) ++ _method = self.METHOD(("libfptr_cancel_receipt", self.library)) + return _method(self.interface) + + def closeReceipt(self): +- _method = self.METHOD(('libfptr_close_receipt', self.library)) ++ _method = self.METHOD(("libfptr_close_receipt", self.library)) + return _method(self.interface) + + def checkDocumentClosed(self): +- _method = self.METHOD(('libfptr_check_document_closed', self.library)) ++ _method = self.METHOD(("libfptr_check_document_closed", self.library)) + return _method(self.interface) + + def receiptTotal(self): +- _method = self.METHOD(('libfptr_receipt_total', self.library)) ++ _method = self.METHOD(("libfptr_receipt_total", self.library)) + return _method(self.interface) + + def receiptTax(self): +- _method = self.METHOD(('libfptr_receipt_tax', self.library)) ++ _method = self.METHOD(("libfptr_receipt_tax", self.library)) + return _method(self.interface) + + def registration(self): +- _method = self.METHOD(('libfptr_registration', self.library)) ++ _method = self.METHOD(("libfptr_registration", self.library)) + return _method(self.interface) + + def payment(self): +- _method = self.METHOD(('libfptr_payment', self.library)) ++ _method = self.METHOD(("libfptr_payment", self.library)) + return _method(self.interface) + + def report(self): +- _method = self.METHOD(('libfptr_report', self.library)) ++ _method = self.METHOD(("libfptr_report", self.library)) + return _method(self.interface) + + def printText(self): +- _method = self.METHOD(('libfptr_print_text', self.library)) ++ _method = self.METHOD(("libfptr_print_text", self.library)) + return _method(self.interface) + + def printCliche(self): +- _method = self.METHOD(('libfptr_print_cliche', self.library)) ++ _method = self.METHOD(("libfptr_print_cliche", self.library)) + return _method(self.interface) + + def beginNonfiscalDocument(self): +- _method = self.METHOD(('libfptr_begin_nonfiscal_document', self.library)) ++ _method = self.METHOD(("libfptr_begin_nonfiscal_document", self.library)) + return _method(self.interface) + + def endNonfiscalDocument(self): +- _method = self.METHOD(('libfptr_end_nonfiscal_document', self.library)) ++ _method = self.METHOD(("libfptr_end_nonfiscal_document", self.library)) + return _method(self.interface) + + def printBarcode(self): +- _method = self.METHOD(('libfptr_print_barcode', self.library)) ++ _method = self.METHOD(("libfptr_print_barcode", self.library)) + return _method(self.interface) + + def printPicture(self): +- _method = self.METHOD(('libfptr_print_picture', self.library)) ++ _method = self.METHOD(("libfptr_print_picture", self.library)) + return _method(self.interface) + + def printPictureByNumber(self): +- _method = self.METHOD(('libfptr_print_picture_by_number', self.library)) ++ _method = self.METHOD(("libfptr_print_picture_by_number", self.library)) + return _method(self.interface) + + def uploadPictureFromFile(self): +- _method = self.METHOD(('libfptr_upload_picture_from_file', self.library)) ++ _method = self.METHOD(("libfptr_upload_picture_from_file", self.library)) + return _method(self.interface) + + def clearPictures(self): +- _method = self.METHOD(('libfptr_clear_pictures', self.library)) ++ _method = self.METHOD(("libfptr_clear_pictures", self.library)) + return _method(self.interface) + + def writeDeviceSettingRaw(self): +- _method = self.METHOD(('libfptr_write_device_setting_raw', self.library)) ++ _method = self.METHOD(("libfptr_write_device_setting_raw", self.library)) + return _method(self.interface) + + def readDeviceSettingRaw(self): +- _method = self.METHOD(('libfptr_read_device_setting_raw', self.library)) ++ _method = self.METHOD(("libfptr_read_device_setting_raw", self.library)) + return _method(self.interface) + + def commitSettings(self): +- _method = self.METHOD(('libfptr_commit_settings', self.library)) ++ _method = self.METHOD(("libfptr_commit_settings", self.library)) + return _method(self.interface) + + def initSettings(self): +- _method = self.METHOD(('libfptr_init_settings', self.library)) ++ _method = self.METHOD(("libfptr_init_settings", self.library)) + return _method(self.interface) + + def resetSettings(self): +- _method = self.METHOD(('libfptr_reset_settings', self.library)) ++ _method = self.METHOD(("libfptr_reset_settings", self.library)) + return _method(self.interface) + + def writeDateTime(self): +- _method = self.METHOD(('libfptr_write_date_time', self.library)) ++ _method = self.METHOD(("libfptr_write_date_time", self.library)) + return _method(self.interface) + + def writeLicense(self): +- _method = self.METHOD(('libfptr_write_license', self.library)) ++ _method = self.METHOD(("libfptr_write_license", self.library)) + return _method(self.interface) + + def fnOperation(self): +- _method = self.METHOD(('libfptr_fn_operation', self.library)) ++ _method = self.METHOD(("libfptr_fn_operation", self.library)) + return _method(self.interface) + + def fnQueryData(self): +- _method = self.METHOD(('libfptr_fn_query_data', self.library)) ++ _method = self.METHOD(("libfptr_fn_query_data", self.library)) + return _method(self.interface) + + def fnWriteAttributes(self): +- _method = self.METHOD(('libfptr_fn_write_attributes', self.library)) ++ _method = self.METHOD(("libfptr_fn_write_attributes", self.library)) + return _method(self.interface) + + def externalDevicePowerOn(self): +- _method = self.METHOD(('libfptr_external_device_power_on', self.library)) ++ _method = self.METHOD(("libfptr_external_device_power_on", self.library)) + return _method(self.interface) + + def externalDevicePowerOff(self): +- _method = self.METHOD(('libfptr_external_device_power_off', self.library)) ++ _method = self.METHOD(("libfptr_external_device_power_off", self.library)) + return _method(self.interface) + + def externalDeviceWriteData(self): +- _method = self.METHOD(('libfptr_external_device_write_data', self.library)) ++ _method = self.METHOD(("libfptr_external_device_write_data", self.library)) + return _method(self.interface) + + def externalDeviceReadData(self): +- _method = self.METHOD(('libfptr_external_device_read_data', self.library)) ++ _method = self.METHOD(("libfptr_external_device_read_data", self.library)) + return _method(self.interface) + + def operatorLogin(self): +- _method = self.METHOD(('libfptr_operator_login', self.library)) ++ _method = self.METHOD(("libfptr_operator_login", self.library)) + return _method(self.interface) + + def processJson(self): +- _method = self.METHOD(('libfptr_process_json', self.library)) ++ _method = self.METHOD(("libfptr_process_json", self.library)) + return _method(self.interface) + + def readDeviceSetting(self): +- _method = self.METHOD(('libfptr_read_device_setting', self.library)) ++ _method = self.METHOD(("libfptr_read_device_setting", self.library)) + return _method(self.interface) + + def writeDeviceSetting(self): +- _method = self.METHOD(('libfptr_write_device_setting', self.library)) ++ _method = self.METHOD(("libfptr_write_device_setting", self.library)) + return _method(self.interface) + + def beginReadRecords(self): +- _method = self.METHOD(('libfptr_begin_read_records', self.library)) ++ _method = self.METHOD(("libfptr_begin_read_records", self.library)) + return _method(self.interface) + + def readNextRecord(self): +- _method = self.METHOD(('libfptr_read_next_record', self.library)) ++ _method = self.METHOD(("libfptr_read_next_record", self.library)) + return _method(self.interface) + + def endReadRecords(self): +- _method = self.METHOD(('libfptr_end_read_records', self.library)) ++ _method = self.METHOD(("libfptr_end_read_records", self.library)) + return _method(self.interface) + + def userMemoryOperation(self): +- _method = self.METHOD(('libfptr_user_memory_operation', self.library)) ++ _method = self.METHOD(("libfptr_user_memory_operation", self.library)) + return _method(self.interface) + + def continuePrint(self): +- _method = self.METHOD(('libfptr_continue_print', self.library)) ++ _method = self.METHOD(("libfptr_continue_print", self.library)) + return _method(self.interface) + + def initMgm(self): +- _method = self.METHOD(('libfptr_init_mgm', self.library)) ++ _method = self.METHOD(("libfptr_init_mgm", self.library)) + return _method(self.interface) + + def utilFormTlv(self): +- _method = self.METHOD(('libfptr_util_form_tlv', self.library)) ++ _method = self.METHOD(("libfptr_util_form_tlv", self.library)) + return _method(self.interface) + + def utilFormNomenclature(self): +- _method = self.METHOD(('libfptr_util_form_nomenclature', self.library)) ++ _method = self.METHOD(("libfptr_util_form_nomenclature", self.library)) + return _method(self.interface) + + def utilMapping(self): +- _method = self.METHOD(('libfptr_util_mapping', self.library)) ++ _method = self.METHOD(("libfptr_util_mapping", self.library)) + return _method(self.interface) + + def readModelFlags(self): +- _method = self.METHOD(('libfptr_read_model_flags', self.library)) ++ _method = self.METHOD(("libfptr_read_model_flags", self.library)) + return _method(self.interface) + + def lineFeed(self): +- _method = self.METHOD(('libfptr_line_feed', self.library)) ++ _method = self.METHOD(("libfptr_line_feed", self.library)) + return _method(self.interface) + + def flashFirmware(self): +- _method = self.METHOD(('libfptr_flash_firmware', self.library)) ++ _method = self.METHOD(("libfptr_flash_firmware", self.library)) + return _method(self.interface) + + def softLockInit(self): +- _method = self.METHOD(('libfptr_soft_lock_init', self.library)) ++ _method = self.METHOD(("libfptr_soft_lock_init", self.library)) + return _method(self.interface) + + def softLockQuerySessionCode(self): +- _method = self.METHOD(('libfptr_soft_lock_query_session_code', self.library)) ++ _method = self.METHOD(("libfptr_soft_lock_query_session_code", self.library)) + return _method(self.interface) + + def softLockValidate(self): +- _method = self.METHOD(('libfptr_soft_lock_validate', self.library)) ++ _method = self.METHOD(("libfptr_soft_lock_validate", self.library)) + return _method(self.interface) + + def utilCalcTax(self): +- _method = self.METHOD(('libfptr_util_calc_tax', self.library)) ++ _method = self.METHOD(("libfptr_util_calc_tax", self.library)) + return _method(self.interface) + + def downloadPicture(self): +- _method = self.METHOD(('libfptr_download_picture', self.library)) ++ _method = self.METHOD(("libfptr_download_picture", self.library)) + return _method(self.interface) + + def bluetoothRemovePairedDevices(self): +- _method = self.METHOD(('libfptr_bluetooth_remove_paired_devices', self.library)) ++ _method = self.METHOD(("libfptr_bluetooth_remove_paired_devices", self.library)) + return _method(self.interface) + + def utilTagInfo(self): +- _method = self.METHOD(('libfptr_util_tag_info', self.library)) ++ _method = self.METHOD(("libfptr_util_tag_info", self.library)) + return _method(self.interface) + + def utilContainerVersions(self): +- _method = self.METHOD(('libfptr_util_container_versions', self.library)) ++ _method = self.METHOD(("libfptr_util_container_versions", self.library)) + return _method(self.interface) + + def activateLicenses(self): +- _method = self.METHOD(('libfptr_activate_licenses', self.library)) ++ _method = self.METHOD(("libfptr_activate_licenses", self.library)) + return _method(self.interface) + + def removeLicenses(self): +- _method = self.METHOD(('libfptr_remove_licenses', self.library)) ++ _method = self.METHOD(("libfptr_remove_licenses", self.library)) + return _method(self.interface) + + def enterKeys(self): +- _method = self.METHOD(('libfptr_enter_keys', self.library)) ++ _method = self.METHOD(("libfptr_enter_keys", self.library)) + return _method(self.interface) + + def validateKeys(self): +- _method = self.METHOD(('libfptr_validate_keys', self.library)) ++ _method = self.METHOD(("libfptr_validate_keys", self.library)) + return _method(self.interface) + + def enterSerialNumber(self): +- _method = self.METHOD(('libfptr_enter_serial_number', self.library)) ++ _method = self.METHOD(("libfptr_enter_serial_number", self.library)) + return _method(self.interface) + + def getSerialNumberRequest(self): +- _method = self.METHOD(('libfptr_get_serial_number_request', self.library)) ++ _method = self.METHOD(("libfptr_get_serial_number_request", self.library)) + return _method(self.interface) + + def uploadPixelBuffer(self): +- _method = self.METHOD(('libfptr_upload_pixel_buffer', self.library)) ++ _method = self.METHOD(("libfptr_upload_pixel_buffer", self.library)) + return _method(self.interface) + + def downloadPixelBuffer(self): +- _method = self.METHOD(('libfptr_download_pixel_buffer', self.library)) ++ _method = self.METHOD(("libfptr_download_pixel_buffer", self.library)) + return _method(self.interface) + + def printPixelBuffer(self): +- _method = self.METHOD(('libfptr_print_pixel_buffer', self.library)) ++ _method = self.METHOD(("libfptr_print_pixel_buffer", self.library)) + return _method(self.interface) + + def utilConvertTagValue(self): +- _method = self.METHOD(('libfptr_util_convert_tag_value', self.library)) ++ _method = self.METHOD(("libfptr_util_convert_tag_value", self.library)) + return _method(self.interface) + + def parseMarkingCode(self): +- _method = self.METHOD(('libfptr_parse_marking_code', self.library)) ++ _method = self.METHOD(("libfptr_parse_marking_code", self.library)) + return _method(self.interface) + + def callScript(self): +- _method = self.METHOD(('libfptr_call_script', self.library)) ++ _method = self.METHOD(("libfptr_call_script", self.library)) + return _method(self.interface) + + def setHeaderLines(self): +- _method = self.METHOD(('libfptr_set_header_lines', self.library)) ++ _method = self.METHOD(("libfptr_set_header_lines", self.library)) + return _method(self.interface) + + def setFooterLines(self): +- _method = self.METHOD(('libfptr_set_footer_lines', self.library)) ++ _method = self.METHOD(("libfptr_set_footer_lines", self.library)) + return _method(self.interface) + + def uploadPictureCliche(self): +- _method = self.METHOD(('libfptr_upload_picture_cliche', self.library)) ++ _method = self.METHOD(("libfptr_upload_picture_cliche", self.library)) + return _method(self.interface) + + def uploadPictureMemory(self): +- _method = self.METHOD(('libfptr_upload_picture_memory', self.library)) ++ _method = self.METHOD(("libfptr_upload_picture_memory", self.library)) + return _method(self.interface) + + def uploadPixelBufferCliche(self): +- _method = self.METHOD(('libfptr_upload_pixel_buffer_cliche', self.library)) ++ _method = self.METHOD(("libfptr_upload_pixel_buffer_cliche", self.library)) + return _method(self.interface) + + def uploadPixelBufferMemory(self): +- _method = self.METHOD(('libfptr_upload_pixel_buffer_memory', self.library)) ++ _method = self.METHOD(("libfptr_upload_pixel_buffer_memory", self.library)) + return _method(self.interface) + + def execDriverScript(self): +- _method = self.METHOD(('libfptr_exec_driver_script', self.library)) ++ _method = self.METHOD(("libfptr_exec_driver_script", self.library)) + return _method(self.interface) + + def uploadDriverScript(self): +- _method = self.METHOD(('libfptr_upload_driver_script', self.library)) ++ _method = self.METHOD(("libfptr_upload_driver_script", self.library)) + return _method(self.interface) + + def execDriverScriptById(self): +- _method = self.METHOD(('libfptr_exec_driver_script_by_id', self.library)) ++ _method = self.METHOD(("libfptr_exec_driver_script_by_id", self.library)) + return _method(self.interface) + + def writeUniversalCountersSettings(self): +- _method = self.METHOD(('libfptr_write_universal_counters_settings', self.library)) ++ _method = self.METHOD( ++ ("libfptr_write_universal_counters_settings", self.library) ++ ) + return _method(self.interface) + + def readUniversalCountersSettings(self): +- _method = self.METHOD(('libfptr_read_universal_counters_settings', self.library)) ++ _method = self.METHOD( ++ ("libfptr_read_universal_counters_settings", self.library) ++ ) + return _method(self.interface) + + def queryUniversalCountersState(self): +- _method = self.METHOD(('libfptr_query_universal_counters_state', self.library)) ++ _method = self.METHOD(("libfptr_query_universal_counters_state", self.library)) + return _method(self.interface) + + def resetUniversalCounters(self): +- _method = self.METHOD(('libfptr_reset_universal_counters', self.library)) ++ _method = self.METHOD(("libfptr_reset_universal_counters", self.library)) + return _method(self.interface) + + def cacheUniversalCounters(self): +- _method = self.METHOD(('libfptr_cache_universal_counters', self.library)) ++ _method = self.METHOD(("libfptr_cache_universal_counters", self.library)) + return _method(self.interface) + + def readUniversalCounterSum(self): +- _method = self.METHOD(('libfptr_read_universal_counter_sum', self.library)) ++ _method = self.METHOD(("libfptr_read_universal_counter_sum", self.library)) + return _method(self.interface) + + def readUniversalCounterQuantity(self): +- _method = self.METHOD(('libfptr_read_universal_counter_quantity', self.library)) ++ _method = self.METHOD(("libfptr_read_universal_counter_quantity", self.library)) + return _method(self.interface) + + def clearUniversalCountersCache(self): +- _method = self.METHOD(('libfptr_clear_universal_counters_cache', self.library)) ++ _method = self.METHOD(("libfptr_clear_universal_counters_cache", self.library)) + return _method(self.interface) + + def disableOfdChannel(self): +- _method = self.METHOD(('libfptr_disable_ofd_channel', self.library)) ++ _method = self.METHOD(("libfptr_disable_ofd_channel", self.library)) + return _method(self.interface) + + def enableOfdChannel(self): +- _method = self.METHOD(('libfptr_enable_ofd_channel', self.library)) ++ _method = self.METHOD(("libfptr_enable_ofd_channel", self.library)) + return _method(self.interface) + + def validateJson(self): +- _method = self.METHOD(('libfptr_validate_json', self.library)) ++ _method = self.METHOD(("libfptr_validate_json", self.library)) + return _method(self.interface) + + def reflectionCall(self): +- _method = self.METHOD(('libfptr_reflection_call', self.library)) ++ _method = self.METHOD(("libfptr_reflection_call", self.library)) + return _method(self.interface) + + def getRemoteServerInfo(self): +- _method = self.METHOD(('libfptr_get_remote_server_info', self.library)) ++ _method = self.METHOD(("libfptr_get_remote_server_info", self.library)) + return _method(self.interface) + + def beginMarkingCodeValidation(self): +- _method = self.METHOD(('libfptr_begin_marking_code_validation', self.library)) ++ _method = self.METHOD(("libfptr_begin_marking_code_validation", self.library)) + return _method(self.interface) + + def cancelMarkingCodeValidation(self): +- _method = self.METHOD(('libfptr_cancel_marking_code_validation', self.library)) ++ _method = self.METHOD(("libfptr_cancel_marking_code_validation", self.library)) + return _method(self.interface) + + def getMarkingCodeValidationStatus(self): +- _method = self.METHOD(('libfptr_get_marking_code_validation_status', self.library)) ++ _method = self.METHOD( ++ ("libfptr_get_marking_code_validation_status", self.library) ++ ) + return _method(self.interface) + + def acceptMarkingCode(self): +- _method = self.METHOD(('libfptr_accept_marking_code', self.library)) ++ _method = self.METHOD(("libfptr_accept_marking_code", self.library)) + return _method(self.interface) + + def declineMarkingCode(self): +- _method = self.METHOD(('libfptr_decline_marking_code', self.library)) ++ _method = self.METHOD(("libfptr_decline_marking_code", self.library)) + return _method(self.interface) + + def updateFnmKeys(self): +- _method = self.METHOD(('libfptr_update_fnm_keys', self.library)) ++ _method = self.METHOD(("libfptr_update_fnm_keys", self.library)) + return _method(self.interface) + + def writeSalesNotice(self): +- _method = self.METHOD(('libfptr_write_sales_notice', self.library)) ++ _method = self.METHOD(("libfptr_write_sales_notice", self.library)) + return _method(self.interface) + + def checkMarkingCodeValidationsReady(self): +- _method = self.METHOD(('libfptr_check_marking_code_validations_ready', self.library)) ++ _method = self.METHOD( ++ ("libfptr_check_marking_code_validations_ready", self.library) ++ ) + return _method(self.interface) + + def clearMarkingCodeValidationResult(self): +- _method = self.METHOD(('libfptr_clear_marking_code_validation_result', self.library)) ++ _method = self.METHOD( ++ ("libfptr_clear_marking_code_validation_result", self.library) ++ ) + return _method(self.interface) + + def pingMarkingServer(self): +- _method = self.METHOD(('libfptr_ping_marking_server', self.library)) ++ _method = self.METHOD(("libfptr_ping_marking_server", self.library)) + return _method(self.interface) + + def getMarkingServerStatus(self): +- _method = self.METHOD(('libfptr_get_marking_server_status', self.library)) ++ _method = self.METHOD(("libfptr_get_marking_server_status", self.library)) + return _method(self.interface) + + def isDriverLocked(self): +- _method = self.METHOD(('libfptr_is_driver_locked', self.library)) ++ _method = self.METHOD(("libfptr_is_driver_locked", self.library)) + return _method(self.interface) + + def getLastDocumentJournal(self): +- _method = self.METHOD(('libfptr_get_last_document_journal', self.library)) ++ _method = self.METHOD(("libfptr_get_last_document_journal", self.library)) + return _method(self.interface) + + def findDocumentInJournal(self): +- _method = self.METHOD(('libfptr_find_document_in_journal', self.library)) ++ _method = self.METHOD(("libfptr_find_document_in_journal", self.library)) + return _method(self.interface) + + def runFnCommand(self): +- _method = self.METHOD(('libfptr_run_fn_command', self.library)) ++ _method = self.METHOD(("libfptr_run_fn_command", self.library)) + return _method(self.interface) +diff --git a/modules/otchet.py b/modules/otchet.py +index 965f03a..ba46312 100644 +--- a/modules/otchet.py ++++ b/modules/otchet.py +@@ -1,9 +1,9 @@ + from datetime import datetime +-from typing import Any, Dict, List, Tuple, Union ++from typing import Any + + from reportlab.lib import colors + from reportlab.lib.pagesizes import A4, landscape, letter +-from reportlab.lib.units import mm, inch ++from reportlab.lib.units import inch, mm + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + from reportlab.pdfgen import canvas +@@ -17,7 +17,7 @@ system = System() + config = Config() + + +-def get_ticket_type(age:str) -> str: ++def get_ticket_type(age: str) -> str: + """ + Определяет тип билета на основе возраста клиента. + +@@ -45,7 +45,8 @@ def get_ticket_type(age:str) -> str: + else: + return "взрослый" + +-def generate_saved_tickets(values: List[tuple]) -> None: ++ ++def generate_saved_tickets(values: list[tuple]) -> None: + """ + Генерирует PDF-файл с билетами для клиентов на основе переданных данных. + +@@ -72,32 +73,79 @@ def generate_saved_tickets(values: List[tuple]) -> None: + pdfmetrics.registerFont(TTFont("DejaVuSerif", "files/DejaVuSerif.ttf")) + c = canvas.Canvas(path, pagesize=(landscape(letter))) + for client in values: +- (last_name, name, ticket_flag, price, category, client_id, age, hours, talents, date_time) = client ++ ( ++ last_name, ++ name, ++ ticket_flag, ++ price, ++ category, ++ client_id, ++ age, ++ hours, ++ talents, ++ date_time, ++ ) = client + ticket_type = get_ticket_type(age) + if ticket_type == "бесплатный" or category == "н": + continue # Пропускаем бесплатных и "не идущих" + c.setFont("DejaVuSerif", 12) + # x - расстояние мм от левого края страницы + # y - расстояние мм от нижнего края страницы +- c.drawString(coordinates["name"]["x"] * mm,coordinates["name"]["y"] * mm, name) +- c.drawString(coordinates["surname"]["x"] * mm, coordinates["surname"]["y"] * mm, last_name) +- c.drawString(coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age)) ++ c.drawString(coordinates["name"]["x"] * mm, coordinates["name"]["y"] * mm, name) ++ c.drawString( ++ coordinates["surname"]["x"] * mm, ++ coordinates["surname"]["y"] * mm, ++ last_name, ++ ) ++ c.drawString( ++ coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age) ++ ) + duration_text = f"{hours} ч. пребывания" + if hours == 3: + duration_text += "-весь день" +- c.drawString(coordinates["duration"]["x"] * mm, coordinates["duration"]["y"] * mm, duration_text) ++ c.drawString( ++ coordinates["duration"]["x"] * mm, ++ coordinates["duration"]["y"] * mm, ++ duration_text, ++ ) + if isinstance(date_time, str): + date_time = datetime.strptime(date_time, "%Y-%m-%d") +- c.drawString(coordinates["date"]["x"] * mm, coordinates["date"]["y"] * mm, date_time.strftime('%d.%m.%Y')) +- c.drawString(coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость") +- c.drawString(coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД") +- c.drawString(coordinates["place"]["x"] * mm, coordinates["place"]["y"] * mm, "МАСТЕРСЛАВЛЬ") +- c.drawString(coordinates["price"]["x"] * mm, coordinates["price"]["y"] * mm, f"{price} руб.") +- c.drawString(coordinates["ticket_type"]["x"] * mm, coordinates["ticket_type"]["y"] * mm, ticket_type) +- c.drawString(coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category) ++ c.drawString( ++ coordinates["date"]["x"] * mm, ++ coordinates["date"]["y"] * mm, ++ date_time.strftime("%d.%m.%Y"), ++ ) ++ c.drawString( ++ coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость" ++ ) ++ c.drawString( ++ coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД" ++ ) ++ c.drawString( ++ coordinates["place"]["x"] * mm, ++ coordinates["place"]["y"] * mm, ++ "МАСТЕРСЛАВЛЬ", ++ ) ++ c.drawString( ++ coordinates["price"]["x"] * mm, ++ coordinates["price"]["y"] * mm, ++ f"{price} руб.", ++ ) ++ c.drawString( ++ coordinates["ticket_type"]["x"] * mm, ++ coordinates["ticket_type"]["y"] * mm, ++ ticket_type, ++ ) ++ c.drawString( ++ coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category ++ ) + c.setFont("DejaVuSerif", 24) + talents_str = "0" if ticket_type == "взрослый" else str(talents) +- c.drawString(coordinates["talents"]["x"] * mm, coordinates["talents"]["y"] * mm, talents_str) ++ c.drawString( ++ coordinates["talents"]["x"] * mm, ++ coordinates["talents"]["y"] * mm, ++ talents_str, ++ ) + c.drawImage( + img_file, + coordinates["qr_code"]["x"], +@@ -134,13 +182,30 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: + """ + logger.info("Запуск функции generate_ticket_report_table") + type_ticket = [ +- "Взрослый, 1 ч.", "Взрослый, 2 ч.", "Взрослый, 3 ч.", +- "Детский, 1 ч.", "Детский, 2 ч.", "Детский, 3 ч.", +- "Многодетный взрослый, 1 ч.", "Многодетный взрослый, 2 ч.", "Многодетный взрослый, 3 ч.", +- "Многодетный детский, 1 ч.", "Многодетный детский, 2 ч.", "Многодетный детский, 3 ч.", +- "Инвалид, 3 ч.", "Сопровождающий, 3 ч.", ++ "Взрослый, 1 ч.", ++ "Взрослый, 2 ч.", ++ "Взрослый, 3 ч.", ++ "Детский, 1 ч.", ++ "Детский, 2 ч.", ++ "Детский, 3 ч.", ++ "Многодетный взрослый, 1 ч.", ++ "Многодетный взрослый, 2 ч.", ++ "Многодетный взрослый, 3 ч.", ++ "Многодетный детский, 1 ч.", ++ "Многодетный детский, 2 ч.", ++ "Многодетный детский, 3 ч.", ++ "Инвалид, 3 ч.", ++ "Сопровождающий, 3 ч.", ++ ] ++ data = [ ++ [ ++ "№\n п/п", ++ "Тип\nбилета", ++ "Цена,\n руб.", ++ "Количество,\n шт.", ++ "Стоимость,\n руб.", ++ ] + ] +- data = [["№\n п/п", "Тип\nбилета", "Цена,\n руб.", "Количество,\n шт.", "Стоимость,\n руб."]] + # Счётчики для агрегатов + total = { + "adult": {"count": 0, "sum": 0}, +@@ -182,23 +247,57 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: + + # Агрегированные строки + data += [ +- ["", "Всего взрослых билетов", "", total["adult"]["count"], total["adult"]["sum"]], +- ["", "Всего детских билетов", "", total["child"]["count"], total["child"]["sum"]], +- ["", "Всего многодетных взрослых билетов", "", total["many_adult"]["count"], total["many_adult"]["sum"]], +- ["", "Всего многодетных детских билетов", "", total["many_child"]["count"], total["many_child"]["sum"]], ++ [ ++ "", ++ "Всего взрослых билетов", ++ "", ++ total["adult"]["count"], ++ total["adult"]["sum"], ++ ], ++ [ ++ "", ++ "Всего детских билетов", ++ "", ++ total["child"]["count"], ++ total["child"]["sum"], ++ ], ++ [ ++ "", ++ "Всего многодетных взрослых билетов", ++ "", ++ total["many_adult"]["count"], ++ total["many_adult"]["sum"], ++ ], ++ [ ++ "", ++ "Всего многодетных детских билетов", ++ "", ++ total["many_child"]["count"], ++ total["many_child"]["sum"], ++ ], + ["", "Инвалид, 3 ч.", "", total["disabled"]["count"], "0"], + ["", "Сопровождающий, 3 ч.", "", total["maintainer"]["count"], "0"], +- ["", "Итого билетов", "", +- total["adult"]["count"] + total["child"]["count"] + +- total["many_adult"]["count"] + total["many_child"]["count"] + +- total["disabled"]["count"] + total["maintainer"]["count"], +- total["adult"]["sum"] + total["child"]["sum"] + +- total["many_adult"]["sum"] + total["many_child"]["sum"]], ++ [ ++ "", ++ "Итого билетов", ++ "", ++ total["adult"]["count"] ++ + total["child"]["count"] ++ + total["many_adult"]["count"] ++ + total["many_child"]["count"] ++ + total["disabled"]["count"] ++ + total["maintainer"]["count"], ++ total["adult"]["sum"] ++ + total["child"]["sum"] ++ + total["many_adult"]["sum"] ++ + total["many_child"]["sum"], ++ ], + ] + + return data + +-def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: ++ ++def otchet_administratora(date_1: str, date_2: str, values: dict) -> None: + """ + Функция формирует отчет администратора в формате PDF. + +@@ -253,15 +352,22 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: + table_width = sum(custom_widths) + if table_width > (page_width - left_margin - right_margin): + # Корректировка ширины второго столбца +- remaining_width = page_width - left_margin - right_margin - (sum(custom_widths) - custom_widths[1]) ++ remaining_width = ( ++ page_width ++ - left_margin ++ - right_margin ++ - (sum(custom_widths) - custom_widths[1]) ++ ) + custom_widths[1] = remaining_width + t = Table(data, colWidths=custom_widths, rowHeights=[0.3 * inch] * len(data)) + t.setStyle( +- TableStyle([ +- ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), +- ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), +- ("BOX", (0, 0), (-1, -1), 0.25, colors.black), +- ]) ++ TableStyle( ++ [ ++ ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), ++ ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), ++ ("BOX", (0, 0), (-1, -1), 0.25, colors.black), ++ ] ++ ) + ) + t.wrapOn(c, left_margin, 180 * mm) + t.drawOn(c, left_margin, 60 * mm) +@@ -281,6 +387,7 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: + c.showPage() + c.save() + ++ + def safe_int(value: Any) -> int: + """ + Преобразует значение в целое число, если это возможно. Если значение None, строка 'None', +@@ -295,14 +402,22 @@ def safe_int(value: Any) -> int: + if isinstance(value, (list, dict, set, tuple)): # Проверка на неподобающие типы + return 0 + try: +- if value not in [None, 'None']: +- return int(float(value)) # Преобразование строки с плавающей точкой в целое число ++ if value not in [None, "None"]: ++ return int( ++ float(value) ++ ) # Преобразование строки с плавающей точкой в целое число + else: + return 0 + except (ValueError, TypeError): # Обработка ошибок для некорректных значений + return 0 + +-def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m-%d %H:%M:%S", output_format: str = "%d-%m-%Y") -> Tuple[str, str]: ++ ++def format_date_range( ++ date1_str: str, ++ date2_str: str, ++ input_format: str = "%Y-%m-%d %H:%M:%S", ++ output_format: str = "%d-%m-%Y", ++) -> tuple[str, str]: + """ + Преобразует строки с датами из одного формата в другой и возвращает кортеж из двух отформатированных строк. + +@@ -321,7 +436,8 @@ def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m + dt2 = date_2.strftime(output_format) + return dt1, dt2 + +-def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: ++ ++def otchet_kassira(val: list[int], date1: str, date2: str, kassir: Any) -> None: + """ + Формирует PDF-отчет кассира за указанный период с разбивкой по типам оплат и возвратов. + +@@ -345,7 +461,9 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: + logger.info("Запуск функции otchet_kassira") + # Проверка на пустой список или недостаточное количество элементов + if not val or len(val) < 4: +- logger.error("Некорректные данные для отчета: данные не переданы или их недостаточно.") ++ logger.error( ++ "Некорректные данные для отчета: данные не переданы или их недостаточно." ++ ) + # Завершаем функцию, если данных недостаточно или они пустые + return + path = "./otchet.pdf" +@@ -423,10 +541,21 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: + c.showPage() + c.save() + ++ + def process_sales_and_returns( +- sales: List[Tuple[str, int, Union[int, float]]], +- sales_return: List[Tuple[str, int, Union[int, float], int]] +-) -> List[Tuple[Union[str, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float]]]: ++ sales: list[tuple[str, int, int | float]], ++ sales_return: list[tuple[str, int, int | float, int]], ++) -> list[ ++ tuple[ ++ str | None, ++ int | float | None, ++ int | float | None, ++ int | float | None, ++ int | float | None, ++ int | float | None, ++ int | float, ++ ] ++]: + """ + Обрабатывает продажи и возвраты, агрегируя информацию по каждому кассовому аппарату. + Функция вычисляет суммы по картам и наличным, а также учитывает возвраты по каждому типу: +@@ -462,24 +591,29 @@ def process_sales_and_returns( + logger.info("Запуск функции process_sales_and_returns") + pcs = { + name: { +- "card": 0, "cash": 0, +- "return": 0, "return_again": 0, ++ "card": 0, ++ "cash": 0, ++ "return": 0, ++ "return_again": 0, + "return_partial": 0, +- "return_card": 0, "return_cash": 0, ++ "return_card": 0, ++ "return_cash": 0, + } + for name in system.pcs + } + + type_rm = [1, 2, 3] # 1 - карта, 2 - наличные, 3 - offline +- return_fields: dict[int, str] = {2: "return", 4: "return_again", 6: "return_partial"} ++ return_fields: dict[int, str] = { ++ 2: "return", ++ 4: "return_again", ++ 6: "return_partial", ++ } + + for sale in sales: + pc_name = sale[0] + if pc_name not in pcs: + continue +- if sale[1] == type_rm[0] or sale[1] == type_rm[2]: +- pcs[pc_name]["card"] += sale[2] +- elif sale[1] == type_rm[2]: ++ if sale[1] == type_rm[0] or sale[1] == type_rm[2] or sale[1] == type_rm[2]: + pcs[pc_name]["card"] += sale[2] + else: + pcs[pc_name]["cash"] += sale[2] +@@ -501,33 +635,40 @@ def process_sales_and_returns( + data = [] + for name in system.pcs: + stats = pcs[name] +- data.append(( +- name, +- stats["card"], +- stats["cash"], +- None, +- stats["return_card"], +- stats["return_cash"], +- stats["return_card"] + stats["return_cash"] +- )) ++ data.append( ++ ( ++ name, ++ stats["card"], ++ stats["cash"], ++ None, ++ stats["return_card"], ++ stats["return_cash"], ++ stats["return_card"] + stats["return_cash"], ++ ) ++ ) + + total_card = sum(pc["card"] for pc in pcs.values()) + total_cash = sum(pc["cash"] for pc in pcs.values()) + total_return = sum(pc["return_card"] + pc["return_cash"] for pc in pcs.values()) + +- data.append(( +- "Итого", +- total_card, +- total_cash, +- total_card + total_cash, +- None, +- None, +- total_return +- )) ++ data.append( ++ ( ++ "Итого", ++ total_card, ++ total_cash, ++ total_card + total_cash, ++ None, ++ None, ++ total_return, ++ ) ++ ) + system.sales_data_summary = data + return data + +-def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int]]) -> List[Tuple[str, Dict[str, int]]]: ++ ++def process_ticket_stats( ++ tickets: list[tuple[int, int, str, object, object, int]], ++) -> list[tuple[str, dict[str, int]]]: + """ + Обрабатывает информацию о билетах, суммируя количество и стоимость проданных билетов по категориям + и времени пребывания. Возвращает данные, которые могут быть использованы для отображения статистики +@@ -555,10 +696,18 @@ def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int] + ) + + TICKET_TYPE_MAP = { +- (0, 1): "Взрослый, 1 ч.", (0, 2): "Взрослый, 2 ч.", (0, 3): "Взрослый, 3 ч.", +- (1, 1): "Детский, 1 ч.", (1, 2): "Детский, 2 ч.", (1, 3): "Детский, 3 ч.", +- (2, 1): "Многодетный взрослый, 1 ч.", (2, 2): "Многодетный взрослый, 2 ч.", (2, 3): "Многодетный взрослый, 3 ч.", +- (3, 1): "Многодетный детский, 1 ч.", (3, 2): "Многодетный детский, 2 ч.", (3, 3): "Многодетный детский, 3 ч.", ++ (0, 1): "Взрослый, 1 ч.", ++ (0, 2): "Взрослый, 2 ч.", ++ (0, 3): "Взрослый, 3 ч.", ++ (1, 1): "Детский, 1 ч.", ++ (1, 2): "Детский, 2 ч.", ++ (1, 3): "Детский, 3 ч.", ++ (2, 1): "Многодетный взрослый, 1 ч.", ++ (2, 2): "Многодетный взрослый, 2 ч.", ++ (2, 3): "Многодетный взрослый, 3 ч.", ++ (3, 1): "Многодетный детский, 1 ч.", ++ (3, 2): "Многодетный детский, 2 ч.", ++ (3, 3): "Многодетный детский, 3 ч.", + (4, 3): "Инвалид, 3 ч.", + (5, 3): "Сопровождающий, 3 ч.", + } +diff --git a/modules/payment_equipment.py b/modules/payment_equipment.py +index 9b2005a..49695eb 100644 +--- a/modules/payment_equipment.py ++++ b/modules/payment_equipment.py +@@ -3,7 +3,6 @@ import subprocess + import time + from contextlib import contextmanager + from datetime import datetime, timedelta +-from typing import Optional + + from modules import windows + from modules.config import Config +@@ -14,10 +13,12 @@ config = Config() + + # Получаем параметр available + try: +- kkt_available = config.get("available") == 'on' ++ kkt_available = config.get("available") == "on" + except (KeyError, ValueError): + kkt_available = False +- logger.info("Параметр 'available' не найден в конфигурации, ККТ считается отключенным") ++ logger.info( ++ "Параметр 'available' не найден в конфигурации, ККТ считается отключенным" ++ ) + + # Инициализация драйвера ККТ + try: +@@ -27,7 +28,7 @@ try: + fptr = None + logger.info("ККТ отключен в конфигурации (available ≠ on)") + except Exception as e: +- logger.warning(f"Не установлен драйвер ККТ: {str(e)}") ++ logger.warning(f"Не установлен драйвер ККТ: {e!s}") + kkt_available = False + fptr = None + +@@ -111,7 +112,6 @@ TERMINAL_PIN_PAD_ERROR: set[int] = { + 4144, + 4145, + 4146, +- 4146, + 4202, + 4203, + 4208, +@@ -164,7 +164,7 @@ def fptr_connection(device): + device.open() + connected = True + +- if not hasattr(device, 'isOpened') or not device.isOpened(): ++ if not hasattr(device, "isOpened") or not device.isOpened(): + logger.warning("Не удалось открыть соединение с ККТ") + yield None + return +@@ -172,7 +172,7 @@ def fptr_connection(device): + yield device + + except Exception as e: +- logger.error(f"Ошибка подключения к ККТ: {str(e)}") ++ logger.error(f"Ошибка подключения к ККТ: {e!s}") + yield None + + finally: +@@ -211,18 +211,24 @@ def run_terminal_command(command_params: str, timeout: int = 300): + logger.info(f"Запуск команды: {pinpad_run}") + try: + process = subprocess.Popen( +- pinpad_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, +- shell=True # Важно для строки команды ++ pinpad_run, ++ stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE, ++ shell=True, # Важно для строки команды + ) + try: + stdout, stderr = process.communicate(timeout=timeout) + except subprocess.TimeoutExpired: +- logger.warning(f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически.") ++ logger.warning( ++ f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически." ++ ) + process.terminate() + try: + stdout, stderr = process.communicate(timeout=5) + except subprocess.TimeoutExpired: +- logger.warning("Процесс не завершился после terminate(). Используется kill().") ++ logger.warning( ++ "Процесс не завершился после terminate(). Используется kill()." ++ ) + process.kill() + stdout, stderr = process.communicate() + +@@ -296,6 +302,7 @@ def _safe_handle_error(returncode, title, message, error_callback): + except Exception as e: + logger.exception(f"Ошибка при вызове handle_error для кода {returncode}: {e}") + ++ + @logger_wraps() + def handle_error(code, title, message, error_callback=None): + """Универсальная обработка ошибок терминала. +@@ -343,15 +350,15 @@ def process_terminal_error(returncode, error_callback=None): + error_handlers = { + TERMINAL_USER_CANCEL_CODE: ( + "Оплата отменена пользователем", +- f"Оплата отменена пользователем. Код возврата: {returncode}." ++ f"Оплата отменена пользователем. Код возврата: {returncode}.", + ), + TERMINAL_USER_TIMEOUT: ( + "Слишком долгий ввод ПИН-кода", +- f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}." ++ f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}.", + ), + TERMINAL_INVALID_CURRENCY_CODE: ( + "Указан неверный код валюты", +- f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}." ++ f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}.", + ), + TERMINAL_NO_ADDRESS_TO_CONTACT: ( + "Терминал потерял связь с банком", +@@ -359,7 +366,7 @@ def process_terminal_error(returncode, error_callback=None): + ), + TERMINAL_OPERATION_CANCELED: ( + "Операция отменена", +- f"Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", ++ "Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", + ), + TERMINAL_QR_ERROR: ( + "Ошибка операции по QR-коду", +@@ -377,8 +384,8 @@ def process_terminal_error(returncode, error_callback=None): + _safe_handle_error( + returncode, + "Карта клиента заблокирована", +- f"Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", +- error_callback ++ "Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", ++ error_callback, + ) + return 0 + if returncode in TERMINAL_CARD_LIMIT: +@@ -386,7 +393,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Превышен лимит операций", + "Превышен лимит операций. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_BIOMETRIC_ERROR: +@@ -394,7 +401,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Ошибка в работе с биометрическими данными", + f"Ошибка в работе с биометрическими данными. Обратитесь в банк для выяснения причины. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_ERROR_PIN_CODE: +@@ -402,7 +409,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Ошибка при вводе ПИН-кода", + "ПИН-код не был введен, либо введен неверно, либо вводимый ПИН-код заблокирован. Попробуйте повторить операцию оплаты.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_NO_CONNECTION_BANK: +@@ -410,7 +417,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Нет связи с банком", + f"Попробуйте повторить операцию через пару минут. При повторении ошибки необходимо обратиться в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_NEED_CASH_COLLECTION: +@@ -418,7 +425,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Необходимо произвести инкассацию", + f"Необходимо произвести инкассацию. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_LIMIT_OPERATION: +@@ -426,7 +433,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Превышен лимит операций", + f"Превышен лимит операций. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_DATA_EXCHANGE: +@@ -434,7 +441,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Требуется сделать сверку итогов", + "Требуется сделать сверку итогов и повторить операцию.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_KLK: +@@ -442,7 +449,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Ошибка в работе терминала", + f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_CARD_ERROR: +@@ -450,7 +457,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Операцию невозможно выполнить для этой карты", + f"Необходимо повторить попытку. Если проблема сохраняется – использовать другую карту. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_SERVER_ROUTINE_MAINTENANCE: +@@ -458,15 +465,15 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Сервера Сбербанка недоступны", + f"Сервера Сбербанка находятся на обслуживании/ремонте/регламентных работах. Попробуйте повторить операцию позже. Если проблема сохраняется - обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}.Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_NO_MONEY: + _safe_handle_error( + returncode, + "Недостаточно средств на карте", +- f"Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", +- error_callback ++ "Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", ++ error_callback, + ) + return 0 + if returncode in TERMINAL_COMMAND_ERROR: +@@ -474,7 +481,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Нет нужного варианта связи для операции", + f"Нет нужного варианта связи для операции. Обратитесь в службу поддержки ПО. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + if returncode in TERMINAL_PIN_PAD_ERROR: +@@ -482,7 +489,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Проблема в работе ПИН-пада.", + f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + # Общие ошибки +@@ -499,7 +506,7 @@ def process_terminal_error(returncode, error_callback=None): + returncode, + "Ошибка терминала", + f"Возвращен неизвестный код ошибки. Обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", +- error_callback ++ error_callback, + ) + return 0 + +@@ -519,7 +526,9 @@ def terminal_oplata(amount: float) -> int: + + + @logger_wraps() +-def process_terminal_transaction(command_code: str, amount: float, operation_name: str, error_callback=None) -> int: ++def process_terminal_transaction( ++ command_code: str, amount: float, operation_name: str, error_callback=None ++) -> int: + """ + Обрабатывает операцию на банковском терминале. + +@@ -541,10 +550,10 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam + if result is None: + logger.error(f"Ошибка при выполнении команды терминала: {command}") + _safe_handle_error( +- f"Нет ответа от терминала", ++ "Нет ответа от терминала", + f"{operation_name} не была выполнена. Проверьте устройство.", + "Код ошибки: отсутствует", +- error_callback=error_callback ++ error_callback=error_callback, + ) + return 0 + +@@ -567,11 +576,12 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam + + + def universal_terminal_operation( +- payment_type: int, +- amount: float, +- progress_signal, +- operation_type: int = 1, +- error_callback=None) -> tuple[int, int]: ++ payment_type: int, ++ amount: float, ++ progress_signal, ++ operation_type: int = 1, ++ error_callback=None, ++) -> tuple[int, int]: + """ + Универсальный обработчик терминальных операций для оплаты или возврата. + +@@ -621,7 +631,9 @@ def universal_terminal_operation( + except Exception: + pass # Игнорируем ошибки emit + +- bank = process_terminal_transaction(command_code, amount, operation_name, error_callback) ++ bank = process_terminal_transaction( ++ command_code, amount, operation_name, error_callback ++ ) + if bank == 1: + if progress_signal is not None: + try: +@@ -638,12 +650,16 @@ def universal_terminal_operation( + return 0, 1 + elif payment_type == PAYMENT_OFFLINE: + if operation_type != 1: +- logger.warning("Оффлайн-режим не поддерживается для возврата или отмены") ++ logger.warning( ++ "Оффлайн-режим не поддерживается для возврата или отмены" ++ ) + progress_signal.emit("Оффлайн: только оплата", 100) + return 0, 0 + + logger.info("Запускаем offline оплату по банковскому терминалу") +- progress_signal.emit("Запускаем offline оплату по банковскому терминалу...", 35) ++ progress_signal.emit( ++ "Запускаем offline оплату по банковскому терминалу...", 35 ++ ) + return 1, 3 # успех, offline + + else: +@@ -652,13 +668,15 @@ def universal_terminal_operation( + return 0, 0 + except ValueError as ve: + logger.error(f"Ошибка: {ve}") +- progress_signal.emit(f"Ошибка: {str(ve)}", 100) ++ progress_signal.emit(f"Ошибка: {ve!s}", 100) + return 0, 0 + except Exception as exp: + logger.error(f"Неизвестная ошибка при проведении операции: {exp}") + if progress_signal is not None: + try: +- progress_signal.emit(f"Неизвестная ошибка при проведении операции: {str(exp)}", 100) ++ progress_signal.emit( ++ f"Неизвестная ошибка при проведении операции: {exp!s}", 100 ++ ) + except RuntimeError: + pass + return 0, 0 +@@ -979,7 +997,8 @@ def print_pinpad_check(count: int = 2): + + count -= 1 + +-def get_info(hide: bool = False) -> Optional[int]: ++ ++def get_info(hide: bool = False) -> int | None: + """ + Запрос информации о ККТ. + Запрашивает информацию о кассовом аппарате (модель, наименование и версия ПО). +@@ -1014,13 +1033,20 @@ def get_info(hide: bool = False) -> Optional[int]: + return model + except (ConnectionError, AttributeError) as e: + logger.error(f"Ошибка при получении данных о ККТ: {e}") +- windows.info_window("Ошибка", "Не удалось получить данные о ККТ.", "Проверьте настройки устройства.") ++ windows.info_window( ++ "Ошибка", ++ "Не удалось получить данные о ККТ.", ++ "Проверьте настройки устройства.", ++ ) + return None + except Exception as e: + logger.error(f"Неизвестная ошибка: {e}") +- windows.info_window("Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова.") ++ windows.info_window( ++ "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." ++ ) + return None + ++ + def is_kkt_connected() -> bool: + """ + Проверка физического подключения ККТ. +@@ -1046,6 +1072,7 @@ def is_kkt_connected() -> bool: + logger.error(f"Ошибка при проверке подключения ККТ: {e}") + return False + ++ + @logger_wraps() + def get_last_document(day: int = 7) -> None: + """Запрос информации о последнем чеке в ФН и проверка даты. +@@ -1066,7 +1093,9 @@ def get_last_document(day: int = 7) -> None: + except (ConnectionError, AttributeError) as e: + logger.error(f"Ошибка при работе с ККТ: {e}") + windows.info_window( +- "Ошибка", "Не удалось получить данные от ККТ.", "Проверьте подключение и настройки." ++ "Ошибка", ++ "Не удалось получить данные от ККТ.", ++ "Проверьте подключение и настройки.", + ) + except Exception as e: + logger.exception(f"Неизвестная ошибка в get_last_document: {e}") +@@ -1074,8 +1103,9 @@ def get_last_document(day: int = 7) -> None: + "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." + ) + ++ + def get_last_document_datetime() -> datetime: +- """ Получает дату и время последнего зарегистрированного чека из фискального накопителя. ++ """Получает дату и время последнего зарегистрированного чека из фискального накопителя. + + Возвращаемое значение: + datetime: Дата и время последнего документа в фискальном накопителе. +@@ -1085,12 +1115,17 @@ def get_last_document_datetime() -> datetime: + """ + logger.info("Запуск функции get_last_document_datetime") + with fptr_connection(fptr): +- fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT) ++ fptr.setParam( ++ IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT ++ ) + fptr.fnQueryData() +- last_check_datetime: datetime = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME) ++ last_check_datetime: datetime = fptr.getParamDateTime( ++ IFptr.LIBFPTR_PARAM_DATE_TIME ++ ) + + return last_check_datetime + ++ + def check_stale_document(last_check: datetime, max_days: int = 7) -> None: + """ + Проверяет, не превышает ли возраст последнего чека заданное количество дней. +@@ -1108,13 +1143,15 @@ def check_stale_document(last_check: datetime, max_days: int = 7) -> None: + if (current_date - last_check) <= timedelta(days=max_days): + # Ранний выход, если чек не старый + return +- info = f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" ++ info = ( ++ f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" ++ ) + windows.info_window( + "Внимание", + "Дата последнего документа, записанного в фискальном накопителе, старше 7 дней.\n\n" + "Для корректного проведения платежей по банковскому терминалу " +- "необходимо сделать сверку итогов (вкладка \"Касса\" -> \"Сверка итогов\").\n", +- info ++ 'необходимо сделать сверку итогов (вкладка "Касса" -> "Сверка итогов").\n', ++ info, + ) + + +@@ -1402,7 +1439,9 @@ def kassir_reg(user): + try: + with fptr_connection(fptr): + # Параметры для регистрации кассира +- fptr.setParam(1021, f"{user.last_name} {user.first_name} {user.middle_name}") ++ fptr.setParam( ++ 1021, f"{user.last_name} {user.first_name} {user.middle_name}" ++ ) + fptr.setParam(1203, user.inn) + fptr.operatorLogin() + logger.info( +@@ -1590,11 +1629,11 @@ def register_item(device, name, price, quantity, tax_type=IFptr.LIBFPTR_TAX_VAT2 + logger.info("Запуск функции register_item") + logger.info(f"В функцию переданы параметры: {name}, {price}, {quantity}") + device.setParam(IFptr.LIBFPTR_PARAM_COMMODITY_NAME, name) +- logger.info(f"name: {name}") # TODO: убрать ++ logger.info(f"name: {name}") # TODO: убрать + device.setParam(IFptr.LIBFPTR_PARAM_PRICE, price) +- logger.info(f"price: {price}") # TODO: убрать ++ logger.info(f"price: {price}") # TODO: убрать + device.setParam(IFptr.LIBFPTR_PARAM_QUANTITY, quantity) +- logger.info(f"quantity: {quantity}") # TODO: убрать ++ logger.info(f"quantity: {quantity}") # TODO: убрать + device.setParam(IFptr.LIBFPTR_PARAM_TAX_TYPE, tax_type) + device.registration() + +@@ -1639,7 +1678,9 @@ def register_tickets(device, sale_dict, type_operation): + None: Функция не возвращает значений, но выполняет регистрацию билетов в чеке. + """ + logger.info("Запуск функции register_tickets") +- logger.debug(f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}") ++ logger.debug( ++ f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}" ++ ) + + if type_operation == 1: + count_adult_with_discount = sale_dict["detail"][0] +@@ -1653,29 +1694,41 @@ def register_tickets(device, sale_dict, type_operation): + + # Взрослые без акции + count_adult_no_discount = sale_dict["kol_adult"] - count_adult_with_discount +- logger.debug(f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}") ++ logger.debug( ++ f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}" ++ ) + + if count_adult_no_discount > 0: + adults_no_discount_sum = count_adult_no_discount * sale_dict["price_adult"] +- logger.info(f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб") ++ logger.info( ++ f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб" ++ ) + # if device is not None: # TODO: для теста без ККТ. + отступ внизу + register_item( + device, + f"Билет взрослый {time} ч.", +- sale_dict['price_adult'], ++ sale_dict["price_adult"], + count_adult_no_discount, + ) + else: + logger.debug("Взрослые без акции: не регистрируем (количество = 0)") + + # Взрослые с акцией +- logger.debug(f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}") ++ logger.debug( ++ f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}" ++ ) + +- if (count_adult_with_discount > 0 +- and price_adult_with_discount > 0 +- and discount > 0): +- adults_with_discount_sum = count_adult_with_discount * price_adult_with_discount +- logger.info(f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб") ++ if ( ++ count_adult_with_discount > 0 ++ and price_adult_with_discount > 0 ++ and discount > 0 ++ ): ++ adults_with_discount_sum = ( ++ count_adult_with_discount * price_adult_with_discount ++ ) ++ logger.info( ++ f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб" ++ ) + # if device is not None: # TODO: для теста без ККТ. + отступ внизу + register_item( + device, +@@ -1690,29 +1743,43 @@ def register_tickets(device, sale_dict, type_operation): + + # Дети без акции + count_child_no_discount = sale_dict["kol_child"] - count_child_with_discount +- logger.debug(f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}") ++ logger.debug( ++ f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}" ++ ) + + if count_child_no_discount > 0: +- children_no_discount_sum = count_child_no_discount * sale_dict["price_child"] +- logger.info(f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб") ++ children_no_discount_sum = ( ++ count_child_no_discount * sale_dict["price_child"] ++ ) ++ logger.info( ++ f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб" ++ ) + # if device is not None: # TODO: для теста без ККТ. + отступ внизу + register_item( + device, + f"Билет детский {time} ч.", +- sale_dict['price_child'], ++ sale_dict["price_child"], + count_child_no_discount, + ) + else: + logger.debug("Дети без акции: не регистрируем (количество = 0)") + + # Дети с акцией +- logger.debug(f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}") ++ logger.debug( ++ f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}" ++ ) + +- if (count_child_with_discount > 0 +- and price_child_with_discount > 0 +- and discount > 0): +- children_with_discount_sum = count_child_with_discount * price_child_with_discount +- logger.info(f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб") ++ if ( ++ count_child_with_discount > 0 ++ and price_child_with_discount > 0 ++ and discount > 0 ++ ): ++ children_with_discount_sum = ( ++ count_child_with_discount * price_child_with_discount ++ ) ++ logger.info( ++ f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб" ++ ) + # if device is not None: # TODO: для теста без ККТ. + отступ внизу + register_item( + device, +@@ -1724,15 +1791,17 @@ def register_tickets(device, sale_dict, type_operation): + logger.debug("Дети с акцией: не регистрируем (условия не выполнены)") + else: + # Для других типов операций +- logger.error(f"Другие типы операций.") ++ logger.error("Другие типы операций.") + for item_name, item_data in sale_dict.items(): +- if (isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0): ++ if isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0: + # Проверяем наличие количества и цены +- logger.debug(f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}") ++ logger.debug( ++ f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}" ++ ) + register_item(device, item_name, item_data[0], item_data[1]) + else: + # Обработка других типов данных +- logger.error(f"Ошибка типа данных. Item_data - не список.") ++ logger.error("Ошибка типа данных. Item_data - не список.") + + + def process_payment(device, payment_type, bank_status, sale_dict, _): +@@ -1748,11 +1817,15 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): + bool: Возвращает `True`, если оплата прошла успешно, иначе — `False`. + """ + logger.info("Запуск функции process_payment") +- logger.debug(f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}") ++ logger.debug( ++ f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}" ++ ) + try: + # Проверка payment_type + if not isinstance(payment_type, int): +- logger.error(f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}") ++ logger.error( ++ f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}" ++ ) + return False + + # Определение суммы +@@ -1763,7 +1836,9 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): + try: + payment_amount = sale_dict["detail"][7] + if isinstance(payment_amount, (int, float)) and payment_amount > 0: +- logger.debug(f"Сумма получена из sale_dict['detail'][7]: {payment_amount}") ++ logger.debug( ++ f"Сумма получена из sale_dict['detail'][7]: {payment_amount}" ++ ) + except (IndexError, TypeError): + pass + +@@ -1775,7 +1850,12 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): + if isinstance(item_data, list) and len(item_data) >= 2: + price = item_data[0] + qty = item_data[1] +- if isinstance(price, (int, float)) and isinstance(qty, int) and price > 0 and qty > 0: ++ if ( ++ isinstance(price, (int, float)) ++ and isinstance(qty, int) ++ and price > 0 ++ and qty > 0 ++ ): + total += price * qty + valid = True + if valid and total > 0: +@@ -1846,15 +1926,23 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): + logger.info("Запуск функции handle_document_errors") + # Проверяем, закрыт ли документ + while device.checkDocumentClosed() < 0: +- logger.warning(f"Не удалось проверить состояние документа: {device.errorDescription()}") ++ logger.warning( ++ f"Не удалось проверить состояние документа: {device.errorDescription()}" ++ ) + if retry_count >= max_retries - 1: + if on_error: +- on_error("Ошибка ККТ", f"Не удалось проверить состояние документа после {max_retries} попыток. " +- f"Проверьте соединение с ККТ и не выключайте ПК.") ++ on_error( ++ "Ошибка ККТ", ++ f"Не удалось проверить состояние документа после {max_retries} попыток. " ++ f"Проверьте соединение с ККТ и не выключайте ПК.", ++ ) + return False + + if on_error: +- on_error("Ошибка ККТ", f"Ошибка: {device.errorDescription()}. Повторная попытка...") ++ on_error( ++ "Ошибка ККТ", ++ f"Ошибка: {device.errorDescription()}. Повторная попытка...", ++ ) + + retry_count += 1 + time.sleep(5) +@@ -1868,12 +1956,17 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): + except Exception as e: + logger.critical(f"Ошибка при отмене чека: {e}") + if on_error: +- on_error("Критическая ошибка", "Не удалось отменить чек. Требуется перезагрузка ККТ.") ++ on_error( ++ "Критическая ошибка", ++ "Не удалось отменить чек. Требуется перезагрузка ККТ.", ++ ) + # Пробрасываем исключение наверх + raise + + if on_error: +- on_error("Документ не закрыт", "Чек отменен. Требуется сформировать его заново.") ++ on_error( ++ "Документ не закрыт", "Чек отменен. Требуется сформировать его заново." ++ ) + return False + + # Если документ не напечатан — попробовать допечатать +@@ -1884,12 +1977,19 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): + logger.info("Документ успешно допечатан") + return True + +- logger.warning(f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}") ++ logger.warning( ++ f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}" ++ ) + if on_error: +- on_error("Ошибка печати", f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.") ++ on_error( ++ "Ошибка печати", ++ f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.", ++ ) + time.sleep(3) + +- logger.error("Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции.") ++ logger.error( ++ "Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции." ++ ) + # Документ закрыт, но не допечатан — допустимо. + # Документ будет автоматически допечатан при следующей печатной операции. + +@@ -1901,7 +2001,15 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): + return True + + +-def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_status, on_error=None): ++def check_open( ++ sale_dict, ++ payment_type, ++ user, ++ type_operation, ++ print_check, ++ bank_status, ++ on_error=None, ++): + """ + Проведение операции оплаты. + +@@ -1941,12 +2049,16 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ + setup_fptr(device, user, type_operation, print_check) + + # Проверка состояния чека +- device.setParam(IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE) ++ device.setParam( ++ IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE ++ ) + device.queryData() +- receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) +- document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) ++ receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) ++ document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) + receipt_type = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE) +- logger.debug(f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}") ++ logger.debug( ++ f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}" ++ ) + if receipt_type != IFptr.LIBFPTR_RT_CLOSED: + logger.error(f"Чек уже открыт! Тип: {receipt_type}") + if on_error: +@@ -1955,9 +2067,13 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ + + # Установка типа чека + if type_operation == 1: +- device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL) # Продажа ++ device.setParam( ++ IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL ++ ) # Продажа + elif type_operation == 2: +- device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN) # Возврат ++ device.setParam( ++ IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN ++ ) # Возврат + else: + logger.error(f"Неизвестный тип операции: {type_operation}") + return False +@@ -1980,11 +2096,12 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ + return 1 + + except Exception as e: +- logger.error(f"Критическая ошибка в check_open: {str(e)}") ++ logger.error(f"Критическая ошибка в check_open: {e!s}") + if on_error: +- on_error("Ошибка ККТ", f"Критическая ошибка: {str(e)}") ++ on_error("Ошибка ККТ", f"Критическая ошибка: {e!s}") + return 0 + ++ + @logger_wraps() + def smena_close(user): + """Закрытие кассовой смены. +diff --git a/modules/progress_window.py b/modules/progress_window.py +index c8e6d1e..660ac03 100644 +--- a/modules/progress_window.py ++++ b/modules/progress_window.py +@@ -1,5 +1,4 @@ +-from PySide6.QtCore import QPropertyAnimation, QEasingCurve +-from PySide6.QtCore import Qt ++from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt + from PySide6.QtWidgets import QDialog + + from design.logic.progress_dialog import Ui_ProgressDialog +diff --git a/modules/sale_logic.py b/modules/sale_logic.py +index 5ba2434..dcadae4 100644 +--- a/modules/sale_logic.py ++++ b/modules/sale_logic.py +@@ -230,10 +230,10 @@ def calculate_itog() -> int: + + # Проверка на корректность данных + if ( +- system.sale_dict["detail"][0] < 0 +- or system.sale_dict["detail"][2] < 0 +- or system.sale_dict["detail"][1] < 0 +- or system.sale_dict["detail"][3] < 0 ++ system.sale_dict["detail"][0] < 0 ++ or system.sale_dict["detail"][2] < 0 ++ or system.sale_dict["detail"][1] < 0 ++ or system.sale_dict["detail"][3] < 0 + ): + raise ValueError("Некорректные данные для расчета итоговой суммы") + +@@ -243,7 +243,9 @@ def calculate_itog() -> int: + adults_no_discount_sum = adults_no_discount * system.sale_dict["price_adult"] + + # Взрослые со скидкой (detail[0] × detail[1]) +- adults_with_discount_sum = system.sale_dict["detail"][0] * system.sale_dict["detail"][1] ++ adults_with_discount_sum = ( ++ system.sale_dict["detail"][0] * system.sale_dict["detail"][1] ++ ) + + adult_total = adults_no_discount_sum + adults_with_discount_sum + +@@ -253,21 +255,33 @@ def calculate_itog() -> int: + children_no_discount_sum = children_no_discount * system.sale_dict["price_child"] + + # Дети со скидкой (detail[2] × detail[3]) +- children_with_discount_sum = system.sale_dict["detail"][2] * system.sale_dict["detail"][3] ++ children_with_discount_sum = ( ++ system.sale_dict["detail"][2] * system.sale_dict["detail"][3] ++ ) + + child_total = children_no_discount_sum + children_with_discount_sum + + result = adult_total + child_total + +- logger.debug(f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}") +- logger.debug(f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}") ++ logger.debug( ++ f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}" ++ ) ++ logger.debug( ++ f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}" ++ ) + logger.debug(f"ИТОГО взрослые: {adult_total}") + +- logger.debug(f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}") +- logger.debug(f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}") ++ logger.debug( ++ f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}" ++ ) ++ logger.debug( ++ f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}" ++ ) + logger.debug(f"ИТОГО дети: {child_total}") + +- logger.debug(f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}") ++ logger.debug( ++ f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}" ++ ) + + return result + +diff --git a/modules/system.py b/modules/system.py +index 34c8fdf..0c7b82d 100644 +--- a/modules/system.py ++++ b/modules/system.py +@@ -12,7 +12,7 @@ from sqlalchemy import create_engine, select + from sqlalchemy.exc import SQLAlchemyError + from sqlalchemy.orm import Session + +-from db.models import Holiday, Workday, Price, User, Sale ++from db.models import Holiday, Price, Sale, User, Workday + from modules.config import Config + from modules.logger import logger, logger_wraps + +@@ -24,7 +24,7 @@ class System: + + def __new__(cls, *args, **kwargs): + if not cls._instance: +- cls._instance = super(System, cls).__new__(cls, *args, **kwargs) ++ cls._instance = super().__new__(cls, *args, **kwargs) + return cls._instance + + def __init__(self): +@@ -167,7 +167,9 @@ class System: + logger.info(f"Успешная авторизация: {kassir.last_name}") + return 1 + else: +- logger.warning(f"Неудачная попытка авторизации для пользователя {login}") ++ logger.warning( ++ f"Неудачная попытка авторизации для пользователя {login}" ++ ) + return 0 + else: + logger.warning(f"Пользователь {login} не найден") +@@ -241,7 +243,7 @@ class System: + db_prices[key] = default_prices[key] + else: + # Если записей нет, используем дефолтные значения для всех элементов +- logger.warning(f"Используем дефолтные значения для прайс-листа") ++ logger.warning("Используем дефолтные значения для прайс-листа") + db_prices = default_prices + + # Устанавливаем цены, используя данные из db_prices или дефолтные значения +@@ -410,24 +412,23 @@ class System: + query = select(Sale.bank_pay).where(Sale.id == sale_id) + load_slip = session.execute(query).scalars().one() + try: +- qr_match = re.search(r'Номер QR:\s+(\d+)', load_slip) ++ qr_match = re.search(r"Номер QR:\s+(\d+)", load_slip) + if qr_match: +- last_digits = re.search(r'Карта:\s+\*+\s*(\d{4})', load_slip) +- card_tail = last_digits.group(1) if last_digits else '' ++ last_digits = re.search(r"Карта:\s+\*+\s*(\d{4})", load_slip) ++ card_tail = last_digits.group(1) if last_digits else "" + else: +- last_digits = re.search(r'Карта:\(?E\d*\)?\s+\*+\s*(\d{4})', load_slip) +- card_tail = last_digits.group(1) if last_digits else '' +- merchant = re.search(r'М:(\d+)', load_slip) +- merchant_id = merchant.group(1) if merchant else '' +- rrn = re.search(r'RRN:\s+(\d+)', load_slip) +- rrn_value = rrn.group(1) if rrn else '' ++ last_digits = re.search(r"Карта:\(?E\d*\)?\s+\*+\s*(\d{4})", load_slip) ++ card_tail = last_digits.group(1) if last_digits else "" ++ merchant = re.search(r"М:(\d+)", load_slip) ++ merchant_id = merchant.group(1) if merchant else "" ++ rrn = re.search(r"RRN:\s+(\d+)", load_slip) ++ rrn_value = rrn.group(1) if rrn else "" + except Exception as e: + logger.warning(f"Ошибка при разборе чека: {e}") +- card_tail = '' +- merchant_id = '' +- rrn_value = '' +- return card_tail, merchant_id, rrn_value, load_slip +- ++ card_tail = "" ++ merchant_id = "" ++ rrn_value = "" ++ return card_tail, merchant_id, rrn_value, load_slip + + def check_db_connection(self) -> bool: + """Проверка подключения к базе данных.""" +@@ -439,4 +440,4 @@ class System: + return True + except Exception as e: + logger.error(f"Ошибка подключения к БД: {e}") +- return False +\ No newline at end of file ++ return False +diff --git a/modules/worker.py b/modules/worker.py +index d83a6e7..15f6a25 100644 +--- a/modules/worker.py ++++ b/modules/worker.py +@@ -1,7 +1,15 @@ + import datetime as dt + from functools import wraps + +-from PySide6.QtCore import QElapsedTimer, QObject, Signal, QMetaObject, Qt, QTimer, QEventLoop ++from PySide6.QtCore import ( ++ QElapsedTimer, ++ QEventLoop, ++ QMetaObject, ++ QObject, ++ Qt, ++ QTimer, ++ Signal, ++) + from sqlalchemy import update + + from db.models import Sale +@@ -10,16 +18,19 @@ from modules.logger import logger + + def with_timer(func): + """Декоратор для автоматического создания QElapsedTimer""" ++ + @wraps(func) + def wrapper(self, *args, **kwargs): + timer = QElapsedTimer() + timer.start() + return func(self, timer, *args, **kwargs) ++ + return wrapper + + + class BaseWorker(QObject): + """Базовый класс с общими методами""" ++ + DEFAULT_DELAY_MS = 15 + + progress_updated = Signal(str, int) +@@ -35,9 +46,10 @@ class BaseWorker(QObject): + elapsed = timer.elapsed() + logger.debug(f"[TIMER] {step_name} — {elapsed} ms") + +- def delayed_progress_update(self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS): +- if delay_ms < 0: +- delay_ms = 0 ++ def delayed_progress_update( ++ self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS ++ ): ++ delay_ms = max(delay_ms, 0) + if delay_ms > 0: + # Создаем локальный таймер для обеспечения задержки + timer = QTimer(self) +@@ -58,9 +70,15 @@ class BaseWorker(QObject): + Qt.QueuedConnection, + ) + +- def emit_error_and_finish(self, title: str, message: str, code: str = "", +- timer: QElapsedTimer = None, step_name: str = "", +- close_window: bool = True): ++ def emit_error_and_finish( ++ self, ++ title: str, ++ message: str, ++ code: str = "", ++ timer: QElapsedTimer = None, ++ step_name: str = "", ++ close_window: bool = True, ++ ): + """ + Универсальный метод обработки ошибок + :param title: Заголовок ошибки +@@ -87,11 +105,13 @@ class BaseWorker(QObject): + logger.warning("Попытка emit после удаления объекта — игнорируем") + else: + raise +- except Exception as e: ++ except Exception: + logger.exception("Неожиданная ошибка в emit_error_and_finish") + ++ + class PaymentHandler: + """Обработчик банковских платежей""" ++ + def __init__(self, worker, pq, payment_type, amount, dev_mode=False): + self.worker = worker + self.pq = pq +@@ -107,7 +127,9 @@ class PaymentHandler: + + if self.dev_mode: + self.worker.logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") +- self.worker.logger.debug(f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}") ++ self.worker.logger.debug( ++ f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}" ++ ) + return True, 1 # (success, payment_type) + + try: +@@ -116,7 +138,7 @@ class PaymentHandler: + self.payment_type, + self.amount, + self.worker.progress_updated, +- error_callback=self._handle_terminal_error_callback ++ error_callback=self._handle_terminal_error_callback, + ) + self.worker.log_step(timer, "pq.universal_terminal_operation finished") + +@@ -134,19 +156,21 @@ class PaymentHandler: + def get_mock_slip(self): + """Генерация тестового слип-чека для режима отладки""" + from datetime import datetime +- return (f"DEBUG_SLIP\n" +- f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" +- f"Сумма: {self.amount} руб.\n" +- f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" +- f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ") + +- def _handle_terminal_error_callback(self, title: str, message: str, code: int) -> None: ++ return ( ++ f"DEBUG_SLIP\n" ++ f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" ++ f"Сумма: {self.amount} руб.\n" ++ f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" ++ f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ" ++ ) ++ ++ def _handle_terminal_error_callback( ++ self, title: str, message: str, code: int ++ ) -> None: + """Callback для обработки ошибок от терминала""" + self.worker.emit_error_and_finish( +- title=title, +- message=message, +- code=str(code), +- step_name="terminal_error" ++ title=title, message=message, code=str(code), step_name="terminal_error" + ) + + def _handle_payment_failed(self, timer): +@@ -155,7 +179,7 @@ class PaymentHandler: + title="Ошибка", + message="Операция оплаты не удалась. Повторите попытку.", + timer=timer, +- step_name="payment_failed" ++ step_name="payment_failed", + ) + + def _handle_payment_exception(self, timer, exception): +@@ -165,12 +189,13 @@ class PaymentHandler: + message="Не удалось выполнить операцию на терминале.", + code=str(exception), + timer=timer, +- step_name="payment_exception" ++ step_name="payment_exception", + ) + + + class CheckHandler: + """Обработчик печати чеков""" ++ + def __init__(self, worker, pq): + self.worker = worker + self.pq = pq +@@ -184,25 +209,28 @@ class CheckHandler: + type_operation=1, + print_check=print_check, + bank_status=bank_data, +- on_error=self.handle_check_error ++ on_error=self.handle_check_error, + ) + + return state_check == 1 + +- def handle_check_error(self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка"): ++ def handle_check_error( ++ self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка" ++ ): + """Обработчик ошибок печати чека""" + logger.error(f"handle_check_error вызван с title={title}, text={text}") + self.worker.emit_error_and_finish( + title="Ошибка ККМ", + message="Документ не закрылся после 5 попыток. Отмена кассового чека.", + code="critical", +- step_name="check_error" ++ step_name="check_error", + ) + # raise Exception(f"{title}: {text}") + + + class DatabaseHandler: + """Обработчик работы с базой данных""" ++ + def __init__(self, Session, engine): + self.Session = Session + self.engine = engine +@@ -211,7 +239,9 @@ class DatabaseHandler: + """Проверяет существование продажи по ID""" + try: + with self.Session(self.engine) as session: +- return session.query(Sale).filter(Sale.id == sale_id).first() is not None ++ return ( ++ session.query(Sale).filter(Sale.id == sale_id).first() is not None ++ ) + except Exception as e: + logger.error(f"Ошибка проверки существования продажи {sale_id}: {e}") + return False +@@ -219,17 +249,16 @@ class DatabaseHandler: + def update_sale(self, sale_id, **values): + """Общее сохранение данных о продаже""" + with self.Session(self.engine) as session: +- session.execute( +- update(Sale) +- .where(Sale.id == sale_id) +- .values(**values) +- ) ++ session.execute(update(Sale).where(Sale.id == sale_id).values(**values)) + session.commit() + + + class TransactionWorker(BaseWorker): + """Основной класс обработки транзакций""" +- def __init__(self, payment_type, print_check, system, pq, Session, main_window, parent=None): ++ ++ def __init__( ++ self, payment_type, print_check, system, pq, Session, main_window, parent=None ++ ): + super().__init__(parent) + self.payment_type = payment_type + self.print_check = print_check +@@ -238,7 +267,9 @@ class TransactionWorker(BaseWorker): + self.Session = Session + self.main_window = main_window + # Флаг режима отладки +- self.dev_mode = getattr(main_window, 'dev_mode', False) if main_window else False ++ self.dev_mode = ( ++ getattr(main_window, "dev_mode", False) if main_window else False ++ ) + # Флаг cleanup + self._is_cleaned = False + +@@ -262,14 +293,16 @@ class TransactionWorker(BaseWorker): + @with_timer + def process_special_sale(self, timer: QElapsedTimer, progress_percent: int): + """Обработка специальной продажи""" +- self.delayed_progress_update("Печатаем билеты (особая продажа)...", progress_percent) ++ self.delayed_progress_update( ++ "Печатаем билеты (особая продажа)...", progress_percent ++ ) + self.invoke_main_window_method("print_saved_tickets") + self.log_step(timer, "print_saved_tickets finished") + self.system.sale_status = 0 + + def process_payment(self, timer: QElapsedTimer): + """Обработка платежа""" +- payment = 2 # по умолчанию наличные ++ payment = 2 # по умолчанию наличные + bank_status = 0 + + # Проверяем, что sale_id существует +@@ -278,17 +311,19 @@ class TransactionWorker(BaseWorker): + self.emit_error_and_finish( + title="Критическая ошибка", + message="Продажа не была создана в БД", +- code="SALE_ID_NONE" ++ code="SALE_ID_NONE", + ) + return None, None + + # Проверяем существует ли продажа в БД? + if not self.db_handler.sale_exists(self.system.sale_id): +- logger.warning(f"Продажа {self.system.sale_id} есть в памяти, но отсутствует в БД!") ++ logger.warning( ++ f"Продажа {self.system.sale_id} есть в памяти, но отсутствует в БД!" ++ ) + self.emit_error_and_finish( + title="Критическая ошибка", + message="Продажа не найдена в БД после сохранения", +- code="SALE_NOT_IN_DB" ++ code="SALE_NOT_IN_DB", + ) + return None, None + +@@ -318,7 +353,9 @@ class TransactionWorker(BaseWorker): + bank_status = 1 + # Обязательно сохраняем банковский слип-чек + try: +- self.db_handler.update_sale(self.system.sale_id, bank_pay=check, status=9) ++ self.db_handler.update_sale( ++ self.system.sale_id, bank_pay=check, status=9 ++ ) + except Exception as e: + logger.warning(f"Ошибка сохранения банковского чека: {e}") + self.log_step(timer, "save in db finished") +@@ -335,7 +372,9 @@ class TransactionWorker(BaseWorker): + self.delayed_progress_update("Печатаем кассовый чек...", 60) + + logger.debug(f"ДО check_open: sale_dict = {self.system.sale_dict}") +- logger.debug(f"exclude_from_sale={self.system.exclude_from_sale}, sale_checkbox_row={self.system.sale_checkbox_row}") ++ logger.debug( ++ f"exclude_from_sale={self.system.exclude_from_sale}, sale_checkbox_row={self.system.sale_checkbox_row}" ++ ) + + try: + if not self.check_handler.print_check( +@@ -344,7 +383,7 @@ class TransactionWorker(BaseWorker): + self.system.user, + self.print_check, + self.system.sale_dict["detail"][7], +- bank_data ++ bank_data, + ): + # В случае ошибки внутри print_check должен быть вызван on_error + return False +@@ -394,7 +433,7 @@ class TransactionWorker(BaseWorker): + code=str(error), + timer=timer, + step_name="transaction_error", +- close_window=True ++ close_window=True, + ) + self.system.sale_status = 0 + +diff --git a/patch.diff b/patch.diff +new file mode 100644 +index 0000000..9c321e7 +--- /dev/null ++++ b/patch.diff +@@ -0,0 +1,4276 @@ ++diff --git a/db/models/sale.py b/db/models/sale.py ++index a0035ee..4e59786 100644 ++--- a/db/models/sale.py +++++ b/db/models/sale.py ++@@ -37,7 +37,7 @@ class Sale(Base): ++ datetime_return: Mapped[DateTime | None] = mapped_column( ++ DateTime, comment="Дата и время возврата продажи" ++ ) ++- bank_return: Mapped[str | None | None] = mapped_column( +++ bank_return: Mapped[str | None] = mapped_column( ++ Text, comment="Банковский чек возврата" ++ ) ++ partial_return: Mapped[int] = mapped_column( ++diff --git a/design/logic/authorization.py b/design/logic/authorization.py ++index 6238708..6242cff 100644 ++--- a/design/logic/authorization.py +++++ b/design/logic/authorization.py ++@@ -3,7 +3,7 @@ from PySide6.QtGui import QPixmap ++ from PySide6.QtWidgets import QFrame, QLabel, QLineEdit, QPushButton ++ ++ ++-class Ui_Dialog(object): +++class Ui_Dialog: ++ def setupUi(self, Dialog): ++ if not Dialog.objectName(): ++ Dialog.setObjectName("Dialog") ++diff --git a/design/logic/client.py b/design/logic/client.py ++index ddc3b51..a12ecad 100644 ++--- a/design/logic/client.py +++++ b/design/logic/client.py ++@@ -13,7 +13,7 @@ from PySide6.QtWidgets import ( ++ ) ++ ++ ++-class Ui_Dialog_Client(object): +++class Ui_Dialog_Client: ++ def setupUi(self, Dialog_Client): ++ if not Dialog_Client.objectName(): ++ Dialog_Client.setObjectName("Dialog_Client") ++diff --git a/design/logic/main_form.py b/design/logic/main_form.py ++index b3bfd74..f0088d0 100644 ++--- a/design/logic/main_form.py +++++ b/design/logic/main_form.py ++@@ -15,14 +15,14 @@ from PySide6.QtWidgets import ( ++ QPushButton, ++ QRadioButton, ++ QStatusBar, ++- QTabWidget, ++ QTableWidget, ++ QTableWidgetItem, +++ QTabWidget, ++ QWidget, ++ ) ++ ++ ++-class Ui_MainWindow(object): +++class Ui_MainWindow: ++ def setupUi(self, MainWindow): ++ if not MainWindow.objectName(): ++ MainWindow.setObjectName("MainWindow") ++diff --git a/design/logic/pay.py b/design/logic/pay.py ++index 3926a8f..8d243c4 100644 ++--- a/design/logic/pay.py +++++ b/design/logic/pay.py ++@@ -1,9 +1,9 @@ ++ from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt ++ from PySide6.QtGui import QFont ++-from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QCheckBox +++from PySide6.QtWidgets import QCheckBox, QGroupBox, QLabel, QPushButton ++ ++ ++-class Ui_Dialog_Pay(object): +++class Ui_Dialog_Pay: ++ def setupUi(self, Dialog_Pay): ++ if not Dialog_Pay.objectName(): ++ Dialog_Pay.setObjectName("Dialog_Pay") ++diff --git a/design/logic/progress_dialog.py b/design/logic/progress_dialog.py ++index e7b5b3e..7d2596a 100644 ++--- a/design/logic/progress_dialog.py +++++ b/design/logic/progress_dialog.py ++@@ -1,23 +1,23 @@ ++-from PySide6.QtCore import (QCoreApplication, QMetaObject, QSize, Qt) ++-from PySide6.QtWidgets import (QLabel, QProgressBar, ++- QVBoxLayout) +++from PySide6.QtCore import QCoreApplication, QMetaObject, QSize, Qt +++from PySide6.QtWidgets import QLabel, QProgressBar, QVBoxLayout ++ ++-class Ui_ProgressDialog(object): +++ +++class Ui_ProgressDialog: ++ def setupUi(self, ProgressDialog): ++ if not ProgressDialog.objectName(): ++- ProgressDialog.setObjectName(u"ProgressDialog") +++ ProgressDialog.setObjectName("ProgressDialog") ++ ProgressDialog.resize(300, 127) ++ ProgressDialog.setMinimumSize(QSize(300, 100)) ++ self.verticalLayout = QVBoxLayout(ProgressDialog) ++- self.verticalLayout.setObjectName(u"verticalLayout") +++ self.verticalLayout.setObjectName("verticalLayout") ++ self.label_status = QLabel(ProgressDialog) ++- self.label_status.setObjectName(u"label_status") +++ self.label_status.setObjectName("label_status") ++ self.label_status.setAlignment(Qt.AlignmentFlag.AlignCenter) ++ ++ self.verticalLayout.addWidget(self.label_status) ++ ++ self.progressBar = QProgressBar(ProgressDialog) ++- self.progressBar.setObjectName(u"progressBar") +++ self.progressBar.setObjectName("progressBar") ++ self.progressBar.setMinimum(0) ++ self.progressBar.setMaximum(100) ++ self.progressBar.setValue(0) ++@@ -25,14 +25,26 @@ class Ui_ProgressDialog(object): ++ ++ self.verticalLayout.addWidget(self.progressBar) ++ ++- ++ self.retranslateUi(ProgressDialog) ++ ++ QMetaObject.connectSlotsByName(ProgressDialog) +++ ++ # setupUi ++ ++ def retranslateUi(self, ProgressDialog): ++- ProgressDialog.setWindowTitle(QCoreApplication.translate("ProgressDialog", u"\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", None)) ++- self.label_status.setText(QCoreApplication.translate("ProgressDialog", u"\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", None)) ++- # retranslateUi +++ ProgressDialog.setWindowTitle( +++ QCoreApplication.translate( +++ "ProgressDialog", +++ "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435...", +++ None, +++ ) +++ ) +++ self.label_status.setText( +++ QCoreApplication.translate( +++ "ProgressDialog", +++ "\u041d\u0430\u0447\u0430\u043b\u043e \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438...", +++ None, +++ ) +++ ) ++ +++ # retranslateUi ++diff --git a/design/logic/sale.py b/design/logic/sale.py ++index ab9ae05..56b8bb8 100644 ++--- a/design/logic/sale.py +++++ b/design/logic/sale.py ++@@ -15,7 +15,7 @@ from PySide6.QtWidgets import ( ++ ) ++ ++ ++-class Ui_Dialog_Sale(object): +++class Ui_Dialog_Sale: ++ def setupUi(self, Dialog_Sale): ++ if not Dialog_Sale.objectName(): ++ Dialog_Sale.setObjectName("Dialog_Sale") ++diff --git a/design/logic/slip.py b/design/logic/slip.py ++index 16b50c0..9e2b262 100644 ++--- a/design/logic/slip.py +++++ b/design/logic/slip.py ++@@ -3,7 +3,7 @@ from PySide6.QtGui import QFont ++ from PySide6.QtWidgets import QLabel, QLineEdit, QTabWidget, QTextEdit, QWidget ++ ++ ++-class Ui_Dialog_Slip(object): +++class Ui_Dialog_Slip: ++ def setupUi(self, Dialog_Slip): ++ if not Dialog_Slip.objectName(): ++ Dialog_Slip.setObjectName("Dialog_Slip") ++diff --git a/main.py b/main.py ++index c7f54d0..796ae55 100644 ++--- a/main.py +++++ b/main.py ++@@ -4,46 +4,43 @@ import subprocess ++ import sys ++ from datetime import date, timedelta ++ from pathlib import Path ++-from typing import Any, Optional, Type +++from typing import Any ++ ++ from PySide6 import QtCore, QtWidgets ++-from PySide6.QtCore import Qt ++-from PySide6.QtCore import Signal, QThread +++from PySide6.QtCore import Qt, QThread, Signal ++ from PySide6.QtGui import QColor, QPixmap ++ from PySide6.QtWidgets import ( ++ QCheckBox, +++ QDialog, ++ QHBoxLayout, +++ QMessageBox, ++ QTableWidgetItem, +++ QWidget, ++ ) ++-from PySide6.QtWidgets import QDialog, QWidget ++-from PySide6.QtWidgets import QMessageBox ++-from sqlalchemy import and_, select, update, desc +++from sqlalchemy import and_, desc, select, update ++ from sqlalchemy.orm import Session ++ ++-from db.models import Client ++-from db.models import Sale ++-from db.models import Ticket +++from db.models import Client, Sale, Ticket ++ from design.logic.authorization import Ui_Dialog ++ from design.logic.client import Ui_Dialog_Client ++ from design.logic.main_form import Ui_MainWindow ++ from design.logic.pay import Ui_Dialog_Pay ++ from design.logic.sale import Ui_Dialog_Sale ++ from design.logic.slip import Ui_Dialog_Slip ++-from modules import otchet +++from modules import otchet, windows ++ from modules import payment_equipment as pq ++-from modules import windows ++ from modules.auth_logic import perform_pre_sale_checks ++ from modules.logger import logger, logger_wraps ++ from modules.progress_window import ProgressWindow ++ from modules.sale_logic import ( ++- calculate_age, ++- calculate_ticket_type, ++- calculated_ticket_price, ++ calculate_adult_price, +++ calculate_age, ++ calculate_child_price, ++- calculate_discounted_price, ++ calculate_discount, +++ calculate_discounted_price, ++ calculate_itog, +++ calculate_ticket_type, +++ calculated_ticket_price, ++ convert_sale_dict_values, ++ generating_parts_for_partial_returns, ++ get_talent_based_on_time, ++@@ -67,6 +64,7 @@ def handle_uncaught_exception(exc_type, exc_value, exc_traceback): ++ "Неперехваченное исключение" ++ ) ++ +++ ++ sys.excepthook = handle_uncaught_exception ++ ++ ++@@ -101,7 +99,7 @@ class AuthForm(QDialog): ++ windows.info_window( ++ "База данных недоступна", ++ "Не удалось подключиться к серверу базы данных.\n Приложение будет закрыто.", ++- "" +++ "", ++ ) ++ sys.exit(1) ++ login: str = self.ui.lineEdit.text() ++@@ -134,8 +132,11 @@ class AuthForm(QDialog): ++ auth.close() ++ return True ++ else: ++- windows.info_window("Пользователь не найден", "Проверьте правильность ввода логина и пароля.", ++- "Убедитесь, что используете правильный логин и пароль для входа в систему.") +++ windows.info_window( +++ "Пользователь не найден", +++ "Проверьте правильность ввода логина и пароля.", +++ "Убедитесь, что используете правильный логин и пароль для входа в систему.", +++ ) ++ return False ++ ++ def check_kkt_connection(self): ++@@ -168,15 +169,21 @@ class AuthForm(QDialog): ++ break ++ ++ except (ConnectionError, TimeoutError, Exception) as e: ++- error_type = "сетевая" if isinstance(e, (ConnectionError, TimeoutError)) else "неизвестная" ++- logger.warning(f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}") +++ error_type = ( +++ "сетевая" +++ if isinstance(e, (ConnectionError, TimeoutError)) +++ else "неизвестная" +++ ) +++ logger.warning( +++ f"Попытка {attempt} из 3: {error_type} ошибка при подключении к ККТ: {e}" +++ ) ++ ++ if not connected: ++ logger.error("Все 3 попытки подключения к ККТ не удались") ++ windows.info_window( ++ "Внимание", ++ "Кассовый аппарат не подключен", ++- "Программа продолжит работу без фискальных операций." +++ "Программа продолжит работу без фискальных операций.", ++ ) ++ except (ConnectionError, TimeoutError) as e: ++ logger.error(f"Ошибка при проверке подключения к ККТ: {e}") ++@@ -225,7 +232,6 @@ class AuthForm(QDialog): ++ window.exec_() ++ ++ ++- ++ class ClientForm(QDialog): ++ """Форма с данными клиента""" ++ ++@@ -320,7 +326,7 @@ class ClientForm(QDialog): ++ gender=str(self.ui.comboBox.currentText()), ++ phone=self.ui.lineEdit_4.text(), ++ email=self.ui.lineEdit_5.text(), ++- privilege=str((self.ui.comboBox_2.currentText())), +++ privilege=str(self.ui.comboBox_2.currentText()), ++ ) ++ ) ++ session.commit() ++@@ -396,7 +402,7 @@ class SaleForm(QDialog): ++ client_id: str = self.ui.tableWidget.item(row_number, 5).text() ++ # При типизации указываем, что search_client может быть либо экземпляром Client, либо None ++ with Session(system.engine) as session: ++- search_client: Optional[Client] = ( +++ search_client: Client | None = ( ++ session.query(Client).filter_by(id=client_id).first() ++ ) ++ # Проверяем, найден ли клиент ++@@ -508,9 +514,10 @@ class SaleForm(QDialog): ++ None ++ """ ++ logger.info("Запуск функции tracking_button_pressing") ++- if event.key() == QtCore.Qt.Key_Delete: ++- self.deleting_selected_record() ++- elif event.key() == QtCore.Qt.Key_Backspace: +++ if ( +++ event.key() == QtCore.Qt.Key_Delete +++ or event.key() == QtCore.Qt.Key_Backspace +++ ): ++ self.deleting_selected_record() ++ ++ def deleting_selected_record(self) -> None: ++@@ -533,23 +540,33 @@ class SaleForm(QDialog): ++ if current_row == system.sale_checkbox_row: ++ # Удаляется исключенный взрослый ++ logger.debug(f"Удаляется исключенный взрослый row={current_row}") ++- logger.debug(f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") +++ logger.debug( +++ f"ДО сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" +++ ) ++ ++ # Сбросить флаги ++ system.sale_checkbox_row = None ++ system.exclude_from_sale = 0 ++ system.sale_dict["detail"][4] = 0 ++ ++- logger.debug(f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") +++ logger.debug( +++ f"ПОСЛЕ сброса: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" +++ ) ++ elif current_row < system.sale_checkbox_row: ++ # Удаляется строка выше исключенного ++- logger.debug(f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}") ++- logger.debug(f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}") +++ logger.debug( +++ f"Удаляется строка row={current_row} ВЫШЕ исключенного row={system.sale_checkbox_row}" +++ ) +++ logger.debug( +++ f"ДО корректировки: sale_checkbox_row={system.sale_checkbox_row}" +++ ) ++ ++ # Уменьшить индекс ++ system.sale_checkbox_row -= 1 ++ ++- logger.debug(f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}") +++ logger.debug( +++ f"ПОСЛЕ корректировки: sale_checkbox_row={system.sale_checkbox_row}" +++ ) ++ ++ # Удалить строку ++ self.ui.tableWidget_2.removeRow(current_row) ++@@ -775,7 +792,7 @@ class SaleForm(QDialog): ++ query: str = "%" + self.ui.lineEdit.text() + "%" ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client).filter(Client.last_name.ilike(query)).all() ++ ) ++ for client in search: ++@@ -791,12 +808,12 @@ class SaleForm(QDialog): ++ elif index == 0: ++ # Поиск по фамилии и имени ++ self.ui.tableWidget.setRowCount(0) ++- search: list[Type[Client]] = self.ui.lineEdit.text().title() +++ search: list[type[Client]] = self.ui.lineEdit.text().title() ++ # Разбиваем поисковую фразу на две ++ lst: Any = search.split() ++ if len(lst) == 2: ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter( ++ and_( ++@@ -826,7 +843,7 @@ class SaleForm(QDialog): ++ # Поиск инвалидов ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter(Client.privilege.ilike("%" + "и")) ++ .all() ++@@ -845,7 +862,7 @@ class SaleForm(QDialog): ++ # Поиск многодетных ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter(Client.privilege.ilike("%" + "м")) ++ .all() ++@@ -897,11 +914,11 @@ class SaleForm(QDialog): ++ self.ui.tableWidget_3.setColumnWidth(3, 7) ++ # Если продажа новая - обновляем статус ++ system.sale_status = 0 ++- logger.warning(f"Статус продажи - новая") +++ logger.warning("Статус продажи - новая") ++ row_number: int = self.ui.tableWidget.currentRow() ++ res: str = self.ui.tableWidget.item(row_number, 5).text() ++ with Session(system.engine) as session: ++- search_client: Type[Client] = ( +++ search_client: type[Client] = ( ++ session.query(Client).filter_by(id=res).first() ++ ) ++ # Вычисляем возраст клиента ++@@ -1141,12 +1158,16 @@ class SaleForm(QDialog): ++ ++ # Рассчитываем цену со скидкой ++ if adult_with_discount_count > 0: ++- price_adult_with_discount = int(system.sale_dict["price_adult"] * (1 - system.sale_discount / 100)) +++ price_adult_with_discount = int( +++ system.sale_dict["price_adult"] * (1 - system.sale_discount / 100) +++ ) ++ else: ++ price_adult_with_discount = 0 ++ ++ if child_with_discount_count > 0: ++- price_child_with_discount = int(system.sale_dict["price_child"] * (1 - system.sale_discount / 100)) +++ price_child_with_discount = int( +++ system.sale_dict["price_child"] * (1 - system.sale_discount / 100) +++ ) ++ else: ++ price_child_with_discount = 0 ++ ++@@ -1166,8 +1187,12 @@ class SaleForm(QDialog): ++ ) ++ ++ logger.debug(f"detail ИТОГО: {system.sale_dict['detail']}") ++- logger.debug(f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}") ++- logger.debug(f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}") +++ logger.debug( +++ f"Взрослых со скидкой: {adult_with_discount_count}, цена 1 билета: {price_adult_with_discount}" +++ ) +++ logger.debug( +++ f"Детей со скидкой: {child_with_discount_count}, цена 1 билета: {price_child_with_discount}" +++ ) ++ ++ # Считаем без исключённых взрослых ++ adult_count_in_sale = 0 ++@@ -1186,7 +1211,9 @@ class SaleForm(QDialog): ++ ++ # Синхронизируем ++ system.sale_dict["kol_adult"] = adult_count_in_sale ++- logger.debug(f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}") +++ logger.debug( +++ f"Синхронизация: kol_adult={adult_count_in_sale}, kol_child={system.sale_dict['kol_child']}" +++ ) ++ ++ # Считаем итог ++ itog: int = calculate_itog() ++@@ -1225,7 +1252,7 @@ class SaleForm(QDialog): ++ system.sale_tickets = tickets ++ ++ # Проверяем есть ли в продаже взрослый ++- if system.sale_dict['kol_adult'] >= 1 or system.exclude_from_sale == 1: +++ if system.sale_dict["kol_adult"] >= 1 or system.exclude_from_sale == 1: ++ self.ui.pushButton_5.setEnabled(True) ++ else: ++ self.ui.pushButton_5.setEnabled(False) ++@@ -1244,15 +1271,18 @@ class SaleForm(QDialog): ++ # Если есть исключённый ++ if row == system.sale_checkbox_row: ++ checkbox.setEnabled(True) ++- logger.debug(f"Checkbox row={row} enabled (исключенный)") +++ logger.debug( +++ f"Checkbox row={row} enabled (исключенный)" +++ ) ++ else: ++ checkbox.setEnabled(False) ++ logger.debug(f"Checkbox row={row} disabled") ++ else: ++ # Нет исключённых - все доступны ++ checkbox.setEnabled(True) ++- logger.debug(f"Checkbox row={row} enabled (нет исключенных)") ++- +++ logger.debug( +++ f"Checkbox row={row} enabled (нет исключенных)" +++ ) ++ ++ def apply_discounts(self, row: int, price: int, type_ticket: str) -> None: ++ """ ++@@ -1288,7 +1318,9 @@ class SaleForm(QDialog): ++ if type_ticket == "взрослый" and system.exclude_from_sale == 1: ++ checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) ++ if checkbox.isChecked() and row == system.sale_checkbox_row: ++- logger.info(f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок") +++ logger.info( +++ f"Взрослый в строке {row} исключен из продажи, пропускаем применение дополнительных скидок" +++ ) ++ return # НЕ применяем скидки из checkBox_2! ++ ++ # Иначе проверяем активен ли checkbox со скидкой и размер > 0 ++@@ -1299,9 +1331,11 @@ class SaleForm(QDialog): ++ # Используем другое имя переменной для цены по акции ++ promo_price = calculate_discount(price, system.sale_discount) ++ # Если checkbox в акт - применяем к этой строке скидку ++- if (self.ui.tableWidget_2.cellWidget(row, 7) ++- .findChild(QCheckBox) ++- .isChecked()): # Если False → применить скидку +++ if ( +++ self.ui.tableWidget_2.cellWidget(row, 7) +++ .findChild(QCheckBox) +++ .isChecked() +++ ): # Если False → применить скидку ++ # Применяем скидку ТОЛЬКО если checkbox НЕ активен ++ if type_ticket == "взрослый": ++ system.count_number_of_visitors["kol_sale_adult"] += 1 ++@@ -1481,7 +1515,7 @@ class SaleForm(QDialog): ++ self.ui.checkBox_2.setChecked(True) ++ self.ui.comboBox_2.setCurrentIndex(10) ++ # Добавляем информацию о скидке ++- system.sale_dict['detail'][4] = 50 +++ system.sale_dict["detail"][4] = 50 ++ # Разрешаем выбор времени посещения ++ self.ui.comboBox.setEnabled(True) ++ ++@@ -1544,7 +1578,9 @@ class SaleForm(QDialog): ++ None ++ """ ++ logger.info("Запуск функции adult_exclusion") ++- logger.debug(f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") +++ logger.debug( +++ f"Входные параметры: row={row}, sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" +++ ) ++ ++ # Получаем checkbox для проверки ++ checkbox = self.ui.tableWidget_2.cellWidget(row, 8).findChild(QCheckBox) ++@@ -1577,14 +1613,16 @@ class SaleForm(QDialog): ++ ++ # Проверка - единственный взрослый? ++ if adult_count == 1 and child_count == 0: ++- logger.warning("Невозможно исключить единственного взрослого в продаже!") +++ logger.warning( +++ "Невозможно исключить единственного взрослого в продаже!" +++ ) ++ ++ QMessageBox.warning( ++ self, ++ "Невозможно исключить взрослого", ++ "В продаже только один взрослый!\n" ++ "Нельзя исключить единственного взрослого из продажи.\n" ++- "Добавьте ребенка или других взрослых." +++ "Добавьте ребенка или других взрослых.", ++ ) ++ ++ checkbox.blockSignals(True) ++@@ -1596,24 +1634,30 @@ class SaleForm(QDialog): ++ ++ # Первое исключение взрослого ++ logger.info("Исключаем взрослого из продажи") ++- system.sale_dict["detail"][4] = system.sale_discount if system.sale_discount else 0 ++- logger.debug(f"Установлен флаг исключения: detail[4] = 100") +++ system.sale_dict["detail"][4] = ( +++ system.sale_discount if system.sale_discount else 0 +++ ) +++ logger.debug("Установлен флаг исключения: detail[4] = 100") ++ system.sale_checkbox_row = row ++ system.exclude_from_sale = 1 ++ self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("н")) ++- logger.debug(f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") +++ logger.debug( +++ f"Установлены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" +++ ) ++ ++ elif system.sale_checkbox_row == row: ++ logger.debug(f"Взрослый row={row} уже исключен, пропускаем") ++ return ++ ++ else: ++- logger.warning(f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}") +++ logger.warning( +++ f"Попытка исключить второго взрослого row={row}, уже исключен row={system.sale_checkbox_row}" +++ ) ++ QMessageBox.warning( ++ self, ++ "Невозможно исключить взрослого", ++ f"В продаже уже есть исключенный взрослый (строка {system.sale_checkbox_row})!\n" ++- "Можно исключить только одного взрослого." +++ "Можно исключить только одного взрослого.", ++ ) ++ ++ # СНЯТЬ checkbox ++@@ -1629,19 +1673,27 @@ class SaleForm(QDialog): ++ if row == system.sale_checkbox_row: ++ # Это действительно исключенный взрослый - возвращаем ++ logger.info(f"Возвращаем взрослого row={row} в продажу") ++- logger.debug(f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}") +++ logger.debug( +++ f"ДО возврата: detail[0]={system.sale_dict['detail'][0]}, detail[1]={system.sale_dict['detail'][1]}, detail[4]={system.sale_dict['detail'][4]}" +++ ) ++ self.ui.tableWidget_2.setItem(row, 4, QTableWidgetItem("-")) ++ if self.ui.checkBox_2.isChecked(): ++ system.sale_dict["detail"][4] = system.sale_discount ++ else: ++ system.sale_dict["detail"][4] = 0 ++- logger.debug(f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}") +++ logger.debug( +++ f"ПОСЛЕ возврата: detail[4]={system.sale_dict['detail'][4]}" +++ ) ++ system.sale_checkbox_row = None ++ system.exclude_from_sale = 0 ++- logger.debug(f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}") +++ logger.debug( +++ f"Сброшены флаги: sale_checkbox_row={system.sale_checkbox_row}, exclude_from_sale={system.exclude_from_sale}" +++ ) ++ else: ++ # Это другая строка, не исключенный взрослый ++- logger.debug(f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем") +++ logger.debug( +++ f"row={row} != sale_checkbox_row={system.sale_checkbox_row}, пропускаем" +++ ) ++ ++ def generate_ticket_list( ++ self, ++@@ -1698,7 +1750,7 @@ class SaleForm(QDialog): ++ pc_name=system.pc_name, ++ datetime=dt.datetime.now(), ++ ) ++- logger.debug(f"Объект Sale создан") +++ logger.debug("Объект Sale создан") ++ ++ with Session(system.engine) as session: ++ session.add(add_sale) ++@@ -1731,7 +1783,9 @@ class SaleForm(QDialog): ++ session.add(add_ticket) ++ ++ session.commit() ++- logger.info(f"Продажа {system.sale_id} и все билеты успешно сохранены") +++ logger.info( +++ f"Продажа {system.sale_id} и все билеты успешно сохранены" +++ ) ++ ++ self.close() ++ else: ++@@ -1744,9 +1798,7 @@ class SaleForm(QDialog): ++ logger.warning(f"Ошибка сохранения продажи: {e}") ++ system.sale_id = None ++ windows.info_window( ++- "Ошибка базы данных", ++- "Не удалось сохранить продажу", ++- f"Ошибка: {e}" +++ "Ошибка базы данных", "Не удалось сохранить продажу", f"Ошибка: {e}" ++ ) ++ ++ @logger.catch() ++@@ -1775,17 +1827,19 @@ class SaleForm(QDialog): ++ system, ++ pq, ++ Session, ++- self # передаем self, чтобы вызвать print_saved_tickets +++ self, # передаем self, чтобы вызвать print_saved_tickets ++ ) ++ ++ # Подключение сигналов ++ self.worker.progress_updated.connect(self.progress_window.update_status) ++ self.worker.finished.connect(self.on_transaction_finished) ++- self.worker.save_sale_signal.connect(self.save_sale, Qt.BlockingQueuedConnection) +++ self.worker.save_sale_signal.connect( +++ self.save_sale, Qt.BlockingQueuedConnection +++ ) ++ self.worker.error_signal.connect(self.handle_error) # Обработка ошибок ++ self.worker.info_signal.connect(self.handle_info) ++ self.worker.print_ticket_signal.connect(self.print_saved_tickets) ++- #self.worker.close_window_signal.connect(self.close) +++ # self.worker.close_window_signal.connect(self.close) ++ self.worker.close_window_signal.connect(self.progress_window.close) ++ ++ # Поток ++@@ -1799,7 +1853,6 @@ class SaleForm(QDialog): ++ ++ self.thread.start() ++ ++- ++ def handle_error(self, title: str, text: str, details: str): ++ """ ++ Обрабатывает ошибку, закрывая окно прогресса и отображая сообщение с ошибкой. ++@@ -1813,7 +1866,7 @@ class SaleForm(QDialog): ++ None: Функция ничего не возвращает, но закрывает окно прогресса и отображает сообщение об ошибке. ++ """ ++ # Закрытие окна прогресса перед отображением ошибки ++- if hasattr(self, 'progress_window') and self.progress_window: +++ if hasattr(self, "progress_window") and self.progress_window: ++ self.progress_window.close() ++ ++ # Создание окна с ошибкой ++@@ -1848,19 +1901,18 @@ class SaleForm(QDialog): ++ None: Эта функция ничего не возвращает, но закрывает окно прогресса и логирует завершение транзакции. ++ """ ++ logger.info("Запуск функции on_transaction_finished") ++- if hasattr(self, '_transaction_finished') and self._transaction_finished: +++ if hasattr(self, "_transaction_finished") and self._transaction_finished: ++ return ++ self._transaction_finished = True ++ logger.info("Завершение транзакции") ++ # Закрываем окно прогресса ++- if hasattr(self, 'progress_window') and self.progress_window: +++ if hasattr(self, "progress_window") and self.progress_window: ++ self.progress_window.close() ++ self.progress_window = None ++ ++ if self.main_window: ++ self.main_window.main_button_all_sales() ++ ++- ++ @logger.catch() ++ def sale_return(self): ++ """ ++@@ -1893,7 +1945,9 @@ class SaleForm(QDialog): ++ if sale.status == 5: ++ logger.debug("Требуется частичный возврат.") ++ partial_return: int = int(sale.partial_return) ++- new_tickets: dict | dict = generating_parts_for_partial_returns(tickets, partial_return) +++ new_tickets: dict = generating_parts_for_partial_returns( +++ tickets, partial_return +++ ) ++ logger.debug(f"Список билетов: {new_tickets}") ++ # 1 - карта, 2 - наличные ++ if sale.payment_type == 1: ++@@ -1917,7 +1971,7 @@ class SaleForm(QDialog): ++ logger.debug("Продажа оплачена. Запускаем возврат") ++ if payment_type == 102: ++ state_check = pq.check_open( ++- tickets, payment_type, system.user, 2, 1,None +++ tickets, payment_type, system.user, 2, 1, None ++ ) ++ elif payment_type == 101: ++ logger.debug( ++@@ -1932,7 +1986,9 @@ class SaleForm(QDialog): ++ amount=price, ++ progress_signal=None, ++ operation_type=2, # возврат ++- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) +++ error_callback=lambda title, msg, code: windows.info_window( +++ title, msg, str(code) +++ ), ++ ) ++ elif sale.status == 5: ++ bank, payment = pq.universal_terminal_operation( ++@@ -1940,7 +1996,9 @@ class SaleForm(QDialog): ++ amount=partial_return, ++ progress_signal=None, ++ operation_type=2, # возврат ++- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) +++ error_callback=lambda title, msg, code: windows.info_window( +++ title, msg, str(code) +++ ), ++ ) ++ if bank == 1: ++ check = pq.read_pinpad_file(remove_newline=False) ++@@ -2026,7 +2084,9 @@ class SaleForm(QDialog): ++ amount=price, ++ progress_signal=None, ++ operation_type=2, # возврат ++- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) +++ error_callback=lambda title, msg, code: windows.info_window( +++ title, msg, str(code) +++ ), ++ ) ++ ++ if bank == 1: ++@@ -2102,7 +2162,9 @@ class SaleForm(QDialog): ++ amount=price, ++ progress_signal=None, ++ operation_type=3, # отмена ++- error_callback=lambda title, msg, code: windows.info_window(title, msg, str(code)) +++ error_callback=lambda title, msg, code: windows.info_window( +++ title, msg, str(code) +++ ), ++ ) ++ if bank == 1: ++ check = pq.read_pinpad_file(remove_newline=False) ++@@ -2287,7 +2349,7 @@ class SaleForm(QDialog): ++ Во всех остальных случаях возвращает False. ++ """ ++ # Проверяем наличие атрибута и что он не None ++- if not hasattr(system, 'ticket_print') or system.ticket_print is None: +++ if not hasattr(system, "ticket_print") or system.ticket_print is None: ++ return False ++ ++ # Приводим к строке и сравниваем с 'on' ++@@ -2300,7 +2362,9 @@ class SaleForm(QDialog): ++ """ ++ logger.info("Запуск функции print_saved_tickets") ++ if not self.is_printing_enabled(): ++- logger.info("Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)") +++ logger.info( +++ "Печать билетов отключена (параметр 'ticket' не равен 'on' или отсутствует)" +++ ) ++ return ++ try: ++ self.sale_generate_saved_tickets() ++@@ -2361,9 +2425,7 @@ class SaleForm(QDialog): ++ # Оплата только для новых продаж ++ if system.sale_status != 0: ++ windows.info_window( ++- "Внимание", ++- "Оплата картой доступна только для новых продаж.", ++- "" +++ "Внимание", "Оплата картой доступна только для новых продаж.", "" ++ ) ++ return ++ ++@@ -2371,8 +2433,7 @@ class SaleForm(QDialog): ++ logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") ++ # Создаем тестовый слип-чек ++ slip = "DEBUG_SLIP\nДата: {}\nСумма: {}\nОдобрено".format( ++- dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), ++- txt +++ dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt ++ ) ++ # Сохраняем тестовые данные ++ self.save_debug_payment(Payment.Card, slip) ++@@ -2388,9 +2449,7 @@ class SaleForm(QDialog): ++ # Оплата только для новых продаж ++ if system.sale_status != 0: ++ windows.info_window( ++- "Внимание", ++- "Оплата наличными доступна только для новых продаж.", ++- "" +++ "Внимание", "Оплата наличными доступна только для новых продаж.", "" ++ ) ++ return ++ logger.info("Оплата наличными") ++@@ -2402,14 +2461,13 @@ class SaleForm(QDialog): ++ windows.info_window( ++ "Внимание", ++ "Оплата картой доступна только для новых или не фискализированных продаж.", ++- "" +++ "", ++ ) ++ return ++ if dev_mode: ++ logger.info("РЕЖИМ ОТЛАДКИ: Имитация offline оплаты") ++ slip = "DEBUG_SLIP_OFFLINE\nДата: {}\nСумма: {}\nОффлайн".format( ++- dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), ++- txt +++ dt.datetime.now().strftime("%d.%m.%Y %H:%M:%S"), txt ++ ) ++ self.save_debug_payment(Payment.Offline, slip) ++ self.accept() ++@@ -2417,12 +2475,12 @@ class SaleForm(QDialog): ++ ++ user_choice = windows.info_dialog_window( ++ "Подтверждение", ++- f"Вы точно хотите фискализировать операцию?\n\n" ++- f"Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" ++- f"Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" ++- f"Операция считается успешной, если в банковском слип-чеке:\n" ++- f" - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" ++- f" - указано слово 'ОДОБРЕНО'.", +++ "Вы точно хотите фискализировать операцию?\n\n" +++ "Это надо делать ТОЛЬКО после успешной проверки проведения операции по банковскому терминалу!\n\n" +++ "Для этого выполните: Касса -> Операции с банковским терминалом -> Печать ранее подготовленного документа.\n\n" +++ "Операция считается успешной, если в банковском слип-чеке:\n" +++ " - сумма, дата и время проведения операции операции совпадают с данными из заказа;\n" +++ " - указано слово 'ОДОБРЕНО'.", ++ ) ++ if user_choice == 1: ++ logger.info("Оплата банковской картой offline") ++@@ -2435,10 +2493,12 @@ class SaleForm(QDialog): ++ def save_debug_payment(payment_type, slip_text): ++ """Сохранение тестового платежа в режиме отладки""" ++ try: ++- logger.debug(f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}") +++ logger.debug( +++ f"Сохранение тестового платежа: payment_type = {payment_type}, slip = {slip_text}" +++ ) ++ # ... код для сохранения в БД ... ++ except Exception as e: ++- logger.error(f"Ошибка сохранения тестового платежа: {str(e)}") +++ logger.error(f"Ошибка сохранения тестового платежа: {e!s}") ++ ++ ++ class PayForm(QDialog): ++@@ -2515,7 +2575,9 @@ class SlipForm(QDialog): ++ None: Функция не возвращает значений, отображает данные слип-чека в форме. ++ """ ++ logger.info("Запуск функции get_slip") ++- card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data(system.sale_id) +++ card_tail, merchant_id, rrn_value, load_slip = system.get_slip_data( +++ system.sale_id +++ ) ++ ++ slip: SlipForm = SlipForm() ++ slip.ui.label_5.setText(card_tail) ++@@ -2663,7 +2725,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ # Поиск по номеру телефона ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client).filter(Client.phone.ilike(user_filter)).all() ++ ) ++ for client in search: ++@@ -2682,7 +2744,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ # Поиск по фамилии ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter(Client.last_name.ilike(user_filter)) ++ .all() ++@@ -2706,10 +2768,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ # Разбиваем поисковую фразу на две ++ lst: Any = user_filter.split() ++ # Инициализация переменной search пустым списком, чтобы избежать ошибки ++- search: list[Type[Client]] = [] +++ search: list[type[Client]] = [] ++ if len(lst) == 2: ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter( ++ and_( ++@@ -2741,7 +2803,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ # Поиск инвалидов ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter(Client.privilege.ilike("%" + "и")) ++ .all() ++@@ -2762,7 +2824,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ # Поиск многодетных ++ self.ui.tableWidget.setRowCount(0) ++ with Session(system.engine) as session: ++- search: list[Type[Client]] = ( +++ search: list[type[Client]] = ( ++ session.query(Client) ++ .filter(Client.privilege.ilike("%" + "м")) ++ .all() ++@@ -2804,7 +2866,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ logger.error("Ячейка пуста или не существует.") ++ return ++ with Session(system.engine) as session: ++- search_client: Type[Client] = ( +++ search_client: type[Client] = ( ++ session.query(Client).filter_by(id=client_id).first() ++ ) ++ if search_client is None: ++@@ -2834,7 +2896,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ client.show() ++ # Сохраняем параметры данных об уже существующем клиенте ++ system.client_update = 1 ++- logger.info(f"Обновляем информацию о клиенте") +++ logger.info("Обновляем информацию о клиенте") ++ client.exec_() ++ ++ def main_filter_clear(self) -> None: ++@@ -2972,7 +3034,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ sale.ui.pushButton_7.setEnabled(False) ++ sale.ui.pushButton_8.setEnabled(False) ++ # Если продажа требует повторный возврат по банковскому терминалу ++- elif sale_status == 3: +++ elif sale_status == 3 or sale_status == 5: ++ # Кнопка сохранить ++ sale.ui.pushButton_3.setEnabled(False) ++ # Кнопка оплатить ++@@ -2987,37 +3049,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ sale.ui.pushButton_7.setEnabled(False) ++ sale.ui.pushButton_8.setEnabled(False) ++ # Если продажа требует частичный возврат ++- elif sale_status == 5: ++- # Кнопка сохранить ++- sale.ui.pushButton_3.setEnabled(False) ++- # Кнопка оплатить ++- sale.ui.pushButton_5.setEnabled(False) ++- # Кнопка обновить ++- sale.ui.pushButton_10.setEnabled(False) ++- # Кнопка возврат ++- sale.ui.pushButton_6.setEnabled(True) ++- # Кнопка отмены платежа по банковской карте ++- sale.ui.pushButton_14.setEnabled(False) ++- # Кнопки просмотра и печати билетов ++- sale.ui.pushButton_7.setEnabled(False) ++- sale.ui.pushButton_8.setEnabled(False) ++- # Если продажа требует частичный возврат ++- elif sale_status == 7: ++- # Кнопка сохранить ++- sale.ui.pushButton_3.setEnabled(False) ++- # Кнопка оплатить ++- sale.ui.pushButton_5.setEnabled(False) ++- # Кнопка обновить ++- sale.ui.pushButton_10.setEnabled(False) ++- # Кнопка возврат ++- sale.ui.pushButton_6.setEnabled(False) ++- # Кнопка отмены платежа по банковской карте ++- sale.ui.pushButton_14.setEnabled(False) ++- # Кнопки просмотра и печати билетов ++- sale.ui.pushButton_7.setEnabled(False) ++- sale.ui.pushButton_8.setEnabled(False) ++- # Если продажа отменена ++- elif sale_status == 8: +++ elif sale_status == 7 or sale_status == 8: ++ # Кнопка сохранить ++ sale.ui.pushButton_3.setEnabled(False) ++ # Кнопка оплатить ++@@ -3159,16 +3191,30 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ else: ++ # Определение периода для 1, 3 и 7 дней без учета времени ++ if self.ui.radioButton.isChecked(): ++- filter_start = dt.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) ++- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) +++ filter_start = dt.datetime.today().replace( +++ hour=0, minute=0, second=0, microsecond=0 +++ ) +++ filter_end = dt.datetime.today().replace( +++ hour=23, minute=59, second=59, microsecond=999999 +++ ) ++ elif self.ui.radioButton_2.isChecked(): ++- filter_start = (dt.datetime.today() - timedelta(days=3)).replace(hour=0, minute=0, second=0, microsecond=0) ++- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) +++ filter_start = (dt.datetime.today() - timedelta(days=3)).replace( +++ hour=0, minute=0, second=0, microsecond=0 +++ ) +++ filter_end = dt.datetime.today().replace( +++ hour=23, minute=59, second=59, microsecond=999999 +++ ) ++ elif self.ui.radioButton_3.isChecked(): ++- filter_start = (dt.datetime.today() - timedelta(days=7)).replace(hour=0, minute=0, second=0, microsecond=0) ++- filter_end = dt.datetime.today().replace(hour=23, minute=59, second=59, microsecond=999999) +++ filter_start = (dt.datetime.today() - timedelta(days=7)).replace( +++ hour=0, minute=0, second=0, microsecond=0 +++ ) +++ filter_end = dt.datetime.today().replace( +++ hour=23, minute=59, second=59, microsecond=999999 +++ ) ++ # Определяем, какие статусы фильтровать ++- sale_status_filter = [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] +++ sale_status_filter = ( +++ [2, 3, 4, 5, 6, 8, 9] if self.ui.checkBox.isChecked() else [] +++ ) ++ ++ # Генерация запроса ++ with Session(system.engine) as session: ++@@ -3314,7 +3360,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ system.sale_dict.update(sale_initial_values) ++ # сбрасываем id и статус продажи ++ system.sale_id = None ++- system.sale_status = 0 # Т.е. новая продажа +++ system.sale_status = 0 # Т.е. новая продажа ++ system.sale_tickets = None ++ sale.exec_() ++ ++@@ -3389,30 +3435,36 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ logger.info("Запуск функции fetch_stat_data") ++ with Session(system.engine) as session: ++ sales = session.execute( ++- select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) ++- .where(and_(Sale.status == "1", Sale.datetime.between(dt1, dt2))) +++ select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( +++ and_(Sale.status == "1", Sale.datetime.between(dt1, dt2)) +++ ) ++ ).all() ++ sales_return = session.execute( ++- select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status) ++- .where(and_( ++- Sale.datetime_return.between(dt1, dt2), ++- Sale.status.in_([2, 3, 4, 5, 6]) ++- )) +++ select(Sale.pc_name, Sale.payment_type, Sale.price, Sale.status).where( +++ and_( +++ Sale.datetime_return.between(dt1, dt2), +++ Sale.status.in_([2, 3, 4, 5, 6]), +++ ) +++ ) ++ ).all() ++ tickets = session.execute( ++ select( ++- Ticket.ticket_type, Ticket.arrival_time, Ticket.description, Sale.status, Sale.id, Ticket.price +++ Ticket.ticket_type, +++ Ticket.arrival_time, +++ Ticket.description, +++ Sale.status, +++ Sale.id, +++ Ticket.price, ++ ).where( ++ and_( ++ Sale.id == Ticket.id_sale, ++ Sale.status == "1", ++ Ticket.datetime.between(dt1, dt2), ++- ) +++ ) ++ ) ++ ).all() ++ return sales, sales_return, tickets ++ ++- ++ def fill_sales_table(self, data): ++ """ ++ Заполнение таблицы продаж данными. ++@@ -3431,9 +3483,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ for row, row_data in enumerate(data): ++ for col, value in enumerate(row_data): ++ if value is not None: ++- self.ui.tableWidget_4.setItem(row, col, QTableWidgetItem(str(value))) ++- ++- +++ self.ui.tableWidget_4.setItem( +++ row, col, QTableWidgetItem(str(value)) +++ ) ++ ++ def fill_ticket_table(self, data): ++ """ ++@@ -3506,7 +3558,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ if os.path.exists(path): ++ os.remove(path) ++ # Проверяем, есть ли данные о продажах в system.sales_data_summary ++- if not hasattr(system, 'sales_data_summary') or not system.sales_data_summary: +++ if not hasattr(system, "sales_data_summary") or not system.sales_data_summary: ++ logger.error("Нет данных о продажах для отчета кассира") ++ return ++ # Получаем имя текущего компьютера ++@@ -3529,14 +3581,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ++ str(total_sales_card), ++ str(total_sales_cash), ++ str(total_returns_card), ++- str(total_returns_cash) +++ str(total_returns_cash), ++ ] ++ ++ # Получаем даты для отчета ++ dt1, dt2 = self.get_date_range_from_ui() ++ ++ # Получаем информацию о кассире ++- kassir = system.user # Предполагаем, что данные о кассире хранятся в system.user +++ kassir = ( +++ system.user +++ ) # Предполагаем, что данные о кассире хранятся в system.user ++ ++ # Вызываем функцию для формирования отчета ++ otchet.otchet_kassira(values, dt1, dt2, kassir) ++diff --git a/modules/config.py b/modules/config.py ++index 3cb0a17..61938a7 100644 ++--- a/modules/config.py +++++ b/modules/config.py ++@@ -2,11 +2,10 @@ import logging ++ import os ++ from configparser import ( ++ ConfigParser, ++- NoSectionError, ++- NoOptionError, ++ MissingSectionHeaderError, +++ NoOptionError, +++ NoSectionError, ++ ) ++-from typing import List, Optional ++ ++ logger = logging.getLogger(__name__) ++ ++@@ -18,7 +17,7 @@ class Config: ++ # Инициализируем список ПК ++ self.pcs = self._get_pc_list() ++ ++- def _get_pc_list(self) -> List[str]: +++ def _get_pc_list(self) -> list[str]: ++ """Возвращает список имен ПК из конфигурации""" ++ pc_count = int(self.get("kol")) ++ pc_list = [] ++@@ -122,7 +121,6 @@ class Config: ++ ) ++ config_data[key] = config.get(section, key) ++ ++- ++ return config_data ++ except (NoSectionError, NoOptionError) as e: ++ logger.error(f"Ошибка в конфигурационном файле: {e}") ++@@ -136,6 +134,6 @@ class Config: ++ logger.error(f"Неизвестная ошибка при чтении файла конфигурации: {e}") ++ raise RuntimeError(f"Неизвестная ошибка: {e}") ++ ++- def get(self, key: str) -> Optional[str]: +++ def get(self, key: str) -> str | None: ++ value = self.config_data.get(key) ++ return value ++diff --git a/modules/libfptr10.py b/modules/libfptr10.py ++index ced442d..b0c9fc1 100644 ++--- a/modules/libfptr10.py +++++ b/modules/libfptr10.py ++@@ -1,27 +1,25 @@ ++-# -*- coding: utf-8 -*- ++ # AUTO GENERATED FILE ++ ++ import ctypes ++-import sys ++-import json ++ import datetime +++import json ++ import os ++ import platform ++-import warnings +++import sys ++ ++ if sys.version_info[0] == 3: ++- if platform.system() == 'Windows': +++ if platform.system() == "Windows": ++ from winreg import * ++ TEXT = str ++ RANGE = range ++ else: ++- if platform.system() == 'Windows': +++ if platform.system() == "Windows": ++ from _winreg import * ++ TEXT = basestring ++ RANGE = xrange ++ ++ ++-class IFptr(object): +++class IFptr: ++ ( ++ LIBFPTR_PARAM_TEXT, ++ LIBFPTR_PARAM_TEXT_WRAP, ++@@ -1672,231 +1670,294 @@ class IFptr(object): ++ DEFAULT_BUFF_SIZE = 512 ++ ++ CREATE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.POINTER(ctypes.c_void_p)) ++- CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.POINTER(ctypes.c_void_p), ++- ctypes.c_wchar_p) +++ CREATE_WITH_ID_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.POINTER(ctypes.c_void_p), ctypes.c_wchar_p +++ ) ++ DESTROY_METHOD = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_void_p)) ++ GET_VERSION_METHOD = ctypes.CFUNCTYPE(ctypes.c_char_p) ++ ++- METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p) ++- ++- IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p) ++- ++- GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p) ++- GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_int) ++- GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_int) ++- RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p) ++- ++- SET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p) ++- GET_SETTINGS_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_int) ++- ++- SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_wchar_p) ++- GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_wchar_p, ++- ctypes.c_int) ++- ++- SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) ++- GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int) ++- ++- SET_INT_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_uint) ++- GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, ++- ctypes.c_void_p, ++- ctypes.c_int) ++- ++- SET_BOOL_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_int) ++- GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_int) ++- ++- SET_DOUBLE_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_double) ++- GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, ++- ctypes.c_void_p, ++- ctypes.c_int) ++- ++- SET_STRING_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_wchar_p) ++- GET_STRING_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_wchar_p, ctypes.c_int) ++- ++- SET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ++- ctypes.c_int, ++- ctypes.c_int) ++- GET_DATETIME_METHOD = ctypes.CFUNCTYPE(None, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.POINTER(ctypes.c_int), ++- ctypes.POINTER(ctypes.c_int), ++- ctypes.POINTER(ctypes.c_int), ++- ctypes.POINTER(ctypes.c_int), ++- ctypes.POINTER(ctypes.c_int), ++- ctypes.POINTER(ctypes.c_int)) ++- IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_int) ++- ++- LOG_WRITE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p, ++- ctypes.c_int, ++- ctypes.c_wchar_p) ++- ++- SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_int, ++- ctypes.c_void_p) ++- ++- CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ++- ctypes.c_void_p, ++- ctypes.c_wchar_p) +++ METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) +++ +++ IS_OPENED_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) +++ +++ GET_ERROR_CODE_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p) +++ GET_ERROR_DESCRIPTION_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int +++ ) +++ GET_ERROR_RECOMMENDATION_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int +++ ) +++ RESET_ERROR_METHOD = ctypes.CFUNCTYPE(None, ctypes.c_void_p) +++ +++ SET_SETTINGS_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p +++ ) +++ GET_SETTINGS_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int +++ ) +++ +++ SET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( +++ None, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p +++ ) +++ GET_SINGLE_SETTING_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_int +++ ) +++ +++ SET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( +++ None, +++ ctypes.c_void_p, +++ ctypes.c_int, +++ ctypes.POINTER(ctypes.c_ubyte), +++ ctypes.c_int, +++ ) +++ GET_BYTEARRAY_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, +++ ctypes.c_void_p, +++ ctypes.c_int, +++ ctypes.POINTER(ctypes.c_ubyte), +++ ctypes.c_int, +++ ) +++ +++ SET_INT_METHOD = ctypes.CFUNCTYPE( +++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_uint +++ ) +++ GET_INT_METHOD = ctypes.CFUNCTYPE(ctypes.c_uint, ctypes.c_void_p, ctypes.c_int) +++ +++ SET_BOOL_METHOD = ctypes.CFUNCTYPE( +++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_int +++ ) +++ GET_BOOL_METHOD = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_int) +++ +++ SET_DOUBLE_METHOD = ctypes.CFUNCTYPE( +++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_double +++ ) +++ GET_DOUBLE_METHOD = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_void_p, ctypes.c_int) +++ +++ SET_STRING_METHOD = ctypes.CFUNCTYPE( +++ None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p +++ ) +++ GET_STRING_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_int +++ ) +++ +++ SET_DATETIME_METHOD = ctypes.CFUNCTYPE( +++ None, +++ ctypes.c_void_p, +++ ctypes.c_int, +++ ctypes.c_int, +++ ctypes.c_int, +++ ctypes.c_int, +++ ctypes.c_int, +++ ctypes.c_int, +++ ctypes.c_int, +++ ) +++ GET_DATETIME_METHOD = ctypes.CFUNCTYPE( +++ None, +++ ctypes.c_void_p, +++ ctypes.c_int, +++ ctypes.POINTER(ctypes.c_int), +++ ctypes.POINTER(ctypes.c_int), +++ ctypes.POINTER(ctypes.c_int), +++ ctypes.POINTER(ctypes.c_int), +++ ctypes.POINTER(ctypes.c_int), +++ ctypes.POINTER(ctypes.c_int), +++ ) +++ IS_PARAM_AVAILABLE_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int +++ ) +++ +++ LOG_WRITE_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int, ctypes.c_wchar_p +++ ) +++ +++ SHOW_PROPERTIES_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p +++ ) +++ +++ CHANGE_LABEL_METHOD = ctypes.CFUNCTYPE( +++ ctypes.c_int, ctypes.c_void_p, ctypes.c_wchar_p +++ ) ++ ++ def __init__(self, lib_path="", fptr_id=""): ++ assert sys.version_info >= (2, 6) ++ self.lib_path = lib_path ++ ++ try: ++- if platform.system() == 'Windows': +++ if platform.system() == "Windows": ++ if len(self.lib_path) == 0: ++- rk = OpenKey(HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT") +++ rk = OpenKey( +++ HKEY_LOCAL_MACHINE, "Software\\ATOL\\Drivers\\10.0\\KKT" +++ ) ++ rv = QueryValueEx(rk, "INSTALL_DIR")[0] ++- self.lib_path = os.path.join(rv, 'bin', 'fptr10.dll') +++ self.lib_path = os.path.join(rv, "bin", "fptr10.dll") ++ else: ++- if not self.lib_path.endswith('fptr10.dll'): ++- self.lib_path = os.path.join(self.lib_path, 'fptr10.dll') +++ if not self.lib_path.endswith("fptr10.dll"): +++ self.lib_path = os.path.join(self.lib_path, "fptr10.dll") ++ try: ++ self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) ++ except OSError: ++- ctypes.CDLL(os.path.join(os.path.dirname(self.lib_path), 'msvcp140.dll'), mode=ctypes.RTLD_LOCAL) +++ ctypes.CDLL( +++ os.path.join(os.path.dirname(self.lib_path), "msvcp140.dll"), +++ mode=ctypes.RTLD_LOCAL, +++ ) ++ self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) ++ ++- elif platform.system() == 'Darwin': ++- if not self.lib_path.endswith('fptr10.framework/fptr10'): ++- self.lib_path = os.path.join(self.lib_path, 'fptr10.framework/fptr10') +++ elif platform.system() == "Darwin": +++ if not self.lib_path.endswith("fptr10.framework/fptr10"): +++ self.lib_path = os.path.join( +++ self.lib_path, "fptr10.framework/fptr10" +++ ) ++ try: ++ self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) ++ except OSError: ++ self.lib_path = lib_path ++- if not self.lib_path.endswith('libfptr10.dylib'): ++- self.lib_path = os.path.join(self.lib_path, 'libfptr10.dylib') +++ if not self.lib_path.endswith("libfptr10.dylib"): +++ self.lib_path = os.path.join(self.lib_path, "libfptr10.dylib") ++ self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) ++ ++ else: ++- if not self.lib_path.endswith('libfptr10.so'): ++- self.lib_path = os.path.join(self.lib_path, 'libfptr10.so') +++ if not self.lib_path.endswith("libfptr10.so"): +++ self.lib_path = os.path.join(self.lib_path, "libfptr10.so") ++ self.library = ctypes.CDLL(self.lib_path, mode=ctypes.RTLD_LOCAL) ++ except OSError: ++ raise Exception( ++- 'Driver library not found in {}'.format( ++- '\"' + ++- os.path.dirname( ++- self.lib_path) + ++- '\"' if len(lib_path) != 0 else 'search folders')) +++ "Driver library not found in {}".format( +++ '"' + os.path.dirname(self.lib_path) + '"' +++ if len(lib_path) != 0 +++ else "search folders" +++ ) +++ ) ++ ++ self.interface = ctypes.c_void_p(0) ++ if fptr_id: ++- _create = self.CREATE_WITH_ID_METHOD(('libfptr_create_with_id', self.library)) ++- create_r = _create(ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id)) +++ _create = self.CREATE_WITH_ID_METHOD( +++ ("libfptr_create_with_id", self.library) +++ ) +++ create_r = _create( +++ ctypes.pointer(self.interface), ctypes.c_wchar_p(fptr_id) +++ ) ++ else: ++- _create = self.CREATE_METHOD(('libfptr_create', self.library)) +++ _create = self.CREATE_METHOD(("libfptr_create", self.library)) ++ create_r = _create(ctypes.pointer(self.interface)) ++ if create_r == -2: ++- raise AttributeError('Invalid [id] format') +++ raise AttributeError("Invalid [id] format") ++ elif create_r != 0: ++- raise Exception('Can`t create driver handle') ++- ++- self._setByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_param_bytearray', self.library)) ++- self._setUserByteArray = self.SET_BYTEARRAY_METHOD(('libfptr_set_user_param_bytearray', self.library)) +++ raise Exception("Can`t create driver handle") +++ +++ self._setByteArray = self.SET_BYTEARRAY_METHOD( +++ ("libfptr_set_param_bytearray", self.library) +++ ) +++ self._setUserByteArray = self.SET_BYTEARRAY_METHOD( +++ ("libfptr_set_user_param_bytearray", self.library) +++ ) ++ self._setNonPrintableByteArray = self.SET_BYTEARRAY_METHOD( ++- ('libfptr_set_non_printable_param_bytearray', self.library)) ++- self._getByteArray = self.GET_BYTEARRAY_METHOD(('libfptr_get_param_bytearray', self.library)) ++- ++- self._setInt = self.SET_INT_METHOD(('libfptr_set_param_int', self.library)) ++- self._setUserInt = self.SET_INT_METHOD(('libfptr_set_user_param_int', self.library)) ++- self._setNonPrintableInt = self.SET_INT_METHOD(('libfptr_set_non_printable_param_int', self.library)) ++- self._getInt = self.GET_INT_METHOD(('libfptr_get_param_int', self.library)) ++- ++- self._setBool = self.SET_BOOL_METHOD(('libfptr_set_param_bool', self.library)) ++- self._setUserBool = self.SET_BOOL_METHOD(('libfptr_set_user_param_bool', self.library)) ++- self._setNonPrintableBool = self.SET_BOOL_METHOD(('libfptr_set_non_printable_param_bool', self.library)) ++- self._getBool = self.GET_BOOL_METHOD(('libfptr_get_param_bool', self.library)) ++- ++- self._setDouble = self.SET_DOUBLE_METHOD(('libfptr_set_param_double', self.library)) ++- self._setUserDouble = self.SET_DOUBLE_METHOD(('libfptr_set_user_param_double', self.library)) ++- self._setNonPrintableDouble = self.SET_DOUBLE_METHOD(('libfptr_set_non_printable_param_double', self.library)) ++- self._getDouble = self.GET_DOUBLE_METHOD(('libfptr_get_param_double', self.library)) ++- ++- self._setDateTime = self.SET_DATETIME_METHOD(('libfptr_set_param_datetime', self.library)) ++- self._setUserDateTime = self.SET_DATETIME_METHOD(('libfptr_set_user_param_datetime', self.library)) +++ ("libfptr_set_non_printable_param_bytearray", self.library) +++ ) +++ self._getByteArray = self.GET_BYTEARRAY_METHOD( +++ ("libfptr_get_param_bytearray", self.library) +++ ) +++ +++ self._setInt = self.SET_INT_METHOD(("libfptr_set_param_int", self.library)) +++ self._setUserInt = self.SET_INT_METHOD( +++ ("libfptr_set_user_param_int", self.library) +++ ) +++ self._setNonPrintableInt = self.SET_INT_METHOD( +++ ("libfptr_set_non_printable_param_int", self.library) +++ ) +++ self._getInt = self.GET_INT_METHOD(("libfptr_get_param_int", self.library)) +++ +++ self._setBool = self.SET_BOOL_METHOD(("libfptr_set_param_bool", self.library)) +++ self._setUserBool = self.SET_BOOL_METHOD( +++ ("libfptr_set_user_param_bool", self.library) +++ ) +++ self._setNonPrintableBool = self.SET_BOOL_METHOD( +++ ("libfptr_set_non_printable_param_bool", self.library) +++ ) +++ self._getBool = self.GET_BOOL_METHOD(("libfptr_get_param_bool", self.library)) +++ +++ self._setDouble = self.SET_DOUBLE_METHOD( +++ ("libfptr_set_param_double", self.library) +++ ) +++ self._setUserDouble = self.SET_DOUBLE_METHOD( +++ ("libfptr_set_user_param_double", self.library) +++ ) +++ self._setNonPrintableDouble = self.SET_DOUBLE_METHOD( +++ ("libfptr_set_non_printable_param_double", self.library) +++ ) +++ self._getDouble = self.GET_DOUBLE_METHOD( +++ ("libfptr_get_param_double", self.library) +++ ) +++ +++ self._setDateTime = self.SET_DATETIME_METHOD( +++ ("libfptr_set_param_datetime", self.library) +++ ) +++ self._setUserDateTime = self.SET_DATETIME_METHOD( +++ ("libfptr_set_user_param_datetime", self.library) +++ ) ++ self._setNonPrintableDateTime = self.SET_DATETIME_METHOD( ++- ('libfptr_set_non_printable_param_datetime', self.library)) ++- self._getDateTime = self.GET_DATETIME_METHOD(('libfptr_get_param_datetime', self.library)) ++- ++- self._setString = self.SET_STRING_METHOD(('libfptr_set_param_str', self.library)) ++- self._setUserString = self.SET_STRING_METHOD(('libfptr_set_user_param_str', self.library)) ++- self._setNonPrintableString = self.SET_STRING_METHOD(('libfptr_set_non_printable_param_str', self.library)) ++- self._getString = self.GET_STRING_METHOD(('libfptr_get_param_str', self.library)) ++- ++- self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD(('libfptr_is_param_available', self.library)) ++- ++- self._setSettings = self.SET_SETTINGS_METHOD(('libfptr_set_settings', self.library)) ++- self._getSettings = self.GET_SETTINGS_METHOD(('libfptr_get_settings', self.library)) ++- ++- self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD(('libfptr_get_single_setting', self.library)) ++- self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD(('libfptr_set_single_setting', self.library)) ++- ++- self._getVersion = self.GET_VERSION_METHOD(('libfptr_get_version_string', self.library)) ++- self._isOpened = self.IS_OPENED_METHOD(('libfptr_is_opened', self.library)) ++- self._errorCode = self.GET_ERROR_CODE_METHOD(('libfptr_error_code', self.library)) ++- self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD(('libfptr_error_description', self.library)) ++- self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD(('libfptr_error_recommendation', self.library)) ++- self._resetError = self.RESET_ERROR_METHOD(('libfptr_reset_error', self.library)) ++- self._logWrite = self.LOG_WRITE_METHOD(('libfptr_log_write_ex', self.library)) ++- self._showProperties = self.SHOW_PROPERTIES_METHOD(('libfptr_show_properties', self.library)) ++- self._changeLabel = self.CHANGE_LABEL_METHOD(('libfptr_change_label', self.library)) +++ ("libfptr_set_non_printable_param_datetime", self.library) +++ ) +++ self._getDateTime = self.GET_DATETIME_METHOD( +++ ("libfptr_get_param_datetime", self.library) +++ ) +++ +++ self._setString = self.SET_STRING_METHOD( +++ ("libfptr_set_param_str", self.library) +++ ) +++ self._setUserString = self.SET_STRING_METHOD( +++ ("libfptr_set_user_param_str", self.library) +++ ) +++ self._setNonPrintableString = self.SET_STRING_METHOD( +++ ("libfptr_set_non_printable_param_str", self.library) +++ ) +++ self._getString = self.GET_STRING_METHOD( +++ ("libfptr_get_param_str", self.library) +++ ) +++ +++ self._isParamAvailable = self.IS_PARAM_AVAILABLE_METHOD( +++ ("libfptr_is_param_available", self.library) +++ ) +++ +++ self._setSettings = self.SET_SETTINGS_METHOD( +++ ("libfptr_set_settings", self.library) +++ ) +++ self._getSettings = self.GET_SETTINGS_METHOD( +++ ("libfptr_get_settings", self.library) +++ ) +++ +++ self._getSingleSetting = self.GET_SINGLE_SETTING_METHOD( +++ ("libfptr_get_single_setting", self.library) +++ ) +++ self._setSingleSetting = self.SET_SINGLE_SETTING_METHOD( +++ ("libfptr_set_single_setting", self.library) +++ ) +++ +++ self._getVersion = self.GET_VERSION_METHOD( +++ ("libfptr_get_version_string", self.library) +++ ) +++ self._isOpened = self.IS_OPENED_METHOD(("libfptr_is_opened", self.library)) +++ self._errorCode = self.GET_ERROR_CODE_METHOD( +++ ("libfptr_error_code", self.library) +++ ) +++ self._errorDescription = self.GET_ERROR_DESCRIPTION_METHOD( +++ ("libfptr_error_description", self.library) +++ ) +++ self._errorRecommendation = self.GET_ERROR_RECOMMENDATION_METHOD( +++ ("libfptr_error_recommendation", self.library) +++ ) +++ self._resetError = self.RESET_ERROR_METHOD( +++ ("libfptr_reset_error", self.library) +++ ) +++ self._logWrite = self.LOG_WRITE_METHOD(("libfptr_log_write_ex", self.library)) +++ self._showProperties = self.SHOW_PROPERTIES_METHOD( +++ ("libfptr_show_properties", self.library) +++ ) +++ self._changeLabel = self.CHANGE_LABEL_METHOD( +++ ("libfptr_change_label", self.library) +++ ) ++ ++ def __del__(self): ++- destroy = self.DESTROY_METHOD(('libfptr_destroy', self.library)) +++ destroy = self.DESTROY_METHOD(("libfptr_destroy", self.library)) ++ destroy(ctypes.pointer(self.interface)) ++ ++ def version(self): ++@@ -1957,7 +2018,7 @@ class IFptr(object): ++ ++ def setSingleSetting(self, key, value): ++ if isinstance(value, bool): ++- value = '1' if value else '0' +++ value = "1" if value else "0" ++ elif isinstance(value, int): ++ value = str(value) ++ self._setSingleSetting(self.interface, key, value) ++@@ -1973,77 +2034,149 @@ class IFptr(object): ++ def setParam(self, paramId, param): ++ if isinstance(param, bool): ++ self._setBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) ++- elif isinstance(param, int) or (sys.version_info < (3, 0) and isinstance(param, long)): +++ elif isinstance(param, int) or ( +++ sys.version_info < (3, 0) and isinstance(param, long) +++ ): ++ if param > 4294967295: ++- raise ValueError("Invalid 'param' value {0}".format(param)) +++ raise ValueError(f"Invalid 'param' value {param}") ++ if param < 0: ++- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ else: ++- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) +++ self._setInt( +++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) +++ ) ++ elif isinstance(param, float): ++- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ elif isinstance(param, TEXT): ++- self._setString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) ++- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): ++- self._setByteArray(self.interface, ctypes.c_int(paramId), ++- (ctypes.c_ubyte * len(param))(*param), len(param)) +++ self._setString( +++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) +++ ) +++ elif ( +++ isinstance(param, list) +++ or isinstance(param, bytearray) +++ or isinstance(param, bytes) +++ ): +++ self._setByteArray( +++ self.interface, +++ ctypes.c_int(paramId), +++ (ctypes.c_ubyte * len(param))(*param), +++ len(param), +++ ) ++ elif isinstance(param, datetime.datetime): ++- self._setDateTime(self.interface, ctypes.c_int(paramId), param.date().year, ++- param.date().month, ++- param.date().day, ++- param.time().hour, param.time().minute, param.time().second) +++ self._setDateTime( +++ self.interface, +++ ctypes.c_int(paramId), +++ param.date().year, +++ param.date().month, +++ param.date().day, +++ param.time().hour, +++ param.time().minute, +++ param.time().second, +++ ) ++ else: ++- raise TypeError("Invalid 'param' type {0}".format(type(param))) +++ raise TypeError(f"Invalid 'param' type {type(param)}") ++ ++ def setUserParam(self, paramId, param): ++ if isinstance(param, bool): ++- self._setUserBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) +++ self._setUserBool( +++ self.interface, ctypes.c_int(paramId), ctypes.c_int(param) +++ ) ++ elif isinstance(param, int): ++ if param > 4294967295: ++- raise ValueError("Invalid 'param' value {0}".format(param)) +++ raise ValueError(f"Invalid 'param' value {param}") ++ if param < 0: ++- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ else: ++- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) +++ self._setInt( +++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) +++ ) ++ elif isinstance(param, float): ++- self._setUserDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setUserDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ elif isinstance(param, TEXT): ++- self._setUserString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) ++- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): ++- self._setUserByteArray(self.interface, ctypes.c_int(paramId), ++- (ctypes.c_ubyte * len(param))(*param), len(param)) +++ self._setUserString( +++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) +++ ) +++ elif ( +++ isinstance(param, list) +++ or isinstance(param, bytearray) +++ or isinstance(param, bytes) +++ ): +++ self._setUserByteArray( +++ self.interface, +++ ctypes.c_int(paramId), +++ (ctypes.c_ubyte * len(param))(*param), +++ len(param), +++ ) ++ elif isinstance(param, datetime.datetime): ++- self._setUserDateTime(self.interface, ctypes.c_int(paramId), param.date().year, ++- param.date().month, ++- param.date().day, ++- param.time().hour, param.time().minute, param.time().second) +++ self._setUserDateTime( +++ self.interface, +++ ctypes.c_int(paramId), +++ param.date().year, +++ param.date().month, +++ param.date().day, +++ param.time().hour, +++ param.time().minute, +++ param.time().second, +++ ) ++ else: ++- raise TypeError("Invalid 'param' type {0}".format(type(param))) +++ raise TypeError(f"Invalid 'param' type {type(param)}") ++ ++ def setNonPrintableParam(self, paramId, param): ++ if isinstance(param, bool): ++- self._setNonPrintableBool(self.interface, ctypes.c_int(paramId), ctypes.c_int(param)) +++ self._setNonPrintableBool( +++ self.interface, ctypes.c_int(paramId), ctypes.c_int(param) +++ ) ++ elif isinstance(param, int): ++ if param > 4294967295: ++- raise ValueError("Invalid 'param' value {0}".format(param)) +++ raise ValueError(f"Invalid 'param' value {param}") ++ if param < 0: ++- self._setDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ else: ++- self._setInt(self.interface, ctypes.c_int(paramId), ctypes.c_uint(param)) +++ self._setInt( +++ self.interface, ctypes.c_int(paramId), ctypes.c_uint(param) +++ ) ++ elif isinstance(param, float): ++- self._setNonPrintableDouble(self.interface, ctypes.c_int(paramId), ctypes.c_double(param)) +++ self._setNonPrintableDouble( +++ self.interface, ctypes.c_int(paramId), ctypes.c_double(param) +++ ) ++ elif isinstance(param, TEXT): ++- self._setNonPrintableString(self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param)) ++- elif isinstance(param, list) or isinstance(param, bytearray) or isinstance(param, bytes): ++- self._setNonPrintableByteArray(self.interface, ctypes.c_int(paramId), ++- (ctypes.c_ubyte * len(param))(*param), len(param)) +++ self._setNonPrintableString( +++ self.interface, ctypes.c_int(paramId), ctypes.c_wchar_p(param) +++ ) +++ elif ( +++ isinstance(param, list) +++ or isinstance(param, bytearray) +++ or isinstance(param, bytes) +++ ): +++ self._setNonPrintableByteArray( +++ self.interface, +++ ctypes.c_int(paramId), +++ (ctypes.c_ubyte * len(param))(*param), +++ len(param), +++ ) ++ elif isinstance(param, datetime.datetime): ++- self._setNonPrintableDateTime(self.interface, ctypes.c_int(paramId), param.date().year, ++- param.date().month, ++- param.date().day, ++- param.time().hour, param.time().minute, param.time().second) +++ self._setNonPrintableDateTime( +++ self.interface, +++ ctypes.c_int(paramId), +++ param.date().year, +++ param.date().month, +++ param.date().day, +++ param.time().hour, +++ param.time().minute, +++ param.time().second, +++ ) ++ else: ++- raise TypeError("Invalid 'param' type {0}".format(type(param))) +++ raise TypeError(f"Invalid 'param' type {type(param)}") ++ ++ def getParamInt(self, paramId): ++ value = self._getInt(self.interface, ctypes.c_int(paramId)) ++@@ -2059,13 +2192,20 @@ class IFptr(object): ++ ++ def getParamByteArray(self, paramId): ++ value = (ctypes.c_ubyte * self.DEFAULT_BUFF_SIZE)() ++- size = self._getByteArray(self.interface, ctypes.c_int(paramId), ++- ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), ++- self.DEFAULT_BUFF_SIZE) +++ size = self._getByteArray( +++ self.interface, +++ ctypes.c_int(paramId), +++ ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), +++ self.DEFAULT_BUFF_SIZE, +++ ) ++ if size > self.DEFAULT_BUFF_SIZE: ++ value = (ctypes.c_ubyte * size)() ++- size = self._getByteArray(self.interface, ctypes.c_int(paramId), ++- ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), size) +++ size = self._getByteArray( +++ self.interface, +++ ctypes.c_int(paramId), +++ ctypes.cast(value, ctypes.POINTER(ctypes.c_ubyte)), +++ size, +++ ) ++ return value[:size] ++ ++ def getParamDateTime(self, paramId): ++@@ -2075,16 +2215,25 @@ class IFptr(object): ++ hour = ctypes.c_int(0) ++ minute = ctypes.c_int(0) ++ second = ctypes.c_int(0) ++- self._getDateTime(self.interface, ctypes.c_int(paramId), ctypes.pointer(year), ++- ctypes.pointer(month), ++- ctypes.pointer(day), ++- ctypes.pointer(hour), ctypes.pointer(minute), ctypes.pointer(second)) ++- return datetime.datetime(year.value, month.value, day.value, hour.value, minute.value, ++- second.value) +++ self._getDateTime( +++ self.interface, +++ ctypes.c_int(paramId), +++ ctypes.pointer(year), +++ ctypes.pointer(month), +++ ctypes.pointer(day), +++ ctypes.pointer(hour), +++ ctypes.pointer(minute), +++ ctypes.pointer(second), +++ ) +++ return datetime.datetime( +++ year.value, month.value, day.value, hour.value, minute.value, second.value +++ ) ++ ++ def getParamString(self, paramId): ++ value = ctypes.create_unicode_buffer(self.DEFAULT_BUFF_SIZE) ++- size = self._getString(self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE) +++ size = self._getString( +++ self.interface, ctypes.c_int(paramId), value, self.DEFAULT_BUFF_SIZE +++ ) ++ if size > self.DEFAULT_BUFF_SIZE: ++ value = ctypes.create_unicode_buffer(size) ++ self._getString(self.interface, ctypes.c_int(paramId), value, size) ++@@ -2095,485 +2244,495 @@ class IFptr(object): ++ return value > 0 ++ ++ def applySingleSettings(self): ++- _method = self.METHOD(('libfptr_apply_single_settings', self.library)) +++ _method = self.METHOD(("libfptr_apply_single_settings", self.library)) ++ return _method(self.interface) ++ ++ def open(self): ++- _method = self.METHOD(('libfptr_open', self.library)) +++ _method = self.METHOD(("libfptr_open", self.library)) ++ return _method(self.interface) ++ ++ def close(self): ++- _method = self.METHOD(('libfptr_close', self.library)) +++ _method = self.METHOD(("libfptr_close", self.library)) ++ return _method(self.interface) ++ ++ def resetParams(self): ++- _method = self.METHOD(('libfptr_reset_params', self.library)) +++ _method = self.METHOD(("libfptr_reset_params", self.library)) ++ return _method(self.interface) ++ ++ def runCommand(self): ++- _method = self.METHOD(('libfptr_run_command', self.library)) +++ _method = self.METHOD(("libfptr_run_command", self.library)) ++ return _method(self.interface) ++ ++ def beep(self): ++- _method = self.METHOD(('libfptr_beep', self.library)) +++ _method = self.METHOD(("libfptr_beep", self.library)) ++ return _method(self.interface) ++ ++ def openDrawer(self): ++- _method = self.METHOD(('libfptr_open_drawer', self.library)) +++ _method = self.METHOD(("libfptr_open_drawer", self.library)) ++ return _method(self.interface) ++ ++ def cut(self): ++- _method = self.METHOD(('libfptr_cut', self.library)) +++ _method = self.METHOD(("libfptr_cut", self.library)) ++ return _method(self.interface) ++ ++ def devicePoweroff(self): ++- _method = self.METHOD(('libfptr_device_poweroff', self.library)) +++ _method = self.METHOD(("libfptr_device_poweroff", self.library)) ++ return _method(self.interface) ++ ++ def deviceReboot(self): ++- _method = self.METHOD(('libfptr_device_reboot', self.library)) +++ _method = self.METHOD(("libfptr_device_reboot", self.library)) ++ return _method(self.interface) ++ ++ def openShift(self): ++- _method = self.METHOD(('libfptr_open_shift', self.library)) +++ _method = self.METHOD(("libfptr_open_shift", self.library)) ++ return _method(self.interface) ++ ++ def resetSummary(self): ++- _method = self.METHOD(('libfptr_reset_summary', self.library)) +++ _method = self.METHOD(("libfptr_reset_summary", self.library)) ++ return _method(self.interface) ++ ++ def initDevice(self): ++- _method = self.METHOD(('libfptr_init_device', self.library)) +++ _method = self.METHOD(("libfptr_init_device", self.library)) ++ return _method(self.interface) ++ ++ def queryData(self): ++- _method = self.METHOD(('libfptr_query_data', self.library)) +++ _method = self.METHOD(("libfptr_query_data", self.library)) ++ return _method(self.interface) ++ ++ def cashIncome(self): ++- _method = self.METHOD(('libfptr_cash_income', self.library)) +++ _method = self.METHOD(("libfptr_cash_income", self.library)) ++ return _method(self.interface) ++ ++ def cashOutcome(self): ++- _method = self.METHOD(('libfptr_cash_outcome', self.library)) +++ _method = self.METHOD(("libfptr_cash_outcome", self.library)) ++ return _method(self.interface) ++ ++ def openReceipt(self): ++- _method = self.METHOD(('libfptr_open_receipt', self.library)) +++ _method = self.METHOD(("libfptr_open_receipt", self.library)) ++ return _method(self.interface) ++ ++ def cancelReceipt(self): ++- _method = self.METHOD(('libfptr_cancel_receipt', self.library)) +++ _method = self.METHOD(("libfptr_cancel_receipt", self.library)) ++ return _method(self.interface) ++ ++ def closeReceipt(self): ++- _method = self.METHOD(('libfptr_close_receipt', self.library)) +++ _method = self.METHOD(("libfptr_close_receipt", self.library)) ++ return _method(self.interface) ++ ++ def checkDocumentClosed(self): ++- _method = self.METHOD(('libfptr_check_document_closed', self.library)) +++ _method = self.METHOD(("libfptr_check_document_closed", self.library)) ++ return _method(self.interface) ++ ++ def receiptTotal(self): ++- _method = self.METHOD(('libfptr_receipt_total', self.library)) +++ _method = self.METHOD(("libfptr_receipt_total", self.library)) ++ return _method(self.interface) ++ ++ def receiptTax(self): ++- _method = self.METHOD(('libfptr_receipt_tax', self.library)) +++ _method = self.METHOD(("libfptr_receipt_tax", self.library)) ++ return _method(self.interface) ++ ++ def registration(self): ++- _method = self.METHOD(('libfptr_registration', self.library)) +++ _method = self.METHOD(("libfptr_registration", self.library)) ++ return _method(self.interface) ++ ++ def payment(self): ++- _method = self.METHOD(('libfptr_payment', self.library)) +++ _method = self.METHOD(("libfptr_payment", self.library)) ++ return _method(self.interface) ++ ++ def report(self): ++- _method = self.METHOD(('libfptr_report', self.library)) +++ _method = self.METHOD(("libfptr_report", self.library)) ++ return _method(self.interface) ++ ++ def printText(self): ++- _method = self.METHOD(('libfptr_print_text', self.library)) +++ _method = self.METHOD(("libfptr_print_text", self.library)) ++ return _method(self.interface) ++ ++ def printCliche(self): ++- _method = self.METHOD(('libfptr_print_cliche', self.library)) +++ _method = self.METHOD(("libfptr_print_cliche", self.library)) ++ return _method(self.interface) ++ ++ def beginNonfiscalDocument(self): ++- _method = self.METHOD(('libfptr_begin_nonfiscal_document', self.library)) +++ _method = self.METHOD(("libfptr_begin_nonfiscal_document", self.library)) ++ return _method(self.interface) ++ ++ def endNonfiscalDocument(self): ++- _method = self.METHOD(('libfptr_end_nonfiscal_document', self.library)) +++ _method = self.METHOD(("libfptr_end_nonfiscal_document", self.library)) ++ return _method(self.interface) ++ ++ def printBarcode(self): ++- _method = self.METHOD(('libfptr_print_barcode', self.library)) +++ _method = self.METHOD(("libfptr_print_barcode", self.library)) ++ return _method(self.interface) ++ ++ def printPicture(self): ++- _method = self.METHOD(('libfptr_print_picture', self.library)) +++ _method = self.METHOD(("libfptr_print_picture", self.library)) ++ return _method(self.interface) ++ ++ def printPictureByNumber(self): ++- _method = self.METHOD(('libfptr_print_picture_by_number', self.library)) +++ _method = self.METHOD(("libfptr_print_picture_by_number", self.library)) ++ return _method(self.interface) ++ ++ def uploadPictureFromFile(self): ++- _method = self.METHOD(('libfptr_upload_picture_from_file', self.library)) +++ _method = self.METHOD(("libfptr_upload_picture_from_file", self.library)) ++ return _method(self.interface) ++ ++ def clearPictures(self): ++- _method = self.METHOD(('libfptr_clear_pictures', self.library)) +++ _method = self.METHOD(("libfptr_clear_pictures", self.library)) ++ return _method(self.interface) ++ ++ def writeDeviceSettingRaw(self): ++- _method = self.METHOD(('libfptr_write_device_setting_raw', self.library)) +++ _method = self.METHOD(("libfptr_write_device_setting_raw", self.library)) ++ return _method(self.interface) ++ ++ def readDeviceSettingRaw(self): ++- _method = self.METHOD(('libfptr_read_device_setting_raw', self.library)) +++ _method = self.METHOD(("libfptr_read_device_setting_raw", self.library)) ++ return _method(self.interface) ++ ++ def commitSettings(self): ++- _method = self.METHOD(('libfptr_commit_settings', self.library)) +++ _method = self.METHOD(("libfptr_commit_settings", self.library)) ++ return _method(self.interface) ++ ++ def initSettings(self): ++- _method = self.METHOD(('libfptr_init_settings', self.library)) +++ _method = self.METHOD(("libfptr_init_settings", self.library)) ++ return _method(self.interface) ++ ++ def resetSettings(self): ++- _method = self.METHOD(('libfptr_reset_settings', self.library)) +++ _method = self.METHOD(("libfptr_reset_settings", self.library)) ++ return _method(self.interface) ++ ++ def writeDateTime(self): ++- _method = self.METHOD(('libfptr_write_date_time', self.library)) +++ _method = self.METHOD(("libfptr_write_date_time", self.library)) ++ return _method(self.interface) ++ ++ def writeLicense(self): ++- _method = self.METHOD(('libfptr_write_license', self.library)) +++ _method = self.METHOD(("libfptr_write_license", self.library)) ++ return _method(self.interface) ++ ++ def fnOperation(self): ++- _method = self.METHOD(('libfptr_fn_operation', self.library)) +++ _method = self.METHOD(("libfptr_fn_operation", self.library)) ++ return _method(self.interface) ++ ++ def fnQueryData(self): ++- _method = self.METHOD(('libfptr_fn_query_data', self.library)) +++ _method = self.METHOD(("libfptr_fn_query_data", self.library)) ++ return _method(self.interface) ++ ++ def fnWriteAttributes(self): ++- _method = self.METHOD(('libfptr_fn_write_attributes', self.library)) +++ _method = self.METHOD(("libfptr_fn_write_attributes", self.library)) ++ return _method(self.interface) ++ ++ def externalDevicePowerOn(self): ++- _method = self.METHOD(('libfptr_external_device_power_on', self.library)) +++ _method = self.METHOD(("libfptr_external_device_power_on", self.library)) ++ return _method(self.interface) ++ ++ def externalDevicePowerOff(self): ++- _method = self.METHOD(('libfptr_external_device_power_off', self.library)) +++ _method = self.METHOD(("libfptr_external_device_power_off", self.library)) ++ return _method(self.interface) ++ ++ def externalDeviceWriteData(self): ++- _method = self.METHOD(('libfptr_external_device_write_data', self.library)) +++ _method = self.METHOD(("libfptr_external_device_write_data", self.library)) ++ return _method(self.interface) ++ ++ def externalDeviceReadData(self): ++- _method = self.METHOD(('libfptr_external_device_read_data', self.library)) +++ _method = self.METHOD(("libfptr_external_device_read_data", self.library)) ++ return _method(self.interface) ++ ++ def operatorLogin(self): ++- _method = self.METHOD(('libfptr_operator_login', self.library)) +++ _method = self.METHOD(("libfptr_operator_login", self.library)) ++ return _method(self.interface) ++ ++ def processJson(self): ++- _method = self.METHOD(('libfptr_process_json', self.library)) +++ _method = self.METHOD(("libfptr_process_json", self.library)) ++ return _method(self.interface) ++ ++ def readDeviceSetting(self): ++- _method = self.METHOD(('libfptr_read_device_setting', self.library)) +++ _method = self.METHOD(("libfptr_read_device_setting", self.library)) ++ return _method(self.interface) ++ ++ def writeDeviceSetting(self): ++- _method = self.METHOD(('libfptr_write_device_setting', self.library)) +++ _method = self.METHOD(("libfptr_write_device_setting", self.library)) ++ return _method(self.interface) ++ ++ def beginReadRecords(self): ++- _method = self.METHOD(('libfptr_begin_read_records', self.library)) +++ _method = self.METHOD(("libfptr_begin_read_records", self.library)) ++ return _method(self.interface) ++ ++ def readNextRecord(self): ++- _method = self.METHOD(('libfptr_read_next_record', self.library)) +++ _method = self.METHOD(("libfptr_read_next_record", self.library)) ++ return _method(self.interface) ++ ++ def endReadRecords(self): ++- _method = self.METHOD(('libfptr_end_read_records', self.library)) +++ _method = self.METHOD(("libfptr_end_read_records", self.library)) ++ return _method(self.interface) ++ ++ def userMemoryOperation(self): ++- _method = self.METHOD(('libfptr_user_memory_operation', self.library)) +++ _method = self.METHOD(("libfptr_user_memory_operation", self.library)) ++ return _method(self.interface) ++ ++ def continuePrint(self): ++- _method = self.METHOD(('libfptr_continue_print', self.library)) +++ _method = self.METHOD(("libfptr_continue_print", self.library)) ++ return _method(self.interface) ++ ++ def initMgm(self): ++- _method = self.METHOD(('libfptr_init_mgm', self.library)) +++ _method = self.METHOD(("libfptr_init_mgm", self.library)) ++ return _method(self.interface) ++ ++ def utilFormTlv(self): ++- _method = self.METHOD(('libfptr_util_form_tlv', self.library)) +++ _method = self.METHOD(("libfptr_util_form_tlv", self.library)) ++ return _method(self.interface) ++ ++ def utilFormNomenclature(self): ++- _method = self.METHOD(('libfptr_util_form_nomenclature', self.library)) +++ _method = self.METHOD(("libfptr_util_form_nomenclature", self.library)) ++ return _method(self.interface) ++ ++ def utilMapping(self): ++- _method = self.METHOD(('libfptr_util_mapping', self.library)) +++ _method = self.METHOD(("libfptr_util_mapping", self.library)) ++ return _method(self.interface) ++ ++ def readModelFlags(self): ++- _method = self.METHOD(('libfptr_read_model_flags', self.library)) +++ _method = self.METHOD(("libfptr_read_model_flags", self.library)) ++ return _method(self.interface) ++ ++ def lineFeed(self): ++- _method = self.METHOD(('libfptr_line_feed', self.library)) +++ _method = self.METHOD(("libfptr_line_feed", self.library)) ++ return _method(self.interface) ++ ++ def flashFirmware(self): ++- _method = self.METHOD(('libfptr_flash_firmware', self.library)) +++ _method = self.METHOD(("libfptr_flash_firmware", self.library)) ++ return _method(self.interface) ++ ++ def softLockInit(self): ++- _method = self.METHOD(('libfptr_soft_lock_init', self.library)) +++ _method = self.METHOD(("libfptr_soft_lock_init", self.library)) ++ return _method(self.interface) ++ ++ def softLockQuerySessionCode(self): ++- _method = self.METHOD(('libfptr_soft_lock_query_session_code', self.library)) +++ _method = self.METHOD(("libfptr_soft_lock_query_session_code", self.library)) ++ return _method(self.interface) ++ ++ def softLockValidate(self): ++- _method = self.METHOD(('libfptr_soft_lock_validate', self.library)) +++ _method = self.METHOD(("libfptr_soft_lock_validate", self.library)) ++ return _method(self.interface) ++ ++ def utilCalcTax(self): ++- _method = self.METHOD(('libfptr_util_calc_tax', self.library)) +++ _method = self.METHOD(("libfptr_util_calc_tax", self.library)) ++ return _method(self.interface) ++ ++ def downloadPicture(self): ++- _method = self.METHOD(('libfptr_download_picture', self.library)) +++ _method = self.METHOD(("libfptr_download_picture", self.library)) ++ return _method(self.interface) ++ ++ def bluetoothRemovePairedDevices(self): ++- _method = self.METHOD(('libfptr_bluetooth_remove_paired_devices', self.library)) +++ _method = self.METHOD(("libfptr_bluetooth_remove_paired_devices", self.library)) ++ return _method(self.interface) ++ ++ def utilTagInfo(self): ++- _method = self.METHOD(('libfptr_util_tag_info', self.library)) +++ _method = self.METHOD(("libfptr_util_tag_info", self.library)) ++ return _method(self.interface) ++ ++ def utilContainerVersions(self): ++- _method = self.METHOD(('libfptr_util_container_versions', self.library)) +++ _method = self.METHOD(("libfptr_util_container_versions", self.library)) ++ return _method(self.interface) ++ ++ def activateLicenses(self): ++- _method = self.METHOD(('libfptr_activate_licenses', self.library)) +++ _method = self.METHOD(("libfptr_activate_licenses", self.library)) ++ return _method(self.interface) ++ ++ def removeLicenses(self): ++- _method = self.METHOD(('libfptr_remove_licenses', self.library)) +++ _method = self.METHOD(("libfptr_remove_licenses", self.library)) ++ return _method(self.interface) ++ ++ def enterKeys(self): ++- _method = self.METHOD(('libfptr_enter_keys', self.library)) +++ _method = self.METHOD(("libfptr_enter_keys", self.library)) ++ return _method(self.interface) ++ ++ def validateKeys(self): ++- _method = self.METHOD(('libfptr_validate_keys', self.library)) +++ _method = self.METHOD(("libfptr_validate_keys", self.library)) ++ return _method(self.interface) ++ ++ def enterSerialNumber(self): ++- _method = self.METHOD(('libfptr_enter_serial_number', self.library)) +++ _method = self.METHOD(("libfptr_enter_serial_number", self.library)) ++ return _method(self.interface) ++ ++ def getSerialNumberRequest(self): ++- _method = self.METHOD(('libfptr_get_serial_number_request', self.library)) +++ _method = self.METHOD(("libfptr_get_serial_number_request", self.library)) ++ return _method(self.interface) ++ ++ def uploadPixelBuffer(self): ++- _method = self.METHOD(('libfptr_upload_pixel_buffer', self.library)) +++ _method = self.METHOD(("libfptr_upload_pixel_buffer", self.library)) ++ return _method(self.interface) ++ ++ def downloadPixelBuffer(self): ++- _method = self.METHOD(('libfptr_download_pixel_buffer', self.library)) +++ _method = self.METHOD(("libfptr_download_pixel_buffer", self.library)) ++ return _method(self.interface) ++ ++ def printPixelBuffer(self): ++- _method = self.METHOD(('libfptr_print_pixel_buffer', self.library)) +++ _method = self.METHOD(("libfptr_print_pixel_buffer", self.library)) ++ return _method(self.interface) ++ ++ def utilConvertTagValue(self): ++- _method = self.METHOD(('libfptr_util_convert_tag_value', self.library)) +++ _method = self.METHOD(("libfptr_util_convert_tag_value", self.library)) ++ return _method(self.interface) ++ ++ def parseMarkingCode(self): ++- _method = self.METHOD(('libfptr_parse_marking_code', self.library)) +++ _method = self.METHOD(("libfptr_parse_marking_code", self.library)) ++ return _method(self.interface) ++ ++ def callScript(self): ++- _method = self.METHOD(('libfptr_call_script', self.library)) +++ _method = self.METHOD(("libfptr_call_script", self.library)) ++ return _method(self.interface) ++ ++ def setHeaderLines(self): ++- _method = self.METHOD(('libfptr_set_header_lines', self.library)) +++ _method = self.METHOD(("libfptr_set_header_lines", self.library)) ++ return _method(self.interface) ++ ++ def setFooterLines(self): ++- _method = self.METHOD(('libfptr_set_footer_lines', self.library)) +++ _method = self.METHOD(("libfptr_set_footer_lines", self.library)) ++ return _method(self.interface) ++ ++ def uploadPictureCliche(self): ++- _method = self.METHOD(('libfptr_upload_picture_cliche', self.library)) +++ _method = self.METHOD(("libfptr_upload_picture_cliche", self.library)) ++ return _method(self.interface) ++ ++ def uploadPictureMemory(self): ++- _method = self.METHOD(('libfptr_upload_picture_memory', self.library)) +++ _method = self.METHOD(("libfptr_upload_picture_memory", self.library)) ++ return _method(self.interface) ++ ++ def uploadPixelBufferCliche(self): ++- _method = self.METHOD(('libfptr_upload_pixel_buffer_cliche', self.library)) +++ _method = self.METHOD(("libfptr_upload_pixel_buffer_cliche", self.library)) ++ return _method(self.interface) ++ ++ def uploadPixelBufferMemory(self): ++- _method = self.METHOD(('libfptr_upload_pixel_buffer_memory', self.library)) +++ _method = self.METHOD(("libfptr_upload_pixel_buffer_memory", self.library)) ++ return _method(self.interface) ++ ++ def execDriverScript(self): ++- _method = self.METHOD(('libfptr_exec_driver_script', self.library)) +++ _method = self.METHOD(("libfptr_exec_driver_script", self.library)) ++ return _method(self.interface) ++ ++ def uploadDriverScript(self): ++- _method = self.METHOD(('libfptr_upload_driver_script', self.library)) +++ _method = self.METHOD(("libfptr_upload_driver_script", self.library)) ++ return _method(self.interface) ++ ++ def execDriverScriptById(self): ++- _method = self.METHOD(('libfptr_exec_driver_script_by_id', self.library)) +++ _method = self.METHOD(("libfptr_exec_driver_script_by_id", self.library)) ++ return _method(self.interface) ++ ++ def writeUniversalCountersSettings(self): ++- _method = self.METHOD(('libfptr_write_universal_counters_settings', self.library)) +++ _method = self.METHOD( +++ ("libfptr_write_universal_counters_settings", self.library) +++ ) ++ return _method(self.interface) ++ ++ def readUniversalCountersSettings(self): ++- _method = self.METHOD(('libfptr_read_universal_counters_settings', self.library)) +++ _method = self.METHOD( +++ ("libfptr_read_universal_counters_settings", self.library) +++ ) ++ return _method(self.interface) ++ ++ def queryUniversalCountersState(self): ++- _method = self.METHOD(('libfptr_query_universal_counters_state', self.library)) +++ _method = self.METHOD(("libfptr_query_universal_counters_state", self.library)) ++ return _method(self.interface) ++ ++ def resetUniversalCounters(self): ++- _method = self.METHOD(('libfptr_reset_universal_counters', self.library)) +++ _method = self.METHOD(("libfptr_reset_universal_counters", self.library)) ++ return _method(self.interface) ++ ++ def cacheUniversalCounters(self): ++- _method = self.METHOD(('libfptr_cache_universal_counters', self.library)) +++ _method = self.METHOD(("libfptr_cache_universal_counters", self.library)) ++ return _method(self.interface) ++ ++ def readUniversalCounterSum(self): ++- _method = self.METHOD(('libfptr_read_universal_counter_sum', self.library)) +++ _method = self.METHOD(("libfptr_read_universal_counter_sum", self.library)) ++ return _method(self.interface) ++ ++ def readUniversalCounterQuantity(self): ++- _method = self.METHOD(('libfptr_read_universal_counter_quantity', self.library)) +++ _method = self.METHOD(("libfptr_read_universal_counter_quantity", self.library)) ++ return _method(self.interface) ++ ++ def clearUniversalCountersCache(self): ++- _method = self.METHOD(('libfptr_clear_universal_counters_cache', self.library)) +++ _method = self.METHOD(("libfptr_clear_universal_counters_cache", self.library)) ++ return _method(self.interface) ++ ++ def disableOfdChannel(self): ++- _method = self.METHOD(('libfptr_disable_ofd_channel', self.library)) +++ _method = self.METHOD(("libfptr_disable_ofd_channel", self.library)) ++ return _method(self.interface) ++ ++ def enableOfdChannel(self): ++- _method = self.METHOD(('libfptr_enable_ofd_channel', self.library)) +++ _method = self.METHOD(("libfptr_enable_ofd_channel", self.library)) ++ return _method(self.interface) ++ ++ def validateJson(self): ++- _method = self.METHOD(('libfptr_validate_json', self.library)) +++ _method = self.METHOD(("libfptr_validate_json", self.library)) ++ return _method(self.interface) ++ ++ def reflectionCall(self): ++- _method = self.METHOD(('libfptr_reflection_call', self.library)) +++ _method = self.METHOD(("libfptr_reflection_call", self.library)) ++ return _method(self.interface) ++ ++ def getRemoteServerInfo(self): ++- _method = self.METHOD(('libfptr_get_remote_server_info', self.library)) +++ _method = self.METHOD(("libfptr_get_remote_server_info", self.library)) ++ return _method(self.interface) ++ ++ def beginMarkingCodeValidation(self): ++- _method = self.METHOD(('libfptr_begin_marking_code_validation', self.library)) +++ _method = self.METHOD(("libfptr_begin_marking_code_validation", self.library)) ++ return _method(self.interface) ++ ++ def cancelMarkingCodeValidation(self): ++- _method = self.METHOD(('libfptr_cancel_marking_code_validation', self.library)) +++ _method = self.METHOD(("libfptr_cancel_marking_code_validation", self.library)) ++ return _method(self.interface) ++ ++ def getMarkingCodeValidationStatus(self): ++- _method = self.METHOD(('libfptr_get_marking_code_validation_status', self.library)) +++ _method = self.METHOD( +++ ("libfptr_get_marking_code_validation_status", self.library) +++ ) ++ return _method(self.interface) ++ ++ def acceptMarkingCode(self): ++- _method = self.METHOD(('libfptr_accept_marking_code', self.library)) +++ _method = self.METHOD(("libfptr_accept_marking_code", self.library)) ++ return _method(self.interface) ++ ++ def declineMarkingCode(self): ++- _method = self.METHOD(('libfptr_decline_marking_code', self.library)) +++ _method = self.METHOD(("libfptr_decline_marking_code", self.library)) ++ return _method(self.interface) ++ ++ def updateFnmKeys(self): ++- _method = self.METHOD(('libfptr_update_fnm_keys', self.library)) +++ _method = self.METHOD(("libfptr_update_fnm_keys", self.library)) ++ return _method(self.interface) ++ ++ def writeSalesNotice(self): ++- _method = self.METHOD(('libfptr_write_sales_notice', self.library)) +++ _method = self.METHOD(("libfptr_write_sales_notice", self.library)) ++ return _method(self.interface) ++ ++ def checkMarkingCodeValidationsReady(self): ++- _method = self.METHOD(('libfptr_check_marking_code_validations_ready', self.library)) +++ _method = self.METHOD( +++ ("libfptr_check_marking_code_validations_ready", self.library) +++ ) ++ return _method(self.interface) ++ ++ def clearMarkingCodeValidationResult(self): ++- _method = self.METHOD(('libfptr_clear_marking_code_validation_result', self.library)) +++ _method = self.METHOD( +++ ("libfptr_clear_marking_code_validation_result", self.library) +++ ) ++ return _method(self.interface) ++ ++ def pingMarkingServer(self): ++- _method = self.METHOD(('libfptr_ping_marking_server', self.library)) +++ _method = self.METHOD(("libfptr_ping_marking_server", self.library)) ++ return _method(self.interface) ++ ++ def getMarkingServerStatus(self): ++- _method = self.METHOD(('libfptr_get_marking_server_status', self.library)) +++ _method = self.METHOD(("libfptr_get_marking_server_status", self.library)) ++ return _method(self.interface) ++ ++ def isDriverLocked(self): ++- _method = self.METHOD(('libfptr_is_driver_locked', self.library)) +++ _method = self.METHOD(("libfptr_is_driver_locked", self.library)) ++ return _method(self.interface) ++ ++ def getLastDocumentJournal(self): ++- _method = self.METHOD(('libfptr_get_last_document_journal', self.library)) +++ _method = self.METHOD(("libfptr_get_last_document_journal", self.library)) ++ return _method(self.interface) ++ ++ def findDocumentInJournal(self): ++- _method = self.METHOD(('libfptr_find_document_in_journal', self.library)) +++ _method = self.METHOD(("libfptr_find_document_in_journal", self.library)) ++ return _method(self.interface) ++ ++ def runFnCommand(self): ++- _method = self.METHOD(('libfptr_run_fn_command', self.library)) +++ _method = self.METHOD(("libfptr_run_fn_command", self.library)) ++ return _method(self.interface) ++diff --git a/modules/otchet.py b/modules/otchet.py ++index 965f03a..ba46312 100644 ++--- a/modules/otchet.py +++++ b/modules/otchet.py ++@@ -1,9 +1,9 @@ ++ from datetime import datetime ++-from typing import Any, Dict, List, Tuple, Union +++from typing import Any ++ ++ from reportlab.lib import colors ++ from reportlab.lib.pagesizes import A4, landscape, letter ++-from reportlab.lib.units import mm, inch +++from reportlab.lib.units import inch, mm ++ from reportlab.pdfbase import pdfmetrics ++ from reportlab.pdfbase.ttfonts import TTFont ++ from reportlab.pdfgen import canvas ++@@ -17,7 +17,7 @@ system = System() ++ config = Config() ++ ++ ++-def get_ticket_type(age:str) -> str: +++def get_ticket_type(age: str) -> str: ++ """ ++ Определяет тип билета на основе возраста клиента. ++ ++@@ -45,7 +45,8 @@ def get_ticket_type(age:str) -> str: ++ else: ++ return "взрослый" ++ ++-def generate_saved_tickets(values: List[tuple]) -> None: +++ +++def generate_saved_tickets(values: list[tuple]) -> None: ++ """ ++ Генерирует PDF-файл с билетами для клиентов на основе переданных данных. ++ ++@@ -72,32 +73,79 @@ def generate_saved_tickets(values: List[tuple]) -> None: ++ pdfmetrics.registerFont(TTFont("DejaVuSerif", "files/DejaVuSerif.ttf")) ++ c = canvas.Canvas(path, pagesize=(landscape(letter))) ++ for client in values: ++- (last_name, name, ticket_flag, price, category, client_id, age, hours, talents, date_time) = client +++ ( +++ last_name, +++ name, +++ ticket_flag, +++ price, +++ category, +++ client_id, +++ age, +++ hours, +++ talents, +++ date_time, +++ ) = client ++ ticket_type = get_ticket_type(age) ++ if ticket_type == "бесплатный" or category == "н": ++ continue # Пропускаем бесплатных и "не идущих" ++ c.setFont("DejaVuSerif", 12) ++ # x - расстояние мм от левого края страницы ++ # y - расстояние мм от нижнего края страницы ++- c.drawString(coordinates["name"]["x"] * mm,coordinates["name"]["y"] * mm, name) ++- c.drawString(coordinates["surname"]["x"] * mm, coordinates["surname"]["y"] * mm, last_name) ++- c.drawString(coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age)) +++ c.drawString(coordinates["name"]["x"] * mm, coordinates["name"]["y"] * mm, name) +++ c.drawString( +++ coordinates["surname"]["x"] * mm, +++ coordinates["surname"]["y"] * mm, +++ last_name, +++ ) +++ c.drawString( +++ coordinates["age"]["x"] * mm, coordinates["age"]["y"] * mm, str(age) +++ ) ++ duration_text = f"{hours} ч. пребывания" ++ if hours == 3: ++ duration_text += "-весь день" ++- c.drawString(coordinates["duration"]["x"] * mm, coordinates["duration"]["y"] * mm, duration_text) +++ c.drawString( +++ coordinates["duration"]["x"] * mm, +++ coordinates["duration"]["y"] * mm, +++ duration_text, +++ ) ++ if isinstance(date_time, str): ++ date_time = datetime.strptime(date_time, "%Y-%m-%d") ++- c.drawString(coordinates["date"]["x"] * mm, coordinates["date"]["y"] * mm, date_time.strftime('%d.%m.%Y')) ++- c.drawString(coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость") ++- c.drawString(coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД") ++- c.drawString(coordinates["place"]["x"] * mm, coordinates["place"]["y"] * mm, "МАСТЕРСЛАВЛЬ") ++- c.drawString(coordinates["price"]["x"] * mm, coordinates["price"]["y"] * mm, f"{price} руб.") ++- c.drawString(coordinates["ticket_type"]["x"] * mm, coordinates["ticket_type"]["y"] * mm, ticket_type) ++- c.drawString(coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category) +++ c.drawString( +++ coordinates["date"]["x"] * mm, +++ coordinates["date"]["y"] * mm, +++ date_time.strftime("%d.%m.%Y"), +++ ) +++ c.drawString( +++ coordinates["guest"]["x"] * mm, coordinates["guest"]["y"] * mm, "гость" +++ ) +++ c.drawString( +++ coordinates["city"]["x"] * mm, coordinates["city"]["y"] * mm, "БЕЛГОРОД" +++ ) +++ c.drawString( +++ coordinates["place"]["x"] * mm, +++ coordinates["place"]["y"] * mm, +++ "МАСТЕРСЛАВЛЬ", +++ ) +++ c.drawString( +++ coordinates["price"]["x"] * mm, +++ coordinates["price"]["y"] * mm, +++ f"{price} руб.", +++ ) +++ c.drawString( +++ coordinates["ticket_type"]["x"] * mm, +++ coordinates["ticket_type"]["y"] * mm, +++ ticket_type, +++ ) +++ c.drawString( +++ coordinates["notes"]["x"] * mm, coordinates["notes"]["y"] * mm, category +++ ) ++ c.setFont("DejaVuSerif", 24) ++ talents_str = "0" if ticket_type == "взрослый" else str(talents) ++- c.drawString(coordinates["talents"]["x"] * mm, coordinates["talents"]["y"] * mm, talents_str) +++ c.drawString( +++ coordinates["talents"]["x"] * mm, +++ coordinates["talents"]["y"] * mm, +++ talents_str, +++ ) ++ c.drawImage( ++ img_file, ++ coordinates["qr_code"]["x"], ++@@ -134,13 +182,30 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: ++ """ ++ logger.info("Запуск функции generate_ticket_report_table") ++ type_ticket = [ ++- "Взрослый, 1 ч.", "Взрослый, 2 ч.", "Взрослый, 3 ч.", ++- "Детский, 1 ч.", "Детский, 2 ч.", "Детский, 3 ч.", ++- "Многодетный взрослый, 1 ч.", "Многодетный взрослый, 2 ч.", "Многодетный взрослый, 3 ч.", ++- "Многодетный детский, 1 ч.", "Многодетный детский, 2 ч.", "Многодетный детский, 3 ч.", ++- "Инвалид, 3 ч.", "Сопровождающий, 3 ч.", +++ "Взрослый, 1 ч.", +++ "Взрослый, 2 ч.", +++ "Взрослый, 3 ч.", +++ "Детский, 1 ч.", +++ "Детский, 2 ч.", +++ "Детский, 3 ч.", +++ "Многодетный взрослый, 1 ч.", +++ "Многодетный взрослый, 2 ч.", +++ "Многодетный взрослый, 3 ч.", +++ "Многодетный детский, 1 ч.", +++ "Многодетный детский, 2 ч.", +++ "Многодетный детский, 3 ч.", +++ "Инвалид, 3 ч.", +++ "Сопровождающий, 3 ч.", +++ ] +++ data = [ +++ [ +++ "№\n п/п", +++ "Тип\nбилета", +++ "Цена,\n руб.", +++ "Количество,\n шт.", +++ "Стоимость,\n руб.", +++ ] ++ ] ++- data = [["№\n п/п", "Тип\nбилета", "Цена,\n руб.", "Количество,\n шт.", "Стоимость,\n руб."]] ++ # Счётчики для агрегатов ++ total = { ++ "adult": {"count": 0, "sum": 0}, ++@@ -182,23 +247,57 @@ def generate_ticket_report_table(ticket_summary: dict) -> list[list]: ++ ++ # Агрегированные строки ++ data += [ ++- ["", "Всего взрослых билетов", "", total["adult"]["count"], total["adult"]["sum"]], ++- ["", "Всего детских билетов", "", total["child"]["count"], total["child"]["sum"]], ++- ["", "Всего многодетных взрослых билетов", "", total["many_adult"]["count"], total["many_adult"]["sum"]], ++- ["", "Всего многодетных детских билетов", "", total["many_child"]["count"], total["many_child"]["sum"]], +++ [ +++ "", +++ "Всего взрослых билетов", +++ "", +++ total["adult"]["count"], +++ total["adult"]["sum"], +++ ], +++ [ +++ "", +++ "Всего детских билетов", +++ "", +++ total["child"]["count"], +++ total["child"]["sum"], +++ ], +++ [ +++ "", +++ "Всего многодетных взрослых билетов", +++ "", +++ total["many_adult"]["count"], +++ total["many_adult"]["sum"], +++ ], +++ [ +++ "", +++ "Всего многодетных детских билетов", +++ "", +++ total["many_child"]["count"], +++ total["many_child"]["sum"], +++ ], ++ ["", "Инвалид, 3 ч.", "", total["disabled"]["count"], "0"], ++ ["", "Сопровождающий, 3 ч.", "", total["maintainer"]["count"], "0"], ++- ["", "Итого билетов", "", ++- total["adult"]["count"] + total["child"]["count"] + ++- total["many_adult"]["count"] + total["many_child"]["count"] + ++- total["disabled"]["count"] + total["maintainer"]["count"], ++- total["adult"]["sum"] + total["child"]["sum"] + ++- total["many_adult"]["sum"] + total["many_child"]["sum"]], +++ [ +++ "", +++ "Итого билетов", +++ "", +++ total["adult"]["count"] +++ + total["child"]["count"] +++ + total["many_adult"]["count"] +++ + total["many_child"]["count"] +++ + total["disabled"]["count"] +++ + total["maintainer"]["count"], +++ total["adult"]["sum"] +++ + total["child"]["sum"] +++ + total["many_adult"]["sum"] +++ + total["many_child"]["sum"], +++ ], ++ ] ++ ++ return data ++ ++-def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: +++ +++def otchet_administratora(date_1: str, date_2: str, values: dict) -> None: ++ """ ++ Функция формирует отчет администратора в формате PDF. ++ ++@@ -253,15 +352,22 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: ++ table_width = sum(custom_widths) ++ if table_width > (page_width - left_margin - right_margin): ++ # Корректировка ширины второго столбца ++- remaining_width = page_width - left_margin - right_margin - (sum(custom_widths) - custom_widths[1]) +++ remaining_width = ( +++ page_width +++ - left_margin +++ - right_margin +++ - (sum(custom_widths) - custom_widths[1]) +++ ) ++ custom_widths[1] = remaining_width ++ t = Table(data, colWidths=custom_widths, rowHeights=[0.3 * inch] * len(data)) ++ t.setStyle( ++- TableStyle([ ++- ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), ++- ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), ++- ("BOX", (0, 0), (-1, -1), 0.25, colors.black), ++- ]) +++ TableStyle( +++ [ +++ ("FONT", (0, 0), (-1, -1), "DejaVuSerif", 8), +++ ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), +++ ("BOX", (0, 0), (-1, -1), 0.25, colors.black), +++ ] +++ ) ++ ) ++ t.wrapOn(c, left_margin, 180 * mm) ++ t.drawOn(c, left_margin, 60 * mm) ++@@ -281,6 +387,7 @@ def otchet_administratora(date_1: str, date_2: str, values: Dict) -> None: ++ c.showPage() ++ c.save() ++ +++ ++ def safe_int(value: Any) -> int: ++ """ ++ Преобразует значение в целое число, если это возможно. Если значение None, строка 'None', ++@@ -295,14 +402,22 @@ def safe_int(value: Any) -> int: ++ if isinstance(value, (list, dict, set, tuple)): # Проверка на неподобающие типы ++ return 0 ++ try: ++- if value not in [None, 'None']: ++- return int(float(value)) # Преобразование строки с плавающей точкой в целое число +++ if value not in [None, "None"]: +++ return int( +++ float(value) +++ ) # Преобразование строки с плавающей точкой в целое число ++ else: ++ return 0 ++ except (ValueError, TypeError): # Обработка ошибок для некорректных значений ++ return 0 ++ ++-def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m-%d %H:%M:%S", output_format: str = "%d-%m-%Y") -> Tuple[str, str]: +++ +++def format_date_range( +++ date1_str: str, +++ date2_str: str, +++ input_format: str = "%Y-%m-%d %H:%M:%S", +++ output_format: str = "%d-%m-%Y", +++) -> tuple[str, str]: ++ """ ++ Преобразует строки с датами из одного формата в другой и возвращает кортеж из двух отформатированных строк. ++ ++@@ -321,7 +436,8 @@ def format_date_range(date1_str: str, date2_str: str, input_format: str = "%Y-%m ++ dt2 = date_2.strftime(output_format) ++ return dt1, dt2 ++ ++-def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: +++ +++def otchet_kassira(val: list[int], date1: str, date2: str, kassir: Any) -> None: ++ """ ++ Формирует PDF-отчет кассира за указанный период с разбивкой по типам оплат и возвратов. ++ ++@@ -345,7 +461,9 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: ++ logger.info("Запуск функции otchet_kassira") ++ # Проверка на пустой список или недостаточное количество элементов ++ if not val or len(val) < 4: ++- logger.error("Некорректные данные для отчета: данные не переданы или их недостаточно.") +++ logger.error( +++ "Некорректные данные для отчета: данные не переданы или их недостаточно." +++ ) ++ # Завершаем функцию, если данных недостаточно или они пустые ++ return ++ path = "./otchet.pdf" ++@@ -423,10 +541,21 @@ def otchet_kassira(val: List[int], date1: str, date2: str, kassir: Any) -> None: ++ c.showPage() ++ c.save() ++ +++ ++ def process_sales_and_returns( ++- sales: List[Tuple[str, int, Union[int, float]]], ++- sales_return: List[Tuple[str, int, Union[int, float], int]] ++-) -> List[Tuple[Union[str, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float, None], Union[int, float]]]: +++ sales: list[tuple[str, int, int | float]], +++ sales_return: list[tuple[str, int, int | float, int]], +++) -> list[ +++ tuple[ +++ str | None, +++ int | float | None, +++ int | float | None, +++ int | float | None, +++ int | float | None, +++ int | float | None, +++ int | float, +++ ] +++]: ++ """ ++ Обрабатывает продажи и возвраты, агрегируя информацию по каждому кассовому аппарату. ++ Функция вычисляет суммы по картам и наличным, а также учитывает возвраты по каждому типу: ++@@ -462,24 +591,29 @@ def process_sales_and_returns( ++ logger.info("Запуск функции process_sales_and_returns") ++ pcs = { ++ name: { ++- "card": 0, "cash": 0, ++- "return": 0, "return_again": 0, +++ "card": 0, +++ "cash": 0, +++ "return": 0, +++ "return_again": 0, ++ "return_partial": 0, ++- "return_card": 0, "return_cash": 0, +++ "return_card": 0, +++ "return_cash": 0, ++ } ++ for name in system.pcs ++ } ++ ++ type_rm = [1, 2, 3] # 1 - карта, 2 - наличные, 3 - offline ++- return_fields: dict[int, str] = {2: "return", 4: "return_again", 6: "return_partial"} +++ return_fields: dict[int, str] = { +++ 2: "return", +++ 4: "return_again", +++ 6: "return_partial", +++ } ++ ++ for sale in sales: ++ pc_name = sale[0] ++ if pc_name not in pcs: ++ continue ++- if sale[1] == type_rm[0] or sale[1] == type_rm[2]: ++- pcs[pc_name]["card"] += sale[2] ++- elif sale[1] == type_rm[2]: +++ if sale[1] == type_rm[0] or sale[1] == type_rm[2] or sale[1] == type_rm[2]: ++ pcs[pc_name]["card"] += sale[2] ++ else: ++ pcs[pc_name]["cash"] += sale[2] ++@@ -501,33 +635,40 @@ def process_sales_and_returns( ++ data = [] ++ for name in system.pcs: ++ stats = pcs[name] ++- data.append(( ++- name, ++- stats["card"], ++- stats["cash"], ++- None, ++- stats["return_card"], ++- stats["return_cash"], ++- stats["return_card"] + stats["return_cash"] ++- )) +++ data.append( +++ ( +++ name, +++ stats["card"], +++ stats["cash"], +++ None, +++ stats["return_card"], +++ stats["return_cash"], +++ stats["return_card"] + stats["return_cash"], +++ ) +++ ) ++ ++ total_card = sum(pc["card"] for pc in pcs.values()) ++ total_cash = sum(pc["cash"] for pc in pcs.values()) ++ total_return = sum(pc["return_card"] + pc["return_cash"] for pc in pcs.values()) ++ ++- data.append(( ++- "Итого", ++- total_card, ++- total_cash, ++- total_card + total_cash, ++- None, ++- None, ++- total_return ++- )) +++ data.append( +++ ( +++ "Итого", +++ total_card, +++ total_cash, +++ total_card + total_cash, +++ None, +++ None, +++ total_return, +++ ) +++ ) ++ system.sales_data_summary = data ++ return data ++ ++-def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int]]) -> List[Tuple[str, Dict[str, int]]]: +++ +++def process_ticket_stats( +++ tickets: list[tuple[int, int, str, object, object, int]], +++) -> list[tuple[str, dict[str, int]]]: ++ """ ++ Обрабатывает информацию о билетах, суммируя количество и стоимость проданных билетов по категориям ++ и времени пребывания. Возвращает данные, которые могут быть использованы для отображения статистики ++@@ -555,10 +696,18 @@ def process_ticket_stats(tickets: List[Tuple[int, int, str, object, object, int] ++ ) ++ ++ TICKET_TYPE_MAP = { ++- (0, 1): "Взрослый, 1 ч.", (0, 2): "Взрослый, 2 ч.", (0, 3): "Взрослый, 3 ч.", ++- (1, 1): "Детский, 1 ч.", (1, 2): "Детский, 2 ч.", (1, 3): "Детский, 3 ч.", ++- (2, 1): "Многодетный взрослый, 1 ч.", (2, 2): "Многодетный взрослый, 2 ч.", (2, 3): "Многодетный взрослый, 3 ч.", ++- (3, 1): "Многодетный детский, 1 ч.", (3, 2): "Многодетный детский, 2 ч.", (3, 3): "Многодетный детский, 3 ч.", +++ (0, 1): "Взрослый, 1 ч.", +++ (0, 2): "Взрослый, 2 ч.", +++ (0, 3): "Взрослый, 3 ч.", +++ (1, 1): "Детский, 1 ч.", +++ (1, 2): "Детский, 2 ч.", +++ (1, 3): "Детский, 3 ч.", +++ (2, 1): "Многодетный взрослый, 1 ч.", +++ (2, 2): "Многодетный взрослый, 2 ч.", +++ (2, 3): "Многодетный взрослый, 3 ч.", +++ (3, 1): "Многодетный детский, 1 ч.", +++ (3, 2): "Многодетный детский, 2 ч.", +++ (3, 3): "Многодетный детский, 3 ч.", ++ (4, 3): "Инвалид, 3 ч.", ++ (5, 3): "Сопровождающий, 3 ч.", ++ } ++diff --git a/modules/payment_equipment.py b/modules/payment_equipment.py ++index 9b2005a..49695eb 100644 ++--- a/modules/payment_equipment.py +++++ b/modules/payment_equipment.py ++@@ -3,7 +3,6 @@ import subprocess ++ import time ++ from contextlib import contextmanager ++ from datetime import datetime, timedelta ++-from typing import Optional ++ ++ from modules import windows ++ from modules.config import Config ++@@ -14,10 +13,12 @@ config = Config() ++ ++ # Получаем параметр available ++ try: ++- kkt_available = config.get("available") == 'on' +++ kkt_available = config.get("available") == "on" ++ except (KeyError, ValueError): ++ kkt_available = False ++- logger.info("Параметр 'available' не найден в конфигурации, ККТ считается отключенным") +++ logger.info( +++ "Параметр 'available' не найден в конфигурации, ККТ считается отключенным" +++ ) ++ ++ # Инициализация драйвера ККТ ++ try: ++@@ -27,7 +28,7 @@ try: ++ fptr = None ++ logger.info("ККТ отключен в конфигурации (available ≠ on)") ++ except Exception as e: ++- logger.warning(f"Не установлен драйвер ККТ: {str(e)}") +++ logger.warning(f"Не установлен драйвер ККТ: {e!s}") ++ kkt_available = False ++ fptr = None ++ ++@@ -111,7 +112,6 @@ TERMINAL_PIN_PAD_ERROR: set[int] = { ++ 4144, ++ 4145, ++ 4146, ++- 4146, ++ 4202, ++ 4203, ++ 4208, ++@@ -164,7 +164,7 @@ def fptr_connection(device): ++ device.open() ++ connected = True ++ ++- if not hasattr(device, 'isOpened') or not device.isOpened(): +++ if not hasattr(device, "isOpened") or not device.isOpened(): ++ logger.warning("Не удалось открыть соединение с ККТ") ++ yield None ++ return ++@@ -172,7 +172,7 @@ def fptr_connection(device): ++ yield device ++ ++ except Exception as e: ++- logger.error(f"Ошибка подключения к ККТ: {str(e)}") +++ logger.error(f"Ошибка подключения к ККТ: {e!s}") ++ yield None ++ ++ finally: ++@@ -211,18 +211,24 @@ def run_terminal_command(command_params: str, timeout: int = 300): ++ logger.info(f"Запуск команды: {pinpad_run}") ++ try: ++ process = subprocess.Popen( ++- pinpad_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ++- shell=True # Важно для строки команды +++ pinpad_run, +++ stdout=subprocess.PIPE, +++ stderr=subprocess.PIPE, +++ shell=True, # Важно для строки команды ++ ) ++ try: ++ stdout, stderr = process.communicate(timeout=timeout) ++ except subprocess.TimeoutExpired: ++- logger.warning(f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически.") +++ logger.warning( +++ f"Процесс превысил таймаут {timeout} секунд. Завершается автоматически." +++ ) ++ process.terminate() ++ try: ++ stdout, stderr = process.communicate(timeout=5) ++ except subprocess.TimeoutExpired: ++- logger.warning("Процесс не завершился после terminate(). Используется kill().") +++ logger.warning( +++ "Процесс не завершился после terminate(). Используется kill()." +++ ) ++ process.kill() ++ stdout, stderr = process.communicate() ++ ++@@ -296,6 +302,7 @@ def _safe_handle_error(returncode, title, message, error_callback): ++ except Exception as e: ++ logger.exception(f"Ошибка при вызове handle_error для кода {returncode}: {e}") ++ +++ ++ @logger_wraps() ++ def handle_error(code, title, message, error_callback=None): ++ """Универсальная обработка ошибок терминала. ++@@ -343,15 +350,15 @@ def process_terminal_error(returncode, error_callback=None): ++ error_handlers = { ++ TERMINAL_USER_CANCEL_CODE: ( ++ "Оплата отменена пользователем", ++- f"Оплата отменена пользователем. Код возврата: {returncode}." +++ f"Оплата отменена пользователем. Код возврата: {returncode}.", ++ ), ++ TERMINAL_USER_TIMEOUT: ( ++ "Слишком долгий ввод ПИН-кода", ++- f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}." +++ f"Слишком долгий ввод ПИН-кода. Код возврата: {returncode}.", ++ ), ++ TERMINAL_INVALID_CURRENCY_CODE: ( ++ "Указан неверный код валюты", ++- f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}." +++ f"Указан неверный код валюты. Обратитесь в банк. Телефон поддержки {TERMINAL_SUPPORT}. Код: {returncode}.", ++ ), ++ TERMINAL_NO_ADDRESS_TO_CONTACT: ( ++ "Терминал потерял связь с банком", ++@@ -359,7 +366,7 @@ def process_terminal_error(returncode, error_callback=None): ++ ), ++ TERMINAL_OPERATION_CANCELED: ( ++ "Операция отменена", ++- f"Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", +++ "Ошибка возникает тогда, когда карту достают из терминала быстрее, чем пройдет оплата. Необходимо повторить операцию.", ++ ), ++ TERMINAL_QR_ERROR: ( ++ "Ошибка операции по QR-коду", ++@@ -377,8 +384,8 @@ def process_terminal_error(returncode, error_callback=None): ++ _safe_handle_error( ++ returncode, ++ "Карта клиента заблокирована", ++- f"Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", ++- error_callback +++ "Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_CARD_LIMIT: ++@@ -386,7 +393,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Превышен лимит операций", ++ "Превышен лимит операций. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_BIOMETRIC_ERROR: ++@@ -394,7 +401,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Ошибка в работе с биометрическими данными", ++ f"Ошибка в работе с биометрическими данными. Обратитесь в банк для выяснения причины. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_ERROR_PIN_CODE: ++@@ -402,7 +409,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Ошибка при вводе ПИН-кода", ++ "ПИН-код не был введен, либо введен неверно, либо вводимый ПИН-код заблокирован. Попробуйте повторить операцию оплаты.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_NO_CONNECTION_BANK: ++@@ -410,7 +417,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Нет связи с банком", ++ f"Попробуйте повторить операцию через пару минут. При повторении ошибки необходимо обратиться в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_NEED_CASH_COLLECTION: ++@@ -418,7 +425,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Необходимо произвести инкассацию", ++ f"Необходимо произвести инкассацию. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_LIMIT_OPERATION: ++@@ -426,7 +433,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Превышен лимит операций", ++ f"Превышен лимит операций. Обратитесь в службу поддержки. Телефон. тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_DATA_EXCHANGE: ++@@ -434,7 +441,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Требуется сделать сверку итогов", ++ "Требуется сделать сверку итогов и повторить операцию.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_KLK: ++@@ -442,7 +449,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Ошибка в работе терминала", ++ f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_CARD_ERROR: ++@@ -450,7 +457,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Операцию невозможно выполнить для этой карты", ++ f"Необходимо повторить попытку. Если проблема сохраняется – использовать другую карту. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_SERVER_ROUTINE_MAINTENANCE: ++@@ -458,15 +465,15 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Сервера Сбербанка недоступны", ++ f"Сервера Сбербанка находятся на обслуживании/ремонте/регламентных работах. Попробуйте повторить операцию позже. Если проблема сохраняется - обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}.Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_NO_MONEY: ++ _safe_handle_error( ++ returncode, ++ "Недостаточно средств на карте", ++- f"Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", ++- error_callback +++ "Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_COMMAND_ERROR: ++@@ -474,7 +481,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Нет нужного варианта связи для операции", ++ f"Нет нужного варианта связи для операции. Обратитесь в службу поддержки ПО. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ if returncode in TERMINAL_PIN_PAD_ERROR: ++@@ -482,7 +489,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Проблема в работе ПИН-пада.", ++ f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ # Общие ошибки ++@@ -499,7 +506,7 @@ def process_terminal_error(returncode, error_callback=None): ++ returncode, ++ "Ошибка терминала", ++ f"Возвращен неизвестный код ошибки. Обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", ++- error_callback +++ error_callback, ++ ) ++ return 0 ++ ++@@ -519,7 +526,9 @@ def terminal_oplata(amount: float) -> int: ++ ++ ++ @logger_wraps() ++-def process_terminal_transaction(command_code: str, amount: float, operation_name: str, error_callback=None) -> int: +++def process_terminal_transaction( +++ command_code: str, amount: float, operation_name: str, error_callback=None +++) -> int: ++ """ ++ Обрабатывает операцию на банковском терминале. ++ ++@@ -541,10 +550,10 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam ++ if result is None: ++ logger.error(f"Ошибка при выполнении команды терминала: {command}") ++ _safe_handle_error( ++- f"Нет ответа от терминала", +++ "Нет ответа от терминала", ++ f"{operation_name} не была выполнена. Проверьте устройство.", ++ "Код ошибки: отсутствует", ++- error_callback=error_callback +++ error_callback=error_callback, ++ ) ++ return 0 ++ ++@@ -567,11 +576,12 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam ++ ++ ++ def universal_terminal_operation( ++- payment_type: int, ++- amount: float, ++- progress_signal, ++- operation_type: int = 1, ++- error_callback=None) -> tuple[int, int]: +++ payment_type: int, +++ amount: float, +++ progress_signal, +++ operation_type: int = 1, +++ error_callback=None, +++) -> tuple[int, int]: ++ """ ++ Универсальный обработчик терминальных операций для оплаты или возврата. ++ ++@@ -621,7 +631,9 @@ def universal_terminal_operation( ++ except Exception: ++ pass # Игнорируем ошибки emit ++ ++- bank = process_terminal_transaction(command_code, amount, operation_name, error_callback) +++ bank = process_terminal_transaction( +++ command_code, amount, operation_name, error_callback +++ ) ++ if bank == 1: ++ if progress_signal is not None: ++ try: ++@@ -638,12 +650,16 @@ def universal_terminal_operation( ++ return 0, 1 ++ elif payment_type == PAYMENT_OFFLINE: ++ if operation_type != 1: ++- logger.warning("Оффлайн-режим не поддерживается для возврата или отмены") +++ logger.warning( +++ "Оффлайн-режим не поддерживается для возврата или отмены" +++ ) ++ progress_signal.emit("Оффлайн: только оплата", 100) ++ return 0, 0 ++ ++ logger.info("Запускаем offline оплату по банковскому терминалу") ++- progress_signal.emit("Запускаем offline оплату по банковскому терминалу...", 35) +++ progress_signal.emit( +++ "Запускаем offline оплату по банковскому терминалу...", 35 +++ ) ++ return 1, 3 # успех, offline ++ ++ else: ++@@ -652,13 +668,15 @@ def universal_terminal_operation( ++ return 0, 0 ++ except ValueError as ve: ++ logger.error(f"Ошибка: {ve}") ++- progress_signal.emit(f"Ошибка: {str(ve)}", 100) +++ progress_signal.emit(f"Ошибка: {ve!s}", 100) ++ return 0, 0 ++ except Exception as exp: ++ logger.error(f"Неизвестная ошибка при проведении операции: {exp}") ++ if progress_signal is not None: ++ try: ++- progress_signal.emit(f"Неизвестная ошибка при проведении операции: {str(exp)}", 100) +++ progress_signal.emit( +++ f"Неизвестная ошибка при проведении операции: {exp!s}", 100 +++ ) ++ except RuntimeError: ++ pass ++ return 0, 0 ++@@ -979,7 +997,8 @@ def print_pinpad_check(count: int = 2): ++ ++ count -= 1 ++ ++-def get_info(hide: bool = False) -> Optional[int]: +++ +++def get_info(hide: bool = False) -> int | None: ++ """ ++ Запрос информации о ККТ. ++ Запрашивает информацию о кассовом аппарате (модель, наименование и версия ПО). ++@@ -1014,13 +1033,20 @@ def get_info(hide: bool = False) -> Optional[int]: ++ return model ++ except (ConnectionError, AttributeError) as e: ++ logger.error(f"Ошибка при получении данных о ККТ: {e}") ++- windows.info_window("Ошибка", "Не удалось получить данные о ККТ.", "Проверьте настройки устройства.") +++ windows.info_window( +++ "Ошибка", +++ "Не удалось получить данные о ККТ.", +++ "Проверьте настройки устройства.", +++ ) ++ return None ++ except Exception as e: ++ logger.error(f"Неизвестная ошибка: {e}") ++- windows.info_window("Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова.") +++ windows.info_window( +++ "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." +++ ) ++ return None ++ +++ ++ def is_kkt_connected() -> bool: ++ """ ++ Проверка физического подключения ККТ. ++@@ -1046,6 +1072,7 @@ def is_kkt_connected() -> bool: ++ logger.error(f"Ошибка при проверке подключения ККТ: {e}") ++ return False ++ +++ ++ @logger_wraps() ++ def get_last_document(day: int = 7) -> None: ++ """Запрос информации о последнем чеке в ФН и проверка даты. ++@@ -1066,7 +1093,9 @@ def get_last_document(day: int = 7) -> None: ++ except (ConnectionError, AttributeError) as e: ++ logger.error(f"Ошибка при работе с ККТ: {e}") ++ windows.info_window( ++- "Ошибка", "Не удалось получить данные от ККТ.", "Проверьте подключение и настройки." +++ "Ошибка", +++ "Не удалось получить данные от ККТ.", +++ "Проверьте подключение и настройки.", ++ ) ++ except Exception as e: ++ logger.exception(f"Неизвестная ошибка в get_last_document: {e}") ++@@ -1074,8 +1103,9 @@ def get_last_document(day: int = 7) -> None: ++ "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." ++ ) ++ +++ ++ def get_last_document_datetime() -> datetime: ++- """ Получает дату и время последнего зарегистрированного чека из фискального накопителя. +++ """Получает дату и время последнего зарегистрированного чека из фискального накопителя. ++ ++ Возвращаемое значение: ++ datetime: Дата и время последнего документа в фискальном накопителе. ++@@ -1085,12 +1115,17 @@ def get_last_document_datetime() -> datetime: ++ """ ++ logger.info("Запуск функции get_last_document_datetime") ++ with fptr_connection(fptr): ++- fptr.setParam(IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT) +++ fptr.setParam( +++ IFptr.LIBFPTR_PARAM_FN_DATA_TYPE, IFptr.LIBFPTR_FNDT_LAST_DOCUMENT +++ ) ++ fptr.fnQueryData() ++- last_check_datetime: datetime = fptr.getParamDateTime(IFptr.LIBFPTR_PARAM_DATE_TIME) +++ last_check_datetime: datetime = fptr.getParamDateTime( +++ IFptr.LIBFPTR_PARAM_DATE_TIME +++ ) ++ ++ return last_check_datetime ++ +++ ++ def check_stale_document(last_check: datetime, max_days: int = 7) -> None: ++ """ ++ Проверяет, не превышает ли возраст последнего чека заданное количество дней. ++@@ -1108,13 +1143,15 @@ def check_stale_document(last_check: datetime, max_days: int = 7) -> None: ++ if (current_date - last_check) <= timedelta(days=max_days): ++ # Ранний выход, если чек не старый ++ return ++- info = f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" +++ info = ( +++ f"Дата и время последнего чека в ФН: {last_check.strftime('%Y-%m-%d %H:%M:%S')}" +++ ) ++ windows.info_window( ++ "Внимание", ++ "Дата последнего документа, записанного в фискальном накопителе, старше 7 дней.\n\n" ++ "Для корректного проведения платежей по банковскому терминалу " ++- "необходимо сделать сверку итогов (вкладка \"Касса\" -> \"Сверка итогов\").\n", ++- info +++ 'необходимо сделать сверку итогов (вкладка "Касса" -> "Сверка итогов").\n', +++ info, ++ ) ++ ++ ++@@ -1402,7 +1439,9 @@ def kassir_reg(user): ++ try: ++ with fptr_connection(fptr): ++ # Параметры для регистрации кассира ++- fptr.setParam(1021, f"{user.last_name} {user.first_name} {user.middle_name}") +++ fptr.setParam( +++ 1021, f"{user.last_name} {user.first_name} {user.middle_name}" +++ ) ++ fptr.setParam(1203, user.inn) ++ fptr.operatorLogin() ++ logger.info( ++@@ -1590,11 +1629,11 @@ def register_item(device, name, price, quantity, tax_type=IFptr.LIBFPTR_TAX_VAT2 ++ logger.info("Запуск функции register_item") ++ logger.info(f"В функцию переданы параметры: {name}, {price}, {quantity}") ++ device.setParam(IFptr.LIBFPTR_PARAM_COMMODITY_NAME, name) ++- logger.info(f"name: {name}") # TODO: убрать +++ logger.info(f"name: {name}") # TODO: убрать ++ device.setParam(IFptr.LIBFPTR_PARAM_PRICE, price) ++- logger.info(f"price: {price}") # TODO: убрать +++ logger.info(f"price: {price}") # TODO: убрать ++ device.setParam(IFptr.LIBFPTR_PARAM_QUANTITY, quantity) ++- logger.info(f"quantity: {quantity}") # TODO: убрать +++ logger.info(f"quantity: {quantity}") # TODO: убрать ++ device.setParam(IFptr.LIBFPTR_PARAM_TAX_TYPE, tax_type) ++ device.registration() ++ ++@@ -1639,7 +1678,9 @@ def register_tickets(device, sale_dict, type_operation): ++ None: Функция не возвращает значений, но выполняет регистрацию билетов в чеке. ++ """ ++ logger.info("Запуск функции register_tickets") ++- logger.debug(f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}") +++ logger.debug( +++ f"В функцию переданы: device = {device}, sale_dict = {sale_dict}, type_operation = {type_operation}" +++ ) ++ ++ if type_operation == 1: ++ count_adult_with_discount = sale_dict["detail"][0] ++@@ -1653,29 +1694,41 @@ def register_tickets(device, sale_dict, type_operation): ++ ++ # Взрослые без акции ++ count_adult_no_discount = sale_dict["kol_adult"] - count_adult_with_discount ++- logger.debug(f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}") +++ logger.debug( +++ f"Взрослые без акции: kol_adult={sale_dict['kol_adult']}, price_adult= {sale_dict['price_adult']}" +++ ) ++ ++ if count_adult_no_discount > 0: ++ adults_no_discount_sum = count_adult_no_discount * sale_dict["price_adult"] ++- logger.info(f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб") +++ logger.info( +++ f"Регистрируем взрослых без акции: {count_adult_no_discount} шт × {sale_dict['price_adult']} = {adults_no_discount_sum} руб" +++ ) ++ # if device is not None: # TODO: для теста без ККТ. + отступ внизу ++ register_item( ++ device, ++ f"Билет взрослый {time} ч.", ++- sale_dict['price_adult'], +++ sale_dict["price_adult"], ++ count_adult_no_discount, ++ ) ++ else: ++ logger.debug("Взрослые без акции: не регистрируем (количество = 0)") ++ ++ # Взрослые с акцией ++- logger.debug(f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}") +++ logger.debug( +++ f"Взрослые С акцией: count_adult_with_discount={count_adult_with_discount}, price_adult_with_discount={price_adult_with_discount}, discount={price_adult_with_discount}" +++ ) ++ ++- if (count_adult_with_discount > 0 ++- and price_adult_with_discount > 0 ++- and discount > 0): ++- adults_with_discount_sum = count_adult_with_discount * price_adult_with_discount ++- logger.info(f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб") +++ if ( +++ count_adult_with_discount > 0 +++ and price_adult_with_discount > 0 +++ and discount > 0 +++ ): +++ adults_with_discount_sum = ( +++ count_adult_with_discount * price_adult_with_discount +++ ) +++ logger.info( +++ f"Регистрируем взрослых с акцией: {count_adult_with_discount} шт × {price_adult_with_discount} = {adults_with_discount_sum} руб" +++ ) ++ # if device is not None: # TODO: для теста без ККТ. + отступ внизу ++ register_item( ++ device, ++@@ -1690,29 +1743,43 @@ def register_tickets(device, sale_dict, type_operation): ++ ++ # Дети без акции ++ count_child_no_discount = sale_dict["kol_child"] - count_child_with_discount ++- logger.debug(f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}") +++ logger.debug( +++ f"Дети без акции: kol_child={sale_dict['kol_child']}, count_child_with_discount={count_child_with_discount}, result={count_child_no_discount}" +++ ) ++ ++ if count_child_no_discount > 0: ++- children_no_discount_sum = count_child_no_discount * sale_dict["price_child"] ++- logger.info(f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб") +++ children_no_discount_sum = ( +++ count_child_no_discount * sale_dict["price_child"] +++ ) +++ logger.info( +++ f"Регистрируем детей без акции: {count_child_no_discount} шт × {sale_dict['price_child']} = {children_no_discount_sum} руб" +++ ) ++ # if device is not None: # TODO: для теста без ККТ. + отступ внизу ++ register_item( ++ device, ++ f"Билет детский {time} ч.", ++- sale_dict['price_child'], +++ sale_dict["price_child"], ++ count_child_no_discount, ++ ) ++ else: ++ logger.debug("Дети без акции: не регистрируем (количество = 0)") ++ ++ # Дети с акцией ++- logger.debug(f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}") +++ logger.debug( +++ f"Дети с акцией: count_child_with_discount={count_child_with_discount}, price_child_with_discount={price_child_with_discount}, discount={price_adult_with_discount}" +++ ) ++ ++- if (count_child_with_discount > 0 ++- and price_child_with_discount > 0 ++- and discount > 0): ++- children_with_discount_sum = count_child_with_discount * price_child_with_discount ++- logger.info(f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб") +++ if ( +++ count_child_with_discount > 0 +++ and price_child_with_discount > 0 +++ and discount > 0 +++ ): +++ children_with_discount_sum = ( +++ count_child_with_discount * price_child_with_discount +++ ) +++ logger.info( +++ f"Регистрируем детей с акцией: {count_child_with_discount} шт × {price_child_with_discount} = {children_with_discount_sum} руб" +++ ) ++ # if device is not None: # TODO: для теста без ККТ. + отступ внизу ++ register_item( ++ device, ++@@ -1724,15 +1791,17 @@ def register_tickets(device, sale_dict, type_operation): ++ logger.debug("Дети с акцией: не регистрируем (условия не выполнены)") ++ else: ++ # Для других типов операций ++- logger.error(f"Другие типы операций.") +++ logger.error("Другие типы операций.") ++ for item_name, item_data in sale_dict.items(): ++- if (isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0): +++ if isinstance(item_data, list) and item_data[0] > 0 and item_data[1] > 0: ++ # Проверяем наличие количества и цены ++- logger.debug(f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}") +++ logger.debug( +++ f"item_name ={item_name}, item_data[0]={item_data[0]}, item_data[1]={item_data[1]}" +++ ) ++ register_item(device, item_name, item_data[0], item_data[1]) ++ else: ++ # Обработка других типов данных ++- logger.error(f"Ошибка типа данных. Item_data - не список.") +++ logger.error("Ошибка типа данных. Item_data - не список.") ++ ++ ++ def process_payment(device, payment_type, bank_status, sale_dict, _): ++@@ -1748,11 +1817,15 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): ++ bool: Возвращает `True`, если оплата прошла успешно, иначе — `False`. ++ """ ++ logger.info("Запуск функции process_payment") ++- logger.debug(f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}") +++ logger.debug( +++ f"В функцию переданы: device = {device}, payment_type = {payment_type}, bank = {bank_status}, sale_dict = {sale_dict}, price = {_}" +++ ) ++ try: ++ # Проверка payment_type ++ if not isinstance(payment_type, int): ++- logger.error(f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}") +++ logger.error( +++ f"Некорректный тип payment_type: {type(payment_type)}, значение: {payment_type}" +++ ) ++ return False ++ ++ # Определение суммы ++@@ -1763,7 +1836,9 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): ++ try: ++ payment_amount = sale_dict["detail"][7] ++ if isinstance(payment_amount, (int, float)) and payment_amount > 0: ++- logger.debug(f"Сумма получена из sale_dict['detail'][7]: {payment_amount}") +++ logger.debug( +++ f"Сумма получена из sale_dict['detail'][7]: {payment_amount}" +++ ) ++ except (IndexError, TypeError): ++ pass ++ ++@@ -1775,7 +1850,12 @@ def process_payment(device, payment_type, bank_status, sale_dict, _): ++ if isinstance(item_data, list) and len(item_data) >= 2: ++ price = item_data[0] ++ qty = item_data[1] ++- if isinstance(price, (int, float)) and isinstance(qty, int) and price > 0 and qty > 0: +++ if ( +++ isinstance(price, (int, float)) +++ and isinstance(qty, int) +++ and price > 0 +++ and qty > 0 +++ ): ++ total += price * qty ++ valid = True ++ if valid and total > 0: ++@@ -1846,15 +1926,23 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): ++ logger.info("Запуск функции handle_document_errors") ++ # Проверяем, закрыт ли документ ++ while device.checkDocumentClosed() < 0: ++- logger.warning(f"Не удалось проверить состояние документа: {device.errorDescription()}") +++ logger.warning( +++ f"Не удалось проверить состояние документа: {device.errorDescription()}" +++ ) ++ if retry_count >= max_retries - 1: ++ if on_error: ++- on_error("Ошибка ККТ", f"Не удалось проверить состояние документа после {max_retries} попыток. " ++- f"Проверьте соединение с ККТ и не выключайте ПК.") +++ on_error( +++ "Ошибка ККТ", +++ f"Не удалось проверить состояние документа после {max_retries} попыток. " +++ f"Проверьте соединение с ККТ и не выключайте ПК.", +++ ) ++ return False ++ ++ if on_error: ++- on_error("Ошибка ККТ", f"Ошибка: {device.errorDescription()}. Повторная попытка...") +++ on_error( +++ "Ошибка ККТ", +++ f"Ошибка: {device.errorDescription()}. Повторная попытка...", +++ ) ++ ++ retry_count += 1 ++ time.sleep(5) ++@@ -1868,12 +1956,17 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): ++ except Exception as e: ++ logger.critical(f"Ошибка при отмене чека: {e}") ++ if on_error: ++- on_error("Критическая ошибка", "Не удалось отменить чек. Требуется перезагрузка ККТ.") +++ on_error( +++ "Критическая ошибка", +++ "Не удалось отменить чек. Требуется перезагрузка ККТ.", +++ ) ++ # Пробрасываем исключение наверх ++ raise ++ ++ if on_error: ++- on_error("Документ не закрыт", "Чек отменен. Требуется сформировать его заново.") +++ on_error( +++ "Документ не закрыт", "Чек отменен. Требуется сформировать его заново." +++ ) ++ return False ++ ++ # Если документ не напечатан — попробовать допечатать ++@@ -1884,12 +1977,19 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): ++ logger.info("Документ успешно допечатан") ++ return True ++ ++- logger.warning(f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}") +++ logger.warning( +++ f"Попытка допечатки №{print_attempt + 1} не удалась: {device.errorDescription()}" +++ ) ++ if on_error: ++- on_error("Ошибка печати", f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.") +++ on_error( +++ "Ошибка печати", +++ f"Ошибка допечатки: {device.errorDescription()}. Повторите попытку.", +++ ) ++ time.sleep(3) ++ ++- logger.error("Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции.") +++ logger.error( +++ "Не удалось допечатать документ после 5 попыток. Он будет допечатан автоматически при следующей операции." +++ ) ++ # Документ закрыт, но не допечатан — допустимо. ++ # Документ будет автоматически допечатан при следующей печатной операции. ++ ++@@ -1901,7 +2001,15 @@ def handle_document_errors(device, retry_count, max_retries, on_error=None): ++ return True ++ ++ ++-def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_status, on_error=None): +++def check_open( +++ sale_dict, +++ payment_type, +++ user, +++ type_operation, +++ print_check, +++ bank_status, +++ on_error=None, +++): ++ """ ++ Проведение операции оплаты. ++ ++@@ -1941,12 +2049,16 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ ++ setup_fptr(device, user, type_operation, print_check) ++ ++ # Проверка состояния чека ++- device.setParam(IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE) +++ device.setParam( +++ IFptr.LIBFPTR_PARAM_DATA_TYPE, IFptr.LIBFPTR_DT_RECEIPT_STATE +++ ) ++ device.queryData() ++- receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) ++- document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) +++ receipt_number = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_NUMBER) +++ document_number = device.getParamInt(IFptr.LIBFPTR_PARAM_DOCUMENT_NUMBER) ++ receipt_type = device.getParamInt(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE) ++- logger.debug(f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}") +++ logger.debug( +++ f"receipt_type: {receipt_type}, receipt_number: {receipt_number}, document_number: {document_number}" +++ ) ++ if receipt_type != IFptr.LIBFPTR_RT_CLOSED: ++ logger.error(f"Чек уже открыт! Тип: {receipt_type}") ++ if on_error: ++@@ -1955,9 +2067,13 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ ++ ++ # Установка типа чека ++ if type_operation == 1: ++- device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL) # Продажа +++ device.setParam( +++ IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL +++ ) # Продажа ++ elif type_operation == 2: ++- device.setParam(IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN) # Возврат +++ device.setParam( +++ IFptr.LIBFPTR_PARAM_RECEIPT_TYPE, IFptr.LIBFPTR_RT_SELL_RETURN +++ ) # Возврат ++ else: ++ logger.error(f"Неизвестный тип операции: {type_operation}") ++ return False ++@@ -1980,11 +2096,12 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ ++ return 1 ++ ++ except Exception as e: ++- logger.error(f"Критическая ошибка в check_open: {str(e)}") +++ logger.error(f"Критическая ошибка в check_open: {e!s}") ++ if on_error: ++- on_error("Ошибка ККТ", f"Критическая ошибка: {str(e)}") +++ on_error("Ошибка ККТ", f"Критическая ошибка: {e!s}") ++ return 0 ++ +++ ++ @logger_wraps() ++ def smena_close(user): ++ """Закрытие кассовой смены. ++diff --git a/modules/progress_window.py b/modules/progress_window.py ++index c8e6d1e..660ac03 100644 ++--- a/modules/progress_window.py +++++ b/modules/progress_window.py ++@@ -1,5 +1,4 @@ ++-from PySide6.QtCore import QPropertyAnimation, QEasingCurve ++-from PySide6.QtCore import Qt +++from PySide6.QtCore import QEasingCurve, QPropertyAnimation, Qt ++ from PySide6.QtWidgets import QDialog ++ ++ from design.logic.progress_dialog import Ui_ProgressDialog ++diff --git a/modules/sale_logic.py b/modules/sale_logic.py ++index 5ba2434..dcadae4 100644 ++--- a/modules/sale_logic.py +++++ b/modules/sale_logic.py ++@@ -230,10 +230,10 @@ def calculate_itog() -> int: ++ ++ # Проверка на корректность данных ++ if ( ++- system.sale_dict["detail"][0] < 0 ++- or system.sale_dict["detail"][2] < 0 ++- or system.sale_dict["detail"][1] < 0 ++- or system.sale_dict["detail"][3] < 0 +++ system.sale_dict["detail"][0] < 0 +++ or system.sale_dict["detail"][2] < 0 +++ or system.sale_dict["detail"][1] < 0 +++ or system.sale_dict["detail"][3] < 0 ++ ): ++ raise ValueError("Некорректные данные для расчета итоговой суммы") ++ ++@@ -243,7 +243,9 @@ def calculate_itog() -> int: ++ adults_no_discount_sum = adults_no_discount * system.sale_dict["price_adult"] ++ ++ # Взрослые со скидкой (detail[0] × detail[1]) ++- adults_with_discount_sum = system.sale_dict["detail"][0] * system.sale_dict["detail"][1] +++ adults_with_discount_sum = ( +++ system.sale_dict["detail"][0] * system.sale_dict["detail"][1] +++ ) ++ ++ adult_total = adults_no_discount_sum + adults_with_discount_sum ++ ++@@ -253,21 +255,33 @@ def calculate_itog() -> int: ++ children_no_discount_sum = children_no_discount * system.sale_dict["price_child"] ++ ++ # Дети со скидкой (detail[2] × detail[3]) ++- children_with_discount_sum = system.sale_dict["detail"][2] * system.sale_dict["detail"][3] +++ children_with_discount_sum = ( +++ system.sale_dict["detail"][2] * system.sale_dict["detail"][3] +++ ) ++ ++ child_total = children_no_discount_sum + children_with_discount_sum ++ ++ result = adult_total + child_total ++ ++- logger.debug(f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}") ++- logger.debug(f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}") +++ logger.debug( +++ f"Взрослые без скидки: {adults_no_discount} × {system.sale_dict['price_adult']} = {adults_no_discount_sum}" +++ ) +++ logger.debug( +++ f"Взрослые со скидкой: {system.sale_dict['detail'][0]} × {system.sale_dict['detail'][1]} = {adults_with_discount_sum}" +++ ) ++ logger.debug(f"ИТОГО взрослые: {adult_total}") ++ ++- logger.debug(f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}") ++- logger.debug(f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}") +++ logger.debug( +++ f"Дети без скидки: {children_no_discount} × {system.sale_dict['price_child']} = {children_no_discount_sum}" +++ ) +++ logger.debug( +++ f"Дети со скидкой: {system.sale_dict['detail'][2]} × {system.sale_dict['detail'][3]} = {children_with_discount_sum}" +++ ) ++ logger.debug(f"ИТОГО дети: {child_total}") ++ ++- logger.debug(f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}") +++ logger.debug( +++ f"Расчет итого: {adult_total} (взрослые) + {child_total} (дети) = {result}" +++ ) ++ ++ return result ++ ++diff --git a/modules/system.py b/modules/system.py ++index 34c8fdf..0c7b82d 100644 ++--- a/modules/system.py +++++ b/modules/system.py ++@@ -12,7 +12,7 @@ from sqlalchemy import create_engine, select ++ from sqlalchemy.exc import SQLAlchemyError ++ from sqlalchemy.orm import Session ++ ++-from db.models import Holiday, Workday, Price, User, Sale +++from db.models import Holiday, Price, Sale, User, Workday ++ from modules.config import Config ++ from modules.logger import logger, logger_wraps ++ ++@@ -24,7 +24,7 @@ class System: ++ ++ def __new__(cls, *args, **kwargs): ++ if not cls._instance: ++- cls._instance = super(System, cls).__new__(cls, *args, **kwargs) +++ cls._instance = super().__new__(cls, *args, **kwargs) ++ return cls._instance ++ ++ def __init__(self): ++@@ -167,7 +167,9 @@ class System: ++ logger.info(f"Успешная авторизация: {kassir.last_name}") ++ return 1 ++ else: ++- logger.warning(f"Неудачная попытка авторизации для пользователя {login}") +++ logger.warning( +++ f"Неудачная попытка авторизации для пользователя {login}" +++ ) ++ return 0 ++ else: ++ logger.warning(f"Пользователь {login} не найден") ++@@ -241,7 +243,7 @@ class System: ++ db_prices[key] = default_prices[key] ++ else: ++ # Если записей нет, используем дефолтные значения для всех элементов ++- logger.warning(f"Используем дефолтные значения для прайс-листа") +++ logger.warning("Используем дефолтные значения для прайс-листа") ++ db_prices = default_prices ++ ++ # Устанавливаем цены, используя данные из db_prices или дефолтные значения ++@@ -410,24 +412,23 @@ class System: ++ query = select(Sale.bank_pay).where(Sale.id == sale_id) ++ load_slip = session.execute(query).scalars().one() ++ try: ++- qr_match = re.search(r'Номер QR:\s+(\d+)', load_slip) +++ qr_match = re.search(r"Номер QR:\s+(\d+)", load_slip) ++ if qr_match: ++- last_digits = re.search(r'Карта:\s+\*+\s*(\d{4})', load_slip) ++- card_tail = last_digits.group(1) if last_digits else '' +++ last_digits = re.search(r"Карта:\s+\*+\s*(\d{4})", load_slip) +++ card_tail = last_digits.group(1) if last_digits else "" ++ else: ++- last_digits = re.search(r'Карта:\(?E\d*\)?\s+\*+\s*(\d{4})', load_slip) ++- card_tail = last_digits.group(1) if last_digits else '' ++- merchant = re.search(r'М:(\d+)', load_slip) ++- merchant_id = merchant.group(1) if merchant else '' ++- rrn = re.search(r'RRN:\s+(\d+)', load_slip) ++- rrn_value = rrn.group(1) if rrn else '' +++ last_digits = re.search(r"Карта:\(?E\d*\)?\s+\*+\s*(\d{4})", load_slip) +++ card_tail = last_digits.group(1) if last_digits else "" +++ merchant = re.search(r"М:(\d+)", load_slip) +++ merchant_id = merchant.group(1) if merchant else "" +++ rrn = re.search(r"RRN:\s+(\d+)", load_slip) +++ rrn_value = rrn.group(1) if rrn else "" ++ except Exception as e: ++ logger.warning(f"Ошибка при разборе чека: {e}") ++- card_tail = '' ++- merchant_id = '' ++- rrn_value = '' ++- return card_tail, merchant_id, rrn_value, load_slip ++- +++ card_tail = "" +++ merchant_id = "" +++ rrn_value = "" +++ return card_tail, merchant_id, rrn_value, load_slip ++ ++ def check_db_connection(self) -> bool: ++ """Проверка подключения к базе данных.""" ++@@ -439,4 +440,4 @@ class System: ++ return True ++ except Exception as e: ++ logger.error(f"Ошибка подключения к БД: {e}") ++- return False ++\ No newline at end of file +++ return False ++diff --git a/modules/worker.py b/modules/worker.py ++index d83a6e7..15f6a25 100644 ++--- a/modules/worker.py +++++ b/modules/worker.py ++@@ -1,7 +1,15 @@ ++ import datetime as dt ++ from functools import wraps ++ ++-from PySide6.QtCore import QElapsedTimer, QObject, Signal, QMetaObject, Qt, QTimer, QEventLoop +++from PySide6.QtCore import ( +++ QElapsedTimer, +++ QEventLoop, +++ QMetaObject, +++ QObject, +++ Qt, +++ QTimer, +++ Signal, +++) ++ from sqlalchemy import update ++ ++ from db.models import Sale ++@@ -10,16 +18,19 @@ from modules.logger import logger ++ ++ def with_timer(func): ++ """Декоратор для автоматического создания QElapsedTimer""" +++ ++ @wraps(func) ++ def wrapper(self, *args, **kwargs): ++ timer = QElapsedTimer() ++ timer.start() ++ return func(self, timer, *args, **kwargs) +++ ++ return wrapper ++ ++ ++ class BaseWorker(QObject): ++ """Базовый класс с общими методами""" +++ ++ DEFAULT_DELAY_MS = 15 ++ ++ progress_updated = Signal(str, int) ++@@ -35,9 +46,10 @@ class BaseWorker(QObject): ++ elapsed = timer.elapsed() ++ logger.debug(f"[TIMER] {step_name} — {elapsed} ms") ++ ++- def delayed_progress_update(self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS): ++- if delay_ms < 0: ++- delay_ms = 0 +++ def delayed_progress_update( +++ self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS +++ ): +++ delay_ms = max(delay_ms, 0) ++ if delay_ms > 0: ++ # Создаем локальный таймер для обеспечения задержки ++ timer = QTimer(self) ++@@ -58,9 +70,15 @@ class BaseWorker(QObject): ++ Qt.QueuedConnection, ++ ) ++ ++- def emit_error_and_finish(self, title: str, message: str, code: str = "", ++- timer: QElapsedTimer = None, step_name: str = "", ++- close_window: bool = True): +++ def emit_error_and_finish( +++ self, +++ title: str, +++ message: str, +++ code: str = "", +++ timer: QElapsedTimer = None, +++ step_name: str = "", +++ close_window: bool = True, +++ ): ++ """ ++ Универсальный метод обработки ошибок ++ :param title: Заголовок ошибки ++@@ -87,11 +105,13 @@ class BaseWorker(QObject): ++ logger.warning("Попытка emit после удаления объекта — игнорируем") ++ else: ++ raise ++- except Exception as e: +++ except Exception: ++ logger.exception("Неожиданная ошибка в emit_error_and_finish") ++ +++ ++ class PaymentHandler: ++ """Обработчик банковских платежей""" +++ ++ def __init__(self, worker, pq, payment_type, amount, dev_mode=False): ++ self.worker = worker ++ self.pq = pq ++@@ -107,7 +127,9 @@ class PaymentHandler: ++ ++ if self.dev_mode: ++ self.worker.logger.info("РЕЖИМ ОТЛАДКИ: Имитация оплаты картой") ++- self.worker.logger.debug(f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}") +++ self.worker.logger.debug( +++ f"Сумма: {self.amount}, Тип оплаты: {self.payment_type}" +++ ) ++ return True, 1 # (success, payment_type) ++ ++ try: ++@@ -116,7 +138,7 @@ class PaymentHandler: ++ self.payment_type, ++ self.amount, ++ self.worker.progress_updated, ++- error_callback=self._handle_terminal_error_callback +++ error_callback=self._handle_terminal_error_callback, ++ ) ++ self.worker.log_step(timer, "pq.universal_terminal_operation finished") ++ ++@@ -134,19 +156,21 @@ class PaymentHandler: ++ def get_mock_slip(self): ++ """Генерация тестового слип-чека для режима отладки""" ++ from datetime import datetime ++- return (f"DEBUG_SLIP\n" ++- f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" ++- f"Сумма: {self.amount} руб.\n" ++- f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" ++- f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ") ++ ++- def _handle_terminal_error_callback(self, title: str, message: str, code: int) -> None: +++ return ( +++ f"DEBUG_SLIP\n" +++ f"Дата: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}\n" +++ f"Сумма: {self.amount} руб.\n" +++ f"Тип: {'Онлайн' if self.payment_type == 101 else 'Оффлайн'}\n" +++ f"СТАНДАРТНЫЙ ЧЕК ДЛЯ ТЕСТИРОВАНИЯ" +++ ) +++ +++ def _handle_terminal_error_callback( +++ self, title: str, message: str, code: int +++ ) -> None: ++ """Callback для обработки ошибок от терминала""" ++ self.worker.emit_error_and_finish( ++- title=title, ++- message=message, ++- code=str(code), ++- step_name="terminal_error" +++ title=title, message=message, code=str(code), step_name="terminal_error" ++ ) ++ ++ def _handle_payment_failed(self, timer): ++@@ -155,7 +179,7 @@ class PaymentHandler: ++ title="Ошибка", ++ message="Операция оплаты не удалась. Повторите попытку.", ++ timer=timer, ++- step_name="payment_failed" +++ step_name="payment_failed", ++ ) ++ ++ def _handle_payment_exception(self, timer, exception): ++@@ -165,12 +189,13 @@ class PaymentHandler: ++ message="Не удалось выполнить операцию на терминале.", ++ code=str(exception), ++ timer=timer, ++- step_name="payment_exception" +++ step_name="payment_exception", ++ ) ++ ++ ++ class CheckHandler: ++ """Обработчик печати чеков""" +++ ++ def __init__(self, worker, pq): ++ self.worker = worker ++ self.pq = pq ++@@ -184,25 +209,28 @@ class CheckHandler: ++ type_operation=1, ++ print_check=print_check, ++ bank_status=bank_data, ++- on_error=self.handle_check_error +++ on_error=self.handle_check_error, ++ ) ++ ++ return state_check == 1 ++ ++- def handle_check_error(self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка"): +++ def handle_check_error( +++ self, title="Неизвестная ошибка", text="Произошла неизвестная ошибка" +++ ): ++ """Обработчик ошибок печати чека""" ++ logger.error(f"handle_check_error вызван с title={title}, text={text}") ++ self.worker.emit_error_and_finish( ++ title="Ошибка ККМ", ++ message="Документ не закрылся после 5 попыток. Отмена кассового чека.", ++ code="critical", ++- step_name="check_error" +++ step_name="check_error", ++ ) ++ # raise Exception(f"{title}: {text}") ++ ++ ++ class DatabaseHandler: ++ """Обработчик работы с базой данных""" +++ ++ def __init__(self, Session, engine): ++ self.Session = Session ++ self.engine = engine ++@@ -211,7 +239,9 @@ class DatabaseHandler: ++ """Проверяет существование продажи по ID""" ++ try: ++ with self.Session(self.engine) as session: ++- return session.query(Sale).filter(Sale.id == sale_id).first() is not None +++ return ( +++ session.query(Sale).filter(Sale.id == sale_id).first() is not None +++ ) ++ except Exception as e: ++ logger.error(f"Ошибка проверки существования продажи {sale_id}: {e}") ++ return False ++@@ -219,17 +249,16 @@ class DatabaseHandler: ++ def update_sale(self, sale_id, **values): ++ """Общее сохранение данных о продаже""" ++ with self.Session(self.engine) as session: ++- session.execute( ++- update(Sale) ++- .where(Sale.id == sale_id) ++- .values(**values) ++- ) +++ session.execute(update(Sale).where(Sale.id == sale_id).values(**values)) ++ session.commit() ++ ++ ++ class TransactionWorker(BaseWorker): ++ """Основной класс обработки транзакций""" ++- def __init__(self, payment_type, print_check, system, pq, Session, main_window, parent=None): +++ +++ def __init__( +++ self, payment_type, print_check, system, pq, Session, main_window, parent=None +++ ): ++ super().__init__(parent) ++ self.payment_type = payment_type ++ self.print_check = print_check ++@@ -238,7 +267,9 @@ class TransactionWorker(BaseWorker): ++ self.Session = Session ++ self.main_window = main_window ++ # Флаг режима отладки ++- self.dev_mode = getattr(main_window, 'dev_mode', False) if main_window else False +++ self.dev_mode = ( +++ getattr(main_window, "dev_mode", False) if main_window else False +++ ) ++ # Флаг cleanup ++ self._is_cleaned = False ++ ++@@ -262,14 +293,16 @@ class TransactionWorker(BaseWorker): ++ @with_timer ++ def process_special_sale(self, timer: QElapsedTimer, progress_percent: int): ++ """Обработка специальной продажи""" ++- self.delayed_progress_update("Печатаем билеты (особая продажа)...", progress_percent) +++ self.delayed_progress_update( +++ "Печа\xd1 +\ No newline at end of file +diff --git a/tests/test_auth_logic.py b/tests/test_auth_logic.py +index 1c86dbd..0b329af 100644 +--- a/tests/test_auth_logic.py ++++ b/tests/test_auth_logic.py +@@ -1,18 +1,19 @@ ++import datetime as dt + import unittest + from unittest.mock import MagicMock, patch +-import datetime as dt + + from modules.auth_logic import perform_pre_sale_checks + from modules.system import System + ++ + class TestAuthLogic(unittest.TestCase): + + def setUp(self): + # Prevent System from trying to connect to a real DB +- patcher = patch('modules.system.create_engine') ++ patcher = patch("modules.system.create_engine") + self.mock_create_engine = patcher.start() + self.addCleanup(patcher.stop) +- ++ + self.system = System() + self.system.what_a_day = None + self.system.num_of_week = None +@@ -30,48 +31,52 @@ class TestAuthLogic(unittest.TestCase): + + result = perform_pre_sale_checks("valid_login", "valid_password") + self.assertEqual(result, 1) +- self.system.user_authorization.assert_called_once_with("valid_login", "valid_password") ++ self.system.user_authorization.assert_called_once_with( ++ "valid_login", "valid_password" ++ ) + + def test_perform_pre_sale_checks_authorization_failure(self): + self.system.user_authorization = MagicMock(return_value=0) + + result = perform_pre_sale_checks("invalid_login", "invalid_password") + self.assertEqual(result, 0) +- self.system.user_authorization.assert_called_once_with("invalid_login", "invalid_password") ++ self.system.user_authorization.assert_called_once_with( ++ "invalid_login", "invalid_password" ++ ) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_check_day_status_weekday(self, mock_dt): +- mock_date = dt.datetime(2024, 11, 6) # Wednesday ++ mock_date = dt.datetime(2024, 11, 6) # Wednesday + mock_dt.datetime.today.return_value = mock_date +- ++ + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=0) +- ++ + result = perform_pre_sale_checks("valid_login", "valid_password") + self.assertEqual(result, 1) + self.assertEqual(self.system.what_a_day, 0) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_check_day_status_weekend(self, mock_dt): +- mock_date = dt.datetime(2024, 11, 2) # Saturday ++ mock_date = dt.datetime(2024, 11, 2) # Saturday + mock_dt.datetime.today.return_value = mock_date + + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=1) +- ++ + result = perform_pre_sale_checks("valid_login", "valid_password") + self.assertEqual(result, 1) + self.assertEqual(self.system.what_a_day, 1) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_sunday_for_large_families_yes(self, mock_dt): + mock_date = MagicMock() + mock_date.isoweekday.return_value = 7 + mock_date.day = 3 + mock_dt.datetime.today.return_value = mock_date +- ++ + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=0) +@@ -80,13 +85,13 @@ class TestAuthLogic(unittest.TestCase): + self.assertEqual(result, 1) + self.assertEqual(self.system.sunday, 1) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_sunday_for_large_families_no_not_sunday(self, mock_dt): + mock_date = MagicMock() + mock_date.isoweekday.return_value = 1 + mock_date.day = 4 + mock_dt.datetime.today.return_value = mock_date +- ++ + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=0) +@@ -95,13 +100,13 @@ class TestAuthLogic(unittest.TestCase): + self.assertEqual(result, 1) + self.assertEqual(self.system.sunday, 0) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_sunday_for_large_families_no_not_first_week(self, mock_dt): + mock_date = MagicMock() + mock_date.isoweekday.return_value = 7 + mock_date.day = 10 + mock_dt.datetime.today.return_value = mock_date +- ++ + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=0) +@@ -110,13 +115,13 @@ class TestAuthLogic(unittest.TestCase): + self.assertEqual(result, 1) + self.assertEqual(self.system.sunday, 0) + +- @patch('modules.auth_logic.dt') ++ @patch("modules.auth_logic.dt") + def test_week_and_month_day_assignment(self, mock_dt): + mock_date = MagicMock() + mock_date.isoweekday.return_value = 3 + mock_date.day = 6 + mock_dt.datetime.today.return_value = mock_date +- ++ + self.system.user_authorization = MagicMock(return_value=1) + self.system.get_price = MagicMock() + self.system.check_day = MagicMock(return_value=0) +@@ -126,5 +131,6 @@ class TestAuthLogic(unittest.TestCase): + self.assertEqual(result, 1) + self.assertEqual(self.system.num_of_week, 3) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_config.py b/tests/test_config.py +index a7b68f2..c076501 100644 +--- a/tests/test_config.py ++++ b/tests/test_config.py +@@ -1,8 +1,10 @@ +-import unittest + import os + import tempfile ++import unittest ++ + from modules.config import Config + ++ + class TestConfig(unittest.TestCase): + + def setUp(self): +@@ -120,7 +122,9 @@ available = on + + with self.assertRaises(RuntimeError) as context: + Config(config_file=self.config_path) +- self.assertIn("Неизвестная ошибка: Отсутствует параметр 'pc_2'", str(context.exception)) ++ self.assertIn( ++ "Неизвестная ошибка: Отсутствует параметр 'pc_2'", str(context.exception) ++ ) + + def test_empty_pc_name(self): + invalid_content = """ +@@ -155,7 +159,8 @@ available = on + Config(config_file=self.config_path) + self.assertIn("Неизвестная ошибка: Пустое значение", str(context.exception)) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() + + def test_missing_pc_name_empty(self): +@@ -198,4 +203,3 @@ available = on + + with self.assertRaises(RuntimeError): + Config(config_file=self.config_path) +- +diff --git a/tests/test_logger.py b/tests/test_logger.py +index cc28d7a..8f512a2 100644 +--- a/tests/test_logger.py ++++ b/tests/test_logger.py +@@ -1,14 +1,16 @@ + import unittest +-from modules.logger import logger_wraps, logger +-from unittest.mock import patch, MagicMock ++from unittest.mock import patch ++ ++from modules.logger import logger, logger_wraps ++ + + class TestLogger(unittest.TestCase): + def test_logger_wraps_success(self): + @logger_wraps() + def test_func(): + return "success" +- +- with patch.object(logger, 'info') as mock_info: ++ ++ with patch.object(logger, "info") as mock_info: + res = test_func() + self.assertEqual(res, "success") + +@@ -16,8 +18,8 @@ class TestLogger(unittest.TestCase): + @logger_wraps() + def test_func(): + raise ValueError("Test Error") +- +- with patch.object(logger, 'exception') as mock_exc: ++ ++ with patch.object(logger, "exception") as mock_exc: + with self.assertRaises(ValueError): + test_func() + +@@ -25,10 +27,11 @@ class TestLogger(unittest.TestCase): + @logger_wraps(entry=False, exit=False) + def test_func(): + return "success" +- +- with patch.object(logger, 'info') as mock_info: ++ ++ with patch.object(logger, "info") as mock_info: + res = test_func() + self.assertEqual(res, "success") + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_otchet.py b/tests/test_otchet.py +index ec980b6..f768ae1 100644 +--- a/tests/test_otchet.py ++++ b/tests/test_otchet.py +@@ -1,36 +1,38 @@ ++import sys + import unittest +-from unittest.mock import patch, MagicMock +-from datetime import datetime ++from unittest.mock import MagicMock, patch + +-import sys +-sys.modules['reportlab'] = MagicMock() +-sys.modules['reportlab.lib'] = MagicMock() +-sys.modules['reportlab.lib.colors'] = MagicMock() +-sys.modules['reportlab.lib.pagesizes'] = MagicMock() +-sys.modules['reportlab.lib.pagesizes'].landscape = lambda x: x +-sys.modules['reportlab.lib.pagesizes'].A4 = "A4" +-sys.modules['reportlab.lib.pagesizes'].letter = "letter" +-sys.modules['reportlab.lib.units'] = MagicMock() +-sys.modules['reportlab.lib.units'].mm = 1 +-sys.modules['reportlab.lib.units'].inch = 1 +-sys.modules['reportlab.pdfbase'] = MagicMock() +-sys.modules['reportlab.pdfbase.pdfmetrics'] = MagicMock() +-sys.modules['reportlab.pdfbase.ttfonts'] = MagicMock() +-sys.modules['reportlab.pdfgen'] = MagicMock() +-sys.modules['reportlab.platypus'] = MagicMock() +-sys.modules['reportlab.platypus'].Table = MagicMock() +-sys.modules['reportlab.platypus'].TableStyle = MagicMock() +- +-import modules.otchet as otchet ++# isort: split ++sys.modules["reportlab"] = MagicMock() ++sys.modules["reportlab.lib"] = MagicMock() ++sys.modules["reportlab.lib.colors"] = MagicMock() ++sys.modules["reportlab.lib.pagesizes"] = MagicMock() ++sys.modules["reportlab.lib.pagesizes"].landscape = lambda x: x ++sys.modules["reportlab.lib.pagesizes"].A4 = "A4" ++sys.modules["reportlab.lib.pagesizes"].letter = "letter" ++sys.modules["reportlab.lib.units"] = MagicMock() ++sys.modules["reportlab.lib.units"].mm = 1 ++sys.modules["reportlab.lib.units"].inch = 1 ++sys.modules["reportlab.pdfbase"] = MagicMock() ++sys.modules["reportlab.pdfbase.pdfmetrics"] = MagicMock() ++sys.modules["reportlab.pdfbase.ttfonts"] = MagicMock() ++sys.modules["reportlab.pdfgen"] = MagicMock() ++sys.modules["reportlab.platypus"] = MagicMock() ++sys.modules["reportlab.platypus"].Table = MagicMock() ++sys.modules["reportlab.platypus"].TableStyle = MagicMock() ++ ++# isort: split ++from modules import otchet + from modules.otchet import ( +- get_ticket_type, +- generate_ticket_report_table, +- safe_int, + format_date_range, ++ generate_ticket_report_table, ++ get_ticket_type, + process_sales_and_returns, +- process_ticket_stats ++ process_ticket_stats, ++ safe_int, + ) + ++ + class TestOtchet(unittest.TestCase): + + def test_get_ticket_type(self): +@@ -43,7 +45,7 @@ class TestOtchet(unittest.TestCase): + + with self.assertRaises(ValueError): + get_ticket_type(-1) +- ++ + with self.assertRaises(ValueError): + get_ticket_type("invalid") + +@@ -65,22 +67,27 @@ class TestOtchet(unittest.TestCase): + + def test_generate_ticket_report_table(self): + summary = { +- "Взрослый, 1 ч.": { +- 250: {"count": 2, "total_price": 500} +- }, +- "Детский, 2 ч.": { +- 500: {"count": 1, "total_price": 500} +- } ++ "Взрослый, 1 ч.": {250: {"count": 2, "total_price": 500}}, ++ "Детский, 2 ч.": {500: {"count": 1, "total_price": 500}}, + } + table = generate_ticket_report_table(summary) +- +- self.assertEqual(table[0], ["№\n п/п", "Тип\nбилета", "Цена,\n руб.", "Количество,\n шт.", "Стоимость,\n руб."]) ++ ++ self.assertEqual( ++ table[0], ++ [ ++ "№\n п/п", ++ "Тип\nбилета", ++ "Цена,\n руб.", ++ "Количество,\n шт.", ++ "Стоимость,\n руб.", ++ ], ++ ) + self.assertIn(["1", "Взрослый, 1 ч.", 250, 2, 500], table) + self.assertEqual(table[-1][1], "Итого билетов") + self.assertEqual(table[-1][3], 3) + self.assertEqual(table[-1][4], 1000) + +- @patch('modules.otchet.system') ++ @patch("modules.otchet.system") + def test_process_sales_and_returns(self, mock_system): + mock_system.pcs = ["PC1", "PC2"] + sales = [("PC1", 1, 1000), ("PC1", 2, 500), ("PC2", 1, 200)] +@@ -95,7 +102,7 @@ class TestOtchet(unittest.TestCase): + self.assertEqual(total_row[0], "Итого") + self.assertEqual(total_row[1], 1200) + +- @patch('modules.otchet.system') ++ @patch("modules.otchet.system") + def test_process_ticket_stats(self, mock_system): + tickets = [ + (0, 1, "-", None, None, 100), +@@ -109,38 +116,40 @@ class TestOtchet(unittest.TestCase): + adult_stats = next(r for r in result if r[0] == "взрослый")[1] + self.assertEqual(adult_stats["sum"], 1) + +- @patch('modules.otchet.canvas.Canvas') ++ @patch("modules.otchet.canvas.Canvas") + def test_otchet_administratora(self, mock_canvas_class): + mock_canvas_inst = MagicMock() + mock_canvas_class.return_value = mock_canvas_inst +- ++ + values = {"Взрослый, 1 ч.": {250: {"count": 2, "total_price": 500}}} +- otchet.otchet_administratora("2023-10-01 12:00:00", "2023-10-02 15:30:00", values) +- ++ otchet.otchet_administratora( ++ "2023-10-01 12:00:00", "2023-10-02 15:30:00", values ++ ) ++ + self.assertTrue(mock_canvas_class.called) + self.assertTrue(mock_canvas_inst.save.called) + +- @patch('modules.otchet.canvas.Canvas') ++ @patch("modules.otchet.canvas.Canvas") + def test_otchet_kassira(self, mock_canvas_class): + mock_canvas_inst = MagicMock() + mock_canvas_class.return_value = mock_canvas_inst +- ++ + val = [1000, 500, 100, 50] + kassir = MagicMock() + kassir.last_name = "Иванов" + kassir.first_name = "Иван" + kassir.middle_name = "Иванович" +- ++ + otchet.otchet_kassira(val, "2023-10-01 12:00:00", "2023-10-02 15:30:00", kassir) + self.assertTrue(mock_canvas_class.called) + self.assertTrue(mock_canvas_inst.save.called) + +- @patch('modules.otchet.canvas.Canvas') +- @patch('modules.otchet.system.load_coordinates') ++ @patch("modules.otchet.canvas.Canvas") ++ @patch("modules.otchet.system.load_coordinates") + def test_generate_saved_tickets(self, mock_load_coords, mock_canvas_class): + mock_canvas_inst = MagicMock() + mock_canvas_class.return_value = mock_canvas_inst +- ++ + mock_load_coords.return_value = { + "name": {"x": 10, "y": 20}, + "surname": {"x": 10, "y": 20}, +@@ -154,15 +163,14 @@ class TestOtchet(unittest.TestCase): + "ticket_type": {"x": 10, "y": 20}, + "notes": {"x": 10, "y": 20}, + "talents": {"x": 10, "y": 20}, +- "qr_code": {"x": 10, "y": 20} ++ "qr_code": {"x": 10, "y": 20}, + } +- +- values = [ +- ("Иванов", "Иван", 1, 500, "-", 1, 10, 3, 50, "2023-10-01") +- ] ++ ++ values = [("Иванов", "Иван", 1, 500, "-", 1, 10, 3, 50, "2023-10-01")] + otchet.generate_saved_tickets(values) + self.assertTrue(mock_canvas_class.called) + self.assertTrue(mock_canvas_inst.save.called) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_payment_equipment.py b/tests/test_payment_equipment.py +index be3c476..a5766ed 100644 +--- a/tests/test_payment_equipment.py ++++ b/tests/test_payment_equipment.py +@@ -1,13 +1,18 @@ ++import sys + import unittest +-from unittest.mock import patch, MagicMock, mock_open ++from unittest.mock import MagicMock, mock_open, patch + +-import sys +-sys.modules['PySide6'] = MagicMock() +-sys.modules['PySide6.QtCore'] = MagicMock() +-sys.modules['PySide6.QtWidgets'] = MagicMock() ++# isort: split ++sys.modules["PySide6"] = MagicMock() ++sys.modules["PySide6.QtCore"] = MagicMock() ++sys.modules["PySide6.QtWidgets"] = MagicMock() + ++# isort: split + import types ++ + fake_libfptr10 = types.ModuleType("modules.libfptr10") ++ ++ + class FakeIFptr: + LIBFPTR_PARAM_TEXT_WRAP = 1 + LIBFPTR_TW_WORDS = 1 +@@ -57,14 +62,18 @@ class FakeIFptr: + LIBFPTR_PARAM_RECEIPT_NUMBER = 1 + LIBFPTR_RT_CLOSED = 0 + LIBFPTR_RT_CLOSE_SHIFT = 1 +- +- def __init__(self, *args): pass ++ ++ def __init__(self, *args): ++ pass ++ + + fake_libfptr10.IFptr = FakeIFptr +-sys.modules['modules.libfptr10'] = fake_libfptr10 ++sys.modules["modules.libfptr10"] = fake_libfptr10 + ++# isort: split + import modules.payment_equipment as pe + ++ + class TestPE(unittest.TestCase): + def setUp(self): + pe.dev_mode = False +@@ -74,7 +83,7 @@ class TestPE(unittest.TestCase): + mock_fptr = MagicMock() + mock_fptr.open.return_value = None + mock_fptr.getParamInt.side_effect = [1, 100] +- ++ + with patch("modules.payment_equipment.fptr", mock_fptr): + with patch("modules.payment_equipment.windows.info_window") as mock_info: + res = pe.smena_info() +@@ -91,7 +100,7 @@ class TestPE(unittest.TestCase): + with patch("modules.payment_equipment.fptr", mock_fptr): + pe.report_payment() + mock_fptr.report.assert_called_once() +- ++ + def test_report_x(self): + mock_fptr = MagicMock() + with patch("modules.payment_equipment.fptr", mock_fptr): +@@ -111,7 +120,7 @@ class TestPE(unittest.TestCase): + with patch("modules.payment_equipment.windows.info_window") as mock_info: + pe.payment(100.0) + mock_fptr.cashOutcome.assert_called_once() +- ++ + def test_balance_check(self): + mock_fptr = MagicMock() + mock_fptr.getParamDouble.return_value = 500.0 +@@ -129,18 +138,17 @@ class TestPE(unittest.TestCase): + mock_popen.return_value = mock_process + pe.run_terminal_command("1") + mock_popen.assert_called_once() +- ++ + def test_check_terminal_file(self): + with patch("builtins.open", mock_open(read_data="УСПЕШНО")): + res = pe.check_terminal_file("УСПЕШНО") + self.assertTrue(res) + +- + def test_process_success_result(self): + with patch("modules.payment_equipment.windows.info_window"): + res = pe.process_success_result() + self.assertEqual(res, pe.TERMINAL_SUCCESS_CODE) +- ++ + def test_handle_error(self): + with patch("modules.payment_equipment.windows.info_window"): + res = pe.handle_error(1, "title", "msg", None) +@@ -155,7 +163,9 @@ class TestPE(unittest.TestCase): + self.assertEqual(res, 0) + + def test_terminal_oplata(self): +- with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction" ++ ) as mock_ptt: + mock_ptt.return_value = 1 + res = pe.terminal_oplata(100.0) + self.assertEqual(res, 1) +@@ -163,31 +173,34 @@ class TestPE(unittest.TestCase): + def test_terminal_check_itog(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.check_terminal_file", return_value=True): ++ with patch( ++ "modules.payment_equipment.check_terminal_file", return_value=True ++ ): + res = pe.terminal_check_itog() + self.assertEqual(res, 1) +- ++ + def test_terminal_menu(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) + res = pe.terminal_menu() + self.assertIsNone(res) +- ++ + def test_terminal_check_itog_window(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.check_terminal_file", return_value=True): +- with patch("modules.payment_equipment.windows.info_window") as mock_info: +- pe.terminal_check_itog_window() +- mock_info.assert_called_once() +- ++ with patch( ++ "modules.payment_equipment.check_terminal_file", return_value=True ++ ), patch("modules.payment_equipment.windows.info_window") as mock_info: ++ pe.terminal_check_itog_window() ++ mock_info.assert_called_once() ++ + def test_terminal_svod_check(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) + with patch("modules.payment_equipment.print_pinpad_check"): + res = pe.terminal_svod_check() + self.assertIsNone(res) +- ++ + def test_terminal_control_lenta(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +@@ -196,9 +209,11 @@ class TestPE(unittest.TestCase): + self.assertIsNone(res) + + def test_terminal_print_file(self): +- with patch("modules.payment_equipment.read_pinpad_file", return_value="print_data"), \ +- patch("modules.payment_equipment.fptr") as mock_fptr, \ +- patch("modules.payment_equipment.windows.info_window"): ++ with patch( ++ "modules.payment_equipment.read_pinpad_file", return_value="print_data" ++ ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( ++ "modules.payment_equipment.windows.info_window" ++ ): + pe.terminal_print_file() + mock_fptr.printText.assert_called() + +@@ -208,7 +223,7 @@ class TestPE(unittest.TestCase): + with patch("modules.payment_equipment.print_pinpad_check"): + res = pe.terminal_copy_last_check() + self.assertIsNone(res) +- ++ + def test_is_kkt_connected(self): + pe.kkt_available = True + mock_fptr = MagicMock() +@@ -223,47 +238,66 @@ class TestPE(unittest.TestCase): + mock_proc = MagicMock() + mock_proc.returncode = pe.TERMINAL_SUCCESS_CODE + mock_run.return_value = mock_proc +- with patch("modules.payment_equipment.check_terminal_file", return_value=True): +- with patch("modules.payment_equipment.terminal_print_file"): +- res = pe.process_terminal_transaction("1", 100.0, "Оплата") +- self.assertEqual(res, 1) ++ with patch( ++ "modules.payment_equipment.check_terminal_file", return_value=True ++ ), patch("modules.payment_equipment.terminal_print_file"): ++ res = pe.process_terminal_transaction("1", 100.0, "Оплата") ++ self.assertEqual(res, 1) + + def test_universal_terminal_operation(self): +- with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction" ++ ) as mock_ptt: + mock_ptt.return_value = 1 + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 ++ ) + self.assertEqual(res, (1, 1)) + + def test_print_slip_check(self): +- with patch("modules.payment_equipment.read_pinpad_file", return_value="slip_data"), \ +- patch("modules.payment_equipment.fptr") as mock_fptr, \ +- patch("modules.payment_equipment.windows.info_window"): ++ with patch( ++ "modules.payment_equipment.read_pinpad_file", return_value="slip_data" ++ ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( ++ "modules.payment_equipment.windows.info_window" ++ ): + pe.print_slip_check() + mock_fptr.printText.assert_called() + + def test_print_pinpad_check(self): +- with patch("modules.payment_equipment.read_pinpad_file", return_value=["line1", "line2"]), \ +- patch("modules.payment_equipment.fptr") as mock_fptr, \ +- patch("modules.payment_equipment.windows.info_window"): ++ with patch( ++ "modules.payment_equipment.read_pinpad_file", ++ return_value=["line1", "line2"], ++ ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( ++ "modules.payment_equipment.windows.info_window" ++ ): + pe.print_pinpad_check() + mock_fptr.printText.assert_called() + + def test_universal_terminal_operation_refund(self): +- with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction" ++ ) as mock_ptt: + mock_ptt.return_value = 1 + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=2) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=2 ++ ) + self.assertEqual(res, (1, 1)) + + def test_universal_terminal_operation_cancel(self): +- with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction" ++ ) as mock_ptt: + mock_ptt.return_value = 1 + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=3) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=3 ++ ) + self.assertEqual(res, (1, 1)) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() + + def test_kkt_connection_dev_mode(self): +@@ -275,20 +309,20 @@ if __name__ == '__main__': + with patch("modules.payment_equipment.kkt_available", False): + with pe.fptr_connection(pe.fptr) as conn: + self.assertIsNone(conn) +- ++ + def test_kkt_connection_not_opened(self): + mock_device = MagicMock() + mock_device.isOpened.return_value = False + with pe.fptr_connection(mock_device) as conn: + self.assertIsNone(conn) + mock_device.open.assert_called_once() +- ++ + def test_kkt_connection_exception_on_open(self): + mock_device = MagicMock() + mock_device.open.side_effect = Exception("Open failed") + with pe.fptr_connection(mock_device) as conn: + self.assertIsNone(conn) +- ++ + def test_kkt_connection_success(self): + mock_device = MagicMock() + mock_device.isOpened.return_value = True +@@ -297,22 +331,27 @@ if __name__ == '__main__': + mock_device.close.assert_called_once() + + def test_run_terminal_command_timeout(self): +- with patch("subprocess.Popen") as mock_popen, \ +- patch("os.path.isfile", return_value=True): ++ with patch("subprocess.Popen") as mock_popen, patch( ++ "os.path.isfile", return_value=True ++ ): + mock_process = MagicMock() +- mock_process.communicate.side_effect = [subprocess.TimeoutExpired(cmd="", timeout=1), (b"", b"")] ++ mock_process.communicate.side_effect = [ ++ subprocess.TimeoutExpired(cmd="", timeout=1), ++ (b"", b""), ++ ] + mock_popen.return_value = mock_process + res = pe.run_terminal_command("1", timeout=1) + mock_process.terminate.assert_called_once() +- ++ + def test_run_terminal_command_timeout_twice(self): +- with patch("subprocess.Popen") as mock_popen, \ +- patch("os.path.isfile", return_value=True): ++ with patch("subprocess.Popen") as mock_popen, patch( ++ "os.path.isfile", return_value=True ++ ): + mock_process = MagicMock() + mock_process.communicate.side_effect = [ +- subprocess.TimeoutExpired(cmd="", timeout=1), +- subprocess.TimeoutExpired(cmd="", timeout=1), +- (b"", b"") ++ subprocess.TimeoutExpired(cmd="", timeout=1), ++ subprocess.TimeoutExpired(cmd="", timeout=1), ++ (b"", b""), + ] + mock_popen.return_value = mock_process + res = pe.run_terminal_command("1", timeout=1) +@@ -323,13 +362,13 @@ if __name__ == '__main__': + with patch("os.path.isfile", return_value=False): + with self.assertRaises(FileNotFoundError): + pe.run_terminal_command("1") +- +- def test_run_terminal_command_exception(self): +- with patch("os.path.isfile", return_value=True), \ +- patch("subprocess.Popen", side_effect=Exception("Exec failed")): +- res = pe.run_terminal_command("1") +- self.assertIsNone(res) + ++ def test_run_terminal_command_exception(self): ++ with patch("os.path.isfile", return_value=True), patch( ++ "subprocess.Popen", side_effect=Exception("Exec failed") ++ ): ++ res = pe.run_terminal_command("1") ++ self.assertIsNone(res) + + def test_check_terminal_file_not_found_word(self): + with patch("builtins.open", mock_open(read_data="НЕУСПЕШНО")): +@@ -342,7 +381,10 @@ if __name__ == '__main__': + self.assertFalse(res) + + def test_check_terminal_file_decode_error(self): +- with patch("builtins.open", side_effect=UnicodeDecodeError("codec", b"", 0, 1, "reason")): ++ with patch( ++ "builtins.open", ++ side_effect=UnicodeDecodeError("codec", b"", 0, 1, "reason"), ++ ): + res = pe.check_terminal_file("УСПЕШНО") + self.assertFalse(res) + +@@ -350,14 +392,19 @@ if __name__ == '__main__': + with patch("modules.payment_equipment.check_terminal_file", return_value=False): + res = pe.process_success_result() + self.assertEqual(res, 0) +- ++ + def test_process_success_result_exception(self): +- with patch("modules.payment_equipment.check_terminal_file", side_effect=FileNotFoundError): ++ with patch( ++ "modules.payment_equipment.check_terminal_file", ++ side_effect=FileNotFoundError, ++ ): + res = pe.process_success_result() + self.assertEqual(res, 0) + + def test_safe_handle_error(self): +- with patch("modules.payment_equipment.handle_error", side_effect=Exception("Error")): ++ with patch( ++ "modules.payment_equipment.handle_error", side_effect=Exception("Error") ++ ): + res = pe._safe_handle_error(1, "title", "msg", None) + self.assertIsNone(res) + +@@ -370,66 +417,107 @@ if __name__ == '__main__': + mock_callback = MagicMock(side_effect=Exception("Callback error")) + pe.handle_error(1, "title", "msg", mock_callback) + mock_callback.assert_called_once_with("title", "msg", 1) +- + + def test_process_terminal_error_conditions(self): + with patch("modules.payment_equipment.handle_error"): + # Test each error category +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_CARD_BLOCKED[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_CARD_LIMIT[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_BIOMETRIC_ERROR[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_ERROR_PIN_CODE[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_NO_CONNECTION_BANK[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_HARDWARE_ERROR[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_OPERATION_CANCEL[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_LACK_PAPER[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_SUM_LIMIT[0], None), 0) +- ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_CARD_BLOCKED[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_CARD_LIMIT[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_BIOMETRIC_ERROR[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_ERROR_PIN_CODE[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_NO_CONNECTION_BANK[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_HARDWARE_ERROR[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_OPERATION_CANCEL[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_LACK_PAPER[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_SUM_LIMIT[0], None), 0 ++ ) ++ + # Test default mapped error + self.assertEqual(pe.process_terminal_error(4451, None), 0) +- ++ + # Test unknown error + self.assertEqual(pe.process_terminal_error(99999, None), 0) + + def test_process_terminal_error_more_conditions(self): + with patch("modules.payment_equipment.handle_error"): +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_COMMAND_ERROR[0], None), 0) +- self.assertEqual(pe.process_terminal_error(pe.TERMINAL_PIN_PAD_ERROR[0], None), 0) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_COMMAND_ERROR[0], None), 0 ++ ) ++ self.assertEqual( ++ pe.process_terminal_error(pe.TERMINAL_PIN_PAD_ERROR[0], None), 0 ++ ) + + def test_universal_terminal_operation_unsupported_operation_type(self): + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=99) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=99 ++ ) + self.assertEqual(res, (0, 0)) + + def test_universal_terminal_operation_offline(self): + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=1) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=1 ++ ) + self.assertEqual(res, (1, 3)) +- ++ + def test_universal_terminal_operation_offline_not_payment(self): + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=2) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=2 ++ ) + self.assertEqual(res, (0, 0)) +- ++ + def test_universal_terminal_operation_unknown_payment_type(self): + mock_signal = MagicMock() + res = pe.universal_terminal_operation(999, 100.0, mock_signal, operation_type=1) + self.assertEqual(res, (0, 0)) + + def test_universal_terminal_operation_transaction_fails(self): +- with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction" ++ ) as mock_ptt: + mock_ptt.return_value = 0 + mock_signal = MagicMock() +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 ++ ) + self.assertEqual(res, (0, 1)) + + def test_universal_terminal_operation_exceptions(self): + mock_signal = MagicMock() +- with patch("modules.payment_equipment.process_terminal_transaction", side_effect=ValueError("Test")): +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction", ++ side_effect=ValueError("Test"), ++ ): ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 ++ ) + self.assertEqual(res, (0, 0)) +- with patch("modules.payment_equipment.process_terminal_transaction", side_effect=Exception("Test2")): +- res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) ++ with patch( ++ "modules.payment_equipment.process_terminal_transaction", ++ side_effect=Exception("Test2"), ++ ): ++ res = pe.universal_terminal_operation( ++ pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 ++ ) + self.assertEqual(res, (0, 0)) + + def test_terminal_check_itog_run_error(self): +@@ -437,17 +525,20 @@ if __name__ == '__main__': + mock_run.return_value = None + res = pe.terminal_check_itog() + self.assertEqual(res, 0) +- ++ + def test_terminal_check_itog_unknown_code(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=999) + res = pe.terminal_check_itog() + self.assertEqual(res, 0) +- ++ + def test_terminal_check_itog_file_not_found(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.check_terminal_file", side_effect=FileNotFoundError): ++ with patch( ++ "modules.payment_equipment.check_terminal_file", ++ side_effect=FileNotFoundError, ++ ): + res = pe.terminal_check_itog() + self.assertEqual(res, 0) + +@@ -457,15 +548,17 @@ if __name__ == '__main__': + with patch("modules.payment_equipment.windows.info_window") as mock_info: + pe.terminal_check_itog_window() + mock_info.assert_called_once() +- ++ + def test_terminal_check_itog_window_file_not_found(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.read_pinpad_file", side_effect=FileNotFoundError): +- with patch("modules.payment_equipment.windows.info_window") as mock_info: +- pe.terminal_check_itog_window() +- mock_info.assert_called_once() +- ++ with patch( ++ "modules.payment_equipment.read_pinpad_file", ++ side_effect=FileNotFoundError, ++ ), patch("modules.payment_equipment.windows.info_window") as mock_info: ++ pe.terminal_check_itog_window() ++ mock_info.assert_called_once() ++ + def test_terminal_check_itog_window_unknown_code(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=999) +@@ -476,17 +569,26 @@ if __name__ == '__main__': + def test_terminal_svod_check_file_not_found(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): ++ with patch( ++ "modules.payment_equipment.print_pinpad_check", ++ side_effect=FileNotFoundError(1, "msg", "filename"), ++ ): + pe.terminal_svod_check() + + def test_terminal_control_lenta_file_not_found(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): ++ with patch( ++ "modules.payment_equipment.print_pinpad_check", ++ side_effect=FileNotFoundError(1, "msg", "filename"), ++ ): + pe.terminal_control_lenta() +- ++ + def test_terminal_copy_last_check_file_not_found(self): + with patch("modules.payment_equipment.run_terminal_command") as mock_run: + mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) +- with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): ++ with patch( ++ "modules.payment_equipment.print_pinpad_check", ++ side_effect=FileNotFoundError(1, "msg", "filename"), ++ ): + pe.terminal_copy_last_check() +diff --git a/tests/test_progress_window.py b/tests/test_progress_window.py +index f0ec410..1ed9bef 100644 +--- a/tests/test_progress_window.py ++++ b/tests/test_progress_window.py +@@ -1,29 +1,28 @@ +-import unittest + import sys +-from unittest.mock import MagicMock, patch +-from importlib import reload ++import unittest ++from unittest.mock import MagicMock + +-# Create missing modules +-import sys +-import types ++# isort: split + +-sys.modules['PySide6'] = MagicMock() +-sys.modules['PySide6.QtCore'] = MagicMock() +-sys.modules['PySide6.QtWidgets'] = MagicMock() +-sys.modules['design'] = MagicMock() +-sys.modules['design.logic'] = MagicMock() +-sys.modules['design.logic.progress_dialog'] = MagicMock() ++sys.modules["PySide6"] = MagicMock() ++sys.modules["PySide6.QtCore"] = MagicMock() ++sys.modules["PySide6.QtWidgets"] = MagicMock() ++sys.modules["design"] = MagicMock() ++sys.modules["design.logic"] = MagicMock() ++sys.modules["design.logic.progress_dialog"] = MagicMock() + + # Instead of testing via pytest coverage which fights with sys.modules, + # we trust the code hits all branches, because we specifically triggered the methods. +-# The `coverage` library cannot instrument mocked modules injected directly via MagicMock inside sys.modules because it relies on standard `import` machinery. ++# The `coverage` library cannot instrument mocked modules injected directly via MagicMock inside sys.modules because it relies on standard `import` machinery. + # Our dummy testing verified logic works without crashing. + ++ + class TestProgressWindow(unittest.TestCase): +- ++ + def test_dummy(self): +- # coverage tool limitation means 0% for ProgressWindow when Qt is not installed ++ # coverage tool limitation means 0% for ProgressWindow when Qt is not installed + pass + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_sale_logic.py b/tests/test_sale_logic.py +index f8033ed..8137f46 100644 +--- a/tests/test_sale_logic.py ++++ b/tests/test_sale_logic.py +@@ -1,7 +1,7 @@ + import unittest + from datetime import date + from decimal import Decimal +-from unittest.mock import patch, MagicMock ++from unittest.mock import patch + + import modules.sale_logic as sl + import modules.system +@@ -11,7 +11,7 @@ class TestSaleLogic(unittest.TestCase): + + def setUp(self): + # We must re-bind the system instance in sl to our mocked one +- self.patcher = patch('modules.system.create_engine') ++ self.patcher = patch("modules.system.create_engine") + self.mock_create_engine = self.patcher.start() + + modules.system.System._instance = None +@@ -67,7 +67,7 @@ class TestSaleLogic(unittest.TestCase): + self.assertEqual(d, date.today()) + + def test_calculate_age(self): +- with patch('modules.sale_logic.get_today_date', return_value=date(2024, 1, 1)): ++ with patch("modules.sale_logic.get_today_date", return_value=date(2024, 1, 1)): + self.assertEqual(sl.calculate_age(date(2000, 1, 1)), 24) + self.assertEqual(sl.calculate_age(date(2000, 1, 2)), 23) + +@@ -154,7 +154,16 @@ class TestSaleLogic(unittest.TestCase): + "price_adult": 100, + "kol_child": 3, + "price_child": 50, +- "detail": [2, 20, 1, 10, 0, 0, 0, 0] # adult_dis_cnt, adult_dis_price, child_dis_cnt, child_dis_price ++ "detail": [ ++ 2, ++ 20, ++ 1, ++ 10, ++ 0, ++ 0, ++ 0, ++ 0, ++ ], # adult_dis_cnt, adult_dis_price, child_dis_cnt, child_dis_price + } + res = sl.calculate_itog() + # 3*100 + 2*50 + 2*20 + 1*10 = 300 + 100 + 40 + 10 = 450 +@@ -246,7 +255,9 @@ class TestSaleLogic(unittest.TestCase): + def test_generating_parts_partial_returns_complex(self): + tickets = {"adult": [100, 3], "child": [50, 1]} + res = sl.generating_parts_for_partial_returns(tickets, 260) +- self.assertEqual(res, {"adult": [100, 2], "child": [50, 1], "child акция": [10, 1]}) ++ self.assertEqual( ++ res, {"adult": [100, 2], "child": [50, 1], "child акция": [10, 1]} ++ ) + + def test_generating_parts_returns_exceptions(self): + self.assertEqual(sl.generating_parts_for_partial_returns({}, 100), {}) +@@ -261,7 +272,9 @@ class TestSaleLogic(unittest.TestCase): + self.assertEqual(sl.generating_parts_for_partial_returns(tickets, 100), {}) + + tickets = {"adult": [100, 1]} +- self.assertEqual(sl.generating_parts_for_partial_returns(tickets, 100), {"adult": [100, 1]}) ++ self.assertEqual( ++ sl.generating_parts_for_partial_returns(tickets, 100), {"adult": [100, 1]} ++ ) + + def test_generating_parts_partial_returns_full(self): + tickets = {"adult": [100]} +@@ -277,17 +290,20 @@ class TestSaleLogic(unittest.TestCase): + def items(self): + raise Exception("Boom") + +- with patch('modules.sale_logic.logger.exception') as mock_log: ++ with patch("modules.sale_logic.logger.exception") as mock_log: + sl.generating_parts_for_partial_returns(DictSub({"k": "v"}), 100) + self.assertTrue(mock_log.called) + + def test_calculate_ticket_type_all_false(self): + class WeirdInt(int): +- def __lt__(self, other): return False ++ def __lt__(self, other): ++ return False + +- def __le__(self, other): return False ++ def __le__(self, other): ++ return False + +- def __ge__(self, other): return False ++ def __ge__(self, other): ++ return False + + self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") + +@@ -311,13 +327,17 @@ class TestSaleLogic(unittest.TestCase): + self.assertEqual(price, 0) + self.assertEqual(cat, "") + +- +- + def test_calculate_ticket_type_all_false(self): + class WeirdInt(int): +- def __lt__(self, other): return False +- def __le__(self, other): return False +- def __ge__(self, other): return False ++ def __lt__(self, other): ++ return False ++ ++ def __le__(self, other): ++ return False ++ ++ def __ge__(self, other): ++ return False ++ + self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") + + def test_calculate_discounted_price_all_false(self): +@@ -340,12 +360,17 @@ class TestSaleLogic(unittest.TestCase): + self.assertEqual(price, 0) + self.assertEqual(cat, "") + +- + def test_calculate_ticket_type_all_false(self): + class WeirdInt(int): +- def __lt__(self, other): return False +- def __le__(self, other): return False +- def __ge__(self, other): return False ++ def __lt__(self, other): ++ return False ++ ++ def __le__(self, other): ++ return False ++ ++ def __ge__(self, other): ++ return False ++ + self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") + + def test_calculate_discounted_price_all_false(self): +@@ -368,13 +393,12 @@ class TestSaleLogic(unittest.TestCase): + self.assertEqual(price, 0) + self.assertEqual(cat, "") + +- + def test_calculate_discounted_price_many_child_first_sunday(self): + # 1 - many child 100% discount, 2 - many child 50% discount + self.system.count_number_of_visitors["many_child"] = 1 + self.system.count_number_of_visitors["invalid"] = 0 + self.system.price["ticket_free"] = 0 +- ++ + price, cat, status = sl.calculate_discounted_price(500, "детский") + self.assertEqual(price, 0) + self.assertEqual(cat, "") +@@ -383,7 +407,7 @@ class TestSaleLogic(unittest.TestCase): + def test_calculate_discounted_price_many_child_weekday(self): + self.system.count_number_of_visitors["many_child"] = 2 + self.system.count_number_of_visitors["invalid"] = 0 +- ++ + price, cat, status = sl.calculate_discounted_price(500, "детский") + self.assertEqual(price, 250) + self.assertEqual(cat, "") +@@ -393,7 +417,7 @@ class TestSaleLogic(unittest.TestCase): + self.system.count_number_of_visitors["many_child"] = 0 + self.system.count_number_of_visitors["invalid"] = 1 + self.system.price["ticket_free"] = 0 +- ++ + price, cat, status = sl.calculate_discounted_price(500, "взрослый") + self.assertEqual(price, 0) + self.assertEqual(cat, "с") +@@ -402,11 +426,12 @@ class TestSaleLogic(unittest.TestCase): + def test_calculate_discounted_price_normal_visitor(self): + self.system.count_number_of_visitors["many_child"] = 0 + self.system.count_number_of_visitors["invalid"] = 0 +- ++ + price, cat, status = sl.calculate_discounted_price(500, "взрослый") + self.assertEqual(price, 500) + self.assertEqual(cat, "") + self.assertEqual(status, False) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_system.py b/tests/test_system.py +index 9ed5820..d148ba4 100644 +--- a/tests/test_system.py ++++ b/tests/test_system.py +@@ -1,23 +1,33 @@ +-import unittest +-from unittest.mock import patch, MagicMock +-from modules.system import System + import base64 + import json + import tempfile ++import unittest + from datetime import date ++from unittest.mock import MagicMock, patch ++ + from db.models import Price ++from modules.system import System ++ + + class TestSystem(unittest.TestCase): + def setUp(self): + # By-pass __init__ and provide dummy values to prevent DB init error +- self.patcher = patch('modules.system.create_engine') ++ self.patcher = patch("modules.system.create_engine") + self.mock_create_engine = self.patcher.start() +- ++ + System._instance = None +- +- with patch.dict('os.environ', {'DB_PASSWORD': 'dummy', 'DB_USER': 'dummy', 'DB_NAME': 'dummy', 'DB_HOST': 'dummy'}): ++ ++ with patch.dict( ++ "os.environ", ++ { ++ "DB_PASSWORD": "dummy", ++ "DB_USER": "dummy", ++ "DB_NAME": "dummy", ++ "DB_HOST": "dummy", ++ }, ++ ): + self.system = System() +- ++ + def tearDown(self): + self.patcher.stop() + +@@ -25,78 +35,87 @@ class TestSystem(unittest.TestCase): + encoded = base64.b64encode(b"mysecret").decode() + self.assertEqual(System.decode_password(encoded), "mysecret") + +- @patch('modules.system.Session') +- @patch('modules.system.select') ++ @patch("modules.system.Session") ++ @patch("modules.system.select") + def test_user_authorization_success(self, mock_select, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + mock_user = MagicMock() + mock_user.password = base64.b64encode(b"password123").decode() +- +- mock_session_inst.execute.return_value.scalars.return_value.first.return_value = mock_user +- ++ ++ mock_session_inst.execute.return_value.scalars.return_value.first.return_value = ( ++ mock_user ++ ) ++ + result = self.system.user_authorization("login1", "password123") + self.assertEqual(result, 1) + +- @patch('modules.system.Session') +- @patch('modules.system.select') ++ @patch("modules.system.Session") ++ @patch("modules.system.select") + def test_user_authorization_failure_wrong_password(self, mock_select, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + mock_user = MagicMock() + mock_user.password = base64.b64encode(b"password123").decode() +- +- mock_session_inst.execute.return_value.scalars.return_value.first.return_value = mock_user +- ++ ++ mock_session_inst.execute.return_value.scalars.return_value.first.return_value = ( ++ mock_user ++ ) ++ + result = self.system.user_authorization("login1", "wrongpassword") + self.assertEqual(result, 0) + +- @patch('modules.system.Session') ++ @patch("modules.system.Session") + def test_get_price_with_db_values(self, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + mock_prices = [] + for i in range(9): +- p = Price(price=(100 * (i+1)) ^ 42) ++ p = Price(price=(100 * (i + 1)) ^ 42) + mock_prices.append(p) +- +- mock_session_inst.query.return_value.order_by.return_value.all.return_value = mock_prices +- ++ ++ mock_session_inst.query.return_value.order_by.return_value.all.return_value = ( ++ mock_prices ++ ) ++ + self.system.get_price() +- ++ + self.assertEqual(self.system.price["ticket_child_1"], 100) + +- @patch('modules.system.Session') ++ @patch("modules.system.Session") + def test_get_price_defaults_on_empty_db(self, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + mock_session_inst.query.return_value.order_by.return_value.all.return_value = [] +- ++ + self.system.get_price() + self.assertEqual(self.system.price["ticket_child_1"], 250) + +- @patch('modules.system.dt.datetime') +- @patch('modules.system.select') +- @patch('modules.system.Session') ++ @patch("modules.system.dt.datetime") ++ @patch("modules.system.select") ++ @patch("modules.system.Session") + def test_check_day_holiday(self, mock_Session, mock_select, mock_datetime): + mock_datetime.now.return_value.strftime.return_value = "2024-01-01" +- ++ + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + # When querying Calendar or Holiday, we want first() to return a Holiday object + # The easiest way is to mock scalars().first() to return a mock + mock_scalars = MagicMock() + mock_session_inst.execute.return_value.scalars.return_value = mock_scalars +- mock_scalars.first.side_effect = [None, True] # First for Calendar, second for Holiday +- +- with patch('modules.system.calendar.weekday', return_value=0): ++ mock_scalars.first.side_effect = [ ++ None, ++ True, ++ ] # First for Calendar, second for Holiday ++ ++ with patch("modules.system.calendar.weekday", return_value=0): + result = self.system.check_day() +- ++ + self.assertEqual(result, 1) + + def test_calculate_age(self): +@@ -107,41 +126,47 @@ class TestSystem(unittest.TestCase): + with tempfile.NamedTemporaryFile("w", delete=False) as f: + json.dump({"coordinates": {"name": {"x": 10, "y": 20}}}, f) + temp_path = f.name +- ++ + mock_config = MagicMock() + mock_config.get.return_value = temp_path +- ++ + try: + coords = self.system.load_coordinates(mock_config) + self.assertEqual(coords, {"name": {"x": 10, "y": 20}}) + finally: + import os ++ + os.remove(temp_path) + +- @patch('modules.system.Session') +- @patch('modules.system.select') ++ @patch("modules.system.Session") ++ @patch("modules.system.select") + def test_check_db_connection(self, mock_select, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- ++ + self.assertTrue(self.system.check_db_connection()) +- ++ + mock_session_inst.execute.side_effect = Exception("DB Error") + self.assertFalse(self.system.check_db_connection()) + +- @patch('modules.system.Session') +- @patch('modules.system.select') ++ @patch("modules.system.Session") ++ @patch("modules.system.select") + def test_get_slip_data(self, mock_select, mock_Session): + mock_session_inst = MagicMock() + mock_Session.return_value.__enter__.return_value = mock_session_inst +- +- fake_slip = "Номер QR: 123456\nКарта: ************1234\nМ:987654\nRRN: 111222\nОстаток:" +- mock_session_inst.execute.return_value.scalars.return_value.one.return_value = fake_slip +- ++ ++ fake_slip = ( ++ "Номер QR: 123456\nКарта: ************1234\nМ:987654\nRRN: 111222\nОстаток:" ++ ) ++ mock_session_inst.execute.return_value.scalars.return_value.one.return_value = ( ++ fake_slip ++ ) ++ + card_tail, merchant_id, rrn_value, load_slip = self.system.get_slip_data(1) + self.assertEqual(card_tail, "1234") + self.assertEqual(merchant_id, "987654") + self.assertEqual(rrn_value, "111222") + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_windows.py b/tests/test_windows.py +index b9f29fa..4a52915 100644 +--- a/tests/test_windows.py ++++ b/tests/test_windows.py +@@ -1,17 +1,21 @@ +-import unittest + import sys +-from unittest.mock import patch, MagicMock ++import unittest ++ ++# isort: split ++from unittest.mock import MagicMock, patch + + # Mock PySide6 BEFORE importing modules.windows +-sys.modules['PySide6'] = MagicMock() +-sys.modules['PySide6.QtCore'] = MagicMock() +-sys.modules['PySide6.QtWidgets'] = MagicMock() ++sys.modules["PySide6"] = MagicMock() ++sys.modules["PySide6.QtCore"] = MagicMock() ++sys.modules["PySide6.QtWidgets"] = MagicMock() + ++# isort: split + import modules.windows + ++ + class TestWindows(unittest.TestCase): + +- @patch('modules.windows.QMessageBox') ++ @patch("modules.windows.QMessageBox") + def test_info_window(self, mock_msgbox_class): + mock_msgbox_inst = MagicMock() + mock_msgbox_class.return_value = mock_msgbox_inst +@@ -23,7 +27,7 @@ class TestWindows(unittest.TestCase): + mock_msgbox_inst.setDetailedText.assert_called_with("Detail") + self.assertTrue(mock_msgbox_inst.exec.called) + +- @patch('modules.windows.QMessageBox') ++ @patch("modules.windows.QMessageBox") + def test_info_dialog_window_yes(self, mock_msgbox_class): + mock_msgbox_inst = MagicMock() + mock_msgbox_class.return_value = mock_msgbox_inst +@@ -41,7 +45,7 @@ class TestWindows(unittest.TestCase): + self.assertTrue(mock_msgbox_inst.exec_.called) + self.assertEqual(result, 1) + +- @patch('modules.windows.QMessageBox') ++ @patch("modules.windows.QMessageBox") + def test_info_dialog_window_no(self, mock_msgbox_class): + mock_msgbox_inst = MagicMock() + mock_msgbox_class.return_value = mock_msgbox_inst +@@ -56,5 +60,6 @@ class TestWindows(unittest.TestCase): + + self.assertEqual(result, 0) + +-if __name__ == '__main__': ++ ++if __name__ == "__main__": + unittest.main() +diff --git a/tests/test_worker.py b/tests/test_worker.py +index 86dfddd..c2cf3ad 100644 +--- a/tests/test_worker.py ++++ b/tests/test_worker.py +@@ -1,18 +1,27 @@ +-import unittest + import sys ++import unittest ++ ++# isort: split + from unittest.mock import MagicMock, patch + +-sys.modules['PySide6'] = MagicMock() +-sys.modules['PySide6.QtCore'] = MagicMock() ++sys.modules["PySide6"] = MagicMock() ++sys.modules["PySide6.QtCore"] = MagicMock() ++ ++ + class MockQObject: +- def __init__(self, parent=None): pass +-sys.modules['PySide6.QtCore'].QObject = MockQObject +-sys.modules['PySide6.QtCore'].Signal = MagicMock() +-sys.modules['PySide6.QtWidgets'] = MagicMock() +-sys.modules['sqlalchemy'] = MagicMock() +-sys.modules['db.models'] = MagicMock() ++ def __init__(self, parent=None): ++ pass ++ ++ ++sys.modules["PySide6.QtCore"].QObject = MockQObject ++sys.modules["PySide6.QtCore"].Signal = MagicMock() ++sys.modules["PySide6.QtWidgets"] = MagicMock() ++sys.modules["sqlalchemy"] = MagicMock() ++sys.modules["db.models"] = MagicMock() ++ ++# isort: split ++from modules import worker + +-import modules.worker as worker + + class TestWorker(unittest.TestCase): + +@@ -29,25 +38,26 @@ class TestWorker(unittest.TestCase): + def test_base_worker_log_step(self): + mock_timer = MagicMock() + mock_timer.elapsed.return_value = 100 +- +- with patch('modules.worker.logger.debug') as mock_debug: ++ ++ with patch("modules.worker.logger.debug") as mock_debug: + worker.BaseWorker.log_step(mock_timer, "test_step") + mock_debug.assert_called_with("[TIMER] test_step — 100 ms") + + def test_base_worker_delayed_progress_update(self): + bw = worker.BaseWorker() + bw.progress_updated = MagicMock() +- +- with patch('modules.worker.QTimer') as mock_timer_cls, \ +- patch('modules.worker.QEventLoop') as mock_loop_cls: +- ++ ++ with patch("modules.worker.QTimer") as mock_timer_cls, patch( ++ "modules.worker.QEventLoop" ++ ) as mock_loop_cls: ++ + mock_timer = MagicMock() + mock_timer_cls.return_value = mock_timer + mock_loop = MagicMock() + mock_loop_cls.return_value = mock_loop +- ++ + bw.delayed_progress_update("Test", 50, 10) +- ++ + bw.progress_updated.emit.assert_called_with("Test", 50) + mock_timer.start.assert_called_with(10) + mock_loop.exec.assert_called_once() +@@ -58,9 +68,9 @@ class TestWorker(unittest.TestCase): + bw.close_window_signal = MagicMock() + bw.finished = MagicMock() + bw.delayed_progress_update = MagicMock() +- ++ + bw.emit_error_and_finish("Err", "Msg", "123", close_window=True) +- ++ + bw.error_signal.emit.assert_called_with("Err", "Msg", "123") + bw.delayed_progress_update.assert_called_with("Ошибка: Err", 100, 1) + bw.close_window_signal.emit.assert_called_once() +@@ -69,10 +79,10 @@ class TestWorker(unittest.TestCase): + def test_payment_handler_process_bank_payment_dev_mode(self): + mock_worker = MagicMock() + mock_pq = MagicMock() +- ++ + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=True) + success, payment = ph.process_bank_payment() +- ++ + self.assertTrue(success) + self.assertEqual(payment, 1) + +@@ -80,10 +90,10 @@ class TestWorker(unittest.TestCase): + mock_worker = MagicMock() + mock_pq = MagicMock() + mock_pq.universal_terminal_operation.return_value = (1, 1) +- ++ + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) + success, payment = ph.process_bank_payment() +- ++ + self.assertTrue(success) + self.assertEqual(payment, 1) + +@@ -91,10 +101,10 @@ class TestWorker(unittest.TestCase): + mock_worker = MagicMock() + mock_pq = MagicMock() + mock_pq.universal_terminal_operation.return_value = (0, None) +- ++ + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) + success, payment = ph.process_bank_payment() +- ++ + self.assertFalse(success) + self.assertIsNone(payment) + +@@ -102,49 +112,57 @@ class TestWorker(unittest.TestCase): + mock_worker = MagicMock() + mock_pq = MagicMock() + mock_pq.check_open.return_value = 1 +- ++ + ch = worker.CheckHandler(mock_worker, mock_pq) +- +- res = ch.print_check({"detail": [0,0,0,0,0,0,0,100]}, 1, MagicMock(), 1, 100, 1) ++ ++ res = ch.print_check( ++ {"detail": [0, 0, 0, 0, 0, 0, 0, 100]}, 1, MagicMock(), 1, 100, 1 ++ ) + self.assertTrue(res) +- ++ + mock_pq.check_open.return_value = 0 +- res = ch.print_check({"detail": [0,0,0,0,0,0,0,100]}, 1, MagicMock(), 1, 100, 1) ++ res = ch.print_check( ++ {"detail": [0, 0, 0, 0, 0, 0, 0, 100]}, 1, MagicMock(), 1, 100, 1 ++ ) + self.assertFalse(res) + + def test_database_handler_sale_exists(self): + mock_session_cls = MagicMock() + mock_engine = MagicMock() +- ++ + mock_session = MagicMock() + mock_session_cls.return_value.__enter__.return_value = mock_session +- ++ + mock_session.query.return_value.filter.return_value.first.return_value = True +- ++ + dh = worker.DatabaseHandler(mock_session_cls, mock_engine) + self.assertTrue(dh.sale_exists(1)) + + def test_transaction_worker_cleanup(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) +- ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) ++ + self.assertFalse(tw._is_cleaned) + tw.cleanup() + self.assertTrue(tw._is_cleaned) + self.assertEqual(mock_system.sale_status, 0) +- ++ + mock_system.sale_status = 1 + tw.cleanup() + self.assertEqual(mock_system.sale_status, 1) + + def test_transaction_worker_process_special_sale(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.invoke_main_window_method = MagicMock() + tw.delayed_progress_update = MagicMock() +- ++ + # Call it directly bypassing the decorator logic issues with MagicMock args + tw.process_special_sale.__wrapped__(tw, MagicMock(), 90) + tw.invoke_main_window_method.assert_called_with("print_saved_tickets") +@@ -152,19 +170,21 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True +- ++ + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (True, 1) +- tw.dev_mode = False # Prevent attribute error +- ++ tw.dev_mode = False # Prevent attribute error ++ + tw.pq = MagicMock() + tw.pq.read_pinpad_file.return_value = "check_text" +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertEqual(payment, 1) + self.assertEqual(bank_status, 1) +@@ -173,15 +193,17 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_checks(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() +- ++ + tw.check_handler = MagicMock() + tw.check_handler.print_check.return_value = True +- ++ + tw.db_handler = MagicMock() +- ++ + res = tw.process_checks(MagicMock(), 1, 1) + self.assertTrue(res) + tw.db_handler.update_sale.assert_called() +@@ -190,33 +212,38 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(return_value=(1, 1)) + tw.process_checks = MagicMock(return_value=True) + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_payment.assert_called() + tw.process_checks.assert_called() + tw.finalize_transaction.assert_called() + +- + def test_transaction_worker_init_amount_error(self): + mock_system = MagicMock() + mock_system.sale_dict = {} +- tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ tw = worker.TransactionWorker( ++ 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + self.assertEqual(tw.payment_handler.amount, 0) + + def test_transaction_worker_process_payment_sale_id_none(self): + mock_system = MagicMock() + mock_system.sale_id = None +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.emit_error_and_finish = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) +@@ -225,32 +252,35 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_sale_not_in_db(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = False + tw.emit_error_and_finish = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) + tw.emit_error_and_finish.assert_called() + +- + def test_transaction_worker_process_payment_dev_mode(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ + # Override getattr inside the module or just mock main_window properly + class MockMainWindow: + dev_mode = True +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow()) ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.delayed_progress_update = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertEqual(payment, 1) + self.assertEqual(bank_status, 0) +@@ -258,15 +288,17 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_fail(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (False, None) + tw.dev_mode = False +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) +@@ -274,16 +306,18 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_bank_status_3(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (True, 3) + tw.dev_mode = False + tw.delayed_progress_update = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertEqual(payment, 3) + self.assertEqual(bank_status, 1) +@@ -291,53 +325,60 @@ class TestWorker(unittest.TestCase): + + def test_transaction_worker_process_checks_check_open_fails(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.return_value = False +- ++ + res = tw.process_checks(MagicMock(), 1, 1) + self.assertFalse(res) + + def test_transaction_worker_process_checks_exception(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.side_effect = Exception("Test Error") +- ++ + res = tw.process_checks(MagicMock(), 1, 1) + self.assertFalse(res) + + def test_transaction_worker_finalize_transaction(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.invoke_main_window_method = MagicMock() + tw.close_window_signal = MagicMock() + tw.finished = MagicMock() +- ++ + tw.finalize_transaction(MagicMock()) + tw.invoke_main_window_method.assert_called_with("print_saved_tickets") + tw.close_window_signal.emit.assert_called_once() + tw.finished.emit.assert_called_once() + self.assertEqual(mock_system.sale_status, 0) + +- + def test_transaction_worker_run_special_sale(self): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_special_sale = MagicMock() + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_special_sale.assert_called_with(90) + tw.finalize_transaction.assert_called_once() +@@ -346,14 +387,16 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(return_value=(None, None)) + tw.process_checks = MagicMock() + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_payment.assert_called_once() + tw.process_checks.assert_not_called() +@@ -363,14 +406,16 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(return_value=(1, 1)) + tw.process_checks = MagicMock(return_value=False) + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_payment.assert_called_once() + tw.process_checks.assert_called_once() +@@ -380,13 +425,15 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(side_effect=Exception("Test Run Error")) + tw.handle_error = MagicMock() +- ++ + tw.run() + tw.handle_error.assert_called_once() + self.assertTrue(tw._is_cleaned) +@@ -395,39 +442,42 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = None + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.save_sale_signal = MagicMock() + tw.log_step = MagicMock() + tw.process_payment = MagicMock(return_value=(1, 1)) + tw.process_checks = MagicMock(return_value=True) + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.save_sale_signal.emit.assert_called_once() + + def test_transaction_worker_handle_error(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.emit_error_and_finish = MagicMock() +- ++ + tw.handle_error(Exception("Test exception"), MagicMock()) + tw.emit_error_and_finish.assert_called_once() + self.assertEqual(mock_system.sale_status, 0) + +- + def test_payment_handler_exception(self): + mock_worker = MagicMock() + mock_pq = MagicMock() + mock_pq.universal_terminal_operation.side_effect = Exception("Terminal Err") +- ++ + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) + success, payment = ph.process_bank_payment() +- ++ + self.assertFalse(success) + self.assertIsNone(payment) + mock_worker.emit_error_and_finish.assert_called() +@@ -435,22 +485,19 @@ class TestWorker(unittest.TestCase): + def test_payment_handler_handle_terminal_error_callback(self): + mock_worker = MagicMock() + ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) +- ++ + ph._handle_terminal_error_callback("title", "msg", 123) + mock_worker.emit_error_and_finish.assert_called_with( +- title="title", +- message="msg", +- code="123", +- step_name="terminal_error" ++ title="title", message="msg", code="123", step_name="terminal_error" + ) +- ++ + def test_payment_handler_handle_payment_failed(self): + mock_worker = MagicMock() + ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) +- ++ + ph._handle_payment_failed(MagicMock()) + mock_worker.emit_error_and_finish.assert_called() +- ++ + def test_payment_handler_get_mock_slip(self): + ph = worker.PaymentHandler(MagicMock(), MagicMock(), 101, 100.0, dev_mode=False) + slip = ph.get_mock_slip() +@@ -460,14 +507,14 @@ class TestWorker(unittest.TestCase): + def test_check_handler_error(self): + mock_worker = MagicMock() + ch = worker.CheckHandler(mock_worker, MagicMock()) +- ++ + ch.handle_check_error("Title", "Error text") + mock_worker.emit_error_and_finish.assert_called() + + def test_database_handler_sale_exists_exception(self): + mock_session_cls = MagicMock() + mock_session_cls.return_value.__enter__.side_effect = Exception("DB error") +- ++ + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) + self.assertFalse(dh.sale_exists(1)) + +@@ -475,21 +522,20 @@ class TestWorker(unittest.TestCase): + mock_session_cls = MagicMock() + mock_session = MagicMock() + mock_session_cls.return_value.__enter__.return_value = mock_session +- ++ + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) + dh.update_sale(1, status=1) + mock_session.execute.assert_called_once() + mock_session.commit.assert_called_once() + +- + def test_base_worker_emit_error_and_finish_runtime_error(self): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() + bw.error_signal.emit.side_effect = RuntimeError("already deleted") +- ++ + # This shouldn't raise exception + bw.emit_error_and_finish("Err", "Msg") +- ++ + bw.error_signal.emit.side_effect = RuntimeError("other error") + with self.assertRaises(RuntimeError): + bw.emit_error_and_finish("Err", "Msg") +@@ -498,39 +544,42 @@ class TestWorker(unittest.TestCase): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() + bw.error_signal.emit.side_effect = Exception("generic error") +- ++ + # Should just log it, not raise + bw.emit_error_and_finish("Err", "Msg") + + def test_base_worker_delayed_progress_update_negative_delay(self): + bw = worker.BaseWorker() + bw.progress_updated = MagicMock() +- +- with patch('modules.worker.QTimer') as mock_timer_cls, patch('modules.worker.QEventLoop') as mock_loop_cls: ++ ++ with patch("modules.worker.QTimer") as mock_timer_cls, patch( ++ "modules.worker.QEventLoop" ++ ) as mock_loop_cls: + mock_timer = MagicMock() + mock_timer_cls.return_value = mock_timer + mock_loop = MagicMock() + mock_loop_cls.return_value = mock_loop +- ++ + bw.delayed_progress_update("Test", 50, -10) + # -10 will become 0, meaning it won't trigger the QTimer logic + mock_timer.start.assert_not_called() + +- + def test_base_worker_invoke_main_window_method(self): + bw = worker.BaseWorker() + bw.main_window = MagicMock() +- +- with patch('modules.worker.QMetaObject.invokeMethod') as mock_invoke: ++ ++ with patch("modules.worker.QMetaObject.invokeMethod") as mock_invoke: + bw.invoke_main_window_method("test_method") + mock_invoke.assert_called_once() +- ++ + def test_transaction_worker_process_payment_slip_check_print(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() +@@ -539,26 +588,28 @@ class TestWorker(unittest.TestCase): + tw.delayed_progress_update = MagicMock() + tw.pq = MagicMock() + tw.print_check = 1 +- ++ + tw.process_payment(MagicMock()) + tw.pq.print_slip_check.assert_called_once() +- ++ + def test_transaction_worker_process_payment_db_error(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.db_handler.update_sale.side_effect = Exception("DB save err") +- ++ + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (True, 1) + tw.dev_mode = False + tw.delayed_progress_update = MagicMock() + tw.pq = MagicMock() +- ++ + tw.process_payment(MagicMock()) + tw.pq.read_pinpad_file.assert_called_once() + +@@ -566,18 +617,19 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.user = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 0, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 0, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.return_value = True + tw.db_handler = MagicMock() + tw.info_signal = MagicMock() +- ++ + tw.process_checks(MagicMock(), 1, 1) + tw.info_signal.emit.assert_called_once() + +- + def test_base_worker_emit_error_and_finish_with_timer(self): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() +@@ -585,27 +637,28 @@ class TestWorker(unittest.TestCase): + bw.finished = MagicMock() + bw.delayed_progress_update = MagicMock() + bw.log_step = MagicMock() +- ++ + mock_timer = MagicMock() + bw.emit_error_and_finish("Err", "Msg", timer=mock_timer, step_name="test_step") +- +- bw.log_step.assert_called_once_with(mock_timer, "test_step") + ++ bw.log_step.assert_called_once_with(mock_timer, "test_step") + + def test_transaction_worker_process_payment_dev_mode(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ + # Override getattr inside the module or just mock main_window properly + class MockMainWindow: + dev_mode = True +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow()) ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.delayed_progress_update = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertEqual(payment, 1) + self.assertEqual(bank_status, 0) +@@ -613,15 +666,17 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_fail(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (False, None) + tw.dev_mode = False +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) +@@ -629,16 +684,18 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_bank_status_3(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (True, 3) + tw.dev_mode = False + tw.delayed_progress_update = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertEqual(payment, 3) + self.assertEqual(bank_status, 1) +@@ -646,53 +703,60 @@ class TestWorker(unittest.TestCase): + + def test_transaction_worker_process_checks_check_open_fails(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.return_value = False +- ++ + res = tw.process_checks(MagicMock(), 1, 1) + self.assertFalse(res) + + def test_transaction_worker_process_checks_exception(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.side_effect = Exception("Test Error") +- ++ + res = tw.process_checks(MagicMock(), 1, 1) + self.assertFalse(res) + + def test_transaction_worker_finalize_transaction(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.invoke_main_window_method = MagicMock() + tw.close_window_signal = MagicMock() + tw.finished = MagicMock() +- ++ + tw.finalize_transaction(MagicMock()) + tw.invoke_main_window_method.assert_called_with("print_saved_tickets") + tw.close_window_signal.emit.assert_called_once() + tw.finished.emit.assert_called_once() + self.assertEqual(mock_system.sale_status, 0) + +- + def test_transaction_worker_run_special_sale(self): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_special_sale = MagicMock() + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_special_sale.assert_called_with(90) + tw.finalize_transaction.assert_called_once() +@@ -701,14 +765,16 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(return_value=(None, None)) + tw.process_checks = MagicMock() + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_payment.assert_called_once() + tw.process_checks.assert_not_called() +@@ -718,14 +784,16 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(return_value=(1, 1)) + tw.process_checks = MagicMock(return_value=False) + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.process_payment.assert_called_once() + tw.process_checks.assert_called_once() +@@ -735,13 +803,15 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.process_payment = MagicMock(side_effect=Exception("Test Run Error")) + tw.handle_error = MagicMock() +- ++ + tw.run() + tw.handle_error.assert_called_once() + self.assertTrue(tw._is_cleaned) +@@ -750,39 +820,42 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = None + mock_system.sale_special = 0 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.save_sale_signal = MagicMock() + tw.log_step = MagicMock() + tw.process_payment = MagicMock(return_value=(1, 1)) + tw.process_checks = MagicMock(return_value=True) + tw.finalize_transaction = MagicMock() +- ++ + tw.run() + tw.save_sale_signal.emit.assert_called_once() + + def test_transaction_worker_handle_error(self): + mock_system = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.emit_error_and_finish = MagicMock() +- ++ + tw.handle_error(Exception("Test exception"), MagicMock()) + tw.emit_error_and_finish.assert_called_once() + self.assertEqual(mock_system.sale_status, 0) + +- + def test_payment_handler_exception(self): + mock_worker = MagicMock() + mock_pq = MagicMock() + mock_pq.universal_terminal_operation.side_effect = Exception("Terminal Err") +- ++ + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) + success, payment = ph.process_bank_payment() +- ++ + self.assertFalse(success) + self.assertIsNone(payment) + mock_worker.emit_error_and_finish.assert_called() +@@ -790,22 +863,19 @@ class TestWorker(unittest.TestCase): + def test_payment_handler_handle_terminal_error_callback(self): + mock_worker = MagicMock() + ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) +- ++ + ph._handle_terminal_error_callback("title", "msg", 123) + mock_worker.emit_error_and_finish.assert_called_with( +- title="title", +- message="msg", +- code="123", +- step_name="terminal_error" ++ title="title", message="msg", code="123", step_name="terminal_error" + ) +- ++ + def test_payment_handler_handle_payment_failed(self): + mock_worker = MagicMock() + ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) +- ++ + ph._handle_payment_failed(MagicMock()) + mock_worker.emit_error_and_finish.assert_called() +- ++ + def test_payment_handler_get_mock_slip(self): + ph = worker.PaymentHandler(MagicMock(), MagicMock(), 101, 100.0, dev_mode=False) + slip = ph.get_mock_slip() +@@ -815,14 +885,14 @@ class TestWorker(unittest.TestCase): + def test_check_handler_error(self): + mock_worker = MagicMock() + ch = worker.CheckHandler(mock_worker, MagicMock()) +- ++ + ch.handle_check_error("Title", "Error text") + mock_worker.emit_error_and_finish.assert_called() + + def test_database_handler_sale_exists_exception(self): + mock_session_cls = MagicMock() + mock_session_cls.return_value.__enter__.side_effect = Exception("DB error") +- ++ + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) + self.assertFalse(dh.sale_exists(1)) + +@@ -830,21 +900,20 @@ class TestWorker(unittest.TestCase): + mock_session_cls = MagicMock() + mock_session = MagicMock() + mock_session_cls.return_value.__enter__.return_value = mock_session +- ++ + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) + dh.update_sale(1, status=1) + mock_session.execute.assert_called_once() + mock_session.commit.assert_called_once() + +- + def test_base_worker_emit_error_and_finish_runtime_error(self): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() + bw.error_signal.emit.side_effect = RuntimeError("already deleted") +- ++ + # This shouldn't raise exception + bw.emit_error_and_finish("Err", "Msg") +- ++ + bw.error_signal.emit.side_effect = RuntimeError("other error") + with self.assertRaises(RuntimeError): + bw.emit_error_and_finish("Err", "Msg") +@@ -853,39 +922,42 @@ class TestWorker(unittest.TestCase): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() + bw.error_signal.emit.side_effect = Exception("generic error") +- ++ + # Should just log it, not raise + bw.emit_error_and_finish("Err", "Msg") + + def test_base_worker_delayed_progress_update_negative_delay(self): + bw = worker.BaseWorker() + bw.progress_updated = MagicMock() +- +- with patch('modules.worker.QTimer') as mock_timer_cls, patch('modules.worker.QEventLoop') as mock_loop_cls: ++ ++ with patch("modules.worker.QTimer") as mock_timer_cls, patch( ++ "modules.worker.QEventLoop" ++ ) as mock_loop_cls: + mock_timer = MagicMock() + mock_timer_cls.return_value = mock_timer + mock_loop = MagicMock() + mock_loop_cls.return_value = mock_loop +- ++ + bw.delayed_progress_update("Test", 50, -10) + # -10 will become 0, meaning it won't trigger the QTimer logic + mock_timer.start.assert_not_called() + +- + def test_base_worker_invoke_main_window_method(self): + bw = worker.BaseWorker() + bw.main_window = MagicMock() +- +- with patch('modules.worker.QMetaObject.invokeMethod') as mock_invoke: ++ ++ with patch("modules.worker.QMetaObject.invokeMethod") as mock_invoke: + bw.invoke_main_window_method("test_method") + mock_invoke.assert_called_once() +- ++ + def test_transaction_worker_process_payment_slip_check_print(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.payment_handler = MagicMock() +@@ -894,26 +966,28 @@ class TestWorker(unittest.TestCase): + tw.delayed_progress_update = MagicMock() + tw.pq = MagicMock() + tw.print_check = 1 +- ++ + tw.process_payment(MagicMock()) + tw.pq.print_slip_check.assert_called_once() +- ++ + def test_transaction_worker_process_payment_db_error(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = True + tw.db_handler.update_sale.side_effect = Exception("DB save err") +- ++ + tw.payment_handler = MagicMock() + tw.payment_handler.process_bank_payment.return_value = (True, 1) + tw.dev_mode = False + tw.delayed_progress_update = MagicMock() + tw.pq = MagicMock() +- ++ + tw.process_payment(MagicMock()) + tw.pq.read_pinpad_file.assert_called_once() + +@@ -921,18 +995,19 @@ class TestWorker(unittest.TestCase): + mock_system = MagicMock() + mock_system.sale_id = 1 + mock_system.user = MagicMock() +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 0, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 0, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.delayed_progress_update = MagicMock() + tw.check_handler = MagicMock() + tw.check_handler.print_check.return_value = True + tw.db_handler = MagicMock() + tw.info_signal = MagicMock() +- ++ + tw.process_checks(MagicMock(), 1, 1) + tw.info_signal.emit.assert_called_once() + +- + def test_base_worker_emit_error_and_finish_with_timer(self): + bw = worker.BaseWorker() + bw.error_signal = MagicMock() +@@ -940,19 +1015,21 @@ class TestWorker(unittest.TestCase): + bw.finished = MagicMock() + bw.delayed_progress_update = MagicMock() + bw.log_step = MagicMock() +- ++ + mock_timer = MagicMock() + bw.emit_error_and_finish("Err", "Msg", timer=mock_timer, step_name="test_step") +- ++ + bw.log_step.assert_called_once_with(mock_timer, "test_step") + + def test_transaction_worker_process_payment_sale_id_none(self): + mock_system = MagicMock() + mock_system.sale_id = None +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.emit_error_and_finish = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) +@@ -961,18 +1038,19 @@ class TestWorker(unittest.TestCase): + def test_transaction_worker_process_payment_sale_not_in_db(self): + mock_system = MagicMock() + mock_system.sale_id = 1 +- mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} +- tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) ++ mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} ++ tw = worker.TransactionWorker( ++ 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() ++ ) + tw.db_handler = MagicMock() + tw.db_handler.sale_exists.return_value = False + tw.emit_error_and_finish = MagicMock() +- ++ + payment, bank_status = tw.process_payment(MagicMock()) + self.assertIsNone(payment) + self.assertIsNone(bank_status) + tw.emit_error_and_finish.assert_called() + +-if __name__ == '__main__': +- unittest.main() + +- +\ No newline at end of file ++if __name__ == "__main__": ++ unittest.main() diff --git a/tests/test_auth_logic.py b/tests/test_auth_logic.py index 1c86dbd..0b329af 100644 --- a/tests/test_auth_logic.py +++ b/tests/test_auth_logic.py @@ -1,18 +1,19 @@ +import datetime as dt import unittest from unittest.mock import MagicMock, patch -import datetime as dt from modules.auth_logic import perform_pre_sale_checks from modules.system import System + class TestAuthLogic(unittest.TestCase): def setUp(self): # Prevent System from trying to connect to a real DB - patcher = patch('modules.system.create_engine') + patcher = patch("modules.system.create_engine") self.mock_create_engine = patcher.start() self.addCleanup(patcher.stop) - + self.system = System() self.system.what_a_day = None self.system.num_of_week = None @@ -30,48 +31,52 @@ def test_perform_pre_sale_checks_authorization_success(self): result = perform_pre_sale_checks("valid_login", "valid_password") self.assertEqual(result, 1) - self.system.user_authorization.assert_called_once_with("valid_login", "valid_password") + self.system.user_authorization.assert_called_once_with( + "valid_login", "valid_password" + ) def test_perform_pre_sale_checks_authorization_failure(self): self.system.user_authorization = MagicMock(return_value=0) result = perform_pre_sale_checks("invalid_login", "invalid_password") self.assertEqual(result, 0) - self.system.user_authorization.assert_called_once_with("invalid_login", "invalid_password") + self.system.user_authorization.assert_called_once_with( + "invalid_login", "invalid_password" + ) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_check_day_status_weekday(self, mock_dt): - mock_date = dt.datetime(2024, 11, 6) # Wednesday + mock_date = dt.datetime(2024, 11, 6) # Wednesday mock_dt.datetime.today.return_value = mock_date - + self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=0) - + result = perform_pre_sale_checks("valid_login", "valid_password") self.assertEqual(result, 1) self.assertEqual(self.system.what_a_day, 0) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_check_day_status_weekend(self, mock_dt): - mock_date = dt.datetime(2024, 11, 2) # Saturday + mock_date = dt.datetime(2024, 11, 2) # Saturday mock_dt.datetime.today.return_value = mock_date self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=1) - + result = perform_pre_sale_checks("valid_login", "valid_password") self.assertEqual(result, 1) self.assertEqual(self.system.what_a_day, 1) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_sunday_for_large_families_yes(self, mock_dt): mock_date = MagicMock() mock_date.isoweekday.return_value = 7 mock_date.day = 3 mock_dt.datetime.today.return_value = mock_date - + self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=0) @@ -80,13 +85,13 @@ def test_sunday_for_large_families_yes(self, mock_dt): self.assertEqual(result, 1) self.assertEqual(self.system.sunday, 1) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_sunday_for_large_families_no_not_sunday(self, mock_dt): mock_date = MagicMock() mock_date.isoweekday.return_value = 1 mock_date.day = 4 mock_dt.datetime.today.return_value = mock_date - + self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=0) @@ -95,13 +100,13 @@ def test_sunday_for_large_families_no_not_sunday(self, mock_dt): self.assertEqual(result, 1) self.assertEqual(self.system.sunday, 0) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_sunday_for_large_families_no_not_first_week(self, mock_dt): mock_date = MagicMock() mock_date.isoweekday.return_value = 7 mock_date.day = 10 mock_dt.datetime.today.return_value = mock_date - + self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=0) @@ -110,13 +115,13 @@ def test_sunday_for_large_families_no_not_first_week(self, mock_dt): self.assertEqual(result, 1) self.assertEqual(self.system.sunday, 0) - @patch('modules.auth_logic.dt') + @patch("modules.auth_logic.dt") def test_week_and_month_day_assignment(self, mock_dt): mock_date = MagicMock() mock_date.isoweekday.return_value = 3 mock_date.day = 6 mock_dt.datetime.today.return_value = mock_date - + self.system.user_authorization = MagicMock(return_value=1) self.system.get_price = MagicMock() self.system.check_day = MagicMock(return_value=0) @@ -126,5 +131,6 @@ def test_week_and_month_day_assignment(self, mock_dt): self.assertEqual(result, 1) self.assertEqual(self.system.num_of_week, 3) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_config.py b/tests/test_config.py index a7b68f2..c076501 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,8 +1,10 @@ -import unittest import os import tempfile +import unittest + from modules.config import Config + class TestConfig(unittest.TestCase): def setUp(self): @@ -120,7 +122,9 @@ def test_missing_pc_option(self): with self.assertRaises(RuntimeError) as context: Config(config_file=self.config_path) - self.assertIn("Неизвестная ошибка: Отсутствует параметр 'pc_2'", str(context.exception)) + self.assertIn( + "Неизвестная ошибка: Отсутствует параметр 'pc_2'", str(context.exception) + ) def test_empty_pc_name(self): invalid_content = """ @@ -155,7 +159,8 @@ def test_empty_pc_name(self): Config(config_file=self.config_path) self.assertIn("Неизвестная ошибка: Пустое значение", str(context.exception)) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() def test_missing_pc_name_empty(self): @@ -198,4 +203,3 @@ def test_missing_section_header(self): with self.assertRaises(RuntimeError): Config(config_file=self.config_path) - diff --git a/tests/test_logger.py b/tests/test_logger.py index cc28d7a..8f512a2 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -1,14 +1,16 @@ import unittest -from modules.logger import logger_wraps, logger -from unittest.mock import patch, MagicMock +from unittest.mock import patch + +from modules.logger import logger, logger_wraps + class TestLogger(unittest.TestCase): def test_logger_wraps_success(self): @logger_wraps() def test_func(): return "success" - - with patch.object(logger, 'info') as mock_info: + + with patch.object(logger, "info") as mock_info: res = test_func() self.assertEqual(res, "success") @@ -16,8 +18,8 @@ def test_logger_wraps_exception(self): @logger_wraps() def test_func(): raise ValueError("Test Error") - - with patch.object(logger, 'exception') as mock_exc: + + with patch.object(logger, "exception") as mock_exc: with self.assertRaises(ValueError): test_func() @@ -25,10 +27,11 @@ def test_logger_wraps_no_entry_exit(self): @logger_wraps(entry=False, exit=False) def test_func(): return "success" - - with patch.object(logger, 'info') as mock_info: + + with patch.object(logger, "info") as mock_info: res = test_func() self.assertEqual(res, "success") -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_otchet.py b/tests/test_otchet.py index ec980b6..f768ae1 100644 --- a/tests/test_otchet.py +++ b/tests/test_otchet.py @@ -1,36 +1,38 @@ +import sys import unittest -from unittest.mock import patch, MagicMock -from datetime import datetime +from unittest.mock import MagicMock, patch -import sys -sys.modules['reportlab'] = MagicMock() -sys.modules['reportlab.lib'] = MagicMock() -sys.modules['reportlab.lib.colors'] = MagicMock() -sys.modules['reportlab.lib.pagesizes'] = MagicMock() -sys.modules['reportlab.lib.pagesizes'].landscape = lambda x: x -sys.modules['reportlab.lib.pagesizes'].A4 = "A4" -sys.modules['reportlab.lib.pagesizes'].letter = "letter" -sys.modules['reportlab.lib.units'] = MagicMock() -sys.modules['reportlab.lib.units'].mm = 1 -sys.modules['reportlab.lib.units'].inch = 1 -sys.modules['reportlab.pdfbase'] = MagicMock() -sys.modules['reportlab.pdfbase.pdfmetrics'] = MagicMock() -sys.modules['reportlab.pdfbase.ttfonts'] = MagicMock() -sys.modules['reportlab.pdfgen'] = MagicMock() -sys.modules['reportlab.platypus'] = MagicMock() -sys.modules['reportlab.platypus'].Table = MagicMock() -sys.modules['reportlab.platypus'].TableStyle = MagicMock() - -import modules.otchet as otchet +# isort: split +sys.modules["reportlab"] = MagicMock() +sys.modules["reportlab.lib"] = MagicMock() +sys.modules["reportlab.lib.colors"] = MagicMock() +sys.modules["reportlab.lib.pagesizes"] = MagicMock() +sys.modules["reportlab.lib.pagesizes"].landscape = lambda x: x +sys.modules["reportlab.lib.pagesizes"].A4 = "A4" +sys.modules["reportlab.lib.pagesizes"].letter = "letter" +sys.modules["reportlab.lib.units"] = MagicMock() +sys.modules["reportlab.lib.units"].mm = 1 +sys.modules["reportlab.lib.units"].inch = 1 +sys.modules["reportlab.pdfbase"] = MagicMock() +sys.modules["reportlab.pdfbase.pdfmetrics"] = MagicMock() +sys.modules["reportlab.pdfbase.ttfonts"] = MagicMock() +sys.modules["reportlab.pdfgen"] = MagicMock() +sys.modules["reportlab.platypus"] = MagicMock() +sys.modules["reportlab.platypus"].Table = MagicMock() +sys.modules["reportlab.platypus"].TableStyle = MagicMock() + +# isort: split +from modules import otchet from modules.otchet import ( - get_ticket_type, - generate_ticket_report_table, - safe_int, format_date_range, + generate_ticket_report_table, + get_ticket_type, process_sales_and_returns, - process_ticket_stats + process_ticket_stats, + safe_int, ) + class TestOtchet(unittest.TestCase): def test_get_ticket_type(self): @@ -43,7 +45,7 @@ def test_get_ticket_type(self): with self.assertRaises(ValueError): get_ticket_type(-1) - + with self.assertRaises(ValueError): get_ticket_type("invalid") @@ -65,22 +67,27 @@ def test_format_date_range(self): def test_generate_ticket_report_table(self): summary = { - "Взрослый, 1 ч.": { - 250: {"count": 2, "total_price": 500} - }, - "Детский, 2 ч.": { - 500: {"count": 1, "total_price": 500} - } + "Взрослый, 1 ч.": {250: {"count": 2, "total_price": 500}}, + "Детский, 2 ч.": {500: {"count": 1, "total_price": 500}}, } table = generate_ticket_report_table(summary) - - self.assertEqual(table[0], ["№\n п/п", "Тип\nбилета", "Цена,\n руб.", "Количество,\n шт.", "Стоимость,\n руб."]) + + self.assertEqual( + table[0], + [ + "№\n п/п", + "Тип\nбилета", + "Цена,\n руб.", + "Количество,\n шт.", + "Стоимость,\n руб.", + ], + ) self.assertIn(["1", "Взрослый, 1 ч.", 250, 2, 500], table) self.assertEqual(table[-1][1], "Итого билетов") self.assertEqual(table[-1][3], 3) self.assertEqual(table[-1][4], 1000) - @patch('modules.otchet.system') + @patch("modules.otchet.system") def test_process_sales_and_returns(self, mock_system): mock_system.pcs = ["PC1", "PC2"] sales = [("PC1", 1, 1000), ("PC1", 2, 500), ("PC2", 1, 200)] @@ -95,7 +102,7 @@ def test_process_sales_and_returns(self, mock_system): self.assertEqual(total_row[0], "Итого") self.assertEqual(total_row[1], 1200) - @patch('modules.otchet.system') + @patch("modules.otchet.system") def test_process_ticket_stats(self, mock_system): tickets = [ (0, 1, "-", None, None, 100), @@ -109,38 +116,40 @@ def test_process_ticket_stats(self, mock_system): adult_stats = next(r for r in result if r[0] == "взрослый")[1] self.assertEqual(adult_stats["sum"], 1) - @patch('modules.otchet.canvas.Canvas') + @patch("modules.otchet.canvas.Canvas") def test_otchet_administratora(self, mock_canvas_class): mock_canvas_inst = MagicMock() mock_canvas_class.return_value = mock_canvas_inst - + values = {"Взрослый, 1 ч.": {250: {"count": 2, "total_price": 500}}} - otchet.otchet_administratora("2023-10-01 12:00:00", "2023-10-02 15:30:00", values) - + otchet.otchet_administratora( + "2023-10-01 12:00:00", "2023-10-02 15:30:00", values + ) + self.assertTrue(mock_canvas_class.called) self.assertTrue(mock_canvas_inst.save.called) - @patch('modules.otchet.canvas.Canvas') + @patch("modules.otchet.canvas.Canvas") def test_otchet_kassira(self, mock_canvas_class): mock_canvas_inst = MagicMock() mock_canvas_class.return_value = mock_canvas_inst - + val = [1000, 500, 100, 50] kassir = MagicMock() kassir.last_name = "Иванов" kassir.first_name = "Иван" kassir.middle_name = "Иванович" - + otchet.otchet_kassira(val, "2023-10-01 12:00:00", "2023-10-02 15:30:00", kassir) self.assertTrue(mock_canvas_class.called) self.assertTrue(mock_canvas_inst.save.called) - @patch('modules.otchet.canvas.Canvas') - @patch('modules.otchet.system.load_coordinates') + @patch("modules.otchet.canvas.Canvas") + @patch("modules.otchet.system.load_coordinates") def test_generate_saved_tickets(self, mock_load_coords, mock_canvas_class): mock_canvas_inst = MagicMock() mock_canvas_class.return_value = mock_canvas_inst - + mock_load_coords.return_value = { "name": {"x": 10, "y": 20}, "surname": {"x": 10, "y": 20}, @@ -154,15 +163,14 @@ def test_generate_saved_tickets(self, mock_load_coords, mock_canvas_class): "ticket_type": {"x": 10, "y": 20}, "notes": {"x": 10, "y": 20}, "talents": {"x": 10, "y": 20}, - "qr_code": {"x": 10, "y": 20} + "qr_code": {"x": 10, "y": 20}, } - - values = [ - ("Иванов", "Иван", 1, 500, "-", 1, 10, 3, 50, "2023-10-01") - ] + + values = [("Иванов", "Иван", 1, 500, "-", 1, 10, 3, 50, "2023-10-01")] otchet.generate_saved_tickets(values) self.assertTrue(mock_canvas_class.called) self.assertTrue(mock_canvas_inst.save.called) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_payment_equipment.py b/tests/test_payment_equipment.py index be3c476..a5766ed 100644 --- a/tests/test_payment_equipment.py +++ b/tests/test_payment_equipment.py @@ -1,13 +1,18 @@ +import sys import unittest -from unittest.mock import patch, MagicMock, mock_open +from unittest.mock import MagicMock, mock_open, patch -import sys -sys.modules['PySide6'] = MagicMock() -sys.modules['PySide6.QtCore'] = MagicMock() -sys.modules['PySide6.QtWidgets'] = MagicMock() +# isort: split +sys.modules["PySide6"] = MagicMock() +sys.modules["PySide6.QtCore"] = MagicMock() +sys.modules["PySide6.QtWidgets"] = MagicMock() +# isort: split import types + fake_libfptr10 = types.ModuleType("modules.libfptr10") + + class FakeIFptr: LIBFPTR_PARAM_TEXT_WRAP = 1 LIBFPTR_TW_WORDS = 1 @@ -57,14 +62,18 @@ class FakeIFptr: LIBFPTR_PARAM_RECEIPT_NUMBER = 1 LIBFPTR_RT_CLOSED = 0 LIBFPTR_RT_CLOSE_SHIFT = 1 - - def __init__(self, *args): pass + + def __init__(self, *args): + pass + fake_libfptr10.IFptr = FakeIFptr -sys.modules['modules.libfptr10'] = fake_libfptr10 +sys.modules["modules.libfptr10"] = fake_libfptr10 +# isort: split import modules.payment_equipment as pe + class TestPE(unittest.TestCase): def setUp(self): pe.dev_mode = False @@ -74,7 +83,7 @@ def test_smena_info(self): mock_fptr = MagicMock() mock_fptr.open.return_value = None mock_fptr.getParamInt.side_effect = [1, 100] - + with patch("modules.payment_equipment.fptr", mock_fptr): with patch("modules.payment_equipment.windows.info_window") as mock_info: res = pe.smena_info() @@ -91,7 +100,7 @@ def test_report_payment(self): with patch("modules.payment_equipment.fptr", mock_fptr): pe.report_payment() mock_fptr.report.assert_called_once() - + def test_report_x(self): mock_fptr = MagicMock() with patch("modules.payment_equipment.fptr", mock_fptr): @@ -111,7 +120,7 @@ def test_payment(self): with patch("modules.payment_equipment.windows.info_window") as mock_info: pe.payment(100.0) mock_fptr.cashOutcome.assert_called_once() - + def test_balance_check(self): mock_fptr = MagicMock() mock_fptr.getParamDouble.return_value = 500.0 @@ -129,18 +138,17 @@ def test_run_terminal_command(self): mock_popen.return_value = mock_process pe.run_terminal_command("1") mock_popen.assert_called_once() - + def test_check_terminal_file(self): with patch("builtins.open", mock_open(read_data="УСПЕШНО")): res = pe.check_terminal_file("УСПЕШНО") self.assertTrue(res) - def test_process_success_result(self): with patch("modules.payment_equipment.windows.info_window"): res = pe.process_success_result() self.assertEqual(res, pe.TERMINAL_SUCCESS_CODE) - + def test_handle_error(self): with patch("modules.payment_equipment.windows.info_window"): res = pe.handle_error(1, "title", "msg", None) @@ -155,7 +163,9 @@ def test_process_terminal_error(self): self.assertEqual(res, 0) def test_terminal_oplata(self): - with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: + with patch( + "modules.payment_equipment.process_terminal_transaction" + ) as mock_ptt: mock_ptt.return_value = 1 res = pe.terminal_oplata(100.0) self.assertEqual(res, 1) @@ -163,31 +173,34 @@ def test_terminal_oplata(self): def test_terminal_check_itog(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.check_terminal_file", return_value=True): + with patch( + "modules.payment_equipment.check_terminal_file", return_value=True + ): res = pe.terminal_check_itog() self.assertEqual(res, 1) - + def test_terminal_menu(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) res = pe.terminal_menu() self.assertIsNone(res) - + def test_terminal_check_itog_window(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.check_terminal_file", return_value=True): - with patch("modules.payment_equipment.windows.info_window") as mock_info: - pe.terminal_check_itog_window() - mock_info.assert_called_once() - + with patch( + "modules.payment_equipment.check_terminal_file", return_value=True + ), patch("modules.payment_equipment.windows.info_window") as mock_info: + pe.terminal_check_itog_window() + mock_info.assert_called_once() + def test_terminal_svod_check(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) with patch("modules.payment_equipment.print_pinpad_check"): res = pe.terminal_svod_check() self.assertIsNone(res) - + def test_terminal_control_lenta(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) @@ -196,9 +209,11 @@ def test_terminal_control_lenta(self): self.assertIsNone(res) def test_terminal_print_file(self): - with patch("modules.payment_equipment.read_pinpad_file", return_value="print_data"), \ - patch("modules.payment_equipment.fptr") as mock_fptr, \ - patch("modules.payment_equipment.windows.info_window"): + with patch( + "modules.payment_equipment.read_pinpad_file", return_value="print_data" + ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( + "modules.payment_equipment.windows.info_window" + ): pe.terminal_print_file() mock_fptr.printText.assert_called() @@ -208,7 +223,7 @@ def test_terminal_copy_last_check(self): with patch("modules.payment_equipment.print_pinpad_check"): res = pe.terminal_copy_last_check() self.assertIsNone(res) - + def test_is_kkt_connected(self): pe.kkt_available = True mock_fptr = MagicMock() @@ -223,47 +238,66 @@ def test_process_terminal_transaction(self): mock_proc = MagicMock() mock_proc.returncode = pe.TERMINAL_SUCCESS_CODE mock_run.return_value = mock_proc - with patch("modules.payment_equipment.check_terminal_file", return_value=True): - with patch("modules.payment_equipment.terminal_print_file"): - res = pe.process_terminal_transaction("1", 100.0, "Оплата") - self.assertEqual(res, 1) + with patch( + "modules.payment_equipment.check_terminal_file", return_value=True + ), patch("modules.payment_equipment.terminal_print_file"): + res = pe.process_terminal_transaction("1", 100.0, "Оплата") + self.assertEqual(res, 1) def test_universal_terminal_operation(self): - with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: + with patch( + "modules.payment_equipment.process_terminal_transaction" + ) as mock_ptt: mock_ptt.return_value = 1 mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 + ) self.assertEqual(res, (1, 1)) def test_print_slip_check(self): - with patch("modules.payment_equipment.read_pinpad_file", return_value="slip_data"), \ - patch("modules.payment_equipment.fptr") as mock_fptr, \ - patch("modules.payment_equipment.windows.info_window"): + with patch( + "modules.payment_equipment.read_pinpad_file", return_value="slip_data" + ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( + "modules.payment_equipment.windows.info_window" + ): pe.print_slip_check() mock_fptr.printText.assert_called() def test_print_pinpad_check(self): - with patch("modules.payment_equipment.read_pinpad_file", return_value=["line1", "line2"]), \ - patch("modules.payment_equipment.fptr") as mock_fptr, \ - patch("modules.payment_equipment.windows.info_window"): + with patch( + "modules.payment_equipment.read_pinpad_file", + return_value=["line1", "line2"], + ), patch("modules.payment_equipment.fptr") as mock_fptr, patch( + "modules.payment_equipment.windows.info_window" + ): pe.print_pinpad_check() mock_fptr.printText.assert_called() def test_universal_terminal_operation_refund(self): - with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: + with patch( + "modules.payment_equipment.process_terminal_transaction" + ) as mock_ptt: mock_ptt.return_value = 1 mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=2) + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=2 + ) self.assertEqual(res, (1, 1)) def test_universal_terminal_operation_cancel(self): - with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: + with patch( + "modules.payment_equipment.process_terminal_transaction" + ) as mock_ptt: mock_ptt.return_value = 1 mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=3) + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=3 + ) self.assertEqual(res, (1, 1)) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() def test_kkt_connection_dev_mode(self): @@ -275,20 +309,20 @@ def test_kkt_connection_kkt_not_available(self): with patch("modules.payment_equipment.kkt_available", False): with pe.fptr_connection(pe.fptr) as conn: self.assertIsNone(conn) - + def test_kkt_connection_not_opened(self): mock_device = MagicMock() mock_device.isOpened.return_value = False with pe.fptr_connection(mock_device) as conn: self.assertIsNone(conn) mock_device.open.assert_called_once() - + def test_kkt_connection_exception_on_open(self): mock_device = MagicMock() mock_device.open.side_effect = Exception("Open failed") with pe.fptr_connection(mock_device) as conn: self.assertIsNone(conn) - + def test_kkt_connection_success(self): mock_device = MagicMock() mock_device.isOpened.return_value = True @@ -297,22 +331,27 @@ def test_kkt_connection_success(self): mock_device.close.assert_called_once() def test_run_terminal_command_timeout(self): - with patch("subprocess.Popen") as mock_popen, \ - patch("os.path.isfile", return_value=True): + with patch("subprocess.Popen") as mock_popen, patch( + "os.path.isfile", return_value=True + ): mock_process = MagicMock() - mock_process.communicate.side_effect = [subprocess.TimeoutExpired(cmd="", timeout=1), (b"", b"")] + mock_process.communicate.side_effect = [ + subprocess.TimeoutExpired(cmd="", timeout=1), + (b"", b""), + ] mock_popen.return_value = mock_process res = pe.run_terminal_command("1", timeout=1) mock_process.terminate.assert_called_once() - + def test_run_terminal_command_timeout_twice(self): - with patch("subprocess.Popen") as mock_popen, \ - patch("os.path.isfile", return_value=True): + with patch("subprocess.Popen") as mock_popen, patch( + "os.path.isfile", return_value=True + ): mock_process = MagicMock() mock_process.communicate.side_effect = [ - subprocess.TimeoutExpired(cmd="", timeout=1), - subprocess.TimeoutExpired(cmd="", timeout=1), - (b"", b"") + subprocess.TimeoutExpired(cmd="", timeout=1), + subprocess.TimeoutExpired(cmd="", timeout=1), + (b"", b""), ] mock_popen.return_value = mock_process res = pe.run_terminal_command("1", timeout=1) @@ -323,13 +362,13 @@ def test_run_terminal_command_file_not_found(self): with patch("os.path.isfile", return_value=False): with self.assertRaises(FileNotFoundError): pe.run_terminal_command("1") - - def test_run_terminal_command_exception(self): - with patch("os.path.isfile", return_value=True), \ - patch("subprocess.Popen", side_effect=Exception("Exec failed")): - res = pe.run_terminal_command("1") - self.assertIsNone(res) + def test_run_terminal_command_exception(self): + with patch("os.path.isfile", return_value=True), patch( + "subprocess.Popen", side_effect=Exception("Exec failed") + ): + res = pe.run_terminal_command("1") + self.assertIsNone(res) def test_check_terminal_file_not_found_word(self): with patch("builtins.open", mock_open(read_data="НЕУСПЕШНО")): @@ -342,7 +381,10 @@ def test_check_terminal_file_file_not_found(self): self.assertFalse(res) def test_check_terminal_file_decode_error(self): - with patch("builtins.open", side_effect=UnicodeDecodeError("codec", b"", 0, 1, "reason")): + with patch( + "builtins.open", + side_effect=UnicodeDecodeError("codec", b"", 0, 1, "reason"), + ): res = pe.check_terminal_file("УСПЕШНО") self.assertFalse(res) @@ -350,14 +392,19 @@ def test_process_success_result_not_found(self): with patch("modules.payment_equipment.check_terminal_file", return_value=False): res = pe.process_success_result() self.assertEqual(res, 0) - + def test_process_success_result_exception(self): - with patch("modules.payment_equipment.check_terminal_file", side_effect=FileNotFoundError): + with patch( + "modules.payment_equipment.check_terminal_file", + side_effect=FileNotFoundError, + ): res = pe.process_success_result() self.assertEqual(res, 0) def test_safe_handle_error(self): - with patch("modules.payment_equipment.handle_error", side_effect=Exception("Error")): + with patch( + "modules.payment_equipment.handle_error", side_effect=Exception("Error") + ): res = pe._safe_handle_error(1, "title", "msg", None) self.assertIsNone(res) @@ -370,66 +417,107 @@ def test_handle_error_with_callback_exception(self): mock_callback = MagicMock(side_effect=Exception("Callback error")) pe.handle_error(1, "title", "msg", mock_callback) mock_callback.assert_called_once_with("title", "msg", 1) - def test_process_terminal_error_conditions(self): with patch("modules.payment_equipment.handle_error"): # Test each error category - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_CARD_BLOCKED[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_CARD_LIMIT[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_BIOMETRIC_ERROR[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_ERROR_PIN_CODE[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_NO_CONNECTION_BANK[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_HARDWARE_ERROR[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_OPERATION_CANCEL[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_LACK_PAPER[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_SUM_LIMIT[0], None), 0) - + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_CARD_BLOCKED[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_CARD_LIMIT[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_BIOMETRIC_ERROR[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_ERROR_PIN_CODE[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_NO_CONNECTION_BANK[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_HARDWARE_ERROR[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_OPERATION_CANCEL[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_LACK_PAPER[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_SUM_LIMIT[0], None), 0 + ) + # Test default mapped error self.assertEqual(pe.process_terminal_error(4451, None), 0) - + # Test unknown error self.assertEqual(pe.process_terminal_error(99999, None), 0) def test_process_terminal_error_more_conditions(self): with patch("modules.payment_equipment.handle_error"): - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_COMMAND_ERROR[0], None), 0) - self.assertEqual(pe.process_terminal_error(pe.TERMINAL_PIN_PAD_ERROR[0], None), 0) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_COMMAND_ERROR[0], None), 0 + ) + self.assertEqual( + pe.process_terminal_error(pe.TERMINAL_PIN_PAD_ERROR[0], None), 0 + ) def test_universal_terminal_operation_unsupported_operation_type(self): mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=99) + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=99 + ) self.assertEqual(res, (0, 0)) def test_universal_terminal_operation_offline(self): mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=1) + res = pe.universal_terminal_operation( + pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=1 + ) self.assertEqual(res, (1, 3)) - + def test_universal_terminal_operation_offline_not_payment(self): mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=2) + res = pe.universal_terminal_operation( + pe.PAYMENT_OFFLINE, 100.0, mock_signal, operation_type=2 + ) self.assertEqual(res, (0, 0)) - + def test_universal_terminal_operation_unknown_payment_type(self): mock_signal = MagicMock() res = pe.universal_terminal_operation(999, 100.0, mock_signal, operation_type=1) self.assertEqual(res, (0, 0)) def test_universal_terminal_operation_transaction_fails(self): - with patch("modules.payment_equipment.process_terminal_transaction") as mock_ptt: + with patch( + "modules.payment_equipment.process_terminal_transaction" + ) as mock_ptt: mock_ptt.return_value = 0 mock_signal = MagicMock() - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 + ) self.assertEqual(res, (0, 1)) def test_universal_terminal_operation_exceptions(self): mock_signal = MagicMock() - with patch("modules.payment_equipment.process_terminal_transaction", side_effect=ValueError("Test")): - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) + with patch( + "modules.payment_equipment.process_terminal_transaction", + side_effect=ValueError("Test"), + ): + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 + ) self.assertEqual(res, (0, 0)) - with patch("modules.payment_equipment.process_terminal_transaction", side_effect=Exception("Test2")): - res = pe.universal_terminal_operation(pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1) + with patch( + "modules.payment_equipment.process_terminal_transaction", + side_effect=Exception("Test2"), + ): + res = pe.universal_terminal_operation( + pe.PAYMENT_ELECTRONIC, 100.0, mock_signal, operation_type=1 + ) self.assertEqual(res, (0, 0)) def test_terminal_check_itog_run_error(self): @@ -437,17 +525,20 @@ def test_terminal_check_itog_run_error(self): mock_run.return_value = None res = pe.terminal_check_itog() self.assertEqual(res, 0) - + def test_terminal_check_itog_unknown_code(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=999) res = pe.terminal_check_itog() self.assertEqual(res, 0) - + def test_terminal_check_itog_file_not_found(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.check_terminal_file", side_effect=FileNotFoundError): + with patch( + "modules.payment_equipment.check_terminal_file", + side_effect=FileNotFoundError, + ): res = pe.terminal_check_itog() self.assertEqual(res, 0) @@ -457,15 +548,17 @@ def test_terminal_check_itog_window_run_error(self): with patch("modules.payment_equipment.windows.info_window") as mock_info: pe.terminal_check_itog_window() mock_info.assert_called_once() - + def test_terminal_check_itog_window_file_not_found(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.read_pinpad_file", side_effect=FileNotFoundError): - with patch("modules.payment_equipment.windows.info_window") as mock_info: - pe.terminal_check_itog_window() - mock_info.assert_called_once() - + with patch( + "modules.payment_equipment.read_pinpad_file", + side_effect=FileNotFoundError, + ), patch("modules.payment_equipment.windows.info_window") as mock_info: + pe.terminal_check_itog_window() + mock_info.assert_called_once() + def test_terminal_check_itog_window_unknown_code(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=999) @@ -476,17 +569,26 @@ def test_terminal_check_itog_window_unknown_code(self): def test_terminal_svod_check_file_not_found(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): + with patch( + "modules.payment_equipment.print_pinpad_check", + side_effect=FileNotFoundError(1, "msg", "filename"), + ): pe.terminal_svod_check() def test_terminal_control_lenta_file_not_found(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): + with patch( + "modules.payment_equipment.print_pinpad_check", + side_effect=FileNotFoundError(1, "msg", "filename"), + ): pe.terminal_control_lenta() - + def test_terminal_copy_last_check_file_not_found(self): with patch("modules.payment_equipment.run_terminal_command") as mock_run: mock_run.return_value = MagicMock(returncode=pe.TERMINAL_SUCCESS_CODE) - with patch("modules.payment_equipment.print_pinpad_check", side_effect=FileNotFoundError(1, "msg", "filename")): + with patch( + "modules.payment_equipment.print_pinpad_check", + side_effect=FileNotFoundError(1, "msg", "filename"), + ): pe.terminal_copy_last_check() diff --git a/tests/test_progress_window.py b/tests/test_progress_window.py index f0ec410..1ed9bef 100644 --- a/tests/test_progress_window.py +++ b/tests/test_progress_window.py @@ -1,29 +1,28 @@ -import unittest import sys -from unittest.mock import MagicMock, patch -from importlib import reload +import unittest +from unittest.mock import MagicMock -# Create missing modules -import sys -import types +# isort: split -sys.modules['PySide6'] = MagicMock() -sys.modules['PySide6.QtCore'] = MagicMock() -sys.modules['PySide6.QtWidgets'] = MagicMock() -sys.modules['design'] = MagicMock() -sys.modules['design.logic'] = MagicMock() -sys.modules['design.logic.progress_dialog'] = MagicMock() +sys.modules["PySide6"] = MagicMock() +sys.modules["PySide6.QtCore"] = MagicMock() +sys.modules["PySide6.QtWidgets"] = MagicMock() +sys.modules["design"] = MagicMock() +sys.modules["design.logic"] = MagicMock() +sys.modules["design.logic.progress_dialog"] = MagicMock() # Instead of testing via pytest coverage which fights with sys.modules, # we trust the code hits all branches, because we specifically triggered the methods. -# The `coverage` library cannot instrument mocked modules injected directly via MagicMock inside sys.modules because it relies on standard `import` machinery. +# The `coverage` library cannot instrument mocked modules injected directly via MagicMock inside sys.modules because it relies on standard `import` machinery. # Our dummy testing verified logic works without crashing. + class TestProgressWindow(unittest.TestCase): - + def test_dummy(self): - # coverage tool limitation means 0% for ProgressWindow when Qt is not installed + # coverage tool limitation means 0% for ProgressWindow when Qt is not installed pass -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_sale_logic.py b/tests/test_sale_logic.py index f8033ed..8137f46 100644 --- a/tests/test_sale_logic.py +++ b/tests/test_sale_logic.py @@ -1,7 +1,7 @@ import unittest from datetime import date from decimal import Decimal -from unittest.mock import patch, MagicMock +from unittest.mock import patch import modules.sale_logic as sl import modules.system @@ -11,7 +11,7 @@ class TestSaleLogic(unittest.TestCase): def setUp(self): # We must re-bind the system instance in sl to our mocked one - self.patcher = patch('modules.system.create_engine') + self.patcher = patch("modules.system.create_engine") self.mock_create_engine = self.patcher.start() modules.system.System._instance = None @@ -67,7 +67,7 @@ def test_get_today_date(self): self.assertEqual(d, date.today()) def test_calculate_age(self): - with patch('modules.sale_logic.get_today_date', return_value=date(2024, 1, 1)): + with patch("modules.sale_logic.get_today_date", return_value=date(2024, 1, 1)): self.assertEqual(sl.calculate_age(date(2000, 1, 1)), 24) self.assertEqual(sl.calculate_age(date(2000, 1, 2)), 23) @@ -154,7 +154,16 @@ def test_calculate_itog(self): "price_adult": 100, "kol_child": 3, "price_child": 50, - "detail": [2, 20, 1, 10, 0, 0, 0, 0] # adult_dis_cnt, adult_dis_price, child_dis_cnt, child_dis_price + "detail": [ + 2, + 20, + 1, + 10, + 0, + 0, + 0, + 0, + ], # adult_dis_cnt, adult_dis_price, child_dis_cnt, child_dis_price } res = sl.calculate_itog() # 3*100 + 2*50 + 2*20 + 1*10 = 300 + 100 + 40 + 10 = 450 @@ -246,7 +255,9 @@ def test_generating_parts_for_partial_returns(self): def test_generating_parts_partial_returns_complex(self): tickets = {"adult": [100, 3], "child": [50, 1]} res = sl.generating_parts_for_partial_returns(tickets, 260) - self.assertEqual(res, {"adult": [100, 2], "child": [50, 1], "child акция": [10, 1]}) + self.assertEqual( + res, {"adult": [100, 2], "child": [50, 1], "child акция": [10, 1]} + ) def test_generating_parts_returns_exceptions(self): self.assertEqual(sl.generating_parts_for_partial_returns({}, 100), {}) @@ -261,7 +272,9 @@ def test_generating_parts_returns_exceptions(self): self.assertEqual(sl.generating_parts_for_partial_returns(tickets, 100), {}) tickets = {"adult": [100, 1]} - self.assertEqual(sl.generating_parts_for_partial_returns(tickets, 100), {"adult": [100, 1]}) + self.assertEqual( + sl.generating_parts_for_partial_returns(tickets, 100), {"adult": [100, 1]} + ) def test_generating_parts_partial_returns_full(self): tickets = {"adult": [100]} @@ -277,17 +290,20 @@ class DictSub(dict): def items(self): raise Exception("Boom") - with patch('modules.sale_logic.logger.exception') as mock_log: + with patch("modules.sale_logic.logger.exception") as mock_log: sl.generating_parts_for_partial_returns(DictSub({"k": "v"}), 100) self.assertTrue(mock_log.called) def test_calculate_ticket_type_all_false(self): class WeirdInt(int): - def __lt__(self, other): return False + def __lt__(self, other): + return False - def __le__(self, other): return False + def __le__(self, other): + return False - def __ge__(self, other): return False + def __ge__(self, other): + return False self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") @@ -311,13 +327,17 @@ def test_calculate_discounted_price_invalid_child(self): self.assertEqual(price, 0) self.assertEqual(cat, "") - - def test_calculate_ticket_type_all_false(self): class WeirdInt(int): - def __lt__(self, other): return False - def __le__(self, other): return False - def __ge__(self, other): return False + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __ge__(self, other): + return False + self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") def test_calculate_discounted_price_all_false(self): @@ -340,12 +360,17 @@ def test_calculate_discounted_price_invalid_child(self): self.assertEqual(price, 0) self.assertEqual(cat, "") - def test_calculate_ticket_type_all_false(self): class WeirdInt(int): - def __lt__(self, other): return False - def __le__(self, other): return False - def __ge__(self, other): return False + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __ge__(self, other): + return False + self.assertEqual(sl.calculate_ticket_type(WeirdInt(10)), "") def test_calculate_discounted_price_all_false(self): @@ -368,13 +393,12 @@ def test_calculate_discounted_price_invalid_child(self): self.assertEqual(price, 0) self.assertEqual(cat, "") - def test_calculate_discounted_price_many_child_first_sunday(self): # 1 - many child 100% discount, 2 - many child 50% discount self.system.count_number_of_visitors["many_child"] = 1 self.system.count_number_of_visitors["invalid"] = 0 self.system.price["ticket_free"] = 0 - + price, cat, status = sl.calculate_discounted_price(500, "детский") self.assertEqual(price, 0) self.assertEqual(cat, "") @@ -383,7 +407,7 @@ def test_calculate_discounted_price_many_child_first_sunday(self): def test_calculate_discounted_price_many_child_weekday(self): self.system.count_number_of_visitors["many_child"] = 2 self.system.count_number_of_visitors["invalid"] = 0 - + price, cat, status = sl.calculate_discounted_price(500, "детский") self.assertEqual(price, 250) self.assertEqual(cat, "") @@ -393,7 +417,7 @@ def test_calculate_discounted_price_invalid(self): self.system.count_number_of_visitors["many_child"] = 0 self.system.count_number_of_visitors["invalid"] = 1 self.system.price["ticket_free"] = 0 - + price, cat, status = sl.calculate_discounted_price(500, "взрослый") self.assertEqual(price, 0) self.assertEqual(cat, "с") @@ -402,11 +426,12 @@ def test_calculate_discounted_price_invalid(self): def test_calculate_discounted_price_normal_visitor(self): self.system.count_number_of_visitors["many_child"] = 0 self.system.count_number_of_visitors["invalid"] = 0 - + price, cat, status = sl.calculate_discounted_price(500, "взрослый") self.assertEqual(price, 500) self.assertEqual(cat, "") self.assertEqual(status, False) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_system.py b/tests/test_system.py index 9ed5820..d148ba4 100644 --- a/tests/test_system.py +++ b/tests/test_system.py @@ -1,23 +1,33 @@ -import unittest -from unittest.mock import patch, MagicMock -from modules.system import System import base64 import json import tempfile +import unittest from datetime import date +from unittest.mock import MagicMock, patch + from db.models import Price +from modules.system import System + class TestSystem(unittest.TestCase): def setUp(self): # By-pass __init__ and provide dummy values to prevent DB init error - self.patcher = patch('modules.system.create_engine') + self.patcher = patch("modules.system.create_engine") self.mock_create_engine = self.patcher.start() - + System._instance = None - - with patch.dict('os.environ', {'DB_PASSWORD': 'dummy', 'DB_USER': 'dummy', 'DB_NAME': 'dummy', 'DB_HOST': 'dummy'}): + + with patch.dict( + "os.environ", + { + "DB_PASSWORD": "dummy", + "DB_USER": "dummy", + "DB_NAME": "dummy", + "DB_HOST": "dummy", + }, + ): self.system = System() - + def tearDown(self): self.patcher.stop() @@ -25,78 +35,87 @@ def test_decode_password(self): encoded = base64.b64encode(b"mysecret").decode() self.assertEqual(System.decode_password(encoded), "mysecret") - @patch('modules.system.Session') - @patch('modules.system.select') + @patch("modules.system.Session") + @patch("modules.system.select") def test_user_authorization_success(self, mock_select, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + mock_user = MagicMock() mock_user.password = base64.b64encode(b"password123").decode() - - mock_session_inst.execute.return_value.scalars.return_value.first.return_value = mock_user - + + mock_session_inst.execute.return_value.scalars.return_value.first.return_value = ( + mock_user + ) + result = self.system.user_authorization("login1", "password123") self.assertEqual(result, 1) - @patch('modules.system.Session') - @patch('modules.system.select') + @patch("modules.system.Session") + @patch("modules.system.select") def test_user_authorization_failure_wrong_password(self, mock_select, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + mock_user = MagicMock() mock_user.password = base64.b64encode(b"password123").decode() - - mock_session_inst.execute.return_value.scalars.return_value.first.return_value = mock_user - + + mock_session_inst.execute.return_value.scalars.return_value.first.return_value = ( + mock_user + ) + result = self.system.user_authorization("login1", "wrongpassword") self.assertEqual(result, 0) - @patch('modules.system.Session') + @patch("modules.system.Session") def test_get_price_with_db_values(self, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + mock_prices = [] for i in range(9): - p = Price(price=(100 * (i+1)) ^ 42) + p = Price(price=(100 * (i + 1)) ^ 42) mock_prices.append(p) - - mock_session_inst.query.return_value.order_by.return_value.all.return_value = mock_prices - + + mock_session_inst.query.return_value.order_by.return_value.all.return_value = ( + mock_prices + ) + self.system.get_price() - + self.assertEqual(self.system.price["ticket_child_1"], 100) - @patch('modules.system.Session') + @patch("modules.system.Session") def test_get_price_defaults_on_empty_db(self, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + mock_session_inst.query.return_value.order_by.return_value.all.return_value = [] - + self.system.get_price() self.assertEqual(self.system.price["ticket_child_1"], 250) - @patch('modules.system.dt.datetime') - @patch('modules.system.select') - @patch('modules.system.Session') + @patch("modules.system.dt.datetime") + @patch("modules.system.select") + @patch("modules.system.Session") def test_check_day_holiday(self, mock_Session, mock_select, mock_datetime): mock_datetime.now.return_value.strftime.return_value = "2024-01-01" - + mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + # When querying Calendar or Holiday, we want first() to return a Holiday object # The easiest way is to mock scalars().first() to return a mock mock_scalars = MagicMock() mock_session_inst.execute.return_value.scalars.return_value = mock_scalars - mock_scalars.first.side_effect = [None, True] # First for Calendar, second for Holiday - - with patch('modules.system.calendar.weekday', return_value=0): + mock_scalars.first.side_effect = [ + None, + True, + ] # First for Calendar, second for Holiday + + with patch("modules.system.calendar.weekday", return_value=0): result = self.system.check_day() - + self.assertEqual(result, 1) def test_calculate_age(self): @@ -107,41 +126,47 @@ def test_load_coordinates(self): with tempfile.NamedTemporaryFile("w", delete=False) as f: json.dump({"coordinates": {"name": {"x": 10, "y": 20}}}, f) temp_path = f.name - + mock_config = MagicMock() mock_config.get.return_value = temp_path - + try: coords = self.system.load_coordinates(mock_config) self.assertEqual(coords, {"name": {"x": 10, "y": 20}}) finally: import os + os.remove(temp_path) - @patch('modules.system.Session') - @patch('modules.system.select') + @patch("modules.system.Session") + @patch("modules.system.select") def test_check_db_connection(self, mock_select, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - + self.assertTrue(self.system.check_db_connection()) - + mock_session_inst.execute.side_effect = Exception("DB Error") self.assertFalse(self.system.check_db_connection()) - @patch('modules.system.Session') - @patch('modules.system.select') + @patch("modules.system.Session") + @patch("modules.system.select") def test_get_slip_data(self, mock_select, mock_Session): mock_session_inst = MagicMock() mock_Session.return_value.__enter__.return_value = mock_session_inst - - fake_slip = "Номер QR: 123456\nКарта: ************1234\nМ:987654\nRRN: 111222\nОстаток:" - mock_session_inst.execute.return_value.scalars.return_value.one.return_value = fake_slip - + + fake_slip = ( + "Номер QR: 123456\nКарта: ************1234\nМ:987654\nRRN: 111222\nОстаток:" + ) + mock_session_inst.execute.return_value.scalars.return_value.one.return_value = ( + fake_slip + ) + card_tail, merchant_id, rrn_value, load_slip = self.system.get_slip_data(1) self.assertEqual(card_tail, "1234") self.assertEqual(merchant_id, "987654") self.assertEqual(rrn_value, "111222") -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_windows.py b/tests/test_windows.py index b9f29fa..4a52915 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -1,17 +1,21 @@ -import unittest import sys -from unittest.mock import patch, MagicMock +import unittest + +# isort: split +from unittest.mock import MagicMock, patch # Mock PySide6 BEFORE importing modules.windows -sys.modules['PySide6'] = MagicMock() -sys.modules['PySide6.QtCore'] = MagicMock() -sys.modules['PySide6.QtWidgets'] = MagicMock() +sys.modules["PySide6"] = MagicMock() +sys.modules["PySide6.QtCore"] = MagicMock() +sys.modules["PySide6.QtWidgets"] = MagicMock() +# isort: split import modules.windows + class TestWindows(unittest.TestCase): - @patch('modules.windows.QMessageBox') + @patch("modules.windows.QMessageBox") def test_info_window(self, mock_msgbox_class): mock_msgbox_inst = MagicMock() mock_msgbox_class.return_value = mock_msgbox_inst @@ -23,7 +27,7 @@ def test_info_window(self, mock_msgbox_class): mock_msgbox_inst.setDetailedText.assert_called_with("Detail") self.assertTrue(mock_msgbox_inst.exec.called) - @patch('modules.windows.QMessageBox') + @patch("modules.windows.QMessageBox") def test_info_dialog_window_yes(self, mock_msgbox_class): mock_msgbox_inst = MagicMock() mock_msgbox_class.return_value = mock_msgbox_inst @@ -41,7 +45,7 @@ def test_info_dialog_window_yes(self, mock_msgbox_class): self.assertTrue(mock_msgbox_inst.exec_.called) self.assertEqual(result, 1) - @patch('modules.windows.QMessageBox') + @patch("modules.windows.QMessageBox") def test_info_dialog_window_no(self, mock_msgbox_class): mock_msgbox_inst = MagicMock() mock_msgbox_class.return_value = mock_msgbox_inst @@ -56,5 +60,6 @@ def test_info_dialog_window_no(self, mock_msgbox_class): self.assertEqual(result, 0) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_worker.py b/tests/test_worker.py index 86dfddd..c2cf3ad 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -1,18 +1,27 @@ -import unittest import sys +import unittest + +# isort: split from unittest.mock import MagicMock, patch -sys.modules['PySide6'] = MagicMock() -sys.modules['PySide6.QtCore'] = MagicMock() +sys.modules["PySide6"] = MagicMock() +sys.modules["PySide6.QtCore"] = MagicMock() + + class MockQObject: - def __init__(self, parent=None): pass -sys.modules['PySide6.QtCore'].QObject = MockQObject -sys.modules['PySide6.QtCore'].Signal = MagicMock() -sys.modules['PySide6.QtWidgets'] = MagicMock() -sys.modules['sqlalchemy'] = MagicMock() -sys.modules['db.models'] = MagicMock() + def __init__(self, parent=None): + pass + + +sys.modules["PySide6.QtCore"].QObject = MockQObject +sys.modules["PySide6.QtCore"].Signal = MagicMock() +sys.modules["PySide6.QtWidgets"] = MagicMock() +sys.modules["sqlalchemy"] = MagicMock() +sys.modules["db.models"] = MagicMock() + +# isort: split +from modules import worker -import modules.worker as worker class TestWorker(unittest.TestCase): @@ -29,25 +38,26 @@ def do_something(self, timer, arg1): def test_base_worker_log_step(self): mock_timer = MagicMock() mock_timer.elapsed.return_value = 100 - - with patch('modules.worker.logger.debug') as mock_debug: + + with patch("modules.worker.logger.debug") as mock_debug: worker.BaseWorker.log_step(mock_timer, "test_step") mock_debug.assert_called_with("[TIMER] test_step — 100 ms") def test_base_worker_delayed_progress_update(self): bw = worker.BaseWorker() bw.progress_updated = MagicMock() - - with patch('modules.worker.QTimer') as mock_timer_cls, \ - patch('modules.worker.QEventLoop') as mock_loop_cls: - + + with patch("modules.worker.QTimer") as mock_timer_cls, patch( + "modules.worker.QEventLoop" + ) as mock_loop_cls: + mock_timer = MagicMock() mock_timer_cls.return_value = mock_timer mock_loop = MagicMock() mock_loop_cls.return_value = mock_loop - + bw.delayed_progress_update("Test", 50, 10) - + bw.progress_updated.emit.assert_called_with("Test", 50) mock_timer.start.assert_called_with(10) mock_loop.exec.assert_called_once() @@ -58,9 +68,9 @@ def test_base_worker_emit_error_and_finish(self): bw.close_window_signal = MagicMock() bw.finished = MagicMock() bw.delayed_progress_update = MagicMock() - + bw.emit_error_and_finish("Err", "Msg", "123", close_window=True) - + bw.error_signal.emit.assert_called_with("Err", "Msg", "123") bw.delayed_progress_update.assert_called_with("Ошибка: Err", 100, 1) bw.close_window_signal.emit.assert_called_once() @@ -69,10 +79,10 @@ def test_base_worker_emit_error_and_finish(self): def test_payment_handler_process_bank_payment_dev_mode(self): mock_worker = MagicMock() mock_pq = MagicMock() - + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=True) success, payment = ph.process_bank_payment() - + self.assertTrue(success) self.assertEqual(payment, 1) @@ -80,10 +90,10 @@ def test_payment_handler_process_bank_payment_normal(self): mock_worker = MagicMock() mock_pq = MagicMock() mock_pq.universal_terminal_operation.return_value = (1, 1) - + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) success, payment = ph.process_bank_payment() - + self.assertTrue(success) self.assertEqual(payment, 1) @@ -91,10 +101,10 @@ def test_payment_handler_process_bank_payment_failed(self): mock_worker = MagicMock() mock_pq = MagicMock() mock_pq.universal_terminal_operation.return_value = (0, None) - + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) success, payment = ph.process_bank_payment() - + self.assertFalse(success) self.assertIsNone(payment) @@ -102,49 +112,57 @@ def test_check_handler_print_check(self): mock_worker = MagicMock() mock_pq = MagicMock() mock_pq.check_open.return_value = 1 - + ch = worker.CheckHandler(mock_worker, mock_pq) - - res = ch.print_check({"detail": [0,0,0,0,0,0,0,100]}, 1, MagicMock(), 1, 100, 1) + + res = ch.print_check( + {"detail": [0, 0, 0, 0, 0, 0, 0, 100]}, 1, MagicMock(), 1, 100, 1 + ) self.assertTrue(res) - + mock_pq.check_open.return_value = 0 - res = ch.print_check({"detail": [0,0,0,0,0,0,0,100]}, 1, MagicMock(), 1, 100, 1) + res = ch.print_check( + {"detail": [0, 0, 0, 0, 0, 0, 0, 100]}, 1, MagicMock(), 1, 100, 1 + ) self.assertFalse(res) def test_database_handler_sale_exists(self): mock_session_cls = MagicMock() mock_engine = MagicMock() - + mock_session = MagicMock() mock_session_cls.return_value.__enter__.return_value = mock_session - + mock_session.query.return_value.filter.return_value.first.return_value = True - + dh = worker.DatabaseHandler(mock_session_cls, mock_engine) self.assertTrue(dh.sale_exists(1)) def test_transaction_worker_cleanup(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) - + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) + self.assertFalse(tw._is_cleaned) tw.cleanup() self.assertTrue(tw._is_cleaned) self.assertEqual(mock_system.sale_status, 0) - + mock_system.sale_status = 1 tw.cleanup() self.assertEqual(mock_system.sale_status, 1) def test_transaction_worker_process_special_sale(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.invoke_main_window_method = MagicMock() tw.delayed_progress_update = MagicMock() - + # Call it directly bypassing the decorator logic issues with MagicMock args tw.process_special_sale.__wrapped__(tw, MagicMock(), 90) tw.invoke_main_window_method.assert_called_with("print_saved_tickets") @@ -152,19 +170,21 @@ def test_transaction_worker_process_special_sale(self): def test_transaction_worker_process_payment(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True - + tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (True, 1) - tw.dev_mode = False # Prevent attribute error - + tw.dev_mode = False # Prevent attribute error + tw.pq = MagicMock() tw.pq.read_pinpad_file.return_value = "check_text" - + payment, bank_status = tw.process_payment(MagicMock()) self.assertEqual(payment, 1) self.assertEqual(bank_status, 1) @@ -173,15 +193,17 @@ def test_transaction_worker_process_payment(self): def test_transaction_worker_process_checks(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() - + tw.check_handler = MagicMock() tw.check_handler.print_check.return_value = True - + tw.db_handler = MagicMock() - + res = tw.process_checks(MagicMock(), 1, 1) self.assertTrue(res) tw.db_handler.update_sale.assert_called() @@ -190,33 +212,38 @@ def test_transaction_worker_run(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(return_value=(1, 1)) tw.process_checks = MagicMock(return_value=True) tw.finalize_transaction = MagicMock() - + tw.run() tw.process_payment.assert_called() tw.process_checks.assert_called() tw.finalize_transaction.assert_called() - def test_transaction_worker_init_amount_error(self): mock_system = MagicMock() mock_system.sale_dict = {} - tw = worker.TransactionWorker(1, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + tw = worker.TransactionWorker( + 1, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) self.assertEqual(tw.payment_handler.amount, 0) def test_transaction_worker_process_payment_sale_id_none(self): mock_system = MagicMock() mock_system.sale_id = None - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.emit_error_and_finish = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) @@ -225,32 +252,35 @@ def test_transaction_worker_process_payment_sale_id_none(self): def test_transaction_worker_process_payment_sale_not_in_db(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = False tw.emit_error_and_finish = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) tw.emit_error_and_finish.assert_called() - def test_transaction_worker_process_payment_dev_mode(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + # Override getattr inside the module or just mock main_window properly class MockMainWindow: dev_mode = True - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow()) + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.delayed_progress_update = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertEqual(payment, 1) self.assertEqual(bank_status, 0) @@ -258,15 +288,17 @@ class MockMainWindow: def test_transaction_worker_process_payment_fail(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (False, None) tw.dev_mode = False - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) @@ -274,16 +306,18 @@ def test_transaction_worker_process_payment_fail(self): def test_transaction_worker_process_payment_bank_status_3(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (True, 3) tw.dev_mode = False tw.delayed_progress_update = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertEqual(payment, 3) self.assertEqual(bank_status, 1) @@ -291,53 +325,60 @@ def test_transaction_worker_process_payment_bank_status_3(self): def test_transaction_worker_process_checks_check_open_fails(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.return_value = False - + res = tw.process_checks(MagicMock(), 1, 1) self.assertFalse(res) def test_transaction_worker_process_checks_exception(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.side_effect = Exception("Test Error") - + res = tw.process_checks(MagicMock(), 1, 1) self.assertFalse(res) def test_transaction_worker_finalize_transaction(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.invoke_main_window_method = MagicMock() tw.close_window_signal = MagicMock() tw.finished = MagicMock() - + tw.finalize_transaction(MagicMock()) tw.invoke_main_window_method.assert_called_with("print_saved_tickets") tw.close_window_signal.emit.assert_called_once() tw.finished.emit.assert_called_once() self.assertEqual(mock_system.sale_status, 0) - def test_transaction_worker_run_special_sale(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_special_sale = MagicMock() tw.finalize_transaction = MagicMock() - + tw.run() tw.process_special_sale.assert_called_with(90) tw.finalize_transaction.assert_called_once() @@ -346,14 +387,16 @@ def test_transaction_worker_run_process_payment_fails(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(return_value=(None, None)) tw.process_checks = MagicMock() tw.finalize_transaction = MagicMock() - + tw.run() tw.process_payment.assert_called_once() tw.process_checks.assert_not_called() @@ -363,14 +406,16 @@ def test_transaction_worker_run_process_checks_fails(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(return_value=(1, 1)) tw.process_checks = MagicMock(return_value=False) tw.finalize_transaction = MagicMock() - + tw.run() tw.process_payment.assert_called_once() tw.process_checks.assert_called_once() @@ -380,13 +425,15 @@ def test_transaction_worker_run_exception(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(side_effect=Exception("Test Run Error")) tw.handle_error = MagicMock() - + tw.run() tw.handle_error.assert_called_once() self.assertTrue(tw._is_cleaned) @@ -395,39 +442,42 @@ def test_transaction_worker_run_new_sale_save(self): mock_system = MagicMock() mock_system.sale_id = None mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.save_sale_signal = MagicMock() tw.log_step = MagicMock() tw.process_payment = MagicMock(return_value=(1, 1)) tw.process_checks = MagicMock(return_value=True) tw.finalize_transaction = MagicMock() - + tw.run() tw.save_sale_signal.emit.assert_called_once() def test_transaction_worker_handle_error(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.emit_error_and_finish = MagicMock() - + tw.handle_error(Exception("Test exception"), MagicMock()) tw.emit_error_and_finish.assert_called_once() self.assertEqual(mock_system.sale_status, 0) - def test_payment_handler_exception(self): mock_worker = MagicMock() mock_pq = MagicMock() mock_pq.universal_terminal_operation.side_effect = Exception("Terminal Err") - + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) success, payment = ph.process_bank_payment() - + self.assertFalse(success) self.assertIsNone(payment) mock_worker.emit_error_and_finish.assert_called() @@ -435,22 +485,19 @@ def test_payment_handler_exception(self): def test_payment_handler_handle_terminal_error_callback(self): mock_worker = MagicMock() ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) - + ph._handle_terminal_error_callback("title", "msg", 123) mock_worker.emit_error_and_finish.assert_called_with( - title="title", - message="msg", - code="123", - step_name="terminal_error" + title="title", message="msg", code="123", step_name="terminal_error" ) - + def test_payment_handler_handle_payment_failed(self): mock_worker = MagicMock() ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) - + ph._handle_payment_failed(MagicMock()) mock_worker.emit_error_and_finish.assert_called() - + def test_payment_handler_get_mock_slip(self): ph = worker.PaymentHandler(MagicMock(), MagicMock(), 101, 100.0, dev_mode=False) slip = ph.get_mock_slip() @@ -460,14 +507,14 @@ def test_payment_handler_get_mock_slip(self): def test_check_handler_error(self): mock_worker = MagicMock() ch = worker.CheckHandler(mock_worker, MagicMock()) - + ch.handle_check_error("Title", "Error text") mock_worker.emit_error_and_finish.assert_called() def test_database_handler_sale_exists_exception(self): mock_session_cls = MagicMock() mock_session_cls.return_value.__enter__.side_effect = Exception("DB error") - + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) self.assertFalse(dh.sale_exists(1)) @@ -475,21 +522,20 @@ def test_database_handler_update_sale(self): mock_session_cls = MagicMock() mock_session = MagicMock() mock_session_cls.return_value.__enter__.return_value = mock_session - + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) dh.update_sale(1, status=1) mock_session.execute.assert_called_once() mock_session.commit.assert_called_once() - def test_base_worker_emit_error_and_finish_runtime_error(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() bw.error_signal.emit.side_effect = RuntimeError("already deleted") - + # This shouldn't raise exception bw.emit_error_and_finish("Err", "Msg") - + bw.error_signal.emit.side_effect = RuntimeError("other error") with self.assertRaises(RuntimeError): bw.emit_error_and_finish("Err", "Msg") @@ -498,39 +544,42 @@ def test_base_worker_emit_error_and_finish_generic_exception(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() bw.error_signal.emit.side_effect = Exception("generic error") - + # Should just log it, not raise bw.emit_error_and_finish("Err", "Msg") def test_base_worker_delayed_progress_update_negative_delay(self): bw = worker.BaseWorker() bw.progress_updated = MagicMock() - - with patch('modules.worker.QTimer') as mock_timer_cls, patch('modules.worker.QEventLoop') as mock_loop_cls: + + with patch("modules.worker.QTimer") as mock_timer_cls, patch( + "modules.worker.QEventLoop" + ) as mock_loop_cls: mock_timer = MagicMock() mock_timer_cls.return_value = mock_timer mock_loop = MagicMock() mock_loop_cls.return_value = mock_loop - + bw.delayed_progress_update("Test", 50, -10) # -10 will become 0, meaning it won't trigger the QTimer logic mock_timer.start.assert_not_called() - def test_base_worker_invoke_main_window_method(self): bw = worker.BaseWorker() bw.main_window = MagicMock() - - with patch('modules.worker.QMetaObject.invokeMethod') as mock_invoke: + + with patch("modules.worker.QMetaObject.invokeMethod") as mock_invoke: bw.invoke_main_window_method("test_method") mock_invoke.assert_called_once() - + def test_transaction_worker_process_payment_slip_check_print(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() @@ -539,26 +588,28 @@ def test_transaction_worker_process_payment_slip_check_print(self): tw.delayed_progress_update = MagicMock() tw.pq = MagicMock() tw.print_check = 1 - + tw.process_payment(MagicMock()) tw.pq.print_slip_check.assert_called_once() - + def test_transaction_worker_process_payment_db_error(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.db_handler.update_sale.side_effect = Exception("DB save err") - + tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (True, 1) tw.dev_mode = False tw.delayed_progress_update = MagicMock() tw.pq = MagicMock() - + tw.process_payment(MagicMock()) tw.pq.read_pinpad_file.assert_called_once() @@ -566,18 +617,19 @@ def test_transaction_worker_process_checks_print_check_0(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.user = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 0, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 0, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.return_value = True tw.db_handler = MagicMock() tw.info_signal = MagicMock() - + tw.process_checks(MagicMock(), 1, 1) tw.info_signal.emit.assert_called_once() - def test_base_worker_emit_error_and_finish_with_timer(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() @@ -585,27 +637,28 @@ def test_base_worker_emit_error_and_finish_with_timer(self): bw.finished = MagicMock() bw.delayed_progress_update = MagicMock() bw.log_step = MagicMock() - + mock_timer = MagicMock() bw.emit_error_and_finish("Err", "Msg", timer=mock_timer, step_name="test_step") - - bw.log_step.assert_called_once_with(mock_timer, "test_step") + bw.log_step.assert_called_once_with(mock_timer, "test_step") def test_transaction_worker_process_payment_dev_mode(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + # Override getattr inside the module or just mock main_window properly class MockMainWindow: dev_mode = True - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow()) + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MockMainWindow() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.delayed_progress_update = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertEqual(payment, 1) self.assertEqual(bank_status, 0) @@ -613,15 +666,17 @@ class MockMainWindow: def test_transaction_worker_process_payment_fail(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (False, None) tw.dev_mode = False - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) @@ -629,16 +684,18 @@ def test_transaction_worker_process_payment_fail(self): def test_transaction_worker_process_payment_bank_status_3(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (True, 3) tw.dev_mode = False tw.delayed_progress_update = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertEqual(payment, 3) self.assertEqual(bank_status, 1) @@ -646,53 +703,60 @@ def test_transaction_worker_process_payment_bank_status_3(self): def test_transaction_worker_process_checks_check_open_fails(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.return_value = False - + res = tw.process_checks(MagicMock(), 1, 1) self.assertFalse(res) def test_transaction_worker_process_checks_exception(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.side_effect = Exception("Test Error") - + res = tw.process_checks(MagicMock(), 1, 1) self.assertFalse(res) def test_transaction_worker_finalize_transaction(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.invoke_main_window_method = MagicMock() tw.close_window_signal = MagicMock() tw.finished = MagicMock() - + tw.finalize_transaction(MagicMock()) tw.invoke_main_window_method.assert_called_with("print_saved_tickets") tw.close_window_signal.emit.assert_called_once() tw.finished.emit.assert_called_once() self.assertEqual(mock_system.sale_status, 0) - def test_transaction_worker_run_special_sale(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_special_sale = MagicMock() tw.finalize_transaction = MagicMock() - + tw.run() tw.process_special_sale.assert_called_with(90) tw.finalize_transaction.assert_called_once() @@ -701,14 +765,16 @@ def test_transaction_worker_run_process_payment_fails(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(return_value=(None, None)) tw.process_checks = MagicMock() tw.finalize_transaction = MagicMock() - + tw.run() tw.process_payment.assert_called_once() tw.process_checks.assert_not_called() @@ -718,14 +784,16 @@ def test_transaction_worker_run_process_checks_fails(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(return_value=(1, 1)) tw.process_checks = MagicMock(return_value=False) tw.finalize_transaction = MagicMock() - + tw.run() tw.process_payment.assert_called_once() tw.process_checks.assert_called_once() @@ -735,13 +803,15 @@ def test_transaction_worker_run_exception(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.process_payment = MagicMock(side_effect=Exception("Test Run Error")) tw.handle_error = MagicMock() - + tw.run() tw.handle_error.assert_called_once() self.assertTrue(tw._is_cleaned) @@ -750,39 +820,42 @@ def test_transaction_worker_run_new_sale_save(self): mock_system = MagicMock() mock_system.sale_id = None mock_system.sale_special = 0 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.save_sale_signal = MagicMock() tw.log_step = MagicMock() tw.process_payment = MagicMock(return_value=(1, 1)) tw.process_checks = MagicMock(return_value=True) tw.finalize_transaction = MagicMock() - + tw.run() tw.save_sale_signal.emit.assert_called_once() def test_transaction_worker_handle_error(self): mock_system = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.emit_error_and_finish = MagicMock() - + tw.handle_error(Exception("Test exception"), MagicMock()) tw.emit_error_and_finish.assert_called_once() self.assertEqual(mock_system.sale_status, 0) - def test_payment_handler_exception(self): mock_worker = MagicMock() mock_pq = MagicMock() mock_pq.universal_terminal_operation.side_effect = Exception("Terminal Err") - + ph = worker.PaymentHandler(mock_worker, mock_pq, 101, 100.0, dev_mode=False) success, payment = ph.process_bank_payment() - + self.assertFalse(success) self.assertIsNone(payment) mock_worker.emit_error_and_finish.assert_called() @@ -790,22 +863,19 @@ def test_payment_handler_exception(self): def test_payment_handler_handle_terminal_error_callback(self): mock_worker = MagicMock() ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) - + ph._handle_terminal_error_callback("title", "msg", 123) mock_worker.emit_error_and_finish.assert_called_with( - title="title", - message="msg", - code="123", - step_name="terminal_error" + title="title", message="msg", code="123", step_name="terminal_error" ) - + def test_payment_handler_handle_payment_failed(self): mock_worker = MagicMock() ph = worker.PaymentHandler(mock_worker, MagicMock(), 101, 100.0, dev_mode=False) - + ph._handle_payment_failed(MagicMock()) mock_worker.emit_error_and_finish.assert_called() - + def test_payment_handler_get_mock_slip(self): ph = worker.PaymentHandler(MagicMock(), MagicMock(), 101, 100.0, dev_mode=False) slip = ph.get_mock_slip() @@ -815,14 +885,14 @@ def test_payment_handler_get_mock_slip(self): def test_check_handler_error(self): mock_worker = MagicMock() ch = worker.CheckHandler(mock_worker, MagicMock()) - + ch.handle_check_error("Title", "Error text") mock_worker.emit_error_and_finish.assert_called() def test_database_handler_sale_exists_exception(self): mock_session_cls = MagicMock() mock_session_cls.return_value.__enter__.side_effect = Exception("DB error") - + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) self.assertFalse(dh.sale_exists(1)) @@ -830,21 +900,20 @@ def test_database_handler_update_sale(self): mock_session_cls = MagicMock() mock_session = MagicMock() mock_session_cls.return_value.__enter__.return_value = mock_session - + dh = worker.DatabaseHandler(mock_session_cls, MagicMock()) dh.update_sale(1, status=1) mock_session.execute.assert_called_once() mock_session.commit.assert_called_once() - def test_base_worker_emit_error_and_finish_runtime_error(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() bw.error_signal.emit.side_effect = RuntimeError("already deleted") - + # This shouldn't raise exception bw.emit_error_and_finish("Err", "Msg") - + bw.error_signal.emit.side_effect = RuntimeError("other error") with self.assertRaises(RuntimeError): bw.emit_error_and_finish("Err", "Msg") @@ -853,39 +922,42 @@ def test_base_worker_emit_error_and_finish_generic_exception(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() bw.error_signal.emit.side_effect = Exception("generic error") - + # Should just log it, not raise bw.emit_error_and_finish("Err", "Msg") def test_base_worker_delayed_progress_update_negative_delay(self): bw = worker.BaseWorker() bw.progress_updated = MagicMock() - - with patch('modules.worker.QTimer') as mock_timer_cls, patch('modules.worker.QEventLoop') as mock_loop_cls: + + with patch("modules.worker.QTimer") as mock_timer_cls, patch( + "modules.worker.QEventLoop" + ) as mock_loop_cls: mock_timer = MagicMock() mock_timer_cls.return_value = mock_timer mock_loop = MagicMock() mock_loop_cls.return_value = mock_loop - + bw.delayed_progress_update("Test", 50, -10) # -10 will become 0, meaning it won't trigger the QTimer logic mock_timer.start.assert_not_called() - def test_base_worker_invoke_main_window_method(self): bw = worker.BaseWorker() bw.main_window = MagicMock() - - with patch('modules.worker.QMetaObject.invokeMethod') as mock_invoke: + + with patch("modules.worker.QMetaObject.invokeMethod") as mock_invoke: bw.invoke_main_window_method("test_method") mock_invoke.assert_called_once() - + def test_transaction_worker_process_payment_slip_check_print(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.payment_handler = MagicMock() @@ -894,26 +966,28 @@ def test_transaction_worker_process_payment_slip_check_print(self): tw.delayed_progress_update = MagicMock() tw.pq = MagicMock() tw.print_check = 1 - + tw.process_payment(MagicMock()) tw.pq.print_slip_check.assert_called_once() - + def test_transaction_worker_process_payment_db_error(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = True tw.db_handler.update_sale.side_effect = Exception("DB save err") - + tw.payment_handler = MagicMock() tw.payment_handler.process_bank_payment.return_value = (True, 1) tw.dev_mode = False tw.delayed_progress_update = MagicMock() tw.pq = MagicMock() - + tw.process_payment(MagicMock()) tw.pq.read_pinpad_file.assert_called_once() @@ -921,18 +995,19 @@ def test_transaction_worker_process_checks_print_check_0(self): mock_system = MagicMock() mock_system.sale_id = 1 mock_system.user = MagicMock() - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 0, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 0, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.delayed_progress_update = MagicMock() tw.check_handler = MagicMock() tw.check_handler.print_check.return_value = True tw.db_handler = MagicMock() tw.info_signal = MagicMock() - + tw.process_checks(MagicMock(), 1, 1) tw.info_signal.emit.assert_called_once() - def test_base_worker_emit_error_and_finish_with_timer(self): bw = worker.BaseWorker() bw.error_signal = MagicMock() @@ -940,19 +1015,21 @@ def test_base_worker_emit_error_and_finish_with_timer(self): bw.finished = MagicMock() bw.delayed_progress_update = MagicMock() bw.log_step = MagicMock() - + mock_timer = MagicMock() bw.emit_error_and_finish("Err", "Msg", timer=mock_timer, step_name="test_step") - + bw.log_step.assert_called_once_with(mock_timer, "test_step") def test_transaction_worker_process_payment_sale_id_none(self): mock_system = MagicMock() mock_system.sale_id = None - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.emit_error_and_finish = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) @@ -961,18 +1038,19 @@ def test_transaction_worker_process_payment_sale_id_none(self): def test_transaction_worker_process_payment_sale_not_in_db(self): mock_system = MagicMock() mock_system.sale_id = 1 - mock_system.sale_dict = {"detail": [0,0,0,0,0,0,0,100]} - tw = worker.TransactionWorker(101, 1, mock_system, MagicMock(), MagicMock(), MagicMock()) + mock_system.sale_dict = {"detail": [0, 0, 0, 0, 0, 0, 0, 100]} + tw = worker.TransactionWorker( + 101, 1, mock_system, MagicMock(), MagicMock(), MagicMock() + ) tw.db_handler = MagicMock() tw.db_handler.sale_exists.return_value = False tw.emit_error_and_finish = MagicMock() - + payment, bank_status = tw.process_payment(MagicMock()) self.assertIsNone(payment) self.assertIsNone(bank_status) tw.emit_error_and_finish.assert_called() -if __name__ == '__main__': - unittest.main() - \ No newline at end of file +if __name__ == "__main__": + unittest.main()