Запись документа из клиентского кода 1С 8.3: метод формы, вызов сервера, блокировки

· 9 мин

Содержание

На клиенте документ записывают методом формы Записать(): Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение)) записывает и проводит открытый документ и возвращает Истина или Ложь. Если формы нет, создают или меняют документ на сервере, а с клиента вызывают серверную функцию &НаСервереБезКонтекста и передают ей только ссылку и нужные значения.

Типичная ситуация: программист добавляет на форму кнопку «Провести и создать возврат», пишет в обработчике команды Объект.Записать() и получает Метод объекта не обнаружен (Записать). Причина не в опечатке, а в архитектуре управляемого приложения. Ниже разобрано, как записывать документ на клиенте правильно, как не делать лишних вызовов сервера и как ловить ошибки записи.

Почему на клиенте нельзя вызвать ДокументОбъект.Записать()

В управляемом приложении код выполняется в двух контекстах. Директива &НаКлиенте — это компьютер пользователя, тонкий клиент или браузер. &НаСервере — рабочий процесс сервера 1С (или файловая база в том же процессе).

Объекты базы данных живут только на сервере:

  • 🚫 ДокументОбъект, СправочникОбъект, НаборЗаписей на клиенте недоступны;
  • 🚫 запросы, менеджеры документов (Документы.ИмяДокумента) и транзакции — тоже только сервер;
  • ✅ на клиенте доступен реквизит формы Объект — это ДанныеФормыСтруктура, копия данных документа без методов объекта;
  • ✅ ссылки (ДокументСсылка) передавать на клиент можно, но читать через точку их реквизиты на клиенте нельзя.

Отсюда два легальных пути записи из клиентского кода:

СпособКогда подходитЧто учесть
Метод формы Записать()Документ открыт в формеСрабатывают все события формы и объекта
Серверная функцияДокумента на экране нет, нужна массовая обработкаСобытия формы не срабатывают, список нужно оповестить
РеквизитФормыВЗначение в &НаСервереНужна логика объекта прямо из формыОбязательно вернуть объект в форму, иначе конфликт версий

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

Запись документа из формы: метод Записать() и ПараметрыЗаписи

Метод Записать() есть у самой формы (ФормаКлиентскогоПриложения). Он берёт данные из реквизита Объект, отправляет их на сервер, записывает документ и возвращает результат в форму. Вызов без параметров равносилен стандартной кнопке записи: документ записывается без проведения.

Записать и провести документ из своей команды:

&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)

	ПараметрыЗаписи = Новый Структура;
	ПараметрыЗаписи.Вставить("РежимЗаписи", РежимЗаписиДокумента.Проведение);
	ПараметрыЗаписи.Вставить("РежимПроведения", РежимПроведенияДокумента.Неоперативный);

	Если Записать(ПараметрыЗаписи) Тогда
		Закрыть();
	КонецЕсли;

КонецПроцедуры

Значения РежимЗаписиДокумента:

  1. Запись — записать без изменения проведения.
  2. Проведение — записать и провести.
  3. ОтменаПроведения — записать и снять проведение.

Если в любом обработчике установлен Отказ = Истина или проведение не прошло, метод вернёт Ложь, а форма останется открытой с сообщениями пользователю. Исключение при этом на клиент не выбрасывается — проверяйте именно возвращаемое значение.

Порядок событий при записи из формы

Знать порядок нужно, чтобы класть код в правильное место и не гонять данные на сервер дважды.

№СобытиеГде выполняется
1ПередЗаписью(Отказ, ПараметрыЗаписи)Клиент, модуль формы
2ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)Сервер, модуль формы
3ПередЗаписью, ОбработкаПроведения, ПриЗаписиСервер, модуль объекта (внутри транзакции)
4ПриЗаписиНаСервере, ПослеЗаписиНаСервереСервер, модуль формы
5ПослеЗаписи(ПараметрыЗаписи)Клиент, модуль формы

Проверка заполнения (ОбработкаПроверкиЗаполнения и флаги «Проверка заполнения» у реквизитов) у документов по умолчанию выполняется при проведении, а не при простой записи.

Быстрые проверки, которым не нужна база (пустое поле, отрицательное количество), делайте в клиентском ПередЗаписью. Так пользователь получит ответ без обращения к серверу.

Как передать данные с клиента в модуль объекта

ПараметрыЗаписи — обычная структура. В неё можно добавить свои ключи на клиенте, и они дойдут до серверных событий формы. А вот в модуль объекта сама структура не попадает. Мост — ДополнительныеСвойства объекта.

// Модуль формы, клиент
&НаКлиенте
Процедура ЗаписатьСПересчетом(Команда)
	ПараметрыЗаписи = Новый Структура("ПересчитатьЦены", Истина);
	Записать(ПараметрыЗаписи);
КонецПроцедуры

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

В модуле объекта в ПередЗаписью проверьте ДополнительныеСвойства.Свойство("ПересчитатьЦены") и выполните пересчёт.

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

Вопрос пользователю перед записью

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

&НаКлиенте
Перем ЗаписьПодтверждена;

&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)

	Если ЗаписьПодтверждена = Истина Тогда
		ЗаписьПодтверждена = Ложь;
		Возврат;
	КонецЕсли;

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

КонецПроцедуры

&НаКлиенте
Процедура ПослеВопросаОЗаписи(Ответ, ПараметрыЗаписи) Экспорт
	Если Ответ = КодВозвратаДиалога.Да Тогда
		ЗаписьПодтверждена = Истина;
		Записать(ПараметрыЗаписи);
	КонецЕсли;
КонецПроцедуры

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

Если пользователь нажал «Провести и закрыть», после такого повтора форма сама не закроется: исходная команда уже отменена. Закрытие допишите в ПослеВопросаОЗаписи, если оно нужно.

Запись документа без формы через вызов сервера

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

&НаКлиенте
Процедура СоздатьВозврат(Команда)

	Если Не ЗначениеЗаполнено(Объект.Ссылка) Тогда
		ПоказатьПредупреждение(, "Сначала запишите документ-основание.");
		Возврат;
	КонецЕсли;

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

	ОповеститьОбИзменении(НовыйДокумент);
	ПоказатьЗначение(, НовыйДокумент);

КонецПроцедуры

&НаСервереБезКонтекста
Функция СоздатьИПровестиНаСервере(Основание)

	ДокОбъект = Документы.ВозвратТоваровОтКлиента.СоздатьДокумент();
	ДокОбъект.Заполнить(Основание);
	ДокОбъект.Дата = ТекущаяДатаСеанса();
	ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);

	Возврат ДокОбъект.Ссылка;

КонецФункции

Имя документа подставьте своё. Заполнить() вызывает обработчик ОбработкаЗаполнения модуля объекта — так документ заполнится по основанию теми же правилами, что и при вводе на основании вручную.

Как сократить число вызовов сервера

Каждый серверный вызов из тонкого или веб-клиента — это сетевой обмен. По локальной сети он незаметен, через интернет складывается в секунды.

  • ⚡ &НаСервереБезКонтекста вместо &НаСервере: не передаёт на сервер и обратно данные формы. Для формы с табличной частью на сотни строк разница ощутима.
  • 📦 Один вызов на пакет: собирайте массив ссылок или структур на клиенте и отдавайте серверу целиком, а не по одной строке в цикле.
  • 🧩 Общий модуль с флагом «Вызов сервера» — когда одна и та же запись нужна из разных форм.
  • 🔄 ОповеститьОбИзменении(Ссылка) после серверной записи обновляет открытые динамические списки без лишних запросов.
  • 🧪 Проверить, сколько вызовов делает команда, помогает замер производительности: в конфигураторе Отладка → Замер производительности.

Не вызывайте сервер внутри клиентского цикла по строкам табличной части — это самая частая причина медленной записи из форм.

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

Блокировки и транзакции при записи с клиента

На тонком клиенте НачатьТранзакцию() недоступна. Запись документа платформа сама выполняет в транзакции на сервере: если ОбработкаПроведения упала, откатятся и запись, и движения.

Когда нужно записать несколько документов атомарно — «всё или ничего», — открывайте транзакцию в серверной функции:

&НаСервереБезКонтекста
Процедура ПровестиПакет(МассивСсылок)

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

КонецПроцедуры

ВызватьИсключение без параметров пробрасывает исходную ошибку на клиент, где её поймает Попытка из предыдущего раздела.

В записи участвуют блокировки трёх видов:

  1. Пессимистическая объектная. Форма ставит её автоматически, когда пользователь начинает менять документ. Второй пользователь при попытке правки получит Объект уже заблокирован с именем пользователя и компьютера.
  2. Оптимистическая. При записи платформа сверяет версию данных. Если документ успели изменить в другом сеансе, запись отклоняется с сообщением, что данные изменены или удалены другим пользователем.
  3. Управляемая блокировка данных. Ставится в транзакции объектом БлокировкаДанных — обычно в ОбработкаПроведения перед чтением остатков:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
Блокировка.Заблокировать();

Имя регистра и измерения замените своими. Блокируйте ровно те измерения, которые читаете: блокировка всего регистра заставит ждать всех, кто проводит документы по другим складам.

Держите транзакцию короткой: не открывайте её на клиенте через цепочку вызовов и не задавайте вопросов пользователю, пока она открыта. В серверной транзакции диалог невозможен в принципе.

Обработка ошибок при записи документа на клиенте

Метод формы Записать() и серверная функция ведут себя по-разному. Это частый источник путаницы.

Что произошлоЗаписать() формыСерверная функция
Отказ = Истина в обработчикеВозвращает ЛожьИсключение «не удалось записать/провести»
Ошибка в коде проведенияСообщение, возвращает ЛожьИсключение, транзакция откатывается
Сообщения СообщениеПользователюПоказываются в формеДоставляются на клиент после вызова

Сообщение лучше привязывать к полю — тогда пользователь увидит, что исправить:

Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не указан склад";
Сообщение.Поле = "Склад";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
Отказ = Истина;

Этот фрагмент для модуля объекта. В модуле формы вместо УстановитьДанные укажите Сообщение.Поле = "Объект.Склад".

Диагностика: выберите симптом

Запись в обычном и управляемом приложении, разница по версиям платформы

Код из старых примеров часто не работает именно из-за другой модели приложения.

  • 🗂 Обычное приложение, толстый клиент (типовые на 8.1–8.2 обычных форм). Объекты базы доступны на клиенте, ДокОбъект.Записать() прямо в модуле формы работает, транзакции тоже. Директив &НаКлиенте в обычных формах нет.
  • 🖥 Управляемое приложение, 8.2 и 8.3. Разделение клиент/сервер обязательно. Запись — только через метод формы или серверный вызов.
  • 🪟 Режим модальности «Не использовать» (платформа 8.3.3 и выше в новых конфигурациях). Модальные Вопрос() и Предупреждение() заменяют на ПоказатьВопрос(), ПоказатьПредупреждение() с ОписаниеОповещения.
  • ⏳ Асинх и Ждать (платформа с номером не ниже 8.3.18). Можно писать Ответ = Ждать ВопросАсинх(...) в асинхронной процедуре-команде. Но обработчик ПередЗаписью асинхронным делать нельзя: Отказ должен быть установлен сразу, поэтому схема с повторным Записать() остаётся рабочей.

Если конфигурация работает в режиме совместимости, новые возможности языка ограничены этим режимом, а не номером установленной платформы. Свойство смотрите в корне конфигурации: «Режим совместимости».

Частые вопросы

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