В процессе разработки на платформе 1С:Предприятие 8.3 часто возникает задача определить, какие именно поля (реквизиты) были отредактированы пользователем или программно перед тем, как объект будет зафиксирован в базе данных. Это необходимо для ведения логов (журналирования изменений) (поможет расширение для быстрого логирования изменений объектов), запуска специфических бизнес-процессов при изменении ключевых показателей или для дополнительной валидации данных. Чтобы обеспечить качество кода при реализации подобных механизмов, полезно проводить регулярный анализ конфигураций и расширений на наличие ошибок. В этой статье мы подробно разберем, как реализовать такой механизм в модуле объекта, проанализируем нюансы производительности и рассмотрим готовые примеры кода.
Рассмотрим ситуацию: в документе ЗаказПокупателя менеджер может изменить дату отгрузки. Если это произошло, система должна автоматически уведомить склад. Однако, если менеджер изменил лишь комментарий, уведомление отправлять не нужно. Простая проверка в процедуре ПередЗаписью позволяет гибко управлять логикой приложения, экономя ресурсы системы и время сотрудников.
Разберем основной принцип решения задачи. Когда объект открыт в форме или обрабатывается программно, у нас есть доступ к двум состояниям данных:
ЭтотОбъект.ЭтотОбъект.Ссылка.Важно помнить, что если объект новый (ЭтоНовый() = Истина), то сравнивать его не с чем — все его заполненные реквизиты считаются измененными (созданными).
Проанализируем алгоритм, который позволяет универсально обойти все реквизиты объекта и найти отличия. Разместим этот код в модуле объекта в процедуре ПередЗаписью. При работе со сложными структурами данных разработчикам может пригодиться MCP сервер для работы с метаданными 1С, упрощающий получение описаний объектов.
Разберем по шагам:
ПолучитьОбъект() по ссылке.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Если объект новый, отслеживать изменения нет смысла
Если ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Получаем состояние объекта из базы данных
ОбъектИзБазы = Ссылка.ПолучитьОбъект();
// Получаем метаданные текущего объекта (Справочник или Документ)
МетаданныеОбъекта = Метаданные();
СписокИзменений = Новый СписокЗначений;
// Проходим циклом по всем основным реквизитам
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
// Сравниваем значения.
// Внимание: для некоторых типов данных (например, ХранилищеЗначения)
// прямое сравнение может работать некорректно
Если ЭтотОбъект[ИмяРеквизита] <> ОбъектИзБазы[ИмяРеквизита] Тогда
СписокИзменений.Добавить(ИмяРеквизита, "Изменено: " + ИмяРеквизита);
// Здесь можно сразу реализовать логику, например:
// Если ИмяРеквизита = "СуммаДокумента" Тогда...
КонецЕсли;
КонецЦикла;
// Выведем результат в окно сообщений для теста
Для Каждого Изменение Из СписокИзменений Цикл
Сообщить(Изменение.Представление);
КонецЦикла;
КонецПроцедуры
Метод Ссылка.ПолучитьОбъект() является достаточно "тяжелым", так как он считывает из базы данных абсолютно все поля объекта, включая табличные части, даже если они нам не нужны. Если в объекте много данных (например, огромная табличная часть "Товары"), это может замедлить работу системы.
Проанализируем более эффективный способ — использование запроса. Мы выберем только те поля, которые нам действительно интересны для сравнения.
// Пример оптимизированного получения данных через запрос
ТекстЗапроса = "ВЫБРАТЬ
| Ссылка.Статус КАК Статус,
| Ссылка.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя КАК Ссылка
|ГДЕ
| Ссылка = &Ссылка";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Если Выборка.Статус <> ЭтотОбъект.Статус Тогда
// Статус изменился, выполняем действия
КонецЕсли;
КонецЕсли;
Сравнение табличных частей — более трудоемкая задача. Нам необходимо понять, были ли добавлены новые строки, удалены старые или изменены значения в существующих строках. В процессе отладки таких алгоритмов часто помогает Универсальный редактор данных (УРД), который позволяет удобно просматривать и редактировать реквизиты и движения объектов.
Рассмотрим алгоритм сравнения табличных частей пошагово:
ТаблицаЗначений.ТаблицаЗначений.Количество() для быстрой проверки на добавление/удаление.Свернуть() или посимвольное сравнение строк для поиска отличий.Совет: Для точного сравнения строк табличной части часто используют сравнение по номеру строки или по уникальному идентификатору ключа связи, если он предусмотрен в конфигурации.
Часто информацию об измененных реквизитах, полученную в ПередЗаписью, нужно использовать позже — в процедуре ПриЗаписи (когда ссылка уже зафиксирована в базе) или в ОбработкаПроведения. Для этого в 1С предусмотрена коллекция ДополнительныеСвойства.
Рассмотрим пример передачи флага изменения:
// В процедуре ПередЗаписью
Если ЭтотОбъект.Сумма <> Ссылка.Сумма Тогда
ЭтотОбъект.ДополнительныеСвойства.Вставить("СуммаИзменилась", Истина);
КонецЕсли;
// В процедуре ПриЗаписи
Если ЭтотОбъект.ДополнительныеСвойства.Свойство("СуммаИзменилась") Тогда
// Выполняем действия, зная, что сумма была изменена пользователем
КонецЕсли;
ЛюбаяСсылка, сравнение <> работает корректно. Однако для составных типов данных лучше убедиться, что типы совпадают перед сравнением значений.Неопределено и NULL не равны друг другу. При получении данных запросом из базы может вернуться NULL, если поле не заполнено, что нужно учитывать при сравнении.ПередЗаписью выполняется внутри транзакции. Если вы вызываете тяжелые процедуры сравнения, это блокирует данные в таблицах СУБД для других пользователей.Выясним причину, почему иногда метод сравнения через Ссылка.ПолучитьОбъект() может не показать изменений. Это происходит, если запись объекта инициирована из самой формы, где данные уже были модифицированы, но метод ЭтотОбъект.Записать() был вызван без обновления ссылки в памяти. В типовых конфигурациях на БСП для таких целей часто используются готовые механизмы, которые можно изучить, используя Справочник по методам БСП. Также стоит рассмотреть переход на механизм Версионирование объектов БСП (здесь пригодится подсистема детальной истории изменений для 1С), если вы планируете отказаться от стандартной "Истории данных" в пользу более гибкого решения.
Таким образом, мы проанализировали несколько способов определения измененных реквизитов: от универсального перебора метаданных до точечных запросов. Выбор метода зависит от ваших требований к производительности, сложности структуры самого объекта и необходимости формировать анализ изменений конфигурации для отчетности или согласования с заказчиком.