Раздел «Приём платежей»
Бэк-офисное рабочее место бухгалтера/менеджера: список всех непогашенных документов корпорации, по которым нужно получить оплату от контрагентов, — расходные накладные (Invoice.type = OUTGOING) и акты оказания услуг (ServiceAct). Приходные накладные (Invoice.type = INCOMING — наш долг поставщикам) в этот список не попадают: они обслуживаются отдельным сценарием «Оплата поставщикам». Из этого экрана оператор:
- видит, кто и сколько должен корпорации, по каким документам, в каком статусе;
- принимает оплату по одному или нескольким документам сразу (в т.ч. от разных контрагентов одним батчем);
- печатает приходные кассовые ордера (ПКО);
- открывает карточку контрагента/документа для деталей;
- выгружает выборку в Excel.
Связанная веха: LOCALIOFFICE-968 — Ревизия accounting.
Customer Journey Map
┌──────────────────────────────────────────────┐ │ Список «Приём платежей» │ │ GET /accounting/payable-documents │ │ (фильтры: период, контрагент, тип, статус, │ │ поиск, сортировка, пагинация) │ └─┬──────────────────────┬─────────────┬───────┘ │ │ │ клик по строке (документ) клик по имени «Download» │ контрагента в строке │ ▼ ▼ ▼ Карточка документа Модалка GET /accounting/ (вне этого раздела — «Персональная payable-documents/export Invoice/ServiceAct) карточка» (Excel) GET /counterparties/:id
┌──────────────────────────────────────────────┐ │ чекбоксы на строках + «Принять оплату» │ │ │ ▼ │ ┌──────────────────────────────────────┐ │ │ Модалка «Приём оплаты» │ │ │ (отметить документы, выбрать │ │ │ счета, дату, ДДС, флаг ПКО) │ │ │ │ │ │ ┌───── «+» возле счёта ─────┐ │ │ │ ▼ │ │ │ │ Модалка «Новый счёт» │ │ │ │ POST /accounting/accounts │ │ │ │ (после создания — новый счёт │ │ │ │ доступен в селекторе) │ │ │ │ ─────────────────────────────── │ │ │ │ │ │ Кнопка «Оплатить» │ │ │ POST /accounting/payments/batch │ │ │ → { payments[], paymentReceipts[] } │ │ └────────────────────┬─────────────────┘ │ │ │ для каждого ПКО (если был флаг printPkoPerCounterparty) │ ▼ │ GET /accounting/payment-receipts/:id/pdf │ (StreamableFile, PDF) │Подкомпонент в модалке «Новый счёт» — quick-add внутри потока приёма оплаты. Та же модалка вызывается из основного раздела «План счетов», но описана здесь, потому что именно тут она триггерится частее.
Эндпоинты раздела
Основные
| Метод | Маршрут | Назначение | Контроллер |
|---|---|---|---|
GET | /accounting/payable-documents | Список документов к оплате с фильтрами и итогами по выборке | payable-document.controller.ts:83 |
GET | /accounting/payable-documents/export | Экспорт текущей выборки в Excel | payable-document.controller.ts:32 |
POST | /accounting/payments/batch | Создание batch-платежа (один или несколько документов, опционально ПКО) | payment.controller.ts:74 |
GET | /accounting/payment-receipts/:id/pdf | PDF приходного кассового ордера | payment-receipt.controller.ts:53 |
POST | /accounting/payments/:id/cancel | Отменить ранее проведённый платёж | payment.controller.ts:92 |
Селекторы и справочники
| Метод | Маршрут | Используется для |
|---|---|---|
GET | /organization/counterparties | Селектор «Контрагент» в шапке (мультивыбор) |
GET | /organization/counterparties/:id | Открытие модалки «Персональная карточка» |
GET | /organization/counterparties/search-by-card | Сабмодалка «Считайте штрих-код» (поиск по карте) |
GET | /organization/subdivisions | Селекторы подразделения/склада |
GET | /organization/legal-entities | Селектор юр.лица корпорации |
GET | /organization/concepts | Селектор концепции |
GET | /organization/price-categories | Селектор ценовой категории |
GET | /accounting/accounts | Селекторы счетов (дебет/кредит/ДДС/счёт выручки) |
POST | /accounting/accounts | Quick-add нового счёта из модалки «Новый счёт» |
GET | /staffing/employees | Селектор «Ответственные лица» в модалке «Новый счёт» (⚠️ см. LOCALIOFFICE-976) |
Таблица «Приём платежей»
Параметры запроса GET /accounting/payable-documents
Breaking changes LOCALIOFFICE-969:
- параметр
counterpartyId(одиночный UUID) удалён, используйтеcounterpartyIds[](мультивыбор);- старые вызовы с
counterpartyId=…молча игнорируют фильтр (благодаря глобальномуwhitelist: trueв ValidationPipe), фронту необходима миграция;- аналогично появился
subdivisionIds[]вместо одиночного фильтра подразделения (раньше его не было — не breaking, но семантика мультивыбора зафиксирована).Breaking changes LOCALIOFFICE-969 для
GET /organization/counterparties:
- ответ изменился с массива
CounterpartyResponseDto[]на envelope{ items, total, page, limit };- параметр
type(singular) удалён, используйтеtypes[](мультивыбор).
| Параметр | Тип | Назначение |
|---|---|---|
dateFrom, dateTo | ISO date | Фильтр периода по documentDate |
counterpartyIds | UUID[] | Мультивыбор контрагентов. Передаётся как повторяемый параметр (?counterpartyIds=…&counterpartyIds=…) или через запятую. До 100 значений. |
subdivisionIds | UUID[] | Мультивыбор подразделения/склада. При заданном фильтре акты услуг исключаются (у них нет склада). |
revenueAccountIds | UUID[] | Мультивыбор счёта выручки. Для накладных — по счёту из автоматической проводки документа; для актов услуг — по полю revenueAccountId. |
paymentStatus | enum PaymentStatus | UNPAID / PARTIALLY_PAID / PAID |
documentType | enum PayableDocumentType | INVOICE / SERVICE_ACT |
statuses | PayableDocumentStatus[] | Мультивыбор визуального статуса строки (PAID / OVERDUE / DUE_SOON / WITH_DEBT / UNPAID). Применяется после вычисления статуса — выборка соответствует тем же правилам, что и поле status в ответе. |
search | строка | Поиск по номеру документа или имени контрагента |
showPaid | boolean | По умолчанию скрываем оплаченные |
sortBy | строка | documentDate / documentNumber / totalAmount / remainingAmount / counterpartyName / dueDate / paymentDate / subdivisionName / revenueAccountName / vatAmount / counterpartyDebt. null-значения уходят в конец независимо от направления. |
sortOrder | asc / desc | По умолчанию desc |
page, limit | int | Пагинация |
Маппинг колонок UI ↔ полей DTO
PayableDocumentDto (payable-document.mapper.ts:13):
| Колонка UI | Поле DTO | Примечание |
|---|---|---|
| Дата | documentDate | ISO date |
| Дата оплаты | paymentDate | Дата последнего успешного (не отменённого) платежа или null, если оплат ещё не было |
| Срок оплаты | dueDate | null допустим |
| №документа | documentNumber | |
| Тип документа | documentType | INVOICE / SERVICE_ACT |
| Контрагент | counterpartyName (counterpartyId для drill-down) | |
| Сумма, ₽ | totalAmount | string |
| Оплатить (чекбокс) | — | Клиентский, не отправляем на бэк |
| Осталось, ₽ | remainingAmount | string |
| Оста. (долг по контрагенту) | counterpartyDebt | Суммарный остаток по всем непогашенным документам этого контрагента в корпорации (Invoice + ServiceAct). Дублируется в каждой строке. |
| Склад | subdivisionName (subdivisionId) | У актов услуг — null |
| Счёт выручки | revenueAccountName (revenueAccountId) | |
| В т.ч НДС | vatAmount | string |
| Комментарий | comment | |
| (цвет строки) | status | См. секцию ниже |
Итоги в подвале
В ответе списка: total, totalAmount, totalRemaining, page, limit. Итоги пересчитываются по тому же набору фильтров.
Поле «Оста.» в подвале (Кол-во: 187)
Клиентский счётчик отмеченных чекбоксами строк, на бэке не считается.
Семантика статусов и цветов
Поле status в PayableDocumentDto — визуальный статус строки. Вычисляется в payable-document.mapper.ts:74 с приоритетом: оплачено → просрочено → подходит срок → задолженность.
status | Цвет на UI | Условие |
|---|---|---|
PAID | 🟢 зелёный | paymentStatus === "PAID" |
OVERDUE | 🔴 красный | есть dueDate и она в прошлом |
DUE_SOON | 🟠 оранжевый | есть dueDate, до неё ≤ 7 дней |
WITH_DEBT | ⬛ чёрный | paymentStatus === "PARTIALLY_PAID", срок не просрочен |
UNPAID | ⬛ чёрный | всё остальное (нет платежей, нет срока) |
Два значения (WITH_DEBT и UNPAID) маппятся в один и тот же чёрный цвет. Это осознанно: бэк сохраняет различение «частично оплачено» vs «не оплачивалось», UI рисует одинаково.
Селекторы раздела
Шапка таблицы
| Селектор | Источник | Параметры запроса |
|---|---|---|
| За период (Открытый/Сегодня/Неделя/Месяц/…) | клиентский пресет | фронт сам разворачивает в dateFrom / dateTo |
| Контрагент (мультивыбор) | GET /organization/counterparties | types[], search (по подстроке имени, регистронезависимо), page/limit. Ответ — { items, total, page, limit }. |
| Поиск | строка в запросе | search |
| Показывать оплаченные | флаг в запросе | showPaid |
| Автообновление | клиентский | поллинг, бэк не нужен |
Внутри модалки «Приём оплаты»
| Селектор | Источник | Фильтр |
|---|---|---|
| Дата | datepicker | — |
| Дебет счёт | GET /accounting/accounts?role=CASH | Счета денежных средств (касса/банк) |
| Кредит счёт | GET /accounting/accounts?role=COUNTERPARTY_SETTLEMENTS | Расчёты с контрагентами (60/62) |
| Статья ДДС | GET /accounting/accounts?role=CASH_FLOW_ITEM | Статьи ДДС |
Дефолтные значения трёх селекторов берутся из настроек корпорации (defaultPaymentDebitAccountId, defaultPaymentCreditAccountId, defaultPaymentDdsAccountId). После прогона сидера типового плана счетов они проставлены автоматически (Расчётный счёт / Расчёты с покупателями / ДДС «Поступления от продаж») и редактируются через PATCH /organization/corporation. Если поле = null, фронт показывает селектор без предзаполнения.
Внутри модалки «Новый счёт»
| Селектор | Источник |
|---|---|
| Тип | GET /accounting/account-types |
| Счёт (родитель) | GET /accounting/accounts |
| Ответственные лица | GET /staffing/employees (⚠️ интегрируется задачей LOCALIOFFICE-976) |
В ответе AccountType есть поле isSystemAccount: boolean. Системные типы (создаются сидером типового плана счетов) защищены на бэке: переименование и смена superType отдают 400, удаление — 400. Менять role у системного типа разрешено. По isSystemAccount=true фронт должен дизейблить кнопки «Переименовать» и «Удалить» в админке справочника типов. Аналогично у Account есть isSystemAccount — системные счета (Касса, 62 и др.) полностью защищены от редактирования и удаления.
Внутри модалки «Персональная карточка»
| Селектор | Источник |
|---|---|
| Пол | enum CounterpartyGender (хардкод на фронте) |
| Тип поставщика | enum SupplierType (хардкод) |
| Ценовая категория | GET /organization/price-categories |
| Подразделение | GET /organization/subdivisions |
| Юр лицо корпорации | GET /organization/legal-entities |
| Концепция | GET /organization/concepts |
| Система EDI | enum EdiSystem (хардкод) |
| Счёт ДДС (банк. выписка) | GET /accounting/accounts?role=CASH_FLOW_ITEM |
| Корр. счёт (банк. выписка) | GET /accounting/accounts?role=COUNTERPARTY_SETTLEMENTS |
| Действие при отклонении цены | enum PriceDeviationAction (хардкод) |
| День платежа | enum PayWeekday (хардкод) |
Enum’ы фронт берёт из Swagger-схемы, отдельных reference-эндпоинтов в проекте нет.
Модалка «Приём оплаты»
Триггер: кнопка «Принять оплату» на странице. Перед открытием UI собирает идентификаторы отмеченных в таблице документов.
Поля формы
| Поле UI | Поле DTO CreateBatchPaymentDto |
|---|---|
| Дата | paymentDate (ISO date) |
| Дебет счёт | accountId (UUID) |
| Кредит счёт | creditAccountId (UUID) |
| Статья ДДС | ddsAccountId (UUID, опционально) |
| Таблица «№ + Сумма» | items[]: { documentType, documentId, amount } |
| ⚪ Распечатать ПКО для каждого контрагента | printPkoPerCounterparty (boolean) |
DTO: create-batch-payment.dto.ts.
Сценарий «Оплатить»
POST /accounting/payments/batch запускает всю бизнес-логику в одной транзакции (payment.service.ts:94):
- Валидация: непустой
items, корректная дата, разные дебет/кредит, отсутствие дубликатов документов. - Блокирующая дата корпорации проверяется.
- Загружаются дебет- и кредит-счета с проверкой принадлежности корпорации.
- По каждому документу позиции (с сортировкой по
documentIdдля анти-deadlock):SELECT ... FOR UPDATEна документ (защита от двойной оплаты),- валидация суммы (
amount ≤ remainingAmount, не на полностью оплаченный, без переплаты), - создание
Posting(проводка: дебет/кредит + аналитика контрагент + ДДС), - создание
Paymentсо ссылкой наPosting, - обновление документа:
paidAmount += amount, пересчётpaymentStatus.
- Если
printPkoPerCounterparty=true: группировка платежей по(counterpartyId, legalEntityId), созданиеPaymentReceiptна каждую группу (сводный если в группе >1 платежа), генерация номера.
Смешанный batch (несколько контрагентов в одном запросе) поддерживается — на каждого контрагента создаётся отдельный ПКО.
Ответ
BatchPaymentResponseDto (batch-payment-response.dto.ts):
{ "payments": [ /* PaymentResponseDto[] — по одному на позицию */ ], "paymentReceipts": [ /* PaymentReceiptResponseDto[] — только если был флаг */ ]}Фронт получает paymentReceipts[] и для каждого ПКО дёргает GET /accounting/payment-receipts/:id/pdf для отображения/печати.
Ошибки
Все валидационные ошибки — 400 BadRequest с понятным русским текстом. Дата платежа в заблокированном периоде — 400. Документ из другой корпорации — 400.
Модалка «Новый счёт» (quick-add)
Триггер: кнопка «+» возле любого из трёх селекторов счетов в модалке «Приём оплаты». Также открывается из основного раздела «План счетов» — поведение и DTO одинаковые.
Поля формы
| Поле UI | Поле DTO CreateAccountDto | Примечание |
|---|---|---|
| Тип | typeId | UUID, ссылка на AccountType |
| Название | name | |
| ⬜ Является подсчётом + Счёт | parentId | UUID родительского счёта |
| Комментарий | comment | |
| Код (0516) | code | строка |
| ⬜ (галка справа от кода) | autoGenerateCode | ⚠️ см. LOCALIOFFICE-976 |
| Ответственные лица | responsibleEmployeeIds[] | ⚠️ см. LOCALIOFFICE-976 |
DTO: create-account.dto.ts.
Поведение автогенерации (после задачи 976)
autoGenerateCode=true+parentIdзадан → бэк подбирает<код родителя>.<NN>, гдеNN— следующий свободный двузначный суффикс среди активных детей того же родителя.autoGenerateCode=trueбезparentId→ следующий свободный целочисленный код среди корневых счетов корпорации.typeIdв подборе не участвует: уникальность кода — в рамках всей корпорации.autoGenerateCode=true+codeв теле →codeигнорируется, возвращается сгенерированный.autoGenerateCode=false(или нет флага) →codeберётся как есть, валидируется уникальность.
Дубликат кода — 409 Conflict.
После создания
Новый счёт сразу доступен в селекторе, из которого был открыт. Фронт обновляет соответствующий список через повторный GET /accounting/accounts с тем же фильтром.
Модалка «Персональная карточка»
Триггер: клик по имени контрагента в строке таблицы. Открывает существующую карточку контрагента (Counterparty) — ту же, что и из раздела «Контрагенты».
Вкладки и поля
DTO: create-counterparty.dto.ts, UpdateCounterpartyDto = PartialType(CreateCounterpartyDto).
Основные сведения
| Поле UI | Поле DTO |
|---|---|
| Имя в системе | name |
| Фамилия / Имя / Отчество | lastName / firstName / middleName |
| Компания | company |
| Телефон / Доп. телефон | phone / additionalPhone |
| Адрес | actualAddress (⚠️ зафиксировано в LOCALIOFFICE-975) |
| Пол / Дата рождения | gender / birthDate |
email | |
| Тип поставщика | supplierType |
| Ценовая категория | priceCategoryId |
| ⬜ Сотрудник / Поставщик / Гость | isEmployee / isSupplier / isGuest |
Чекбокс «Клиент» (isClient) в UI скрыт. Бэк сам выставляет: isClient = !(isSupplier || isEmployee || isGuest) при каждом сохранении (⚠️ LOCALIOFFICE-975).
Дополнительные сведения
| Поле UI | Поле DTO |
|---|---|
| Подразделение | subdivisionId |
| Юр лицо | legalEntityId |
| Концепция | conceptId |
| Система EDI | ediSystem |
| Комментарий | comment |
| Счёт ДДС (банк. выписка) | cashFlowAccountId |
| Корр. счёт (банк. выписка) | bankStatementCorrAccountId |
| ⬜ Нельзя проводить операции по счёту без прокатывания карточки | requireCardSwipe |
| Номер карты (вручную / штрих-код) | cardNumber |
Сабмодалка «Считайте штрих-код» — UI-flow считывания. Найти контрагента по карте: GET /organization/counterparties/search-by-card?cardNumber=.... Записать новое значение — обычным сохранением карточки.
Паспорт/Лицензия
| Поле UI | Поле DTO |
|---|---|
| Серия / Номер / Выдан | passportSeries / passportNumber / passportIssuedBy |
| Дата выдачи / Действителен до | passportIssueDate / passportValidUntil |
| Вид деятельности | activityType |
Юр. лицо
Массив legalEntities[] в DTO заменяет текущий набор юр.лиц контрагента. На текущем этапе в UI одно юр.лицо: PATCH с массивом длиннее 1 элемента отдаёт 400 (⚠️ LOCALIOFFICE-975).
DTO одного юр.лица: create-counterparty-legal-entity.dto.ts.
| Поле UI | Поле DTO legalEntities[i] |
|---|---|
| Наименование | name |
| Полное наименование | ⚠️ fullName — добавляется задачей LOCALIOFFICE-975 |
| ОКПО / ИНН / КПП / ОКПД | okpo / inn / kpp / okpd |
| Юр. адрес / Факт. адрес | legalAddress / actualAddress |
| Банк / БИК / Расчётный счёт | bankName / bik / accountNumber |
| Город банка / Корр. счёт | bankCity / correspondentAccount |
| SWIFT BIC / GLN / Рег. номер | swiftBic / gln / regNumber |
Контроль цен
| Поле UI | Поле DTO |
|---|---|
| Действие при отклонении цены | priceDeviationAction |
| Отсрочка платежа, дней | paymentDeferDays |
| День платежа | payWeekday |
| ⬜ Запрещать запись в прайс-лист | denyPriceListWrite |
Фото
Не в bulk. Отдельные эндпоинты:
POST /organization/counterparties/:id/photo— multipart-загрузка, валидация ≥300×300 px.DELETE /organization/counterparties/:id/photo— удаление.
В bulk-DTO поле photoUrl не передаётся; фронт после загрузки получает обновлённый URL отдельно.
Bulk-сохранение
Один PATCH /organization/counterparties/:id принимает все поля всех вкладок кроме фото, включая массив юр.лиц.
Известные расхождения и план доработок
| Что не работает / появится | Задача |
|---|---|
Карточка контрагента: fullName юр.лица, фиксация «Адрес → actualAddress», автоматический isClient, одно юр.лицо | LOCALIOFFICE-975 |
| Модалка «Новый счёт»: ответственные лица, автогенерация кода | LOCALIOFFICE-976 |
Что работает уже сейчас и менять не нужно
POST /accounting/payments/batchсоздаёт платежи, проводки, обновляет документы, генерирует ПКО — всё в одной транзакции с защитой от двойной оплаты (FOR UPDATE) и блокирующей датой корпорации.- Смешанный batch: один запрос может содержать документы разных контрагентов; при
printPkoPerCounterparty=trueсоздаётся отдельный ПКО на каждую пару(counterpartyId, legalEntityId)со сводным флагомisSummary=true, если в группе >1 платежа. В ответе для каждого платежа доступныcounterpartyId,legalEntityId,invoiceId/serviceActId,documentType— фронт сопоставляет платёж с выбранной строкой таблицы. ВpaymentReceipts[].payments[]—paymentId/invoiceId/serviceActIdдля построения связи «строка → ПКО». - Фильтр селекторов счетов по семантической роли (
GET /accounting/accounts?role=CASH|COUNTERPARTY_SETTLEMENTS|CASH_FLOW_ITEM) выполняется на бэке; клиентская пост-фильтрация не нужна. - Корпоративные дефолты
defaultPaymentDebitAccountId/defaultPaymentCreditAccountId/defaultPaymentDdsAccountIdхранятся наCorporation, читаются/правятся черезGET/PATCH /organization/corporation, проставляются сидером типового плана счетов; если полеnull, селектор открывается без предзаполнения. - Валидация суммы по остатку, отказ от оплаты полностью оплаченного, отказ от переплаты — всё с понятными
400. - Карточка контрагента уже сохраняется одним bulk-PATCH запросом, фото — отдельно multipart, ПКО — отдельный PDF-эндпоинт.
Эти точки не закладывать в задачи на изменение — это согласованное текущее поведение.