Рассмотрим распространенную задачу, с которой сталкиваются начинающие разработчики 1С при создании внешних обработок на основе готового шаблона. Суть задачи заключается в том, чтобы при выборе документа в форме обработки программно прочитать его табличную часть, выполнить определенные вычисления и, возможно, вывести результат обратно на форму. В процессе решения мы разберем основные ошибки, связанные с клиент-серверным взаимодействием, и изучим правильные методы доступа к данным.
Давайте проанализируем исходный код, который часто пытаются написать новички, и выясним, почему он не работает. Обычно попытка выглядит так: создается обработчик события изменения поля ввода (где выбирается документ), и внутри него, на клиенте, происходит попытка обратиться к содержимому документа.
Вот пример ошибочного подхода:
&НаКлиенте
Процедура ДокументПриИзменении(Элемент)
// ОШИБКА 1: Лишнее действие с UUID
// ОШИБКА 2: Попытка доступа к данным объекта на клиенте
Данные = Документ.УникальныйИдентификатор();
КонецПроцедуры
Разберем детально, почему этот код неверен и какие архитектурные ограничения платформы 1С:Предприятие 8.3 здесь нарушаются.
&НаКлиенте означает, что код выполняется на компьютере пользователя. В этом контексте у нас нет прямого доступа к базе данных. Переменная, хранящая ссылку на документ, на клиенте имеет тип ДокументСсылка. Этот тип содержит только уникальный идентификатор и представление. Он не содержит табличных частей. Попытка обратиться к Документ.Товары на клиенте вызовет ошибку, которую помогает исправить правильная организация вызовов.Документ.УникальныйИдентификатор(). Для внутренних задач конфигурации это действие избыточно. Платформа 1С оперирует понятием Ссылка (Ref). UUID обычно нужен только для интеграции с внешними системами, но не для того, чтобы изучать состав табличной части внутри 1С.Чтобы получить данные табличной части (например, список товаров, цены, количество), нам необходимо обратиться к базе данных. В управляемом приложении любые операции с базой данных должны выполняться на Сервере.
Следовательно, алгоритм действий должен быть таким:
Это наиболее производительный способ. Запрос позволяет выбрать только те поля, которые нам действительно нужны, не загружая в память сервера весь "тяжелый" объект. Подобная оптимизация запросов крайне важна для быстродействия системы, а для написания и быстрой проверки текста запроса рекомендуем использовать консоль запросов для управляемых форм.
Посмотрим, как это реализовать. Создадим серверную функцию, которая принимает ссылку на документ и возвращает таблицу значений или массив структур.
&НаКлиенте
Процедура ДокументПриИзменении(Элемент)
// Вызываем серверную функцию, передавая ссылку из реквизита "ДокументСсылка"
ДанныеТЧ = ПолучитьДанныеТабличнойЧастиНаСервере(Объект.ДокументСсылка);
// Здесь мы можем обработать полученные данные
// Обратите внимание: ТаблицаЗначений не существует на Клиенте,
// поэтому сервер должен вернуть, например, Массив Структур,
// либо мы должны обработать результат сразу на сервере.
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДанныеТабличнойЧастиНаСервере(СсылкаНаДокумент)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧТовары.Номенклатура КАК Номенклатура,
| ТЧТовары.Количество КАК Количество,
| ТЧТовары.Цена КАК Цена,
| ТЧТовары.Сумма КАК Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ТЧТовары
|ГДЕ
| ТЧТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Результат = Запрос.Выполнить();
// Если нужно вернуть данные на Клиент, лучше выгрузить их в массив структур,
// так как ТаблицаЗначений не передается на Клиент.
МассивДанных = Новый Массив;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаДанных = Новый Структура("Номенклатура, Количество, Цена, Сумма");
ЗаполнитьЗначенияСвойств(СтрокаДанных, Выборка);
МассивДанных.Добавить(СтрокаДанных);
КонецЦикла;
Возврат МассивДанных;
КонецФункции
В данном примере мы использовали директиву &НаСервереБезКонтекста. Это хороший тон, так как такая функция не передает на сервер всю форму обработки, что полезно при разработке высоконагруженных систем.
Этот способ следует использовать, если вы планируете не просто прочитать данные, но и изменить сам документ. Для простого чтения он менее оптимален, чем использование запросов, так как требует больше памяти. Если вы разрабатываете внешнюю печатную форму, то упростить процесс тестирования кода поможет универсальная форма отладки.
Для доступа к табличной части через объект, нам нужно получить ДокументОбъект из ссылки.
&НаСервере
Процедура ОбработатьДокументНаСервере()
// "Объект.ДокументСсылка" - это реквизит нашей обработки
Если Объект.ДокументСсылка.Пустая() Тогда
Возврат;
КонецЕсли;
// Получаем объект для чтения и записи
ДокОбъект = Объект.ДокументСсылка.ПолучитьОбъект();
// Теперь мы можем обращаться к табличным частям через точку
Для Каждого СтрокаТЧ Из ДокОбъект.Товары Цикл
// Здесь можно читать данные
ТекНоменклатура = СтрокаТЧ.Номенклатура;
ТекКоличество = СтрокаТЧ.Количество;
// И производить вычисления или изменения
// СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
КонецЦикла;
// Если меняли данные, нужно записать
// ДокОбъект.Записать();
КонецПроцедуры
Часто задача состоит в том, чтобы отобразить данные из документа в таблице самой внешней обработки. Если вы используете программное управление элементами формы, то знаете, что заполнение коллекций должно происходить на сервере. Ускорить написание кода для таких операций поможет инструмент для создания кода новой таблицы значений из реквизита на форме.
Предположим, у нас на форме есть ТаблицаФормы, связанная с табличной частью обработки ТоварыОбработки.
&НаКлиенте
Процедура ДокументПриИзменении(Элемент)
ЗаполнитьТаблицуОбработкиНаСервере();
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТаблицуОбработкиНаСервере()
// Очистим текущие данные
Объект.ТоварыОбработки.Очистить();
СсылкаНаДок = Объект.ДокументСсылка;
If СсылкаНаДок.Пустая() Тогда
Возврат;
КонецЕсли;
// Используем ЗАПРОС, но выгружаем сразу в таблицу
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ИЗ Документ.РеализацияТоваровУслуг.Товары ГДЕ Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДок);
ТЗРезультат = Запрос.Выполнить().Выгрузить();
// Загружаем результат запроса в табличную часть обработки
// Важно: имена колонок в запросе и в ТЧ обработки должны совпадать!
Объект.ТоварыОбработки.Загрузить(ТЗРезультат);
КонецПроцедуры
Подведем краткий итог правильного подхода к решению задачи:
&НаКлиенте, чтобы не провоцировать ошибки клиент-серверного взаимодействия.Запрос в процедуре &НаСервере.УникальныйИдентификатор() для поиска данных внутри базы, достаточно стандартной Ссылки.Загрузить(), изучив предварительно работу с коллекциями в 1С.