Pull to refresh

Расчет календаря периодических событий с учетом праздничных дней

Reading time17 min
Views3.1K
image

На пиар вряд ли тянет, но кроме как в «Я пиарюсь» не придумал, куда разместить.

Все началось с того, что знакомые в банке из отдела отчетности обратились ко мне с просьбой, не знаю ли я ресурса, где можно автоматически рассчитать даты сдачи отчетности. На тот момент использовался большой лист формата А3, который висел на стене и содержал в себе список отчетных форм и срок сдачи в соответствии с нормативными требованиями Банка России.

Вся соль в том, что сроки ЦБ обычно устанавливает, например, как «7 рабочий день месяца, следующего за отчетным». В то время, как формы могут быть как месячные, так и квартальные, годовые, декадные, пятидневки. А тот факт, что на каждый календарный год в России Правительством устанавливается производственный календарь, где закрепляются праздничные дни и все необходимые переносы, только усугубляет положение, так как нужно подстраиваться под все эти изменения.


Собственно, на том листе стояли примерные дни сдачи, как-то: для 7 рабочего дня отчет отображался к сдаче в графе «7 число месяца», что заведомо было раньше срока. И получалось, всегда это нужно держать в голове и не забыть сдать тот или иной отчет. Все это было не сильно удобно. А главная задача была — контроль сроков сдачи, так как за нарушение от ЦБ полагаются соответствующие меры воздействия. А если допустить просрочку свыше 15 дней, то могут и лицензию по закону отозвать.

В общем, потыкав Outlook и еще ряд календарных менеджеров, понял, что периодические события, со сроком исчисляемым в рабочих днях, настроить нельзя. Подумал сделать костыль, так как календарь отчетности можно за раз рассчитать на год вперед и на время забыть про это.

Самая простая идея, которая пришла в голову — получить CSV в нужном формате, который можно подгрузить в Outlook. Для того, чтобы сгенерировать CSV, первое о чем подумал, — SQL, благо какое-то время назад разбирался с Oracle и стандарт SQL там расширен множеством полезного функционала.

Для этого завел список событий в Excel, на выходе получал список insert'ов для вставки в таблицу настройки.

А базовых таблиц понадобилось 3:
  • таблицы праздничных дней, которые нужно добавить (add_red_days)
  • таблицы дней, которые являются субботой или воскресенье, но в связи с переносом становятся рабочими (remove_red_days)
  • собственно, список самих форм отчетности с необходимыми параметрами (cb_reports_settings)

SQL> desc remove_red_days
Name Type Nullable Default Comments
---- ---- -------- ------- --------
DT  DATE Y

SQL> select * from remove_red_days
 2 /

DT
-----------
27.02.2010
13.11.2010

SQL> desc add_red_days
Name Type Nullable Default Comments
---- ---- -------- ------- --------
DT  DATE Y

SQL> select * from add_red_days
 2 /

DT
-----------
01.01.2010
04.01.2010
05.01.2010
06.01.2010
07.01.2010
08.01.2010
22.02.2010
23.02.2010
08.03.2010
03.05.2010
10.05.2010
14.06.2010
04.11.2010
05.11.2010

14 rows selected

SQL> desc cb_reports_settings
Name            Type         Nullable Default Comments
--------------- ------------- -------- ------- --------
FORM_NAME      VARCHAR2(100) Y
CALENDAR_DAY    NUMBER(2)     Y
WORKING_DAY     NUMBER(2)     Y
TIME_TO_BE_SENT NUMBER(2)     Y
IS_QUARTERLY    NUMBER(1)     Y

SQL> select * from cb_reports_settings
 2 /

FORM_NAME             CALENDAR_DAY WORKING_DAY TIME_TO_BE_SENT IS_QUARTERLY
--------------------- ------------ ----------- --------------- ------------
350                             1          2                            0
301                             1          2             13            0
634_декада                      1          4                            0
101                             1          4                            0
134                             1          4                            0
135                             1          6                            0
153                             1          6                            0
711                             1          5                            0
301                             5          2             13            0
115                             1          7                            0
116                             1          7                            0
117                             1          7                            0
118                             1          7                            0
155                             1          7                            0
125                             1          8                            0
157                             1          8                            0
501                             1          8                            0
603                             1          8                            0
102                             1          8                            1
110                             1          8                            0
128                             1          7                            0
129                             1          7                            0
302                             1         10                            0
316                             1         10                            0
251                             1         10                            1
401                             1         10                            0
301                             10          2             13            0
350                             11          2                            0
634_декада                     11          4                            0
345                             1         11                            1
405                             1         17                            0
301                             15          2             13            0
301                             20          2             13            0
350                             21          2                            0
634_декада                     21          4                            0
301                             25          2             13            0
342-П (ФОР)                     1         10                            0

37 rows selected


* This source code was highlighted with Source Code Highlighter.


Генерацию CSV для загрузки в MS Outlook сделал таким вот запросом:

/*сгенерируем все даты года*/
with t1 as (select trunc(sysdate,'y')+rownum-1 dt from dual connect by level<=365),
--
/*найдем из них все рабочие дни и пронумеруем их внутри месяцев*/
t2 as (select dt, row_number() over (partition by trunc(dt,'mm') order by dt) working_day,
                 row_number() over (order by dt) wd2 from t1
        where (mod(to_char(dt, 'j'),7) +1 not in (6,7)--выкинуть выходные
          or dt in (select dt from remove_red_days)) --добавим выходные, которые стали рабочими
         and dt not in (select dt from add_red_days)), --выкинем новые перенесенные праздники
--
/*соберем в один запрос все дни с номером календарного дня в месяце и рабочего дня*/
t3 as (select t1.dt, to_number(to_char(t1.dt,'dd')) calendar_day, working_day, wd2 from t1, t2
        where t1.dt = t2.dt(+)
        order by t1.dt),
--
/*добавим колонки с первой ближайшей рабочей датой после данной даты, если она выходной, иначе текущая дата*/
t4 as (
select t3.*,
      first_value(decode(working_day, null, null, t3.wd2) ignore nulls) over (order by t3.dt rows between current row and unbounded following) wd3,
      first_value(decode(working_day, null, null, t3.dt) ignore nulls) over(order by t3.dt rows between current row and unbounded following) dt2,
      nvl2(working_day, dt, null) dt3
 from t3
) /*select * from t4*/,
--
t5 as (select (select dt
         from t4 t4_inner
         where t4_inner.wd3 - t4_outer.wd3 + 1 = cbrs.working_day
          and t4_inner.wd2 is not null
          and rownum = 1) needed_date,
      t4_outer.*,
      cbrs.*
 from t4 t4_outer,
      cb_reports_settings cbrs
where t4_outer.calendar_day = cbrs.calendar_day
and (cbrs.is_quarterly = 0 or cbrs.is_quarterly = 1 and trunc(t4_outer.dt,'mm') = trunc(t4_outer.dt,'Q'))
order by 1)
--
select '"'||form_name||'","'||to_char(needed_date,'dd.mm.yyyy')||'","'||nvl(time_to_be_sent,'17')||':00:00","'||to_char(needed_date, 'dd.mm.yyyy')||'","'||nvl(time_to_be_sent,'17')||':00:00","Ложь","Истина","'||to_char(needed_date, 'dd.mm.yyyy')||'","09:00:00","Обычная"' col
from t5
order by form_name, needed_date


* This source code was highlighted with Source Code Highlighter.


На выходе получаем:
COL
--------
"101","14.01.2010","17:00:00","14.01.2010","17:00:00","Ложь","Истина","14.01.2010","09:00:00","Обычная"
"101","04.02.2010","17:00:00","04.02.2010","17:00:00","Ложь","Истина","04.02.2010","09:00:00","Обычная"
"101","04.03.2010","17:00:00","04.03.2010","17:00:00","Ложь","Истина","04.03.2010","09:00:00","Обычная"
"101","06.04.2010","17:00:00","06.04.2010","17:00:00","Ложь","Истина","06.04.2010","09:00:00","Обычная"
"101","07.05.2010","17:00:00","07.05.2010","17:00:00","Ложь","Истина","07.05.2010","09:00:00","Обычная"
"101","04.06.2010","17:00:00","04.06.2010","17:00:00","Ложь","Истина","04.06.2010","09:00:00","Обычная"
"101","06.07.2010","17:00:00","06.07.2010","17:00:00","Ложь","Истина","06.07.2010","09:00:00","Обычная"
"101","05.08.2010","17:00:00","05.08.2010","17:00:00","Ложь","Истина","05.08.2010","09:00:00","Обычная"
"101","06.09.2010","17:00:00","06.09.2010","17:00:00","Ложь","Истина","06.09.2010","09:00:00","Обычная"
"101","06.10.2010","17:00:00","06.10.2010","17:00:00","Ложь","Истина","06.10.2010","09:00:00","Обычная"
...


Все это успешно импортируется в Outlook.

И все вроде ничего, вот только так как Oracle на работе отсутствовал, пришлось все выгрузки делать дома. Не совсем удобно.

Пришел новый год, обновились сроки сдачи и список отчетных форм в связи с новым указанием ЦБ, установлен новый производственный календарь. Пришлось собрать данные по новому списку отчетов на работе, дома обновлять данные в таблицах, заново генерить CSV.

Не понравилось мне это и решил я сделать небольшой веб-сервис, который будет полезен в первую очередь мне самому, а также другим людям, кто столкнулся с аналогичной задачей.

Процесс разбил на 3 шага:

1. Нужно отметить какие даты являются праздничными на очередной год. Это можно сделать после того, как примут официальный производственный календарь. Также можно поменять признак выходного дня на рабочий, в случае, если осуществляется перенос.

image

2. Создание календаря событий и добавление отдельных событий с параметрами.

Параметры:
— периодичность: месяц, квартал или год
— сдвиг в месяцах
— сдвиг в календарных днях
— сдвиг в рабочих днях

image

Последние три параметра нужны для установки сдвига срока сдачи отчетности. Например, если выбрана периодичность «месяц» в выгрузке будет 12 событий для каждого месяца. По умолчанию дата события 01 число каждого месяца. Вот от нее и считаем сдвиг.

— исключить первый
Этот параметр, позволяет задать особые сроки для первого события в году.

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

image

Сейчас поддерживаются стандартный *.iCal и *.csv для MS Outlook.

Собственно, адрес getcalendar.ru. Так как, скорее всего, сервис будет полезен очень редко (раз в год), никакой регистрации локальной не стал делать, а сделал авторизацию только через OpenID.

На работе помимо отчетников ЦБ, стали пользоваться и налоговики, и кадровики. В нужный день в Outlook автоматически с утра всплывает напоминание, если нужно какой-то отчет предоставить или совершить платеж.

Надеюсь, кому-нибудь пригодится.
Tags:
Hubs:
Total votes 27: ↑25 and ↓2+23
Comments15

Articles