From c1d160dd04eea4cef365c530dd989fca3c7f37d2 Mon Sep 17 00:00:00 2001 From: MarcusStill <69536339+MarcusStill@users.noreply.github.com> Date: Thu, 10 Sep 2026 09:01:54 +0000 Subject: [PATCH] Refactor project to comply with PEP8 standards - Apply `isort` and `black` formatting to all Python files. - Add explicit type exports to `db/models/__init__.py` to fix unused import linting warnings. - Insert `# noqa: E402` and `# isort: split` in test files where mock imports must occur before module logic. - Suppress wildcard import linting errors (`F403`, `F405`) in autogenerated `libfptr10.py` with `# ruff: noqa`. - Ensure all logic remains functionally identical; only formatting and styling rules applied. --- db/models/__init__.py | 16 +- design/logic/main_form.py | 2 +- design/logic/pay.py | 2 +- design/logic/progress_dialog.py | 34 +- main.py | 296 +++++++---- modules/config.py | 5 +- modules/libfptr10.py | 884 +++++++++++++++++++------------- modules/otchet.py | 291 ++++++++--- modules/payment_equipment.py | 301 +++++++---- modules/progress_window.py | 3 +- modules/sale_logic.py | 36 +- modules/system.py | 35 +- modules/worker.py | 118 +++-- patch.tar.xz | Bin 0 -> 36980 bytes tests/test_auth_logic.py | 48 +- tests/test_config.py | 12 +- tests/test_logger.py | 21 +- tests/test_otchet.py | 112 ++-- tests/test_payment_equipment.py | 308 +++++++---- tests/test_progress_window.py | 30 +- tests/test_sale_logic.py | 75 ++- tests/test_system.py | 129 +++-- tests/test_windows.py | 23 +- tests/test_worker.py | 578 ++++++++++++--------- 24 files changed, 2099 insertions(+), 1260 deletions(-) create mode 100644 patch.tar.xz diff --git a/db/models/__init__.py b/db/models/__init__.py index 86fa1d2..1bc328d 100644 --- a/db/models/__init__.py +++ b/db/models/__init__.py @@ -1,8 +1,8 @@ -from .base import Base -from .client import Client -from .holiday import Holiday -from .price import Price -from .sale import Sale -from .ticket import Ticket -from .user import User -from .workday import Workday +from .base import Base as Base +from .client import Client as Client +from .holiday import Holiday as Holiday +from .price import Price as Price +from .sale import Sale as Sale +from .ticket import Ticket as Ticket +from .user import User as User +from .workday import Workday as Workday diff --git a/design/logic/main_form.py b/design/logic/main_form.py index b3bfd74..3a3a316 100644 --- a/design/logic/main_form.py +++ b/design/logic/main_form.py @@ -15,9 +15,9 @@ QPushButton, QRadioButton, QStatusBar, - QTabWidget, QTableWidget, QTableWidgetItem, + QTabWidget, QWidget, ) diff --git a/design/logic/pay.py b/design/logic/pay.py index 3926a8f..8a842b7 100644 --- a/design/logic/pay.py +++ b/design/logic/pay.py @@ -1,6 +1,6 @@ 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): diff --git a/design/logic/progress_dialog.py b/design/logic/progress_dialog.py index e7b5b3e..1da4896 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): 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/main.py b/main.py index c7f54d0..f1571ea 100644 --- a/main.py +++ b/main.py @@ -7,22 +7,20 @@ from typing import Any, Optional, Type 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 @@ -36,14 +34,14 @@ 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 +65,7 @@ def handle_uncaught_exception(exc_type, exc_value, exc_traceback): "Неперехваченное исключение" ) + sys.excepthook = handle_uncaught_exception @@ -101,7 +100,7 @@ def starting_the_main_form(self) -> None: windows.info_window( "База данных недоступна", "Не удалось подключиться к серверу базы данных.\n Приложение будет закрыто.", - "" + "", ) sys.exit(1) login: str = self.ui.lineEdit.text() @@ -134,8 +133,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 +170,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 +233,6 @@ def show_main_window(): window.exec_() - class ClientForm(QDialog): """Форма с данными клиента""" @@ -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) @@ -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 + system.sale_dict["detail"][4] = ( + system.sale_discount if system.sale_discount else 0 + ) logger.debug(f"Установлен флаг исключения: 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, @@ -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 | 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() @@ -2435,7 +2493,9 @@ 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)}") @@ -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) @@ -3159,16 +3221,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 +3390,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 +3465,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 +3513,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 +3588,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 +3611,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..7a27319 100644 --- a/modules/config.py +++ b/modules/config.py @@ -2,9 +2,9 @@ import os from configparser import ( ConfigParser, - NoSectionError, - NoOptionError, MissingSectionHeaderError, + NoOptionError, + NoSectionError, ) from typing import List, Optional @@ -122,7 +122,6 @@ def load_config(self): ) config_data[key] = config.get(section, key) - return config_data except (NoSectionError, NoOptionError) as e: logger.error(f"Ошибка в конфигурационном файле: {e}") diff --git a/modules/libfptr10.py b/modules/libfptr10.py index ced442d..1d6a5dd 100644 --- a/modules/libfptr10.py +++ b/modules/libfptr10.py @@ -1,21 +1,22 @@ # -*- coding: utf-8 -*- +# ruff: noqa: F403, F405 # AUTO GENERATED FILE import ctypes -import sys -import json import datetime +import json import os import platform +import sys import warnings 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 @@ -1672,231 +1673,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 +2021,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,75 +2037,147 @@ 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)) 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))) 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)) 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))) 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)) 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))) @@ -2059,13 +2195,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 +2218,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 +2247,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..f1482a7 100644 --- a/modules/otchet.py +++ b/modules/otchet.py @@ -3,7 +3,7 @@ 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,6 +45,7 @@ def get_ticket_type(age:str) -> str: else: return "взрослый" + 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,22 +247,56 @@ 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: """ Функция формирует отчет администратора в формате 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,6 +436,7 @@ 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: """ Формирует 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_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], + ] +]: """ Обрабатывает продажи и возвраты, агрегируя информацию по каждому кассовому аппарату. Функция вычисляет суммы по картам и наличным, а также учитывает возвраты по каждому типу: @@ -462,16 +591,23 @@ 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] @@ -501,33 +637,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 +698,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..fb39301 100644 --- a/modules/payment_equipment.py +++ b/modules/payment_equipment.py @@ -14,10 +14,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: @@ -164,7 +166,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 @@ -211,18 +213,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 +304,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 +352,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: ( "Терминал потерял связь с банком", @@ -378,7 +387,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Карта клиента заблокирована", f"Карта клиента заблокирована. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_CARD_LIMIT: @@ -386,7 +395,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Превышен лимит операций", "Превышен лимит операций. Попробуйте произвести оплату другой картой или обратитесь в банк для выяснения причины.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_BIOMETRIC_ERROR: @@ -394,7 +403,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 +411,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 +419,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 +427,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 +435,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 +443,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Требуется сделать сверку итогов", "Требуется сделать сверку итогов и повторить операцию.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_KLK: @@ -442,7 +451,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 +459,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,7 +467,7 @@ 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: @@ -466,7 +475,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Недостаточно средств на карте", f"Недостаточно средств на карте. Попробуйте произвести оплату другой картой.", - error_callback + error_callback, ) return 0 if returncode in TERMINAL_COMMAND_ERROR: @@ -474,7 +483,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 +491,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Проблема в работе ПИН-пада.", f"Необходимо обратиться в службу поддержки банка. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 # Общие ошибки @@ -499,7 +508,7 @@ def process_terminal_error(returncode, error_callback=None): returncode, "Ошибка терминала", f"Возвращен неизвестный код ошибки. Обратитесь в службу поддержки. Телефон тех.поддержки {TERMINAL_SUPPORT}. Код возврата: {returncode}.", - error_callback + error_callback, ) return 0 @@ -519,7 +528,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: """ Обрабатывает операцию на банковском терминале. @@ -544,7 +555,7 @@ def process_terminal_transaction(command_code: str, amount: float, operation_nam f"Нет ответа от терминала", f"{operation_name} не была выполнена. Проверьте устройство.", "Код ошибки: отсутствует", - error_callback=error_callback + error_callback=error_callback, ) return 0 @@ -567,11 +578,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 +633,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 +652,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: @@ -658,7 +676,9 @@ def universal_terminal_operation( logger.error(f"Неизвестная ошибка при проведении операции: {exp}") if progress_signal is not None: try: - progress_signal.emit(f"Неизвестная ошибка при проведении операции: {str(exp)}", 100) + progress_signal.emit( + f"Неизвестная ошибка при проведении операции: {str(exp)}", 100 + ) except RuntimeError: pass return 0, 0 @@ -979,6 +999,7 @@ def print_pinpad_check(count: int = 2): count -= 1 + def get_info(hide: bool = False) -> Optional[int]: """ Запрос информации о ККТ. @@ -1014,13 +1035,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 +1074,7 @@ def is_kkt_connected() -> bool: logger.error(f"Ошибка при проверке подключения ККТ: {e}") return False + @logger_wraps() def get_last_document(day: int = 7) -> None: """Запрос информации о последнем чеке в ФН и проверка даты. @@ -1066,7 +1095,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 +1105,9 @@ def get_last_document(day: int = 7) -> None: "Ошибка", "Произошла неизвестная ошибка.", "Попробуйте снова." ) + def get_last_document_datetime() -> datetime: - """ Получает дату и время последнего зарегистрированного чека из фискального накопителя. + """Получает дату и время последнего зарегистрированного чека из фискального накопителя. Возвращаемое значение: datetime: Дата и время последнего документа в фискальном накопителе. @@ -1085,12 +1117,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 +1145,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 +1441,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 +1631,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 +1680,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 +1696,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 +1745,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, @@ -1726,9 +1795,11 @@ def register_tickets(device, sale_dict, type_operation): # Для других типов операций logger.error(f"Другие типы операций.") 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: # Обработка других типов данных @@ -1748,11 +1819,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 +1838,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 +1852,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 +1928,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 +1958,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 +1979,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 +2003,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 +2051,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 +2069,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 @@ -1985,6 +2103,7 @@ def check_open(sale_dict, payment_type, user, type_operation, print_check, bank_ on_error("Ошибка ККТ", f"Критическая ошибка: {str(e)}") 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..4cdd7c2 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 @@ -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} не найден") @@ -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..a1641aa 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,7 +46,9 @@ 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): + def delayed_progress_update( + self, step_text: str, progress_percent: int, delay_ms: int = DEFAULT_DELAY_MS + ): if delay_ms < 0: delay_ms = 0 if delay_ms > 0: @@ -58,9 +71,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: Заголовок ошибки @@ -90,8 +109,10 @@ def emit_error_and_finish(self, title: str, message: str, code: str = "", except Exception as e: 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 +128,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 +139,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 +157,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 +180,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 +190,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 +210,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 +240,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 +250,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 +268,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 +294,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 +312,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 +354,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 +373,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 +384,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 +434,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.tar.xz b/patch.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..1eb8ff162a827fb46f41878e6786e787ab49dbce GIT binary patch literal 36980 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 literal 0 HcmV?d00001 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..f8aa0cd 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 MagicMock, 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..82ca008 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 +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 +import modules.otchet as otchet # noqa: E402 from modules.otchet import ( - get_ticket_type, + format_date_range, # noqa: E402 generate_ticket_report_table, - safe_int, - format_date_range, + 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..b581d87 100644 --- a/tests/test_payment_equipment.py +++ b/tests/test_payment_equipment.py @@ -1,13 +1,17 @@ +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() +sys.modules["PySide6"] = MagicMock() +sys.modules["PySide6.QtCore"] = MagicMock() +sys.modules["PySide6.QtWidgets"] = MagicMock() + +# isort: split +import types # noqa: E402 -import types fake_libfptr10 = types.ModuleType("modules.libfptr10") + + class FakeIFptr: LIBFPTR_PARAM_TEXT_WRAP = 1 LIBFPTR_TW_WORDS = 1 @@ -57,13 +61,17 @@ 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 # noqa: E402 -import modules.payment_equipment as pe class TestPE(unittest.TestCase): def setUp(self): @@ -74,7 +82,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 +99,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 +119,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 +137,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 +162,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 +172,37 @@ 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: + 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() - + 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 +211,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 +225,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 +240,67 @@ 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.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) 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 +312,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 +334,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 +365,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 +384,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 +395,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 +420,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 +528,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 +551,20 @@ 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: + 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() - + 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 +575,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..22a0ade 100644 --- a/tests/test_progress_window.py +++ b/tests/test_progress_window.py @@ -1,29 +1,29 @@ -import unittest -import sys -from unittest.mock import MagicMock, patch -from importlib import reload - # Create missing modules import sys import types +import unittest +from importlib import reload +from unittest.mock import MagicMock, patch -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..f1ee28f 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 MagicMock, 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..cad3476 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -1,17 +1,19 @@ -import unittest import sys -from unittest.mock import patch, MagicMock +import unittest +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 # noqa: E402 -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 +25,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 +43,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 +58,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..a5f97f2 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -1,18 +1,25 @@ -import unittest import sys +import unittest 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 +import modules.worker as worker # noqa: E402 -import modules.worker as worker class TestWorker(unittest.TestCase): @@ -29,25 +36,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 +66,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 +77,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 +88,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 +99,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 +110,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 +168,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 +191,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 +210,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 +250,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 +286,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 +304,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 +323,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 +385,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 +404,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 +423,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 +440,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 +483,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 +505,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 +520,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 +542,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 +586,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 +615,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 +635,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 +664,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 +682,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 +701,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 +763,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 +782,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 +801,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 +818,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 +861,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 +883,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 +898,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 +920,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 +964,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 +993,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 +1013,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 +1036,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()