Как надежно отследить изменение реквизита в модуле объекта 1С 8.3?

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

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача: зафиксировать факт изменения конкретного реквизита документа (например, СтатусЗаказа), чтобы выполнить определенное действие — записать историю в регистр сведений, отправить уведомление в Telegram или проверить допустимость перехода из одного состояния в другое. Казалось бы, алгоритм прост: запомнить старое значение в начале транзакции и сравнить его с новым при проведении. Однако разработчики часто сталкиваются с ситуацией, когда условие сравнения «не срабатывает», хотя визуально статус изменился.

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

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

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


Перем СтатусЗаказаНачальный;

Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Попытка получить «старое» значение из базы через Ссылку
    СтатусЗаказаНачальный = Ссылка.СтатусЗаказа;
КонецПроцедуры

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    Если СтатусЗаказа <> СтатусЗаказаНачальный Тогда
        // Логика при изменении статуса
    КонецЕсли;
КонецПроцедуры

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

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

Решение №1: Использование структуры ДополнительныеСвойства

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

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


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

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

Важный момент: использование функции ОбщегоНазначения.ЗначениеРеквизитаОбъекта предпочтительнее прямого обращения к Ссылка.Реквизит, так как внутри этой функции обычно выполняется легкий запрос ВЫБРАТЬ... ИЗ, который игнорирует объектный кэш и обращается напрямую к таблицам БД.

Решение №2: Проверка по Регистру Сведений (История статусов)

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

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


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Получаем последнее состояние из регистра сведений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |	ИсторияСтатусовСрезПоследних.Статус КАК Статус
        |ИЗ
        |	РегистрСведений.ИсторияСтатусов.СрезПоследних(&Период, Документ = &Документ) КАК ИсторияСтатусовСрезПоследних";
    
    // Используем МоментВремени, чтобы не захватить текущую запись, если она уже попала в базу
    Запрос.УстановитьПараметр("Период", МоментВремени()); 
    Запрос.УстановитьПараметр("Документ", Ссылка);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    СтарыйСтатус = Справочники.СтатусыЗаказов.ПустаяСсылка();
    Если Выборка.Следующий() Тогда
        СтарыйСтатус = Выборка.Статус;
    КонецЕсли;
    
    Если СтатусЗаказа <> СтарыйСтатус Тогда
        // Статус реально изменился относительно последней записи в истории
        // Пишем новую запись в регистр
        МенеджерЗаписи = РегистрыСведений.ИсторияСтатусов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Период = ТекущаяДата();
        МенеджерЗаписи.Документ = Ссылка;
        МенеджерЗаписи.Статус = СтатусЗаказа;
        МенеджерЗаписи.Записать();
    КонецЕсли;

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

Решение №3: Контроль перехода в экранных формах

Иногда задача стоит шире: не просто зафиксировать изменение, а запретить некорректный переход (например, нельзя перевести заказ из статуса «Доставлен» обратно в «Новый»).

Для интерактивной работы пользователя лучше всего использовать событие формы ПриИзменении. Однако помните, что проверка в форме не сработает при программном изменении объекта. Поэтому мы рекомендуем комбинированный подход:

  1. В форме: Визуально ограничиваем выбор статусов или выдаем предупреждение сразу при смене значения в поле.
  2. В модуле объекта: В процедуре ПередЗаписью выполняем жесткую проверку. Если переход недопустим, устанавливаем Отказ = Истина.

Пример проверки в модуле объекта с использованием ДополнительныеСвойства:


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

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

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

Подведем итоги

Проанализировав ситуацию, мы видим, что использование простых переменных модуля (Перем) в связке с обращением к Ссылка.Реквизит — это путь к «плавающим» ошибкам, которые сложно воспроизвести. Платформа 1С оптимизирует работу с данными, и эти оптимизации (кэширование) могут играть против разработчика в транзакционных механизмах.

Основные рекомендации:

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

← На главную