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