Как разработать внешний отчет в 1С:ЗУП 3.1 с выводом в собственный макет табличного документа?

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

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

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

Архитектура решения: почему ручной вывод?

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

Правильный архитектурный подход в данном случае выглядит так:

  1. Использовать СКД исключительно как инструмент для быстрого и удобного сбора данных (запросы, параметры, отборы).
  2. Программно запустить компоновку данных и выгрузить результат во временную ТаблицуЗначений.
  3. Использовать встроенный макет табличного документа (не макет СКД) для отрисовки печатной формы.
  4. Программно обойти полученную таблицу значений, заполнить параметры областей макета и вывести их в результирующий табличный документ.

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


#Область СведенияОбОбработке

Функция СведенияОВнешнейОбработке() Экспорт
    ПараметрыРегистрации = Новый Структура;
    МассивНазначений = Новый Массив;
    
    ПараметрыРегистрации.Вставить("Вид", "ДополнительныйОтчет");
    ПараметрыРегистрации.Вставить("Назначение", МассивНазначений);
    ПараметрыРегистрации.Вставить("Наименование", "Справка о зарплате за 3 месяца");
    ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина);
    ПараметрыРегистрации.Вставить("Версия", "1.0");
    ПараметрыРегистрации.Вставить("Информация", "Отчет 'Справка о зарплате за последние три месяца'");
    
    ТаблицаКоманд = ПолучитьТаблицуКоманд();
    ДобавитьКоманду(ТаблицаКоманд, "Справка о зарплате", "СправкаОЗарплате", "ОткрытиеФормы", Ложь, "");
    ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
    
    Возврат ПараметрыРегистрации;
КонецФункции

Функция ПолучитьТаблицуКоманд()
    Команды = Новый ТаблицаЗначений;
    Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
    Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
    Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
    Возврат Команды;
КонецФункции

Процедура ДобавитьКоманду(ТаблицаКоманд, Представление, Идентификатор, Использование, ПоказыватьОповещение = Ложь, Модификатор = "")
    НоваяКоманда = ТаблицаКоманд.Добавить();
    НоваяКоманда.Представление = Представление;
    НоваяКоманда.Идентификатор = Идентификатор;
    НоваяКоманда.Использование = Использование;
    НоваяКоманда.ПоказыватьОповещение = ПоказыватьОповещение;
    НоваяКоманда.Модификатор = Модификатор;
КонецПроцедуры

#КонецОбласти

Реализация метода ПриКомпоновкеРезультата

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

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


// ОШИБКА: Использование переменной до ее определения!
ЗначениеПараметраПериод = НастройкиОтчета.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ДатаНачала"));
Если ДатаНачалаНП = '00010101' Тогда 
    ДатаНачалаНП = Дата(ТекущаяДата);
КонецЕсли;

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


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

Как правильно получать кадровые данные сотрудника

Частой ошибкой при разработке отчетов под ЗУП является попытка напрямую читать кадровые данные через регистры сведений типа КадроваяИсторияСотрудников. Конфигурация ЗУП 3.1 построена на сложной архитектуре, и получение кадровых срезов напрямую запросом к физическим таблицам может привести к потере актуальности данных (из-за нестыковки дат, работы по совместительству или неопределенных состояний).

Для этих целей в конфигурации предусмотрен специализированный программный интерфейс (API) — общий модуль КадровыйУчет. На форуме возникла проблема с извлечением возвращаемого значения:


// Неверный подход, приводящий к выводу типа "СтрокаТаблицыЗначений" вместо текста
ДанныеОрганизации.Вставить("ДатаПриема", КадровыеДанныеСотрудников[0]);

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

Проанализируем правильные способы получения кадровых сведений.

Способ 1. Использование функции для множества сотрудников (с проверкой на пустую таблицу)

Если мы работаем со списком, нам обязательно нужно проверять количество строк в результате работы функции, чтобы не вызвать ошибку обращения по несуществующему индексу:


МассивСотрудников = Новый Массив;
МассивСотрудников.Добавить(Сотрудник);

КадровыеДанныеТаблица = КадровыйУчет.КадровыеДанныеСотрудников(Истина, МассивСотрудников, "ДатаПриема,Организация", ТекущаяДата);

Если КадровыеДанныеТаблица.Количество() > 0 Тогда
    СтрокаДанных = КадровыеДанныеТаблица[0];
    
    // Обращаемся строго к конкретным свойствам строки!
    ОрганизацияСотрудника = СтрокаДанных.Организация;
    ДатаПриемаСотрудника  = СтрокаДанных.ДатаПриема;
Иначе
    ОрганизацияСотрудника = Справочники.Организации.ПустаяСсылка();
    ДатаПриемаСотрудника  = '00010101';
КонецЕсли;

Способ 2. Использование функции для одного сотрудника (Рекомендуемый)

В библиотеке БСП ЗУП 3.1 есть более изящное решение для получения кадровых данных одного конкретного физлица — функция КадровыйУчет.КадровыеДанныеСотрудника. Она сразу возвращает структуру с нужными свойствами, избавляя нас от лишней работы с коллекциями:


КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудника(Истина, Сотрудник, "ДатаПриема,Организация,Должность", ТекущаяДата);

Если КадровыеДанные <> Неопределено Тогда
    Организация = КадровыеДанные.Организация;
    ДатаПриема  = КадровыеДанные.ДатаПриема;
    Должность   = КадровыеДанные.Должность;
КонецЕсли;

Где брать данные о начислениях за 3 месяца?

В сообщении 4 автор предполагает использовать срез последних регистра КадроваяИсторияСотрудников для получения сумм. Это в корне неверно. Кадровая история хранит только сведения о должностных перемещениях, ставках и подразделениях.

Чтобы получить реальные начисленные сотруднику суммы за расчетный период (3 месяца), необходимо обращаться к оборотам регистра накопления НачисленияУдержанияПоСотрудникам. Нам нужна виртуальная таблица Обороты.

Ниже приведен правильный текст запроса для СКД или ручного запроса, который позволит получить "грязную" (начисленную) заработную плату без учета удержаний и НДФЛ:


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

Программный вывод макета и заполнение параметров

Перейдем к созданию процедуры СформироватьМакет. Предположим, у нас в отчете создан макет табличного документа с именем "МакетСправки". В макете мы нарисовали три области:

Особое внимание уделим методу Заполнить(). Этот метод сопоставляет имена ключей переданной структуры (или имена колонок строки таблицы) с именами параметров в ячейках макета. Если в макете параметр называется Организация, а в структуру вы передаете ключ ОрганизацияАктуальная, автозаполнение для этого поля не сработает! Названия должны совпадать символ в символ.

Реализуем процедуру ручного формирования документа:


Процедура СформироватьМакет(ДокументРезультат, ТаблицаДанных, ДатаНачала, ДатаОкончания)
    
    // Получаем макет табличного документа из состава внешнего отчета
    Макет = ПолучитьМакет("МакетСправки");
    
    // 1. Формируем и выводим Шапку
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    
    // Определяем сотрудника из параметров или первой строки таблицы данных
    Если ТаблицаДанных.Количество() > 0 Тогда
        ВыбранныйСотрудник = ТаблицаДанных[0].Сотрудник;
    Иначе
        Возврат; // Данных для отчета нет
    КонецЕсли;
    
    // Получаем кадровые данные сотрудника
    КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудника(Истина, ВыбранныйСотрудник, "Организация,ДатаПриема,Должность", ДатаОкончания);
    
    СтруктураШапки = Новый Структура;
    СтруктураШапки.Вставить("Сотрудник", ВыбранныйСотрудник);
    СтруктураШапки.Вставить("Организация", ?(КадровыеДанные <> Неопределено, КадровыеДанные.Организация, ""));
    СтруктураШапки.Вставить("ДатаПриема", ?(КадровыеДанные <> Неопределено, Формат(КадровыеДанные.ДатаПриема, "ДЛФ=DD"), ""));
    СтруктураШапки.Вставить("ПериодСправки", "за период с " + Формат(ДатаНачала, "ДФ=dd.MM.yyyy") + " по " + Формат(ДатаОкончания, "ДФ=dd.MM.yyyy"));
    
    // Заполняем параметры области Шапка
    ОбластьШапка.Параметры.Заполнить(СтруктураШапки);
    ДокументРезультат.Вывести(ОбластьШапка);
    
    // 2. Выводим строки таблицы (начисления по месяцам)
    ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
    ОбщаяСумма = 0;
    
    Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
        
        СтруктураСтроки = Новый Структура;
        СтруктураСтроки.Вставить("Месяц", Формат(СтрокаДанных.ПериодМесяц, "ДФ='ММММ yyyy'"));
        СтруктураСтроки.Вставить("Сумма", СтрокаДанных.СуммаНачислено);
        
        ОбластьСтрока.Параметры.Заполнить(СтруктураСтроки);
        ДокументРезультат.Вывести(ОбластьСтрока);
        
        ОбщаяСумма = ОбщаяСумма + СтрокаДанных.СуммаНачислено;
        
    КонецЦикла;
    
    // 3. Формируем и выводим Подвал
    ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
    
    СтруктураПодвала = Новый Структура;
    СтруктураПодвала.Вставить("ИтогоСумма", ОбщаяСумма);
    СтруктураПодвала.Вставить("ДолжностьРуководителя", "Главный бухгалтер");
    
    ОбластьПодвал.Параметры.Заполнить(СтруктураПодвала);
    ДокументРезультат.Вывести(ОбластьПодвал);
    
КонецПроцедуры

Полезный лайфхак: Использование штатных механизмов ЗУП 3.1

Если вы разрабатываете отчет, аналогичный справке для службы занятости (которая как раз собирает средний заработок за последние 3 месяца), обратите внимание на типовой функционал 1С:ЗУП. В программе этот отчет уже встроен и формируется напрямую из документа Увольнение.

Чтобы не писать сложнейшие алгоритмы расчета среднего заработка вручную (которые должны учитывать праздничные дни, больничные, премии и фактически отработанное время), рекомендуется исследовать структуру общего модуля СправкаОСреднемЗаработкеДляОпределенияРазмераПособияПоБезработице или заглянуть внутрь макета и кода печати типовой печатной формы Т-1. Вы можете вызвать готовые функции расчета среднего заработка БСП ЗУП, передав туда ссылку на физическое лицо и расчетный период.

← На главную