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