В процессе разработки в 1С часто возникает задача программно изменять реквизиты документа перед его записью. Классический пример — установка определенного времени для документа, если пользователь его не указал. Однако иногда разработчики сталкиваются с загадочным поведением: код, который прекрасно работает при нажатии кнопки "Провести", отказывается сохранять изменения при использовании кнопки "Провести и закрыть". При этом отладчик может показывать противоречивые данные, что еще больше запутывает ситуацию. Давайте разберемся, почему так происходит и как это исправить.
Проанализируем типичную ситуацию: вы написали код для установки времени документа, например, на 09:00:00, в обработчике события. При проведении нового документа через "Провести и закрыть" вы обнаруживаете, что время не изменилось. А при отладке в обработчике ОбработкаПроведения вы видите, что ЭтотОбъект.Дата содержит уже новое, правильное время, а вот Ссылка.Дата — все еще старое. Выясним причины этого явления.
Чтобы понять корень проблемы, необходимо четко представлять себе порядок вызова серверных обработчиков событий объекта при записи и проведении документа. Для нашей задачи ключевыми являются три из них:
ПередЗаписью(). Этот обработчик вызывается до того, как платформа начнет формировать данные для записи в базу данных. Это идеальное и единственно правильное место для модификации реквизитов записываемого объекта. Все изменения, внесенные в ЭтотОбъект на данном этапе, гарантированно попадут в транзакцию записи.ПриЗаписи(). Этот обработчик вызывается в момент записи, когда данные для сохранения уже подготовлены. Пытаться изменить реквизиты в ЭтотОбъект на этом этапе уже поздно. Платформа проигнорирует эти изменения, и они не будут сохранены в текущей транзакции. Этот обработчик предназначен в основном для дополнительных проверок или записи связанных данных, но не для модификации самого объекта.ОбработкаПроведения(). Этот обработчик выполняется после успешной записи объекта в базу данных, но в рамках той же транзакции. Его задача — сформировать движения по регистрам. На этом этапе объект уже записан с теми данными, которые были у него после выполнения ПередЗаписью.Вывод: Любая логика, изменяющая реквизиты документа (дату, номер, сумму и т.д.), должна находиться исключительно в обработчике ПередЗаписью().
// ПРАВИЛЬНО: код находится в модуле объекта
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что время не установлено (равно началу дня)
Если ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) Тогда
// Устанавливаем время 09:00:00
// 32400 секунд = 9 часов * 3600 сек/час
ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) + 32400;
КонецЕсли;
КонецПроцедуры
// НЕПРАВИЛЬНО: код в этом обработчике не сработает для изменения объекта
Процедура ПриЗаписи(Отказ)
// Этот код не сохранит изменения даты!
ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) + 32400;
КонецПроцедуры
Даже если ваш код находится в правильном месте (ПередЗаписью), есть еще один фактор, который может свести на нет все ваши усилия. Это свойство АвтоВремя у поля ввода на форме, которое связано с реквизитом "Дата" документа.
Это свойство управляет тем, как форма будет работать со временем документа. Оно может иметь несколько значений, но нас интересуют два основных поведения:
ПередЗаписью. Именно поэтому при разных действиях ("Провести" и "Провести и закрыть") результат может отличаться.Решение: Если вам нужен полный программный контроль над временем документа, всегда устанавливайте для соответствующего реквизита на форме свойство АвтоВремя в значение "Не использовать". Это гарантирует, что платформа не изменит ваше значение в последний момент.
Это самый тонкий момент, который часто вводит в заблуждение даже опытных разработчиков. Вы все сделали правильно: код в ПередЗаписью, АвтоВремя отключено. Вы ставите точку останова в ОбработкаПроведения и видите в табло:
ЭтотОбъект.Дата = "20.10.2023 9:00:00" (новое, правильное значение)Ссылка.Дата = "20.10.2023 0:00:00" (старое значение)Почему так? Дело в механизме транзакций и их изоляции.
Когда вы нажимаете "Провести и закрыть", платформа запускает одну большую транзакцию, в которой происходят запись и проведение. Пока эта транзакция не будет полностью завершена (зафиксирована), все изменения видны только внутри самой этой транзакции. Ваш отладчик (Конфигуратор) работает в отдельном сеансе и является "внешним наблюдателем". По умолчанию уровень изоляции транзакций в 1С (Read Committed) не позволяет внешним сеансам читать "грязные", то есть еще не зафиксированные, данные.
Поэтому, когда вы в отладчике смотрите на Ссылка.Дата, вы обращаетесь к базе данных извне транзакции и видите то значение, которое было в базе до начала записи. В то же время ЭтотОбъект — это переменная, которая существует внутри исполняемого кода и содержит актуальные, измененные данные. Как только транзакция успешно завершится, данные в базе обновятся, и Ссылка.Дата тоже будет возвращать новое значение.
Вывод: При отладке кода внутри транзакции записи/проведения всегда доверяйте данным самого объекта (ЭтотОбъект), а не данным, полученным по его ссылке (Ссылка). Расхождение в значениях — это нормальное поведение, обусловленное механизмами СУБД, а не ошибка в вашем коде.
В исходной теме автор использовал небезопасную конструкцию для проверки времени:
Если Формат(ЭтотОбъект.Дата, "ДЛФ=T") <> "09:00:00" Тогда
// ...
КонецЕсли;
Такой код является "хрупким" и считается плохой практикой. Его работа зависит от региональных настроек формата даты и времени на сервере, что может привести к непредсказуемым ошибкам. Например, в другой системе формат времени может быть "9:00:00" (без ведущего нуля), и условие перестанет работать.
Для работы с компонентами даты и времени следует использовать специальные функции. Например, если задача — установить время 9:00, только если оно еще не установлено (т.е. равно началу дня), правильный код будет выглядеть так:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, что время документа равно началу дня (00:00:00)
Если ЭтотОбъект.Дата = НачалоДня(ЭтотОбъект.Дата) Тогда
// Устанавливаем время 09:00:00.
// 32400 — это количество секунд в 9 часах (9 * 60 * 60).
ЭтотОбъект.Дата = ЭтотОбъект.Дата + 32400;
КонецЕсли;
КонецПроцедуры
Этот код надежен, не зависит от строковых представлений и настроек форматирования, и будет работать одинаково в любой системе.
Итак, чтобы окончательно решить проблему с несохранением даты при проведении и закрытии, выполните следующие шаги:
ПередЗаписью() модуля объекта.АвтоВремя значение "Не использовать".ЭтотОбъект, а не к его ссылке.НачалоДня, КонецДня, НачалоЧаса и т.д.) и арифметических операций с секундами.Следование этим простым правилам обеспечит предсказуемое и корректное поведение вашей системы и избавит от долгих часов поиска "плавающих" ошибок.