Как перебрать все элементы строк табличной части документа в 1С:Предприятии 8.3?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет
← На главную

При разработке функционала в 1С:Предприятии 8.3 часто возникает необходимость программно обрабатывать все строки табличной части документа — есть консоль кода и запросов для разработчика 1С. Например, требуется заполнить или пересчитать значения в каждой строке табличной части после какого-либо события. В управляемых формах этот процесс имеет свои особенности, связанные с архитектурой клиент-серверного взаимодействия.

Рассмотрим ситуацию, с которой столкнулся наш коллега: требовалось заполнить счета, КПС и КЭК для всех строк табличной части "Материалы" в документе "Поступление МЗ" (БГУ 2) — для этого подойдёт настройка логики автозаполнения и контроля реквизитов. Изначально попытка была сделана на клиенте с использованием Элементы.Материалы.ТекущиеДанные, но это работает только для активной, то есть выбранной пользователем, строки. Нам же необходимо обработать абсолютно все строки табличной части.

1. Почему Элементы.Материалы.ТекущиеДанные работает только для активной строки?

Прежде всего, давайте выясним, почему прямая работа с Элементы.Материалы.ТекущиеДанные не подходит для обработки всех строк. Объект Элементы.Материалы на клиенте представляет собой элемент формы, отображающий табличную часть. Свойство ТекущиеДанные этого элемента формы действительно предоставляет доступ к данным текущей (активной) строки, то есть той, на которой установлен курсор пользователя. Это сделано для оптимизации, чтобы на клиент передавалась только необходимая для отображения и взаимодействия часть данных.

Прямой перебор всех строк табличной части на клиенте через свойства элемента формы не предусмотрен для случаев, когда строк больше, чем загружено на форму. Если строк много, платформа подгружает их порциями с сервера по мере прокрутки, что может привести к неэффективным множественным вызовам сервера, если мы попытаемся принудительно обойти все строки на клиенте.

2. Ключевое отличие клиентского и серверного контекста при работе с данными

Чтобы понять, как правильно перебрать все строки, необходимо четко представлять разницу между клиентским и серверным контекстом в 1С:Предприятии.

  1. Клиентский контекст: На клиенте мы работаем с формой документа и данными, которые на этой форме отображаются. Мы можем взаимодействовать с элементами формы, получать значения реквизитов формы, вызывать серверные процедуры. Однако у нас нет прямого доступа ко всему объекту документа в том виде, в каком он хранится в базе данных. Платформа оптимизирует передачу данных, отправляя на клиент только то, что нужно для отображения текущего состояния формы.

  2. Серверный контекст: На сервере у нас есть полный доступ к объекту документа (или любому другому объекту базы данных) со всеми его реквизитами и табличными частями. Именно на сервере происходят все операции чтения и записи данных в базу, выполняются ресурсоемкие вычисления, обращения к другим объектам конфигурации (справочникам, регистрам сведений, документам).

Из этого следует критически важный вывод: если нам необходимо программно обойти все строки табличной части документа и внести в них изменения, especially если эти изменения требуют обращения к другим данным или выполнения сложной логики (как в случае с выбором КПС/КЭК), то такую операцию необходимо выполнять на сервере. Для этой задачи есть пошаговая отладка кода 1С в режиме Предприятия.

3. Перебираем элементы строк табличной части на сервере

Итак, правильным решением является перенос логики заполнения всех строк на сервер (аналогично тому, как реализовано автозаполнение полей в авансовых отчетах) — в этом поможет управление вводом данных и выполнение произвольных обработчиков. Для этого мы создадим серверную процедуру, которая получит объект документа и сможет напрямую обратиться к его табличной части.

Шаг 1: Создаем серверную процедуру

Сначала нам нужно создать процедуру или функцию, которая будет выполняться на сервере. Для этого добавим директиву компиляции &НаСервере перед определением процедуры.


&НаСервере
Процедура ЗаполнитьВсеСтрокиМатериаловНаСервере()

    // Получаем текущий объект документа из реквизитов формы
    // ЭтотОбъект в клиентской процедуре, на сервере нам нужен сам объект документа
    ДокументОбъект = РеквизитФормыВЗначение("Объект"); 

    // Проверяем, что объект получен
    Если ДокументОбъект = Неопределено Тогда
        Возврат;
    КонецЕсли;

    // Перебираем каждую строку табличной части "Материалы"
    Для Каждого Стр Из ДокументОбъект.Материалы Цикл
        // Здесь размещаем логику заполнения для каждой строки
        // Предположим, что у нас есть функция, которая заполняет счет, КПС и КЭК для одной строки
        // Эту функцию нужно будет доработать, чтобы она принимала строку ТЧ и работала с ней напрямую
        ЗаполнитьДанныеСчетаВСтрокеСервер(Стр); 
    КонецЦикла;

    // После изменения табличной части необходимо записать документ
    // Если документ новый и еще не записан, это приведет к его записи
    // Если документ уже существует, то обновятся только измененные данные
    Попытка
        ДокументОбъект.Записать();
    Исключение
        // Обработка ошибки записи, если необходимо
        Сообщить("Ошибка при записи документа: " + ОписаниеОшибки());
    КонецПопытки;

    // Обновляем данные формы на клиенте, чтобы пользователь увидел изменения
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");

КонецПроцедуры

&НаСервере
Процедура ЗаполнитьДанныеСчетаВСтрокеСервер(СтрокаТЧ)

    // Пример логики заполнения для отдельной строки
    // Здесь мы можем обращаться к любым справочникам, регистрам, выполнять запросы и т.д.
    // Например, если счет не заполнен, заполняем его по умолчанию или по каким-то правилам
    Если ЗначениеЗаполнено(СтрокаТЧ.СчетУчета) Тогда
        // Возможно, нужно обновить КПС и КЭК на основе уже выбранного счета
        // Допустим, есть функция, которая по счету возвращает КПС и КЭК
        // Стр.КПС = ПолучитьКПСПоСчету(Стр.СчетУчета);
        // Стр.КЭК = ПолучитьКЭКПоСчету(Стр.СчетУчета);
    Иначе
        // Или выбираем счет, а затем и КПС, КЭК
        // Стр.СчетУчета = Справочники.Счета.НайтиПоКоду("000000001"); // Пример
        // Стр.КПС = ПолучитьКПСПоСчету(Стр.СчетУчета);
        // Стр.КЭК = ПолучитьКЭКПоСчету(Стр.СчетУчета);
    КонецЕсли;
    
    // В вашем случае, логика привязывается к счету, который нужно выбрать.
    // Это значит, что сначала может потребоваться диалог выбора счета,
    // а уже потом, после его выбора, программно устанавливать КПС и КЭК.
    // Если выбор счета происходит интерактивно перед вызовом этой серверной процедуры,
    // то здесь мы уже работаем с результатом этого выбора.
    
    // Для примера, имитируем заполнение:
    СтрокаТЧ.КПС = "00000000000000000"; // Пример заполнения
    СтрокаТЧ.КЭК = "000";               // Пример заполнения
    // Здесь же мы можем вызывать ту функцию, которая у вас уже работает
    // но ее нужно адаптировать, чтобы она принимала ОбъектДокумента и СтрокуТЧ
    // ЗаполнитьДанныеСчетаВСтроке(ДокументОбъект, СтрокаТЧ, Ложь); // Если эта функция тоже на сервере
    
КонецПроцедуры

Обратите внимание на несколько важных моментов в серверной процедуре ЗаполнитьВсеСтрокиМатериаловНаСервере:

  1. РеквизитФормыВЗначение("Объект"): Этот метод позволяет получить сам объект документа (который хранится в базе данных) из реквизита формы с именем "Объект". Именно этот объект нам нужен для полноценной работы с его данными, включая табличные части.

  2. Для Каждого Стр Из ДокументОбъект.Материалы Цикл: Это стандартный и наиболее эффективный способ перебора всех строк табличной части на сервере. ДокументОбъект.Материалы предоставляет доступ ко всей коллекции строк табличной части, что позволяет реализовать даже добавление строк по разным ИФО.

  3. ДокументОбъект.Записать(): После внесения изменений в объект документа на сервере, обязательно нужно вызвать метод Записать(), чтобы сохранить эти изменения в базе данных. Если этого не сделать, все ваши программные изменения будут потеряны (учтите, что в некоторых случаях может потребоваться редактирование документов в закрытом периоде).

  4. ЗначениеВРеквизитФормы(ДокументОбъект, "Объект"): После записи изменений на сервере, необходимо обновить реквизит формы "Объект" на клиенте, чтобы пользователь увидел актуальные данные в форме документа. Без этого пользователь увидит старые значения, пока не обновит форму вручную.

Шаг 2: Вызываем серверную процедуру с клиента

Теперь нам нужно вызвать эту серверную процедуру из нашей клиентской команды (например, добавив кнопку «Заполнить по ассигнованиям»). Это делается очень просто:


&НаКлиенте
Процедура изм_ЗаполнитьКПСКФОПосле(Команда)
    
    // Вызов серверной процедуры
    ЗаполнитьВсеСтрокиМатериаловНаСервере(); 
    
КонецПроцедуры

В этом случае, вся сложная логика по перебору и заполнению строк выполняется на сервере, что является правильным подходом и обеспечивает корректность работы со всеми данными документа. Вызов серверной процедуры с клиента инициирует один серверный вызов, в рамках которого обрабатываются все строки, что значительно эффективнее, чем попытки "дочитать" строки с клиента.

4. Дополнительные аспекты и оптимизация

Передача параметров в серверную процедуру

В нашем примере серверная процедура не принимала параметров, так как она сама получала объект из формы. Но если бы нам нужно было передать какие-то специфические данные с клиента на сервер для использования в логике (например, новый счет, который пользователь выбрал в диалоге), мы бы просто передали их как параметры. Важно помнить, что нельзя напрямую передавать "мутабельные" (изменяемые) значения, такие как таблицы значений или объекты формы, с клиента на сервер. Для передачи больших объемов данных лучше использовать временное хранилище.

Пример передачи параметра:


&НаКлиенте
Процедура изм_ЗаполнитьКПСКФОПосле(Команда)
    // Допустим, пользователь выбрал некий СчетУчета
    ВыбранныйСчет = Элементы.СчетПользователя.Значение;
    ЗаполнитьВсеСтрокиМатериаловНаСервере(ВыбранныйСчет); 
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьВсеСтрокиМатериаловНаСервере(СчетДляЗаполнения)

    ДокументОбъект = РеквизитФормыВЗначение("Объект"); 
    Если ДокументОбъект = Неопределено Тогда
        Возврат;
    КонецЕсли;

    Для Каждого Стр Из ДокументОбъект.Материалы Цикл
        // Используем переданный счет
        Стр.СчетУчета = СчетДляЗаполнения;
        // Дальнейшая логика по заполнению КПС и КЭК
        // ЗаполнитьДанныеСчетаВСтрокеСервер(Стр); 
    КонецЦикла;
    ДокументОбъект.Записать();
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");

КонецПроцедуры

Производительность при больших табличных частях

Если табличная часть документа содержит очень большое количество строк (сотни или тысячи), простой перебор в цикле может быть не самым оптимальным решением с точки зрения производительности — такая задача решается через загрузка и изменение данных в табличных частях 1С. В таких случаях можно рассмотреть следующие подходы на сервере:

  1. Использование запросов: Если логика заполнения позволяет, можно использовать запросы к базе данных для массового обновления данных в табличной части или для получения данных, необходимых для заполнения. Это может быть гораздо быстрее, чем построчный перебор.

  2. Пакетное обновление: Вместо изменения каждой строки по отдельности, можно сначала собрать все необходимые изменения в таблицу значений, а затем использовать методы вроде Загрузить() для табличной части, или более сложные запросы для массового обновления.

Однако для большинства типовых задач с табличными частями документов (до нескольких сотен строк) цикл Для Каждого ... Цикл на сервере является полностью адекватным и производительным решением.

Заключение

Мы выяснили, что для корректного и эффективного перебора всех элементов строк табличной части документа в 1С:Предприятии 8.3 (управляемые формы) необходимо переносить логику обработки на сервер. Использование метода РеквизитФормыВЗначение("Объект") позволяет получить полный объект документа, а цикл Для Каждого Стр Из Объект.ТабЧасть Цикл обеспечивает доступ ко всем его строкам. Не забывайте записывать изменения методом Записать() и обновлять форму методом ЗначениеВРеквизитФормы(), чтобы пользователь видел актуальные данные.

← На главную