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.
- obuvstore-1.1.0/MANIFEST.in +4 -0
- obuvstore-1.1.0/PKG-INFO +15 -0
- obuvstore-1.1.0/obuvstore/__init__.py +94 -0
- obuvstore-1.1.0/obuvstore/config.py +38 -0
- obuvstore-1.1.0/obuvstore/data/obuvstore_full.sql +197 -0
- obuvstore-1.1.0/obuvstore/database/__init__.py +94 -0
- obuvstore-1.1.0/obuvstore/database/connection.py +124 -0
- obuvstore-1.1.0/obuvstore/ui/__init__.py +94 -0
- obuvstore-1.1.0/obuvstore/ui/login.py +121 -0
- obuvstore-1.1.0/obuvstore/ui/main_window.py +418 -0
- obuvstore-1.1.0/obuvstore/ui/styles.py +179 -0
- obuvstore-1.1.0/obuvstore.egg-info/PKG-INFO +15 -0
- obuvstore-1.1.0/obuvstore.egg-info/SOURCES.txt +16 -0
- obuvstore-1.1.0/obuvstore.egg-info/dependency_links.txt +1 -0
- obuvstore-1.1.0/obuvstore.egg-info/requires.txt +3 -0
- obuvstore-1.1.0/obuvstore.egg-info/top_level.txt +1 -0
- obuvstore-1.1.0/setup.cfg +4 -0
- obuvstore-1.1.0/setup.py +28 -0
obuvstore-1.1.0/PKG-INFO
ADDED
|
@@ -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_())
|