Как программно создать документ в 1С, соблюдая клиент-серверную архитектуру?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При работе с управляемыми формами в 1С начинающие разработчики часто сталкиваются с ошибками, связанными с неправильным пониманием клиент-серверной модели. Классическая задача — создать документ по нажатию кнопки — может вызвать затруднения, если смешать клиентский и серверный код. Давайте разберем типичные ошибки и рассмотрим правильный, структурированный подход к программному созданию документов на примере документа «Установка цен номенклатуры».

Анализ исходной проблемы и типичных ошибок

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

Вот пример кода, который не будет работать:


&НаКлиенте
Функция СоздатьНовыйДокумент(Команда)
    // Ошибка: создание объекта базы данных на клиенте
    НовыйДокумент = Объект.Документы.УстановкаЦенНоменклатуры.СоздатьДокумент(); 
    НовыйДокумент.Номер = "111";
    НовыйДокумент.Дата = ТекущаяДата();
    // Ошибка: запись в базу данных с клиента
    НовыйДокумент.Записать(); 
    Возврат НовыйДокумент.Ссылка;
КонецФункции

Выясним причины, по которым этот код неработоспособен:

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

Правильная архитектура: разделение клиента и сервера

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

  1. Клиентская процедура (&НаКлиенте). Она привязана к кнопке на форме. Ее единственная задача — вызвать серверную функцию и, при необходимости, обработать результат (например, полученную ссылку на новый документ).
  2. Серверная функция (&НаСервере или &НаСервереБезКонтекста). В ней происходит вся "магия": создание объекта документа, заполнение его реквизитов и запись в базу данных. Эта функция возвращает результат (например, ссылку) обратно на клиент.

Давайте напишем работающий код. Он будет состоять из двух частей.


// 1. Процедура-обработчик команды на форме (выполняется на клиенте)
&НаКлиенте
Процедура СоздатьДокумент(Команда)
	// Вызываем серверную функцию и получаем результат (ссылку на документ)
	СсылкаНаНовыйДокумент = СоздатьДокументНаСервере();
	
	// Если документ успешно создан, выводим сообщение
	Если ЗначениеЗаполнено(СсылкаНаНовыйДокумент) Тогда
		Сообщить("Документ '" + СсылкаНаНовыйДокумент + "' успешно создан!");
	КонецЕсли;
	
КонецПроцедуры

// 2. Функция, которая выполняет всю работу с базой данных (выполняется на сервере)
&НаСервереБезКонтекста
Функция СоздатьДокументНаСервере()
	
	// Создаем объект документа
	НовыйДокумент = Документы.УстановкаЦенНоменклатуры.СоздатьДокумент();
	
	// Заполняем реквизиты шапки
	НовыйДокумент.Дата = ТекущаяДата();
	
	// Устанавливаем номер автоматически. Это предпочтительнее, чем ручное присвоение
	НовыйДокумент.УстановитьНовыйНомер();
	
	// Записываем документ в базу данных
	НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
	
	// Возвращаем ссылку на созданный документ на клиент
	Возврат НовыйДокумент.Ссылка;
			
КонецФункции

Важные нюансы и детали реализации

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

&НаСервереБезКонтекста против &НаСервере

В примере мы использовали директиву &НаСервереБезКонтекста. Это означает, что при вызове серверной функции с клиента на сервер не передаются данные формы (ее реквизиты). Такой вызов "легче" и быстрее. Его стоит использовать, когда для выполнения серверной логики не нужны никакие данные с формы. Если бы нам понадобилось, например, использовать значение из поля ввода на форме для заполнения документа, мы бы использовали директиву &НаСервере и передали бы это значение как параметр функции.

Автоматическая нумерация

Вместо жесткого присвоения номера (НовыйДокумент.Номер = "112") мы использовали метод объекта УстановитьНовыйНомер(). Это правильный подход, так как он гарантирует уникальность номера в соответствии с настройками нумерации в конфигурации. Запомните, что это метод объекта документа, а не глобальная функция, поэтому вызов должен быть таким: НовыйДокумент.УстановитьНовыйНомер().

Вывод сообщений пользователю

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

"Странные" номера в тестовой базе

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

Следующий шаг: Заполнение табличных частей

Документ «Установка цен номенклатуры» не имеет смысла без заполненных табличных частей «Виды цен» и «Товары». Давайте расширим наш серверный код, чтобы он не только создавал шапку документа, но и заполнял его данными.

Логика заполнения следующая:

  1. Сначала мы должны заполнить табличную часть ВидыЦен, перечислив все виды цен, которые хотим установить.
  2. Затем мы заполняем табличную часть Товары, добавляя номенклатуру и указывая для нее цены в колонках, соответствующих видам цен из первого шага.

Посмотрим на пример кода, который создает документ и устанавливает оптовую цену в 1500 рублей для товара «Стол офисный».


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

	// --- Заполнение табличной части "Товары" ---

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

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

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

← На главную