Как отследить изменения в колонке табличной части документа 1С?

Программист
← На главную

Часто перед разработчиками 1С встает задача выполнить определенное действие, например, сделать запись в регистр сведений, только в том случае, если пользователь изменил данные в конкретной колонке табличной части документа. Просто перезаписывать все данные при каждой записи документа не всегда оптимально, особенно если речь идет о больших объемах информации или сложной логике. Рассмотрим несколько подходов для решения этой задачи.

Способ 1. Сравнение табличных частей в событии «ПередЗаписью»

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

Разберем алгоритм по шагам:

  1. Получаем исходные данные. В обработчике события ПередЗаписью модуля объекта документа нам нужно получить данные табличной части, которые были сохранены в базе до того, как пользователь начал вносить правки. Это можно сделать, обратившись к данным по ссылке на объект. Важно помнить, что этот способ работает только для уже существующих, а не для новых документов.
  2. Получаем текущие данные. Данные, измененные пользователем, находятся непосредственно в объекте документа, с которым мы работаем.
  3. Сравниваем таблицы. Как верно было замечено на форуме, простое построчное сравнение "в лоб" не подойдет. Пользователь мог добавить новые строки, удалить существующие или поменять их местами. Поэтому сравнивать строки нужно по уникальному ключу — набору колонок, который однозначно идентифицирует строку (например, «Номенклатура» + «Характеристика»). Если такого логического ключа нет, можно использовать системный реквизит НомерСтроки, но с осторожностью, так как его уникальность гарантируется только в пределах одной таблицы.

Посмотрим на пример. Допустим, у нас есть документ «ПоступлениеТоваров» с табличной частью «Товары», и нам нужно отследить изменение колонки «Цена».


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Для нового документа сравнивать не с чем, выходим
    Если ЭтотОбъект.ЭтоНовый() Тогда
        Возврат;
    КонецЕсли;
    
    // 1. Получаем старую версию табличной части из базы данных
    // Выгружаем в ТаблицуЗначений для удобства работы
    ТаблицаДо = ЭтотОбъект.Ссылка.Товары.Выгрузить();
    
    // Добавляем индекс по ключевой колонке для быстрого поиска
    ТаблицаДо.Индексы.Добавить("Номенклатура");
    
    // 2. Получаем новую версию табличной части из объекта
    ТаблицаПосле = ЭтотОбъект.Товары.Выгрузить();
    
    // 3. Сравниваем таблицы
    // Перебираем строки измененной таблицы
    Для Каждого СтрокаПосле Из ТаблицаПосле Цикл
        
        // Ищем соответствующую строку в старой таблице по ключу
        ПараметрыПоиска = Новый Структура("Номенклатура", СтрокаПосле.Номенклатура);
        НайденныеСтрокиДо = ТаблицаДо.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтрокиДо.Количество() > 0 Тогда
            // Строка с такой номенклатурой была и раньше, сравним ее
            СтрокаДо = НайденныеСтрокиДо[0];
            
            Если СтрокаДо.Цена <> СтрокаПосле.Цена Тогда
                // Значение в колонке "Цена" изменилось!
                // Выполняем необходимые действия, например, пишем в регистр
                Сообщить("Цена для номенклатуры '" + СтрокаПосле.Номенклатура + "' была изменена с " + СтрокаДо.Цена + " на " + СтрокаПосле.Цена);
                // ... ваш код для записи в регистр ...
            КонецЕсли;
            
        Иначе
            // Строку не нашли в старой таблице, значит, это новая строка
            Сообщить("Добавлена новая позиция: '" + СтрокаПосле.Номенклатура + "' с ценой " + СтрокаПосле.Цена);
            // ... ваш код для записи в регистр ...
        КонецЕсли;
        
    КонецЦикла;
    
    // Дополнительно можно реализовать поиск удаленных строк,
    // сравнив количество строк или перебирая ТаблицаДо и ища строки в ТаблицаПосле.
    
КонецПроцедуры

Способ 2. Использование события «ПриИзменении»

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

Особенности метода:

В общем случае, для задачи записи в регистр этот метод менее предпочтителен, чем анализ в событии ПередЗаписью.

Способ 3. Анализ с помощью универсальной функции сравнения

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

Функция для поиска разницы между двумя таблицами значений обычно принимает на вход три параметра: старую таблицу, новую таблицу и список колонок-измерений (ключевых колонок для сопоставления строк).

Пример вызова мог бы выглядеть так:


// Предположим, у нас есть функция РазницаТаблиц(ТаблицаДо, ТаблицаПосле, "Номенклатура,Характеристика")
ТаблицаДо = ЭтотОбъект.Ссылка.Товары.Выгрузить();
ТаблицаПосле = ЭтотОбъект.Товары.Выгрузить();

ТаблицаРазличий = ОбщегоНазначения.РазницаТаблиц(ТаблицаДо, ТаблицаПосле, "Номенклатура");

Для Каждого СтрокаРазличия Из ТаблицаРазличий Цикл
    // Анализируем таблицу различий и выполняем действия
КонецЦикла;

В современных конфигурациях, использующих Библиотеку стандартных подсистем (БСП), уже могут быть готовые функции для подобных сравнений. Например, стоит поискать в общем модуле ОбщегоНазначения функции вроде КоллекцииИдентичны. Если ваша конфигурация самописная и старая, придется писать такую функцию самостоятельно или использовать готовые примеры, как тот, что был в теме форума.

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

Альтернативный подход: полная перезапись данных в регистре

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

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

Пример для регистра сведений:


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

КонецПроцедуры

Преимущества:

Недостатки:

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

← На главную