Как записать движения документа в привилегированном режиме в обычных формах без ошибки передачи мутабельного значения?

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

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

Однако при попытке передать объект документа (параметр Источник) из обработчика подписки на событие или из модуля самого документа в привилегированный серверный модуль система выдает критическую ошибку: "Передача мутабельного значения". Выясним причину этой проблемы и подробно разберем три надежных способа ее решения.

Почему возникает ошибка передачи мутабельного значения?

Для понимания сути проблемы проанализируем физику процессов платформы «1С:Предприятие» в контексте обычного приложения:

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

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

---

Способ 1. Двухзвенная схема проведения с передачей ссылки (Рекомендуемый стандарт)

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

Шаг 1. Создание клиент-серверного модуля подписки

Создадим первый общий модуль (например, ПроведениеДокументовКлиентСервер) со следующими свойствами:

Поскольку модуль доступен на клиенте, платформа без ошибок вызовет обработчик подписки на событие ОбработкаПроведения. Внутри этого обработчика мы не передаем сам мутабельный объект Источник. Вместо этого мы передаем его немутабельную ссылку — Источник.Ссылка.

Шаг 2. Создание привилегированного модуля на сервере

Создадим второй общий модуль (например, ПроведениеДокументовПривилегированный) со следующими свойствами:

Шаг 3. Реализация кода

Проанализируем пример кода для обоих модулей. В первом модуле мы перенаправляем вызов на сервер, передавая только ссылку:


// В общем модуле ПроведениеДокументовКлиентСервер
Процедура ОбработкаПроведенияДокументаПодписка(Источник, Отказ, РежимПроведения) Экспорт
    
    // Передаем на сервер только ссылку, которая не является мутабельным значением
    ПроведениеДокументовПривилегированный.СформироватьДвиженияПривилегированно(Источник.Ссылка);
    
КонецПроцедуры

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


// В общем модуле ПроведениеДокументовПривилегированный
Процедура СформироватьДвиженияПривилегированно(СсылкаНаДокумент) Экспорт
    
    // Создаем набор записей нужного регистра
    НаборЗаписей = РегистрыНакопления.Взаиморасчеты.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
    
    // Получаем необходимые данные из документа запросом
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ТЧ.Номенклатура,
        |   ТЧ.Количество,
        |   ТЧ.Сумма
        |ИЗ
        |   Документ.РеализацияТоваровУслуг.Товары КАК ТЧ
        |ГДЕ
        |   ТЧ.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        ТаблицаДанных = РезультатЗапроса.Выгрузить();
        
        Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
            Движение = НаборЗаписей.Добавить();
            Движение.Период = СсылкаНаДокумент.Дата;
            Движение.Регистратор = СсылкаНаДокумент;
            Движение.Номенклатура = СтрокаДанных.Номенклатура;
            Движение.Сумма = СтрокаДанных.Сумма;
        КонецЦикла;
        
        // Записываем движения в привилегированном режиме
        НаборЗаписей.Записать();
    КонецЕсли;
    
КонецПроцедуры

---

Способ 2. Передача данных через структуру-посредник

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

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

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


// В обработчике проведения на клиенте (или в клиент-серверном модуле подписки)
ПараметрыПроведения = Новый Структура;
ПараметрыПроведения.Вставить("Ссылка", Источник.Ссылка);
ПараметрыПроведения.Вставить("Дата", Источник.Дата);
ПараметрыПроведения.Вставить("Контрагент", Источник.Контрагент);

// Выгружаем табличную часть в обычную Таблицу Значений
ТаблицаТоваров = Источник.Товары.Выгрузить();
ПараметрыПроведения.Вставить("Товары", ТаблицаТоваров);

// Вызываем привилегированный серверный метод
ПроведениеДокументовПривилегированный.ЗаписатьДвиженияПоСтруктуре(ПараметрыПроведения);

На сервере в привилегированном модуле мы принимаем структуру, обрабатываем ее данные и производим запись движений:


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

---

Способ 3. Принудительный перенос транзакции проведения на сервер

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

Для этого нам необходимо переопределить стандартную кнопку проведения на форме документа:

  1. В форме документа для кнопки "Провести" (или в событии формы ПередЗаписью) отключаем стандартную обработку проведения на клиенте.
  2. Передаем на сервер только ссылку на документ.
  3. На сервере получаем объект документа из базы данных с помощью метода ПолучитьОбъект().
  4. Вызываем метод Записать(РежимЗаписиДокумента.Проведение).

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


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

Серверный метод в общем модуле (без флага "Привилегированный", так как используется стандартный механизм документа):


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

Важное примечание к Способу 3: Данный метод требует дополнительного чтения объекта из базы данных на сервере и может вызывать избыточные блокировки при высокой конкурентной работе пользователей. Используйте его тогда, когда логика проведения документа слишком сложна для переноса в наборы записей (Способ 1).

← На главную