obuv-exam 0.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 (32) hide show
  1. obuv_exam-0.1.0/MANIFEST.in +4 -0
  2. obuv_exam-0.1.0/PKG-INFO +12 -0
  3. obuv_exam-0.1.0/README.md +3 -0
  4. obuv_exam-0.1.0/pyproject.toml +23 -0
  5. obuv_exam-0.1.0/setup.cfg +4 -0
  6. obuv_exam-0.1.0/src/obuv_exam/__init__.py +1 -0
  7. obuv_exam-0.1.0/src/obuv_exam/bundle.py +657 -0
  8. obuv_exam-0.1.0/src/obuv_exam/database/__init__.py +1 -0
  9. obuv_exam-0.1.0/src/obuv_exam/database/db.py +163 -0
  10. obuv_exam-0.1.0/src/obuv_exam/database/script.sql +132 -0
  11. obuv_exam-0.1.0/src/obuv_exam/main.py +31 -0
  12. obuv_exam-0.1.0/src/obuv_exam/resources/icons/Icon.JPG +0 -0
  13. obuv_exam-0.1.0/src/obuv_exam/resources/icons/Icon.ico +0 -0
  14. obuv_exam-0.1.0/src/obuv_exam/resources/icons/Icon.png +0 -0
  15. obuv_exam-0.1.0/src/obuv_exam/resources/images/1.jpg +0 -0
  16. obuv_exam-0.1.0/src/obuv_exam/resources/images/10.jpg +0 -0
  17. obuv_exam-0.1.0/src/obuv_exam/resources/images/2.jpg +0 -0
  18. obuv_exam-0.1.0/src/obuv_exam/resources/images/3.jpg +0 -0
  19. obuv_exam-0.1.0/src/obuv_exam/resources/images/4.jpg +0 -0
  20. obuv_exam-0.1.0/src/obuv_exam/resources/images/5.jpg +0 -0
  21. obuv_exam-0.1.0/src/obuv_exam/resources/images/6.jpg +0 -0
  22. obuv_exam-0.1.0/src/obuv_exam/resources/images/7.jpg +0 -0
  23. obuv_exam-0.1.0/src/obuv_exam/resources/images/8.jpg +0 -0
  24. obuv_exam-0.1.0/src/obuv_exam/resources/images/9.jpg +0 -0
  25. obuv_exam-0.1.0/src/obuv_exam/resources/images/picture.png +0 -0
  26. obuv_exam-0.1.0/src/obuv_exam/styles/common.qss +19 -0
  27. obuv_exam-0.1.0/src/obuv_exam.egg-info/PKG-INFO +12 -0
  28. obuv_exam-0.1.0/src/obuv_exam.egg-info/SOURCES.txt +30 -0
  29. obuv_exam-0.1.0/src/obuv_exam.egg-info/dependency_links.txt +1 -0
  30. obuv_exam-0.1.0/src/obuv_exam.egg-info/entry_points.txt +2 -0
  31. obuv_exam-0.1.0/src/obuv_exam.egg-info/requires.txt +2 -0
  32. obuv_exam-0.1.0/src/obuv_exam.egg-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ recursive-include src/obuv_exam/resources *
3
+ recursive-include src/obuv_exam/styles *
4
+ recursive-include src/obuv_exam/database *
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: obuv-exam
3
+ Version: 0.1.0
4
+ Summary: Demo exam project for shoe store UI
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: PyQt6>=6.0
8
+ Requires-Dist: PyMySQL>=1.1
9
+
10
+ # obuv-exam
11
+
12
+ Demo exam package for a shoe store UI project.
@@ -0,0 +1,3 @@
1
+ # obuv-exam
2
+
3
+ Demo exam package for a shoe store UI project.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "obuv-exam"
7
+ version = "0.1.0"
8
+ description = "Demo exam project for shoe store UI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = ["PyQt6>=6.0", "PyMySQL>=1.1"]
12
+
13
+ [project.scripts]
14
+ obuv-exam = "obuv_exam.main:main"
15
+
16
+ [tool.setuptools]
17
+ package-dir = {"" = "src"}
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["src"]
21
+
22
+ [tool.setuptools.package-data]
23
+ obuv_exam = ["resources/**/*", "database/script.sql", "styles/common.qss"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .main import main
@@ -0,0 +1,657 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import random
5
+ from string import ascii_uppercase
6
+
7
+ from PyQt6.QtCore import Qt, QDate, pyqtSignal
8
+ from PyQt6.QtGui import QPixmap
9
+ from PyQt6.QtWidgets import (
10
+ QComboBox,
11
+ QDateEdit,
12
+ QDialog,
13
+ QDoubleSpinBox,
14
+ QFileDialog,
15
+ QFormLayout,
16
+ QFrame,
17
+ QHBoxLayout,
18
+ QLabel,
19
+ QLineEdit,
20
+ QMessageBox,
21
+ QPushButton,
22
+ QScrollArea,
23
+ QSpinBox,
24
+ QTabWidget,
25
+ QTextEdit,
26
+ QVBoxLayout,
27
+ QWidget,
28
+ )
29
+
30
+ from .database.db import dao
31
+
32
+
33
+ BASE_DIR = Path(__file__).resolve().parent
34
+
35
+
36
+ def generate_product_articule():
37
+ digits = "0123456789"
38
+ return (
39
+ random.choice(ascii_uppercase)
40
+ + "".join(random.choices(digits, k=3))
41
+ + random.choice(ascii_uppercase)
42
+ + random.choice(digits)
43
+ )
44
+
45
+
46
+ class AuthWindow(QDialog):
47
+ def __init__(self) -> None:
48
+ super().__init__()
49
+ self.main_window = None
50
+ self.setWindowTitle("Вход в систему")
51
+ self.setFixedSize(420, 220)
52
+
53
+ layout = QVBoxLayout(self)
54
+ title = QLabel("ООО «Обувь»")
55
+ title.setAlignment(Qt.AlignmentFlag.AlignCenter)
56
+ layout.addWidget(title)
57
+
58
+ form = QFormLayout()
59
+ self.loginLineEdit = QLineEdit()
60
+ self.passwordLineEdit = QLineEdit()
61
+ self.passwordLineEdit.setEchoMode(QLineEdit.EchoMode.Password)
62
+ form.addRow("Логин", self.loginLineEdit)
63
+ form.addRow("Пароль", self.passwordLineEdit)
64
+ layout.addLayout(form)
65
+
66
+ buttons = QHBoxLayout()
67
+ self.loginButton = QPushButton("Войти")
68
+ self.guestButton = QPushButton("Гость")
69
+ buttons.addWidget(self.loginButton)
70
+ buttons.addWidget(self.guestButton)
71
+ layout.addLayout(buttons)
72
+
73
+ self.loginButton.clicked.connect(self.login)
74
+ self.guestButton.clicked.connect(self.guest_login)
75
+
76
+ def login(self):
77
+ login = self.loginLineEdit.text().strip()
78
+ password = self.passwordLineEdit.text().strip()
79
+
80
+ if not login or not password:
81
+ QMessageBox.warning(self, "Ошибка заполнения", "Введите логин и пароль!")
82
+ return
83
+
84
+ account = dao.login(login, password)
85
+ if account is None:
86
+ QMessageBox.critical(self, "Ошибка авторизации", "Неправильный логин или пароль!")
87
+ return
88
+
89
+ self.main_window = MainWindow(account)
90
+ self.main_window.show()
91
+ self.close()
92
+
93
+ def guest_login(self):
94
+ self.main_window = MainWindow()
95
+ self.main_window.show()
96
+ self.close()
97
+
98
+
99
+ class ProductDialog(QDialog):
100
+ def __init__(self, product: dict | None = None) -> None:
101
+ super().__init__()
102
+ self.product = product
103
+ self.image_path: str | None = None
104
+ self.setWindowTitle("Товар")
105
+ self.setMinimumSize(620, 560)
106
+
107
+ layout = QVBoxLayout(self)
108
+ self.imageLabel = QLabel()
109
+ self.imageLabel.setFixedSize(150, 150)
110
+ self.imageLabel.setScaledContents(True)
111
+ layout.addWidget(self.imageLabel)
112
+
113
+ form = QFormLayout()
114
+ self.nameLineEdit = QLineEdit()
115
+ self.categoryComboBox = QComboBox()
116
+ self.supplierComboBox = QComboBox()
117
+ self.manufactureComboBox = QComboBox()
118
+ self.unitComboBox = QComboBox()
119
+ self.discountSpinBox = QSpinBox()
120
+ self.discountSpinBox.setRange(0, 100)
121
+ self.priceDoubleSpinBox = QDoubleSpinBox()
122
+ self.priceDoubleSpinBox.setMaximum(1_000_000)
123
+ self.priceDoubleSpinBox.setDecimals(2)
124
+ self.amountSpinBox = QSpinBox()
125
+ self.amountSpinBox.setMaximum(1_000_000)
126
+ self.descriptionEdit = QTextEdit()
127
+ form.addRow("Название", self.nameLineEdit)
128
+ form.addRow("Категория", self.categoryComboBox)
129
+ form.addRow("Поставщик", self.supplierComboBox)
130
+ form.addRow("Производитель", self.manufactureComboBox)
131
+ form.addRow("Ед. изм.", self.unitComboBox)
132
+ form.addRow("Скидка", self.discountSpinBox)
133
+ form.addRow("Цена", self.priceDoubleSpinBox)
134
+ form.addRow("Количество", self.amountSpinBox)
135
+ form.addRow("Описание", self.descriptionEdit)
136
+ layout.addLayout(form)
137
+
138
+ row = QHBoxLayout()
139
+ self.upload_image_button = QPushButton("Загрузить изображение")
140
+ self.saveButton = QPushButton("Сохранить")
141
+ row.addWidget(self.upload_image_button)
142
+ row.addWidget(self.saveButton)
143
+ if self.product is not None:
144
+ self.deleteButton = QPushButton("Удалить")
145
+ row.addWidget(self.deleteButton)
146
+ layout.addLayout(row)
147
+
148
+ self.upload_image_button.clicked.connect(self.load_image)
149
+ self.saveButton.clicked.connect(self.on_save)
150
+ if self.product is not None:
151
+ self.deleteButton.clicked.connect(self.on_delete)
152
+ self.load_comboboxes()
153
+ self.load_product_info(product)
154
+
155
+ def load_comboboxes(self):
156
+ for item in dao.load_categories():
157
+ self.categoryComboBox.addItem(item["name"], item["id"])
158
+ for item in dao.load_suppliers():
159
+ self.supplierComboBox.addItem(item["name"], item["id"])
160
+ for item in dao.load_manufactures():
161
+ self.manufactureComboBox.addItem(item["name"], item["id"])
162
+ for item in dao.load_units():
163
+ self.unitComboBox.addItem(item["name"], item["id"])
164
+
165
+ def load_product_info(self, product: dict | None):
166
+ if product is None:
167
+ self.imageLabel.setPixmap(QPixmap(str(BASE_DIR / "resources/images/picture.png")).scaled(150, 150))
168
+ return
169
+ self.nameLineEdit.setText(product["name"])
170
+ self.discountSpinBox.setValue(product["discount"])
171
+ self.priceDoubleSpinBox.setValue(float(product["price"]))
172
+ self.amountSpinBox.setValue(product["warehouse_amount"])
173
+ self.descriptionEdit.setPlainText(product["description"] or "")
174
+ self.categoryComboBox.setCurrentText(product["category_name"])
175
+ self.supplierComboBox.setCurrentText(product["supplier_name"])
176
+ self.manufactureComboBox.setCurrentText(product["manufacture_name"])
177
+ self.unitComboBox.setCurrentText(product["unit_name"])
178
+ if product.get("image_name"):
179
+ self.image_path = product["image_name"]
180
+ self.imageLabel.setPixmap(QPixmap(str(BASE_DIR / "resources/images" / product["image_name"])).scaled(150, 150))
181
+ else:
182
+ self.imageLabel.setPixmap(QPixmap(str(BASE_DIR / "resources/images/picture.png")).scaled(150, 150))
183
+
184
+ def load_image(self):
185
+ file_path, _ = QFileDialog.getOpenFileName(self, "Выбор изображения", str(BASE_DIR / "resources/images"), "Изображения (*.png *.jpg *.jpeg)")
186
+ if file_path:
187
+ self.image_path = file_path
188
+ self.imageLabel.setPixmap(QPixmap(file_path).scaled(150, 150))
189
+
190
+ def validate_form(self):
191
+ if not self.nameLineEdit.text().strip():
192
+ raise ValueError("Введите название товара")
193
+
194
+ def on_save(self):
195
+ try:
196
+ self.validate_form()
197
+ self.accept()
198
+ except ValueError as error:
199
+ QMessageBox.warning(self, "Ошибка ввода", str(error))
200
+
201
+ def on_delete(self):
202
+ if QMessageBox.question(self, "Удаление", "Удалить товар?") == QMessageBox.StandardButton.Yes:
203
+ self.done(2)
204
+
205
+ def get_data(self):
206
+ return (
207
+ self.nameLineEdit.text().strip(),
208
+ self.categoryComboBox.currentData(),
209
+ self.supplierComboBox.currentData(),
210
+ self.manufactureComboBox.currentData(),
211
+ self.unitComboBox.currentData(),
212
+ self.discountSpinBox.value(),
213
+ self.priceDoubleSpinBox.value(),
214
+ self.image_path,
215
+ self.amountSpinBox.value(),
216
+ self.descriptionEdit.toPlainText().strip(),
217
+ )
218
+
219
+
220
+ class OrderDialog(QDialog):
221
+ def __init__(self, account: dict | None = None, order: dict | None = None) -> None:
222
+ super().__init__()
223
+ self.account = account
224
+ self.order = order
225
+ self.setWindowTitle("Заказ")
226
+ self.setMinimumSize(520, 420)
227
+
228
+ layout = QVBoxLayout(self)
229
+ form = QFormLayout()
230
+
231
+ self.articuleLineEdit = QLineEdit()
232
+ self.orderDateEdit = QDateEdit()
233
+ self.orderDateEdit.setCalendarPopup(True)
234
+ self.orderDateEdit.setDate(QDate.currentDate())
235
+ self.deliveryDateEdit = QDateEdit()
236
+ self.deliveryDateEdit.setCalendarPopup(True)
237
+ self.deliveryDateEdit.setDate(QDate.currentDate())
238
+ self.pickupPointComboBox = QComboBox()
239
+ self.userComboBox = QComboBox()
240
+ self.codeSpinBox = QSpinBox()
241
+ self.codeSpinBox.setMaximum(999999)
242
+ self.statusComboBox = QComboBox()
243
+ self.productComboBox = QComboBox()
244
+ self.amountSpinBox = QSpinBox()
245
+ self.amountSpinBox.setMaximum(100000)
246
+
247
+ form.addRow("Артикул", self.articuleLineEdit)
248
+ form.addRow("Дата заказа", self.orderDateEdit)
249
+ form.addRow("Дата доставки", self.deliveryDateEdit)
250
+ form.addRow("Пункт выдачи", self.pickupPointComboBox)
251
+ form.addRow("Пользователь", self.userComboBox)
252
+ form.addRow("Код", self.codeSpinBox)
253
+ form.addRow("Статус", self.statusComboBox)
254
+ form.addRow("Товар", self.productComboBox)
255
+ form.addRow("Количество", self.amountSpinBox)
256
+ layout.addLayout(form)
257
+
258
+ buttons = QHBoxLayout()
259
+ self.saveButton = QPushButton("Сохранить")
260
+ buttons.addWidget(self.saveButton)
261
+ if self.order is not None:
262
+ self.deleteButton = QPushButton("Удалить")
263
+ buttons.addWidget(self.deleteButton)
264
+ layout.addLayout(buttons)
265
+
266
+ self.saveButton.clicked.connect(self.on_save)
267
+ if self.order is not None:
268
+ self.deleteButton.clicked.connect(self.on_delete)
269
+ self.load_comboboxes()
270
+ self.load_order_info(order)
271
+ if self.account and self.account.get("role_name") == "Авторизованный клиент":
272
+ self.userComboBox.setCurrentIndex(0)
273
+ self.userComboBox.setEnabled(False)
274
+
275
+ def load_comboboxes(self):
276
+ for item in dao.load_pickup_points():
277
+ self.pickupPointComboBox.addItem(item["address"], item["id"])
278
+ for item in dao.select("users"):
279
+ full_name = f"{item['last_name']} {item['first_name']} {item['middle_name'] or ''}".strip()
280
+ self.userComboBox.addItem(full_name, item["id"])
281
+ for item in dao.load_order_statuses():
282
+ self.statusComboBox.addItem(item["status_name"], item["id"])
283
+ for item in dao.load_products():
284
+ self.productComboBox.addItem(f"{item['name']} | {item['articule']}", item["id"])
285
+
286
+ def load_order_info(self, order: dict | None):
287
+ if order is None:
288
+ return
289
+ self.articuleLineEdit.setText(order["articule"])
290
+ self.codeSpinBox.setValue(order["code"])
291
+ self.amountSpinBox.setValue(order.get("amount", 1))
292
+ self.userComboBox.setCurrentText(f"{order['user_last_name']} {order['user_first_name']} {order['user_middle_name'] or ''}".strip())
293
+ self.pickupPointComboBox.setCurrentText(order["address"])
294
+ self.statusComboBox.setCurrentText(order["status_name"])
295
+ self.productComboBox.setCurrentText(f"{order['product_name']} | {order['articule']}")
296
+ if order.get("order_date"):
297
+ self.orderDateEdit.setDate(QDate.fromString(str(order["order_date"]), "yyyy-MM-dd"))
298
+ if order.get("delivery_date"):
299
+ self.deliveryDateEdit.setDate(QDate.fromString(str(order["delivery_date"]), "yyyy-MM-dd"))
300
+
301
+ def validate_form(self):
302
+ if not self.articuleLineEdit.text().strip():
303
+ raise ValueError("Введите артикул")
304
+ if self.productComboBox.currentData() is None:
305
+ raise ValueError("Выберите товар")
306
+
307
+ def on_save(self):
308
+ try:
309
+ self.validate_form()
310
+ self.accept()
311
+ except ValueError as error:
312
+ QMessageBox.warning(self, "Ошибка ввода", str(error))
313
+
314
+ def on_delete(self):
315
+ if QMessageBox.question(self, "Удаление", "Удалить заказ?") == QMessageBox.StandardButton.Yes:
316
+ self.done(2)
317
+
318
+ def get_data(self):
319
+ return (
320
+ self.articuleLineEdit.text().strip(),
321
+ self.orderDateEdit.date().toString("yyyy-MM-dd"),
322
+ self.deliveryDateEdit.date().toString("yyyy-MM-dd"),
323
+ self.pickupPointComboBox.currentData(),
324
+ self.userComboBox.currentData(),
325
+ self.codeSpinBox.value(),
326
+ self.statusComboBox.currentData(),
327
+ self.productComboBox.currentData(),
328
+ self.amountSpinBox.value(),
329
+ )
330
+
331
+
332
+ class ProductCard(QFrame):
333
+ clicked = pyqtSignal(dict)
334
+
335
+ def __init__(self, product: dict, admin_mode: bool = False) -> None:
336
+ super().__init__()
337
+ self.product = product
338
+ self.setObjectName("ProductCard")
339
+
340
+ layout = QHBoxLayout(self)
341
+ self.image_frame = QFrame()
342
+ self.image_frame.setObjectName("cardBlock")
343
+ image_layout = QVBoxLayout(self.image_frame)
344
+ self.image = QLabel()
345
+ self.image.setFixedSize(130, 130)
346
+ self.image.setScaledContents(True)
347
+ image_layout.addWidget(self.image)
348
+ layout.addWidget(self.image_frame)
349
+
350
+ self.main_frame = QFrame()
351
+ self.main_frame.setObjectName("cardBlock")
352
+ content = QVBoxLayout(self.main_frame)
353
+ self.category_name_label = QLabel()
354
+ self.description_label = QLabel()
355
+ self.description_label.setWordWrap(True)
356
+ self.manufacture_label = QLabel()
357
+ self.supplier_label = QLabel()
358
+ self.price_label = QLabel()
359
+ self.unit_label = QLabel()
360
+ self.amount_label = QLabel()
361
+ for label in [self.category_name_label, self.description_label, self.manufacture_label, self.supplier_label, self.price_label, self.unit_label, self.amount_label]:
362
+ content.addWidget(label)
363
+ layout.addWidget(self.main_frame, 1)
364
+
365
+ self.discount_frame = QFrame()
366
+ self.discount_frame.setObjectName("cardBlock")
367
+ discount_layout = QVBoxLayout(self.discount_frame)
368
+ self.discount_label = QLabel()
369
+ self.discount_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
370
+ discount_layout.addWidget(self.discount_label)
371
+ layout.addWidget(self.discount_frame)
372
+
373
+ self.load_product(product)
374
+ self.adjust_styles()
375
+
376
+ def adjust_styles(self):
377
+ styles = "QFrame#ProductCard {border: 1px solid black; padding: 4px;} QFrame#ProductCard:hover {border: 1px solid #00FA9A;} QFrame#cardBlock {border: 1px solid black; padding: 6px;}"
378
+ if self.product["discount"] > 15:
379
+ styles += "QFrame#ProductCard {background-color: #2E8B57;}"
380
+ if self.product["warehouse_amount"] == 0:
381
+ styles += "#amount {color: blue;}"
382
+ self.setStyleSheet(styles)
383
+
384
+ def load_product(self, product: dict):
385
+ image_name = product.get("image_name")
386
+ if image_name:
387
+ image_path = BASE_DIR / "resources/images" / image_name
388
+ else:
389
+ image_path = BASE_DIR / "resources/images/picture.png"
390
+ self.image.setPixmap(QPixmap(str(image_path)).scaled(130, 130))
391
+ self.category_name_label.setText(f"<b>{product['category_name']} | {product['name']}</b>")
392
+ self.description_label.setText(f"Описание: {product['description']}")
393
+ self.manufacture_label.setText(f"Производитель: {product['manufacture_name']}")
394
+ self.supplier_label.setText(f"Поставщик: {product['supplier_name']}")
395
+ if product["discount"] > 0:
396
+ new_price = float(product["price"]) * (1 - product["discount"] / 100)
397
+ self.price_label.setText(f"Цена: <s style='color: red;'>{float(product['price']):.2f}</s> {new_price:.2f}")
398
+ else:
399
+ self.price_label.setText(f"Цена: {float(product['price']):.2f}")
400
+ self.unit_label.setText(f"Единица измерения: {product['unit_name']}")
401
+ self.amount_label.setText(f"Количество на складе: {product['warehouse_amount']}")
402
+ self.amount_label.setObjectName("amount")
403
+ self.discount_label.setText(f"Скидка: {product['discount']}%")
404
+
405
+ def mousePressEvent(self, event):
406
+ self.clicked.emit(self.product)
407
+ return super().mousePressEvent(event)
408
+
409
+
410
+ class OrderCard(QFrame):
411
+ clicked = pyqtSignal(dict)
412
+
413
+ def __init__(self, order: dict, admin_mode: bool = False) -> None:
414
+ super().__init__()
415
+ self.order = order
416
+ self.setObjectName("OrderCard")
417
+
418
+ layout = QHBoxLayout(self)
419
+ self.image_frame = QFrame()
420
+ self.image_frame.setObjectName("cardBlock")
421
+ image_layout = QVBoxLayout(self.image_frame)
422
+ self.image = QLabel()
423
+ self.image.setFixedSize(110, 110)
424
+ self.image.setScaledContents(True)
425
+ image_name = order.get("image_name") or "picture.png"
426
+ self.image.setPixmap(QPixmap(str(BASE_DIR / "resources/images" / image_name)).scaled(110, 110))
427
+ image_layout.addWidget(self.image)
428
+ layout.addWidget(self.image_frame)
429
+
430
+ self.main_frame = QFrame()
431
+ self.main_frame.setObjectName("cardBlock")
432
+ content = QVBoxLayout(self.main_frame)
433
+ self.articule_label = QLabel(order["articule"])
434
+ self.product_label = QLabel(f"Товар: {order.get('product_name', '')}")
435
+ self.order_date_label = QLabel(f"Дата заказа: {order['order_date']}")
436
+ self.pickup_address = QLabel(order["address"])
437
+ self.status_label = QLabel(order["status_name"])
438
+ for label in [self.articule_label, self.product_label, self.order_date_label, self.pickup_address, self.status_label]:
439
+ content.addWidget(label)
440
+ layout.addWidget(self.main_frame, 1)
441
+
442
+ self.delivery_frame = QFrame()
443
+ self.delivery_frame.setObjectName("cardBlock")
444
+ delivery_layout = QVBoxLayout(self.delivery_frame)
445
+ self.date_delivery_label = QLabel(f"Дата доставки: {order['delivery_date']}" if order["delivery_date"] else "Дата доставки: Не указано")
446
+ self.date_delivery_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
447
+ delivery_layout.addWidget(self.date_delivery_label)
448
+ layout.addWidget(self.delivery_frame)
449
+
450
+ self.adjust_styles()
451
+
452
+ def adjust_styles(self):
453
+ self.setStyleSheet("QFrame#OrderCard {border: 1px solid black; padding: 4px;} QFrame#OrderCard:hover {border: 1px solid #00FA9A;} QFrame#cardBlock {border: 1px solid black; padding: 6px;}")
454
+
455
+ def mousePressEvent(self, event):
456
+ self.clicked.emit(self.order)
457
+ return super().mousePressEvent(event)
458
+
459
+
460
+ class MainWindow(QWidget):
461
+ def __init__(self, account: dict | None = None) -> None:
462
+ super().__init__()
463
+ self.account = account
464
+ self.role = account["role_name"] if account else None
465
+ self.auth_window = None
466
+ self.setWindowTitle("ООО «Обувь»")
467
+ self.resize(1300, 900)
468
+
469
+ main_layout = QHBoxLayout(self)
470
+ sidebar = QVBoxLayout()
471
+ self.logo = QLabel()
472
+ self.logo.setPixmap(QPixmap(str(BASE_DIR / "resources/icons/Icon.png")).scaled(110, 110, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
473
+ self.logoutButton = QPushButton("Выйти")
474
+ sidebar.addWidget(self.logo)
475
+ sidebar.addStretch()
476
+ sidebar.addWidget(self.logoutButton)
477
+
478
+ content = QVBoxLayout()
479
+ top_row = QHBoxLayout()
480
+ self.fio_label = QLabel()
481
+ self.fio_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)
482
+ top_row.addStretch()
483
+ top_row.addWidget(self.fio_label)
484
+ content.addLayout(top_row)
485
+
486
+ self.tabWidget = QTabWidget()
487
+ self.products_tab = QWidget()
488
+ self.orders_tab = QWidget()
489
+ self.tabWidget.addTab(self.products_tab, "Товары")
490
+ self.tabWidget.addTab(self.orders_tab, "Заказы")
491
+ content.addWidget(self.tabWidget)
492
+
493
+ main_layout.addLayout(sidebar, 0)
494
+ main_layout.addLayout(content, 1)
495
+
496
+ self._build_products_tab()
497
+ self._build_orders_tab()
498
+ self.load_common_parts()
499
+ self.adjust_to_role()
500
+ self.connect_slots()
501
+ self.load_filters()
502
+ self.load_products()
503
+ self.load_orders()
504
+
505
+ def _build_products_tab(self):
506
+ layout = QVBoxLayout(self.products_tab)
507
+ controls = QHBoxLayout()
508
+ self.searchEdit = QLineEdit()
509
+ self.searchEdit.setPlaceholderText("Поиск")
510
+ self.filterBox = QComboBox()
511
+ self.sortBox = QComboBox()
512
+ self.add_product_button = QPushButton("Добавить товар")
513
+ controls.addWidget(self.searchEdit)
514
+ controls.addWidget(self.filterBox)
515
+ controls.addWidget(self.sortBox)
516
+ controls.addWidget(self.add_product_button)
517
+ layout.addLayout(controls)
518
+ self.products_scroll = QScrollArea()
519
+ self.products_scroll.setWidgetResizable(True)
520
+ self.products_container = QWidget()
521
+ self.products_layout = QVBoxLayout(self.products_container)
522
+ self.products_scroll.setWidget(self.products_container)
523
+ layout.addWidget(self.products_scroll)
524
+
525
+ def _build_orders_tab(self):
526
+ layout = QVBoxLayout(self.orders_tab)
527
+ self.add_order_button = QPushButton("Добавить заказ")
528
+ layout.addWidget(self.add_order_button)
529
+ self.orders_scroll = QScrollArea()
530
+ self.orders_scroll.setWidgetResizable(True)
531
+ self.orders_container = QWidget()
532
+ self.orders_layout = QVBoxLayout(self.orders_container)
533
+ self.orders_scroll.setWidget(self.orders_container)
534
+ layout.addWidget(self.orders_scroll)
535
+
536
+ def connect_slots(self):
537
+ self.logoutButton.clicked.connect(self.logout)
538
+ self.searchEdit.textChanged.connect(self.load_products)
539
+ self.sortBox.currentIndexChanged.connect(self.load_products)
540
+ self.filterBox.currentIndexChanged.connect(self.load_products)
541
+ self.add_product_button.clicked.connect(self.on_add_product)
542
+ self.add_order_button.clicked.connect(self.on_add_order)
543
+
544
+ def load_common_parts(self):
545
+ if self.account:
546
+ fio = f"{self.account['last_name']} {self.account['first_name']} {self.account['middle_name'] or ''}".strip()
547
+ self.fio_label.setText(fio)
548
+ else:
549
+ self.fio_label.setText("Гость")
550
+
551
+ def adjust_to_role(self):
552
+ if self.account is None or self.role == "Авторизованный клиент":
553
+ self.tabWidget.setTabVisible(1, False)
554
+ self.searchEdit.hide()
555
+ self.sortBox.hide()
556
+ self.filterBox.hide()
557
+ self.add_product_button.hide()
558
+ elif self.role == "Менеджер":
559
+ self.add_product_button.hide()
560
+ self.add_order_button.hide()
561
+
562
+ def clear_layout(self, layout):
563
+ while layout.count():
564
+ item = layout.takeAt(0)
565
+ widget = item.widget()
566
+ if widget:
567
+ widget.setParent(None)
568
+ widget.deleteLater()
569
+
570
+ def load_filters(self):
571
+ self.filterBox.clear()
572
+ self.sortBox.clear()
573
+ self.filterBox.addItem("Все поставщики", None)
574
+ for supplier in dao.load_suppliers():
575
+ self.filterBox.addItem(supplier["name"], supplier["id"])
576
+ self.sortBox.addItem("По возрастанию (кол-во на складе)", "ASC")
577
+ self.sortBox.addItem("По убыванию (кол-во на складе)", "DESC")
578
+
579
+ def load_products(self):
580
+ self.clear_layout(self.products_layout)
581
+ search_text = self.searchEdit.text().strip() or None
582
+ supplier_id = self.filterBox.currentData()
583
+ sort_by = self.sortBox.currentData() or "ASC"
584
+ products = dao.load_products(search_text, supplier_id, sort_by) if self.role else dao.load_products()
585
+ for product in products:
586
+ card = ProductCard(product, admin_mode=self.role == "Администратор")
587
+ card.clicked.connect(self.on_update_product)
588
+ self.products_layout.addWidget(card)
589
+ self.products_layout.addStretch()
590
+
591
+ def load_orders(self):
592
+ self.clear_layout(self.orders_layout)
593
+ if self.role == "Авторизованный клиент":
594
+ return
595
+ orders = dao.load_orders()
596
+ for order in orders:
597
+ card = OrderCard(order, admin_mode=self.role == "Администратор")
598
+ card.clicked.connect(self.on_update_order)
599
+ self.orders_layout.addWidget(card)
600
+ self.orders_layout.addStretch()
601
+
602
+ def logout(self):
603
+ self.auth_window = AuthWindow()
604
+ self.auth_window.show()
605
+ self.close()
606
+
607
+ def save_image(self, image_path: str | None):
608
+ if not image_path:
609
+ return None
610
+ image_name = Path(image_path).name
611
+ destination = BASE_DIR / "resources/images" / image_name
612
+ if not destination.exists():
613
+ destination.write_bytes(Path(image_path).read_bytes())
614
+ return image_name
615
+
616
+ def on_add_product(self):
617
+ dialog = ProductDialog()
618
+ if dialog.exec() == QDialog.DialogCode.Accepted:
619
+ data = dialog.get_data()
620
+ image_name = self.save_image(data[7])
621
+ articule = generate_product_articule()
622
+ dao.insert_product(*data[:7], image_name, *data[8:], articule)
623
+ self.load_products()
624
+
625
+ def on_update_product(self, product: dict):
626
+ if self.role != "Администратор":
627
+ return
628
+ dialog = ProductDialog(product)
629
+ result = dialog.exec()
630
+ if result == QDialog.DialogCode.Accepted:
631
+ data = dialog.get_data()
632
+ image_name = self.save_image(data[7])
633
+ dao.update_product(*data[:7], image_name, *data[8:], product["id"])
634
+ self.load_products()
635
+ elif result == 2:
636
+ dao.delete_product(product["id"])
637
+ self.load_products()
638
+
639
+ def on_add_order(self):
640
+ dialog = OrderDialog(account=self.account)
641
+ if dialog.exec() == QDialog.DialogCode.Accepted:
642
+ articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount = dialog.get_data()
643
+ dao.insert_order(articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount)
644
+ self.load_orders()
645
+
646
+ def on_update_order(self, order: dict):
647
+ if self.role != "Администратор":
648
+ return
649
+ dialog = OrderDialog(account=self.account, order=order)
650
+ result = dialog.exec()
651
+ if result == QDialog.DialogCode.Accepted:
652
+ articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount = dialog.get_data()
653
+ dao.update_order(order["id"], articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount)
654
+ self.load_orders()
655
+ elif result == 2:
656
+ dao.delete_order(order["id"])
657
+ self.load_orders()
@@ -0,0 +1 @@
1
+ from .db import dao
@@ -0,0 +1,163 @@
1
+ from __future__ import annotations
2
+
3
+ import pymysql
4
+
5
+
6
+ class Database:
7
+ def __init__(self) -> None:
8
+ self.conn = None
9
+
10
+ def connect(self):
11
+ if self.conn is None:
12
+ self.conn = pymysql.connect(
13
+ user="root",
14
+ password="",
15
+ database="shoes_sales",
16
+ host="localhost",
17
+ cursorclass=pymysql.cursors.DictCursor,
18
+ )
19
+ return self.conn
20
+
21
+ def commit(self):
22
+ self.connect().commit()
23
+
24
+ def rollback(self):
25
+ self.connect().rollback()
26
+
27
+ def cursor(self):
28
+ return self.connect().cursor()
29
+
30
+ def login(self, login: str, password: str):
31
+ cur = self.cursor()
32
+ cur.execute(
33
+ "SELECT users.*, roles.role_name FROM users INNER JOIN roles ON role_id = roles.id WHERE login = %s AND password = %s",
34
+ [login, password],
35
+ )
36
+ row = cur.fetchone()
37
+ cur.close()
38
+ return row
39
+
40
+ def select(self, table_name: str):
41
+ cur = self.cursor()
42
+ cur.execute(f"SELECT * FROM {table_name}")
43
+ rows = cur.fetchall()
44
+ cur.close()
45
+ return rows
46
+
47
+ def load_suppliers(self):
48
+ return self.select("suppliers")
49
+
50
+ def load_categories(self):
51
+ return self.select("product_categories")
52
+
53
+ def load_manufactures(self):
54
+ return self.select("manufactures")
55
+
56
+ def load_units(self):
57
+ return self.select("units")
58
+
59
+ def load_pickup_points(self):
60
+ return self.select("pickup_points")
61
+
62
+ def load_order_statuses(self):
63
+ return self.select("order_statuses")
64
+
65
+ def load_products(self, search: str | None = None, supplier_id: int | None = None, sort_by: str = "ASC"):
66
+ cur = self.cursor()
67
+ params = []
68
+ query = """
69
+ SELECT products.*, units.name AS unit_name, product_categories.name AS category_name,
70
+ suppliers.name AS supplier_name, manufactures.name AS manufacture_name
71
+ FROM products INNER JOIN units ON unit_id = units.id
72
+ INNER JOIN product_categories ON product_categories.id = category_id
73
+ INNER JOIN suppliers ON suppliers.id = supplier_id
74
+ INNER JOIN manufactures ON manufacture_id = manufactures.id
75
+ WHERE 1 = 1
76
+ """
77
+ if search:
78
+ query += " AND (products.name LIKE %s OR units.name LIKE %s OR product_categories.name LIKE %s OR suppliers.name LIKE %s OR manufactures.name LIKE %s OR description LIKE %s OR articule LIKE %s)"
79
+ params.extend([f"%{search}%"] * 7)
80
+ if supplier_id:
81
+ query += " AND supplier_id = %s"
82
+ params.append(supplier_id)
83
+ query += f" ORDER BY warehouse_amount {sort_by}"
84
+ cur.execute(query, params)
85
+ rows = cur.fetchall()
86
+ cur.close()
87
+ return rows
88
+
89
+ def load_orders(self):
90
+ cur = self.cursor()
91
+ query = """
92
+ SELECT orders.*, pickup_points.address, order_statuses.status_name,
93
+ users.first_name AS user_first_name, users.last_name AS user_last_name,
94
+ users.middle_name AS user_middle_name,
95
+ products.name AS product_name, products.image_name, products.price,
96
+ products.discount, products.warehouse_amount,
97
+ order_items.amount
98
+ FROM orders
99
+ INNER JOIN pickup_points ON pickup_point_id = pickup_points.id
100
+ INNER JOIN order_statuses ON order_statuses.id = order_status_id
101
+ INNER JOIN users ON user_id = users.id
102
+ INNER JOIN products ON product_id = products.id
103
+ INNER JOIN order_items ON order_items.order_id = orders.id
104
+ """
105
+ cur.execute(query)
106
+ rows = cur.fetchall()
107
+ cur.close()
108
+ return rows
109
+
110
+ def insert_product(self, name, category_id, supplier_id, manufacture_id, unit_id, discount, price, image_name, warehouse_amount, description, articule: str):
111
+ cur = self.cursor()
112
+ cur.execute(
113
+ "INSERT INTO products (articule, name, unit_id, price, supplier_id, manufacture_id, category_id, discount, warehouse_amount, description, image_name) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
114
+ [articule, name, unit_id, price, supplier_id, manufacture_id, category_id, discount, warehouse_amount, description, image_name],
115
+ )
116
+ cur.close()
117
+ self.commit()
118
+
119
+ def update_product(self, name, category_id, supplier_id, manufacture_id, unit_id, discount, price, image_name, warehouse_amount, description, id: int):
120
+ cur = self.cursor()
121
+ cur.execute(
122
+ "UPDATE products SET name = %s, unit_id = %s, price = %s, supplier_id = %s, manufacture_id = %s, category_id = %s, discount = %s, warehouse_amount = %s, description = %s, image_name = %s WHERE id = %s",
123
+ [name, unit_id, price, supplier_id, manufacture_id, category_id, discount, warehouse_amount, description, image_name, id],
124
+ )
125
+ cur.close()
126
+ self.commit()
127
+
128
+ def delete_product(self, product_id: int):
129
+ cur = self.cursor()
130
+ cur.execute("DELETE FROM products WHERE id = %s", [product_id])
131
+ cur.close()
132
+ self.commit()
133
+
134
+ def insert_order(self, articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount):
135
+ cur = self.cursor()
136
+ cur.execute(
137
+ "INSERT INTO orders (articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
138
+ [articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id],
139
+ )
140
+ order_id = cur.lastrowid
141
+ cur.execute("INSERT INTO order_items (order_id, product_id, amount) VALUES (%s, %s, %s)", [order_id, product_id, amount])
142
+ cur.close()
143
+ self.commit()
144
+
145
+ def update_order(self, order_id, articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, amount):
146
+ cur = self.cursor()
147
+ cur.execute(
148
+ "UPDATE orders SET articule = %s, order_date = %s, delivery_date = %s, pickup_point_id = %s, user_id = %s, code = %s, order_status_id = %s, product_id = %s WHERE id = %s",
149
+ [articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id, order_id],
150
+ )
151
+ cur.execute("UPDATE order_items SET product_id = %s, amount = %s WHERE order_id = %s", [product_id, amount, order_id])
152
+ cur.close()
153
+ self.commit()
154
+
155
+ def delete_order(self, order_id: int):
156
+ cur = self.cursor()
157
+ cur.execute("DELETE FROM order_items WHERE order_id = %s", [order_id])
158
+ cur.execute("DELETE FROM orders WHERE id = %s", [order_id])
159
+ cur.close()
160
+ self.commit()
161
+
162
+
163
+ dao = Database()
@@ -0,0 +1,132 @@
1
+ DROP DATABASE IF EXISTS shoes_sales;
2
+ CREATE DATABASE IF NOT EXISTS shoes_sales;
3
+
4
+ USE shoes_sales;
5
+
6
+ CREATE TABLE roles (
7
+ id INT NOT NULL AUTO_INCREMENT,
8
+ role_name VARCHAR(50) NOT NULL UNIQUE,
9
+ PRIMARY KEY (id)
10
+ );
11
+
12
+ CREATE TABLE units (
13
+ id INT NOT NULL AUTO_INCREMENT,
14
+ name VARCHAR(20) NOT NULL,
15
+ PRIMARY KEY (id)
16
+ );
17
+
18
+ CREATE TABLE suppliers (
19
+ id INT NOT NULL AUTO_INCREMENT,
20
+ name VARCHAR(100) NOT NULL,
21
+ PRIMARY KEY (id)
22
+ );
23
+
24
+ CREATE TABLE manufactures (
25
+ id INT NOT NULL AUTO_INCREMENT,
26
+ name VARCHAR(100) NOT NULL,
27
+ PRIMARY KEY (id)
28
+ );
29
+
30
+ CREATE TABLE product_categories (
31
+ id INT NOT NULL AUTO_INCREMENT,
32
+ name VARCHAR(100) NOT NULL,
33
+ PRIMARY KEY (id)
34
+ );
35
+
36
+ CREATE TABLE pickup_points (
37
+ id INT NOT NULL AUTO_INCREMENT,
38
+ address VARCHAR(300) NOT NULL UNIQUE,
39
+ PRIMARY KEY (id)
40
+ );
41
+
42
+ CREATE TABLE order_statuses (
43
+ id INT NOT NULL AUTO_INCREMENT,
44
+ status_name VARCHAR(50) NOT NULL UNIQUE,
45
+ PRIMARY KEY (id)
46
+ );
47
+
48
+ CREATE TABLE users (
49
+ id INT NOT NULL AUTO_INCREMENT,
50
+ role_id INTEGER NOT NULL,
51
+ first_name VARCHAR(50) NOT NULL,
52
+ last_name VARCHAR(50) NOT NULL,
53
+ middle_name VARCHAR(50) DEFAULT NULL,
54
+ login VARCHAR(200) NOT NULL UNIQUE,
55
+ password VARCHAR(30) NOT NULL,
56
+ PRIMARY KEY (id),
57
+ FOREIGN KEY (role_id) REFERENCES roles (id)
58
+ );
59
+
60
+ CREATE TABLE products (
61
+ id INT NOT NULL AUTO_INCREMENT,
62
+ articule CHAR(6) NOT NULL UNIQUE,
63
+ name VARCHAR(100) NOT NULL,
64
+ unit_id INTEGER NOT NULL,
65
+ price DECIMAL(10,2) NOT NULL,
66
+ supplier_id INTEGER NOT NULL,
67
+ manufacture_id INTEGER NOT NULL,
68
+ category_id INTEGER NOT NULL,
69
+ discount INTEGER NOT NULL DEFAULT 0,
70
+ warehouse_amount INTEGER NOT NULL,
71
+ description TEXT,
72
+ image_name VARCHAR(255) DEFAULT NULL,
73
+ PRIMARY KEY (id),
74
+ FOREIGN KEY (unit_id) REFERENCES units (id),
75
+ FOREIGN KEY (supplier_id) REFERENCES suppliers (id),
76
+ FOREIGN KEY (manufacture_id) REFERENCES manufactures (id),
77
+ FOREIGN KEY (category_id) REFERENCES product_categories (id)
78
+ );
79
+
80
+ CREATE TABLE orders (
81
+ id INT NOT NULL AUTO_INCREMENT,
82
+ articule VARCHAR(200) NOT NULL,
83
+ order_date DATE NOT NULL,
84
+ delivery_date DATE DEFAULT NULL,
85
+ pickup_point_id INTEGER NOT NULL,
86
+ user_id INTEGER NOT NULL,
87
+ code INTEGER NOT NULL,
88
+ order_status_id INTEGER NOT NULL,
89
+ product_id INTEGER NOT NULL,
90
+ PRIMARY KEY (id),
91
+ FOREIGN KEY (pickup_point_id) REFERENCES pickup_points (id),
92
+ FOREIGN KEY (user_id) REFERENCES users (id),
93
+ FOREIGN KEY (order_status_id) REFERENCES order_statuses (id),
94
+ FOREIGN KEY (product_id) REFERENCES products (id)
95
+ );
96
+
97
+ CREATE TABLE order_items (
98
+ id INT NOT NULL AUTO_INCREMENT,
99
+ order_id INTEGER NOT NULL,
100
+ product_id INTEGER NOT NULL,
101
+ amount INTEGER NOT NULL,
102
+ PRIMARY KEY (id),
103
+ FOREIGN KEY (order_id) REFERENCES orders (id),
104
+ FOREIGN KEY (product_id) REFERENCES products (id)
105
+ );
106
+
107
+ INSERT INTO roles (id, role_name) VALUES
108
+ (1, 'Администратор'),
109
+ (2, 'Менеджер'),
110
+ (3, 'Авторизованный клиент');
111
+
112
+ INSERT INTO units (id, name) VALUES (1, 'шт');
113
+ INSERT INTO suppliers (id, name) VALUES (1, 'Kari'), (2, 'Обувь для вас');
114
+ INSERT INTO manufactures (id, name) VALUES (1, 'Kari'), (2, 'Rieker'), (3, 'Poc'), (4, 'Marco Tozzi'), (5, 'CROSBY');
115
+ INSERT INTO product_categories (id, name) VALUES (1, 'Женская обувь'), (2, 'Мужская обувь');
116
+ INSERT INTO pickup_points (id, address) VALUES (1, '420151, г. Лесной, ул. Вишневая, 32');
117
+ INSERT INTO order_statuses (id, status_name) VALUES (1, 'Новый'), (2, 'Завершён');
118
+ INSERT INTO users (id, role_id, first_name, last_name, middle_name, login, password) VALUES
119
+ (1, 1, 'Иван', 'Иванов', 'Иванович', 'admin@gmail.com', 'admin'),
120
+ (2, 2, 'Пётр', 'Петров', 'Петрович', 'manager@gmail.com', 'manager'),
121
+ (3, 3, 'Денис', 'Денисов', 'Денисович', 'client@gmail.com', 'client');
122
+
123
+ INSERT INTO products (id, articule, name, unit_id, price, supplier_id, manufacture_id, category_id, discount, warehouse_amount, description, image_name) VALUES
124
+ (1, 'А112Т4', 'Ботинки', 1, 5590.00, 1, 1, 1, 16, 4, 'Женские ботинки демисезонные kari.', '7.jpg'),
125
+ (2, 'F635R4', 'Ботинки', 1, 2144.00, 1, 4, 1, 10, 3, 'Ботинки Marco Tozzi женские демисезонные, размер 39, цвет бежевый.', '2.jpg'),
126
+ (3, 'A674S9', 'Кроссовки', 1, 2200.00, 2, 5, 2, 10, 20, 'Универсальная модель для повседневной носки.', '6.jpg');
127
+
128
+ INSERT INTO orders (id, articule, order_date, delivery_date, pickup_point_id, user_id, code, order_status_id, product_id) VALUES
129
+ (1, 'А112Т4', '2025-02-27', '2025-04-20', 1, 1, 901, 1, 1);
130
+
131
+ INSERT INTO order_items (id, order_id, product_id, amount) VALUES
132
+ (1, 1, 1, 2);
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ from PyQt6.QtGui import QFont, QIcon
4
+ from PyQt6.QtWidgets import QApplication
5
+
6
+ from .bundle import AuthWindow
7
+
8
+
9
+ BASE_DIR = Path(__file__).resolve().parent
10
+
11
+
12
+ def main() -> None:
13
+ app = QApplication([])
14
+ app.setWindowIcon(QIcon(str(BASE_DIR / "resources/icons/Icon.ico")))
15
+
16
+ font = QFont()
17
+ font.setFamily("Times New Roman")
18
+ font.setPointSize(12)
19
+ app.setFont(font)
20
+
21
+ qss_path = BASE_DIR / "styles/common.qss"
22
+ if qss_path.exists():
23
+ app.setStyleSheet(qss_path.read_text(encoding="utf-8"))
24
+
25
+ window = AuthWindow()
26
+ window.show()
27
+ app.exec()
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,19 @@
1
+ QWidget {
2
+ background: #ffffff;
3
+ font-family: "Times New Roman";
4
+ font-size: 12pt;
5
+ }
6
+
7
+ QPushButton {
8
+ background: #7fff00;
9
+ border: 1px solid #00fa9a;
10
+ padding: 6px 12px;
11
+ }
12
+
13
+ QPushButton:hover {
14
+ background: #00fa9a;
15
+ }
16
+
17
+ QTabWidget::pane {
18
+ border: 1px solid #00fa9a;
19
+ }
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: obuv-exam
3
+ Version: 0.1.0
4
+ Summary: Demo exam project for shoe store UI
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: PyQt6>=6.0
8
+ Requires-Dist: PyMySQL>=1.1
9
+
10
+ # obuv-exam
11
+
12
+ Demo exam package for a shoe store UI project.
@@ -0,0 +1,30 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ src/obuv_exam/__init__.py
5
+ src/obuv_exam/bundle.py
6
+ src/obuv_exam/main.py
7
+ src/obuv_exam.egg-info/PKG-INFO
8
+ src/obuv_exam.egg-info/SOURCES.txt
9
+ src/obuv_exam.egg-info/dependency_links.txt
10
+ src/obuv_exam.egg-info/entry_points.txt
11
+ src/obuv_exam.egg-info/requires.txt
12
+ src/obuv_exam.egg-info/top_level.txt
13
+ src/obuv_exam/database/__init__.py
14
+ src/obuv_exam/database/db.py
15
+ src/obuv_exam/database/script.sql
16
+ src/obuv_exam/resources/icons/Icon.JPG
17
+ src/obuv_exam/resources/icons/Icon.ico
18
+ src/obuv_exam/resources/icons/Icon.png
19
+ src/obuv_exam/resources/images/1.jpg
20
+ src/obuv_exam/resources/images/10.jpg
21
+ src/obuv_exam/resources/images/2.jpg
22
+ src/obuv_exam/resources/images/3.jpg
23
+ src/obuv_exam/resources/images/4.jpg
24
+ src/obuv_exam/resources/images/5.jpg
25
+ src/obuv_exam/resources/images/6.jpg
26
+ src/obuv_exam/resources/images/7.jpg
27
+ src/obuv_exam/resources/images/8.jpg
28
+ src/obuv_exam/resources/images/9.jpg
29
+ src/obuv_exam/resources/images/picture.png
30
+ src/obuv_exam/styles/common.qss
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ obuv-exam = obuv_exam.main:main
@@ -0,0 +1,2 @@
1
+ PyQt6>=6.0
2
+ PyMySQL>=1.1
@@ -0,0 +1 @@
1
+ obuv_exam