2026-08-22 21:29:53 +07:00
|
|
|
|
# Булочная
|
|
|
|
|
|
|
|
|
|
|
|
Десктопный учёт закупки и перепродажи булок. Вся база — один файл,
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
зашифрованный паролем, который раз в час уезжает в отдельный репозиторий.
|
|
|
|
|
|
|
|
|
|
|
|
**Два репозитория, и это принципиально:**
|
|
|
|
|
|
|
|
|
|
|
|
| репозиторий | что внутри |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `gt.ser.gay/kizya/food-market` | исходники программы (этот) |
|
|
|
|
|
|
| `gt.ser.gay/kizya/food-records` | только `vault.fmdb` — зашифрованная база |
|
|
|
|
|
|
|
|
|
|
|
|
Смешивать их нельзя: `git push` отправляет ветку целиком, поэтому в общем
|
|
|
|
|
|
репозитории история данных неизбежно тащила бы за собой историю программы.
|
|
|
|
|
|
На диске это папка `data/` рядом с exe — самостоятельный клон репозитория
|
|
|
|
|
|
с данными, со своим `.git`. Исходники её полностью игнорируют.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
## Что считает
|
|
|
|
|
|
|
|
|
|
|
|
Булки берутся в пекарне **в долг**: к дедлайну надо вернуть себестоимость
|
|
|
|
|
|
партии, иначе новых не напекут. Наценка остаётся тебе. Часть булок уходит
|
|
|
|
|
|
друзьям по себестоимости, часть съедается — и за них пекарне тоже платить.
|
|
|
|
|
|
|
|
|
|
|
|
Программа отвечает на главный вопрос: **успеваю ли я собрать себестоимость
|
|
|
|
|
|
конкретной партии до её срока**.
|
|
|
|
|
|
|
|
|
|
|
|
- **Закупки** — партии с составом, ценами и датой, до которой надо рассчитаться.
|
|
|
|
|
|
- **Продажи** — розница, «другу по себестоимости», «съел сам», подарок, списание.
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
Цена подставляется по типу. Есть быстрый ввод пачки продаж за прошедший период.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
- **Долги** — кто сколько должен, с частичными оплатами.
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
- **Товары** — номенклатура с фасовками и историей изменения цен.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
- **Журнал** — что менялось в базе, когда и с какой машины.
|
|
|
|
|
|
- **Сводка** — долг пекарне, дебиторка, прибыль, остатки, ближайший дедлайн.
|
|
|
|
|
|
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
### Фасовки
|
|
|
|
|
|
|
|
|
|
|
|
Пачка печенья 10 шт за 200 ₽ и та же печенька поштучно за 30 ₽ — это **один
|
|
|
|
|
|
товар с двумя фасовками**, а не два разных товара. У товара есть базовая
|
|
|
|
|
|
единица (штука) и любое число фасовок со своим размером и своей ценой.
|
|
|
|
|
|
|
|
|
|
|
|
Остатки, себестоимость и FIFO всегда считаются в базовых единицах, поэтому
|
|
|
|
|
|
поштучные продажи корректно вычитаются из купленных пачек. Количество и цена
|
|
|
|
|
|
при этом вводятся в той фасовке, которую выбрал ты: «2 пачки по 200 ₽» так
|
|
|
|
|
|
и остаётся в документе. Размер фасовки сохраняется слепком — переопределишь
|
|
|
|
|
|
пачку с 10 на 12 штук, и уже записанные документы не поедут.
|
|
|
|
|
|
|
|
|
|
|
|
### Ввод истории
|
|
|
|
|
|
|
|
|
|
|
|
Кнопка «Быстрый ввод за период» на экране продаж: задаёшь диапазон дат,
|
|
|
|
|
|
каждая строка таблицы — отдельная продажа. Дата новой строки наследуется от
|
|
|
|
|
|
предыдущей, Enter добавляет строку, Ctrl+D дублирует, Ctrl+Enter записывает
|
|
|
|
|
|
всё разом. Незнакомое имя в колонке «Кому» заводится как новый контрагент.
|
|
|
|
|
|
Программа подсказывает, сколько продаж за выбранный период уже записано —
|
|
|
|
|
|
чтобы не внести один и тот же месяц дважды.
|
|
|
|
|
|
|
2026-08-22 21:29:53 +07:00
|
|
|
|
Продажи разносятся по партиям методом FIFO. Цены у поставщика фиксированные,
|
|
|
|
|
|
поэтому на себестоимость это не влияет — FIFO нужен только чтобы понимать,
|
|
|
|
|
|
деньги за какую партию уже пришли.
|
|
|
|
|
|
|
|
|
|
|
|
Прибыль считается как наценка с проданного **минус** себестоимость съеденного
|
|
|
|
|
|
и подаренного: за них пекарне платить всё равно, и покрывается это из маржи.
|
|
|
|
|
|
|
|
|
|
|
|
## Как запустить
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pip install -r requirements.txt
|
|
|
|
|
|
python run.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Собрать один exe:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pip install -r requirements-dev.txt
|
|
|
|
|
|
pyinstaller food-market.spec
|
|
|
|
|
|
```
|
|
|
|
|
|
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
Готовый `dist/food-market.exe` (~50 МБ) можно положить куда угодно — базу он
|
|
|
|
|
|
ищет в папке `data/` рядом с собой и при первом запуске сам заведёт там клон
|
|
|
|
|
|
репозитория с данными.
|
|
|
|
|
|
|
|
|
|
|
|
На новой машине быстрее склонировать данные сразу:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
git clone https://gt.ser.gay/kizya/food-records.git data
|
|
|
|
|
|
```
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
## Хранение и шифрование
|
|
|
|
|
|
|
|
|
|
|
|
`data/vault.fmdb` — единственный файл с данными:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
JSON → gzip → AES-256-GCM (ключ: scrypt от пароля) → файл
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Заголовок файла идёт в GCM как AAD, так что подменить соль или параметры KDF
|
|
|
|
|
|
незаметно нельзя. Запись атомарная, предыдущая версия остаётся в `.bak`.
|
|
|
|
|
|
|
|
|
|
|
|
**Забытый пароль восстановить невозможно.** В настройках есть кнопка выгрузки
|
|
|
|
|
|
открытого JSON — держи такую копию отдельно. В ней лежит и токен GitLab.
|
|
|
|
|
|
|
|
|
|
|
|
## Синхронизация
|
|
|
|
|
|
|
|
|
|
|
|
Раз в час (интервал настраивается) приложение сохраняет базу, коммитит
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
**только** `vault.fmdb` и пушит — в репозиторий данных, не в этот. Что бы ни
|
|
|
|
|
|
оказалось в папке рядом с базой, в коммит оно не попадёт: все команды идут
|
|
|
|
|
|
с явным списком путей.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
Токен GitLab хранится внутри зашифрованной базы и подставляется в URL только на
|
|
|
|
|
|
время вызова — в `.git/config` он не пишется и в журнал не попадает.
|
|
|
|
|
|
|
|
|
|
|
|
### Один коммит на день
|
|
|
|
|
|
|
|
|
|
|
|
Дельта-сжатие на шифртексте не работает: каждый коммит хранит полную копию
|
|
|
|
|
|
файла. При часовых пушах это ~1800 коммитов и порядка 60 МБ в год. Поэтому в
|
|
|
|
|
|
течение суток приложение дописывает в ту же вершину через `--amend` — выходит
|
|
|
|
|
|
около 13 МБ в год.
|
|
|
|
|
|
|
|
|
|
|
|
Форс-пуш делается только с `--force-with-lease` и только когда все условия
|
|
|
|
|
|
выполнены: вершина имеет наш формат сообщения, датирована сегодня, трогает ровно
|
|
|
|
|
|
`data/vault.fmdb` и совпадает с тем, что на сервере. Иначе — обычный коммит без
|
|
|
|
|
|
переписывания истории.
|
|
|
|
|
|
|
|
|
|
|
|
Плата за схлопывание: откатиться через git можно на границу суток, а не на любой
|
|
|
|
|
|
час. Детализацию внутри дня даёт журнал.
|
|
|
|
|
|
|
|
|
|
|
|
### Работа с двух машин
|
|
|
|
|
|
|
|
|
|
|
|
Слить два зашифрованных блоба автоматически невозможно. Если версии разошлись,
|
|
|
|
|
|
автопуш останавливается, версия с сервера кладётся рядом как
|
|
|
|
|
|
`data/vault.remote.fmdb`, а выбор предлагается в настройках. Ничего не
|
|
|
|
|
|
затирается молча.
|
|
|
|
|
|
|
|
|
|
|
|
## Разработка
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pytest -q
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Модули:
|
|
|
|
|
|
|
|
|
|
|
|
| файл | зачем |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `app/crypto.py` | формат файла, scrypt + AES-GCM |
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
| `app/models.py` | структура документа, фасовки, деньги в Decimal |
|
2026-08-22 21:29:53 +07:00
|
|
|
|
| `app/storage.py` | загрузка, атомарная запись, миграции схемы, хеш «грязности» |
|
|
|
|
|
|
| `app/journal.py` | **единственный путь записи** в базу + аудит-лог |
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
| `app/ledger.py` | FIFO в базовых единицах, покрытие партий, долги, остатки |
|
2026-08-22 21:29:53 +07:00
|
|
|
|
| `app/gitsync.py` | git, схлопывание коммитов, разрешение расхождений |
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
| `app/paths.py` | где лежат данные и почему отдельно от кода |
|
|
|
|
|
|
| `app/ui/quick_sales.py` | быстрый ввод продаж за период |
|
|
|
|
|
|
| `app/ui/` | остальные экраны на PySide6 |
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
Главное архитектурное правило: **ни один экран не меняет документ напрямую**.
|
|
|
|
|
|
Всё идёт через функции `app/journal.py`, которые сами считают разницу и
|
|
|
|
|
|
дописывают запись в журнал. Иначе новый экран однажды забыл бы залогировать
|
|
|
|
|
|
изменение, и заметили бы это только когда история понадобится.
|