«Вспоминая требования заказчиков на клиентских интервью, я могу сказать, что сейчас все больше внимания клиенты уделяют знаниям БСП у разработчиков, – рассказывает мой коллега Сергей. – Конечно, вопросы сперва очень общие: «А знаешь ли что-то о БСП?, «Может быть, что-то слышал о БСП?» или «а какие задачи решал с помощью механизмов БСП?». Исходя из этих вопросов, я бы хотел, чтобы у читателя сформировался некий базовый задел, на котором мы и будем дальше развиваться.

Мы рассмотрим стандарты разработки 1С, восполним возможные проблемы и посмотрим на варианты типового решения задач в 1С».

Сергей

разработчик 1С в Programming Store

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

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

Принципы клиент-серверного взаимодействия

Пара базовых моментов, на которых строится проектирование любой системы клиент-серверного взаимодействия:

  • Минимизация объёма передаваемых данных.

  • Минимизация количества серверных вызовов.

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

Начнем со схемы проведения документа.

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

Рассмотрим на примере документа ПоступлениеТоваров в тестовой конфигурации со встроенной БСП, чтобы не путаться назовем конфигурацию Статья и добавим в нее единственную подсистему Статьи:

В нее-то как раз и добавим наш документ ПоступлениеТоваров. Тут же видно, что в конфигурации присутствует подсистема СтандартныеПодсистемы. Я использую версию Стандартных подсистем 3.1.10.403, актуальную на момент написания статьи. Но на самом деле можно использовать и другие версии Стандартных подсистем.

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

Вопросы пользователю ПередЗаписью через метод Оповещение

В форме документа сделаем процедуру Процедура ПередЗаписью(Отказ, ПараметрыЗаписи). Не забывая сделать разметку документа на области, согласно стандартов и рекомендаций 1С (https://its.1c.ru/db/v8std/content/455/hdoc)

В первую очередь взведём флаг Отказ:
Отказ = Истина;

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

ОписаниеОповещения = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект, ДополнительныеПараметры);

Тут у нас «ПослеОтветаНаВопрос» – это процедура, которая будет выполняться после ответа пользователя на вопрос, ЭтотОбъект – это, собственно говоря, сам наш документ. А ДополнительныеПараметры – это параметры, которые мы передаём в процедуру Оповещения. Это будет структура, в которую мы пока поместим одно значение ПараметрыЗаписи. Но в дальнейшем через эту структуру можно передавать и любые другие допустимые параметры.

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

И наш вопрос пользователю будет выглядеть вот так:

ПоказатьВопрос(ОписаниеОповещения, "Готовы продолжить?", РежимДиалогаВопрос.ДаНет);

В процедуре ПослеОтветаНаВопрос нам нужно проанализировать ответ пользователя, и если он положительный, то вызвать стандартную процедуру записи документа. Тут-то нам и пригодится параметр, который мы передали через структуру. А именно формат записи: запись или проведение документа. Поскольку наш метод вызывается не из формы объекта, а платформой, то метод обязательно должен быть Экспортным!

Итак, процедура могла бы выглядеть вот так:

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

Но в общем случае — наша процедура ПередЗаписью() — получается, что всегда будет взводить флаг отказа. А нам это не нужно. Поэтому можем добавить на форму реквизит ВыполнятьПроверкуПередЗаписью. При открытии формы установим его в Истина, и в начало процедуры вставим такой код:

Если Не ВыполнятьПроверкуПередЗаписью Тогда
		ВыполнятьПроверкуПередЗаписью = Истина;
		Возврат;
	КонецЕсли;

А в процедуру ПослеОтветаНаВопрос():        

ВыполнятьПроверкуПередЗаписью = Ложь;

В общем наш метод ПередЗаписью() и процедура с обработкой вопроса-ответа от пользователя будет выглядеть вот так:

В пользовательском режиме наш вопрос пользователю будет выглядеть вот так:

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

Модификатор Знач при передаче параметров в процедуру

При вызове бесконтекстных процедур на сервере мы можем встретить служебное слово Знач. Что это такое и зачем оно используется? Чтобы проверить, я делаю простенькую внешнюю обработку с одной командой ПроверкаВариантовПереходаВПроцедуру. Для этой команды создаю процедуры на Клиенте и НаСервереБезКонтекста. В эту процедуру в качестве параметра передаём ДанныеФормыСтруктура. В случае обработки это будет сама наша обработка, то есть Объект.

Получаем вот такой код:

&НаСервереБезКонтекста
Процедура ПроверкаВариантовПереходаВПроцедуруНаСервере(ДанныеФормыСтруктура)
	// Вставить содержимое обработчика.
КонецПроцедуры

&НаКлиенте
Процедура ПроверкаВариантовПереходаВПроцедуру(Команда)
	ПроверкаВариантовПереходаВПроцедуруНаСервере(Объект);
КонецПроцедуры

Давайте разберёмся. Допустим, у нас есть переменная Перем1 и делаем Перем2 как ссылку на Перем1. Изменяя переменную Перем2, мы, по сути, изменяем и Перем1. Если же мы начинаем работать на сервере, переменные создаём на Клиенте и передаём Перем2 в процедуру, расположенную на сервере, то в этом случае Платформа физически не может работать с той же ячейкой памяти, на которую ссылаются обе переменные Перем1 и Перем2. В этом случае Перем2 упаковывается и передаётся на сервер. А уже там изменяется не зависимо от Перем1. После возврата управления с сервера на клиент эта переменная будет снова упакована и передана на Клиент. Но там она уже будет ссылаться на другую ячейку памяти. То есть Перем1 не станет равной Перем2. И вот, чтобы не возвращать значение, как раз и используется ключевое слово Знач.

Кстати, некоторые разработчики начинают использовать это ключевое слово Знач для оптимизации. Чтобы минимизировать количество передаваемых данных с клиента на сервер (а в данном случае обратно, с сервера на клиент). И в теории это так, но оптимизации тут можно добиться очень не много, а вот при чтении кода следующими разработчиками вы введёте их в заблуждение. Он будет думать, что в программном коде вашей серверной процедуры где-то изменяется переменная, переданная с параметром Знач, и будет искать это место.

Давайте в нашей маленькой обработке проверим этот момент. Создадим вторую команду ПроверкаПередачиПараметраЗначНаКлиенте и напишем вот такой код.

&НаКлиенте
Процедура ПроверкаПередачиПараметраЗначНаКлиенте(Команда)
	
	Перем1 = Новый Структура;
	Перем1.Вставить("Свойство1");
	
	Перем2 = Перем1;	// т.е. ссылаемся на ту же структуру что и в Перем1
	
	ИзменитьПеременную(Перем2);
	
КонецПроцедуры

&НаКлиенте
Процедура ИзменитьПеременную(Параметр)
	
	Параметр.Вставить("Свойство2");

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

Далее ставим точку останова и запускаем обработку:

Теперь попробуем добавить ключевое слово Знач в процедуру ИзменитьПеременную. Я ожидаю, что после выполнения этой процедуры значения переменных Перем1 и Перем2 будут различны.

Но ожидаемого не произошло! Почему?! Да потому что тут не было серверного вызова, и моя процедура отработала на той же машине, что и основная. В данном случае они обе выполнились &НаКлиенте.

А вот если процедуру ИзменитьПеременную выполнить на сервере, то тогда мы и должны увидеть искомую разницу.

Как и ожидалось явно одинаковые переменные стали различны. Это не есть хорошо. Поэтому воспользуемся ключевым словом Знач. Переменную создали, передали на сервер используя ключевое слово Знач, там изменили. А после возвращения в исходный контекст наши переменные остались не изменёнными. Чего мы, собственно, и хотели добиться.

Теперь возвращаемся к нашей первой ошибке и передаём в процедуру наш объект, используя ключевое слово Знач

Мы в процедуре! Ошибки нет. Можно работать с этой переменной, не изменяя данных исходного объекта.

Вопросы пользователю ПередЗаписью, используя Асинхронные методы

Вопрос пользователю можно задать с помощью процедуры ВопросАсинх(). Эта процедура возвращает значение типа Обещание. Что это такое? А это контейнер для, возможно, пока неизвестного результата выполнения некоторого действия (асинхронной функции). 
У функции может быть два результата: нормальное завершение (тогда Обещание содержит возвращаемое значение) или исключение (тогда Обещание оборачивает исключение). Сама процедура используется для асинхронных вычислений. Все асинхронные функции возвращают объект типа Обещание. 

Соответственно, есть специальный оператор Ждать, который используется для вызова таких процедур. Но этот оператор может быть использован только в асинхронных процедурах или функциях. Т.е. использовать слово Асинх перед описанием процедуры. А дальше получается такая логика: как только мы вызываем процедуру с определением Асинх, она сразу разделяется на две части. Одна до вызова ВопросАсинх, а вторая – после.

Таким образом, если мы напишем вот такой код в процедуре ПередЗаписью:

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

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

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

#Область ОбработчикиСобытийФормы

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

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

&НаКлиенте
Асинх Процедура ПоказатьВопросАсинхронно(ДополнительныеПараметры)
	
	Результат = Ждать ВопросАсинх("Готовы продолжить?", РежимДиалогаВопрос.ДаНет);
	
	Если Результат = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Если Результат = КодВозвратаДиалога.Да Тогда
		ВыполнятьПроверкуПередЗаписью = Истина;
		Записать(ДополнительныеПараметры.ПараметрыЗаписи);
	КонецЕсли;
	
КонецПроцедуры

Комментарии (1)


  1. asatost
    10.10.2025 14:57

    Какое отношение всё это имеет к БСП?