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
|
|
|
|
|
|
|
|
|
|
## Что считает
|
|
|
|
|
|
|
|
|
|
|
|
Булки берутся в пекарне **в долг**: к дедлайну надо вернуть себестоимость
|
|
|
|
|
|
партии, иначе новых не напекут. Наценка остаётся тебе. Часть булок уходит
|
|
|
|
|
|
друзьям по себестоимости, часть съедается — и за них пекарне тоже платить.
|
|
|
|
|
|
|
|
|
|
|
|
Программа отвечает на главный вопрос: **успеваю ли я собрать себестоимость
|
|
|
|
|
|
конкретной партии до её срока**.
|
|
|
|
|
|
|
|
|
|
|
|
- **Закупки** — партии с составом, ценами и датой, до которой надо рассчитаться.
|
|
|
|
|
|
- **Продажи** — розница, «другу по себестоимости», «съел сам», подарок, списание.
|
Фильтры и сортировка на экране продаж
Список продаж растёт быстрее всех остальных, а отбор был один — по типу.
Теперь панель: поиск, период, тип, человек, состояние оплаты. Всё
складывается, «за прошлый месяц Васе в долг» набирается тремя щелчками.
Правила отбора вынесены в app/salesfilter.py и живут без Qt: «прошлый
месяц» в январе уезжает в прошлый год, а поиск по двум словам ищет их по
отдельности — такое проверяется тестом, а не глазами по скриншоту.
Под таблицей — итоги ровно по показанному. Половина смысла фильтров
в этой строке: отобрал по человеку и сразу видишь, сколько он взял
и сколько за ним осталось.
Заодно починена сортировка по датам на продажах и закупках.
Отформатированная дата — обычная строка, и по алфавиту она сортируется
по дню месяца: 01.12.2025 оказывалась раньше 02.01.2020. Выглядело
рабочим, просто выдавало бессмыслицу.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:58:52 +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
|
|
|
|
- **Товары** — номенклатура с фасовками и историей изменения цен.
|
Раздел статистики
Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:33:11 +07:00
|
|
|
|
- **Статистика** — периоды, недели, месяцы, годы, разрезы по товарам и людям.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
- **Журнал** — что менялось в базе, когда и с какой машины.
|
|
|
|
|
|
- **Сводка** — долг пекарне, дебиторка, прибыль, остатки, ближайший дедлайн.
|
|
|
|
|
|
|
Раздел статистики
Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:33:11 +07:00
|
|
|
|
### Статистика
|
|
|
|
|
|
|
|
|
|
|
|
Шесть разрезов, у каждого график и таблица с итоговой строкой.
|
|
|
|
|
|
|
|
|
|
|
|
| Разрез | Что показывает |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| **Периоды** | Каждая закупка — период со своим сроком. Долг, отдано, собрано, покрытие, прибыль, статус |
|
|
|
|
|
|
| **Недели** | По ISO-неделям, понедельник–воскресенье |
|
|
|
|
|
|
| **Месяцы** | Календарные месяцы |
|
|
|
|
|
|
| **Годы** | Календарные годы |
|
|
|
|
|
|
| **Товары** | Кто кормит, а кто лежит: наценка, наценка %, съедено, остаток |
|
|
|
|
|
|
| **Люди** | Кто сколько взял, занёс, оставил чаевых и остался должен |
|
|
|
|
|
|
|
|
|
|
|
|
Пустые корзины между непустыми не создаются: если в марте не торговали,
|
|
|
|
|
|
строки за март не будет. Дырка честнее выдуманного нуля.
|
|
|
|
|
|
|
|
|
|
|
|
**База расчёта.** Смешивать «когда продал» и «когда получил деньги» нельзя —
|
|
|
|
|
|
это разные вопросы, и ответы расходятся, как только появляется хоть один долг.
|
|
|
|
|
|
Поэтому в таблице два основания одновременно:
|
|
|
|
|
|
|
|
|
|
|
|
- по дате **продажи** — выручка, себестоимость, наценка, съеденное, чаевые.
|
|
|
|
|
|
Это «сколько наторговал за месяц»;
|
|
|
|
|
|
- по дате **платежа** — колонка «Собрано». Это «сколько денег реально пришло»,
|
|
|
|
|
|
и долг, возвращённый в сентябре, попадёт в сентябрь, а не в августовскую
|
|
|
|
|
|
продажу;
|
Пекарня может простить остаток
Надо было отдать 5 500, взяли 5 000, полтысячи оставили. Долг закрыт,
но денег никто не отдавал.
У расчёта с пекарней появился вид: деньги или прощённый остаток. Свалить
их в одну кучу нельзя — тогда «Отдано пекарне» в статистике показывало бы
суммы, которых не платил. Поэтому «Отдано» — только живые деньги,
«Прощено» — отдельная колонка везде: в закупках, в сводке, в статистике
по периодам и по времени.
В прибыль прощённое идёт целиком: эти деньги предназначались пекарне,
а остались у тебя. «Осталось собрать» на ту же сумму уменьшается —
собирать под прощённый остаток уже не надо. Простить больше долга нельзя:
лишнее не засчитывается и, в отличие от переплаты деньгами, никуда
не переливается.
В диалоге платежа галочка «Остаток простили — закрыть партию»: вводишь
сколько отдал, остаток уходит отдельной записью, партия закрывается сама,
потому что её долг становится нулём.
Миграция схемы 3→4 помечает всё записанное раньше как живые деньги.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 06:44:19 +07:00
|
|
|
|
- по дате **закупки** — «Закуплено», по дате расчёта с пекарней — «Отдано
|
|
|
|
|
|
пекарне» и «Прощено».
|
Раздел статистики
Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:33:11 +07:00
|
|
|
|
|
|
|
|
|
|
У разреза «Периоды» основание третье: там всё привязано к самой партии, потому
|
|
|
|
|
|
что вопрос стоит иначе — «покрыл ли я эту партию к сроку».
|
|
|
|
|
|
|
|
|
|
|
|
Общее количество нигде не показывается одним числом — только по единицам
|
|
|
|
|
|
(«1,5 л · 4 шт»). Сумма литров с килограммами выглядит как настоящая цифра,
|
|
|
|
|
|
но смысла не имеет.
|
|
|
|
|
|
|
|
|
|
|
|
График рисуется на QPainter, а не на QtCharts: та тянет за собой десятки
|
|
|
|
|
|
мегабайт ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Если
|
|
|
|
|
|
корзин больше 24, на графике показываются последние, и об этом написано прямо
|
|
|
|
|
|
над ним — молча показанная часть выглядела бы как всё.
|
|
|
|
|
|
|
Долг как явный выбор, история цен у фасовок
Долг было непонятно как записать, и не зря. Оплата была просто денежным
полем с полной суммой: долг получался, если догадаться стереть число
и вписать ноль. Дальше — хуже. Подсказка при этом продолжала уверять, что
оплачено полностью, а любая правка строк подставляла полную сумму обратно
и молча стирала выставленный долг.
Теперь выбор явный: заплатил полностью / взял в долг целиком / заплатил
часть. Пересчёт работает только в первом режиме. Поле суммы осталось
живым — вписанное руками число само зажигает подходящий переключатель.
Долг без имени записать нельзя: на экране долгов такая запись попадает
в кучу «без контрагента», и с кого спрашивать — уже не узнать. В быстром
вводе это предупреждение, а не запрет: внося историю, можно и правда не
помнить, кто это был.
На экране долгов человека можно выбрать целиком и рассчитаться сразу за
всё: деньги приходят одной суммой, а гасятся долги по очереди, со старых.
Цены фасовок переехали в «Цены и история». Они меняются одним решением
поставщика вместе с ценой товара, а история была только у товара —
у коробки цена уезжала молча. Заодно закрыт второй вход: в карточке
товара цены фасовок теперь только показываются, правится там состав.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 04:25:49 +07:00
|
|
|
|
### Взял в долг
|
|
|
|
|
|
|
|
|
|
|
|
В форме продажи выбор рассчёта явный, тремя переключателями:
|
|
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| **Заплатил полностью** | по умолчанию, сумма подставляется сама |
|
|
|
|
|
|
| **Взял в долг целиком** | получено ноль, вся сумма уходит в долг |
|
|
|
|
|
|
| **Заплатил часть** | сколько дал — в поле, остаток в долг |
|
|
|
|
|
|
|
|
|
|
|
|
Раньше это было просто денежное поле, заполненное полной суммой: долг
|
|
|
|
|
|
получался, если догадаться стереть число и вписать ноль. Хуже того, любая
|
|
|
|
|
|
последующая правка строк подставляла полную оплату обратно — выставленный долг
|
|
|
|
|
|
исчезал молча, а подсказка продолжала уверять, что оплачено полностью.
|
|
|
|
|
|
Теперь пересчёт работает только в режиме «заплатил полностью».
|
|
|
|
|
|
|
|
|
|
|
|
Поле суммы остаётся живым: вписал число руками — загорится подходящий
|
|
|
|
|
|
переключатель. Ноль включает «в долг целиком», часть суммы — «заплатил часть»,
|
|
|
|
|
|
больше суммы — разница уходит в чаевые.
|
|
|
|
|
|
|
|
|
|
|
|
**Долг без имени записать нельзя.** Такая запись невосстановима: на экране
|
|
|
|
|
|
долгов она попадает в кучу «без контрагента», и с кого спрашивать деньги —
|
|
|
|
|
|
уже не узнать. Форма продажи не выпустит такую запись и подсветит подсказку;
|
|
|
|
|
|
контрагента можно завести кнопкой рядом с полем «Кому». В быстром вводе это
|
|
|
|
|
|
предупреждение, а не запрет: внося историю, можно и правда не помнить, кто это
|
|
|
|
|
|
был.
|
|
|
|
|
|
|
|
|
|
|
|
На экране долгов можно выбрать **человека целиком** и рассчитаться сразу за
|
|
|
|
|
|
всё: деньги приходят одной суммой, а гасятся долги по очереди, начиная со
|
|
|
|
|
|
старых. Отдал больше, чем был должен, — излишек становится чаевыми, как и
|
|
|
|
|
|
переплата по одной продаже. Выбранная отдельная продажа по-прежнему гасится
|
|
|
|
|
|
сама по себе.
|
|
|
|
|
|
|
Фильтры и сортировка на экране продаж
Список продаж растёт быстрее всех остальных, а отбор был один — по типу.
Теперь панель: поиск, период, тип, человек, состояние оплаты. Всё
складывается, «за прошлый месяц Васе в долг» набирается тремя щелчками.
Правила отбора вынесены в app/salesfilter.py и живут без Qt: «прошлый
месяц» в январе уезжает в прошлый год, а поиск по двум словам ищет их по
отдельности — такое проверяется тестом, а не глазами по скриншоту.
Под таблицей — итоги ровно по показанному. Половина смысла фильтров
в этой строке: отобрал по человеку и сразу видишь, сколько он взял
и сколько за ним осталось.
Заодно починена сортировка по датам на продажах и закупках.
Отформатированная дата — обычная строка, и по алфавиту она сортируется
по дню месяца: 01.12.2025 оказывалась раньше 02.01.2020. Выглядело
рабочим, просто выдавало бессмыслицу.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:58:52 +07:00
|
|
|
|
### Отбор продаж
|
|
|
|
|
|
|
|
|
|
|
|
Список продаж растёт быстрее всех остальных, поэтому у него своя панель
|
|
|
|
|
|
отбора: поиск, период, тип, человек и состояние оплаты. Всё складывается —
|
|
|
|
|
|
«за прошлый месяц Васе в долг» набирается тремя щелчками.
|
|
|
|
|
|
|
|
|
|
|
|
| Фильтр | Что умеет |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| **Поиск** | По товару, имени, заметке и названию типа |
|
|
|
|
|
|
| **Период** | Сегодня, неделя, месяц, прошлый месяц, 30 дней, год, свой диапазон |
|
|
|
|
|
|
| **Тип** | Розница, другу, съел сам, подарок, списание |
|
|
|
|
|
|
| **Кому** | Конкретный человек либо «без контрагента» |
|
|
|
|
|
|
| **Оплата** | С долгом, без долга, с чаевыми |
|
|
|
|
|
|
|
|
|
|
|
|
Слова в поиске ищутся по отдельности: «вася мак» находит булки с маком,
|
|
|
|
|
|
проданные Васе, хотя подряд эти слова нигде не написаны.
|
|
|
|
|
|
|
|
|
|
|
|
Поля своего диапазона появляются, только когда он выбран, — иначе две даты
|
|
|
|
|
|
занимали бы место в панели всё остальное время. Кнопка «Сбросить» гаснет,
|
|
|
|
|
|
когда сбрасывать нечего.
|
|
|
|
|
|
|
|
|
|
|
|
Под таблицей — итоги **ровно по показанному**: сколько продаж, на какую сумму,
|
|
|
|
|
|
сколько оплачено, чаевых и долга. Половина смысла фильтров в этой строке:
|
|
|
|
|
|
отобрал по человеку — сразу видно, сколько он взял и сколько за ним осталось.
|
|
|
|
|
|
|
|
|
|
|
|
Сортировка — щелчком по заголовку любой колонки, выбор переживает
|
|
|
|
|
|
перерисовку списка. По умолчанию сверху свежие.
|
|
|
|
|
|
|
|
|
|
|
|
Даты сортируются по времени, а не по тексту. Отформатированная дата — обычная
|
|
|
|
|
|
строка, и по алфавиту она идёт по дню месяца: 01.12.2025 оказывается раньше
|
|
|
|
|
|
02.01.2020. Сортировка при этом выглядит рабочей, просто выдаёт бессмыслицу,
|
|
|
|
|
|
поэтому в ячейке рядом с текстом лежит настоящее значение.
|
|
|
|
|
|
|
Пекарня может простить остаток
Надо было отдать 5 500, взяли 5 000, полтысячи оставили. Долг закрыт,
но денег никто не отдавал.
У расчёта с пекарней появился вид: деньги или прощённый остаток. Свалить
их в одну кучу нельзя — тогда «Отдано пекарне» в статистике показывало бы
суммы, которых не платил. Поэтому «Отдано» — только живые деньги,
«Прощено» — отдельная колонка везде: в закупках, в сводке, в статистике
по периодам и по времени.
В прибыль прощённое идёт целиком: эти деньги предназначались пекарне,
а остались у тебя. «Осталось собрать» на ту же сумму уменьшается —
собирать под прощённый остаток уже не надо. Простить больше долга нельзя:
лишнее не засчитывается и, в отличие от переплаты деньгами, никуда
не переливается.
В диалоге платежа галочка «Остаток простили — закрыть партию»: вводишь
сколько отдал, остаток уходит отдельной записью, партия закрывается сама,
потому что её долг становится нулём.
Миграция схемы 3→4 помечает всё записанное раньше как живые деньги.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 06:44:19 +07:00
|
|
|
|
### Пекарня простила остаток
|
|
|
|
|
|
|
|
|
|
|
|
Бывает, что пекарня забирает меньше, чем причиталось: надо было отдать 5 500,
|
|
|
|
|
|
взяли 5 000, полтысячи оставили. Долг при этом закрыт, но денег никто не
|
|
|
|
|
|
отдавал.
|
|
|
|
|
|
|
|
|
|
|
|
В диалоге «Платёж пекарне» есть галочка **«Остаток простили — закрыть
|
|
|
|
|
|
партию»**: вводишь, сколько реально отдал, и остаток уходит отдельной записью
|
|
|
|
|
|
вида «скидка пекарни». Партия закрывается сама — её долг становится нулём.
|
|
|
|
|
|
|
|
|
|
|
|
Скидка хранится отдельно от платежей намеренно. Свали их в одну кучу — и
|
|
|
|
|
|
«Отдано пекарне» в статистике покажет суммы, которых ты не платил. Поэтому:
|
|
|
|
|
|
|
|
|
|
|
|
- **«Отдано»** — только живые деньги;
|
|
|
|
|
|
- **«Прощено»** — закрывает долг, но кармана не покидает;
|
|
|
|
|
|
- в прибыль прощённое идёт **целиком**: эти деньги предназначались пекарне,
|
|
|
|
|
|
а остались у тебя;
|
|
|
|
|
|
- «осталось собрать» уменьшается — под прощённый остаток собирать уже не надо.
|
|
|
|
|
|
|
|
|
|
|
|
Простить больше, чем должен, нельзя: лишнее не засчитывается и никуда не
|
|
|
|
|
|
переливается.
|
|
|
|
|
|
|
Чаевые
Деньги сверх стоимости товара. Хранятся на продаже отдельным полем, а не
внутри платежей: иначе чаевые раздували бы «оплачено», и продажа выглядела
бы закрытой, когда за булки ещё должны.
Ввёл оплату больше суммы — булка 70, дали 100 — разница уходит в чаевые
сама. Разбор делается по окончании ввода, а не на каждом нажатии, иначе
поле дёргалось бы посреди набора. То же при приёме оплаты по долгу:
заплатили больше, чем оставалось, — излишек становится чаевыми, а не
теряется, как было раньше.
В расчётах чаевые идут в покрытие партии (это живые деньги, ими так же
рассчитываются с пекарней) и целиком добавляются в прибыль, но не входят
в выручку и не создают долга. Продажа, списанная с нескольких партий,
делит чаевые между ними пропорционально выручке.
По съеденному и подаренному чаевых не бывает — поле отключается.
Миграция схемы 2→3 проставляет ноль: у кого чаевых нет, тот появления
этой фичи не заметит.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 04:56:26 +07:00
|
|
|
|
### Чаевые
|
|
|
|
|
|
|
|
|
|
|
|
Деньги сверх стоимости товара. Хранятся на продаже отдельным полем, а не
|
|
|
|
|
|
внутри платежей: иначе чаевые раздували бы «оплачено», и продажа выглядела бы
|
|
|
|
|
|
закрытой, когда за булки ещё должны.
|
|
|
|
|
|
|
|
|
|
|
|
Если вводишь оплату больше суммы — булка 70, дали 100 — программа сама
|
|
|
|
|
|
предложит записать разницу в чаевые. То же самое при приёме оплаты по долгу:
|
|
|
|
|
|
заплатили больше, чем оставалось, — излишек становится чаевыми, а не теряется.
|
|
|
|
|
|
|
|
|
|
|
|
В расчётах чаевые:
|
|
|
|
|
|
|
|
|
|
|
|
- **идут в покрытие партии** — это живые деньги, ими так же рассчитываются
|
|
|
|
|
|
с пекарней. Если продажа списалась с нескольких партий, чаевые делятся
|
|
|
|
|
|
между ними пропорционально выручке;
|
|
|
|
|
|
- **добавляются в прибыль целиком** — себестоимости за ними нет;
|
|
|
|
|
|
- **не входят в выручку и не создают долга** — за них никто ничего не должен.
|
|
|
|
|
|
|
|
|
|
|
|
По съеденному, подаренному и списанному чаевых не бывает — поле отключается.
|
|
|
|
|
|
|
Дробные количества: пол-литра сока, 1,125 кг сыра
Количества и раньше хранились в Decimal с тремя знаками, но до документа
они не доходили: поля ввода отвергали точку. Интерфейс русский, локаль
ждёт запятую, а на цифровой клавиатуре клавиша даёт точку — символ молча
не появлялся, и выглядело это так, будто дробные вводить нельзя вообще.
Теперь оба разделителя принимаются, пробелы-разделители разрядов тоже.
Заодно исправлена ошибка, которую дробные вскрыли: в сводке складывалось
общее количество остатка по всем товарам, то есть литры с килограммами
и штуками. Теперь остаток выражен деньгами и числом позиций, а разбивка
по единицам осталась в таблице «Остатки».
Фасовки тоже могут быть дробными: бутылка 1,5 л.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 02:20:29 +07:00
|
|
|
|
### Дробные количества
|
|
|
|
|
|
|
|
|
|
|
|
Количества хранятся с точностью до тысячных, поэтому 0,5 л сока, 1,125 кг сыра
|
|
|
|
|
|
или полбулки учитываются как есть. Единица измерения у товара произвольная —
|
|
|
|
|
|
`шт`, `л`, `кг`.
|
|
|
|
|
|
|
|
|
|
|
|
Поля ввода принимают и запятую, и точку: на цифровой клавиатуре точка, а
|
|
|
|
|
|
интерфейс русский и ждёт запятую, так что точка молча заменяется на запятую.
|
|
|
|
|
|
Без этого символ просто не появлялся бы в поле.
|
|
|
|
|
|
|
|
|
|
|
|
Общее количество остатка по всем товарам сразу нигде не показывается —
|
|
|
|
|
|
складывать литры с килограммами бессмысленно. В сводке остаток выражен деньгами
|
|
|
|
|
|
и числом позиций, а разбивка по товарам есть в таблице «Остатки».
|
|
|
|
|
|
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
### Фасовки
|
|
|
|
|
|
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +07:00
|
|
|
|
Печенье поштучно за 25 ₽ и коробка того же печенья на 20 штук — это **один
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
товар с двумя фасовками**, а не два разных товара. У товара есть базовая
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +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
|
|
|
|
|
|
|
|
|
|
Остатки, себестоимость и FIFO всегда считаются в базовых единицах, поэтому
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +07:00
|
|
|
|
поштучные продажи корректно вычитаются из купленных коробок. Количество и цена
|
|
|
|
|
|
при этом вводятся в той фасовке, которую выбрал ты: «2 коробки по 300 ₽» так
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
и остаётся в документе. Размер фасовки сохраняется слепком — переопределишь
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +07:00
|
|
|
|
коробку с 20 на 24 штуки, и уже записанные документы не поедут.
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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
|
|
|
|
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +07:00
|
|
|
|
**У фасовки обе цены свои и обе за упаковку целиком.** Коробка из 20 штук,
|
|
|
|
|
|
которые внутри выходят по 15 ₽, стоит в закупке 300 ₽ — так и указывается.
|
|
|
|
|
|
Выводить эту сумму из поштучной цены нельзя: получилось бы, что опт стоит
|
|
|
|
|
|
столько же, сколько розница, и в каждой закупке сумму приходилось бы
|
|
|
|
|
|
исправлять руками.
|
|
|
|
|
|
|
|
|
|
|
|
Чтобы не держать пересчёт в голове, карточка товара расшифровывает результат
|
|
|
|
|
|
прямо под таблицей фасовок:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
коробка = 20 шт · закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Цена вводится за упаковку, а думает человек о ней поштучно — «коробка из 20,
|
|
|
|
|
|
они там по 15». Показанные тут же 15 ₽ ловят ошибку в двадцать раз сразу, а не
|
|
|
|
|
|
в закупке.
|
|
|
|
|
|
|
|
|
|
|
|
Оставленный ноль означает «своей цены нет, считай от цены за одну штуку» —
|
|
|
|
|
|
так ведут себя фасовки, заведённые до появления этого поля, поэтому цифры
|
|
|
|
|
|
в старых базах не поехали.
|
|
|
|
|
|
|
Долг как явный выбор, история цен у фасовок
Долг было непонятно как записать, и не зря. Оплата была просто денежным
полем с полной суммой: долг получался, если догадаться стереть число
и вписать ноль. Дальше — хуже. Подсказка при этом продолжала уверять, что
оплачено полностью, а любая правка строк подставляла полную сумму обратно
и молча стирала выставленный долг.
Теперь выбор явный: заплатил полностью / взял в долг целиком / заплатил
часть. Пересчёт работает только в первом режиме. Поле суммы осталось
живым — вписанное руками число само зажигает подходящий переключатель.
Долг без имени записать нельзя: на экране долгов такая запись попадает
в кучу «без контрагента», и с кого спрашивать — уже не узнать. В быстром
вводе это предупреждение, а не запрет: внося историю, можно и правда не
помнить, кто это был.
На экране долгов человека можно выбрать целиком и рассчитаться сразу за
всё: деньги приходят одной суммой, а гасятся долги по очереди, со старых.
Цены фасовок переехали в «Цены и история». Они меняются одним решением
поставщика вместе с ценой товара, а история была только у товара —
у коробки цена уезжала молча. Заодно закрыт второй вход: в карточке
товара цены фасовок теперь только показываются, правится там состав.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 04:25:49 +07:00
|
|
|
|
**Цены фасовок меняются там же, где цены товара** — кнопкой «Цены и история»,
|
|
|
|
|
|
и попадают в ту же историю. Дорожают они обычно одним решением поставщика, и
|
|
|
|
|
|
разводить их по разным окнам значило бы, что у товара история есть, а у коробки
|
|
|
|
|
|
нет. В карточке товара (кнопка «Изменить») цены фасовок показаны, но не
|
|
|
|
|
|
редактируются: иначе цена коробки менялась бы из двух мест, и из одного — молча.
|
|
|
|
|
|
Там правится только состав: название и размер.
|
|
|
|
|
|
|
|
|
|
|
|
Стартовая цена новой фасовки тоже попадает в историю — отдельной точкой
|
|
|
|
|
|
«заведена фасовка «коробка»» либо, если товар только что создан, прямо в его
|
|
|
|
|
|
точку создания. Приписывать коробке цену задним числом программа не станет:
|
|
|
|
|
|
у снимков, сделанных до её появления, фасовок нет, и пустое место честнее
|
|
|
|
|
|
выдуманных цифр.
|
|
|
|
|
|
|
Своя закупочная цена у фасовки
Коробка печенья из 20 штук обходится дешевле, чем 20 штук поодиночке —
ради этого её и берут. Своей была только цена продажи, а себестоимость
выводилась умножением поштучной цены на размер фасовки, то есть опт
считался по цене розницы. В каждой закупке сумму приходилось править
руками.
Теперь у фасовки обе цены свои и обе за упаковку целиком. Ноль означает
«своей цены нет, считай от базовой» — прежнее поведение, поэтому цифры
в уже заведённых базах не поехали.
Карточка товара расшифровывает результат построчно: «коробка = 20 шт ·
закупка 300,00 ₽ (15,00 ₽ за шт) · продажа 500,00 ₽ (25,00 ₽ за шт)».
Цена вводится за упаковку, а думает человек о ней поштучно, и без
пересчёта на виду ошибку в размер фасовки замечаешь уже в закупке.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 19:46:55 +07:00
|
|
|
|
Если один товар попал в партию и коробками, и поштучно, себестоимость базовой
|
|
|
|
|
|
единицы становится средневзвешенной: остаток по товару всё равно один, и при
|
|
|
|
|
|
поштучной продаже иначе было бы непонятно, какая из двух цен списывается.
|
Цена по фасовке не подставлялась после удаления строки
Обработчики строк запоминали номер строки в момент создания. После
удаления любой строки всё, что было ниже, съезжает вверх, и запомненный
номер начинает указывать на соседа или за пределы таблицы. Строка молча
переставала работать целиком: выбираешь фасовку — цена не меняется,
меняешь товар — не пересобирается список фасовок. Приходилось править
цену руками.
Теперь обработчики привязаны к самому виджету, а строка ищется по нему
в момент вызова. То же исправлено в быстром вводе, где строк больше
и удаляют их чаще.
Заодно найдена мина, которую посадил я сам в разделе статистики:
self.metric = QComboBox() на QWidget затеняет метод QWidget.metric(),
который Qt зовёт при смене стиля. Падало это в чужом месте и с невнятным
«object is not callable». Такая же история была с self.layout в сводке.
Оба переименованы, добавлен тест, который обходит все виджеты и проверяет,
что ни один атрибут не затеняет метод Qt.
И подсказка в карточке товара: себестоимость фасовки нигде не вводится,
она выводится из цены базовой единицы. Теперь под таблицей фасовок прямо
написано, какая сумма подставится в закупку.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 21:50:42 +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
|
|
|
|
### Ввод истории
|
|
|
|
|
|
|
|
|
|
|
|
Кнопка «Быстрый ввод за период» на экране продаж: задаёшь диапазон дат,
|
|
|
|
|
|
каждая строка таблицы — отдельная продажа. Дата новой строки наследуется от
|
|
|
|
|
|
предыдущей, Enter добавляет строку, Ctrl+D дублирует, Ctrl+Enter записывает
|
|
|
|
|
|
всё разом. Незнакомое имя в колонке «Кому» заводится как новый контрагент.
|
|
|
|
|
|
Программа подсказывает, сколько продаж за выбранный период уже записано —
|
|
|
|
|
|
чтобы не внести один и тот же месяц дважды.
|
|
|
|
|
|
|
2026-08-22 21:29:53 +07:00
|
|
|
|
Продажи разносятся по партиям методом FIFO. Цены у поставщика фиксированные,
|
|
|
|
|
|
поэтому на себестоимость это не влияет — FIFO нужен только чтобы понимать,
|
|
|
|
|
|
деньги за какую партию уже пришли.
|
|
|
|
|
|
|
Пекарня может простить остаток
Надо было отдать 5 500, взяли 5 000, полтысячи оставили. Долг закрыт,
но денег никто не отдавал.
У расчёта с пекарней появился вид: деньги или прощённый остаток. Свалить
их в одну кучу нельзя — тогда «Отдано пекарне» в статистике показывало бы
суммы, которых не платил. Поэтому «Отдано» — только живые деньги,
«Прощено» — отдельная колонка везде: в закупках, в сводке, в статистике
по периодам и по времени.
В прибыль прощённое идёт целиком: эти деньги предназначались пекарне,
а остались у тебя. «Осталось собрать» на ту же сумму уменьшается —
собирать под прощённый остаток уже не надо. Простить больше долга нельзя:
лишнее не засчитывается и, в отличие от переплаты деньгами, никуда
не переливается.
В диалоге платежа галочка «Остаток простили — закрыть партию»: вводишь
сколько отдал, остаток уходит отдельной записью, партия закрывается сама,
потому что её долг становится нулём.
Миграция схемы 3→4 помечает всё записанное раньше как живые деньги.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 06:44:19 +07:00
|
|
|
|
Прибыль считается как наценка с проданного **плюс** чаевые **плюс**
|
|
|
|
|
|
прощённое пекарней **минус** себестоимость съеденного и подаренного:
|
|
|
|
|
|
за съеденное пекарне платить всё равно, и покрывается это из маржи.
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
## Как запустить
|
|
|
|
|
|
|
|
|
|
|
|
```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 в базовых единицах, покрытие партий, долги, остатки |
|
Раздел статистики
Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:33:11 +07:00
|
|
|
|
| `app/stats.py` | сводные цифры по периодам, товарам и людям |
|
Фильтры и сортировка на экране продаж
Список продаж растёт быстрее всех остальных, а отбор был один — по типу.
Теперь панель: поиск, период, тип, человек, состояние оплаты. Всё
складывается, «за прошлый месяц Васе в долг» набирается тремя щелчками.
Правила отбора вынесены в app/salesfilter.py и живут без Qt: «прошлый
месяц» в январе уезжает в прошлый год, а поиск по двум словам ищет их по
отдельности — такое проверяется тестом, а не глазами по скриншоту.
Под таблицей — итоги ровно по показанному. Половина смысла фильтров
в этой строке: отобрал по человеку и сразу видишь, сколько он взял
и сколько за ним осталось.
Заодно починена сортировка по датам на продажах и закупках.
Отформатированная дата — обычная строка, и по алфавиту она сортируется
по дню месяца: 01.12.2025 оказывалась раньше 02.01.2020. Выглядело
рабочим, просто выдавало бессмыслицу.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 22:58:52 +07:00
|
|
|
|
| `app/salesfilter.py` | правила отбора продаж: период, поиск, оплата |
|
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` | быстрый ввод продаж за период |
|
Раздел статистики
Шесть разрезов: периоды (заданные вручную, то есть партии), недели, месяцы,
годы, товары и люди. У каждого график и таблица с итоговой строкой.
Главное решение — две базы расчёта одновременно. Смешивать «когда продал»
и «когда получил деньги» нельзя: ответы расходятся, как только появляется
хоть один долг. Выручка, наценка, съеденное и чаевые считаются по дате
продажи, «Собрано» — по дате платежа, «Закуплено» и «Отдано пекарне» — по
своим датам. У разреза «Периоды» основание третье, привязка к партии,
потому что вопрос там другой: покрыл ли я её к сроку.
Количество нигде не сводится в одно число — только по единицам, «1,5 л ·
4 шт». Сумма литров с килограммами выглядит как настоящая цифра, но смысла
не имеет; ровно эту ошибку пришлось чинить и в разрезе по партиям.
График на QPainter: QtCharts исключён из сборки, он тянет десятки мегабайт
ради одной диаграммы. Убыток рисуется вниз от нулевой линии. Больше 24
корзин на график не помещается, и про срез написано над ним — молча
показанная часть выглядела бы как всё.
Тесты экранов больше не адресуют разделы по номеру в меню: новый раздел
ломал половину проверок.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:33:11 +07:00
|
|
|
|
| `app/ui/chart.py` | столбчатый график на QPainter |
|
Отдельный репозиторий для базы, фасовки товара, ввод истории
Три правки по замечаниям.
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/ui/` | остальные экраны на PySide6 |
|
2026-08-22 21:29:53 +07:00
|
|
|
|
|
|
|
|
|
|
Главное архитектурное правило: **ни один экран не меняет документ напрямую**.
|
|
|
|
|
|
Всё идёт через функции `app/journal.py`, которые сами считают разницу и
|
|
|
|
|
|
дописывают запись в журнал. Иначе новый экран однажды забыл бы залогировать
|
|
|
|
|
|
изменение, и заметили бы это только когда история понадобится.
|