Skip to content

Архитектура подсистемы ставок и расчёта пеней (ЖКХ, правила РФ)

Обзор

Подсистема обеспечивает:

  1. Справочник ставок — хранение исторических ключевых ставок ЦБ РФ и применяемых ставок для ЖКХ (с учётом ПП №329 и др.).
  2. Автозагрузка ставки ЦБ — раз в сутки запрос к официальному SOAP-сервису ЦБ РФ KeyRateXML; при изменении ставки закрывается предыдущий период и создаётся новая запись.
  3. Расчёт пеней — чистая функция по правилам ПП 354: пени только на основной долг, по дням просрочки с учётом действующей ставки на каждый день.

1. Хранение ставок (справочник)

Модель PenaltyRate (таблица penalty_rates)

ПолеТипОписание
idbigserialPK
valid_fromdateДата начала действия ставки
valid_todate (nullable)Дата окончания (NULL = действует по сей день)
base_cbr_ratenumeric(8,4)Фактическая ставка ЦБ РФ
effective_ratenumeric(8,4)Применяемая ставка для ЖКХ (по умолчанию = base_cbr_rate; может быть заморожена вручную)
descriptionvarchar(500)Комментарий (например «ПП №329», «Автоимпорт»)
  • Непересекающиеся периоды: у одной записи valid_to либо NULL, либо дата окончания.
  • При автоимпорте создаётся запись с effective_rate = base_cbr_rate; ручное переопределение (заморозка) делается через CRUD.

REST API (CRUD)

  • GET /api/billing/penalty-rates — список всех записей (по убыванию valid_from).
  • GET /api/billing/penalty-rates/:id — одна запись.
  • POST /api/billing/penalty-rates — создание (тело: validFrom, validTo?, baseCbrRate, effectiveRate, description).
  • PUT /api/billing/penalty-rates/:id — обновление.
  • DELETE /api/billing/penalty-rates/:id — удаление.

2. Автозагрузка ставки ЦБ (cron)

  • Источник: официальный SOAP-сервис ЦБ РФ KeyRateXML. Сторонние прокси не используются.
  • Расписание: ежедневно в 04:00 (cron 0 4 * * *), плюс один запуск при старте сервиса.
  • Логика:
    1. POST SOAP 1.1 к ЦБ: операция KeyRateXML, параметры fromDate и ToDate (последние 31 день).
    2. Разбор XML-ответа (KeyRateXMLResult): извлечение записей KeyRateOnDate или Table с полями Dt и KeyRate.
    3. Выбор последней по дате ставки.
    4. Поиск в БД записи с valid_to IS NULL; при изменении ставки — закрытие периода и создание новой записи с description = "Автоимпорт с сайта ЦБ РФ (KeyRateXML)".

Ручные записи (заморозка ставки) не перезаписываются.

Как проверить работу

После старта billing-service синхронизация запускается один раз. В логах — «добавлена ставка ЦБ» или «ставка не изменилась». Справочник: GET /api/billing/penalty-rates. Никакой дополнительный URL или прокси настраивать не нужно.


3. Алгоритм расчёта пеней

Правила (ПП 354 и др.)

  • Дни 1–30 просрочки: пени не начисляются (0).
  • Дни 31–90: за каждый день пени = основной долг × (effective_rate / 100) × (1/300).
  • День 91 и далее: за каждый день пени = основной долг × (effective_rate / 100) × (1/130).
  • Пени считаются только на основной долг (principal_debt); пени на пени не начисляются.
  • На каждый день просрочки применяется та ставка, которая действовала в этот день (по справочнику PenaltyRate).

Чистая функция

go
// internal/penalty/calc.go
func CalculatePenaltyForDebt(
    debtAmount decimal.Decimal,
    dueDate, targetDate time.Time,
    rateHistory []RateEntry,
) decimal.Decimal
  • dueDate — дата, с которой долг считается просроченным.
  • targetDate — дата, на которую считаем пени (включительно).
  • rateHistory — срез записей с полями ValidFrom, ValidTo, EffectiveRate (в процентах).

Внутри: цикл по каждому дню просрочки (от dueDate+1 до targetDate), для каждого дня определение ставки через EffectiveRateForDate(day, rateHistory) и прибавление к итогу по формулам 1/300 или 1/130.

Использование в сервисе: загрузить из БД penalty_rates за нужный диапазон дат, преобразовать в []penalty.RateEntry (ValidFrom, ValidTo, EffectiveRate в decimal), вызвать CalculatePenaltyForDebt.

Тесты

В internal/penalty/calc_test.go покрыты сценарии:

  • отсутствие просрочки → 0;
  • первые 30 дней → 0;
  • дни 31–90 (1/300);
  • дни 91+ (1/130);
  • смена ставки по дням (разные effective_rate в разные периоды);
  • нулевой долг;
  • EffectiveRateForDate для границ периодов.

4. Файлы и зависимости

КомпонентПуть
Миграцияservices/billing-service/migrations/005_penalty_rates.sql
Модель GORMinternal/models/penalty_rate.go
CRUD handlersinternal/handlers/penalty_rates.go
Клиент ЦБ (SOAP KeyRateXML)internal/cbr/keyrate_client.go
Cron-задача синхронизацииinternal/job/penalty_rate_sync.go
Расчёт пеней (pure)internal/penalty/calc.go
Тесты расчётаinternal/penalty/calc_test.go

Зависимости: github.com/shopspring/decimal, github.com/robfig/cron/v3.