Как в 1С:УТ 11.4 правильно проверить модифицированность документа для записи истории изменений в регистр?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Почему стандартная модифицированность — не лучший выбор

Разберем типичную ситуацию: разработчики часто пытаются использовать свойство формы ЭтаФорма.Модифицированность или метод объекта ЭтотОбъект.Модифицированность(). Однако для задач логирования истории это плохой ориентир по нескольким причинам:

  1. Ложные срабатывания: Признак Модифицированность может установиться в Истина, если пользователь просто зашел в ячейку и вышел, или если программно были заполнены какие-то служебные реквизиты, не влияющие на бизнес-логику.
  2. Программная запись: Если документ записывается программно (например, через групповую обработку), формы вообще нет, и обращение к свойству формы вызовет ошибку.
  3. Отсутствие деталей: Эти свойства лишь говорят о том, что "что-то изменилось", но не отвечают на вопросы: какие именно строки были добавлены, удалены или отредактированы.

Метод сравнения «Объект vs Ссылка»

Самый надежный способ определить реальные изменения — это сравнение данных в оперативной памяти (ЭтотОбъект) с данными, которые уже записаны в базе данных (Ссылка). В процедуре ПередЗаписью модуля объекта у нас есть доступ к обоим состояниям. ЭтотОбъект содержит новые значения, а Ссылка позволяет получить старые значения через запрос или прямое обращение.

Проанализируем алгоритм действий:

  1. Проверить, не является ли объект новым (ЭтоНовый()). Для новых документов запись в историю делается всегда.
  2. Для существующих документов получить данные табличной части из базы данных по ссылке.
  3. Сравнить полученную таблицу со строками текущего объекта.

Реализация через запрос и сравнение таблиц

Рассмотрим пример кода, который можно разместить в процедуре ПередЗаписью. Мы будем использовать запрос к ссылке документа, чтобы получить состояние "Как было до изменения".


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

    // Для нового документа логика проще - просто фиксируем всё
    Если ЭтоНовый() Тогда
        ЭтотОбъект.ДополнительныеСвойства.Вставить("ВсеСтрокиНовые", Истина);
        Возврат;
    КонецЕсли;

    // Сравниваем изменения
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗаказКлиентаТовары.Номенклатура КАК Номенклатура,
    |	ЗаказКлиентаТовары.ВариантОбеспечения КАК Статус,
    |	ЗаказКлиентаТовары.КоличествоУпаковок КАК Количество,
    |	ЗаказКлиентаТовары.НомерСтроки КАК НомерСтроки
    |ИЗ
    |	Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
    |ГДЕ
    |	ЗаказКлиентаТовары.Ссылка = &Ссылка";

    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    ТЗ_Старая = Запрос.Выполнить().Выгрузить();
    
    // Получаем текущую ТЧ объекта
    ТЗ_Новая = Товары.Выгрузить( , "Номенклатура, ВариантОбеспечения, КоличествоУпаковок, НомерСтроки");
    ТЗ_Новая.Колонки.ВариантОбеспечения.Имя = "Статус";
    ТЗ_Новая.Колонки.КоличествоУпаковок.Имя = "Количество";

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

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

Нюанс с Ключом Строки (RowKey) в УТ 11.4

Проанализируем важную техническую деталь. В типовых конфигурациях, таких как УТ 11.4, ERP или КА 2, в табличных частях используется реквизит КлючСтроки. Если вы будете сравнивать данные по НомерСтроки, вы столкнетесь с проблемой: если пользователь удалит первую строку, у всех последующих строк изменятся номера. Система решит, что изменились ВСЕ строки, хотя по факту изменился только состав.

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

Оптимизация записи в регистр: НаборЗаписей вместо МенеджераЗаписи

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

Выясним, как сделать это правильно. Лучше всего использовать РегистрСведенийНаборЗаписей. Это позволит записать все изменения одним пакетом. Рассмотрим пример правильной записи в процедуре ПриЗаписи, используя данные, подготовленные ранее в ДополнительныеСвойства.


Процедура ПриЗаписи(Отказ)
    
    Если Отказ Или ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Проверяем, были ли изменения, зафиксированные в ПередЗаписью
    Если ЭтотОбъект.ДополнительныеСвойства.Свойство("ДанныеСтатусовИзменены") Тогда
        
        НаборЗаписей = РегистрыСведений.pks_СтатусыВЗаказахКлиентаИстория.СоздатьНаборЗаписей();
        // Если регистр подчинен регистратору, отбор установится сам. 
        // Если независимый (как в примере), ставим отбор по документу:
        НаборЗаписей.Отбор.ЗаказКлиента.Установить(Ссылка);
        
        // Читаем существующие записи, чтобы добавить к ним новые, либо перезаписываем полностью
        НаборЗаписей.Прочитать();
        
        Для Каждого СтрТЧ Из ЭтотОбъект.Товары Цикл
            // Логика определения: нужно ли добавлять новую запись в историю
            НоваяЗапись = НаборЗаписей.Добавить();
            НоваяЗапись.Период = ТекущаяДата();
            НоваяЗапись.ЗаказКлиента = Ссылка;
            НоваяЗапись.Номенклатура = СтрТЧ.Номенклатура;
            НоваяЗапись.Статус = СтрТЧ.ВариантОбеспечения;
            НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
        КонецЦикла;
        
        НаборЗаписей.Записать(Ложь); // Ложь - чтобы не замещать старые записи, если это история
    КонецЕсли;
    
КонецПроцедуры

Проблема СрезаПоследних

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

Если ваша задача — хранить каждое изменение, делайте регистр периодическим. Если нужно знать только текущее состояние (но отдельно от документа), лучше использовать КлючСтроки как измерение. Это исключит дублирование и путаницу при частичном обеспечении заказа.

Резюме по шагам

Подведем итог. Чтобы правильно отследить изменения в 1С:УТ 11.4, придерживайтесь следующего плана:

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

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

← На главную