Как в 1С правильно сравнить показатели текущего и прошлого года по дням недели?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

При анализе коммерческих показателей, особенно в розничной и оптовой торговле, часто возникает необходимость сравнения текущих продаж с продажами за аналогичный период прошлого года. Однако простое сопоставление по календарным датам (например, сравнение 9 июня 2020 года с 9 июня 2019 года) часто дает искаженную картину. В одном году эта дата могла быть вторником (активный рабочий день), а в прошлом — воскресеньем (выходной день с совершенно другим паттерном покупательского поведения). Чтобы получить корректную аналитику типа LFL (Like-for-Like), необходимо выполнять выравнивание по дням недели.

В этой статье мы подробно разберем, как решить эту задачу в 1С. Мы проанализируем различные методологии сопоставления периодов, рассмотрим практические алгоритмы на встроенном языке 1С, изучим особенности работы с производственным календарем и особенности реализации таких отчетов в Системе компоновки данных (СКД).

Проблема календарного сдвига и високосных лет

Давайте разберем, почему прямое сравнение «дата в дату» не работает. Календарный год состоит из 365 дней (или 366 в високосный), что не делится нацело на 7 дней недели (365 / 7 = 52 недели и 1 день). Это означает, что каждый год дни недели смещаются на один день вперед, а в високосные годы — на два дня.

Если клиент ставит задачу сравнивать продажи день в день, но с сохранением дня недели (например, вторник со вторником), то разработчик сталкивается со следующими трудностями:

  1. Сдвиг начала года: Первая неделя года может начинаться с разных дат и даже с разных дней недели.
  2. Високосные годы: Наличие 29 февраля смещает все последующие дни недели на дополнительный день.
  3. Праздничные дни: Даже если дни недели совпадают, один из дней может оказаться государственным праздником, что кардинально меняет объем выручки.

Решение 1: Вычисление соответствия через номера недель и дней недели

Один из очевидных подходов — определить номер недели текущей даты в году, определить день недели и затем рассчитать аналогичную дату в прошлом году по этим параметрам. Рассмотрим пример функции на встроенном языке 1С, которая реализует эту логику:


Функция СоответствиеВПрошломГоду(Дата) Экспорт
    НеделяГода = НеделяГода(Дата);
    ДеньНедели = ДеньНедели(Дата);
    
    // Определяем границы прошлого года
    НачалоПрошлогоГода = НачалоГода(ДобавитьКДате(Дата, "Год", -1));
    КонецПрошлогоГода = КонецГода(НачалоПрошлогоГода);
    
    ДеньНеделиНачалоПрошлогоГода = ДеньНедели(НачалоПрошлогоГода);
    
    // Рассчитываем целевую дату в прошлом году с учетом смещения дней
    ДатаПрошлогоГода = НачалоПрошлогоГода + 3600 * 24 * 7 * (НеделяГода - 1) + 3600 * 24 * (ДеньНедели - ДеньНеделиНачалоПрошлогоГода);
    
    // Проверяем выход за рамки календарного года
    Если ДатаПрошлогоГода > КонецПрошлогоГода Тогда
        ДатаПрошлогоГода = КонецПрошлогоГода;
    КонецЕсли;
    Если ДатаПрошлогоГода < НачалоПрошлогоГода Тогда
        ДатаПрошлогоГода = НачалоПрошлогоГода;
    КонецЕсли;
    
    Возврат ДатаПрошлогоГода;
КонецФункции

Проанализируем ситуацию и проверим, как работает этот алгоритм. Если мы передадим в функцию дату 12 января 2020 года (воскресенье), алгоритм вернет нам 13 января 2019 года (также воскресенье). На первый взгляд, это именно то, что требовалось. Однако у этого метода есть существенные недостатки. В начале и в конце года из-за жесткого ограничения границами года (НачалоПрошлогоГода и КонецПрошлогоГода) происходит искусственное сжатие или обрезание дат. Это неизбежно исказит статистику продаж за первую и последнюю недели года.

Решение 2: Математический стандарт — сдвиг на 364 дня (Правило 52 недель)

В международной практике ритейла и бизнес-аналитики для корректного сравнения дней недели используется гораздо более простой и надежный математический подход — правило 364 дней.

Выясним причину его популярности. Число 364 идеально делится на 7 (52 недели * 7 дней = 364). Это означает, что если мы вычтем ровно 364 дня из любой текущей даты, то гарантированно получим тот же день недели в прошлом году. Рассмотрим подробнее несколько примеров:

  1. Вторник 9 июня 2020 года минус 364 дня дает вторник 11 июня 2019 года.
  2. Воскресенье 12 января 2020 года минус 364 дня дает воскресенье 13 января 2019 года.

Этот метод полностью исключает сложные вычисления, циклы и проблемы на стыках лет. Нам не нужно беспокоиться о високосных годах, так как сдвиг всегда жестко привязан к сетке дней недели. В запросе 1С или схеме компоновки данных (СКД) такое смещение реализуется простой и быстрой инструкцией:


ВЫБРАТЬ
    Продажи.Номенклатура,
    Продажи.СуммаОбороты КАК СуммаТекущая,
    ДОБАВИТЬКДАТЕ(Продажи.Период, ДЕНЬ, -364) КАК ПериодПрошлогоГода
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК Продажи

Важное ограничение: Поскольку астрономический год длится 365 или 366 дней, при использовании сдвига в 364 дня ежегодно накапливается погрешность в 1-2 дня. Примерно раз в 5-6 лет в аналитический календарь приходится искусственно вводить 53-ю неделю, чтобы компенсировать это отставание и вернуть календарную сетку в соответствие с астрономическим циклом. При сравнении такого года с обычным, 53-ю неделю обычно исключают из расчетов, чтобы не искажать динамику LFL.

Решение 3: Использование производственного календаря для учета праздников

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

Для решения этой проблемы в конфигурациях 1С рекомендуется задействовать данные производственного календаря. В системе существует регистр сведений ДанныеПроизводственногоКалендаря. С его помощью мы можем определять тип дня (рабочий, предпраздничный, выходной, праздник). Разберем по шагам алгоритм сравнения в таком случае:

  1. Для каждой анализируемой даты определяем ее характеристики из регистра (номер дня недели, тип дня).
  2. Для сопоставления в прошлом периоде подбираем дату, которая имеет тот же тип дня и находится в аналогичном диапазоне дат.
  3. Если точного совпадения дня недели и типа дня нет, система осуществляет поиск ближайшего подходящего дня (например, смещая выборку на одну неделю вперед или назад).

Решение 4: Оптимизация структуры данных (Добавление измерений в регистры)

Если в вашей системе планируется регулярный и глубокий анализ периодов (день, неделя, месяц, год) с выравниванием по дням недели, имеет смысл оптимизировать структуру регистров накопления. При проведении документов продаж можно сразу записывать в дополнительные измерения регистра подготовленные календарные разрезы:

  1. День года (порядковый номер дня от 1 до 366).
  2. Неделя года (номер недели от 1 до 53).
  3. День недели (число от 1 до 7).
  4. Финансовый месяц (который может отличаться от календарного для выравнивания периодов).

В этом случае текст запроса для сопоставления продаж текущего и прошлого года значительно упрощается. Посмотрим на пример соединения таблиц оборотов:


ВЫБРАТЬ
    ТекущиеПродажи.Номенклатура,
    ТекущиеПродажи.СуммаОборот КАК СуммаТекущая,
    ПрошлыеПродажи.СуммаОборот КАК СуммаПрошлыйГод
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоТекущегоПериода, &КонецТекущегоПериода, Авто, ) КАК ТекущиеПродажи
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПрошлогоПериода, &КонецПрошлогоПериода, Авто, ) КАК ПрошлыеПродажи
        ПО ТекущиеПродажи.Номенклатура = ПрошлыеПродажи.Номенклатура
        И ТекущиеПродажи.НеделяГода = ПрошлыеПродажи.НеделяГода
        И ТекущиеПродажи.ДеньНедели = ПрошлыеПродажи.ДеньНедели

Такой подход обеспечивает максимальное быстродействие отчетов, так как сложные вычисления дат не производятся "на лету" в запросе, а берутся в готовом виде из индексированных измерений регистра накопления.

Решение 5: Обход проблемы високосных лет при расчете периодов в СКД

Если отчет разрабатывается в Системе компоновки данных (СКД), разработчики часто используют стандартную функцию ДобавитьКДате() для расчета границ прошлого года. Например, для получения начала аналогичного месяца прошлого года используют выражение:

ДобавитьКДате(&КонецПериода, "Месяц", -12)

Однако в високосные годы это выражение может работать некорректно. Если конечной датой периода является 28 февраля 2021 года (невисокосный год), то вычитание 12 месяцев вернет нам 28 февраля 2020 года. При этом мы полностью потеряем из анализа продаж день 29 февраля 2020 года, что приведет к искажению финансовой отчетности.

Чтобы обойти эту проблему, в СКД рекомендуется использовать трехшаговый алгоритм смещения через временной сдвиг на одну секунду:


ДобавитьКДате(
    ДобавитьКДате(
        ДобавитьКДате(КонецПериода(&КонецПериода, "День"), "Секунда", 1), 
        "Месяц", 
        -12
    ), 
    "Секунда", 
    -1
)

Проанализируем этот алгоритм по шагам:

  1. Первая функция КонецПериода приводит исходную дату к концу дня (23:59:59).
  2. Прибавление одной секунды переводит нас на начало следующего дня (например, на 1 марта текущего года).
  3. Вычитание 12 месяцев возвращает нас ровно на 1 марта прошлого года.
  4. Вычитание одной секунды возвращает нас назад, гарантированно позиционируя на последнюю секунду февраля прошлого года, независимо от того, високосный это год (29 февраля) или невисокосный (28 февраля).

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

Подводя итоги, отметим, что выбор метода сравнения зависит от специфики бизнеса вашего клиента. Для классического ритейла оптимальным решением является использование математического правила 364 дней (сдвиг на 52 недели) или ведение управленческого учета по финансовому календарю. Для розничных магазинов с выраженной сезонностью, завязанной на государственные праздники, необходимо использовать производственный календарь. Рассмотренные нами технические приемы в 1С помогут вам реализовать любую из этих методологий быстро, точно и эффективно.

← На главную