2026-08-22 21:29:53 +07:00
|
|
|
|
"""Тёмная тема.
|
|
|
|
|
|
|
|
|
|
|
|
Палитра задаётся через QPalette, а не только через QSS: иначе системные
|
|
|
|
|
|
диалоги и всплывающие подсказки остались бы светлыми и резали глаза.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
1. База и код разъехались по разным репозиториям. Автокоммит и раньше
трогал только vault.fmdb, но лежал он в той же ветке, что и исходники,
поэтому пуш тащил всю историю программы. Теперь data/ — самостоятельный
клон food-records со своим .git, а исходники его игнорируют целиком.
При первом запуске приложение встаёт на уже существующую историю
сервера, а не заводит параллельную.
2. Фасовки товара. Пачка печенья 10 шт за 200 ₽ и та же печенька поштучно
за 30 ₽ — один товар с двумя фасовками. Остатки, себестоимость и FIFO
считаются в базовых единицах, поэтому поштучные продажи вычитаются из
купленных пачек. Размер фасовки хранится в документе слепком.
Миграция схемы 1→2 не меняет поведение уже заведённых данных.
3. Быстрый ввод продаж за период: выбор диапазона дат, строка таблицы —
отдельная продажа, ввод с клавиатуры, запись всех разом. Незнакомое имя
заводится как контрагент, повтор периода подсвечивается.
Тесты больше не имеют настроенного remote, чтобы не ходить в сеть.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 01:25:39 +07:00
|
|
|
|
from PySide6.QtCore import QLocale, Qt
|
2026-08-22 21:29:53 +07:00
|
|
|
|
from PySide6.QtGui import QColor, QPalette
|
|
|
|
|
|
|
|
|
|
|
|
BG = "#1c1f24"
|
|
|
|
|
|
BG_ALT = "#23272e"
|
|
|
|
|
|
SURFACE = "#2b3038"
|
|
|
|
|
|
BORDER = "#3a4048"
|
|
|
|
|
|
TEXT = "#e6e8ea"
|
|
|
|
|
|
TEXT_DIM = "#98a0aa"
|
|
|
|
|
|
ACCENT = "#4c9aff"
|
|
|
|
|
|
|
|
|
|
|
|
OK = "#4caf7d"
|
|
|
|
|
|
WARN = "#d9a441"
|
|
|
|
|
|
DANGER = "#e0605e"
|
|
|
|
|
|
MUTED = "#6c757d"
|
|
|
|
|
|
|
|
|
|
|
|
STATUS_COLORS = {
|
|
|
|
|
|
"settled": OK,
|
|
|
|
|
|
"open": ACCENT,
|
|
|
|
|
|
"due_soon": WARN,
|
|
|
|
|
|
"overdue": DANGER,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply(app) -> None:
|
|
|
|
|
|
app.setStyle("Fusion")
|
|
|
|
|
|
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
1. База и код разъехались по разным репозиториям. Автокоммит и раньше
трогал только vault.fmdb, но лежал он в той же ветке, что и исходники,
поэтому пуш тащил всю историю программы. Теперь data/ — самостоятельный
клон food-records со своим .git, а исходники его игнорируют целиком.
При первом запуске приложение встаёт на уже существующую историю
сервера, а не заводит параллельную.
2. Фасовки товара. Пачка печенья 10 шт за 200 ₽ и та же печенька поштучно
за 30 ₽ — один товар с двумя фасовками. Остатки, себестоимость и FIFO
считаются в базовых единицах, поэтому поштучные продажи вычитаются из
купленных пачек. Размер фасовки хранится в документе слепком.
Миграция схемы 1→2 не меняет поведение уже заведённых данных.
3. Быстрый ввод продаж за период: выбор диапазона дат, строка таблицы —
отдельная продажа, ввод с клавиатуры, запись всех разом. Незнакомое имя
заводится как контрагент, повтор периода подсвечивается.
Тесты больше не имеют настроенного remote, чтобы не ходить в сеть.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 01:25:39 +07:00
|
|
|
|
# Иначе поля ввода показывают «1,800.00», а таблицы рядом — «1 800,00».
|
|
|
|
|
|
QLocale.setDefault(QLocale(QLocale.Russian, QLocale.Russia))
|
|
|
|
|
|
|
2026-08-22 21:29:53 +07:00
|
|
|
|
palette = QPalette()
|
|
|
|
|
|
palette.setColor(QPalette.Window, QColor(BG))
|
|
|
|
|
|
palette.setColor(QPalette.WindowText, QColor(TEXT))
|
|
|
|
|
|
palette.setColor(QPalette.Base, QColor(BG_ALT))
|
|
|
|
|
|
palette.setColor(QPalette.AlternateBase, QColor(SURFACE))
|
|
|
|
|
|
palette.setColor(QPalette.Text, QColor(TEXT))
|
|
|
|
|
|
palette.setColor(QPalette.Button, QColor(SURFACE))
|
|
|
|
|
|
palette.setColor(QPalette.ButtonText, QColor(TEXT))
|
|
|
|
|
|
palette.setColor(QPalette.ToolTipBase, QColor(SURFACE))
|
|
|
|
|
|
palette.setColor(QPalette.ToolTipText, QColor(TEXT))
|
|
|
|
|
|
palette.setColor(QPalette.Highlight, QColor(ACCENT))
|
|
|
|
|
|
palette.setColor(QPalette.HighlightedText, QColor("#ffffff"))
|
|
|
|
|
|
palette.setColor(QPalette.PlaceholderText, QColor(TEXT_DIM))
|
|
|
|
|
|
palette.setColor(QPalette.Link, QColor(ACCENT))
|
|
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.Text, QColor(MUTED))
|
|
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(MUTED))
|
|
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(MUTED))
|
|
|
|
|
|
app.setPalette(palette)
|
|
|
|
|
|
|
|
|
|
|
|
app.setStyleSheet(QSS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QSS = f"""
|
|
|
|
|
|
QWidget {{
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
QLabel[role="h1"] {{ font-size: 21px; font-weight: 600; }}
|
|
|
|
|
|
QLabel[role="h2"] {{ font-size: 15px; font-weight: 600; }}
|
|
|
|
|
|
QLabel[role="dim"] {{ color: {TEXT_DIM}; }}
|
|
|
|
|
|
QLabel[role="metric"] {{ font-size: 24px; font-weight: 600; }}
|
|
|
|
|
|
|
|
|
|
|
|
QFrame[role="card"] {{
|
|
|
|
|
|
background: {SURFACE};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
QFrame[role="banner"] {{
|
|
|
|
|
|
background: #3a2f1c;
|
|
|
|
|
|
border: 1px solid {WARN};
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
QListWidget#sidebar {{
|
|
|
|
|
|
background: {BG_ALT};
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-right: 1px solid {BORDER};
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QListWidget#sidebar::item {{
|
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
|
|
margin: 1px 6px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
color: {TEXT_DIM};
|
|
|
|
|
|
}}
|
|
|
|
|
|
QListWidget#sidebar::item:selected {{
|
|
|
|
|
|
background: {SURFACE};
|
|
|
|
|
|
color: {TEXT};
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QListWidget#sidebar::item:hover {{ color: {TEXT}; }}
|
|
|
|
|
|
|
|
|
|
|
|
QPushButton {{
|
|
|
|
|
|
background: {SURFACE};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 6px 14px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QPushButton:hover {{ border-color: {ACCENT}; }}
|
|
|
|
|
|
QPushButton:disabled {{ color: {MUTED}; border-color: {BORDER}; }}
|
|
|
|
|
|
QPushButton[role="primary"] {{
|
|
|
|
|
|
background: {ACCENT};
|
|
|
|
|
|
border-color: {ACCENT};
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QPushButton[role="primary"]:hover {{ background: #5aa6ff; }}
|
|
|
|
|
|
QPushButton[role="danger"]:hover {{ border-color: {DANGER}; color: {DANGER}; }}
|
|
|
|
|
|
|
|
|
|
|
|
QLineEdit, QDateEdit, QComboBox, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit {{
|
|
|
|
|
|
background: {BG_ALT};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 5px 8px;
|
|
|
|
|
|
selection-background-color: {ACCENT};
|
|
|
|
|
|
}}
|
|
|
|
|
|
QLineEdit:focus, QDateEdit:focus, QComboBox:focus,
|
|
|
|
|
|
QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus {{
|
|
|
|
|
|
border-color: {ACCENT};
|
|
|
|
|
|
}}
|
|
|
|
|
|
QComboBox::drop-down {{ border: none; width: 18px; }}
|
|
|
|
|
|
QComboBox QAbstractItemView {{
|
|
|
|
|
|
background: {BG_ALT};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
selection-background-color: {ACCENT};
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
QTableWidget, QTreeWidget {{
|
|
|
|
|
|
background: {BG_ALT};
|
|
|
|
|
|
alternate-background-color: {SURFACE};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
gridline-color: {BORDER};
|
|
|
|
|
|
selection-background-color: {ACCENT};
|
|
|
|
|
|
}}
|
|
|
|
|
|
QHeaderView::section {{
|
|
|
|
|
|
background: {SURFACE};
|
|
|
|
|
|
color: {TEXT_DIM};
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-bottom: 1px solid {BORDER};
|
|
|
|
|
|
padding: 7px 8px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QTableWidget::item, QTreeWidget::item {{ padding: 4px 6px; }}
|
|
|
|
|
|
|
|
|
|
|
|
QProgressBar {{
|
|
|
|
|
|
background: {BG};
|
|
|
|
|
|
border: 1px solid {BORDER};
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: {TEXT};
|
|
|
|
|
|
}}
|
|
|
|
|
|
QProgressBar::chunk {{ border-radius: 5px; }}
|
|
|
|
|
|
|
|
|
|
|
|
QStatusBar {{ border-top: 1px solid {BORDER}; }}
|
|
|
|
|
|
QStatusBar::item {{ border: none; }}
|
|
|
|
|
|
|
|
|
|
|
|
QScrollBar:vertical {{ background: transparent; width: 10px; margin: 0; }}
|
|
|
|
|
|
QScrollBar::handle:vertical {{ background: {BORDER}; border-radius: 5px; min-height: 30px; }}
|
|
|
|
|
|
QScrollBar::handle:vertical:hover {{ background: {MUTED}; }}
|
|
|
|
|
|
QScrollBar::add-line, QScrollBar::sub-line {{ height: 0; width: 0; }}
|
|
|
|
|
|
QScrollBar:horizontal {{ background: transparent; height: 10px; }}
|
|
|
|
|
|
QScrollBar::handle:horizontal {{ background: {BORDER}; border-radius: 5px; min-width: 30px; }}
|
|
|
|
|
|
|
|
|
|
|
|
QTabWidget::pane {{ border: 1px solid {BORDER}; border-radius: 6px; }}
|
|
|
|
|
|
QTabBar::tab {{
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
padding: 7px 14px;
|
|
|
|
|
|
color: {TEXT_DIM};
|
|
|
|
|
|
border-bottom: 2px solid transparent;
|
|
|
|
|
|
}}
|
|
|
|
|
|
QTabBar::tab:selected {{ color: {TEXT}; border-bottom-color: {ACCENT}; }}
|
|
|
|
|
|
"""
|