Skip to content

Касса, платежи и взаиморасчёты ​

Учёт реального движения денег: расчётные счета и кассы, реестр платежей, частичные оплаты документов и взаиморасчёты с контрагентами. Это первый (фундаментальный) блок АРМ бухгалтера и юриста.

Реализовано: модуль internal/modules/payments, миграции 0109 (схема) и 0110 (RBAC).


Зачем ​

До этого блока «оплата» в системе была лишь статусом документа (paid): не было записей о деньгах, частичных оплатах, остатках по счетам и взаиморасчётов с контрагентами. Теперь бухгалтер ведёт реальный денежный контур.


Сущности ​

Счёт / касса (Account) ​

Расчётный счёт банка или наличная касса организации.

ПолеОписание
nameНазвание («Основной р/с», «Касса»)
kindbank или cash
company_bank_account_idНеобязательная связь с банковским счётом компании
currencyВалюта (по умолчанию RUB)
opening_balanceНачальный остаток (точные деньги, см. money)
is_activeАктивен ли счёт
is_payroll_default«Счёт для зарплаты»: на него встают выплаты по табелям. Не больше одного — PATCH {is_payroll_default: true} снимает признак с прежнего счёта

Остаток считается как opening_balance + Σ(incoming) − Σ(outgoing) по платежам в статусе completed.

Счёт правится (PATCH /api/v1/accounts/:id): название, связь с банковским счётом, начальный остаток. Вид и валюта не меняются — по счёту уже могут быть платежи. is_active=false отключает счёт: новых платежей по нему не создать (400 payments_account_inactive), запланированные можно провести или отменить, история и остаток сохраняются, счёт можно включить обратно. Перед отключением интерфейс предупреждает об остатке и числе запланированных платежей; в форме нового платежа отключённых счетов нет, в фильтре реестра они подписаны «(отключён)».

Платёж (Payment) ​

Факт движения денег.

ПолеОписание
account_idПо какому счёту/кассе прошёл платёж
directionincoming (поступление) / outgoing (выплата)
amountСумма (> 0)
vat_amountНДС в сумме платежа (опц., ≤ amount)
dateДата платежа (YYYY-MM-DD)
counterparty_typecompany / contact / employee / supplier / none (supplier ставит оплата по документу поставщика)
counterparty_idID контрагента (обязателен, если тип не none)
purposeНазначение платежа
methodbank / cash / card / offset (взаимозачёт)
statusplanned / completed / cancelled
allocationsРазнесение на документы (частичные оплаты)
supplier_document_idОплата по документу поставщика: только исходящий, документ согласован, сумма не больше остатка
timesheet_idВыплата по табелю: создаётся передачей табеля, проведение закрывает табель, отменить можно только возвратом табеля с причиной (409 payments_payroll_cancel_forbidden)
task_idЗадача финансам «Выплатить зарплату» по выплате табеля: проведение её закрывает, отмена выплаты — отменяет
counterparty_nameИмя сотрудника для выплаты сотруднику (только чтение)

Статусы: planned → completed | cancelled. completed и cancelled — терминальные. В остаток счёта и взаиморасчёты попадают только completed-платежи.

Разнесение (Allocation) ​

Связь платежа с документом из модуля documents. Один счёт можно закрыть несколькими платежами; сумма разнесений по платежу не может превышать его amount. Аванс = сумма платежа сверх разнесённого.

Поступление разносится на счёт прямо из формы платежа: необязательные проект, документ проекта и сумма разнесения (по умолчанию — вся сумма платежа). Для поступления поля выплаты (обязательство, статья затрат) не проверяются. В списке — только выставленные неоплаченные счета проекта.

Счёт закрывается сам. Когда проведённые платежи (при проведении или при создании сразу проведённым) разнесли на выставленный счёт не меньше его суммы, счёт переходит в «Оплачен» обычным путём модуля documents — там же фиксируется выручка проекта. Оплачена часть — счёт «Частично оплачен» (partially_paid), выручка проекта — оплаченная часть и растёт с каждым платежом. Выручка пишется по документу идемпотентно (finance.UpsertRevenue: сумма, а не прибавка), поэтому повтор и ручная отметка «Оплачен» её не удваивают. «Частично оплачен» вручную не ставится — только платежами; остаток можно отметить «Оплачен» вручную. Счета, оплаченные до этих правил, при старте сервера проходит backfillPaidInvoices («Выставлен» и «Частично оплачен» с разнесёнными оплатами).

Проведённый платёж — дальше по цепочке. После проведения (payments.PaymentObserver): документ поставщика пересчитывает оплату, статус и факт проекта; обязательство, оплаченное проведёнными платежами целиком, получает дату факта = дату платежа (постоянный платёж заводит следующий период). Сбой этих реакций платёж не отменяет — пишется в журнал, пересчёт идемпотентен.

Плитка «Проведено» в реестре считает только проведённые платежи (поступления минус выплаты); запланированные выплаты — в плитке «К оплате».

Проведение платежа (planned → completed) в интерфейсе требует подтверждения: проведённый платёж меняет остаток счёта и больше не отменяется.


Взаиморасчёты и акт сверки ​

Для контрагента-компании система сводит:

  • Выставлено (invoiced) — сумма биллинговых документов (invoice, act), кроме черновиков и отменённых.
  • Оплачено (paid) — сумма разнесений по completed-платежам.
  • Баланс (balance) = выставлено − оплачено. Положительный — контрагент должен нам (дебиторка), отрицательный — переплата/аванс.

Акт сверки (reconciliation) детализирует баланс построчно по каждому документу: его сумма, оплачено, остаток (outstanding).

Документы привязаны к компании (documents.company_id), поэтому взаиморасчёты считаются только для counterparty_type=company.


API ​

Все списки — в каноническом конверте {items, meta} с заголовком X-List-Contract-Version.

МетодПутьПравоОписание
GET/api/v1/accountsaccounting.viewСписок счетов/касс (?active=true — только активные)
POST/api/v1/accountsaccounting.manageСоздать счёт/кассу
PATCH/api/v1/accounts/:idaccounting.manageИзменить или отключить (is_active) счёт/кассу
GET/api/v1/accounts/:idaccounting.viewСчёт
GET/api/v1/accounts/:id/balanceaccounting.viewОстаток по счёту
GET/api/v1/paymentsaccounting.viewРеестр платежей (фильтры ниже; kind=payroll — выплаты по табелям)
POST/api/v1/paymentsaccounting.manageСоздать платёж (+ разнесения)
GET/api/v1/payments/:idaccounting.viewПлатёж с разнесениями
PATCH/api/v1/payments/:id/statusaccounting.manageИзменить статус; при проведении — account_id счёта
POST/api/v1/payments/completeaccounting.manageПровести несколько запланированных платежей: {payment_ids, account_id?}, всё или ничего
GET/api/v1/counterparties/:type/:id/balanceaccounting.viewВзаиморасчёты
GET/api/v1/counterparties/:type/:id/reconciliationaccounting.viewАкт сверки
GET/api/v1/reports/cashflowaccounting.viewДДС (движение денежных средств)
GET/api/v1/reports/receivablesaccounting.viewСводная дебиторка/авансы
GET/api/v1/reports/vataccounting.viewНДС-регистр (исходящий/входящий)

Фильтры реестра платежей: account_id, direction, status, counterparty_type, counterparty_id, date_from, date_to (YYYY-MM-DD), limit, offset.

Пример создания платежа ​

json
POST /api/v1/payments
{
  "account_id": 1,
  "direction": "incoming",
  "amount": 50000.0,
  "vat_amount": 9016.39,
  "date": "2026-06-15",
  "counterparty_type": "company",
  "counterparty_id": 7,
  "purpose": "Оплата по счёту №1",
  "method": "bank",
  "status": "completed",
  "allocations": [
    { "document_id": 101, "amount": 50000.0 }
  ]
}

Денежные поля сериализуются как JSON-числа (контракт number), внутри хранятся точным десятичным типом money.Money и колонками NUMERIC(14,2).


Отчётность (ДДС и дебиторка) ​

Поверх реестра платежей строятся два org-wide отчёта (право accounting.view, суммы — точные money.Money):

  • ДДС (GET /api/v1/reports/cashflow?from=&to=&account_id=) — движение денежных средств по месяцам: приход, расход и сальдо (net) по completed-платежам, плюс итоги за период. Фильтры: счёт и диапазон дат (YYYY-MM-DD).
  • Дебиторка/авансы (GET /api/v1/reports/receivables) — сводный баланс по каждому контрагенту-компании: invoiced − paid. Положительный баланс — дебиторка (нам должны), отрицательный — полученный аванс. Итоги: total_receivable и total_advances.

Полная кредиторка (наши обязательства перед поставщиками) появится вместе с учётом входящих счетов в последующих фазах; сейчас «кому должны мы» отражается только как полученные авансы.

НДС-регистр ​

GET /api/v1/reports/vat?from=&to= агрегирует НДС по месяцам из completed-платежей с указанным vat_amount:

  • Исходящий НДС (output_vat) — с поступлений (incoming), НДС с реализации;
  • Входящий НДС (input_vat) — с выплат (outgoing), к вычету;
  • К уплате (net = output − input) по периодам и итогом (net_payable).

Сводный P&L по организации ​

Сводный отчёт о прибылях и убытках по всей организации отдаёт существующий эндпоинт GET /api/v1/reports/financial_overview (без фильтров — по всем проектам): плановая/фактическая выручка, затраты и прибыль. Фонд оплаты труда (payroll) будет включён в P&L после интеграции начислений в finance.

Доступ (RBAC) ​

Новая роль accountant и права accounting.view / accounting.manage (seeded в migrations/0110). admin обходит проверку, director видит данные через system.global_read. Подробнее — rbac_matrix.md.


Что дальше ​

Следующие блоки АРМ опираются на этот контур:

  • ДДС / дебиторка-кредиторка — отчётность поверх реестра платежей.
  • Зарплата — переданный табель создаёт запланированный outgoing-платёж сотруднику (counterparty_type=employee, timesheet_id), его проведение закрывает табель.
  • Авто-смена статуса документа — сделана: «Оплачен» / «Частично оплачен» по разнесённым проведённым платежам.

Дорожная карта целиком — в АРМ бухгалтера и юриста.

Ворота ТЗ — только для расходов. Поступление от клиента принимается по проекту и без утверждённого ТЗ: блокировать приход денег смысла нет (payments_spec_required проверяется только для direction=outgoing, в том числе при оплате по обязательству — по проекту обязательства). Форма выплаты не отправляет то, что сервер отклонит: кнопка «Добавить платёж» выключена с причиной. Охват «свой» (руководитель дивизиона, менеджер) разносит поступление только на счёт своего проекта — в форме проект и документ для него обязательны.

Загружаем документацию…