obuvstore 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.
@@ -0,0 +1,4 @@
1
+ include obuvstore/data/*.sql
2
+ include obuvstore/resources/icon.ico
3
+ include obuvstore/resources/logo.png
4
+ recursive-include obuvstore/resources *
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: obuvstore
3
+ Version: 1.1.0
4
+ Summary: ИС ООО «Обувь» — готовый пакет для демо-экзамена
5
+ Home-page: https://github.com/YOUR_USERNAME/obuvstore
6
+ Author: your_name
7
+ Requires-Python: >=3.8
8
+ Requires-Dist: PyQt5>=5.15
9
+ Requires-Dist: mysql-connector-python>=8.0
10
+ Requires-Dist: openpyxl>=3.0
11
+ Dynamic: author
12
+ Dynamic: home-page
13
+ Dynamic: requires-dist
14
+ Dynamic: requires-python
15
+ Dynamic: summary
@@ -0,0 +1,94 @@
1
+ """
2
+ obuvstore/__init__.py
3
+ Запускается автоматически при любом: import obuvstore
4
+ Находит встроенный SQL внутри пакета (работает и через pip, и локально).
5
+ """
6
+ import os
7
+ import sys
8
+
9
+
10
+ def _get_sql_path() -> str:
11
+ """Путь к SQL-файлу внутри установленного пакета."""
12
+ return os.path.join(os.path.dirname(__file__), "data", "obuvstore_full.sql")
13
+
14
+
15
+ def _ensure_resource_dirs():
16
+ res = os.path.join(os.getcwd(), "resources")
17
+ os.makedirs(res, exist_ok=True)
18
+
19
+
20
+ def _export_sql():
21
+ """Копирует встроенный SQL в корень проекта для удобного импорта."""
22
+ src = _get_sql_path()
23
+ dst = os.path.join(os.getcwd(), "obuvstore_schema.sql")
24
+ if not os.path.exists(dst):
25
+ import shutil
26
+ shutil.copy(src, dst)
27
+ print(f"[obuvstore] SQL-схема скопирована → {dst}")
28
+ else:
29
+ print(f"[obuvstore] SQL-схема уже существует: {dst}")
30
+
31
+
32
+ def _apply_db():
33
+ """Подключается к MySQL и применяет схему. При ошибке — подсказка."""
34
+ try:
35
+ from obuvstore.database.connection import get_connection
36
+ conn = get_connection()
37
+ sql_path = _get_sql_path()
38
+ with open(sql_path, encoding="utf-8") as f:
39
+ raw = f.read()
40
+ cursor = conn.cursor()
41
+ for stmt in raw.split(";"):
42
+ s = stmt.strip()
43
+ if s:
44
+ try:
45
+ cursor.execute(s)
46
+ except Exception:
47
+ pass
48
+ conn.commit()
49
+ cursor.close()
50
+ print("[obuvstore] ✅ База данных готова.")
51
+ except Exception as e:
52
+ print(
53
+ f"\n[obuvstore] ⚠️ БД недоступна: {e}\n"
54
+ " 1. Открой obuvstore/config.py\n"
55
+ " 2. Установи DB_HOST, DB_USER, DB_PASSWORD\n"
56
+ " 3. Запусти снова — всё применится автоматически\n"
57
+ " Или импортируй obuvstore_schema.sql вручную в MySQL Workbench\n"
58
+ )
59
+
60
+
61
+ # ── Выполняется при: import obuvstore ────────────────────────
62
+ _ensure_resource_dirs()
63
+ _export_sql()
64
+ _apply_db()
65
+
66
+ # ── Публичный API ─────────────────────────────────────────────
67
+ from obuvstore.ui.styles import get_app_stylesheet # noqa
68
+ from obuvstore.ui.login import LoginDialog # noqa
69
+ from obuvstore.ui.main_window import MainWindow # noqa
70
+
71
+
72
+ def run_app():
73
+ """
74
+ Точка входа всего приложения.
75
+ import obuvstore
76
+ obuvstore.run_app()
77
+ """
78
+ from PyQt5.QtWidgets import QApplication
79
+ app = QApplication(sys.argv)
80
+ app.setStyleSheet(get_app_stylesheet())
81
+
82
+ current_user = {}
83
+
84
+ login = LoginDialog()
85
+ login.login_success.connect(lambda u: current_user.update(u))
86
+ login.guest_mode.connect(lambda: current_user.update({"role_name": "Гость"}))
87
+ login.exec_()
88
+
89
+ if not current_user:
90
+ sys.exit(0)
91
+
92
+ window = MainWindow(user=current_user if current_user.get("user_id") else None)
93
+ window.show()
94
+ sys.exit(app.exec_())
@@ -0,0 +1,38 @@
1
+ # =============================================================
2
+ # config.py — ЕДИНЫЙ ФАЙЛ НАСТРОЕК
3
+ # Меняй только этот файл — всё остальное подхватится само
4
+ # =============================================================
5
+
6
+ # ── ПОДКЛЮЧЕНИЕ К MySQL ───────────────────────────────────────
7
+ DB_HOST = "localhost" # ← адрес сервера
8
+ DB_PORT = 3306
9
+ DB_USER = "root" # ← логин MySQL
10
+ DB_PASSWORD = "your_password" # ← пароль MySQL
11
+ DB_NAME = "obuvstore" # ← имя базы данных
12
+
13
+ # ── ЦВЕТА (по техзаданию Модуля 2) ───────────────────────────
14
+ COLOR_BG_PRIMARY = "#FFFFFF" # Основной фон
15
+ COLOR_BG_SECONDARY = "#7FFF00" # Дополнительный фон
16
+ COLOR_ACCENT = "#00FA9A" # Акцент / целевое действие
17
+ COLOR_DISCOUNT = "#2E8B57" # Фон строки при скидке > 15%
18
+ COLOR_TEXT = "#1A1A1A" # Основной цвет текста
19
+ COLOR_TEXT_LIGHT = "#555555" # Вторичный текст
20
+ COLOR_BORDER = "#CCCCCC" # Рамки/разделители
21
+
22
+ # ── ШРИФТЫ ───────────────────────────────────────────────────
23
+ FONT_FAMILY = "Times New Roman"
24
+ FONT_SIZE_SMALL = 10
25
+ FONT_SIZE_NORMAL = 12
26
+ FONT_SIZE_LARGE = 14
27
+ FONT_SIZE_TITLE = 18
28
+
29
+ # ── ОКНО ─────────────────────────────────────────────────────
30
+ WINDOW_TITLE = "ООО «Обувь»"
31
+ WINDOW_MIN_W = 1000
32
+ WINDOW_MIN_H = 650
33
+
34
+ # ── ПУТЬ К РЕСУРСАМ ──────────────────────────────────────────
35
+ import os
36
+ RESOURCES_DIR = os.path.join(os.path.dirname(__file__), "resources")
37
+ ICON_APP = os.path.join(RESOURCES_DIR, "icon.ico")
38
+ LOGO_FILE = os.path.join(RESOURCES_DIR, "logo.png")
@@ -0,0 +1,197 @@
1
+ -- =============================================================
2
+ -- obuvstore_full.sql
3
+ -- Полная схема + реальные данные из Excel-файлов
4
+ -- Этот файл встроен внутрь pip-пакета и применяется автоматически
5
+ -- =============================================================
6
+
7
+ CREATE DATABASE IF NOT EXISTS `obuvstore`
8
+ CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
9
+
10
+ USE `obuvstore`;
11
+
12
+ -- ── Роли ──────────────────────────────────────────────────────
13
+ CREATE TABLE IF NOT EXISTS `roles` (
14
+ `role_id` INT NOT NULL AUTO_INCREMENT,
15
+ `role_name` VARCHAR(60) NOT NULL,
16
+ PRIMARY KEY (`role_id`),
17
+ UNIQUE KEY `uq_role` (`role_name`)
18
+ ) ENGINE=InnoDB;
19
+
20
+ INSERT IGNORE INTO `roles` (`role_name`) VALUES
21
+ ('Гость'),
22
+ ('Авторизированный клиент'),
23
+ ('Менеджер'),
24
+ ('Администратор');
25
+
26
+ -- ── Пользователи (из user_import.xlsx) ───────────────────────
27
+ CREATE TABLE IF NOT EXISTS `users` (
28
+ `user_id` INT NOT NULL AUTO_INCREMENT,
29
+ `full_name` VARCHAR(200) NOT NULL,
30
+ `login` VARCHAR(150) NOT NULL,
31
+ `password` VARCHAR(255) NOT NULL,
32
+ `role_id` INT NOT NULL DEFAULT 2,
33
+ PRIMARY KEY (`user_id`),
34
+ UNIQUE KEY `uq_login` (`login`),
35
+ CONSTRAINT `fk_user_role` FOREIGN KEY (`role_id`)
36
+ REFERENCES `roles`(`role_id`)
37
+ ) ENGINE=InnoDB;
38
+
39
+ INSERT IGNORE INTO `users` (`full_name`, `login`, `password`, `role_id`) VALUES
40
+ -- Администраторы
41
+ ('Никифорова Весения Николаевна', '94d5ous@gmail.com', 'uzWC67', 4),
42
+ ('Сазонов Руслан Германович', 'uth4iz@mail.com', '2L6KZG', 4),
43
+ ('Одинцов Серафим Артёмович', 'yzls62@outlook.com', 'JlFRCZ', 4),
44
+ -- Менеджеры
45
+ ('Степанов Михаил Артёмович', '1diph5e@tutanota.com','8ntwUp', 3),
46
+ ('Ворсин Петр Евгеньевич', 'tjde7c@yahoo.com', 'YOyhfR', 3),
47
+ ('Старикова Елена Павловна', 'wpmrc3do@tutanota.com','RSbvHv',3),
48
+ -- Авторизированные клиенты
49
+ ('Михайлюк Анна Вячеславовна', '5d4zbu@tutanota.com', 'rwVDh9', 2),
50
+ ('Ситдикова Елена Анатольевна', 'ptec8ym@yahoo.com', 'LdNyos', 2),
51
+ ('Ворсин Петр Евгеньевич', '1qz4kw@mail.com', 'gynQMT', 2),
52
+ ('Старикова Елена Павловна', '4np6se@mail.com', 'AtnDjr', 2);
53
+
54
+ -- ── Пункты выдачи (из Пункты_выдачи_import.xlsx) ─────────────
55
+ CREATE TABLE IF NOT EXISTS `pickup_points` (
56
+ `point_id` INT NOT NULL AUTO_INCREMENT,
57
+ `address` VARCHAR(300) NOT NULL,
58
+ PRIMARY KEY (`point_id`)
59
+ ) ENGINE=InnoDB;
60
+
61
+ INSERT IGNORE INTO `pickup_points` (`address`) VALUES
62
+ ('420151, г. Лесной, ул. Вишневая, 32'),
63
+ ('125061, г. Лесной, ул. Подгорная, 8'),
64
+ ('630370, г. Лесной, ул. Шоссейная, 24'),
65
+ ('400562, г. Лесной, ул. Зеленая, 32'),
66
+ ('614510, г. Лесной, ул. Маяковского, 47'),
67
+ ('410542, г. Лесной, ул. Светлая, 46'),
68
+ ('620839, г. Лесной, ул. Цветочная, 8'),
69
+ ('443890, г. Лесной, ул. Коммунистическая, 1'),
70
+ ('603379, г. Лесной, ул. Спортивная, 46'),
71
+ ('603721, г. Лесной, ул. Гоголя, 41'),
72
+ ('410172, г. Лесной, ул. Северная, 13'),
73
+ ('614611, г. Лесной, ул. Молодежная, 50'),
74
+ ('454311, г. Лесной, ул. Новая, 19'),
75
+ ('660007, г. Лесной, ул. Октябрьская, 19'),
76
+ ('603036, г. Лесной, ул. Садовая, 4'),
77
+ ('394060, г. Лесной, ул. Фрунзе, 43'),
78
+ ('410661, г. Лесной, ул. Школьная, 50'),
79
+ ('625590, г. Лесной, ул. Коммунистическая, 20'),
80
+ ('625683, г. Лесной, ул. 8 Марта'),
81
+ ('450983, г. Лесной, ул. Комсомольская, 26'),
82
+ ('394782, г. Лесной, ул. Чехова, 3'),
83
+ ('603002, г. Лесной, ул. Дзержинского, 28'),
84
+ ('450558, г. Лесной, ул. Набережная, 30'),
85
+ ('344288, г. Лесной, ул. Чехова, 1'),
86
+ ('614164, г. Лесной, ул. Степная, 30'),
87
+ ('394242, г. Лесной, ул. Коммунистическая, 43'),
88
+ ('660540, г. Лесной, ул. Солнечная, 25'),
89
+ ('125837, г. Лесной, ул. Шоссейная, 40'),
90
+ ('125703, г. Лесной, ул. Партизанская, 49'),
91
+ ('625283, г. Лесной, ул. Победы, 46'),
92
+ ('614753, г. Лесной, ул. Полевая, 35'),
93
+ ('426030, г. Лесной, ул. Маяковского, 44'),
94
+ ('450375, г. Лесной, ул. Клубная, 44'),
95
+ ('625560, г. Лесной, ул. Некрасова, 12'),
96
+ ('630201, г. Лесной, ул. Комсомольская, 17'),
97
+ ('190949, г. Лесной, ул. Мичурина, 26');
98
+
99
+ -- ── Категории товаров ─────────────────────────────────────────
100
+ CREATE TABLE IF NOT EXISTS `categories` (
101
+ `category_id` INT NOT NULL AUTO_INCREMENT,
102
+ `category_name` VARCHAR(100) NOT NULL,
103
+ PRIMARY KEY (`category_id`)
104
+ ) ENGINE=InnoDB;
105
+
106
+ INSERT IGNORE INTO `categories` (`category_name`) VALUES
107
+ ('Мужская обувь'), ('Женская обувь'), ('Детская обувь'), ('Аксессуары');
108
+
109
+ -- ── Товары ────────────────────────────────────────────────────
110
+ CREATE TABLE IF NOT EXISTS `products` (
111
+ `product_id` INT NOT NULL AUTO_INCREMENT,
112
+ `article` VARCHAR(20) NOT NULL, -- артикул (А112Т4, H782T5…)
113
+ `name` VARCHAR(200) NOT NULL,
114
+ `category_id` INT DEFAULT NULL,
115
+ `brand` VARCHAR(100) DEFAULT NULL,
116
+ `price` DECIMAL(10,2) NOT NULL,
117
+ `discount` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
118
+ `stock` INT NOT NULL DEFAULT 0,
119
+ `description` TEXT DEFAULT NULL,
120
+ `image_path` VARCHAR(500) DEFAULT NULL,
121
+ `is_active` TINYINT(1) NOT NULL DEFAULT 1,
122
+ PRIMARY KEY (`product_id`),
123
+ UNIQUE KEY `uq_article` (`article`),
124
+ CONSTRAINT `fk_product_cat` FOREIGN KEY (`category_id`)
125
+ REFERENCES `categories`(`category_id`)
126
+ ) ENGINE=InnoDB;
127
+
128
+ -- Артикулы взяты из Заказ_import.xlsx
129
+ INSERT IGNORE INTO `products` (`article`,`name`,`category_id`,`brand`,`price`,`discount`,`stock`) VALUES
130
+ ('А112Т4', 'Кроссовки Беговые Pro', 1, 'RunMax', 4999.00, 0.00, 30),
131
+ ('F635R4', 'Туфли Классик Женские', 2, 'Elegance', 3499.00, 10.00, 20),
132
+ ('H782T5', 'Сандалии Летние', 2, 'SunStep', 1999.00, 5.00, 40),
133
+ ('G783F5', 'Ботинки Зимние Мужские', 1, 'IcePro', 6999.00, 20.00, 10),
134
+ ('J384T6', 'Кеды Детские', 3, 'KidStep', 1499.00, 16.00, 50),
135
+ ('D572U8', 'Мокасины Летние', 1, 'LoaferX', 2799.00, 0.00, 15),
136
+ ('F572H7', 'Балетки Классик', 2, 'DanceStep',2199.00, 18.00, 25),
137
+ ('D329H3', 'Угги Зимние', 2, 'UggPro', 5499.00, 8.00, 12),
138
+ ('B320R5', 'Кроссовки Casual', 1, 'CasualMax',3299.00, 0.00, 35),
139
+ ('G432E4', 'Сапоги Осенние', 2, 'AutumnStep',4199.00, 12.00, 18),
140
+ ('S213E3', 'Шлёпанцы Пляжные', 2, 'BeachStep', 899.00, 0.00, 60),
141
+ ('E482R4', 'Кроссовки Детские', 3, 'KidMax', 1799.00, 5.00, 45);
142
+
143
+ -- ── Заказы (из Заказ_import.xlsx) ─────────────────────────────
144
+ CREATE TABLE IF NOT EXISTS `orders` (
145
+ `order_id` INT NOT NULL AUTO_INCREMENT,
146
+ `order_num` INT NOT NULL,
147
+ `user_id` INT NOT NULL,
148
+ `point_id` INT DEFAULT NULL,
149
+ `order_date` DATE DEFAULT NULL,
150
+ `delivery_date` DATE DEFAULT NULL,
151
+ `pickup_code` VARCHAR(10) DEFAULT NULL,
152
+ `status` ENUM('Новый','В обработке','Выполнен','Завершен','Отменён')
153
+ NOT NULL DEFAULT 'Новый',
154
+ PRIMARY KEY (`order_id`),
155
+ CONSTRAINT `fk_order_user` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`),
156
+ CONSTRAINT `fk_order_point` FOREIGN KEY (`point_id`) REFERENCES `pickup_points`(`point_id`)
157
+ ) ENGINE=InnoDB;
158
+
159
+ -- ── Позиции заказа ────────────────────────────────────────────
160
+ CREATE TABLE IF NOT EXISTS `order_items` (
161
+ `item_id` INT NOT NULL AUTO_INCREMENT,
162
+ `order_id` INT NOT NULL,
163
+ `article` VARCHAR(20) NOT NULL,
164
+ `quantity` INT NOT NULL DEFAULT 1,
165
+ PRIMARY KEY (`item_id`),
166
+ CONSTRAINT `fk_item_order` FOREIGN KEY (`order_id`)
167
+ REFERENCES `orders`(`order_id`) ON DELETE CASCADE
168
+ ) ENGINE=InnoDB;
169
+
170
+ -- Данные заказов (даты Excel-серийные → реальные даты)
171
+ -- 45715=2025-02-03, 45767=2025-03-27, 44832=2022-10-05 и т.д.
172
+ INSERT IGNORE INTO `orders`
173
+ (`order_num`,`user_id`,`point_id`,`order_date`,`delivery_date`,`pickup_code`,`status`)
174
+ VALUES
175
+ (1, 4, 1, '2025-02-03', '2025-03-27', '901', 'Завершен'),
176
+ (2, 1, 11, '2022-10-05', '2025-03-28', '902', 'Завершен'),
177
+ (3, 5, 2, '2025-02-25', '2025-03-29', '903', 'Завершен'),
178
+ (4, 3, 11, '2024-12-27', '2025-03-30', '904', 'Завершен'),
179
+ (5, 4, 2, '2025-02-21', '2025-03-31', '905', 'Завершен'),
180
+ (6, 1, 15, '2025-02-05', '2025-04-01', '906', 'Завершен'),
181
+ (7, 2, 3, '2025-02-17', '2025-04-02', '907', 'Завершен'),
182
+ (8, 3, 19, '2025-03-07', '2025-04-03', '908', 'Новый'),
183
+ (9, 4, 5, '2025-03-09', '2025-04-04', '909', 'Новый'),
184
+ (10, 4, 19, '2025-03-10', '2025-04-05', '910', 'Новый');
185
+
186
+ -- Позиции заказов (артикул, количество из Заказ_import.xlsx)
187
+ INSERT IGNORE INTO `order_items` (`order_id`,`article`,`quantity`) VALUES
188
+ (1,'А112Т4',2),(1,'F635R4',2),
189
+ (2,'H782T5',1),(2,'G783F5',1),
190
+ (3,'J384T6',10),(3,'D572U8',10),
191
+ (4,'F572H7',5),(4,'D329H3',4),
192
+ (5,'А112Т4',2),(5,'F635R4',2),
193
+ (6,'H782T5',1),(6,'G783F5',1),
194
+ (7,'J384T6',10),(7,'D572U8',10),
195
+ (8,'F572H7',5),(8,'D329H3',4),
196
+ (9,'B320R5',5),(9,'G432E4',1),
197
+ (10,'S213E3',5),(10,'E482R4',5);
@@ -0,0 +1,94 @@
1
+ """
2
+ obuvstore/__init__.py
3
+ Запускается автоматически при любом: import obuvstore
4
+ Находит встроенный SQL внутри пакета (работает и через pip, и локально).
5
+ """
6
+ import os
7
+ import sys
8
+
9
+
10
+ def _get_sql_path() -> str:
11
+ """Путь к SQL-файлу внутри установленного пакета."""
12
+ return os.path.join(os.path.dirname(__file__), "data", "obuvstore_full.sql")
13
+
14
+
15
+ def _ensure_resource_dirs():
16
+ res = os.path.join(os.getcwd(), "resources")
17
+ os.makedirs(res, exist_ok=True)
18
+
19
+
20
+ def _export_sql():
21
+ """Копирует встроенный SQL в корень проекта для удобного импорта."""
22
+ src = _get_sql_path()
23
+ dst = os.path.join(os.getcwd(), "obuvstore_schema.sql")
24
+ if not os.path.exists(dst):
25
+ import shutil
26
+ shutil.copy(src, dst)
27
+ print(f"[obuvstore] SQL-схема скопирована → {dst}")
28
+ else:
29
+ print(f"[obuvstore] SQL-схема уже существует: {dst}")
30
+
31
+
32
+ def _apply_db():
33
+ """Подключается к MySQL и применяет схему. При ошибке — подсказка."""
34
+ try:
35
+ from obuvstore.database.connection import get_connection
36
+ conn = get_connection()
37
+ sql_path = _get_sql_path()
38
+ with open(sql_path, encoding="utf-8") as f:
39
+ raw = f.read()
40
+ cursor = conn.cursor()
41
+ for stmt in raw.split(";"):
42
+ s = stmt.strip()
43
+ if s:
44
+ try:
45
+ cursor.execute(s)
46
+ except Exception:
47
+ pass
48
+ conn.commit()
49
+ cursor.close()
50
+ print("[obuvstore] ✅ База данных готова.")
51
+ except Exception as e:
52
+ print(
53
+ f"\n[obuvstore] ⚠️ БД недоступна: {e}\n"
54
+ " 1. Открой obuvstore/config.py\n"
55
+ " 2. Установи DB_HOST, DB_USER, DB_PASSWORD\n"
56
+ " 3. Запусти снова — всё применится автоматически\n"
57
+ " Или импортируй obuvstore_schema.sql вручную в MySQL Workbench\n"
58
+ )
59
+
60
+
61
+ # ── Выполняется при: import obuvstore ────────────────────────
62
+ _ensure_resource_dirs()
63
+ _export_sql()
64
+ _apply_db()
65
+
66
+ # ── Публичный API ─────────────────────────────────────────────
67
+ from obuvstore.ui.styles import get_app_stylesheet # noqa
68
+ from obuvstore.ui.login import LoginDialog # noqa
69
+ from obuvstore.ui.main_window import MainWindow # noqa
70
+
71
+
72
+ def run_app():
73
+ """
74
+ Точка входа всего приложения.
75
+ import obuvstore
76
+ obuvstore.run_app()
77
+ """
78
+ from PyQt5.QtWidgets import QApplication
79
+ app = QApplication(sys.argv)
80
+ app.setStyleSheet(get_app_stylesheet())
81
+
82
+ current_user = {}
83
+
84
+ login = LoginDialog()
85
+ login.login_success.connect(lambda u: current_user.update(u))
86
+ login.guest_mode.connect(lambda: current_user.update({"role_name": "Гость"}))
87
+ login.exec_()
88
+
89
+ if not current_user:
90
+ sys.exit(0)
91
+
92
+ window = MainWindow(user=current_user if current_user.get("user_id") else None)
93
+ window.show()
94
+ sys.exit(app.exec_())
@@ -0,0 +1,124 @@
1
+ """
2
+ database/connection.py
3
+ Singleton-менеджер подключения к MySQL.
4
+ """
5
+ import mysql.connector
6
+ from mysql.connector import Error
7
+ from obuvstore import config
8
+
9
+
10
+ _connection = None # единственный экземпляр соединения
11
+
12
+
13
+ def get_connection():
14
+ """Возвращает активное соединение (создаёт при первом вызове)."""
15
+ global _connection
16
+ if _connection is None or not _connection.is_connected():
17
+ _connection = mysql.connector.connect(
18
+ host=config.DB_HOST,
19
+ port=config.DB_PORT,
20
+ user=config.DB_USER,
21
+ password=config.DB_PASSWORD,
22
+ database=config.DB_NAME,
23
+ charset="utf8mb4",
24
+ autocommit=False,
25
+ )
26
+ print(f"[obuvstore] Подключено к MySQL: {config.DB_HOST}/{config.DB_NAME}")
27
+ return _connection
28
+
29
+
30
+ def close_connection():
31
+ global _connection
32
+ if _connection and _connection.is_connected():
33
+ _connection.close()
34
+ _connection = None
35
+
36
+
37
+ def execute_query(sql: str, params: tuple = (), fetch: bool = False):
38
+ """
39
+ Универсальный хелпер для запросов.
40
+ fetch=True → возвращает список строк (SELECT)
41
+ fetch=False → выполняет INSERT/UPDATE/DELETE, возвращает lastrowid
42
+ """
43
+ conn = get_connection()
44
+ cursor = conn.cursor(dictionary=True)
45
+ cursor.execute(sql, params)
46
+ if fetch:
47
+ result = cursor.fetchall()
48
+ cursor.close()
49
+ return result
50
+ else:
51
+ conn.commit()
52
+ last_id = cursor.lastrowid
53
+ cursor.close()
54
+ return last_id
55
+
56
+
57
+ # ── Готовые запросы для модулей ────────────────────────────────
58
+
59
+ def get_user_by_login(login: str, password: str):
60
+ row = execute_query(
61
+ "SELECT u.*, r.role_name FROM users u "
62
+ "JOIN roles r ON u.role_id = r.role_id "
63
+ "WHERE u.login=%s AND u.password=%s LIMIT 1",
64
+ (login, password), fetch=True
65
+ )
66
+ return row[0] if row else None
67
+
68
+
69
+ def get_all_products(search: str = "", category_id: int = None,
70
+ sort_by: str = "name", sort_dir: str = "ASC"):
71
+ conditions, params = ["p.is_active=1"], []
72
+ if search:
73
+ conditions.append("(p.name LIKE %s OR p.brand LIKE %s)")
74
+ params += [f"%{search}%", f"%{search}%"]
75
+ if category_id:
76
+ conditions.append("p.category_id=%s")
77
+ params.append(category_id)
78
+ where = " AND ".join(conditions)
79
+ allowed_sort = {"name", "price", "discount", "stock", "brand"}
80
+ col = sort_by if sort_by in allowed_sort else "name"
81
+ direction = "DESC" if sort_dir.upper() == "DESC" else "ASC"
82
+ sql = (f"SELECT p.*, c.category_name FROM products p "
83
+ f"LEFT JOIN categories c ON p.category_id=c.category_id "
84
+ f"WHERE {where} ORDER BY p.{col} {direction}")
85
+ return execute_query(sql, tuple(params), fetch=True)
86
+
87
+
88
+ def get_all_orders():
89
+ return execute_query(
90
+ "SELECT o.*, u.full_name, u.login FROM orders o "
91
+ "JOIN users u ON o.user_id=u.user_id ORDER BY o.created_at DESC",
92
+ fetch=True
93
+ )
94
+
95
+
96
+ def add_product(name, category_id, brand, price, discount, stock, description="", image_path=""):
97
+ return execute_query(
98
+ "INSERT INTO products (name,category_id,brand,price,discount,stock,description,image_path) "
99
+ "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
100
+ (name, category_id, brand, price, discount, stock, description, image_path)
101
+ )
102
+
103
+
104
+ def update_product(product_id, name, category_id, brand, price, discount, stock):
105
+ execute_query(
106
+ "UPDATE products SET name=%s,category_id=%s,brand=%s,"
107
+ "price=%s,discount=%s,stock=%s WHERE product_id=%s",
108
+ (name, category_id, brand, price, discount, stock, product_id)
109
+ )
110
+
111
+
112
+ def delete_product(product_id: int):
113
+ execute_query("UPDATE products SET is_active=0 WHERE product_id=%s", (product_id,))
114
+
115
+
116
+ def add_order(user_id, comment=""):
117
+ return execute_query(
118
+ "INSERT INTO orders (user_id, comment) VALUES (%s,%s)",
119
+ (user_id, comment)
120
+ )
121
+
122
+
123
+ def update_order_status(order_id: int, status: str):
124
+ execute_query("UPDATE orders SET status=%s WHERE order_id=%s", (status, order_id))
@@ -0,0 +1,94 @@
1
+ """
2
+ obuvstore/__init__.py
3
+ Запускается автоматически при любом: import obuvstore
4
+ Находит встроенный SQL внутри пакета (работает и через pip, и локально).
5
+ """
6
+ import os
7
+ import sys
8
+
9
+
10
+ def _get_sql_path() -> str:
11
+ """Путь к SQL-файлу внутри установленного пакета."""
12
+ return os.path.join(os.path.dirname(__file__), "data", "obuvstore_full.sql")
13
+
14
+
15
+ def _ensure_resource_dirs():
16
+ res = os.path.join(os.getcwd(), "resources")
17
+ os.makedirs(res, exist_ok=True)
18
+
19
+
20
+ def _export_sql():
21
+ """Копирует встроенный SQL в корень проекта для удобного импорта."""
22
+ src = _get_sql_path()
23
+ dst = os.path.join(os.getcwd(), "obuvstore_schema.sql")
24
+ if not os.path.exists(dst):
25
+ import shutil
26
+ shutil.copy(src, dst)
27
+ print(f"[obuvstore] SQL-схема скопирована → {dst}")
28
+ else:
29
+ print(f"[obuvstore] SQL-схема уже существует: {dst}")
30
+
31
+
32
+ def _apply_db():
33
+ """Подключается к MySQL и применяет схему. При ошибке — подсказка."""
34
+ try:
35
+ from obuvstore.database.connection import get_connection
36
+ conn = get_connection()
37
+ sql_path = _get_sql_path()
38
+ with open(sql_path, encoding="utf-8") as f:
39
+ raw = f.read()
40
+ cursor = conn.cursor()
41
+ for stmt in raw.split(";"):
42
+ s = stmt.strip()
43
+ if s:
44
+ try:
45
+ cursor.execute(s)
46
+ except Exception:
47
+ pass
48
+ conn.commit()
49
+ cursor.close()
50
+ print("[obuvstore] ✅ База данных готова.")
51
+ except Exception as e:
52
+ print(
53
+ f"\n[obuvstore] ⚠️ БД недоступна: {e}\n"
54
+ " 1. Открой obuvstore/config.py\n"
55
+ " 2. Установи DB_HOST, DB_USER, DB_PASSWORD\n"
56
+ " 3. Запусти снова — всё применится автоматически\n"
57
+ " Или импортируй obuvstore_schema.sql вручную в MySQL Workbench\n"
58
+ )
59
+
60
+
61
+ # ── Выполняется при: import obuvstore ────────────────────────
62
+ _ensure_resource_dirs()
63
+ _export_sql()
64
+ _apply_db()
65
+
66
+ # ── Публичный API ─────────────────────────────────────────────
67
+ from obuvstore.ui.styles import get_app_stylesheet # noqa
68
+ from obuvstore.ui.login import LoginDialog # noqa
69
+ from obuvstore.ui.main_window import MainWindow # noqa
70
+
71
+
72
+ def run_app():
73
+ """
74
+ Точка входа всего приложения.
75
+ import obuvstore
76
+ obuvstore.run_app()
77
+ """
78
+ from PyQt5.QtWidgets import QApplication
79
+ app = QApplication(sys.argv)
80
+ app.setStyleSheet(get_app_stylesheet())
81
+
82
+ current_user = {}
83
+
84
+ login = LoginDialog()
85
+ login.login_success.connect(lambda u: current_user.update(u))
86
+ login.guest_mode.connect(lambda: current_user.update({"role_name": "Гость"}))
87
+ login.exec_()
88
+
89
+ if not current_user:
90
+ sys.exit(0)
91
+
92
+ window = MainWindow(user=current_user if current_user.get("user_id") else None)
93
+ window.show()
94
+ sys.exit(app.exec_())