При разработке универсальных обработок по переносу данных часто возникает задача программного заполнения табличных частей. Основная сложность заключается не только в копировании значений, но и в необходимости обработки данных «на лету» с помощью формул, а также в обеспечении корректного расчета итоговых показателей, таких как Сумма, Всего или НДС. В этой статье мы подробно разберем, как правильно организовать цикл обхода метаданных, использовать механизм Выполнить() для вычислений и заставить систему пересчитать итоги документа после программного вмешательства.
Рассмотрим ситуацию, когда у нас есть дерево значений, описывающее правила соответствия между документом-источником и документом-приемником. Наша задача — обойти строки табличной части источника, создать соответствующие строки в приемнике и заполнить их реквизиты. Разберем типичную ошибку обращения к контексту строки в коде:
Часто в коде обработки можно встретить обращение к свойствам текущей строки через объект верхнего уровня (например, СтрДерева), что приводит к некорректной работе формул для конкретных реквизитов. Проанализируем исправленный вариант кода, где мы обращаемся непосредственно к переменной цикла стрТч:
ИначеЕсли СокрЛП(СтрДерева.ТЧПриемник) = "Табличная часть" Тогда
Если НЕ ПустаяСтрока(СтрДерева.ИдИсточник) Тогда
Для Каждого стрТчИсточник из ДокИсточник[СтрДерева.ИдИсточник] Цикл // Цикл по строкам ТЧ источника
СтрТчПриемник = ДокПриемник[СтрДерева.ИдПриемник].Добавить();
Для Каждого стрТч из СтрДерева.Строки Цикл // Цикл по настройкам колонок (метаданным)
Если стрТч.ВведеноЗначение Тогда
СтрТчПриемник[стрТч.ИдПриемник] = стрТч.Источник;
ИначеЕсли не ПустаяСтрока(стрТч.формула) Тогда
Попытка
ОбъектИсточник = ДокИсточник;
ДанныеСтроки = стрТчИсточник;
Результат = "";
// Используем Выполнить() для вычисления произвольного выражения
Выполнить(стрТч.формула);
СтрТчПриемник[стрТч.ИдПриемник] = Результат;
Исключение
Сообщить("Не удалось выполнить формулу для реквизита: " + стрТч.ИдПриемник);
КонецПопытки;
Иначе
Если НЕ ПустаяСтрока(стрТч.ИдИсточник) Тогда
СтрТчПриемник[стрТч.ИдПриемник] = стрТчИсточник[стрТч.ИдИсточник];
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЕсли;
КонецЕсли;
В данном фрагменте важно обратить внимание на переменную ДанныеСтроки. Мы присваиваем ей значение стрТчИсточник, чтобы внутри текста формулы (который хранится в стрТч.формула) можно было обращаться к полям текущей строки источника, например: Результат = ДанныеСтроки.Количество * 10;.
Одной из главных проблем, с которой сталкиваются разработчики, является «застывание» итоговых сумм. При интерактивной работе пользователя (ввод данных руками в форму) срабатывают события ПриИзменении, которые автоматически рассчитывают сумму как произведение цены на количество. Однако при программном заполнении через метод Добавить() эти события не вызываются.
Проанализируем ситуацию: мы заполнили Количество и Цена, но колонка Сумма осталась нулевой. Как это исправить? Рассмотрим несколько подходов.
Результат = ДанныеСтроки.Количество * ДанныеСтроки.Цена;. Это прямое решение, которое заполнит реквизит Сумма в момент переноса.РассчитатьСумму() или РассчитатьИтоги(). Если она имеет признак Экспорт, ее можно вызвать после цикла заполнения.После заполнения табличной части необходимо корректно сохранить документ. Если нам нужно не просто сохранить черновик, а зафиксировать данные в учете, используем режим проведения. Посмотрим на пример кода:
// Установка даты со смещением (например, на 1 час назад от даты источника)
ДокПриемник.Дата = ДокИсточник.Дата - 3600; // 3600 секунд = 1 час
// Запись документа с проведением
Попытка
ДокПриемник.Записать(РежимЗаписиДокумента.Проведение);
Сообщить("Документ " + ДокПриемник.Ссылка + " успешно создан и проведен.");
Исключение
ДокПриемник.Записать(РежимЗаписиДокумента.Запись);
Сообщить("Не удалось провести документ, выполнена просто запись.");
КонецПопытки;
Обратим внимание на работу с датой. В 1С значения даты хранятся в секундах. Поэтому, если нам нужно изменить время документа (например, чтобы он шел строго перед документом-источником), мы вычитаем нужное количество секунд. В сообщениях форума упоминалась строка ДокИсточник.Дата - "00000000010000" — это альтернативный формат записи, но использование секунд (числовой тип) считается более прозрачным и надежным методом.
Функция Выполнить() позволяет исполнять код, написанный пользователем прямо в пользовательском режиме. Однако разберем важные нюансы:
Во-первых, контекст переменных. Чтобы код внутри Выполнить() «видел» переменную Результат, она должна быть объявлена в той же локальной области видимости, что и сама функция. Мы создаем переменную Результат = ""; непосредственно перед вызовом.
Во-вторых, типизация данных. Если ваша формула возвращает число, а реквизит документа является строкой, платформа может выдать ошибку или записать пустую ссылку. Рекомендуем проверять типы или использовать явное приведение, например: СтрТчПриемник[ИмяРеквизита] = Число(Результат);.
Для создания надежного механизма переноса данных мы рекомендуем придерживаться следующего алгоритма:
СокрЛП() и проверку на пустые строки).Попытка...Исключение, чтобы одна некорректная формула не останавливала всю обработку.Заполнить(Основание), если это предусмотрено конфигурацией.Обновить() для формы или Записать() для объекта, чтобы итоговые суммы в шапке документа актуализировались.Таким образом, комбинируя гибкость динамических формул и стандартные методы объектной модели 1С, можно создать эффективный инструмент для автоматизации заполнения документов любой сложности.