Запись документа из клиентского кода 1С 8.3: метод формы, вызов сервера, блокировки
Содержание
На клиенте документ записывают методом формы Записать(): Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение)) записывает и проводит открытый документ и возвращает Истина или Ложь. Если формы нет, создают или меняют документ на сервере, а с клиента вызывают серверную функцию &НаСервереБезКонтекста и передают ей только ссылку и нужные значения.
Типичная ситуация: программист добавляет на форму кнопку «Провести и создать возврат», пишет в обработчике команды Объект.Записать() и получает Метод объекта не обнаружен (Записать). Причина не в опечатке, а в архитектуре управляемого приложения. Ниже разобрано, как записывать документ на клиенте правильно, как не делать лишних вызовов сервера и как ловить ошибки записи.
Почему на клиенте нельзя вызвать ДокументОбъект.Записать()
В управляемом приложении код выполняется в двух контекстах. Директива &НаКлиенте — это компьютер пользователя, тонкий клиент или браузер. &НаСервере — рабочий процесс сервера 1С (или файловая база в том же процессе).
Объекты базы данных живут только на сервере:
- 🚫
ДокументОбъект,СправочникОбъект,НаборЗаписейна клиенте недоступны; - 🚫 запросы, менеджеры документов (
Документы.ИмяДокумента) и транзакции — тоже только сервер; - ✅ на клиенте доступен реквизит формы
Объект— этоДанныеФормыСтруктура, копия данных документа без методов объекта; - ✅ ссылки (
ДокументСсылка) передавать на клиент можно, но читать через точку их реквизиты на клиенте нельзя.
Отсюда два легальных пути записи из клиентского кода:
| Способ | Когда подходит | Что учесть |
|---|---|---|
Метод формы Записать() | Документ открыт в форме | Срабатывают все события формы и объекта |
| Серверная функция | Документа на экране нет, нужна массовая обработка | События формы не срабатывают, список нужно оповестить |
РеквизитФормыВЗначение в &НаСервере | Нужна логика объекта прямо из формы | Обязательно вернуть объект в форму, иначе конфликт версий |
Правило ограничения директив действует в управляемых формах при любом клиенте, включая толстый. Синтаксический контроль конфигуратора укажет на недоступный на клиенте тип ещё до запуска.
Запись документа из формы: метод Записать() и ПараметрыЗаписи
Метод Записать() есть у самой формы (ФормаКлиентскогоПриложения). Он берёт данные из реквизита Объект, отправляет их на сервер, записывает документ и возвращает результат в форму. Вызов без параметров равносилен стандартной кнопке записи: документ записывается без проведения.
Записать и провести документ из своей команды:
&НаКлиенте
Процедура ПровестиИЗакрыть(Команда)
ПараметрыЗаписи = Новый Структура;
ПараметрыЗаписи.Вставить("РежимЗаписи", РежимЗаписиДокумента.Проведение);
ПараметрыЗаписи.Вставить("РежимПроведения", РежимПроведенияДокумента.Неоперативный);
Если Записать(ПараметрыЗаписи) Тогда
Закрыть();
КонецЕсли;
КонецПроцедуры
Значения РежимЗаписиДокумента:
Запись— записать без изменения проведения.Проведение— записать и провести.ОтменаПроведения— записать и снять проведение.
Если в любом обработчике установлен Отказ = Истина или проведение не прошло, метод вернёт Ложь, а форма останется открытой с сообщениями пользователю. Исключение при этом на клиент не выбрасывается — проверяйте именно возвращаемое значение.
Порядок событий при записи из формы
Знать порядок нужно, чтобы класть код в правильное место и не гонять данные на сервер дважды.
| № | Событие | Где выполняется |
|---|---|---|
| 1 | ПередЗаписью(Отказ, ПараметрыЗаписи) | Клиент, модуль формы |
| 2 | ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи) | Сервер, модуль формы |
| 3 | ПередЗаписью, ОбработкаПроведения, ПриЗаписи | Сервер, модуль объекта (внутри транзакции) |
| 4 | ПриЗаписиНаСервере, ПослеЗаписиНаСервере | Сервер, модуль формы |
| 5 | ПослеЗаписи(ПараметрыЗаписи) | Клиент, модуль формы |
Проверка заполнения (ОбработкаПроверкиЗаполнения и флаги «Проверка заполнения» у реквизитов) у документов по умолчанию выполняется при проведении, а не при простой записи.
Быстрые проверки, которым не нужна база (пустое поле, отрицательное количество), делайте в клиентском ПередЗаписью. Так пользователь получит ответ без обращения к серверу.
Как передать данные с клиента в модуль объекта
ПараметрыЗаписи — обычная структура. В неё можно добавить свои ключи на клиенте, и они дойдут до серверных событий формы. А вот в модуль объекта сама структура не попадает. Мост — ДополнительныеСвойства объекта.
// Модуль формы, клиент
&НаКлиенте
Процедура ЗаписатьСПересчетом(Команда)
ПараметрыЗаписи = Новый Структура("ПересчитатьЦены", Истина);
Записать(ПараметрыЗаписи);
КонецПроцедуры
// Модуль формы, сервер
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Если ПараметрыЗаписи.Свойство("ПересчитатьЦены") Тогда
ТекущийОбъект.ДополнительныеСвойства.Вставить("ПересчитатьЦены", Истина);
КонецЕсли;
КонецПроцедуры
В модуле объекта в ПередЗаписью проверьте ДополнительныеСвойства.Свойство("ПересчитатьЦены") и выполните пересчёт.
Это главный приём для случаев, когда запись из формы должна вести себя иначе, чем запись из обработки или обмена. Флаг живёт только в текущем экземпляре объекта и в базу не сохраняется.
Вопрос пользователю перед записью
В конфигурациях с режимом модальности «Не использовать» модальный Вопрос() в ПередЗаписью запрещён. Рабочая схема: отменить запись, задать вопрос асинхронно и повторить Записать() из обработчика ответа.
&НаКлиенте
Перем ЗаписьПодтверждена;
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
Если ЗаписьПодтверждена = Истина Тогда
ЗаписьПодтверждена = Ложь;
Возврат;
КонецЕсли;
Если Объект.СуммаДокумента > 1000000 Тогда
Отказ = Истина;
Оповещение = Новый ОписаниеОповещения("ПослеВопросаОЗаписи", ЭтотОбъект, ПараметрыЗаписи);
ПоказатьВопрос(Оповещение, "Сумма больше 1 000 000 руб. Записать документ?", РежимДиалогаВопрос.ДаНет);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеВопросаОЗаписи(Ответ, ПараметрыЗаписи) Экспорт
Если Ответ = КодВозвратаДиалога.Да Тогда
ЗаписьПодтверждена = Истина;
Записать(ПараметрыЗаписи);
КонецЕсли;
КонецПроцедуры
СуммаДокумента замените на реквизит своего документа. Повторный вызов Записать() снова запустит ПередЗаписью, поэтому нужен флаг ЗаписьПодтверждена — без него вопрос будет появляться по кругу.
Если пользователь нажал «Провести и закрыть», после такого повтора форма сама не закроется: исходная команда уже отменена. Закрытие допишите в ПослеВопросаОЗаписи, если оно нужно.
Запись документа без формы через вызов сервера
Когда документ нужно создать или изменить по кнопке, а его форма не открыта, клиент только собирает параметры. Вся работа с объектом — в одной серверной функции.
&НаКлиенте
Процедура СоздатьВозврат(Команда)
Если Не ЗначениеЗаполнено(Объект.Ссылка) Тогда
ПоказатьПредупреждение(, "Сначала запишите документ-основание.");
Возврат;
КонецЕсли;
Попытка
НовыйДокумент = СоздатьИПровестиНаСервере(Объект.Ссылка);
Исключение
ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат;
КонецПопытки;
ОповеститьОбИзменении(НовыйДокумент);
ПоказатьЗначение(, НовыйДокумент);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СоздатьИПровестиНаСервере(Основание)
ДокОбъект = Документы.ВозвратТоваровОтКлиента.СоздатьДокумент();
ДокОбъект.Заполнить(Основание);
ДокОбъект.Дата = ТекущаяДатаСеанса();
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Возврат ДокОбъект.Ссылка;
КонецФункции
Имя документа подставьте своё. Заполнить() вызывает обработчик ОбработкаЗаполнения модуля объекта — так документ заполнится по основанию теми же правилами, что и при вводе на основании вручную.
Как сократить число вызовов сервера
Каждый серверный вызов из тонкого или веб-клиента — это сетевой обмен. По локальной сети он незаметен, через интернет складывается в секунды.
- ⚡
&НаСервереБезКонтекставместо&НаСервере: не передаёт на сервер и обратно данные формы. Для формы с табличной частью на сотни строк разница ощутима. - 📦 Один вызов на пакет: собирайте массив ссылок или структур на клиенте и отдавайте серверу целиком, а не по одной строке в цикле.
- 🧩 Общий модуль с флагом «Вызов сервера» — когда одна и та же запись нужна из разных форм.
- 🔄
ОповеститьОбИзменении(Ссылка)после серверной записи обновляет открытые динамические списки без лишних запросов. - 🧪 Проверить, сколько вызовов делает команда, помогает замер производительности: в конфигураторе
Отладка → Замер производительности.
Не вызывайте сервер внутри клиентского цикла по строкам табличной части — это самая частая причина медленной записи из форм.
Если запись длится минуты (сотни документов), выносите её в фоновое задание. В конфигурациях на БСП для этого есть общий модуль ДлительныеОперации.
Блокировки и транзакции при записи с клиента
На тонком клиенте НачатьТранзакцию() недоступна. Запись документа платформа сама выполняет в транзакции на сервере: если ОбработкаПроведения упала, откатятся и запись, и движения.
Когда нужно записать несколько документов атомарно — «всё или ничего», — открывайте транзакцию в серверной функции:
&НаСервереБезКонтекста
Процедура ПровестиПакет(МассивСсылок)
НачатьТранзакцию();
Попытка
Для Каждого Ссылка Из МассивСсылок Цикл
ДокОбъект = Ссылка.ПолучитьОбъект();
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
ВызватьИсключение без параметров пробрасывает исходную ошибку на клиент, где её поймает Попытка из предыдущего раздела.
В записи участвуют блокировки трёх видов:
- Пессимистическая объектная. Форма ставит её автоматически, когда пользователь начинает менять документ. Второй пользователь при попытке правки получит
Объект уже заблокированс именем пользователя и компьютера. - Оптимистическая. При записи платформа сверяет версию данных. Если документ успели изменить в другом сеансе, запись отклоняется с сообщением, что данные изменены или удалены другим пользователем.
- Управляемая блокировка данных. Ставится в транзакции объектом
БлокировкаДанных— обычно вОбработкаПроведенияперед чтением остатков:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
Блокировка.Заблокировать();
Имя регистра и измерения замените своими. Блокируйте ровно те измерения, которые читаете: блокировка всего регистра заставит ждать всех, кто проводит документы по другим складам.
Держите транзакцию короткой: не открывайте её на клиенте через цепочку вызовов и не задавайте вопросов пользователю, пока она открыта. В серверной транзакции диалог невозможен в принципе.
Обработка ошибок при записи документа на клиенте
Метод формы Записать() и серверная функция ведут себя по-разному. Это частый источник путаницы.
| Что произошло | Записать() формы | Серверная функция |
|---|---|---|
Отказ = Истина в обработчике | Возвращает Ложь | Исключение «не удалось записать/провести» |
| Ошибка в коде проведения | Сообщение, возвращает Ложь | Исключение, транзакция откатывается |
Сообщения СообщениеПользователю | Показываются в форме | Доставляются на клиент после вызова |
Сообщение лучше привязывать к полю — тогда пользователь увидит, что исправить:
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не указан склад";
Сообщение.Поле = "Склад";
Сообщение.УстановитьДанные(ЭтотОбъект);
Сообщение.Сообщить();
Отказ = Истина;
Этот фрагмент для модуля объекта. В модуле формы вместо УстановитьДанные укажите Сообщение.Поле = "Объект.Склад".
Запись в обычном и управляемом приложении, разница по версиям платформы
Код из старых примеров часто не работает именно из-за другой модели приложения.
- 🗂 Обычное приложение, толстый клиент (типовые на 8.1–8.2 обычных форм). Объекты базы доступны на клиенте,
ДокОбъект.Записать()прямо в модуле формы работает, транзакции тоже. Директив&НаКлиентев обычных формах нет. - 🖥 Управляемое приложение, 8.2 и 8.3. Разделение клиент/сервер обязательно. Запись — только через метод формы или серверный вызов.
- 🪟 Режим модальности «Не использовать» (платформа 8.3.3 и выше в новых конфигурациях). Модальные
Вопрос()иПредупреждение()заменяют наПоказатьВопрос(),ПоказатьПредупреждение()сОписаниеОповещения. - ⏳ Асинх и Ждать (платформа с номером не ниже 8.3.18). Можно писать
Ответ = Ждать ВопросАсинх(...)в асинхронной процедуре-команде. Но обработчикПередЗаписьюасинхронным делать нельзя:Отказдолжен быть установлен сразу, поэтому схема с повторнымЗаписать()остаётся рабочей.
Если конфигурация работает в режиме совместимости, новые возможности языка ограничены этим режимом, а не номером установленной платформы. Свойство смотрите в корне конфигурации: «Режим совместимости».
Частые вопросы
Можно ли провести документ из формы списка, не открывая его?
Ссылка.ПолучитьОбъект() и вызовите Записать(РежимЗаписиДокумента.Проведение). После вызова обновите список через ОповеститьОбИзменении.Чем &НаСервере отличается от &НаСервереБезКонтекста при записи?
&НаСервере передаёт на сервер всю форму и возвращает её обратно, зато даёт доступ к Объект и РеквизитФормыВЗначение. &НаСервереБезКонтекста работает только с параметрами, поэтому быстрее.Почему после записи через РеквизитФормыВЗначение форма ругается на изменённые данные?
ЗначениеВРеквизитФормы(ДокОбъект, "Объект"), и форма получит актуальную версию.Как записать документ без проверок и проведения, например при загрузке?
ДокОбъект.ОбменДанными.Загрузка = Истина и вызовите Записать(). Обработчики модуля объекта при этом выполняются, но по соглашению их код должен сразу выходить; проверки заполнения не срабатывают. Используйте только для загрузки и обменов.Можно ли узнать на клиенте, что документ уже проведён?
Объект.Проведен. Для документа, который не открыт в форме, это значение нужно получить серверным вызовом.