Как исправить ошибку «Попытка передачи с клиента на сервер мутабельного значения» при записи документа в 1С

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

При разработке в среде 1С:Предприятие, особенно в конфигурациях на обычных формах (таких как УПП или КА 1.1), программисты часто сталкиваются с ошибкой: «Попытка передачи с клиента на сервер мутабельного значения 1-го параметра метода...». Эта проблема возникает при попытке выполнить серверный код, используя объект документа, который в данный момент открыт или записывается на стороне клиента. Рассмотрим подробнее, почему это происходит и как правильно организовать архитектуру кода, чтобы избежать подобных конфликтов.

Разберем причину возникновения ошибки

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

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

Проанализируем ситуацию на примере подписки на события

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

Если мы создадим подписку на событие ПриЗаписи и направим ее в серверный модуль, в качестве первого параметра (Источник) платформа попытается передать сам ДокументОбъект.РеализацияТоваровУслуг. Так как вызов происходит из формы клиента, мы получаем ту самую ошибку мутабельного значения.

Решение проблемы: правильная настройка общего модуля и передачи параметров

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

Шаг 1. Настройка общего модуля

Создадим или выберем существующий общий модуль. Для корректной работы в обычном приложении (УПП) установим следующие флаги в его свойствах:

  1. Клиент — чтобы обработчик подписки мог «подхватить» событие, инициированное формой.
  2. Сервер — для выполнения кода на стороне сервера.
  3. Вызов сервера — для возможности перехода с клиентского контекста на серверный.
  4. Привилегированный — это позволит создавать документы в другой организации без проверки прав доступа пользователя.

Шаг 2. Разработка кода обработчика

Основная хитрость заключается в том, что мы не должны передавать Источник (объект) напрямую в серверные методы. Мы должны передать Источник.Ссылка, так как ссылка является иммутабельной (неизменяемой) и легко передается между клиентом и сервером.

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


// Процедура-обработчик подписки на событие ПриЗаписи
Процедура ПодпискаПриЗаписиДокумента(Источник, Отказ) Экспорт
    
    // Проверяем, что документ уже имеет ссылку (записан в БД)
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    // Вызываем серверную функцию, передавая ССЫЛКУ, а не сам ОБЪЕКТ
    СоздатьСвязанныйДокументНаСервере(Источник.Ссылка);
    
КонецПроцедуры

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

Почему это работает?

Выясним причину успеха такой архитектуры. Когда срабатывает подписка, вызывается процедура ПодпискаПриЗаписиДокумента. Если модуль имеет флаг «Клиент», процедура начинает выполняться в контексте клиента. В этот момент параметр Источник доступен как объект. Далее мы берем его свойство Ссылка. Ссылка — это простой идентификатор (GUID), который платформа умеет передавать на сервер. В процедуре СоздатьСвязанныйДокументНаСервере мы уже работаем в контексте сервера, где можем свободно создавать новые объекты и записывать их в базу данных, игнорируя ограничения прав доступа благодаря привилегированному режиму модуля.

Дополнительные рекомендации и нюансы

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

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

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

← На главную