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

Программист 1С v8.3 (Управляемые формы) 1С:ERP Управление предприятием Бухгалтерский учет Промышленность, строительство и АПК
← К списку

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

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

Особенности работы с табличными частями и клиент-серверным взаимодействием

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

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

Решение 1: Использование типовых механизмов пакетной обработки табличных частей

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

Обратим внимание на общие модули ПакетнаяОбработкаТабличнойЧастиКлиентСервер и ПакетнаяОбработкаТабличнойЧастиСервер. Они предоставляют методы для обработки как всей табличной части, так и отдельных строк.

Шаг 1: Подготовка параметров пересчета.

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

Шаг 2: Формирование структуры действий.

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

Шаг 3: Вызов серверной процедуры обработки.

Для выполнения пакетной обработки всей табличной части мы используем метод ОбработатьТЧ из модуля ПакетнаяОбработкаТабличнойЧастиСервер. Этот метод принимает объект табличной части и структуру действий.

Посмотрим на пример кода, демонстрирующий этот подход:


// Предполагаем, что "об" - это объект документа, например, ЗаказКлиента
// и мы находимся в серверном контексте (&НаСервере)

// === Часть 1: Заполнение ставки НДС (если требуется) ===
// Получаем объект документа
// об = ссылкаРТУ.ПолучитьОбъект(); // Если мы работаем со ссылкой

СтруктураДействийЗаполнения = Новый Структура;
СтруктураДействийЗаполнения.Вставить("ЗаполнитьСтавкуНДС", ПакетнаяОбработкаТабличнойЧастиКлиентСервер.ПараметрыЗаполненияСтавкиНДС(об));
ПакетнаяОбработкаТабличнойЧастиСервер.ОбработатьТЧ(об.Товары, СтруктураДействийЗаполнения, Неопределено);

// === Часть 2: Пересчет сумм НДС и сумм с НДС ===
СтруктураПересчетаСуммы = ПакетнаяОбработкаТабличнойЧастиКлиентСервер.ПараметрыПересчетаСуммыНДСВТЧ(об);

СтруктураДействийПересчета = Новый Структура;
СтруктураДействийПересчета.Вставить("ПересчитатьСуммуНДС", СтруктураПересчетаСуммы);
СтруктураДействийПересчета.Вставить("ПересчитатьСуммуСНДС", СтруктураПересчетаСуммы);

ПакетнаяОбработкаТабличнойЧастиСервер.ОбработатьТЧ(об.Товары, СтруктураДействийПересчета, Неопределено);

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

Решение 2: Пользовательский расчет и построчное обновление табличной части

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

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

Шаг 1: Создание серверной процедуры.

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


Процедура РасчитатьМ2(ДанныеФормы) Экспорт
    ТекстПодробно = "";
    // ... дальнейший код ...
КонецПроцедуры;

Шаг 2: Подготовка параметров для построчного пересчета.

Аналогично первому решению, мы можем использовать ПакетнаяОбработкаТабличнойЧастиКлиентСервер.ПараметрыПересчетаСуммыНДСВСтрокеТЧ для получения параметров пересчета конкретной строки.


СтруктураПересчетаСуммы = ПакетнаяОбработкаТабличнойЧастиКлиентСервер.ПараметрыПересчетаСуммыНДСВСтрокеТЧ(ДанныеФормы);
СтруктураДействий = Новый Структура;
СтруктураДействий.Вставить("ПересчитатьСуммуНДС",  СтруктураПересчетаСуммы);
СтруктураДействий.Вставить("ПересчитатьСуммуСНДС", СтруктураПересчетаСуммы);
СтруктураДействий.Вставить("ПересчитатьСуммуСУчетомРучнойСкидки", Новый Структура("Очищать", Ложь));
СтруктураДействий.Вставить("ПересчитатьСуммуСУчетомАвтоматическойСкидки", Новый Структура("Очищать", Истина));

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

Шаг 3: Оптимизация получения дополнительных реквизитов.

Важным моментом является оптимизация доступа к данным. Если мы получаем ссылки на метаданные (например, на виды дополнительных реквизитов) в цикле, это может замедлить работу. Мы настоятельно рекомендуем вынести такие операции за пределы цикла перебора строк табличной части.


// Получаем ссылки на дополнительные реквизиты один раз до начала цикла
ДопСвойствоШирина = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Ширина");
ДопСвойствоДлина = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Длина");

КэшированныеЗначения = Неопределено; // Используется для оптимизации в типовых механизмах

Шаг 4: Перебор строк табличной части и выполнение расчетов.

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


Для Каждого Товар Из ДанныеФормы.Товары Цикл
    // Получаем значения дополнительных реквизитов для текущей номенклатуры и характеристики
    // Важно: Проверяйте существование реквизитов перед обращением к ним
    Ширина = 0;
    СвойствоШирина = Товар.Номенклатура.ДополнительныеРеквизиты.Найти(ДопСвойствоШирина);
    Если СвойствоШирина <> Неопределено Тогда
        Ширина = СвойствоШирина.Значение;
    КонецЕсли;

    Длина = 0;
    СвойствоДлина = Товар.Характеристика.ДополнительныеРеквизиты.Найти(ДопСвойствоДлина);
    Если СвойствоДлина <> Неопределено Тогда
        Длина = СвойствоДлина.Значение;
    КонецЕсли;

    КоличествоШтук = Товар.Д4Зак_КоличествоСправочно; // Пример пользовательского поля

    // Выполняем наш расчет
    Площадь = (Ширина / 1000) * (Длина / 1000) * КоличествоШтук;

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

    // Вызываем построчную обработку типовым механизмом
    ПакетнаяОбработкаТабличнойЧастиСервер.ОбработатьСтрокуТЧ(Товар, СтруктураДействий, КэшированныеЗначения, ДанныеФормы.Товары);

    // Формируем дополнительную информацию для вывода
    Наименование = "";
    Если Товар.Свойство("НоменклатураПартнера") Тогда
        Если Товар.НоменклатураПартнера <> Справочники.НоменклатураКонтрагентов.ПустаяСсылка() Тогда
            Наименование = Товар.НоменклатураПартнера.НаименованиеПолное;
        КонецЕсли;
    Иначе
        Наименование = Товар.Номенклатура.НаименованиеПолное;
    КонецЕсли;

    ТекстПодробно = ТекстПодробно + Наименование + " длина " + Формат(Длина, "ЧГ=") + " " + КоличествоШтук + " шт" + Символы.ПС;
КонецЦикла;

Шаг 5: Обновление дополнительных полей формы.

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


    Если ДанныеФормы.Свойство("ДополнительнаяИнформация") Тогда
        ДанныеФормы.ДополнительнаяИнформация = ТекстПодробно;
    ИначеЕсли ДанныеФормы.Свойство("ПрочаяДополнительнаяИнформацияТекст") Тогда
        ДанныеФормы.ПрочаяДополнительнаяИнформацияТекст = ТекстПодробно;
    КонецЕсли;
КонецПроцедуры;

Важные замечания по коду:

  1. Переменные для перебора: Для ясности кода, вместо Для Каждого Товар Из ДанныеФормы.Товары Цикл, мы рекомендуем использовать более описательные имена, например, Для Каждого СтрокаТовар Из ДанныеФормы.Товары Цикл или Для Каждого Строка Из ДанныеФормы.Товары Цикл.
  2. Доступ к реквизитам: В типовых конфигурациях, таких как ERP, табличные части документов (например, ЗаказПоставщику.Товары) обычно содержат оба реквизита КоличествоУпаковок и Количество, и они могут быть заполнены. Проверяйте наличие и тип данных перед использованием.
  3. НаименованиеПолное: Использование Товар.Номенклатура.НаименованиеПолное напрямую может быть не всегда оптимальным. В некоторых случаях, особенно при работе с объектами, не записанными в базу, или при сложных сценариях, может потребоваться более аккуратный подход к получению наименования.

Ключевой момент: Обновление отображения формы

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

Мы рассмотрим несколько методов, которые помогут нам обновить форму:

  1. Прочитать(): Этот метод объекта формы (или объекта документа, если мы работаем непосредственно с ним) заставляет форму перечитать свои данные из объекта в базе данных. Если наша внешняя обработка уже записала изменения в базу, Прочитать() — это наиболее надежный способ обновить всю форму.
  2. 
    // Пример вызова после выполнения серверной процедуры и записи объекта
    ЭтаФорма.Прочитать();
    
  3. ОбновитьОтображениеДанных(): Этот метод также позволяет обновить данные на форме. Он может быть полезен, когда изменения вносятся в данные формы, но еще не записаны в базу данных, или когда нужно обновить только часть данных.
  4. 
    // Пример вызова
    ЭтаФорма.ОбновитьОтображениеДанных();
    
  5. ЗначениеВРеквизитФормы(): Если изменения внесены в объект, но еще не записаны в базу данных, и нам нужно перенести эти изменения на реквизиты формы, мы можем использовать ЗначениеВРеквизитФормы(). Этот метод позволяет передать данные из объекта в реквизит формы.
  6. 
    // Пример: Обновить реквизит формы "Объект" из переменной "ОбъектДокумента"
    ЗначениеВРеквизитФормы(ОбъектДокумента, "Объект");
    

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

Лучшие практики и рекомендации

Чтобы ваша работа с табличными частями была эффективной и поддерживаемой, мы предлагаем придерживаться следующих рекомендаций:

  1. Выносите код в общие модули: Процедуры, которые выполняют типовые расчеты или используются в нескольких местах, всегда лучше выносить в общие модули. Это повышает переиспользуемость кода и упрощает его поддержку. Не забудьте указать директивы компиляции (&НаСервере, &НаКлиенте) в зависимости от контекста выполнения.
  2. Используйте серверные вызовы для объемных операций: Если операция требует доступа к базе данных, выполнения сложной логики или большого объема вычислений, всегда выполняйте ее на сервере. Это разгружает клиент и улучшает производительность.
  3. Оптимизируйте доступ к данным в циклах: Как мы выяснили, получение метаданных или часто используемых значений из справочников внутри цикла перебора строк табличной части — плохая практика. Выносите такие операции за пределы цикла.
  4. Программно изменяйте управляемые формы: При разработке внешних обработок или доработок часто требуется добавить новые реквизиты на форму или изменить ее поведение. Для упрощения поддержки и обновления конфигурации рекомендуется вносить все доработки кодом в процедуре ПриСозданииНаСервере формы.
  5. Явно обновляйте форму: Всегда помните о необходимости явно обновлять отображение формы после программного изменения данных. Без этого пользователь не увидит результат вашей работы.

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

← К списку