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

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

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

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

---

Анализ проблемы: почему в консоли работает, а в коде возвращается пустая строка?

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


Запрос.Текст = "ВЫБРАТЬ
| РазовоеНачисление.Начисления.(
| Сотрудник.ФизическоеЛицо КАК СотрудникФизическоеЛицо
| ) КАК Сотрудник,
| РазовоеНачисление.Организация.Ссылка КАК ОрганизацияСсылка,
| РазовоеНачисление.Номер КАК Номер
|ИЗ
| Документ.РазовоеНачисление КАК РазовоеНачисление
|ГДЕ
| РазовоеНачисление.Организация.Ссылка = &Ссылка
| И РазовоеНачисление.Номер = &Номер";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект.Организация);
Запрос.УстановитьПараметр("Номер", СсылкаНаОбъект.Номер);

Проанализируем эту ситуацию и выявим критические ошибки, которые привели к некорректной работе алгоритма:

  1. Неверная фильтрация по реквизитам вместо ссылки. Автор передает в параметр с именем &Ссылка значение СсылкаНаОбъект.Организация. Это запутывает код. Вдобавок фильтрация документа по связке «Организация + Номер» крайне ненадёжна и избыточна, если у нас уже есть готовая ссылка на сам документ (СсылкаНаОбъект). Из-за этого при малейшем несовпадении типов или пустом значении одного из реквизитов запрос возвращает пустоту.
  2. Использование вложенных табличных частей (выборка «через точку»). Конструкция вида РазовоеНачисление.Начисления.(...) выбирает табличную часть как объект типа РезультатЗапроса внутри основной строки. Работать с такими структурами в цикле обхода крайне неудобно, это сильно снижает производительность системы и усложняет чтение кода.
  3. Путаница с типами данных в параметрах. Как справедливо заметили участники обсуждения, в переменной СсылкаНаОбъект находится ссылка на сам документ «Разовое начисление». Пытаясь достать из неё организацию и номер для поиска, разработчик усложнил логику там, где нужно было просто передать саму ссылку на документ.

---

Шаг 1. Переходим к правильной структуре запроса

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


ВЫБРАТЬ
    РазовоеНачислениеНачисления.Сотрудник КАК Сотрудник,
    РазовоеНачислениеНачисления.Сотрудник.ФизическоеЛицо КАК ФизическоеЛицо,
    РазовоеНачислениеНачисления.Ссылка.Организация КАК Организация,
    РазовоеНачислениеНачисления.Ссылка.Номер КАК Номер
ИЗ
    Документ.РазовоеНачисление.Начисления КАК РазовоеНачислениеНачисления
ГДЕ
    РазовоеНачислениеНачисления.Ссылка = &Ссылка

В этом запросе мы обращаемся к таблице Документ.РазовоеНачисление.Начисления. Фильтрация по параметру &Ссылка гарантирует, что мы получим строки только нужного нам документа. Передавать параметры в коде 1С теперь нужно следующим образом:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
|   РазовоеНачислениеНачисления.Сотрудник КАК Сотрудник,
|   РазовоеНачислениеНачисления.Сотрудник.ФизическоеЛицо КАК ФизическоеЛицо
|ИЗ
|   Документ.РазовоеНачисление.Начисления КАК РазовоеНачислениеНачисления
|ГДЕ
|   РазовоеНачислениеНачисления.Ссылка = &Ссылка";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
РезультатЗапроса = Запрос.Выполнить();

---

Шаг 2. Как получить должность сотрудника в ЗУП 3.1

В конфигурации «Зарплата и управление персоналом 3.1» справочник Сотрудники содержит актуальные сведения о людях, но кадровая история (приемы, переводы, должности) меняется во времени и хранится в периодических регистрах сведений. Просто написать в запросе Сотрудник.Должность нельзя, так как такого реквизита в справочнике напрямую нет (или он не отражает историю изменений на дату документа).

Давайте рассмотрим три основных способа решения этой задачи — от самого простого до наиболее производительного и профессионального.

Способ А. Использование функций общего модуля (Рекомендуемый подход БСП)

Фирма «1С» настоятельно рекомендует использовать готовый программный интерфейс Библиотеки стандартных подсистем (БСП) для получения кадровых данных. Это защитит вашу внешнюю печатную форму от сбоев при последующих обновлениях конфигурации.

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


// 1. Получаем список сотрудников из документа
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |   РазовоеНачислениеНачисления.Сотрудник КАК Сотрудник
    |ИЗ
    |   Документ.РазовоеНачисление.Начисления КАК РазовоеНачислениеНачисления
    |ГДЕ
    |   РазовоеНачислениеНачисления.Ссылка = &Ссылка";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
ТаблицаСотрудников = Запрос.Выполнить().Выгрузить();
МассивСотрудников = ТаблицаСотрудников.ВыгрузитьКолонку("Сотрудник");

// 2. Вызываем программный интерфейс для получения должностей на дату документа
КадровыеДанные = "Должность,Подразделение";
ТаблицаКадровыхДанных = КадровыйУчет.КадровыеДанныеСотрудников(
    Истина, 
    МассивСотрудников, 
    КадровыеДанные, 
    СсылкаНаОбъект.Дата
);

// Теперь у нас есть ТаблицаКадровыхДанных, где сопоставлены Сотрудник и его Должность

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

Способ Б. Левое соединение с регистром сведений «Текущие кадровые данные сотрудников»

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


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

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

Способ В. Запрос к регистру «Кадровая история сотрудников» на дату документа

Если вам необходимо получить должность строго на момент проведения или составления документа средствами только одного запроса, следует использовать виртуальную таблицу СрезПоследних регистра сведений КадроваяИсторияСотрудников:


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

При установке параметров данного запроса обязательно передайте дату документа в параметр &Период:


Запрос.УстановитьПараметр("Период", СсылкаНаОбъект.Дата);
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);

---

Шаг 3. Реализуем поддержку групповой печати (Стандарты БСП)

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

Чтобы ваша печатная форма не выдавала ошибку при групповой печати, мы должны принимать в качестве параметра массив объектов — МассивОбъектов. Давайте объединим полученные знания и напишем готовый к внедрению код функции печати.


Функция Печать(МассивОбъектов, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт

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

КонецФункции

---

Подведение итогов

Для создания качественной и быстродействующей внешней печатной формы в 1С:ЗУП 3.1 важно соблюдать следующие правила:

← На главную