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