Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Деньги сверх стоимости товара. Хранятся на продаже отдельным полем, а не
внутри платежей: иначе чаевые раздували бы «оплачено», и продажа выглядела
бы закрытой, когда за булки ещё должны.
Ввёл оплату больше суммы — булка 70, дали 100 — разница уходит в чаевые
сама. Разбор делается по окончании ввода, а не на каждом нажатии, иначе
поле дёргалось бы посреди набора. То же при приёме оплаты по долгу:
заплатили больше, чем оставалось, — излишек становится чаевыми, а не
теряется, как было раньше.
В расчётах чаевые идут в покрытие партии (это живые деньги, ими так же
рассчитываются с пекарней) и целиком добавляются в прибыль, но не входят
в выручку и не создают долга. Продажа, списанная с нескольких партий,
делит чаевые между ними пропорционально выручке.
По съеденному и подаренному чаевых не бывает — поле отключается.
Миграция схемы 2→3 проставляет ноль: у кого чаевых нет, тот появления
этой фичи не заметит.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Цену было невозможно набрать. В прошлой правке я стал вырезать пробелы
из вводимого текста, чтобы принимались разделители разрядов, — но у поля
цены суффикс « ₽» начинается с пробела. Каждое нажатие ломало суффикс,
Qt отвергало символ, и в поле оставалась только первая цифра: набираешь
«25,50», получаешь 2.
Теперь текст на каждом нажатии не трогается вообще: сначала пробуем как
есть, и только если Qt отверг именно из-за точки — повторяем с запятой.
Второе: форма продажи подключала сигнал редактора строк раньше, чем
создавала поле оплаты. Qt глотает исключения в слотах, поэтому вместо
падения оплата просто молча не подставлялась.
Третье: выделение в таблицах держалось за номер строки. Таблицы
отсортированы, и после перерисовки под тем же номером оказывался другой
объект — можно было удалить не тот товар. Теперь выделение
восстанавливается по идентификатору.
Тесты: настоящие нажатия клавиш через QTest вместо проверок validate(),
которые этот баг пропустили, и перехват sys.excepthook, чтобы
проглоченные Qt исключения считались провалом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Приложение создавало GitSync от paths.app_root(). Пока база лежала внутри
репозитория с кодом, это работало; после разделения репозиториев — нет.
exe лежит в корне клона с исходниками, поэтому приложение распоряжалось
репозиторием кода: переписало ему origin на food-records и попыталось
отправить туда исходники. Пуш спас только отказ по fast-forward.
Теперь GitSync создаётся в одном месте — MainWindow._git() — и всегда
от paths.data_dir(). Тест проверяет и цель, и то, что других мест
создания GitSync в файле не осталось.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Количества и раньше хранились в Decimal с тремя знаками, но до документа
они не доходили: поля ввода отвергали точку. Интерфейс русский, локаль
ждёт запятую, а на цифровой клавиатуре клавиша даёт точку — символ молча
не появлялся, и выглядело это так, будто дробные вводить нельзя вообще.
Теперь оба разделителя принимаются, пробелы-разделители разрядов тоже.
Заодно исправлена ошибка, которую дробные вскрыли: в сводке складывалось
общее количество остатка по всем товарам, то есть литры с килограммами
и штуками. Теперь остаток выражен деньгами и числом позиций, а разбивка
по единицам осталась в таблице «Остатки».
Фасовки тоже могут быть дробными: бутылка 1,5 л.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три правки по замечаниям.
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>
Десктопное приложение на PySide6. Вся база — один JSON-документ,
зашифрованный AES-256-GCM под паролем (ключ через scrypt), лежит
в data/vault.fmdb и раз в час уезжает в этот репозиторий.
Основное:
- партии с дедлайном возврата себестоимости пекарне, FIFO-разнос продаж
по партиям и прогресс покрытия к сроку;
- типы выбытия: розница, другу по себестоимости, съел сам, подарок,
списание — съеденное вычитается из прибыли, за него платить всё равно;
- долги контрагентов с частичными оплатами;
- номенклатура с историей изменения цен;
- журнал изменений внутри базы: git хранит непрозрачные снимки,
поэтому настоящая история ведётся здесь.
Синхронизация коммитит только путь базы и схлопывает часовые пуши
в один коммит на день через amend + force-with-lease.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>