При доработке печатных форм первичных документов и универсальных передаточных документов (УПД) часто возникает задача переключения макета в зависимости от даты оформления операции. Законодательные формы периодически обновляются, поэтому печать документов за прошлые периоды должна выполняться по старым правилам, а за новые — по актуальному формату.
Разберем типичную проблему, возникающую при попытке проверить дату документа перед получением макета, выясним причину возникновения ошибки «Ошибка чтения значения» и рассмотрим корректные способы реализации переключения макетов.
Рассмотрим ситуацию, когда в процедуре формирования печатного документа (например, ЗаполнитьТабличныйДокументУПД) разработчик пытается получить реквизит Дата из выборки результата запроса до входа в цикл обработки строк:
ДанныеПечати = ДанныеДляПечати.РезультатПоШапке.Выбрать();
Если ДанныеПечати.Дата >= ('20171001') Тогда
Макет = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент011017");
Иначе
Макет = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент");
КонецЕсли;
При выполнении этого кода платформа 1С выдает ошибку чтения значения. Проанализируем, почему это происходит:
Выбрать() возвращает объект типа ВыборкаИзРезультатаЗапроса, указатель которого изначально находится в начальном состоянии (перед первой записью). Пока не вызван метод ДанныеПечати.Следующий(), выборка не содержит текущей строки, и обращение к любому полю, включая ДанныеПечати.Дата, вызывает исключение.('20171001') в круглых скобках интерпретируется как строковый литерал, а не значение типа Дата. В языке 1С литерал даты задается в одинарных апострофах без круглых скобок — например, '20171001' или '20171001000000'.Посмотрим, как исправить логику обхода. Поскольку в одной процедуре печати может передаваться массив из нескольких документов, проверка даты и переключение макета должны выполняться для каждого документа индивидуально после позиционирования выборки.
Разберем по шагам правильное размещение условия:
ДанныеПечати = ДанныеДляПечати.РезультатПоШапке.Выбрать();
ВыборкаПоДокументам = ДанныеДляПечати.РезультатПоТабличнойЧасти.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
ПервыйДокумент = Истина;
Пока ДанныеПечати.Следующий() Цикл
// Проверяем дату после позиционирования выборки на текущем документе
Если ДанныеПечати.Дата >= '20171001' Тогда
Макет = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент011017");
Иначе
Макет = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент");
КонецЕсли;
// Далее следует стандартный вывод областей шапки, строк и подвала
// ...
КонецЦикла;
Обратим внимание на производительность: постоянный вызов функции УправлениеПечатью.МакетПечатнойФормы() внутри тела цикла приводит к избыточным операциям чтения макета из метаданных на каждой итерации. Если на печать отправляется большой реестр документов, это существенно замедляет формирование отчета.
Оптимизируем алгоритм: получим ссылки на оба макета до входа в цикл, а внутри цикла будем лишь переключать переменную на требуемый экземпляр макета.
// Предварительно получаем оба макета до начала обхода выборки
МакетСтарый = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент");
МакетНовый = УправлениеПечатью.МакетПечатнойФормы("Обработка.ПечатьОбщихФорм.ПФ_MXL_УниверсальныйПередаточныйДокумент011017");
ДанныеПечати = ДанныеДляПечати.РезультатПоШапке.Выбрать();
ВыборкаПоДокументам = ДанныеДляПечати.РезультатПоТабличнойЧасти.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
ПервыйДокумент = Истина;
Пока ДанныеПечати.Следующий() Цикл
// Выбираем макет в зависимости от даты документа
Макет = ?(ДанныеПечати.Дата >= '20171001', МакетНовый, МакетСтарый);
Если ДанныеПечати.УчитыватьНДС И (ДанныеПечати.СтатусУПД = 2 ИЛИ ДанныеПечати.Номер = Неопределено) Тогда
// Обработка сообщений об ошибках заполнения
Продолжить;
КонецЕсли;
Если Не ПервыйДокумент Тогда
ТабличныйДокумент.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
ПервыйДокумент = Ложь;
НомерСтрокиНачало = ТабличныйДокумент.ВысотаТаблицы + 1;
СведенияОбОрганизации = ФормированиеПечатныхФормСервер.СведенияОЮрФизЛице(ДанныеПечати.Организация, ДанныеПечати.Дата);
СведенияОКонтрагенте = ФормированиеПечатныхФормСервер.СведенияОЮрФизЛице(ДанныеПечати.Контрагент, ДанныеПечати.Дата);
ЗаполнитьРеквизитыШапкиУПД(ДанныеПечати, СведенияОбОрганизации, СведенияОКонтрагенте, Макет, ТабличныйДокумент);
// Вывод табличной части и подвала
ЗаголовокТаблицы = Макет.ПолучитьОбласть("ЗаголовокТаблицы");
ТабличныйДокумент.Вывести(ЗаголовокТаблицы);
// ... дальнейший вывод строк
КонецЦикла;
Рассмотрим дополнительные моменты, которые необходимо учитывать при разработке:
ЗаполнитьРеквизитыШапкиУПД, ЗаполнитьРеквизитыСтрокиТовара, ЗаполнитьРеквизитыПодвалаУПД) корректно обрабатывают области нового макета. В новых редакциях УПД добавляются дополнительные колонки (например, код вида товара, идентификатор госконтракта), параметры которых должны присутствовать в структуре данных.