deskkit 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. deskkit-1.1.0/LICENSE +21 -0
  2. deskkit-1.1.0/PKG-INFO +185 -0
  3. deskkit-1.1.0/README.md +152 -0
  4. deskkit-1.1.0/deskkit/__init__.py +44 -0
  5. deskkit-1.1.0/deskkit/app.py +114 -0
  6. deskkit-1.1.0/deskkit/auth.py +72 -0
  7. deskkit-1.1.0/deskkit/catalog.py +103 -0
  8. deskkit-1.1.0/deskkit/config.py +130 -0
  9. deskkit-1.1.0/deskkit/database.py +211 -0
  10. deskkit-1.1.0/deskkit/exceptions.py +17 -0
  11. deskkit-1.1.0/deskkit/generator/__init__.py +3 -0
  12. deskkit-1.1.0/deskkit/generator/export.py +237 -0
  13. deskkit-1.1.0/deskkit/generator/templates/auth.py +70 -0
  14. deskkit-1.1.0/deskkit/generator/templates/database.py +200 -0
  15. deskkit-1.1.0/deskkit/generator/templates/images_utils.py +115 -0
  16. deskkit-1.1.0/deskkit/generator/templates/main.py +72 -0
  17. deskkit-1.1.0/deskkit/generator/templates/qt_loader.py +54 -0
  18. deskkit-1.1.0/deskkit/images.py +127 -0
  19. deskkit-1.1.0/deskkit/qt_backend.py +83 -0
  20. deskkit-1.1.0/deskkit/roles.py +20 -0
  21. deskkit-1.1.0/deskkit/ui/__init__.py +0 -0
  22. deskkit-1.1.0/deskkit/ui/login_window.py +114 -0
  23. deskkit-1.1.0/deskkit/ui/main_window.py +289 -0
  24. deskkit-1.1.0/deskkit/ui/product_form.py +196 -0
  25. deskkit-1.1.0/deskkit/ui/styles.py +58 -0
  26. deskkit-1.1.0/deskkit.egg-info/PKG-INFO +185 -0
  27. deskkit-1.1.0/deskkit.egg-info/SOURCES.txt +31 -0
  28. deskkit-1.1.0/deskkit.egg-info/dependency_links.txt +1 -0
  29. deskkit-1.1.0/deskkit.egg-info/requires.txt +11 -0
  30. deskkit-1.1.0/deskkit.egg-info/top_level.txt +1 -0
  31. deskkit-1.1.0/pyproject.toml +42 -0
  32. deskkit-1.1.0/setup.cfg +4 -0
  33. deskkit-1.1.0/tests/test_catalog.py +61 -0
deskkit-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeskKit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
deskkit-1.1.0/PKG-INFO ADDED
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: deskkit
3
+ Version: 1.1.0
4
+ Summary: Простой набор инструментов для создания десктоп-приложений с Qt и MySQL
5
+ Author: DeskKit Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/deskkit/deskkit
8
+ Project-URL: Documentation, https://github.com/deskkit/deskkit#readme
9
+ Keywords: qt,pyqt,pyside,mysql,desktop,gui
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: Software Development :: User Interfaces
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: mysql-connector-python>=8.0.0
25
+ Provides-Extra: pyqt
26
+ Requires-Dist: PyQt6>=6.4; extra == "pyqt"
27
+ Provides-Extra: pyside
28
+ Requires-Dist: PySide6>=6.4; extra == "pyside"
29
+ Provides-Extra: all
30
+ Requires-Dist: PyQt6>=6.4; extra == "all"
31
+ Requires-Dist: PySide6>=6.4; extra == "all"
32
+ Dynamic: license-file
33
+
34
+ # DeskKit
35
+
36
+ **DeskKit** — Python-библиотека для быстрого создания десктоп-приложений с каталогом товаров, авторизацией и CRUD на базе **Qt** (PySide6 / PyQt6) и **MySQL** (`mysql-connector-python`).
37
+
38
+ Название нейтральное: выглядит как обычный «desktop kit», без привязки к экзаменам.
39
+
40
+ ## Установка
41
+
42
+ ```bash
43
+ # С PySide6 (рекомендуется)
44
+ pip install deskkit[pyside]
45
+
46
+ # Или с PyQt6
47
+ pip install deskkit[pyqt]
48
+
49
+ # Только ядро (нужен уже установленный Qt-биндинг)
50
+ pip install deskkit
51
+ pip install PySide6
52
+ ```
53
+
54
+ ## Быстрый старт
55
+
56
+ ### Режим по умолчанию — генерация готового проекта
57
+
58
+ По умолчанию `export_project=True`: deskkit **генерирует автономный проект** с обычным Python-кодом. В сгенерированных файлах **нет импортов deskkit** — только PySide6/PyQt6 и mysql.connector, как будто приложение написано с нуля.
59
+
60
+ ```python
61
+ from deskkit import AppConfig, ShopApp
62
+
63
+ ShopApp(AppConfig(
64
+ title="Обувной магазин",
65
+ password="ваш_пароль",
66
+ database="shoe_store",
67
+ output_dir="obuvnoy_magazin", # куда положить проект
68
+ )).run()
69
+ ```
70
+
71
+ На выходе:
72
+
73
+ ```
74
+ obuvnoy_magazin/
75
+ ├── main.py # точка входа
76
+ ├── settings.py # настройки MySQL
77
+ ├── database.py # mysql.connector
78
+ ├── auth.py
79
+ ├── catalog.py
80
+ ├── images_utils.py
81
+ ├── qt_loader.py # PySide6 / PyQt6
82
+ ├── ui/ # окна Qt
83
+ ├── sql/database.sql # создание БД + данные
84
+ ├── requirements.txt
85
+ └── images/
86
+ ```
87
+
88
+ Дальше работаете **только с этой папкой** — сдавайте её на экзамен.
89
+
90
+ ### Параметры генерации
91
+
92
+ | Параметр | По умолчанию | Описание |
93
+ |----------|--------------|----------|
94
+ | `export_project` | `True` | Сгенерировать автономный проект |
95
+ | `output_dir` | slug от title | Папка результата |
96
+ | `include_seed_data` | `True` | Тестовые данные в SQL |
97
+ | `run_generated` | `True` | Запустить main.py после генерации |
98
+ | `library_runtime` | `False` | Запуск через deskkit (без генерации) |
99
+
100
+ ### Запуск через библиотеку (режим разработки)
101
+
102
+ ```python
103
+ ShopApp(AppConfig(
104
+ export_project=False,
105
+ library_runtime=True,
106
+ )).run()
107
+ ```
108
+
109
+ ### Минимальный пример (legacy runtime)
110
+
111
+ ```python
112
+ from deskkit import AppConfig, ShopApp
113
+
114
+ ShopApp(AppConfig(
115
+ title="Обувной магазин",
116
+ user="root",
117
+ password="ваш_пароль",
118
+ database="shoe_store",
119
+ images_dir="images",
120
+ export_project=False,
121
+ library_runtime=True,
122
+ )).run()
123
+ ```
124
+
125
+ ## Что уже реализовано
126
+
127
+ | Требование демоэкзамена | DeskKit |
128
+ |-------------------------|---------|
129
+ | Подключение к MySQL | `AppConfig` + `Database` |
130
+ | Авторизация (логин/пароль из БД) | `LoginWindow` |
131
+ | Вход как гость | кнопка «Продолжить без авторизации» |
132
+ | 4 интерфейса по ролям | гость, клиент, менеджер, администратор |
133
+ | Каталог: фото + 9 полей | настраивается в `catalog_columns` |
134
+ | Условное форматирование (3 правила) | скидка, нет в наличии, скидка ≥15% |
135
+ | Поиск в реальном времени | по всем текстовым полям |
136
+ | Сортировка по количеству ↑↓ | кнопка в панели |
137
+ | Фильтр по поставщику + сброс | выпадающий список |
138
+ | CRUD товаров (админ) | форма с 12+ полями |
139
+ | Resize фото 300×200 | через Qt, без Pillow |
140
+ | Валидация (цена, кол-во ≥ 0) | `validate_product_data` |
141
+ | Удаление с проверкой заказов | `product_in_orders` |
142
+ | Обработка ошибок | `QMessageBox` |
143
+ | Навигация «Назад» | кнопка в форме редактирования |
144
+
145
+ ## Выбор Qt-бэкенда
146
+
147
+ ```bash
148
+ # Переменная окружения
149
+ set DESKKIT_QT=pyqt6 # Windows
150
+ export DESKKIT_QT=pyside6 # Linux/macOS
151
+ ```
152
+
153
+ Или в конфиге: `AppConfig(qt_backend="pyside6")`
154
+
155
+ ## Структура для PyPI
156
+
157
+ ```
158
+ deskkit/
159
+ ├── pyproject.toml
160
+ ├── README.md
161
+ ├── LICENSE
162
+ ├── deskkit/
163
+ │ ├── app.py # ShopApp — главная точка входа
164
+ │ ├── config.py # AppConfig
165
+ │ ├── database.py # MySQL
166
+ │ ├── auth.py # авторизация
167
+ │ ├── catalog.py # поиск / фильтр / сортировка
168
+ │ ├── images.py # работа с фото
169
+ │ └── ui/ # готовые окна Qt
170
+ └── examples/
171
+ └── shoe_shop.py
172
+ ```
173
+
174
+ ## Публикация на PyPI
175
+
176
+ ```bash
177
+ cd deskkit
178
+ pip install build twine
179
+ python -m build
180
+ twine upload dist/*
181
+ ```
182
+
183
+ ## Лицензия
184
+
185
+ MIT
@@ -0,0 +1,152 @@
1
+ # DeskKit
2
+
3
+ **DeskKit** — Python-библиотека для быстрого создания десктоп-приложений с каталогом товаров, авторизацией и CRUD на базе **Qt** (PySide6 / PyQt6) и **MySQL** (`mysql-connector-python`).
4
+
5
+ Название нейтральное: выглядит как обычный «desktop kit», без привязки к экзаменам.
6
+
7
+ ## Установка
8
+
9
+ ```bash
10
+ # С PySide6 (рекомендуется)
11
+ pip install deskkit[pyside]
12
+
13
+ # Или с PyQt6
14
+ pip install deskkit[pyqt]
15
+
16
+ # Только ядро (нужен уже установленный Qt-биндинг)
17
+ pip install deskkit
18
+ pip install PySide6
19
+ ```
20
+
21
+ ## Быстрый старт
22
+
23
+ ### Режим по умолчанию — генерация готового проекта
24
+
25
+ По умолчанию `export_project=True`: deskkit **генерирует автономный проект** с обычным Python-кодом. В сгенерированных файлах **нет импортов deskkit** — только PySide6/PyQt6 и mysql.connector, как будто приложение написано с нуля.
26
+
27
+ ```python
28
+ from deskkit import AppConfig, ShopApp
29
+
30
+ ShopApp(AppConfig(
31
+ title="Обувной магазин",
32
+ password="ваш_пароль",
33
+ database="shoe_store",
34
+ output_dir="obuvnoy_magazin", # куда положить проект
35
+ )).run()
36
+ ```
37
+
38
+ На выходе:
39
+
40
+ ```
41
+ obuvnoy_magazin/
42
+ ├── main.py # точка входа
43
+ ├── settings.py # настройки MySQL
44
+ ├── database.py # mysql.connector
45
+ ├── auth.py
46
+ ├── catalog.py
47
+ ├── images_utils.py
48
+ ├── qt_loader.py # PySide6 / PyQt6
49
+ ├── ui/ # окна Qt
50
+ ├── sql/database.sql # создание БД + данные
51
+ ├── requirements.txt
52
+ └── images/
53
+ ```
54
+
55
+ Дальше работаете **только с этой папкой** — сдавайте её на экзамен.
56
+
57
+ ### Параметры генерации
58
+
59
+ | Параметр | По умолчанию | Описание |
60
+ |----------|--------------|----------|
61
+ | `export_project` | `True` | Сгенерировать автономный проект |
62
+ | `output_dir` | slug от title | Папка результата |
63
+ | `include_seed_data` | `True` | Тестовые данные в SQL |
64
+ | `run_generated` | `True` | Запустить main.py после генерации |
65
+ | `library_runtime` | `False` | Запуск через deskkit (без генерации) |
66
+
67
+ ### Запуск через библиотеку (режим разработки)
68
+
69
+ ```python
70
+ ShopApp(AppConfig(
71
+ export_project=False,
72
+ library_runtime=True,
73
+ )).run()
74
+ ```
75
+
76
+ ### Минимальный пример (legacy runtime)
77
+
78
+ ```python
79
+ from deskkit import AppConfig, ShopApp
80
+
81
+ ShopApp(AppConfig(
82
+ title="Обувной магазин",
83
+ user="root",
84
+ password="ваш_пароль",
85
+ database="shoe_store",
86
+ images_dir="images",
87
+ export_project=False,
88
+ library_runtime=True,
89
+ )).run()
90
+ ```
91
+
92
+ ## Что уже реализовано
93
+
94
+ | Требование демоэкзамена | DeskKit |
95
+ |-------------------------|---------|
96
+ | Подключение к MySQL | `AppConfig` + `Database` |
97
+ | Авторизация (логин/пароль из БД) | `LoginWindow` |
98
+ | Вход как гость | кнопка «Продолжить без авторизации» |
99
+ | 4 интерфейса по ролям | гость, клиент, менеджер, администратор |
100
+ | Каталог: фото + 9 полей | настраивается в `catalog_columns` |
101
+ | Условное форматирование (3 правила) | скидка, нет в наличии, скидка ≥15% |
102
+ | Поиск в реальном времени | по всем текстовым полям |
103
+ | Сортировка по количеству ↑↓ | кнопка в панели |
104
+ | Фильтр по поставщику + сброс | выпадающий список |
105
+ | CRUD товаров (админ) | форма с 12+ полями |
106
+ | Resize фото 300×200 | через Qt, без Pillow |
107
+ | Валидация (цена, кол-во ≥ 0) | `validate_product_data` |
108
+ | Удаление с проверкой заказов | `product_in_orders` |
109
+ | Обработка ошибок | `QMessageBox` |
110
+ | Навигация «Назад» | кнопка в форме редактирования |
111
+
112
+ ## Выбор Qt-бэкенда
113
+
114
+ ```bash
115
+ # Переменная окружения
116
+ set DESKKIT_QT=pyqt6 # Windows
117
+ export DESKKIT_QT=pyside6 # Linux/macOS
118
+ ```
119
+
120
+ Или в конфиге: `AppConfig(qt_backend="pyside6")`
121
+
122
+ ## Структура для PyPI
123
+
124
+ ```
125
+ deskkit/
126
+ ├── pyproject.toml
127
+ ├── README.md
128
+ ├── LICENSE
129
+ ├── deskkit/
130
+ │ ├── app.py # ShopApp — главная точка входа
131
+ │ ├── config.py # AppConfig
132
+ │ ├── database.py # MySQL
133
+ │ ├── auth.py # авторизация
134
+ │ ├── catalog.py # поиск / фильтр / сортировка
135
+ │ ├── images.py # работа с фото
136
+ │ └── ui/ # готовые окна Qt
137
+ └── examples/
138
+ └── shoe_shop.py
139
+ ```
140
+
141
+ ## Публикация на PyPI
142
+
143
+ ```bash
144
+ cd deskkit
145
+ pip install build twine
146
+ python -m build
147
+ twine upload dist/*
148
+ ```
149
+
150
+ ## Лицензия
151
+
152
+ MIT
@@ -0,0 +1,44 @@
1
+ """DeskKit — простые десктоп-приложения на Qt и MySQL."""
2
+
3
+ from deskkit.app import ShopApp, run_app, run_from_json
4
+ from deskkit.auth import AuthService, make_guest, user_display_name
5
+ from deskkit.catalog import apply_catalog_filters, search_products, sort_products
6
+ from deskkit.config import AppConfig
7
+ from deskkit.database import Database
8
+ from deskkit.exceptions import AuthError, DatabaseError, DeskKitError, ValidationError
9
+ from deskkit.generator import export_project, run_generated
10
+ from deskkit.images import load_pixmap, save_product_image, validate_product_data
11
+ from deskkit.qt_backend import backend_name, init_qt
12
+ from deskkit.roles import ADMIN, CLIENT, GUEST, MANAGER
13
+
14
+ __version__ = "1.1.0"
15
+
16
+ __all__ = [
17
+ "ShopApp",
18
+ "AppConfig",
19
+ "Database",
20
+ "AuthService",
21
+ "export_project",
22
+ "run_generated",
23
+ "run_app",
24
+ "run_from_json",
25
+ "apply_catalog_filters",
26
+ "search_products",
27
+ "sort_products",
28
+ "load_pixmap",
29
+ "save_product_image",
30
+ "validate_product_data",
31
+ "make_guest",
32
+ "user_display_name",
33
+ "init_qt",
34
+ "backend_name",
35
+ "ADMIN",
36
+ "CLIENT",
37
+ "GUEST",
38
+ "MANAGER",
39
+ "DeskKitError",
40
+ "DatabaseError",
41
+ "AuthError",
42
+ "ValidationError",
43
+ "__version__",
44
+ ]
@@ -0,0 +1,114 @@
1
+ """Точка входа — запуск готового приложения."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Callable
7
+
8
+ from deskkit import qt_backend
9
+ from deskkit.auth import AuthService
10
+ from deskkit.config import AppConfig
11
+ from deskkit.database import Database
12
+ from deskkit.exceptions import DatabaseError
13
+ from deskkit.images import ensure_images_dir
14
+ from deskkit.ui.login_window import LoginWindow
15
+ from deskkit.ui.main_window import MainWindow
16
+ from deskkit.ui.styles import APP_STYLE
17
+
18
+ qt_backend.init_qt()
19
+ QtWidgets = qt_backend.QtWidgets
20
+
21
+
22
+ class ShopApp:
23
+ """
24
+ Готовое приложение «из коробки».
25
+
26
+ Пример:
27
+ from deskkit import ShopApp, AppConfig
28
+
29
+ ShopApp(AppConfig(
30
+ title="Обувной магазин",
31
+ database="shoe_store",
32
+ password="root",
33
+ )).run()
34
+ """
35
+
36
+ def __init__(self, config: AppConfig | None = None):
37
+ self.config = config or AppConfig()
38
+ qt_backend.init_qt(self.config.qt_backend)
39
+ self.db = Database(self.config)
40
+ self.auth = AuthService(self.db)
41
+ self._app: QtWidgets.QApplication | None = None
42
+ self._main_window: MainWindow | None = None
43
+
44
+ def run(self) -> int:
45
+ """Запустить приложение или сгенерировать автономный проект."""
46
+ if self.config.export_project and not self.config.library_runtime:
47
+ from deskkit.generator import export_project, run_generated
48
+
49
+ out = export_project(self.config)
50
+ print(f"Проект создан: {out}")
51
+ if self.config.run_generated:
52
+ return run_generated(self.config, out)
53
+ return 0
54
+
55
+ return self._run_library()
56
+
57
+ def _run_library(self) -> int:
58
+ """Запуск через deskkit (режим разработки)."""
59
+ ensure_images_dir(self.config)
60
+
61
+ self._app = QtWidgets.QApplication.instance()
62
+ if self._app is None:
63
+ self._app = QtWidgets.QApplication(sys.argv)
64
+ self._app.setStyleSheet(APP_STYLE)
65
+
66
+ try:
67
+ if not self.db.test_connection():
68
+ QtWidgets.QMessageBox.critical(
69
+ None,
70
+ "Ошибка подключения",
71
+ "Не удалось подключиться к базе данных.\n"
72
+ "Проверьте настройки AppConfig.",
73
+ )
74
+ return 1
75
+ except DatabaseError as exc:
76
+ QtWidgets.QMessageBox.critical(None, "Ошибка подключения", str(exc))
77
+ return 1
78
+
79
+ if not self._show_login():
80
+ return 0
81
+ return qt_backend.exec_app(self._app)
82
+
83
+ def _show_login(self) -> bool:
84
+ def on_success(user: dict) -> None:
85
+ self._show_main()
86
+
87
+ login = LoginWindow(self.config, self.auth, on_success)
88
+ result = login.exec()
89
+ return result == QtWidgets.QDialog.DialogCode.Accepted
90
+
91
+ def _show_main(self) -> None:
92
+ if self._main_window:
93
+ self._main_window.close()
94
+
95
+ def on_logout() -> None:
96
+ self._show_login()
97
+
98
+ self._main_window = MainWindow(
99
+ self.config,
100
+ self.db,
101
+ self.auth,
102
+ on_logout=on_logout,
103
+ )
104
+ self._main_window.show()
105
+
106
+
107
+ def run_app(config: AppConfig | None = None) -> int:
108
+ """Быстрый запуск приложения одной функцией."""
109
+ return ShopApp(config).run()
110
+
111
+
112
+ def run_from_json(path: str) -> int:
113
+ """Запуск по JSON-файлу настроек."""
114
+ return ShopApp(AppConfig.from_json(path)).run()
@@ -0,0 +1,72 @@
1
+ """Авторизация пользователей."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from deskkit.database import Database
8
+ from deskkit.exceptions import AuthError
9
+ from deskkit.roles import ADMIN, CLIENT, GUEST, MANAGER
10
+
11
+
12
+ def make_guest() -> dict[str, Any]:
13
+ return {
14
+ "id_user": 0,
15
+ "login": "guest",
16
+ "first_name": "Гость",
17
+ "last_name": "",
18
+ "middle_name": None,
19
+ "id_role": 0,
20
+ "role_name": GUEST,
21
+ }
22
+
23
+
24
+ def user_display_name(user: dict[str, Any]) -> str:
25
+ parts = [user.get("last_name", ""), user.get("first_name", "")]
26
+ middle = user.get("middle_name")
27
+ if middle:
28
+ parts.append(middle)
29
+ name = " ".join(p for p in parts if p).strip()
30
+ return name or user.get("login", "Пользователь")
31
+
32
+
33
+ class AuthService:
34
+ def __init__(self, db: Database):
35
+ self.db = db
36
+ self.current_user: dict[str, Any] | None = None
37
+
38
+ @property
39
+ def role(self) -> str:
40
+ if not self.current_user:
41
+ return GUEST
42
+ return str(self.current_user.get("role_name", GUEST))
43
+
44
+ def login(self, login: str, password: str) -> dict[str, Any]:
45
+ if not login.strip() or not password.strip():
46
+ raise AuthError("Введите логин и пароль")
47
+
48
+ user = self.db.authenticate(login.strip(), password)
49
+ if not user:
50
+ raise AuthError("Неверный логин или пароль")
51
+
52
+ self.current_user = user
53
+ return user
54
+
55
+ def login_as_guest(self) -> dict[str, Any]:
56
+ self.current_user = make_guest()
57
+ return self.current_user
58
+
59
+ def logout(self) -> None:
60
+ self.current_user = None
61
+
62
+ def is_admin(self) -> bool:
63
+ return self.role == ADMIN
64
+
65
+ def is_manager(self) -> bool:
66
+ return self.role == MANAGER
67
+
68
+ def is_client(self) -> bool:
69
+ return self.role == CLIENT
70
+
71
+ def is_guest(self) -> bool:
72
+ return self.role == GUEST
@@ -0,0 +1,103 @@
1
+ """Поиск, фильтрация и сортировка товаров."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+
7
+
8
+ def _text(value: Any) -> str:
9
+ if value is None:
10
+ return ""
11
+ return str(value).lower()
12
+
13
+
14
+ def search_products(
15
+ products: list[dict[str, Any]],
16
+ query: str,
17
+ fields: list[str] | None = None,
18
+ ) -> list[dict[str, Any]]:
19
+ """Поиск в реальном времени по текстовым полям."""
20
+ if not query.strip():
21
+ return list(products)
22
+
23
+ q = query.strip().lower()
24
+ if fields is None:
25
+ fields = [
26
+ "article_number",
27
+ "product_name",
28
+ "description",
29
+ "category_name",
30
+ "manufacturer_name",
31
+ "supplier_name",
32
+ "unit",
33
+ "availability_status",
34
+ ]
35
+
36
+ result = []
37
+ for product in products:
38
+ for field in fields:
39
+ if q in _text(product.get(field)):
40
+ result.append(product)
41
+ break
42
+ return result
43
+
44
+
45
+ def filter_by_supplier(
46
+ products: list[dict[str, Any]],
47
+ supplier_id: int | None,
48
+ ) -> list[dict[str, Any]]:
49
+ if supplier_id is None or supplier_id <= 0:
50
+ return list(products)
51
+ return [p for p in products if int(p.get("id_supplier") or 0) == supplier_id]
52
+
53
+
54
+ def filter_by_category(
55
+ products: list[dict[str, Any]],
56
+ category_id: int | None,
57
+ ) -> list[dict[str, Any]]:
58
+ if category_id is None or category_id <= 0:
59
+ return list(products)
60
+ return [p for p in products if int(p.get("id_category") or 0) == category_id]
61
+
62
+
63
+ def sort_products(
64
+ products: list[dict[str, Any]],
65
+ field: str = "stock_quantity",
66
+ ascending: bool = True,
67
+ ) -> list[dict[str, Any]]:
68
+ def key_fn(item: dict[str, Any]) -> Any:
69
+ value = item.get(field, 0)
70
+ if value is None:
71
+ return 0
72
+ return value
73
+
74
+ return sorted(products, key=key_fn, reverse=not ascending)
75
+
76
+
77
+ def apply_catalog_filters(
78
+ products: list[dict[str, Any]],
79
+ *,
80
+ search: str = "",
81
+ supplier_id: int | None = None,
82
+ category_id: int | None = None,
83
+ sort_field: str = "product_name",
84
+ sort_asc: bool = True,
85
+ ) -> list[dict[str, Any]]:
86
+ """Комбинирует поиск, фильтры и сортировку."""
87
+ result = search_products(products, search)
88
+ result = filter_by_supplier(result, supplier_id)
89
+ result = filter_by_category(result, category_id)
90
+ return sort_products(result, sort_field, sort_asc)
91
+
92
+
93
+ def row_style(product: dict[str, Any], rules: list[dict[str, Any]]) -> dict[str, str]:
94
+ """Возвращает стили строки по правилам условного форматирования."""
95
+ styles: dict[str, str] = {}
96
+ for rule in rules:
97
+ condition: Callable[[dict[str, Any]], bool] = rule["when"]
98
+ if condition(product):
99
+ if "background" in rule:
100
+ styles["background"] = rule["background"]
101
+ if "foreground" in rule:
102
+ styles["foreground"] = rule["foreground"]
103
+ return styles