При разработке печатных форм в системе «1С:Предприятие» программисты часто сталкиваются с необходимостью дополнить данные табличной части документа кадровыми или аналитическими сведениями из регистров. Типичный пример — вывод должности и категории сотрудника в печатной форме табеля учета рабочего времени, когда этих реквизитов нет в самом документе, но они хранятся в периодическом регистре сведений РаботникиОрганизаций.
Если попытаться связать эти таблицы некорректно, платформа выдаст дублирующиеся строки, «размножит» сотрудников или разделит макет на несвязанные области. В этой статье мы подробно разберем причины возникновения таких ошибок, спроектируем правильный и оптимизированный запрос, а также научимся программно заполнять параметры макета печатной формы.
Проанализируем исходный запрос, который привел к некорректному выводу данных на форуме:
ВЫБРАТЬ
ТабельУчетаРабочегоВремени.Начисления.(
НомерСтроки КАК НомерСтроки,
Сотрудник КАК Сотрудник,
// ... поля дней ...
) КАК Начисления,
РаботникиОрганизаций.Сотрудник КАК Сотрудник,
РаботникиОрганизаций.Должность КАК Должность
ИЗ
Документ.ТабельУчетаРабочегоВремени КАК ТабельУчетаРабочегоВремени,
РегистрСведений.РаботникиОрганизаций КАК РаботникиОрганизаций
ГДЕ
ТабельУчетаРабочегоВремени.Ссылка В(&Ссылка)
И ТабельУчетаРабочегоВремени.Начисления.Сотрудник = РаботникиОрганизаций.Сотрудник
В этой конструкции кроются сразу три критические ошибки, из-за которых печатная форма работает некорректно:
ТабельУчетаРабочегоВремени.Начисления.(...)) возвращает результат в виде дерева или вложенной выборки. При попытке обхода такого результата в цикле печать неизбежно начнет дробиться по сотрудникам.ИЗ создает декартово произведение множеств. Несмотря на условие связи в секции ГДЕ, СУБД сначала пытается сопоставить каждую строку документа с каждой строкой регистра, что приводит к колоссальному падению производительности на реальных базах данных.РаботникиОрганизаций является периодическим. Если связать его напрямую по сотруднику, запрос выберет всю историю кадровых изменений работника. Если сотрудник за время работы сменил три должности, в выборку попадут все три записи, что приведет к троекратному дублированию строки в табеле.Давайте исправим эти ошибки и построим правильный алгоритм работы с данными.
Чтобы избежать вложенности и дублирования строк, мы должны обратиться напрямую к физической таблице табличной части документа (она называется в формате Документ.[ИмяДокумента].[ИмяТабличнойЧасти]) и связать ее через левое соединение с виртуальной таблицей СрезПоследних кадрового регистра.
Рассмотрим структуру правильного запроса:
ВЫБРАТЬ
ТабельНачисления.НомерСтроки КАК НомерСтроки,
ТабельНачисления.Сотрудник КАК Сотрудник,
ТабельНачисления.Ставка КАК Ставка,
ТабельНачисления.День1 КАК День1,
ТабельНачисления.День2 КАК День2,
// ... остальные дни ...
ТабельНачисления.День31 КАК День31,
ЕСТЬNULL(КадрыСрез.Должность, ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)) КАК Должность,
ЕСТЬNULL(КадрыСрез.Категория, ЗНАЧЕНИЕ(Справочник.Категории.ПустаяСсылка)) КАК Категория
ИЗ
Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
// Присоединяем кадровые данные на дату документа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&Период, ) КАК КадрыСрез
ПО ТабельНачисления.Сотрудник = КадрыСрез.Сотрудник
ГДЕ
ТабельНачисления.Ссылка = &Ссылка
Проанализируем особенности этого решения:
ЛЕВОЕ СОЕДИНЕНИЕ. Это гарантирует, что даже если по какому-то сотруднику в регистре сведений не окажется данных, строка табеля все равно попадет в печатную форму (но с пустой должностью). При использовании внутреннего соединения такая строка просто исчезла бы из отчета.NULL, которые возвращает левое соединение при отсутствии записи в регистре, мы применили функцию ЕСТЬNULL, подставляя пустые ссылки соответствующих справочников.&Период для виртуальной таблицы среза последних необходимо передавать дату документа табеля (или конец месяца табеля), чтобы получить должности, актуальные на момент составления документа.Если в табличной части вашего документа содержится большое количество сотрудников, прямое левое соединение физической таблицы с виртуальной таблицей СрезПоследних может выполняться неоптимально. СУБД будет вынуждена рассчитывать срез последних по всему регистру сведений.
Для ускорения работы системы и минимизации нагрузки на сервер баз данных применим пакетный запрос с использованием временных таблиц. Разберем этот процесс по шагам:
Посмотрим на пример реализации такого пакета запросов:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТабельНачисления.Сотрудник КАК Сотрудник
ПОМЕСТИТЬ ВТ_Сотрудники
ИЗ
Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
ГДЕ
ТабельНачисления.Ссылка = &Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
КадрыСрез.Сотрудник КАК Сотрудник,
КадрыСрез.Должность КАК Должность,
КадрыСрез.Категория КАК Категория
ПОМЕСТИТЬ ВТ_КадровыеДанные
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(
&Период,
Сотрудник В (ВЫБРАТЬ ВТ_Сотрудники.Сотрудник ИЗ ВТ_Сотрудники)) КАК КадрыСрез
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ТабельНачисления.НомерСтроки КАК НомерСтроки,
ТабельНачисления.Сотрудник КАК Сотрудник,
ТабельНачисления.Ставка КАК Ставка,
// ... поля дней ...
ТабельНачисления.День31 КАК День31,
ЕСТЬNULL(КадрыСрез.Должность, ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)) КАК Должность,
ЕСТЬNULL(КадрыСрез.Категория, ЗНАЧЕНИЕ(Справочник.Категории.ПустаяСсылка)) КАК Категория
ИЗ
Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_КадровыеДанные КАК КадрыСрез
ПО ТабельНачисления.Сотрудник = КадрыСрез.Сотрудник
ГДЕ
ТабельНачисления.Ссылка = &Ссылка
УПОРЯДОЧИТЬ ПО
НомерСтроки
Этот подход полностью соответствует стандартам разработки фирмы «1С». Фильтрация сотрудников на уровне виртуальной таблицы среза последних снижает объем обрабатываемых сервером данных в сотни раз.
Теперь разберем вторую проблему автора темы: как связать результат выполнения запроса с параметром Должность в табличном макете печатной формы.
Для начала откроем макет в конфигураторе и настроим ячейку вывода должности:
Строка).Должность. Аналогично настроим параметр для категории — Категория.После настройки макета напишем программный код вывода печатной формы в модуле менеджера документа или модуле команды печати. Рассмотрим пример функции формирования печатной формы:
Функция СформироватьПечатнуюФормуТабеля(СсылкаНаДокумент, АдресМакета) Экспорт
ТабДок = Новый ТабличныйДокумент;
ТабДок.КлючПараметровПечати = "ПараметрыПечати_ТабельУчетаРабочегоВремени";
Макет = ПолучитьМакет("ПФ_MXL_Табель"); // Укажите имя вашего макета
// Получаем области макета
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
// Заполняем и выводим шапку
ОбластьШапка.Параметры.НомерДокумента = СсылкаНаДокумент.Номер;
ОбластьШапка.Параметры.ДатаДокумента = Формат(СсылкаНаДокумент.Дата, "ДФ=dd.MM.yyyy");
ТабДок.Вывести(ОбластьШапка);
// Подготовка запроса
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТабельНачисления.НомерСтроки КАК НомерСтроки,
| ТабельНачисления.Сотрудник КАК Сотрудник,
| ТабельНачисления.Ставка КАК Ставка,
| ТабельНачисления.День1 КАК День1,
| // ... перечисление всех необходимых дней ...
| ЕСТЬNULL(КадрыСрез.Должность, ЗНАЧЕНИЕ(Справочник.Должности.ПустаяСсылка)) КАК Должность,
| ЕСТЬNULL(КадрыСрез.Категория, ЗНАЧЕНИЕ(Справочник.Категории.ПустаяСсылка)) КАК Категория
|ИЗ
| Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&Период, ) КАК КадрыСрез
| ПО ТабельНачисления.Сотрудник = КадрыСрез.Сотрудник
|ГДЕ
| ТабельНачисления.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
// В качестве даты среза передаем дату документа
Запрос.УстановитьПараметр("Период", СсылкаНаДокумент.Date);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Метод Заполнить() сопоставляет имена полей выборки запроса с параметрами области макета
ОбластьСтрока.Параметры.Заполнить(Выборка);
// Если бы имена полей в запросе и макете не совпадали, мы бы заполняли их вручную:
// ОбластьСтрока.Параметры.Должность = Выборка.Должность;
// ОбластьСтрока.Параметры.Категория = Выборка.Категория;
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
// Выводим подвал печатной формы
ТабДок.Вывести(ОбластьПодвал);
Возврат ТабДок;
КонецФункции
Использование метода Параметры.Заполнить() существенно сокращает объем исходного кода. Главное требование для его корректной работы — полное совпадение имен полей выборки запроса (псевдонимов после ключевого слова КАК) с именами параметров в ячейках макета.
Если вы ведете разработку в современных типовых решениях от фирмы «1С» на базе конфигурации «Зарплата и Управление Персоналом», то прямое обращение к регистру сведений РаботникиОрганизаций в запросе может не сработать. В современных системах используется концепция интервальных регистров, а кадровая история сотрудников хранится в специализированных периодических регистрах сведений со сложной структурой (например, КадроваяИсторияСотрудников).
Для таких конфигураций разработчики 1С предоставляют готовый программный интерфейс (API), позволяющий получить актуальные кадровые данные сотрудников в один вызов метода встроенного языка без написания сложных запросов к интервальным таблицам.
Рассмотрим, как это можно реализовать программно:
// 1. Сначала выбираем данные табличной части документа в таблицу значений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ТабельНачисления.Сотрудник КАК Сотрудник
|ИЗ
| Документ.ТабельУчетаРабочегоВремени.Начисления КАК ТабельНачисления
|ГДЕ
| ТабельНачисления.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
ТаблицаСотрудников = Запрос.Выполнить().Выгрузить();
МассивСотрудников = ТаблицаСотрудников.ВыгрузитьКолонку("Сотрудник");
// 2. Используем типовой программный интерфейс кадрового учета
// Передаем массив сотрудников, список нужных полей и дату среза данных
ОписательКадровыхДанных = "Должность, Категория";
КадровыеДанные = КадровыйУчет.КадровыеДанныеСотрудников(Истина, МассивСотрудников, ОписательКадровыхДанных, СсылкаНаДокумент.Дата);
// На выходе получаем заполненную таблицу значений с колонками "Сотрудник", "Должность", "Категория"
// Теперь мы можем связать полученную таблицу с ТЧ документа на уровне встроенного языка или передать в запрос
Этот способ гарантирует совместимость вашего кода с будущими обновлениями типовых конфигураций и значительно упрощает процесс разработки печатных форм в сложных учетных системах.