Недавно мне пришлось проверить расчёты, которые выполняют коммунальщики моего города. Возник спорный вопрос, разрешить который можно только лишь проведя корректные расчёты. Забегая вперёд, скажу, что коммунальщики считают неправильно. Поэтому здесь мы рассмотрим, как правильно начислять инфляционные затраты и 3% годовых на долг по обязательствам, согласно ГК Украины.

Первая часть описывает расчёт инфляционных затрат, это специфично для Украины.
Вторая (большая) часть будет посвящена вопросу расчёта 3% годовых. Она также применима и к РФ, РБ и РК, но в этих странах ставка не является фиксированной, а зависит от ставки рефинансирования ЦБ. Остальные страны не смотрел.

Вторая часть очень близка к расчётам процентов по обычным кредитным сделкам (кредиты, ссуды, займы и пр.).

image alt

Сначала теория, в конце немного кода.

Всё это под катом.

Итак, мне в руки попал документ от коммунальщиков «Ведомость начисления инфляционных затрат и 3% годовых». Мной он был проверен, и я выяснил, что он рассчитан по неверному алгоритму и представляет интерес, поскольку он должен реализовывать требования статьи ГК Украины.

Что надо считать, написано в ГК Украины:
Статья 625 Гражданского кодекса Украины. «Ответственность за нарушение денежного обязательства»
2. Должник, который просрочил выполнение денежного обязательства, по требованию кредитора обязан оплатить сумму долга с учётом установленного индекса инфляции за всё время просрочки, а также 3% годовых от просроченной суммы…
На основании цифр долга коммунальщики рассчитали инфляционные затраты и 3% годовых.

Инфляционные затраты


Далее я буду говорить об алгоритме расчёта, а не о конкретных цифрах.

Инфляция — это процесс, характеризуемый обесценением валюты, то есть снижением её покупательной способности и общим повышением цен. Без учета инфляции конечные результаты расчетов денежных потоков являются весьма условными.

Индекс инфляции (ИИ), или Индекс потребительских цен (ИПЦ), Consumer Price Index (CPI) — один из видов индексов цен, созданный для измерения среднего уровня цен на товары и услуги (потребительской корзины) за определённый период в экономике.

ИИ — это показатель, характеризующий динамику общего уровня цен на товары и услуги, которые приобретает население для непроизводственного потребления. Это индекс прироста, который показывает, на сколько процентов в среднем выросли цены за рассматриваемый период.

В Украине ИИ и ИПЦ являются синонимами

Поскольку индекс цен — это цепной индекс, то для периодов, следующих друг за другом, он рассчитывается путём перемножения показателей, например, 101% * 102% = (1.01 * 1.02) * 100% = 103.02%.

ИИ исчисляется Госкомстатом Украины и публикуется в официальных периодических изданиях. Эти показатели могут использоваться для проведения перерасчета денежных сумм.
Как считают коммунальщики?

Часть документа расчётов коммунальщиков

Последняя дата расчёта — 7 декабря 2016 года.

Посмотрим, как коммунальщики получили коэффициенты инфляционных затрат?

Невооружённым взглядом видно, что в третьей (и пятой колонке) вместо целой части 1 должна быть целая часть 0. Как же они получили дробную часть?

Сентябрь 2016. Дробная часть 0.028. Срок оплаты за сентябрь — 20 октября 2016. Дробная часть соответствует индексу инфляции, опубликованному Госкомстатом, за октябрь 2016 — 102.8%

Август 2016. Дробная часть 0.0465. Срок оплаты за август — 20 сентября 2016. Дробная часть соответствует индексу инфляции за сентябрь умноженному на ИИ за октябрь: 101.8% * 102.8% = 104.65%, или в долях, 1.018 * 1.028 = 1,046504. Коммунальщики округляют до 1.0465 и печатают это число, хотя единицу надо бы отнять, поскольку в следующей колонке они печатают только сумму инфляционных затрат, а не наращенную сумму.

Июль 2016 и ранее. Я провёл эти расчёты и восстановил алгоритм. Коммунальщики перемножают индексы инфляции, начиная с месяца, следующего за месяцем предоставления услуг (месяц срока оплаты), и заканчивая октябрём 2016.

Точка начала верна лишь частично. Как я покажу дальше, в общем случае начинать расчёт нужно действительно с этого месяца, но в нашем конкретном случае первый месяц надо пропускать (приравнивать ИИ к 100%).

Точка окончания перемножения. Поскольку последний день расчёта в декабре, и заканчивать нужно декабрём, это в общем случае. Как я покажу дальше, в этом конкретном случае декабрь тоже нужно пропускать (тоже приравнивать ИИ к 100%). Да и на 7 декабря ИИ за декабрь ещё не успели опубликовать.

Почему же нигде не учитывается ноябрь? Думаю, его просто забыли ввести в систему.

Теперь посмотрим, как надо считать индекс инфляции. Как надо — описано в Информационном письме Высшего хозяйственного суда Украины № 01-06/928/2012 от 17.07.2012 [6].

Я приведу часть абзаца:
Сумма долга, которая должна быть уплачена с 1 по 15 день месяца, индексируется с учётом этого месяца, а если сумма долга должна быть уплачена с 16 по 31 день месяца, расчёт начинается со следующего месяца.

Аналогично, если погашение задолженности произведено с 1 по 15 день месяца, инфляционные затраты рассчитываются без учёта этого месяца, а если с 16 по 31 день месяца, то инфляционные затраты рассчитываются с учётом этого месяца.
В нашем случае срок оплаты — 20 число месяца, следующего за месяцем предоставления услуг, т.е. для предоставления услуг в сентябре, дата начала расчётов — 21 октября 2016, а конечный срок — 7 декабря 2016 года.

Т.е. правильно считать так: за предоставление услуг в сентябре 2016 считать инфляцию за три месяца (пройти курсором или циклом), с октября по декабрь, но в первом и последнем месяце ИИ принять равным 1 (100%), и учесть только ноябрь.

За ноябрь 2016 ИИ составил 101.8%, т.е. коэффициент должен быть 0.0180, а не 0.0280. Про ведущую единицу я написал раньше.

3% годовых


Посмотрим, как коммунальщики получили коэффициенты и суммы начисления 3% годовых? Это пятая колонка в таблице на рисунке выше.

Как я писал раньше, ведущей единицы быть не должно, там должен быть 0. Смотрим на дробную часть, и замечаем, что она как-то подозрительно круглая — каждый предшествующий месяц больше текущего на 0.0025.

Путём нехитрых подсчётов можно определить, что коммунальщики, не мудрствуя лукаво, разделили 3% на 12 месяцев, получили 0.0025, или 0.25% в месяц, и начисляют этот процент за каждый полный и неполный месяц просрочки. Да, если смотреть на октябрь 2016, срок оплаты по которому наступил 21 ноября, а расчёты были сделаны 7 декабря, то и за неполный месяц они насчитали те-же 0.25%.

Подход восхищает брутальной прямолинейностью, но, как я покажу дальше, финансовая математика такого алгоритма не знает.

Законодательство Украины и финансовая математика не дают однозначного ответа на вопрос, как начислять 3% годовых? Мной было разыскано несколько книг по этому вопросу, но книги древние, начиная с 2002 года. Поэтому алгоритмы из книг будут дополнены тем что удалось нагуглить из более современных и западных статей. [1] была переиздана в 2007 году, и более свежих русскоязычных книг разыскать не удалось. Возможно потому, что финансовая математика, как наука, зародилась очень давно, задолго до появления компьютеров, такие понятия как «кредит» и «проценты по кредиту» зародились лет несколько сотен лет назад. И алгоритмы с 2002 года изменились несильно.

А вот западный взгляд на проблему отличается от описанного в русскоязычных книгах. Я буду приводить информацию сначала из русскоязычных книг, а потом из западных статей, указывая, в чём они совпадают.

То, что написано далее, относится к конкретному случаю начисления 3% годовых на задолженность, но может быть обобщено и на расчёт процентов по иным кредитным отношениям, если проценты начисляются по схеме простых процентов, а не сложных.

Согласно ст. 625 ГК Украины начислять нужно 3% в год по схеме простых процентов. Для вычисления суммы процентов сумму задолженности (или тело кредита, в общем случае) нужно умножать на нормированную годовую процентную ставку 3% и умножать на долю года (длительность в годах), которую составила просрочка. Эта доля может быть и больше 1, если просрочка составила больше года.

Длительность просрочки выражается в днях. В более общем случае, и срок кредита может быть выражен в днях (ситуация, когда срок кредита выражен в месяцах и годах здесь не рассматривается). А годовая ставка 3% — это нормированная ставка простых процентов за год. Поэтому нужно уметь приводить длительность просрочки (кредита) в днях к длительности в годах. Это нетривиальная задача, поскольку продолжительность года зависит от его номера.

Продолжительность в днях — однозначно определённая мера длительности календарных промежутков, в отличие от продолжительности в годах. Определение последней требует уточнения в виде правила преобразования продолжительности в днях в продолжительность в годах. Ряд стандартных способов преобразования продолжительности в днях в продолжительность в годах основывается на делении количества дней на так называемый «годовой дивизор». Наиболее типичные значения годового дивизора — 360 и 365 дней. В простейших случаях дивизор является постоянным и не зависящим от промежутка числом. В качестве делимого (числитель дроби) всегда выступает число дней в периоде.
И нужно определиться с порядком исчисления сроков в днях. Рассмотрим два надуманных примера:

  1. Клиент получает краткосрочный кредит в банке с 06.12.2018 по 07.12.2018. Каков срок договора и за сколько дней надо начислять проценты? Очевидно — 1 день.
  2. Абонент заранее заказывает услугу у оператора связи, с периодом действия в будущем с 06.12.2018 по 07.12.2018. Каков строк предоставления услуги и за сколько дней надо брать абонентскую плату за услугу? Очевидно, 2 дня.

В чём же разница?

Дело в том, что день — это не точка на временной шкале, а промежуток, который можно записать двумя способами (напомню, квадратная скобка у границы интервала означает, что точка входит в интервал, круглая скобка — точка не входит в интервал):

  • [06.12.2018 00.00.00, 07.12.2018 00.00.00) — здесь правая граница интервала не входит в интервал,
  • [06.12.2018 00.00.00, 06.12.2018 23.59.59] — здесь правая граница интервала входит в интервал.

В первом случае (1), когда временной интервал дается не явно, а в форме промежутка между датами, обычно вычисляют точное число дней, включая первый или последний день, но не оба. [4] говорит то же самое иными словами: «День выдачи ссуды и день погашения считаются за один день».

Такой способ определяет так называемое точное время. Его также можно легко определить, если обе даты относятся к одному и тому же году и имеется в наличии календарь, показывающий порядковый номер каждого дня года. Тогда достаточно из порядкового номера поздней даты вычесть порядковый номер ранней даты и результат даст продолжительность периода. В Oracle Database можно просто вычитать даты (тип DATE), усечённые до дня (с отброшенным временем), одну из другой, разность — целое число дней.

Во втором случае (2) речь идёт о сутках, поэтому считаем двое суток.

Далее я исхожу из того, что число дней было вами уже определено, например, в случае с коммунальщиками, когда срок оплаты наступает 20 сентября (т.е. это последний допустимый день оплаты), а оплачено было 21 сентября, т.е., очевидно, что просрочку надо принять равной 1 дню. В случае, если вы сталкиваетесь с указанием интервала не включая левую границу — просто подвиньте левую границу на один день вправо.

Как будем считать число дней и числитель дроби? Есть точный подсчёт и приблизительный подсчёт.

Точный подсчёт дней


Русскоязычная практика


Текст далее основан на [1].

Выбор в качестве делителя точного числа дней в периоде, а в качестве знаменателя — годового дивизора 360 или 365 дают два наиболее распространённых правила:

Правило (R1) ACT/365. Для этого правила продолжительность в днях делится на число 365. Замечу, что продолжительность високосного года будет равна 366/365= 1,00274, что больше единицы. Погрешность правила ACT/365 будет тем больше, чем больше високосных лет в периоде. Поэтому и величина 3% годовых, начисленных по этому правилу, будет больше 3%.

Правило (R2) ACT/360. Это так называемое, «Банковское правило», согласно которому, продолжительность в днях делится на 360. Это правило ещё в большей степени увеличивает годовую длину промежутков. Для невисокосного года его длина составит 365/360=1,01389, а для високосного 366/360=1,01667. Естественно, чем больше период, тем больше степень «удлинения» в годах. Это правило чаще всего используется в расчётах, касающихся денежного рынка, т.е. рынка краткосрочных долговых обязательств, таких, как депозиты в банках, векселя, коммерческие бумаги, депозитные сертификаты и пр. Величина 3% годовых за год составит ещё больше, чем при использовании предыдущего правила.

В случае, если годовой дивизор принят равным 360 дням, проценты, полученные с использованием такого дивизора называют обычными, или коммерческими. В случае, если дивизор принят равным 365 или, как я покажу далее, 366 дням, проценты называются точными.

Очевидно, что обычные проценты больше, чем точные.

Хотя первое правило является более точным, чем второе, оба они недостаточно точны.

Для устранения влияния високосных лет на результат, есть два другие правила.

Правило (R3) ACT/365, Япония. Расчёт выполняется так же, как и для правила ACT/365, но при подсчёте длительности интервала (числитель дроби) високосные даты исключаются. Годовой дивизор остаётся неизменным.

Согласно Вики:
Во многих европейских странах вплоть до XVIII века 29 февраля считалось как бы несуществующим днём, датой, не имеющей юридического статуса. Часто в этот день не заключались сделки, не производились выплаты, не давали в долг и т. п. — из-за того, что возникали сложности с формальной стороной вопроса и улаживанием дел в суде.


Другой способ связан с фактическим изменением дивизора.

Правило (R4) ACT/ACT основное. Разобьём искомый период на три части:

  • первая часть, от начальной даты до конца года, в котором находится начальная дата,
  • третья часть — от начала года, в котором находится конечная дата, до конечной даты,
  • вторая часть — 0 или несколько полных календарных лет между первой и третьей частями.

Длительности первой и третьей части получают путём деления количества дней на годовой дивизор 365 или 366, в зависимости от того, високосные ли это годы? Длительность второго периода состоит из целого числа полных календарных лет по определению, не зависимо от того, високосные они или нет. Длительность в годах всего периода получают суммированием длительностей трёх частей, представленных тремя числами, из которых второе — целое, а первое и третье — дробные.

Правило (R5) ACT/ACT «короткая» модификация, для периодов меньше года. В этом случае длительность в днях делится на годовой дивизор 365 или 366 в зависимости от того, есть ли дата 29 февраля в искомом периоде? Особенностью короткого правила ACT/ACT является то, что если промежуток не укладывается в календарный год (его концы принадлежат соседним годам), то применение основного и короткого правила приведёт к разным результатам.

Западная практика


В западной практике правила описаны по-иному. Зачастую тем-же русскоязычным правилам даны иные названия, и, причём, не по одному.

Информации далее основана на [2]. Это Вики, я проверил все источники и использовал другую нагугленную информацию: в правилах с точным числом дней — всё верно, но в правилах с приближённым числом дней есть и иные трактовки правил, причём зачастую они изменяются со временем, поэтому для применения на практике правил с приближённым числом дней этой статьи недостаточно.

Правило (W1) Actual/Actual ISDA. Это правило даёт такой-же результат, как и (R4) «ACT/ACT основное», но формулируется по-другому: интервал разбивается на високосные и невисокосные годы, независимо от того, полные они или неполные, далее, сумма дней, попадающая в високосные годы, делится на 365, а сумма дней в невисокосных годах делится на 366, два числа суммируются.

Иные названия правила в западной практике: Actual/Actual, Act/Act, Actual/365, Act/365. Да, последнее название такое-же, как и у правила (R1), хотя суть правила иная.

Правило (W2) Actual/365 Fixed. Это правило в точности повторяет правило (R1) ACT/365 — количество дней делится на 365.

Иные названия правила в западной практике: Act/365 Fixed, A/365 Fixed, A/365F, English.

Правило (W3) Actual/360. Это правило повторяет (R2) ACT/360 — точное количество дней делится на 360.

Правило (W4) Actual/364. Количество дней делится на 364. Аналога в русскоязычной практике не имеет.

Правило (W5) Actual/365L. Состоит из двух подправил:
  1. Для годовых выплат. Если дата 29 февраля внутри периода, то количество дней в периоде делится на годовой дивизор 366, если нет — на 365. В этом смысле, оно совпадает с правилом (R5) ACT/ACT «короткая модификация».
  2. Если периодичность выплат отличается от годовой. Если конечная дата находится в високосном году (и не обязательно равна 29 февраля), то годовой дивизор равен 366, иначе — 365. В русскоязычной практике аналога не имеет.

Иное название — ISMA-Year.

Правило (W6) Actual/Actual AFB. Состоит из двух подправил:
  1. Основное, для периодов меньше года: Если дата 29 февраля внутри периода, то количество дней в периоде делится на годовой дивизор 366, если нет — на 365. В этом смысле, оно также совпадает с правилом (R5) ACT/ACT «короткая модификация».
  2. Расширенное, для периодов больше года: отсчитывается целое число полных лет назад от конечной даты к начальной. Для остатка меньше года применяется основное подправило. Количество полных лет и доля года по основному подправилу суммируются.

Есть нюансы, связанные с отсчётом лет назад, если отсчёт вёлся от 28 февраля, и через целое количество полных лет назад год оказался високосным. В этом случае дату n лет назад следует принять 29 февраля. Добавлю, что вопрос вызывает также случай, если отсчёт вёлся с 29 февраля, и количество полных лет назад не кратно четырём, и соответственно предыдущий год оказался не високосный. Я не стал углубляться в детали здесь. Но вы можете обратиться к ссылкам с [2].

И «вишенка на торте», Правило (W7) 1/1. Количество дней делится на годовой дивизор 365.25. Используется для вычислений, связанных с инфляцией.

Приближённый подсчёт дней


Перечисленные выше правила базировались на точной продолжительности в днях календарных периодов. Однако, иногда встречаются схемы, основанные на так называемом упрощённом, или приближённом, подсчёте дней. Идея этих схем состоит в «выравнивании» продолжительности всех месяцев до 30 дней. Таким образом, год будет состоять из 12 месяцев по 30 дней, т.е. из 360 дней. Поэтому годовой дивизор (знаменатель дроби) для этих правил всегда будет равен 360, а числитель вычисляется специальным образом.

Поскольку точное число дней ссуды в большинстве случаев, но разумеется, не всегда, больше приближенного (в чем легко убедиться, определив среднее за год число дней в месяце, которое равно 30,44), то метод начисления процентов с точным числом дней ссуды обычно дает больший рост, чем с приближенным. [4]

Простейший вариант этого правила, так называемое Основное правило 30/360, описывается следующим образом (далее информация по книге [1] — русскоязычная практика):

Основное правило 30/360. Приближённое число дней между датами равно
360 * (y2 – y1) + 30 * (m2 – m1) + (d2 – d1),
где y, m, d — год, месяц и день во второй и первой датах.

Это правило появилось до создания первых вычислительных устройств и существенно экономило затраты на вычислительные операции в финансовой практике. Хотя сейчас необходимость в упрощённых методах отпала, тем не менее, закрепившись в практике, по традиции они иногда используются и в настоящее время.

Правило применяется с обязательным указанием модификации, которая указывает, как обрабатывать последние дни месяцев:

Русскоязычная практика


Приведено по [1].

Правило (R6) 30/360 ISDA. Если d1 = 31, то d1’ = 30, иначе d1’=d1. Если d2=31 и d1’=30, то d2’=30, иначе d2’=d2.

Правило (R7) 30E/360. Если d1 = 31, то d1’ = 30, иначе d1’=d1. Если d2=31, то d2’=30, иначе d2’=d2.

Это правило есть вариант правила 30/360 ISDA, используемое, в основном, в Европе, отсюда метка “E” в названии правила. Оно отличается от правила 30/360 ISDA лишь в том случае, когда вторая дата есть также 31 число. Европейский вариант всегда преобразует его в 30 число независимо от первой даты.

Правило (R8) 30/360 PSA. Если d1 = 31 или d1 — последний день февраля, то d1’ = 30, иначе d1’=d1. Если d2=31 и d1’=30, то d2’=30, иначе d2’=d2.

В описании правила 30/360 SIA в [1] скорее всего ошибка, поэтому здесь не привожу.

Западная практика


Дано по [2].

Правило (W8) 30/360 Bond Basis. То-же, что и (R6) 30/360 ISDA. Иное название: 30A/360.

Правило (W9) 30E/360. То-же что и (R7) 30E/360. Иные названия: 30/360 ICMA, 30S/360, Eurobond basis (ISDA 2006), Special German.

Правило (W10) 30E/360 ISDA. Если d1 — последний день месяца, то d1’=30. Если d2 — последний день НЕ февраля, то d2’=30.

Иные названия: 30E/360 ISDA, Eurobond basis (ISDA 2000), German.

Правило (W11) 30/360 SIA. Если d1 — последний день февраля и d2 — последний день февраля, тогда d2’=30. Если d1 = 31 или последний день февраля, тогда d1’ = 30, иначе d1’ = d1. Если d1’=30 и d2 = 31, то d2’ = 30, иначе d2’ = d1.

Правила (R4) ACT/ACT, (W1) Actual/Actual ISDA, (W2) Actual/365 Fixed называются также «Английской практикой».

Правила (R2) ACT/360 и (W3) Actual/360 называются также «Французской практикой».

Правило (R7) 30E/360, (W9) 30E/360 и (W10) 30E/360 ISDA называются также «Германской практикой» или «Немецкой практикой».


Итак, учитывая точные и обычные проценты, а также точное и приближённое число дней для начисления процентов, получим четыре метода вычисления простых процентов (названия правил указаны по русскоязычной практике):

  1. Обычные проценты с точным числом дней (ACT/360 — банковское правило),
  2. Точные проценты с точным числом дней (ACT/365, ACT/ACT),
  3. Обычные проценты с приближённым числом дней (30/360),
  4. Точные проценты с приближённым числом дней.

Наиболее часто применяется первый метод, называемый банковским правилом, реже — второй и третий, и почти никогда — четвертый [1]. А [4] говорит: «…вариант расчета с точными процентами и приближенным числом дней ссуды лишен смысла и не применяется.»

Приведённые выше вычисления приближённой продолжительности периодов в долях года показывают нетривиальность и многообразие применяемых правил. Их применение (а также происхождение) зависит от страны, валюты, типа финансового инструмента, профессиональной ассоциации, соглашений. [1]

ВЫВОД:

Как же считать 3% в моём случае с коммунальщиками в Украине?

Анализ судебной практики применения ст. 625 ГК Украины говорит, что обычно применяется метод (R1) ACT/365 [3], но сформулировано это не вполне чётко. Думаю, всё же надо применять (R4) ACT/ACT, и вот почему: если начислить 3% за полный високосный год по методу ACT/365, то сумма процентов будет равной 3,008219%, что при долге (или величине тела кредита) в 1 миллион денежных единиц даст превышение в 82,19 д.е. над 3%, которые предусматриваются годовой нормированной процентной ставкой по Закону. Это станет особенно важным при прогрессивном начислении процентов.

Буду признателен за комментарии, особенно от банковских работников.

Использованные источники

1) Бочаров, П. П., Касимов, Ю. Ф. Финансовая математика. Москва: Гардарики, 2002
2) Day count convention, en.wikipedia.org/wiki/Day_count_convention
3) Анализ практики применения ст. 625 Гражданского кодекса Украины в гражданском судопроизводстве, zib.com.ua/ua/74602-analiz_praktiki_zastosuvannya_st_625_civilnogo_kodeksu_ukrai.html
4) Четыркин Е.М. Финансовая математика. Москва: Дело, 2005
5) Day Count Conventions and Accrual Factors, docs.fincad.com/support/developerfunc/mathref/Daycount.htm
6) Информационное письмо Высшего хозяйственного суда Украины № 01-06/928/2012 от 17.07.2012, zakon.rada.gov.ua/laws/show/v_928600-12

А теперь немного кода на Oracle Database:

Вот пакет, который я сделал именно для моего конкретного случая — он рассчитывает инфляционные затраты и 3% годовых для Украины.

В Oracle Database нет агрегатной функции, которая бы перемножала значения инфляции. Поэтому я использую суммирование логарифмов.

Скрытый текст
CREATE OR REPLACE TYPE DEBT_OVERHEAD_T as object (ID number, DEBT_DATE date, OVERHEAD_AMOUNT number);
CREATE OR REPLACE TYPE DEBT_WITH_INF_AMOUNT_T as object (ID number, DEBT_DATE date, DEBT_AMOUNT number, PAYMENT_DATE date, INF_MONTH date, INF_RATE number, SKIP_INFLATION char (1), INF_RATE_ACCUMULATED number
                                            , INF_COEF_ACCUMULATED number, INF_AMOUNT_ACCUMULATED number, INF_AMOUNT_MONTH number);
CREATE OR REPLACE TYPE DEBT_WITH_INF_T as object (ID number, DEBT_DATE date, DEBT_AMOUNT number, PAYMENT_DATE date, INF_MONTH date, INF_RATE number, SKIP_INFLATION char (1));
CREATE OR REPLACE TYPE DEBT_WITH_PCT_AMOUNT_T as object (ID number, DEBT_DATE date, DEBT_AMOUNT number, PAYMENT_DATE date, PCT_YEAR number, PCT_YEAR_DAYS number, PCT_YEAR_PCT_PER_DAY number, PCT_YEAR_BEGIN_DATE date
                                            , PCT_YEAR_END_DATE date, PCT_YEAR_DAYS_CALC number, PCT_YEAR_PCT_PER_YEAR number, PCT_AMOUNT_PER_YEAR number);

CREATE OR REPLACE TYPE DEBT_OVERHEAD_LIST_T as table of DEBT_OVERHEAD_T;
CREATE OR REPLACE TYPE DEBT_WITH_INF_AMOUNT_LIST_T as table of DEBT_WITH_INF_AMOUNT_T;
CREATE OR REPLACE TYPE DEBT_WITH_INF_LIST_T as table of DEBT_WITH_INF_T;
CREATE OR REPLACE TYPE DEBT_WITH_PCT_AMOUNT_LIST_T as table of DEBT_WITH_PCT_AMOUNT_T;

CREATE OR REPLACE PACKAGE CALC_OVERHEADS_P is

function GET_DEBT_WITH_INF (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_INF_LIST_T;
function GET_DEBT_WITH_INF_AMOUNT (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_INF_AMOUNT_LIST_T;
function GET_DEBT_WITH_INF_AMOUNT_TOTAL (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_OVERHEAD_LIST_T;
function GET_DEBT_WITH_PCT_AMOUNT (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_PCT_AMOUNT_LIST_T; 
function GET_DEBT_WITH_PCT_AMOUNT_TOTAL (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date)  return DEBT_OVERHEAD_LIST_T;

end;
/

CREATE OR REPLACE PACKAGE BODY CALC_OVERHEADS_P is

function GET_DEBT_WITH_INF (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_INF_LIST_T is
    ret_value DEBT_WITH_INF_LIST_T;
begin

    with
      T1 as (select a.ID, a.DEBT_DATE, a.DEBT_AMOUNT, a.PAYMENT_DATE, b.INF_MONTH, c.AVAL as INF_RATE
             from DEBTS a
             cross apply (select add_months (trunc (a.PAYMENT_DATE, 'month'), rownum - 1) as INF_MONTH from dual connect by level <= months_between (trunc (p_last_collection_date, 'month'), trunc (a.PAYMENT_DATE, 'month')) + 1) b
             left join INFLATION c on c.ADATE = b.INF_MONTH and c.TYP = 'M'
             where a.DEBT_DATE between p_debt_date_begin and p_debt_date_end
            )
    , T2 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, INF_MONTH, INF_RATE
                  , case when trunc (PAYMENT_DATE, 'month') = INF_MONTH and extract (day from PAYMENT_DATE) between 16 and 31 then 'Y'
                         when trunc (p_last_collection_date, 'month') = INF_MONTH and extract (day from p_last_collection_date) between 1 and 15 then 'Y'
                    end as SKIP_INFLATION
             from T1)
    select DEBT_WITH_INF_T (ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, INF_MONTH, INF_RATE, SKIP_INFLATION)
    bulk collect into ret_value
    from T2;

    return ret_value;

end;

function GET_DEBT_WITH_INF_AMOUNT (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_INF_AMOUNT_LIST_T is
    ret_value DEBT_WITH_INF_AMOUNT_LIST_T;
begin

    with 
      T1 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, INF_MONTH, INF_RATE, SKIP_INFLATION
                  , exp (sum (ln (case when SKIP_INFLATION = 'Y' or INF_RATE is null then 1 else INF_RATE / 100 end)) over (partition by ID order by INF_MONTH rows between unbounded preceding and current row)) * 100 as INF_RATE_ACCUMULATED
             from table (CALC_OVERHEADS_P.GET_DEBT_WITH_INF (p_debt_date_begin, p_debt_date_end, p_last_collection_date)))
    , T2 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, INF_MONTH, INF_RATE, SKIP_INFLATION, INF_RATE_ACCUMULATED
                  , (INF_RATE_ACCUMULATED - 100) / 100 as INF_COEF_ACCUMULATED
                  , round (((INF_RATE_ACCUMULATED - 100) / 100) * DEBT_AMOUNT, 2) as INF_AMOUNT_ACCUMULATED 
             from T1)
    select DEBT_WITH_INF_AMOUNT_T (ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, INF_MONTH, INF_RATE, SKIP_INFLATION, INF_RATE_ACCUMULATED, INF_COEF_ACCUMULATED, INF_AMOUNT_ACCUMULATED
         , INF_AMOUNT_ACCUMULATED - lag (INF_AMOUNT_ACCUMULATED, 1, 0) over (partition by ID order by INF_MONTH))
    bulk collect into ret_value
    from T2;
    
    return ret_value;

end;

function GET_DEBT_WITH_INF_AMOUNT_TOTAL (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_OVERHEAD_LIST_T is
    ret_value DEBT_OVERHEAD_LIST_T;
begin

    with
      T1 as (select ID
                  , DEBT_DATE
                  , round ((exp (sum (ln (case when SKIP_INFLATION = 'Y' then 1 else INF_RATE / 100 end))) - 1) * DEBT_AMOUNT, 2) 
                                  as INF_AMOUNT  
             from table (CALC_OVERHEADS_P.GET_DEBT_WITH_INF (p_debt_date_begin, p_debt_date_end, p_last_collection_date))
             group by ID, DEBT_DATE, DEBT_AMOUNT)
    select DEBT_OVERHEAD_T (ID, DEBT_DATE, INF_AMOUNT)
    bulk collect into ret_value
    from T1;
    
    return ret_value;
end;

function GET_DEBT_WITH_PCT_AMOUNT (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date) return DEBT_WITH_PCT_AMOUNT_LIST_T is 
    ret_value DEBT_WITH_PCT_AMOUNT_LIST_T;
begin
    with 
      T1 as (select *
             from DEBTS a
             cross apply (select extract (year from PAYMENT_DATE) + level - 1 as PCT_YEAR from dual connect by level <= extract (year from p_last_collection_date) - extract (year from PAYMENT_DATE) + 1)
             where DEBT_DATE between p_debt_date_begin and p_debt_date_end
             )
    , T2 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, PCT_YEAR, to_date ('31.12.'||PCT_YEAR, 'DD.MM.YYYY') - to_date ('01.01.'||PCT_YEAR, 'DD.MM.YYYY') + 1 as PCT_YEAR_DAYS from T1)
    , T3 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, PCT_YEAR, PCT_YEAR_DAYS
                          , 0.03 / PCT_YEAR_DAYS as PCT_YEAR_PCT_PER_DAY
             from T2)
    , T4 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, PCT_YEAR, PCT_YEAR_DAYS, PCT_YEAR_PCT_PER_DAY
                  , greatest (PAYMENT_DATE + 1, to_date ('01.01.'||PCT_YEAR, 'DD-MM-YYYY')) as PCT_YEAR_BEGIN_DATE
                  , least (to_date ('31.12.'||PCT_YEAR, 'DD-MM-YYYY'), p_last_collection_date)   as PCT_YEAR_END_DATE
             from T3)
    , T5 as (select ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, PCT_YEAR, PCT_YEAR_DAYS, PCT_YEAR_PCT_PER_DAY, PCT_YEAR_BEGIN_DATE, PCT_YEAR_END_DATE
                  , PCT_YEAR_END_DATE - PCT_YEAR_BEGIN_DATE + 1 as PCT_YEAR_DAYS_CALC
                  , (PCT_YEAR_END_DATE - PCT_YEAR_BEGIN_DATE + 1) * PCT_YEAR_PCT_PER_DAY as PCT_YEAR_PCT_PER_YEAR
                  , round ((PCT_YEAR_END_DATE - PCT_YEAR_BEGIN_DATE + 1) * PCT_YEAR_PCT_PER_DAY * DEBT_AMOUNT, 2) as PCT_AMOUNT_PER_YEAR
             from T4)
    select DEBT_WITH_PCT_AMOUNT_T (ID, DEBT_DATE, DEBT_AMOUNT, PAYMENT_DATE, PCT_YEAR, PCT_YEAR_DAYS, PCT_YEAR_PCT_PER_DAY, PCT_YEAR_BEGIN_DATE, PCT_YEAR_END_DATE, PCT_YEAR_DAYS_CALC, PCT_YEAR_PCT_PER_YEAR, PCT_AMOUNT_PER_YEAR)
    bulk collect into ret_value
    from T5;
    
    return ret_value;

end;

function GET_DEBT_WITH_PCT_AMOUNT_TOTAL (p_debt_date_begin date, p_debt_date_end date, p_last_collection_date date)  return DEBT_OVERHEAD_LIST_T is
    ret_value DEBT_OVERHEAD_LIST_T;
begin
    
    select DEBT_OVERHEAD_T (ID, DEBT_DATE, sum (PCT_AMOUNT_PER_YEAR))
    bulk collect into ret_value
    from table (CALC_OVERHEADS_P.GET_DEBT_WITH_PCT_AMOUNT (p_debt_date_begin, p_debt_date_end, p_last_collection_date))
    group by ID, DEBT_DATE; 

    return ret_value;

end;

end;

Комментарии (0)