# 08 · Биллинг [← Home](Home.md) Деньги — самая чувствительная часть домовой бухгалтерии. Здесь: как устроена модель, как начислять, принимать оплаты и делать перерасчёты так, чтобы ни копейка не потерялась и никто не заплатил дважды. ## Модель данных ``` 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 · Уведомления](09-Notifications.md)