Как правильно составить отчет по обновлениям контрагентов по месяцам в 1С с помощью СКД?

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

При разработке управленческих отчетов в системе 1С часто возникает задача вывода периодических данных в виде кросс-таблицы (матрицы). Типичный пример — отчет по обновлениям программного обеспечения у клиентов. Заказчик хочет видеть наглядную картину за год: список контрагентов в строках, 12 месяцев в колонках (даже если в каком-то месяце обновлений не было), а на пересечении — информацию о том, было ли обновление, и если да, то на какой именно релиз.

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

Шаг 1. Проектируем структуру хранения данных: Документы vs Регистр сведений

Первый вопрос, который встает перед разработчиком: откуда брать данные для отчета? В исходной постановке задачи у нас есть документ УчетРабочегоВремени, в шапке которого указаны Сотрудник и Контрагент, а в табличной части — Услуга и Релиз. Причем релиз заполняется только для услуги «Обновление».

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

  1. Избыточность данных. В документе УчетРабочегоВремени содержится множество других услуг (консультации, настройка оборудования, доработки). Базе данных придется перебирать гигантские объемы лишней информации, чтобы отфильтровать только строки с обновлением.
  2. Проблема производительности. Запросы к табличным частям документов выполняются дольше, так как СУБД приходится соединять таблицы шапки и табличных частей для получения дат и контрагентов. С ростом базы отчет начнет ощутимо «тормозить».
  3. Сложность контроля версий. Документ фиксирует лишь факт работы, но не отражает конечное состояние системы. В случае исправления ошибок или отката релиза логика работы с документами превратится в громоздкий код.

Правильное решение: Использование периодического регистра сведений. Регистр сведений хранит готовую, агрегированную и очищенную от «мусора» информацию, оптимизированную для быстрого чтения.

Создадим новый регистр сведений РелизыКонтрагентов со следующими настройками:

Определим ключевые поля регистра:

Шаг 2. Настраиваем проведение документа по регистру

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


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Очищаем старые движения по регистру
    Движения.РелизыКонтрагентов.Записывать = Истина;
    Движения.РелизыКонтрагентов.Очистить();
    
    // Выбираем только строки с обновлением ПО
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТЧ_Услуги.Ссылка.Дата КАК Период,
        |   ТЧ_Услуги.Ссылка.Контрагент КАК Контрагент,
        |   ТЧ_Услуги.ИнформационнаяБаза КАК ИнформационнаяБаза,
        |   ТЧ_Услуги.Релиз КАК Релиз,
        |   ТЧ_Услуги.Ссылка.Сотрудник КАК Сотрудник
        |ИЗ
        |   Документ.УчетРабочегоВремени.Услуги КАК ТЧ_Услуги
        |ГДЕ
        |   ТЧ_Услуги.Ссылка = &Ссылка
        |   И ТЧ_Услуги.Услуга = &УслугаОбновление";
        
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    // Предполагаем, что ссылка на предопределенную услугу хранится в настройках или константах
    Запрос.УстановитьПараметр("УслугаОбновление", Справочники.Услуги.ОбновлениеПО);
    
    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Пока Выборка.Следующий() Цикл
            Движение = Движения.РелизыКонтрагентов.Добавить();
            Движение.Период = Выборка.Период;
            Движение.Контрагент = Выборка.Контрагент;
            Движение.ИнформационнаяБаза = Выборка.ИнформационнаяБаза;
            Движение.Релиз = Выборка.Релиз;
            Движение.Сотрудник = Выборка.Сотрудник;
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Шаг 3. Создаем схему компоновки данных (СКД)

Теперь переходим к самому интересному — построению отчета в СКД. Разберем по шагам, как получить нужную структуру.

Создадим новый отчет, добавим основную схему компоновки данных и добавим набор данных - запрос. Напишем простой запрос к нашему регистру сведений:


ВЫБРАТЬ
    НАЧАЛОПЕРИОДА(РелизыКонтрагентов.Период, МЕСЯЦ) КАК Месяц,
    РелизыКонтрагентов.Контрагент КАК Контрагент,
    РелизыКонтрагентов.ИнформационнаяБаза КАК ИнформационнаяБаза,
    РелизыКонтрагентов.Релиз КАК Релиз
ИЗ
    РегистрСведений.РелизыКонтрагентов КАК РелизыКонтрагентов
ГДЕ
    РелизыКонтрагентов.Период МЕЖДУ &НачалоПериода И &КонецПериода

Обратите внимание на конструкцию НАЧАЛОПЕРИОДА(РелизыКонтрагентов.Период, МЕСЯЦ). Она приводит точную дату проведения документа к первому числу месяца, что позволит нам использовать это поле в качестве периодической группировки.

Шаг 4. Настраиваем вывод ровно 12 колонок (Дополнение периодов)

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

Разберем детальный алгоритм настройки кросс-таблицы:

  1. Переходим на вкладку Настройки в конструкторе СКД.
  2. Добавляем в структуру отчета элемент типа Таблица.
  3. В раздел Строки добавляем группировку по полю Контрагент (и, если требуется детальнее, подчиненную группировку по полю ИнформационнаяБаза).
  4. В раздел Колонки добавляем группировку по полю Месяц.
  5. Нажимаем правой кнопкой мыши на поле группировки Месяц в окне структуры колонок и выбираем пункт Свойства группировки.
  6. В открывшемся окне находим вкладку (или раздел настроек) Дополнение:
    • Включаем флаг Тип дополнения и выбираем значение Месяц.
    • В полях Начало периода и Конец периода привязываем параметры нашего отчета: &НачалоПериода и &КонецПериода соответственно.

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

Шаг 5. Решаем проблему множественных обновлений и откатов

Проанализируем сложную ситуацию: что произойдет, если в течение одного месяца контрагенту накатывали обновления несколько раз? Например, в начале мая обновили до версии 3.0.120, а в конце мая — до 3.0.125. Если мы выберем ресурс Релиз как обычное текстовое поле с функцией Максимум, СКД выведет только одно из значений.

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

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

Теперь, если за месяц было несколько обновлений, в ячейке отчета пользователь увидит красивую строку через запятую: «3.0.120, 3.0.125».

Шаг 6. Получение актуального релиза на конец месяца (Срез последних)

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

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

Посмотрим на пример построения такого запроса:


ВЫБРАТЬ
    ВзаиморасчетыПериоды.Период КАК Месяц,
    АктивныеБазы.Контрагент КАК Контрагент,
    АктивныеБазы.ИнформационнаяБаза КАК ИнформационнаяБаза,
    ЕСТЬNULL(ИсторияРелизов.Релиз, "Не обновлялся") КАК Релиз
ИЗ
    (ВЫБРАТЬ РАЗЛИЧНЫЕ
        НАЧАЛОПЕРИОДА(РелизыКонтрагентов.Период, МЕСЯЦ) КАК Период
    ИЗ
        РегистрСведений.РелизыКонтрагентов КАК РелизыКонтрагентов
    ГДЕ
        РелизыКонтрагентов.Период МЕЖДУ &НачалоПериода И &КонецПериода) КАК ВзаиморасчетыПериоды
        
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ РАЗЛИЧНЫЕ
        РелизыКонтрагентов.Контрагент КАК Контрагент,
        РелизыКонтрагентов.ИнформационнаяБаза КАК ИнформационнаяБаза
    ИЗ
        РегистрСведений.РелизыКонтрагентов КАК РелизыКонтрагентов) КАК АктивныеБазы
    ПО Истина

    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РелизыКонтрагентов.СрезПоследних(
        &КонецПериода,
        ) КАК ИсторияРелизов
    ПО АктивныеБазы.Контрагент = ИсторияРелизов.Контрагент
    И АктивныеБазы.ИнформационнаяБаза = ИсторияРелизов.ИнформационнаяБаза
    И ВзаиморасчетыПериоды.Период >= НАЧАЛОПЕРИОДА(ИсторияРелизов.Период, МЕСЯЦ)

Используя этот подход, мы гарантируем, что в колонке каждого месяца отобразится именно то состояние релиза, которое было зафиксировано как последнее и актуальное на конец этого временного интервала.

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

← На главную