Skip to content

08 Billing

Mikhail Deynekin edited this page Oct 9, 2026 · 1 revision

08 · Биллинг

← Home

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

Модель данных

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.

  1. Общая сумма → предпросмотр: участников 20, на каждого 750 ₽, сумма счетов = 15 000 ₽ ровно (расщепление Money::split распределяет остаток по копейке между первыми участниками — расхождение максимум 1 копейка на счёт, сумма всегда сходится).
  2. Способ распределения (billing.allocation или в мастере):
    • equal — поровну;
    • by_area — пропорционально площади квартир;
    • by_persons — по числу проживающих;
    • by_share — по доле собственности (residents.share).
  3. Период: распознаются 10.2026, 2026-10, «Октябрь 2026», «октябрь»; произвольный текст сохраняется как есть. Даты периода → срок оплаты (due_days дней или точная due_date).
  4. Ссылка на оплату (pay_link): подстановки {id}, {apartment}, {sum} (копейки/рубли по настройке), {period}, {name} — подставляются в каждый счёт индивидуально. Пример: https://pay.example/charge?flat={apartment}&sum={sum}&month={period}.
  5. Сообщение: своё (с теми же плейсхолдерами) или автогенерируемое из шаблона payment_new.
  6. Выставить счета → транзакция: 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 (scope billing.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 · Уведомления

Clone this wiki locally