food-market/README.md
Claude c8b9f5048b Учёт закупки и перепродажи булок
Десктопное приложение на PySide6. Вся база — один JSON-документ,
зашифрованный AES-256-GCM под паролем (ключ через scrypt), лежит
в data/vault.fmdb и раз в час уезжает в этот репозиторий.

Основное:
- партии с дедлайном возврата себестоимости пекарне, FIFO-разнос продаж
  по партиям и прогресс покрытия к сроку;
- типы выбытия: розница, другу по себестоимости, съел сам, подарок,
  списание — съеденное вычитается из прибыли, за него платить всё равно;
- долги контрагентов с частичными оплатами;
- номенклатура с историей изменения цен;
- журнал изменений внутри базы: git хранит непрозрачные снимки,
  поэтому настоящая история ведётся здесь.

Синхронизация коммитит только путь базы и схлопывает часовые пуши
в один коммит на день через amend + force-with-lease.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 17:29:53 +03:00

6.8 KiB
Raw Blame History

Булочная

Десктопный учёт закупки и перепродажи булок. Вся база — один файл, зашифрованный паролем, который раз в час уезжает в этот репозиторий.

Что считает

Булки берутся в пекарне в долг: к дедлайну надо вернуть себестоимость партии, иначе новых не напекут. Наценка остаётся тебе. Часть булок уходит друзьям по себестоимости, часть съедается — и за них пекарне тоже платить.

Программа отвечает на главный вопрос: успеваю ли я собрать себестоимость конкретной партии до её срока.

  • Закупки — партии с составом, ценами и датой, до которой надо рассчитаться.
  • Продажи — розница, «другу по себестоимости», «съел сам», подарок, списание. Цена подставляется по типу.
  • Долги — кто сколько должен, с частичными оплатами.
  • Товары — номенклатура с историей изменения цен.
  • Журнал — что менялось в базе, когда и с какой машины.
  • Сводка — долг пекарне, дебиторка, прибыль, остатки, ближайший дедлайн.

Продажи разносятся по партиям методом FIFO. Цены у поставщика фиксированные, поэтому на себестоимость это не влияет — FIFO нужен только чтобы понимать, деньги за какую партию уже пришли.

Прибыль считается как наценка с проданного минус себестоимость съеденного и подаренного: за них пекарне платить всё равно, и покрывается это из маржи.

Как запустить

pip install -r requirements.txt
python run.py

Собрать один exe:

pip install -r requirements-dev.txt
pyinstaller food-market.spec

Готовый dist/food-market.exe (~50 МБ) положи в корень клона этого репозитория. Базу он ищет в data/vault.fmdb рядом с собой, поэтому достаточно склонировать репозиторий, положить exe в корень и запустить.

Хранение и шифрование

data/vault.fmdb — единственный файл с данными:

JSON → gzip → AES-256-GCM (ключ: scrypt от пароля) → файл

Заголовок файла идёт в GCM как AAD, так что подменить соль или параметры KDF незаметно нельзя. Запись атомарная, предыдущая версия остаётся в .bak.

Забытый пароль восстановить невозможно. В настройках есть кнопка выгрузки открытого JSON — держи такую копию отдельно. В ней лежит и токен GitLab.

Синхронизация

Раз в час (интервал настраивается) приложение сохраняет базу, коммитит только data/vault.fmdb и пушит. Незакоммиченные правки исходников в автокоммит не попадают.

Токен GitLab хранится внутри зашифрованной базы и подставляется в URL только на время вызова — в .git/config он не пишется и в журнал не попадает.

Один коммит на день

Дельта-сжатие на шифртексте не работает: каждый коммит хранит полную копию файла. При часовых пушах это ~1800 коммитов и порядка 60 МБ в год. Поэтому в течение суток приложение дописывает в ту же вершину через --amend — выходит около 13 МБ в год.

Форс-пуш делается только с --force-with-lease и только когда все условия выполнены: вершина имеет наш формат сообщения, датирована сегодня, трогает ровно data/vault.fmdb и совпадает с тем, что на сервере. Иначе — обычный коммит без переписывания истории.

Плата за схлопывание: откатиться через git можно на границу суток, а не на любой час. Детализацию внутри дня даёт журнал.

Работа с двух машин

Слить два зашифрованных блоба автоматически невозможно. Если версии разошлись, автопуш останавливается, версия с сервера кладётся рядом как data/vault.remote.fmdb, а выбор предлагается в настройках. Ничего не затирается молча.

Разработка

pytest -q

Модули:

файл зачем
app/crypto.py формат файла, scrypt + AES-GCM
app/models.py структура документа, деньги в Decimal
app/storage.py загрузка, атомарная запись, миграции схемы, хеш «грязности»
app/journal.py единственный путь записи в базу + аудит-лог
app/ledger.py FIFO, покрытие партий, долги, остатки, сводка
app/gitsync.py git, схлопывание коммитов, разрешение расхождений
app/ui/ экраны на PySide6

Главное архитектурное правило: ни один экран не меняет документ напрямую. Всё идёт через функции app/journal.py, которые сами считают разницу и дописывают запись в журнал. Иначе новый экран однажды забыл бы залогировать изменение, и заметили бы это только когда история понадобится.