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