Skip to content

Гибкий НДС и «Моя компания» ​

Налог на проекте задаётся гибко: ставка и режим НДС хранятся на реквизитах нашей компании («Моя компания»), наследуются проектом при выборе компании-плательщика и могут быть переопределены прямо в проекте. От выбранного режима зависит, как считается смета и формируются ли документы с налогом.

Налоговый режим (vat_mode) ​

РежимСмыслКак считается НДС
noneБез НДСНалог = 0, документы без налоговой строки
includedНДС включён в цену и выделяется из неё (по умолчанию)НДС = сумма × ставка / (100 + ставка)
on_topНДС начисляется сверху на ценуНДС = сумма × ставка / 100

vat_rate — ставка в процентах (по умолчанию действующая ставка 22).

Расчёт выполняется точной десятичной арифметикой в internal/money (money.ComputeVAT), без float64. См. Деньги, округление и НДС.

Где это хранится ​

«Моя компания» — реквизиты-исполнитель ​

Наборы реквизитов нашей компании (owner_type = 'internal') администрируются через Настройки → Моя компания:

GET    /api/v1/settings/my-company/requisites
POST   /api/v1/settings/my-company/requisites
GET    /api/v1/settings/my-company/requisites/{id}
PUT    /api/v1/settings/my-company/requisites/{id}
DELETE /api/v1/settings/my-company/requisites/{id}
PATCH  /api/v1/settings/my-company/requisites/{id}/set-primary

Каждый набор несёт юридические/банковские данные и налоговые поля vat_mode + vat_rate. Так одна и та же компания может вести и проекты с НДС, и без — заведите два набора с разными режимами. Читают реквизиты все сотрудники, а управляющие эндпоинты (POST/PUT/DELETE/set-primary) доступны только финансам: запись затрат «все» (OWNER, OPS, FIN_DIR) или глобальный администратор (transport.IsFinance). Юристы и остальные получают 403 requisites_my_company_finance_only. То же правило действует для записи owner_type=internal через общий /api/v1/requisites; реквизиты клиентов и поставщиков по-прежнему правит тот, у кого запись справочника контрагентов. Интерфейс прячет кнопки по capability my_company_requisites_manage.

Чтение списка для формы проекта (доступно всем) ​

Чтобы заполнить выбор «Моя компания» в форме проекта, есть read-only маршруты. Читают их все аутентифицированные сотрудники — как и /settings/my-company/requisites (раньше требовалось projects.manage):

GET /my-company/requisites      — список наборов (для дропдауна)
GET /my-company/requisites/:id  — конкретный набор

Формат ответа идентичен админским (массив объектов Requisite). Используйте id + name, бейджем можно показать vat_mode/vat_rate.

Все маршруты /settings/my-company/* и /my-company/requisites присутствуют в OpenAPI/Scalar и в Postman/Insomnia-коллекциях (генератор контрактов теперь сканирует все функции Register*Routes).

Проект — выбор компании и переопределение ​

При создании/редактировании проекта:

ПолеТипНазначение
payer_requisite_idint64?Выбранная «Моя компания» (набор internal-реквизитов).
vat_modestring?Переопределение режима. null → наследуется от payer_requisite_id.
vat_ratenumber?Переопределение ставки. null → наследуется.

payer_requisite_id валидируется: он должен ссылаться на существующий internal-набор, иначе 400.

Сброс к наследованию ​

Переопределение НДС можно снять, вернувшись к режиму выбранной компании:

  • vat_mode: "inherit" — сбрасывает и режим, и ставку проекта в NULL (НДС снова наследуется от payer_requisite_id).
  • payer_requisite_id: 0 — снимает выбранную «Мою компанию» (в NULL).

При создании проекта эти сентинелы эквивалентны «не задано».

Действующий (эффективный) НДС ​

Эффективный режим проекта = переопределение проекта, иначе режим компании-продавца — выбранной «Моей компании», а если она не выбрана — основного набора (от его имени выпускаются документы, см. «Система документов»), иначе none:

seller   = requisites WHERE owner_type='internal'
           ORDER BY (id = project.payer_requisite_id) DESC, is_primary DESC, id LIMIT 1
vat_mode = COALESCE(project.vat_mode, seller.vat_mode, 'none')
vat_rate = COALESCE(project.vat_rate, seller.vat_rate, 0)

До 2026-09-30 без выбранного плательщика НДС не считался вовсе, даже если основная компания работает с НДС.

Эти значения возвращаются в сводке бюджета и используются документами.

Сводка бюджета ​

GET /api/v1/projects/{id}/budget отдаёт НДС, рассчитанный после наценки и УСН: сначала считается total (позиции + markup_percent), затем УСН, затем НДС.

json
{
  "subtotal": 4583.33,
  "markup_percent": 0,
  "markup_amount": 0,
  "total": 5500.00,
  "vat_mode": "included",
  "vat_rate": 20,
  "vat_amount": 916.67,
  "total_net": 4583.33,
  "total_with_vat": 5500.00,
  "grand_total": 5500.00
}
  • included — налог выделен из суммы после УСН и не увеличивает grand_total.
  • on_top — grand_total = total + usn_amount + vat_amount.
  • none — vat_amount = 0, grand_total = total + usn_amount.

Отображение НДС в строках сметы управляется отдельно настройкой бюджета vat_display_mode (separate или included). Это не меняет юридический режим НДС: vat_mode отвечает за арифметику налога, vat_display_mode — только за представление в UI и проектных документах.

Где смотреть в коде ​

  • Налоговые поля реквизитов: migrations/0116_requisites_tax.sql, internal/modules/requisites/.
  • Выбор плательщика и переопределение на проекте: migrations/0117_projects_payer_tax.sql, internal/modules/projects/ (EffectiveVAT, validatePayerAndVAT).
  • Расчёт НДС в сводке: internal/modules/budget/service.go.
  • Хелперы НДС: internal/money/money.go (ComputeVAT, VATModes).

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