При разработке или кастомизации печатных форм в решениях на платформе 1С:Предприятие 8.3 (например, в конфигурации «Управление торговлей 11») программисты часто сталкиваются с задачей вывода дополнительных данных, которых изначально нет в объекте печати. Классический пример такой задачи — вывод текущего долга клиента в печатной форме заказа или накладной. В качестве альтернативы ручной доработке макета для оперативного контроля взаиморасчетов можно применить готовое расширение для отображения долга клиента в формах заказов и реализаций.
В процессе реализации этой задачи легко совершить типовые ошибки, связанные с непониманием области видимости переменных, неправильной работой с выборкой запроса или некорректной настройкой отборов в виртуальных таблицах регистров накопления. В этой статье мы подробно разберем, как шаг за шагом решить эту проблему, исправить популярные ошибки компиляции и сделать вывод задолженности корректным и быстрым.
Рассмотрим ситуацию, с которой часто начинают начинающие разработчики. В коде модуля менеджера или объекта пишется запрос к регистру накопления взаиморасчетов, после чего предпринимается попытка сразу передать поле из запроса в макет или в параметры самого запроса:
// ОШИБОЧНЫЙ КОД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыСКлиентамиОстатки.СуммаОстаток КАК СуммаОстаток
|ИЗ
| РегистрНакопления.РасчетыСКлиентами.Остатки КАК РасчетыСКлиентамиОстатки";
РезультатЗапроса = Запрос.Выполнить();
Запрос.УстановитьПараметр("ДогКлиента", СуммаОстаток); // Ошибка: Переменная не определена (СуммаОстаток)
Выясним причину этой ошибки. Программа сообщает о неопределенной переменной по двум важным причинам:
Запрос.УстановитьПараметр() служит для передачи данных внутрь запроса (например, для отбора по контрагенту), а не для получения данных из него. Для вывода данных в печатную форму нам нужно заполнять параметры ОбластиМакета.СуммаОстаток существует только в тексте запроса как имя колонки будущей таблицы результатов. Чтобы обратиться к этому значению в коде 1С, необходимо выполнить запрос, получить его выборку и спозиционироваться на нужной строке.Давайте посмотрим на пример правильной структуры кода для извлечения данных из запроса:
// Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
// Создаем выборку для обхода результатов
Выборка = РезультатЗапроса.Выбрать();
// Переходим к первой (и в данном случае единственной) строке результата
Если Выборка.Следующий() Тогда
// Получаем область макета
ОбластьМакета = Макет.ПолучитьОбласть("Заказчик");
// Заполняем параметр макета полученным значением из выборки
ОбластьМакета.Параметры.ДолгКлиента = Выборка.СуммаОстаток;
КонецЕсли;
Если запустить запрос в первоначальном виде, мы столкнемся со второй проблемой: запрос вернет общую сумму остатка по абсолютно всем контрагентам в базе данных (а в случае отсутствия движений может вернуть пустой результат). Нам же требуется получить долг конкретного клиента, для которого распечатывается документ.
Для этого необходимо наложить отбор на виртуальную таблицу остатков регистра накопления. В «1С:Управлении торговлей 11» взаиморасчеты ведутся в разрезе партнеров и контрагентов через аналитику учета. В простейшем случае мы можем сделать отбор по контрагенту, связав его с данными печатаемого документа. При необходимости контролировать задолженность еще на этапе выбора контрагента в документах продажи пригодится расширение для вывода долга партнеров в формы списков и выбора.
Обычно в процедуру печати передается структура или строка таблицы значений ДанныеПечати, содержащая реквизиты текущего документа (включая ссылку на сам документ, контрагента, организацию и т.д.). Мы воспользуемся этим, чтобы передать контрагента в параметры нашего запроса.
Модернизируем наш запрос и добавим в него параметры:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыСКлиентамиОстатки.СуммаОстаток КАК СуммаОстаток
|ИЗ
| РегистрНакопления.РасчетыСКлиентами.Остатки(, АналитикаУчетаПоПартнерам.Контрагент = &Контрагент) КАК РасчетыСКлиентамиОстатки";
// Передаем контрагента из данных печатаемого документа в параметр запроса
Запрос.УстановитьПараметр("Контрагент", ДанныеПечати.Контрагент);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
ОбластьМакета = Макет.ПолучитьОбласть("Заказчик");
// Инициализируем параметр макета значением по умолчанию (на случай, если долга нет)
ОбластьМакета.Параметры.ДолгКлиента = 0;
Если Выборка.Следующий() Тогда
// Заполняем реальным значением задолженности
ОбластьМакета.Параметры.ДолгКлиента = Выборка.СуммаОстаток;
КонецЕсли;
// Выводим заполненную область в табличный документ
ТабличныйДокумент.Вывести(ОбластьМакета);
На практике у одного и того же контрагента могут быть незакрытые взаиморасчеты в разных валютах (например, в рублях и долларах США). В этом случае виртуальная таблица остатков вернет не одну строку, а несколько — по каждой валюте отдельно.
Если мы оставим код без изменений и используем конструкцию Если Выборка.Следующий() Тогда, программа выведет долг только по первой попавшейся валюте, проигнорировав остальные. Нам нужно обойти все строки выборки в цикле и сформировать красивую итоговую строку для вывода в макет.
Проанализируем, как решить эту задачу с помощью обхода в цикле Пока Выборка.Следующий():
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыСКлиентамиОстатки.СуммаОстаток КАК СуммаОстаток,
| РасчетыСКлиентамиОстатки.Валюта.Символ КАК СимволВалюты,
| РасчетыСКлиентамиОстатки.Валюта.Наименование КАК ВалютаНаименование
|ИЗ
| РегистрНакопления.РасчетыСКлиентами.Остатки(, АналитикаУчетаПоПартнерам.Контрагент = &Контрагент) КАК РасчетыСКлиентамиОстатки";
Запрос.УстановитьПараметр("Contragent", ДанныеПечати.Контрагент);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
ОбластьМакета = Макет.ПолучитьОбласть("Заказчик");
СтрокаДолга = "";
// Обходим все валютные остатки контрагента
Пока Выборка.Следующий() Цикл
// Определяем представление валюты (символ или краткое наименование)
ПредставлениеВалюты = ?(ЗначениеЗаполнено(Выборка.СимволВалюты), Выборка.SимволВалюты, Выборка.ВалютаНаименование);
// Форматируем сумму долга для красивого вывода (например, 150 000,00)
ФорматированнаяСумма = Формат(Выборка.СуммаОстаток, "ЧДЦ=2; ЧГ=3");
// Собираем общую строку задолженности
ТекущийДолг = ФорматированнаяСумма + " " + ПредставлениеВалюты;
СтрокаДолга = СтрокаДолга + ?(СтрокаДолга = "", "", ", ") + ТекущийДолг;
КонецЦикла;
// Если долгов не обнаружено, пишем "Нет долга" или оставляем пустым
Если ПустаяСтрока(СтрокаДолга) Тогда
СтрокаДолга = "0.00 руб.";
КонецЕсли;
// Устанавливаем итоговый параметр в макет
ОбластьМакета.Параметры.ДолгКлиента = СтрокаДолга;
ТабличныйДокумент.Вывести(ОбластьМакета);
Для того чтобы ваша печатная форма работала стабильно в любых сценариях ведения учета в компании, важно учитывать несколько особенностей архитектуры «1С:Управление торговлей»:
РасчетыСКлиентами (например, ЗаказКлиента = &Заказ), а учет у данного клиента ведется в целом по договору, ваш запрос вернет пустой результат. Для получения общего долга клиента всегда делайте отбор только по контрагенту/партнеру.РасчетыСКлиентами измерение ЗаказКлиента было заменено на универсальное измерение ОбъектРасчетов. Если ваша конфигурация обновится, старый код запроса с обращением к ЗаказКлиента вызовет ошибку компиляции.АналитикаУчетаПоПартнерам.Контрагент) в условиях виртуальных таблиц запроса крайне негативно сказывается на производительности СУБД. В крупных базах данных это может приводить к зависанию печати документов.Рассмотрим, как правильно оптимизировать этот запрос с точки зрения производительности 1С, получив ключ аналитики расчетов заранее в коде и передав его в запрос в готовом виде:
// Получаем ключ аналитики расчетов программным путем
ПараметрыАналитики = РегистрыНакопления.РасчетыСКлиентами.ПараметрыАналитикиУчетаПоПартнерам();
ПараметрыАналитики.Партнер = ДанныеПечати.Партнер;
ПараметрыАналитики.Контрагент = ДанныеПечати.Контрагент;
ПараметрыАналитики.Организация = ДанныеПечати.Организация;
КлючАналитики = РегистрыСведений.АналитикаУчетаПоПартнерам.ЗначениеКлючаАналитики(ПараметрыАналитики);
// Теперь наш запрос будет работать максимально быстро, используя индексированное поле
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыСКлиентамиОстатки.СуммаОстаток КАК СуммаОстаток,
| РасчетыСКлиентамиОстатки.Валюта.Символ КАК СимволВалюты
|ИЗ
| РегистрНакопления.РасчетыСКлиентами.Остатки(, АналитикаУчетаПоПартнерам = &АналитикаУчетаПоПартнерам) КАК РасчетыСКлиентамиОстатки";
Запрос.УстановитьПараметр("АналитикаУчетаПоПартнерам", КлючАналитики);
Использование этого метода гарантирует моментальное формирование печатной формы даже в базах данных с миллионами записей в регистре взаиморасчетов.