framework/skills/bsl-practices/form-patterns/SKILL.md
Для кода модуля управляемой формы и серверных вызовов
npx skillsauth add steelmorgan/1c-agent-based-dev-framework form-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Ключевой принцип: Минимизируйте количество и объём серверных вызовов. Каждый вызов &НаСервере — это сетевой round-trip плюс сериализация всего контекста формы.
1. &НаКлиентеНаСервереБезКонтекста — чистые вычисления, обе среды
2. &НаСервереБезКонтекста — обращение к БД, без контекста формы
3. &НаСервере — нужен доступ к реквизитам формы
4. &НаКлиенте — интерактивная логика (диалоги, навигация)
Нужен доступ к базе данных?
├── Нет → &НаКлиенте или &НаКлиентеНаСервереБезКонтекста
└── Да →
Нужен доступ к реквизитам формы?
├── Нет → &НаСервереБезКонтекста (передаём только параметры)
└── Да → &НаСервере (передаётся весь контекст)
// Чистое вычисление - обе среды
&НаКлиентеНаСервереБезКонтекста
Функция РассчитатьСумму(Количество, Цена, СтавкаНДС)
СуммаБезНДС = Количество * Цена;
СуммаНДС = СуммаБезНДС * СтавкаНДС / 100;
Возврат СуммаБезНДС + СуммаНДС;
КонецФункции
// Нужны данные из базы, но реквизиты формы НЕ нужны
&НаСервереБезКонтекста
Функция ПолучитьДанныеНоменклатуры(НоменклатураСсылка)
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
НоменклатураСсылка,
"Наименование, ЕдиницаИзмерения, СтавкаНДС, Цена");
КонецФункции
// Нужен доступ к реквизитам формы
&НаСервере
Процедура ПересчитатьИтогиНаСервере()
Объект.СуммаДокумента = Объект.Товары.Итог("Сумма");
Объект.СуммаНДС = Объект.Товары.Итог("СуммаНДС");
КонецПроцедуры
Каждый серверный вызов — это около 100 мс (сериализация + round-trip + десериализация). Три вызова = 300 мс задержки. Один вызов, возвращающий все данные = 100 мс.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
ДанныеЗаполнения = ПолучитьДанныеЗаполненияПоКонтрагенту(Объект.Контрагент, Объект.Дата);
Объект.Договор = ДанныеЗаполнения.Договор;
Объект.Организация = ДанныеЗаполнения.Организация;
Объект.Валюта = ДанныеЗаполнения.Валюта;
КурсВалюты = ДанныеЗаполнения.Курс;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДанныеЗаполненияПоКонтрагенту(КонтрагентСсылка, ДатаДокумента)
Результат = Новый Структура;
РеквизитыКонтрагента = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
КонтрагентСсылка, "ДоговорПоУмолчанию, ОсновнаяОрганизация");
Результат.Вставить("Договор", РеквизитыКонтрагента.ДоговорПоУмолчанию);
Результат.Вставить("Организация", РеквизитыКонтрагента.ОсновнаяОрганизация);
Если ЗначениеЗаполнено(РеквизитыКонтрагента.ДоговорПоУмолчанию) Тогда
ВалютаДоговора = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
РеквизитыКонтрагента.ДоговорПоУмолчанию, "ВалютаВзаиморасчетов");
Результат.Вставить("Валюта", ВалютаДоговора);
Результат.Вставить("Курс", РаботаСКурсамиВалют.ПолучитьКурсВалюты(ВалютаДоговора, ДатаДокумента));
Иначе
Результат.Вставить("Валюта", Неопределено);
Результат.Вставить("Курс", 1);
КонецЕсли;
Возврат Результат;
КонецФункции
На сервере формы данные объекта — это не реальный объект, а ДанныеФормы*. Для вызова методов модуля объекта требуется преобразование.
| Сценарий | Нужно преобразование? |
|----------|----------------------|
| Чтение реквизитов формы | Нет - Объект.Реквизит работает напрямую |
| Вызов методов модуля объекта | Да |
| Передача объекта в общий модуль | Да - общие модули работают с реальными объектами |
&НаСервере
Процедура ЗаполнитьПоУмолчаниюНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТоварыПоУмолчанию();
// ОБЯЗАТЕЛЬНО: преобразовать обратно, иначе изменения не отразятся на форме!
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
// ПЛОХО: изменения теряются!
&НаСервере
Процедура ЗаполнитьНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТаблицу();
// ЗАБЫЛИ: ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
1. ПриСозданииНаСервере - форма создаётся на сервере, данные загружаются
2. ПриОткрытии - форма отображается на клиенте
1. ПередЗаписью - клиент: может быть отменена (Отказ = Истина)
2. ПередЗаписьюНаСервере - сервер: финальная проверка
3. ПриЗаписиНаСервере - сервер: в той же транзакции
4. ПослеЗаписиНаСервере - сервер: обновление формы
5. ПослеЗаписи - клиент: оповещение
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Параметры.Свойство("ЗначенияЗаполнения") Тогда
ЗаполнитьЗначенияСвойств(Объект, Параметры.ЗначенияЗаполнения);
КонецЕсли;
Элементы.ГруппаСклад.Видимость = (Объект.ВидОперации <>
Перечисления.ВидыОпераций.Услуга);
ОбновитьСписокВыбораДоговоров();
КонецПроцедуры
// ПередЗаписью (клиент) - проверки, требующие подтверждения пользователя
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
Если Объект.СуммаДокумента > 1000000 Тогда
Если Не ПараметрыЗаписи.Свойство("ПодтверждениеСуммы") Тогда
Отказ = Истина;
ПоказатьВопрос(
Новый ОписаниеОповещения("ПослеПодтвержденияСуммы", ЭтотОбъект, ПараметрыЗаписи),
НСтр("ru = 'Сумма документа превышает 1 000 000. Продолжить?'"),
РежимДиалогаВопрос.ДаНет);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
// ПослеЗаписи - оповещения на клиенте
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Оповестить("Запись_РеализацияТоваровУслуг",
Новый Структура("Ссылка", Объект.Ссылка), ЭтотОбъект);
КонецПроцедуры
Динамический список автоматически реализует постраничную загрузку, поиск и сортировку.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Отборы через КомпоновкаДанных (пользователь может их изменить)
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
СписокДокументов,
"Организация",
Организация,
ВидСравненияКомпоновкиДанных.Равно,
,
ЗначениеЗаполнено(Организация));
КонецПроцедуры
СписокДокументов.ПроизвольныйЗапрос = Истина;
СписокДокументов.ТекстЗапроса =
"ВЫБРАТЬ
| Реализация.Ссылка,
| Реализация.Дата,
| Реализация.Номер,
| Реализация.Контрагент,
| Реализация.СуммаДокумента,
| ЕСТЬNULL(Задолженность.СуммаОстаток, 0) КАК ОстатокЗадолженности
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Реализация
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК Задолженность
| ПО Реализация.Контрагент = Задолженность.Контрагент
| И Реализация.Договор = Задолженность.Договор
|{ГДЕ
| Реализация.Дата >= &ДатаНачала}";
Правила: не загружайте все данные; используйте КомпоновкаДанных для отборов, а не WHERE; не используйте УПОРЯДОЧИТЬ ПО в произвольном запросе.
&НаСервере
Процедура УстановитьУсловноеОформление()
УсловноеОформление.Элементы.Очистить();
ЭлементУО = УсловноеОформление.Элементы.Добавить();
ЭлементОтбора = ЭлементУО.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ДатаОплаты");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Меньше;
ЭлементОтбора.ПравоеЗначение = ТекущаяДатаСеанса();
ЭлементОтбора.Использование = Истина;
ЭлементОтбора2 = ЭлементУО.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора2.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Оплачен");
ЭлементОтбора2.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора2.ПравоеЗначение = Ложь;
ЭлементОтбора2.Использование = Истина;
ЭлементУО.Оформление.УстановитьЗначениеПараметра("ЦветФона", WebЦвета.МисттиРоуз);
ПолеОформления = ЭлементУО.Поля.Элементы.Добавить();
ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("СписокДокументов");
КонецПроцедуры
Модальные вызовы (Предупреждение(), Вопрос()) запрещены в веб-клиенте. Используйте ОписаниеОповещения.
Стандарт ИТС: «Ограничения на использование модальных методов».
&НаКлиенте
Процедура УдалитьСтрокуТоваров(Команда)
Если Элементы.Товары.ТекущаяСтрока = Неопределено Тогда
Возврат;
КонецЕсли;
ПоказатьВопрос(
Новый ОписаниеОповещения("ПослеПодтвержденияУдаления", ЭтотОбъект),
НСтр("ru = 'Удалить выбранную строку?'"),
РежимДиалогаВопрос.ДаНет,
,
КодВозвратаДиалога.Нет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодтвержденияУдаления(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
Объект.Товары.Удалить(Элементы.Товары.ТекущаяСтрока);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьФормуПодбораТоваров(Команда)
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Организация", Объект.Организация);
ПараметрыФормы.Вставить("Склад", Объект.Склад);
ПараметрыФормы.Вставить("Дата", Объект.Дата);
ПараметрыФормы.Вставить("МножественныйВыбор", Истина);
ОткрытьФорму("Обработка.ПодборТоваров.Форма",
ПараметрыФормы,
ЭтотОбъект,
,,,
Новый ОписаниеОповещения("ПослеПодбораТоваров", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодбораТоваров(РезультатПодбора, ДополнительныеПараметры) Экспорт
Если РезультатПодбора = Неопределено Тогда
Возврат;
КонецЕсли;
Для Каждого ДанныеТовара Из РезультатПодбора Цикл
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеТовара);
КонецЦикла;
Модифицированность = Истина;
КонецПроцедуры
Собирайте все изменения видимости/доступности в один серверный вызов, чтобы избежать "мерцания" интерфейса.
&НаСервере
Процедура УправлениеВидимостью()
ЭтоУслуга = Объект.ВидОперации = Перечисления.ВидыОпераций.Услуга;
Элементы.Склад.Видимость = НЕ ЭтоУслуга;
Элементы.ТоварыКоличество.Видимость = НЕ ЭтоУслуга;
Элементы.ТоварыЕдиницаИзмерения.Видимость = НЕ ЭтоУслуга;
Элементы.ГруппаДоставка.Видимость = НЕ ЭтоУслуга;
Элементы.Контрагент.ТолькоПросмотр = Объект.Проведен;
КонецПроцедуры
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
УправлениеВидимостью(); // Один серверный вызов на все изменения
КонецПроцедуры
// Изменение количества - пересчёт суммы на клиенте
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
ТекущаяСтрока.Сумма = РассчитатьСумму(
ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры
// Изменение номенклатуры - нужен сервер, чтобы получить данные
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
ДанныеНоменклатуры = ПолучитьДанныеНоменклатуры(ТекущаяСтрока.Номенклатура);
ТекущаяСтрока.ЕдиницаИзмерения = ДанныеНоменклатуры.ЕдиницаИзмерения;
ТекущаяСтрока.Цена = ДанныеНоменклатуры.Цена;
ТекущаяСтрока.СтавкаНДС = ДанныеНоменклатуры.СтавкаНДС;
ТекущаяСтрока.Сумма = РассчитатьСумму(
ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры
// Форма документа - после записи
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Оповестить("Запись_РеализацияТоваровУслуг",
Новый Структура("Ссылка, Проведен", Объект.Ссылка, Объект.Проведен),
ЭтотОбъект);
КонецПроцедуры
// Форма списка - обработка оповещения
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "Запись_РеализацияТоваровУслуг" Тогда
Элементы.Список.Обновить();
КонецЕсли;
КонецПроцедуры
Примитивные (Строка, Число, Дата, Булево), Ссылки, Перечисления, Структура, Соответствие, Массив, ФиксированныеКоллекции, ХранилищеЗначения.
| Тип | Альтернатива | |-----|-------------| | ТаблицаЗначений | ДанныеФормыКоллекция (через реквизиты формы) | | ДеревоЗначений | ДанныеФормыДерево (через реквизиты формы) | | ОбъектМетаданных | Передавать ИмяМетаданных (строку) | | Запрос, РезультатЗапроса | Передавать результат (структура/массив) |
Некоторые аспекты корректности формы определяются не кодом модуля, а свойствами формы и её элементов. При генерации XML, ревью формы или доработке типовой формы проверяйте эти инварианты.
| Область | Проверка |
|---------|----------|
| Команды | Команда, изменяющая сохраняемые данные, должна иметь ИзменяетСохраняемыеДанные. Иначе форма может не становиться модифицированной, а при ТолькоПросмотр команда останется активной. |
| Табличные части | На странице табличной части должен быть заполнен путь к данным заголовка, например Объект.Товары.КоличествоСтрок, если заголовок должен показывать количество строк. |
| Обязательные поля | Используйте свойство ПроверкаЗаполнения там, где обязательность — это свойство реквизита. Ручная проверка в коде допустима для условных бизнес-правил, но не должна без причины подменять платформенную проверку. |
| Форма списка | Добавляйте ГруппаПользовательскихНастроек, если форма списка должна показывать пользовательские отборы. |
| Типовые формы | Реализуйте доработки типовых форм программно. При наличии вкладок размещайте новые реквизиты на отдельной служебной вкладке; при отсутствии вкладок — в служебной группе без заголовка и выделения, если в спецификации не указано точное расположение. |
Для проверки заполнения в обработчике формы используйте стандартный вызов:
Если Не ПроверитьЗаполнение() Тогда
Возврат;
КонецЕсли;
Не разбрасывайте присвоения Видимость, Доступность и ТолькоПросмотр по разным обработчикам формы. Собирайте их в одну процедуру и вызывайте её последним шагом после инициализации и заполнения данных.
&НаКлиенте
Процедура ОбновитьВидимостьДоступностьЭлементовФормы()
ЭтоУслуга = Объект.ВидОперации = Перечисления.ВидыОпераций.Услуга;
Элементы.Склад.Видимость = Не ЭтоУслуга;
Элементы.Комментарий.ТолькоПросмотр = Объект.Проведен;
КонецПроцедуры
Предпочитайте булеву алгебру ветвлениям Если ... Тогда ... Иначе ..., когда значение свойства напрямую выражается условием.
development
1C server maintenance webhooks: container restart and external component cache cleanup
development
Interactive DAP debugging of a single BSL procedure
tools
Rules for using RLM tools for project search and navigation in 1C/BSL
development
Creates web applications and routes on Winow (a web server on OneScript and Autumn). Use when working with a web server on OneScript, routing, or Winow controllers.