Как программно заполнить колонку табличной части на управляемой форме в 1С:ЗУП 3.1

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом IT и автоматизация бизнеса
← На главную

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

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

Главная ошибка начинающих разработчиков: Контекст выполнения

Давайте проанализируем код, который изначально пытался использовать автор темы:


&НаСервереБезКонтекста
Процедура Расш1_ЗаполнитьЗаказыПередНаСервере
    Сообщить("Кнопка работает");
КонецПроцедуры

&НаКлиенте
Процедура Расш1_ЗаполнитьЗаказыПеред(Команда)
    Расш1_ЗаполнитьЗаказыПередНаСервере;
КонецПроцедуры

В чем заключается критическая ошибка данного подхода? Автор использовал директиву компиляции &НаСервереБезКонтекста. Процедуры и функции с этой директивой выполняются на стороне сервера, но при этом они не имеют доступа к реквизитам формы. Это означает, что внутри такой процедуры переменная Объект (и, соответственно, табличная часть Объект.Начисления) просто не существует. Платформа выдаст ошибку компиляции: «Переменная не определена (Объект)».

Директива &НаСервереБезКонтекста используется для выполнения быстрых серверных вызовов (например, получить значение из базы данных по ссылке), когда передавать всю форму на сервер не имеет смысла из-за экономии трафика. Но если наша цель — изменить данные на самой форме (заполнить строки табличной части, которые видит пользователь), нам необходимо использовать контекстный вызов с директивой &НаСервере.

При контекстном вызове состояние всех реквизитов формы передается на сервер, там мы их модифицируем, и измененная форма возвращается обратно клиенту.

Как получить номер и ссылку текущего документа программно

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

  1. Номер документа: Доступен через свойство Объект.Номер. Это свойство можно читать как на клиенте, так и на сервере.
  2. Ссылка на документ: Доступна через свойство Объект.Ссылка. Обратите внимание: если пользователь только что создал новый документ и еще не записал его, Объект.Ссылка будет содержать пустую ссылку соответствующего типа (Документы.НачислениеЗарплатыИВзносов.ПустаяСсылка()).

Рассмотрим, как это выглядит в коде:


&НаСервере
Процедура ПолучитьДанныеДокумента()
    // Получаем номер текущего документа
    ТекущийНомер = Объект.Номер;
    
    // Получаем ссылку
    ТекущаяСсылка = Объект.Ссылка;
    
    Если ТекущаяСсылка.Пустая() Тогда
        // Документ еще не записан в базу данных
        Сообщить("Внимание! Документ новый и еще не записан.");
    Иначе
        Сообщить("Работаем с документом номер: " + ТекущийНомер);
    КонецЕсли;
КонецПроцедуры

Обход табличной части в цикле

Для того чтобы обойти строки табличной части Начисления текущего документа, мы используем стандартный цикл платформы 1С — Для Каждого ... Из ... Цикл.

Важный момент: обращаться к коллекции строк табличной части на сервере нужно через конструкцию Объект.Начисления. Каждая строка в этом цикле будет иметь тип ДанныеФормыЭлементКоллекции, и мы сможем напрямую обращаться к ее колонкам (как к стандартным, так и к созданным через расширение).

Простейший пример обхода и изменения ячеек выглядит так:


Для Каждого СтрокаТЧ Из Объект.Начисления Цикл
    // Заполняем реквизит "НомерЗаказа" в каждой строке
    СтрокаТЧ.НомерЗаказа = "Значение_По_Умолчанию";
КонецЦикла;

Связывание строк с данными из справочника (Оптимальный поиск)

По задумке автора, данные для колонки НомерЗаказа должны подтягиваться из справочника по определенному условию.

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


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

Однако если в документе «Начисление зарплаты и взносов» содержатся сотни или тысячи строк (что часто бывает в крупных организациях), поиск справочника в цикле приведет к критическому падению производительности. На каждую строку система будет делать отдельный запрос к базе данных (так называемая проблема "запросов в цикле").

Рассмотрим второй вариант решения (профессиональный и оптимизированный) — получение всех данных одним запросом перед обходом табличной части:


&НаСервере
Процедура ЗаполнитьЗаказыОптимизированно()
    
    // 1. Выгружаем уникальных сотрудников из табличной части в массив
    МассивСотрудников = Новый Массив;
    Для Каждого СтрокаТЧ Из Объект.Начисления Цикл
        Если ЗначениеЗаполнено(СтрокаТЧ.Сотрудник) Тогда
            МассивСотрудников.Добавить(СтрокаТЧ.Сотрудник);
        КонецЕсли;
    КонецЦикла;
    
    // Исключаем дубликаты сотрудников для оптимизации запроса
    ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивСотрудников);
    
    Если МассивСотрудников.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Одним запросом выбираем соответствие сотрудников и их заказов из справочника
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Заказы.Сотрудник КАК Сотрудник,
        |   Заказы.Ссылка КАК Заказ
        |ИЗ
        |   Справочник.Заказы КАК Заказы
        |ГДЕ
        |   Заказы.Сотрудник В (&МассивСотрудников)";
        
    Запрос.УстановитьПараметр("МассивСотрудников", МассивСотрудников);
    
    // 3. Сохраняем результаты запроса во временную структуру Соответствие для быстрого поиска в памяти
    СоответствиеЗаказов = Новый Соответствие;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        СоответствиеЗаказов.Вставить(Выборка.Сотрудник, Выборка.Заказ);
    КонецЦикла;
    
    // 4. Обходим ТЧ и быстро заполняем колонку из подготовленного Соответствия без обращений к БД
    Для Каждого СтрокаТЧ Из Объект.Начисления Цикл
        
        Если ЗначениеЗаполнено(СтрокаТЧ.Сотрудник) Тогда
            НайденныйЗаказ = СоответствиеЗаказов.Получить(СтрокаТЧ.Сотрудник);
            Если НайденныйЗаказ <> Неопределено Тогда
                СтрокаТЧ.НомерЗаказа = НайденныйЗаказ;
            КонецЕсли;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

Модифицированность формы

Когда мы программно изменяем данные в табличной части на сервере, платформа изменяет значения в памяти. Однако пользователь должен увидеть, что документ изменился (в заголовке окна появится символ «*», а кнопка «Записать» станет активной).

Для этого в конце серверной процедуры обязательно нужно взвести флаг модифицированности формы, выполнив простую команду: ЭтаФорма.Модифицированность = Истина;. Если этого не сделать, при закрытии формы система может не предложить пользователю сохранить изменения, и проделанная работа по заполнению колонок будет утеряна.

Полный шаблон рабочего решения для расширения

Ниже представлен готовый к использованию код, который необходимо разместить в модуле расширения формы документа «Начисление зарплаты и взносов»:


&НаКлиенте
Процедура Расш1_ЗаполнитьЗаказыПеред(Команда)
    
    // Вызываем контекстную серверную процедуру для обработки данных формы
    Расш1_ЗаполнитьЗаказыНаСервере();
    
КонецПроцедуры

&НаСервере
Процедура Расш1_ЗаполнитьЗаказыНаСервере()
    
    // Выполняем обход табличной части документа
    Для Каждого СтрокаТЧ Из Объект.Начисления Цикл
        
        // Алгоритм поиска заказа в справочнике (замените "Заказы" на фактическое имя вашего справочника)
        Если ЗначениеЗаполнено(СтрокаТЧ.Сотрудник) Тогда
            
            // Выполняем поиск по реквизиту "Сотрудник"
            НайденныйЗаказ = Справочники.Заказы.НайтиПоРеквизиту("Сотрудник", СтрокаТЧ.Сотрудник);
            
            // Если значение найдено, записываем его в добавленный реквизит "НомерЗаказа"
            Если ЗначениеЗаполнено(НайденныйЗаказ) Тогда
                СтрокаТЧ.НомерЗаказа = НайденныйЗаказ;
            КонецЕсли;
            
        КонецЕсли;
        
    КонецЦикла;
    
    // Уведомляем форму, что данные были изменены пользователем программно
    ЭтаФорма.Modифицированность = Истина;
    
    // Выводим информационное сообщение пользователю
    ПоказатьОповещениеПользователя("Заполнение завершено", , "Колонка ""Номер заказа"" успешно заполнена!", КартинкаОповещения.Информация);
    
КонецПроцедуры

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

← На главную