mirror of
https://github.com/Horizontal42/warehouse-master.git
synced 2026-11-10 14:52:02 +03:00
117 lines
8.9 KiB
Markdown
117 lines
8.9 KiB
Markdown
[English](ARCHITECTURE.md)
|
||
|
||
# Архитектура
|
||
|
||
## Обзор
|
||
|
||
Warehouse Master — десктопное приложение для печати нумерованных этикеток в PDF и синхронизации пересборки ноутбуков через Google Sheets. Два независимых таба: Labels (генерация PDF) и Reassembly (синхронизация с таблицей).
|
||
|
||
## Структура файлов
|
||
|
||
| Файл | Назначение |
|
||
|------|-----------|
|
||
| **main.py** | Entry point, главное окно `LabelApp`, жизненный цикл табов, dispatch i18n |
|
||
| **printing.py** | Утилиты: файловый ввод-вывод, пути к ресурсам, обнаружение принтеров, папка конфига |
|
||
| **styles.py** | Генератор стилей: `build_stylesheet(dark: bool)` → возвращает CSS для обеих тем |
|
||
| **workers.py** | Фоновые потоки: `PDFWorker` (reportlab), `ReassemblyWorker` (синхронизация gspread) |
|
||
| **widgets.py** | Переиспользуемые UI компоненты: `NoScrollComboBox`, `RangeRowWidget`, `PreferencesDialog` |
|
||
| **strings.py** | i18n: словарь `STRINGS` `{"en": {...}, "ru": {...}}` со всеми строками UI |
|
||
| **sheet_manager.py** | Обёртка Google Sheets API: конфиг-управляемые индексы колонок, вычисление категории, батч-обновления |
|
||
| **sheet_config.example.json** | Шаблон конфига: листы, маппинги колонок, переводчики типов, статусы |
|
||
| **WarehouseMaster.spec** | Спек PyInstaller: бандлит Inter.ttf, собирает single-file exe |
|
||
| **requirements.txt** | Зависимости: PyQt5, reportlab, gspread, google-auth |
|
||
|
||
## Ответственность модулей
|
||
|
||
### main.py
|
||
- Загружает/сохраняет QSettings (тема, язык, состояние окна)
|
||
- Рендерит два таба: labels и reassembly
|
||
- Подключает обработчики событий
|
||
- Вызывает `_retranslate()` при смене языка
|
||
- Очистка временных файлов при закрытии
|
||
|
||
### printing.py
|
||
- `resource_path()` — ищет бандлированные файлы (Inter.ttf, icon.ico)
|
||
- `app_data_dir()` — `%APPDATA%/WarehouseMaster/` для token.json, локальных конфигов
|
||
- `config_path()` — возвращает sheet_config.json (локальный или из appdata)
|
||
- `register_fonts()` — загружает Inter.ttf в reportlab
|
||
- `get_installed_printers()` — перечисляет принтеры Windows (win32api)
|
||
- `open_file()` — кросс-платформенный открыватель файлов (Windows/macOS/Linux)
|
||
- `print_file()` — печать на конкретный принтер или системный по умолчанию
|
||
|
||
### styles.py
|
||
- `build_stylesheet(dark: bool)` — одна функция, возвращает полный CSS палитры
|
||
- Цвета захардкожены (dark: #212529 фон, #f8f9fa текст; light: инверсия)
|
||
- Вызывается при переключении темы → `setStyleSheet()`
|
||
|
||
### workers.py
|
||
- `PDFWorker(QThread)` — генерирует PDF в фоне, эмитит `finished(filepath)` или `error_occurred(str)`
|
||
- `ReassemblyWorker(QThread)` — вызывает `SheetManager.process_reassembly()`, эмитит `finished_signal(comment, tech_op)` или `error_signal(str)`
|
||
- Оба предотвращают блокировку UI
|
||
|
||
### widgets.py
|
||
- `NoScrollComboBox` — подавляет скроллинг на колесике мыши
|
||
- `RangeRowWidget` — поля start/end + кнопки +/-; управляется callback-ами
|
||
- `PreferencesDialog` — язык + тема + URL таблицы + папка сохранения, сохраняет в QSettings
|
||
|
||
### strings.py
|
||
- Два словаря: `STRINGS["en"]`, `STRINGS["ru"]`
|
||
- Ключи: `"window_title"`, `"tab_labels"`, шаблоны сообщений с `{n}` плейсхолдерами
|
||
- Никакой логики переключения локали — main.py читает `settings.value("locale")` и индексирует
|
||
|
||
### sheet_manager.py
|
||
- `SheetManager(sheet_url, config_path)` — загружает config.json, авторизует gspread
|
||
- `process_reassembly(laptop_id, parts_str, ssd_id, akb_id)` — атомарно обновляет: отмечает запчасти отгруженными, пишет грейды в строку ноутбука, пересчитывает категорию (D/C/B/A/NEW)
|
||
- `_calculate_category(laptop_data)` — правило-based скоринг (D если любая деталь ≤rank 1, NEW если все ≥A и cycles < 150 и т.д.)
|
||
- Управляется конфигом: все индексы колонок, range clears, значения статусов из sheet_config.json
|
||
|
||
## Горячие пути
|
||
|
||
### Генерация PDF
|
||
1. Пользователь нажимает «Generate PDF»
|
||
2. `_start_generation()` валидирует диапазоны (start ≤ end), спавнит `PDFWorker`
|
||
3. `PDFWorker.run()` → `reportlab.canvas` рисует этикетки в сеточном макете
|
||
4. При успехе: `_on_pdf_done()` → открыть файл или печать
|
||
5. UI разблокирован (рабочий поток)
|
||
|
||
### Синхронизация пересборки
|
||
1. Пользователь заполняет заказ/запчасти, нажимает «Apply changes»
|
||
2. `_run_reassembly()` спавнит `ReassemblyWorker`
|
||
3. `ReassemblyWorker.run()` → `SheetManager.process_reassembly()`
|
||
4. Manager: фетчит ID из всех листов, обрабатывает каждую запчасть (SSD/АКБ/запчасть), вычисляет новую категорию
|
||
5. Батч-обновляет все листы, возвращает comment + tech строки
|
||
6. `_on_reassembly_done()` отображает результаты
|
||
7. UI разблокирован
|
||
|
||
## Добавление функциональности
|
||
|
||
### Новый формат этикетки (например 6×6 сетка)
|
||
1. Добавить в `PAGE_SIZES` и `DEFAULT_FONT_SIZES` в workers.py
|
||
2. Добавить radio button в `_init_gen_tab()`
|
||
3. Добавить ветку в `PDFWorker.run()` для макета сетки
|
||
|
||
### Новое поле пересборки
|
||
1. Добавить ключ конфига в sheet_config.example.json (например `"warranty_months": 52`)
|
||
2. Прочитать в `SheetManager.__init__()` через self.cfg
|
||
3. Добавить обработку в `_process_part()` или новый `_process_<thing>()` метод
|
||
|
||
### Новый язык
|
||
1. Добавить `STRINGS["new_lang"]` словарь в strings.py
|
||
2. Добавить в языковую комбобокс в PreferencesDialog (widgets.py)
|
||
3. Маппировать индекс комбо → код локали (например index 2 → "de")
|
||
|
||
### Новый лист Google Sheets
|
||
1. Добавить в `sheet_config.json` → `worksheets`
|
||
2. Добавить маппинги колонок если нужно
|
||
3. Добавить fetch/batch в методы `SheetManager`
|
||
4. Подключить в `process_reassembly()`
|
||
|
||
## Подводные камни
|
||
|
||
- **QSettings кейс:** На Linux имена файлов case-sensitive; использовать lowercase ключи (`"sheet_url"` не `"SheetURL"`)
|
||
- **Обновление токена:** Если учетные данные gspread истекают во время сессии, рабочий поток упадёт; пользователь должен перезапустить и переавторизоваться
|
||
- **Выравнивание колонок:** В reportlab canvas Y-ось направлена вверх (0 = внизу). Сеточный макет правильно смещает Y через `ph - (row + 1) * cell_h`
|
||
- **Конфиг отсутствует:** Если sheet_config.json не найден, SheetManager выбросит на инит; обработать в UI с try-except, показать понятное сообщение
|
||
- **Кириллические грейды:** Перед проверкой ранга нормализовать кириллицу А/В/С/Д → латинь A/B/C/D через `str.maketrans()`
|
||
- **Принтер не найден:** Если сохранённый принтер удалён, `findText()` вернёт -1; guard перед `setCurrentIndex()`
|
||
- **Временные файлы:** Если приложение упадёт, временные PDF не удалятся; рассмотреть OS-уровневую очистку tmp директорий (автоматическая purge)
|