Repository navigation
08 Billing
Деньги — самая чувствительная часть домовой бухгалтерии. Здесь: как устроена модель, как начислять, принимать оплаты и делать перерасчёты так, чтобы ни копейка не потерялась и никто не заплатил дважды.
Charge (начисление) — «15 000 ₽ за Октябрь 2026, 20 участников»
└── Invoice (счёт) × N — по одному на участника: сумма, срок, статус
└── Payment (оплата) — деньги: FIFO по старейшим счетам
BillingHistory — проводки по балансу жителя
residents.balance — кэш: переплата (+) / долг (−)
-
Charge: общая сумма, период (метка + даты), способ распределения,
статус
draft → issued → closed | cancelled, номерНАЧ-2026-0001. -
Invoice: номер
СЧ-2026-…, сумма, оплачено, остаток, срок, статусpending → partial → paid,overdue,void(сторно). - Payment: сумма, дата, метод (наличные/перевод/карта/онлайн/бартер), внешний ID платёжной системы, ссылка на счёт(а).
Суммы хранятся целыми копейками (Money::parse), форматирование —
Money::format с неразрывными пробелами: 15 000 ₽.
В начислении участвуют жители, у которых одновременно:
-
is_active = 1— чекбокс «включен» (отключённые не видны и не считаются), -
is_billed = 1— «участвует в оплатах», - не удалён (мягкое удаление).
Опционально (billing.count_only_with_access) — ещё и флаг «доступ».
Это правило едино для UI, CLI, API и перерасчётов.
Админка → Оплаты → Мастер или
bin/bas-ip billing:charge --total=15000 --period=2026-10 --notify --yes.
-
Общая сумма → предпросмотр: участников 20, на каждого 750 ₽,
сумма счетов = 15 000 ₽ ровно (расщепление
Money::splitраспределяет остаток по копейке между первыми участниками — расхождение максимум 1 копейка на счёт, сумма всегда сходится). -
Способ распределения (
billing.allocationили в мастере):-
equal— поровну; -
by_area— пропорционально площади квартир; -
by_persons— по числу проживающих; -
by_share— по доле собственности (residents.share).
-
-
Период: распознаются
10.2026,2026-10, «Октябрь 2026», «октябрь»; произвольный текст сохраняется как есть. Даты периода → срок оплаты (due_daysдней или точнаяdue_date). -
Ссылка на оплату (
pay_link): подстановки{id},{apartment},{sum}(копейки/рубли по настройке),{period},{name}— подставляются в каждый счёт индивидуально. Пример:https://pay.example/charge?flat={apartment}&sum={sum}&month={period}. -
Сообщение: своё (с теми же плейсхолдерами) или автогенерируемое из
шаблона
payment_new. -
Выставить счета → транзакция: charge + N invoices + (опционально)
очередь уведомлений. Черновик (
draft,--no-issue) — без счетов.
Результат в CLI:
ℹ 15 000 ₽ ÷ 20 активных = 750 ₽ на каждого
✔ Начисление CHG-2026-0001: выставлено 20 счетов по 750 ₽.
ℹ В рассылку поставлено 18 сообщений (пропущено 2).
(«пропущено» — отключённые жители или без каналов связи.)
- UI: карточка жителя → «Принять оплату»; Оплаты → Платежи → «Добавить».
-
CLI:
billing:payment --resident=5 --amount=750 --method=cash. -
API:
POST /api/v1/payments(scopebilling.payment.record) — для вебхука платёжного сервиса;external_idзащищает от дублей.
Правила:
- оплата распределяется FIFO: сначала самый старый непогашенный счёт;
- частичные оплаты разрешены (
allow_partial) — счёт становитсяpartial; - переплата остаётся на балансе жителя (
overpay_to_balance) и видна в следующем начислении; - отрицательные суммы запрещены (возврат = удаление платежа);
- удаление платежа откатывает счета обратно (pending/partial);
- просроченные счета помечает cron (
auto_mark_overdue).
Ситуация: сумма изменилась (15 000 → 20 000) или состав участников изменился (кто-то въехал/выехал/включён-выключен). Перерасчёт не трогает выставленные счета — он создаёт разницу:
- Админка: начисление → «Перерасчёт» → новая сумма/состав → предпросмотр;
-
CLI:
billing:recalc --charge=CHG-2026-0001 --total=20000.
Механика (проверена тестами):
- каждому участнику с недостающей суммой — дополнительный счёт на дельту (20 000 ÷ 20 = 1 000; было 750 → досчёт +250);
- выключенным/выехавшим — сторно (кредит-инвойс отрицательной суммой);
- новые активные добавляются в состав со своей долей;
- если ничего не изменилось — no-op, ничего не создаётся;
- все корректировки попадают в BillingHistory, уведомление — шаблон
charge_recalculated.
Итог: сумма счетов начисления всегда соответствует последней утверждённой сумме, история изменений сохраняется, дважды никто не платит.
- Оплаты → Должники: активные жители с непогашёнными/просроченными счетами; сортировки по сумме и давности; фильтр по периоду.
-
«Напомнить» (UI) /
billing:remind(CLI) — рассылкаpayment_overdueвсем активным должникам одной очередью. - Планировщик шлёт напоминания по расписанию
billing.reminder(за 3 и 1 день до срока; через 1/7/30 дней после). - Пени (
late_fee) — опционально: процент или фикс после N дней.
-
residents.balance— кэш; пересчёт —billing:balances(cron делает сам); - сторнированные (
void) счета игнорируются во всех итогах; - каждое действие (начисление, оплата, удаление, перерасчёт) пишет BillingHistory и аудит.
- Начисление → «Экспорт» (CSV): все счета с оплатами — для бухгалтерии.
-
/admin/billing/payments→ экспорт платежей за период. - API
GET /api/v1/stats— сводки для внешних отчётов.
| Задача | Действие |
|---|---|
| Начислить за месяц | Мастер: сумма → период → выставить |
| Принять наличные | Карточка жителя → Принять оплату |
| Житель заплатил онлайн | API POST /api/v1/payments или ręчной ввод с external_id
|
| Изменилась смета | Начисление → Перерасчёт |
| Житель съехал | Выключить жителя → Перерасчёт (сторно) |
| Напомнить всем | Должники → Напомнить |
| Закрыть период | Начисление → Закрыть (read-only) |
| Сверить суммы |
billing:balances, затем billing:charges
|
Дальше: 09 · Уведомления