Почему не сохраняется программно измененная дата документа при нажатии "Провести и закрыть" в 1С?

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

В процессе разработки в 1С часто возникает задача программно изменять реквизиты документа перед его записью. Классический пример — установка определенного времени для документа, если пользователь его не указал. Однако иногда разработчики сталкиваются с загадочным поведением: код, который прекрасно работает при нажатии кнопки "Провести", отказывается сохранять изменения при использовании кнопки "Провести и закрыть". При этом отладчик может показывать противоречивые данные, что еще больше запутывает ситуацию. Давайте разберемся, почему так происходит и как это исправить.

Проанализируем типичную ситуацию: вы написали код для установки времени документа, например, на 09:00:00, в обработчике события. При проведении нового документа через "Провести и закрыть" вы обнаруживаете, что время не изменилось. А при отладке в обработчике ОбработкаПроведения вы видите, что ЭтотОбъект.Дата содержит уже новое, правильное время, а вот Ссылка.Дата — все еще старое. Выясним причины этого явления.

Анализ последовательности событий: ПередЗаписью, ПриЗаписи и ОбработкаПроведения

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

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

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


// ПРАВИЛЬНО: код находится в модуле объекта
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    // Проверяем, что время не установлено (равно началу дня)
    Если ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) Тогда
        // Устанавливаем время 09:00:00
        // 32400 секунд = 9 часов * 3600 сек/час
        ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) + 32400;
    КонецЕсли;
КонецПроцедуры

// НЕПРАВИЛЬНО: код в этом обработчике не сработает для изменения объекта
Процедура ПриЗаписи(Отказ)
    // Этот код не сохранит изменения даты!
    ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) + 32400;
КонецПроцедуры

Причина №1: Влияние свойства формы "АвтоВремя"

Даже если ваш код находится в правильном месте (ПередЗаписью), есть еще один фактор, который может свести на нет все ваши усилия. Это свойство АвтоВремя у поля ввода на форме, которое связано с реквизитом "Дата" документа.

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

Решение: Если вам нужен полный программный контроль над временем документа, всегда устанавливайте для соответствующего реквизита на форме свойство АвтоВремя в значение "Не использовать". Это гарантирует, что платформа не изменит ваше значение в последний момент.

Причина №2: Особенности работы отладчика и изоляция транзакций

Это самый тонкий момент, который часто вводит в заблуждение даже опытных разработчиков. Вы все сделали правильно: код в ПередЗаписью, АвтоВремя отключено. Вы ставите точку останова в ОбработкаПроведения и видите в табло:

Почему так? Дело в механизме транзакций и их изоляции.

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

Поэтому, когда вы в отладчике смотрите на Ссылка.Дата, вы обращаетесь к базе данных извне транзакции и видите то значение, которое было в базе до начала записи. В то же время ЭтотОбъект — это переменная, которая существует внутри исполняемого кода и содержит актуальные, измененные данные. Как только транзакция успешно завершится, данные в базе обновятся, и Ссылка.Дата тоже будет возвращать новое значение.

Вывод: При отладке кода внутри транзакции записи/проведения всегда доверяйте данным самого объекта (ЭтотОбъект), а не данным, полученным по его ссылке (Ссылка). Расхождение в значениях — это нормальное поведение, обусловленное механизмами СУБД, а не ошибка в вашем коде.

Бонус: Как правильно проверять и устанавливать время

В исходной теме автор использовал небезопасную конструкцию для проверки времени:


Если Формат(ЭтотОбъект.Дата, "ДЛФ=T") <> "09:00:00" Тогда
    // ...
КонецЕсли;

Такой код является "хрупким" и считается плохой практикой. Его работа зависит от региональных настроек формата даты и времени на сервере, что может привести к непредсказуемым ошибкам. Например, в другой системе формат времени может быть "9:00:00" (без ведущего нуля), и условие перестанет работать.

Для работы с компонентами даты и времени следует использовать специальные функции. Например, если задача — установить время 9:00, только если оно еще не установлено (т.е. равно началу дня), правильный код будет выглядеть так:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    // Проверяем, что время документа равно началу дня (00:00:00)
    Если ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) Тогда
    
        // Устанавливаем время 09:00:00. 
        // 32400 — это количество секунд в 9 часах (9 * 60 * 60).
        ЭтотОбъект.Дата = ЭтотОбъект.Дата + 32400;
        
    КонецЕсли;

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

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

Пошаговый план решения проблемы

Итак, чтобы окончательно решить проблему с несохранением даты при проведении и закрытии, выполните следующие шаги:

  1. Убедитесь, что весь ваш код, изменяющий реквизиты документа, находится исключительно в обработчике ПередЗаписью() модуля объекта.
  2. Откройте форму документа. В дереве элементов формы найдите поле, связанное с датой, и в его свойствах установите для параметра АвтоВремя значение "Не использовать".
  3. При отладке процессов записи и проведения помните о транзакциях. Для получения актуальных данных внутри кода обращайтесь к ЭтотОбъект, а не к его ссылке.
  4. Пересмотрите свой код на предмет сравнения дат через форматированные строки. Замените такие проверки на использование встроенных функций работы с датой (НачалоДня, КонецДня, НачалоЧаса и т.д.) и арифметических операций с секундами.

Следование этим простым правилам обеспечит предсказуемое и корректное поведение вашей системы и избавит от долгих часов поиска "плавающих" ошибок.

← На главную