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

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

В практике разработки на платформе «1С:Предприятие 8» часто возникает задача автоматического заполнения текстовых полей документов уникальными идентификаторами (GUID). Типичный пример — формирование назначения платежа для выгрузки в банк, где в текстовую строку необходимо встроить УИД созданной квитанции для последующей автоматической идентификации платежа при обратном импорте выписки.

Однако начинающие разработчики часто сталкиваются с неприятной архитектурной ловушкой: при интерактивном нажатии кнопок «Записать» или «Провести» реквизиты документа на форме успешно обновляются, но при использовании команды «Провести и закрыть» данные в базе данных остаются старыми или незаполненными. В этой статье мы подробно разберем, почему так происходит, проанализируем внутренние механизмы платформы 1С и реализуем правильное, надежное решение.

Анализ проблемного кода

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


Перем ЭтоНовыйОбъект;

&После("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ЭтоНовый() Тогда
        ЭтоНовыйОбъект = ИСТИНА;
    Иначе
        ЭтоНовыйОбъект = ЛОЖЬ;
    КонецЕсли;
КонецПроцедуры

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

На первый взгляд логика кажется разумной: в событии ПередЗаписью мы определяем, является ли документ новым (так как после записи метод ЭтоНовый() всегда будет возвращать значение Ложь). Затем в событии ПриЗаписи, когда у документа уже гарантированно появилась ссылка в базе данных, мы получаем его уникальный идентификатор через метод Ссылка.УникальныйИдентификатор() и модифицируем реквизит шапки НазначениеПлатежа.

Но почему этот код работает некорректно при закрытии формы? Выясним истинную причину поведения платформы.

Разбор механизма: Почему "Записать" работает, а "Провести и закрыть" — нет?

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

Рассмотрим пошагово, что происходит, когда вы модифицируете реквизиты внутри обработчика ПриЗаписи:

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

Теперь проанализируем разницу в поведении интерфейса при различных действиях пользователя:

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

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

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

Правильное решение: использование метода УстановитьСсылкуНового()

Основное правило разработки в среде 1С:Предприятие гласит: любые изменения реквизитов записываемого объекта должны выполняться исключительно до транзакции записи, то есть в событии ПередЗаписью.

Но как получить уникальный идентификатор нового документа, если он еще не записан в базу данных и ссылки у него физически нет?

Для решения этой архитектурной задачи разработчики платформы предусмотрели специальный механизм — предварительное резервирование ссылки с помощью метода УстановитьСсылкуНового(). Мы можем самостоятельно сгенерировать новую уникальную ссылку для документа еще до отправки данных в СУБД, назначить её объекту и извлечь из неё необходимый GUID.

Разберем этот механизм по шагам:

  1. В обработчике ПередЗаписью мы проверяем, является ли объект новым с помощью метода ЭтоНовый().
  2. Если объект новый, мы получаем пустую зарезервированную ссылку нужного типа. Для этого используется метод менеджера документа: Документы.[ИмяДокумента].ПолучитьСсылку().
  3. Мы явно назначаем эту ссылку нашему объекту через метод УстановитьСсылкуНового(ЗарезервированнаяСсылка). Теперь платформа знает, под каким именно уникальным идентификатором этот документ будет сохранен в базе данных.
  4. Мы извлекаем УИД из зарезервированной ссылки с помощью метода УникальныйИдентификатор() и формируем строку назначения платежа.
  5. При физической записи документа в базу данных платформа использует зарезервированную ссылку. Данные гарантированно сохраняются в СУБД с первого раза.

Реализация решения в расширении

Давайте напишем чистый, отказоустойчивый код для нашего расширения. Обратите внимание, что мы откажемся от использования переменной модуля ЭтоНовыйОбъект, так как хранить состояние между серверными вызовами в переменных модуля — не лучшая практика программирования.

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


&После("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Проверяем, не отказался ли уже кто-то от записи ранее в стеке вызовов
    Если Отказ Тогда
        Возврат;
    КонецЕсли;
    
    // Работаем только с новыми документами
    Если ЭтоНовый() Тогда
        
        // Динамически получаем имя текущего документа для универсальности кода
        ИмяДокумента = ЭтотОбъект.Метаданные().Имя;
        
        // Шаг 1: Генерируем и резервируем новую ссылку в базе данных
        НоваяСсылка = Документы[ИмяДокумента].ПолучитьСсылку();
        УстановитьСсылкуНового(НоваяСсылка);
        
        // Шаг 2: Извлекаем строковый GUID из зарезервированной ссылки
        СтрокаGUID = СокрЛП(НоваяСсылка.УникальныйИдентификатор());
        
        // Шаг 3: Заполняем реквизиты объекта на основе полученного GUID
        Если ЗначениеЗаполнено(КодБКПлатежа) Тогда
            НазначениеПлатежа = "Отр код. " + КодБКПлатежа + "/" + СтрокаGUID + "/" + НазначениеПлатежа;
        Иначе
            НазначениеПлатежа = "Отр. код. 00000000000000000130/" + СтрокаGUID + "/" + НазначениеПлатежа;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Преимущества правильного подхода

Использование метода УстановитьСсылкуНового() решает сразу несколько ключевых проблем оптимизации и надежности кода:

Рассмотренный шаблон проектирования является стандартом разработки фирмы «1С» и рекомендуется к применению во всех конфигурациях на платформе «1С:Предприятие 8».

← На главную