Как правильно обновить данные на управляемой форме при удалении строки из таблицы

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

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

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

Анализ проблемы: почему данные формы не обновлялись

Разберем типичный ошибочный сценарий. Разработчик пытается выполнить расчет и запись документа непосредственно в событии таблицы ПередУдалением:


&НаКлиенте
Процедура ТоварыПередУдалением(Элемент, Отказ)
    
    Номенклатура = Элемент.ТекущиеДанные.Номенклатура;
    Количество = Элемент.ТекущиеДанные.Количество;
    
    УбратьКоличествоНаСервере(Номенклатура, Количество);
    
    // ОШИБКА: Запись вызывается ДО того, как платформа удалит строку из таблицы формы!
    Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение));
    
КонецПроцедуры

В этом коде нарушена последовательность обработки действий платформы:

  1. Событие ПередУдалением срабатывает до физического удаления строки из коллекции формы. На этом этапе строка все еще присутствует в таблице.
  2. Вызов метода Записать() отправляет на сервер текущее состояние формы, где строка еще не удалена. Происходит запись и проведение документа со старым составом строк.
  3. После завершения обработчика платформа выполняет стандартное действие и удаляет строку только из первой таблицы на клиенте, но данные на сервере уже сохранены некорректно, а зависимая таблица теряет актуальность.

Решение 1: Разделение логики между событиями ПередУдалением и ПослеУдаления

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

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

Посмотрим на пример реализации:


&НаКлиенте
Процедура ТоварыПередУдалением(Элемент, Отказ)
    
    // Получаем данные удаляемой строки до того, как она исчезнет из коллекции
    Номенклатура = Элемент.ТекущиеДанные.Номенклатура;
    Количество = Элемент.ТекущиеДанные.Количество;
    
    // Корректируем зависимую таблицу на сервере
    УбратьКоличествоНаСервере(Номенклатура, Количество);    
    
КонецПроцедуры

&НаСервере
Процедура УбратьКоличествоНаСервере(Номенклатура, Количество)
      
    СтруктураПоиска = Новый Структура;
    СтруктураПоиска.Вставить("Номенклатура", Номенклатура);
    
    МассивСтрок = Объект.ПланФакт.НайтиСтроки(СтруктураПоиска);
    Если МассивСтрок.Количество() > 0 Тогда
        СтрокаПланФакт = МассивСтрок[0];    
        СтрокаПланФакт.Количество = СтрокаПланФакт.Количество - Количество;
    КонецЕсли;
        
КонецПроцедуры

&НаКлиенте
Процедура ТоварыПослеУдаления(Элемент)
    
    // Записываем и проводим документ, когда строка уже физически удалена из формы
    Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение));
    
КонецПроцедуры

Важное ограничение: данный подход корректно работает только при построчном удалении (например, на терминалах сбора данных или при отключенном множественном выделении строк). Если пользователь выделит несколько строк через комбинации Shift или Ctrl, свойство Элемент.ТекущиеДанные вернет только одну активную строку, а платформа удалит все выделенные, что приведет к расхождению расчетов.

Решение 2: Отмена стандартного удаления и атомарная обработка на сервере

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

Разберем по шагам, как это реализовать:

  1. В обработчике ПередУдалением устанавливаем Отказ = Истина, блокируя стандартное платформенное удаление.
  2. Передаем идентификаторы или индексы удаляемых строк на сервер.
  3. На сервере за одну транзакцию удаляем строки из первой таблицы, пересчитываем вторую таблицу и сохраняем документ.

&НаКлиенте
Процедура ТоварыПередУдалением(Элемент, Отказ)
    
    // Отменяем стандартное удаление платформы
    Отказ = Истина;
    
    // Получаем массив идентификаторов всех выделенных строк
    ВыделенныеСтроки = Элементы.Товары.ВыделенныеСтроки;
    
    Если ВыделенныеСтроки.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    МассивИдентификаторов = Новый Массив;
    Для Каждого Идентификатор Из ВыделенныеСтроки Цикл
        МассивИдентификаторов.Добавить(Идентификатор);
    КонецЦикла;
    
    // Выполняем полное удаление, пересчет и запись за один вызов
    УдалитьСтрокиИЗаписатьНаСервере(МассивИдентификаторов);
    
КонецПроцедуры

&НаСервере
Процедура УдалитьСтрокиИЗаписатьНаСервере(МассивИдентификаторов)
    
    Для Каждого Идентификатор Из МассивИдентификаторов Цикл
        СтрокаТовары = Объект.Товары.НайтиПоИдентификатору(Идентификатор);
        Если СтрокаТовары <> Неопределено Тогда
            
            // Корректируем зависимую таблицу
            СтруктураПоиска = Новый Структура("Номенклатура", СтрокаТовары.Номенклатура);
            НайденныеСтроки = Объект.ПланФакт.НайтиСтроки(СтруктураПоиска);
            Если НайденныеСтроки.Количество() > 0 Тогда
                НайденныеСтроки[0].Количество = НайденныеСтроки[0].Количество - СтрокаТовары.Количество;
            КонецЕсли;
            
            // Удаляем строку из основной таблицы
            Объект.Товары.Удалить(СтрокаТовары);
            
        КонецЕсли;
    КонецЦикла;
    
    // Записываем документ с проведением
    ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
    
КонецПроцедуры

Решение 3: Полный пересчет в модуле объекта при записи

Если таблица ПланФакт является расчетной или сводной, связывать форму сложными вычислениями не рекомендуется. Наиболее надежным архитектурным паттерном в 1С является перенос логики заполнения служебных таблиц в модуль объекта в событие ПередЗаписью.

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

  1. Форма документа отвечает только за ввод данных пользователем и не содержит избыточного кода пересчета.
  2. Сводная таблица автоматически обновляется при любом варианте записи: интерактивно, программно из внешней обработки или через интеграцию.
  3. Исключаются ошибки накопления дельт (когда при сбоях инкрементальный расчет дает неверный остаток). Таблица заполняется актуальной сверткой основной таблицы.

Пример кода в модуле документа:


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

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

← На главную