Почему очищается реквизит документа после его записи на сервере в 1С?

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

В процессе разработки на платформе 1С:Предприятие 8.3 (Управляемые формы) начинающие, а порой и опытные программисты сталкиваются с ситуацией, когда данные, программно присвоенные реквизиту документа, бесследно исчезают после закрытия и повторного открытия формы. На первый взгляд код выглядит абсолютно рабочим: в процессе отладки мы четко видим, что реквизиту присваивается корректное значение. Однако в базе данных это значение не сохраняется.

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

Выясняем причину: жизненный цикл записи и транзакции

Давайте подробно проанализируем ситуацию, описанную автором темы. В форме документа в обработчике события ПриЗаписиНаСервере выполняется следующий код:


&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    Штрихкод = ОбщийМодульСервер.ПолучитьШтрихкод(Объект.Номенклатура);
    Если Штрихкод.Количество() > 1 Тогда
        ТекущийОбъект.Штрихкод = "Учет поштучно";
    Иначе
        ТекущийОбъект.Штрихкод = Штрихкод[0];
    КонецЕсли;
КонецПроцедуры

При пошаговой отладке переменная ТекущийОбъект.Штрихкод действительно принимает нужное значение. Но почему же при перезапуске формы реквизит оказывается пустым?

Все дело в строгом порядке вызова событий платформы 1С при записи объекта из формы управляемого приложения. Давайте пошагово разберем эту последовательность:

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

Обратите внимание на пункт 6 и 8. Событие ПриЗаписиНаСервере вызывается тогда, когда объект уже записан в базу данных. Параметр ТекущийОбъект, который передается в эту процедуру, является лишь временной копией объекта в оперативной памяти сервера приложений. Изменяя свойства ТекущийОбъект на этом этапе, мы модифицируем только эту временную переменную. Платформа повторно записывать объект в СУБД из этого события не будет. Соответственно, все внесенные изменения просто уничтожаются при завершении серверного вызова.

Для чего тогда нужна процедура ПриЗаписиНаСервере? Она предназначена для записи сопряженных данных, не входящих в структуру самого документа. Например, здесь можно программно записать данные в независимый регистр сведений, создать подчиненный документ, отправить задачу в бизнес-процесс или зарегистрировать изменения в планах обмена.

Ловушка проведения: изменение реквизитов внутри транзакции проведения

Автор темы также упоминает, что штрихкод генерируется в момент проведения документа (в обработчике ОбработкаПроведения модуля объекта). Это еще одна классическая архитектурная ловушка.

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

Давайте разберем три правильных и безопасных способа решения этой задачи.

Решение А. Перенос генерации и записи штрихкода в обработчик ПередЗаписью

Если бизнес-логика требует, чтобы штрихкод обязательно хранился непосредственно в реквизите документа, правильным решением будет перенос алгоритма в модуль самого объекта (а не формы) в обработчик события ПередЗаписью.

Этот обработчик вызывается до физической записи в базу данных, поэтому любые изменения свойств объекта (в данном случае ЭтотОбъект) будут успешно сохранены в СУБД. Кроме того, этот обработчик гарантированно сработает как при интерактивной работе пользователя в форме, так и при программном создании или проведении документа (например, при загрузке данных через API или обработкой группового проведения).

Рассмотрим пример реализации в модуле объекта:


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

При таком подходе к моменту начала транзакции проведения реквизит Штрихкод уже будет заполнен актуальным значением и успешно сохранен в базе данных.

Решение Б. Отказ от хранения штрихкода в документе (динамический вывод)

С точки зрения теории проектирования баз данных и стандартов разработки 1С, дублирование штрихкода номенклатуры в каждом документе часто является избыточным (нарушение принципов нормализации данных). Если штрихкод привязан к номенклатуре и хранится, например, в регистре сведений ШтрихкодыНоменклатуры, то хранить его копию в документе не имеет смысла.

Рассмотрим альтернативный вариант: мы отказываемся от реквизита объекта и выводим штрихкод на форму динамически. При этом мы всегда будем видеть актуальный штрихкод, даже если он изменится в карточке номенклатуры.

Разберем реализацию этого метода по шагам:

  1. Удаляем реквизит «Штрихкод» из метаданных документа.
  2. В форме документа добавляем новый Реквизит формы (не путать с реквизитом объекта) с именем ШтрихкодФормы и типом данных Строка.
  3. Выносим этот реквизит на форму в виде поля ввода или надписи, доступной только для чтения.
  4. Заполняем этот реквизит при открытии документа и при изменении номенклатуры.

Посмотрим на пример кода в модуле формы документа:


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

&НаСервере
Процедура ЗаполнитьШтрихкодНаФорме()
    
    // Получаем актуальный штрихкод из регистра сведений через общий модуль
    ШтрихкодНоменклатуры = ОбщийМодульСервер.ПолучитьШтрихкод(Объект.Номенклатура);
    
    Если ШтрихкодНоменклатуры.Количество() > 1 Тогда
        ШтрихкодФормы = "Учет поштучно";
    ИначеЕсли ШтрихкодНоменклатуры.Количество() = 1 Тогда
        ШтрихкодФормы = ШтрихкодНоменклатуры[0];
    Иначе
        ШтрихкодФормы = "Штрихкод не назначен";
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура НоменклатураПриИзменении(Элемент)
    
    // Перезаполняем штрихкод при интерактивном изменении номенклатуры пользователем
    ЗаполнитьШтрихкодНаСервереФормы();
    
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьШтрихкодНаСервереФормы()
    ЗаполнитьШтрихкодНаФорме();
КонецПроцедуры

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

Решение В. Регистрация штрихкода в регистре сведений при проведении

Если бизнес-логика все же требует жесткой привязки момента создания штрихкода к факту проведения конкретного документа, мы можем использовать комбинированный метод. При проведении документа мы не меняем его реквизиты, а делаем запись в независимый регистр сведений, связывая документ-регистратор, номенклатуру и новый штрихкод.

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


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Проверяем, существует ли уже штрихкод для данной номенклатуры
    ТаблицаШтрихкодов = ОбщийМодульСервер.ПолучитьШтрихкод(Номенклатура);
    
    Если ТаблицаШтрихкодов.Количество() = 0 Тогда
        
        // Генерируем новый уникальный штрихкод
        НовыйШтрихкод = ОбщийМодульСервер.СгенерироватьНовыйШтрихкод();
        
        // Записываем данные в регистр сведений "ШтрихкодыНоменклатуры"
        МенеджерЗаписи = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Номенклатура = Номенклатура;
        МенеджерЗаписи.Штрихкод     = НовыйШтрихкод;
        МенеджерЗаписи.Документ     = Ссылка; // Привязка к документу-основанию
        
        Попытка
            МенеджерЗаписи.Записать(Истина);
        Исключение
            Отказ = Истина;
            ЗаписьЖурналаРегистрации("РегистрацияШтрихкода", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
            Возврат;
        КонецПопытки;
        
    КонецЕсли;
    
КонецПроцедуры

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

Подведем итоги

Мы проанализировали причины сброса данных реквизита документа и определили, что модификация реквизитов объекта в обработчиках ПриЗаписиНаСервере или ОбработкаПроведения является неэффективной и архитектурно неверной.

Выбор оптимального метода решения зависит от ваших задач:

← На главную