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

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

В процессе разработки в среде «1С:Предприятие 8.3» перед программистами часто встает простая, на первый взгляд, задача: при записи документа автоматически зафиксировать какое-либо служебное значение. Например, записать текущего пользователя, который вносит изменения, в реквизит документа Расш1_ПоследнийРедактор_ (имеющий тип СправочникСсылка.Пользователи).

Однако при реализации этого механизма начинающие разработчики (особенно те, кто переходит на платформу 8.3 с версии 7.7) регулярно совершают ряд критических ошибок. Они пытаются манипулировать объектами формы, вызывать принудительную запись в неподходящих местах или использовать неоптимальные методы поиска данных. В этой статье мы подробно разберем, почему возникают такие проблемы, выясним причину «краша» платформы при записи и рассмотрим два правильных сценария реализации этой задачи в соответствии со стандартами разработки фирмы «1С».

Анализ типичных ошибок разработчика

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


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

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

Ошибка 1. Использование ЭтотОбъект в модуле формы. В управляемом приложении «1С» контексты четко разделены. Если мы пишем код в модуле управляемой формы, то ключевое слово ЭтотОбъект возвращает саму форму — объект типа ФормаКлиентскогоПриложения, а вовсе не записываемый документ. Пытаться обращаться к реквизитам документа через ЭтотОбъект в форме бессмысленно: у формы нет такого реквизита базы данных, она является лишь визуальным представлением.

Ошибка 2. Бесконечная рекурсия и аварийное закрытие программы. Пытаясь заставить код работать, автор темы добавил в процедуру строку ЭтотОбъект.Записать(). В результате программа начала зависать и закрываться без вывода каких-либо окон с ошибками. Почему это произошло? Разберем по шагам логику работы платформы:

  1. Пользователь нажимает кнопку «Записать».
  2. Платформа инициирует транзакцию записи и вызывает обработчик события формы ПередЗаписьюНаСервере.
  3. Внутри этого обработчика выполняется команда Записать().
  4. Эта команда заставляет платформу снова начать процесс записи документа.
  5. Платформа повторно вызывает обработчик ПередЗаписьюНаСервере.
  6. Программа снова доходит до строки Записать()... и так до бесконечности.

Происходит так называемое зацикливание (бесконечная рекурсия). Стек вызовов оперативной памяти переполняется (Stack Overflow), и операционная система аварийно завершает процесс 1cv8.exe. Запомните важное правило: никогда не вызывайте методы записи объекта или формы внутри обработчиков событий, связанных с этой самой записью (таких как ПередЗаписью, ПриЗаписи, ПередЗаписьюНаСервере).

Ошибка 3. Неэффективный и ненадежный поиск пользователя. Код поиска пользователя выглядит так:


ИмяПользователь = ПользователиИнформационнойБазы.ТекущийПользователь().ПолноеИмя;
Расш1_ПоследнийРедактор_ = Справочники.Пользователи.НайтиПоНаименованию(ИмяПользователь);

Такой подход крайне не рекомендуется стандартами разработки «1С» по двум причинам:


Решение №1. Использование Модуля Объекта (Рекомендуемый стандарт)

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

Почему этот способ считается лучшим? Дело в том, что документ может записываться не только интерактивно пользователем через форму, но и программно. Например, при проведении групповой обработки документов, обмене данными, загрузке через API или при работе фонового задания. Если логика записи редактора написана в форме, она сработает только при физическом нажатии кнопки пользователем. Если же код расположен в модуле объекта, он гарантированно отработает при любом способе записи.

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Обязательная проверка на обмен данными!
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем текущего пользователя оптимальным способом
    Расш1_ПоследнийРедактор_ = ПараметрыСеанса.ТекущийПользователь;
    
КонецПроцедуры

Проанализируем ключевые моменты этого кода:

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

Решение №2. Использование обработчика в Модуле Формы

Если по каким-то причинам бизнес-логика требует, чтобы реквизит заполнялся исключительно при интерактивной работе пользователя в форме (например, для мгновенного отображения изменений на экране до момента физического сохранения данных в СУБД), мы можем использовать обработчик события формы ПередЗаписьюНаСервере.

Однако здесь важно правильно использовать параметры процедуры. Посмотрим на сигнатуру этого метода:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)

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

Разберем по шагам, как должен выглядеть правильный код в модуле формы:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    // Напрямую изменяем реквизит в передаваемом объекте базы данных
    ТекущийОбъект.Расш1_ПоследнийРедактор_ = ПараметрыСеанса.ТекущийПользователь;
    
КонецПроцедуры

После того как процедура успешно завершится, платформа «1С» сама запишет измененный ТекущийОбъект в базу данных и автоматически обновит отображение данных на форме клиента.

Важное замечание относительно преобразований типов: в Сообщении 11 на форуме предлагался альтернативный вариант с использованием тяжеловесных функций конвертации:


ОбъектЗначение = РеквизитФормыВЗначение("Объект");
ОбъектЗначение.Расш1_ПоследнийРедактор_ = ПараметрыСеанса.ТекущийПользователь;
ЗначениеВРеквизитФормы(ОбъектЗначение, "Объект");

Этот подход в процедуре ПередЗаписьюНаСервере является избыточным и неоптимальным. Вызовы РеквизитФормыВЗначение и ЗначениеВРеквизитФормы создают дополнительную вычислительную нагрузку на сервер, так как заставляют платформу производить полную сериализацию и десериализацию данных формы. Поскольку платформа уже предоставила готовый к модификации ТекущийОбъект в параметрах процедуры, использовать данные методы преобразования не имеет смысла.


Как правильно получать текущего пользователя (работа с БСП)

Если ваша конфигурация построена на базе БСП (Библиотеки стандартных подсистем), то для получения текущего авторизованного пользователя рекомендуется использовать программный интерфейс библиотеки вместо прямого обращения к параметрам сеанса.

Давайте посмотрим, как выглядит этот вызов:


// Универсальный вызов функций БСП для получения ссылки на пользователя
ТекущийПользовательСсылка = Пользователи.АвторизованныйПользователь();

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

Сводная таблица подходов

Для закрепления материала сравним два основных корректных подхода к автоматическому заполнению реквизитов:

Критерий сравнения Запись в Модуле Объекта (ПередЗаписью) Запись в Модуле Формы (ПередЗаписьюНаСервере)
Где размещается код В модуле самого документа В модуле управляемой формы документа
Программная запись документа Работает всегда. Код выполнится при любом вызове метода Записать(). Не работает. При программном изменении без открытия формы код проигнорируется.
Какую переменную изменять Реквизит пишется напрямую: ИмяРеквизита = ... Изменяется параметр процедуры: ТекущийОбъект.ИмяРеквизита = ...
Проверка обмена данными Обязательна проверка Если ОбменДанными.Загрузка Тогда Обычно не требуется, так как обмены идут напрямую через объект, минуя формы.

Используйте эти рекомендации в своей повседневной практике разработки, избегайте вызовов методов записи внутри транзакций сохранения и всегда отдавайте предпочтение модулю объекта для хранения бизнес-логики сущностей базы данных.

← На главную