Как вывести и рассчитать значение в дополнительной колонке таблицы на форме 1С без сохранения в базе данных?

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

При разработке интерфейсов в среде 1С:Предприятие часто возникает задача вывести в табличную часть документа или справочника на форме дополнительные расчетные данные, которые не должны храниться в базе данных. Это могут быть текущие остатки товаров на складах, актуальные цены поставщиков, процент скидки, информация о резервах или любые другие динамические показатели. Главное требование при этом — не засорять структуру метаданных конфигурации лишними реквизитами и рассчитывать значения "на лету" при открытии или прокрутке формы.

Решение этой задачи кардинально отличается в зависимости от того, какой тип интерфейса вы используете: Обычные формы (ОФ) или Управляемые формы (УФ). Проанализируем оба варианта, подробно разберем механизмы работы платформы и рассмотрим готовые шаблоны оптимизированного кода для каждого случая.

---

Решение для Обычных форм (ОФ)

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

Для динамического заполнения таких колонок в обычных формах разработчику доступны два ключевых события табличного поля:

  1. ПриВыводеСтроки
  2. ПриПолученииДанных

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

Способ 1. Использование события ПриВыводеСтроки (для простых расчетов)

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

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

Разберем по шагам пример реализации такого расчета:


Процедура ТоварыПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
    
    // Проверим, что данные строки доступны
    Если ДанныеСтроки = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Пример простого расчета процента наценки на лету
    Если ДанныеСтроки.Себестоимость <> 0 Тогда
        ПроцентНаценки = ((ДанныеСтроки.Цена - ДанныеСтроки.Себестоимость) / ДанныеСтроки.Себестоимость) * 100;
        
        // Устанавливаем текст в нашу виртуальную колонку "Наценка"
        ОформлениеСтроки.Ячейки.Наценка.УстановитьТекст(Формат(ПроцентНаценки, "ЧДЦ=2"));
    Иначе
        ОформлениеСтроки.Ячейки.Наценка.УстановитьТекст("0.00");
    КонецЕсли;
    
КонецПроцедуры

Важное предупреждение: Выясним причину, почему в обработчике ПриВыводеСтроки категорически запрещено использовать запросы к базе данных или обращаться через точку к реквизитам ссылочных полей (например, ДанныеСтроки.Номенклатура.Артикул). Поскольку событие отрабатывает построчно при каждом шевелении мыши или прокрутке списка, выполнение запроса внутри этого обработчика породит классическую проблему производительности "N+1 запросов". Система начнет совершать сотни отдельных запросов к СУБД в секунду, что приведет к глухому зависанию интерфейса программы.

Способ 2. Оптимальное использование события ПриПолученииДанных (для сложных расчетов и запросов к БД)

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

В отличие от предыдущего метода, это событие вызывается платформой один раз для всей порции (пакета) строк, которые выводятся на экран (обычно это 15–30 видимых строк). В параметре ОформленияСтрок передается коллекция оформлений всей этой группы строк.

Используем пакетный подход и разберем по шагам правильный алгоритм работы с базой данных:

  1. Создаем массив для накопления ссылок на номенклатуру из всех видимых на экране строк.
  2. Создаем соответствие, где ключом будет ссылка на номенклатуру, а значением — сам объект оформления строки ОформлениеСтроки. Это позволит нам быстро связать результаты запроса с нужными строками таблицы.
  3. Обходим коллекцию ОформленияСтрок, наполняем массив и соответствие.
  4. Выполняем один единственный запрос к базе данных, передавая собранный массив номенклатуры в качестве параметра.
  5. Выгружаем результат запроса и в цикле устанавливаем текст в ячейки оформления строк с помощью метода УстановитьТекст().

Проанализируем пример кода, реализующий данный алгоритм:


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

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

---

Решение для Управляемых форм (УФ)

В Управляемом интерфейсе платформа устроена иначе. События ПриВыводеСтроки и ПриПолученииДанных в УФ отсутствуют, так как отрисовкой элементов занимается клиент, а данные живут на сервере.

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

Разберем по шагам алгоритм настройки в Конфигураторе:

  1. Откроем управляемую форму нужного документа и перейдем в правое верхнее окно на вкладку «Реквизиты».
  2. Развернем основной реквизит формы Объект и найдем в нем целевую табличную часть (например, Товары).
  3. Нажмем правой кнопкой мыши по табличной части Товары и выберем команду «Добавить реквизит формы».
  4. Назовем наш новый реквизит (например, СвободныйОстаток) и зададим ему подходящий тип данных (например, Число).
  5. Перетащим созданный реквизит формы в левое верхнее окно «Элементы» внутрь таблицы формы. Платформа автоматически создаст колонку, у которой свойство ПутьКДанным примет вид Объект.Товары.СвободныйОстаток.

Созданный реквизит формы является полноценным полем данных, по которому платформа позволяет выполнять интерактивную сортировку, группировку и даже ручной ввод значений пользователем. При этом данные колонки существуют в оперативной памяти только пока открыта форма, и при записи документа они не будут пытаться сохраниться в СУБД.

Пример программного обхода и заполнения на сервере:

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


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Шаг 1: Выгружаем массив номенклатуры из табличной части формы
    МассивНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
    
    // Шаг 2: Получаем таблицу остатков одним запросом
    ТаблицаОстатков = ПолучитьТаблицуОстатковНаСервере(МассивНоменклатуры);
    
    // Шаг 3: Простым циклом обходим строки табличной части формы и заполняем колонку
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        
        СтрокаОстатка = ТаблицаОстатков.Найти(СтрокаТЧ.Номенклатура, "Номенклатура");
        
        Если СтрокаОстатка <> Неопределено Тогда
            // Прямое присвоение значения реквизиту формы, который не хранится в БД!
            СтрокаТЧ.СвободныйОстаток = СтрокаОстатка.Остаток;
        Иначе
            СтрокаТЧ.СвободныйОстаток = 0;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

&НаСервере
Функция ПолучитьТаблицуОстатковНаСервере(МассивНоменклатуры)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
        |    ТоварыНаСкладахОстатки.ВНаличииОстаток КАК Остаток
        |ИЗ
        |    РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК ТоварыНаСкладахОстатки";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Возврат Запрос.Выполнить().Выгрузить();
    
КонецФункции

---

Резюме и сравнение подходов

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

← На главную