framework/skills/bsl-practices/form-patterns/SKILL.md
MUST use WHEN пишешь код модуля управляемой формы 1С. Provides правила выбора директив контекста (&НаСервереБезКонтекста и др.) и минимизации серверных round-trip.
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 = 'Удалить выбранную строку?'"),
РежимДиалогаВопрос.ДаНет,
,
КодВозвратаДиалога.Нет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодтвержденияУдаления(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
Объект.Товары.Удалить(Элементы.Товары.ТекущаяСтрока);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьФормуПодбораТоваров(Команда)
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Организация", Объект.Организация);
ПараметрыФормы.Вставить("Склад", Объект.Склад);
ПараметрыФормы.Вставить("Дата", Объект.Дата);
ПараметрыФормы.Вставить("МножественныйВыбор", Истина);
ОткрытьФорму("Обработка.ПодборТоваров.Форма",
ПараметрыФормы,
ЭтотОбъект,
,,,
Новый ОписаниеОповещения("ПослеПодбораТоваров", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодбораТоваров(РезультатПодбора, ДополнительныеПараметры) Экспорт
Если РезультатПодбора = Неопределено Тогда
Возврат;
КонецЕсли;
Для Каждого ДанныеТовара Из РезультатПодбора Цикл
НоваяСтрока = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеТовара);
КонецЦикла;
Модифицированность = Истина;
КонецПроцедуры
Все изменения видимости/доступности — в одном серверном вызове, чтобы избежать «дёрганья» интерфейса.
&НаСервере
Процедура УправлениеВидимостью()
ЭтоУслуга = Объект.ВидОперации = Перечисления.ВидыОпераций.Услуга;
Элементы.Склад.Видимость = НЕ ЭтоУслуга;
Элементы.ТоварыКоличество.Видимость = НЕ ЭтоУслуга;
Элементы.ТоварыЕдиницаИзмерения.Видимость = НЕ ЭтоУслуга;
Элементы.ГруппаДоставка.Видимость = НЕ ЭтоУслуга;
Элементы.Контрагент.ТолькоПросмотр = Объект.Проведен;
КонецПроцедуры
&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
УправлениеВидимостью(); // Один серверный вызов для всех изменений
КонецПроцедуры
// Изменение количества — пересчёт суммы на клиенте
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
ТекущаяСтрока.Сумма = РассчитатьСумму(
ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры
// Изменение номенклатуры — нужен сервер для получения данных
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
ДанныеНоменклатуры = ПолучитьДанныеНоменклатуры(ТекущаяСтрока.Номенклатура);
ТекущаяСтрока.ЕдиницаИзмерения = ДанныеНоменклатуры.ЕдиницаИзмерения;
ТекущаяСтрока.Цена = ДанныеНоменклатуры.Цена;
ТекущаяСтрока.СтавкаНДС = ДанныеНоменклатуры.СтавкаНДС;
ТекущаяСтрока.Сумма = РассчитатьСумму(
ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры
// Форма документа — после записи
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Оповестить("Запись_РеализацияТоваровУслуг",
Новый Структура("Ссылка, Проведен", Объект.Ссылка, Объект.Проведен),
ЭтотОбъект);
КонецПроцедуры
// Форма списка — обработка оповещения
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "Запись_РеализацияТоваровУслуг" Тогда
Элементы.Список.Обновить();
КонецЕсли;
КонецПроцедуры
Примитивные (Строка, Число, Дата, Булево), Ссылки, Перечисления, Структура, Соответствие, Массив, ФиксированныеКоллекции, ХранилищеЗначения.
| Тип | Альтернатива | |-----|-------------| | ТаблицаЗначений | ДанныеФормыКоллекция (через реквизиты формы) | | ДеревоЗначений | ДанныеФормыДерево (через реквизиты формы) | | ОбъектМетаданных | Передавать ИмяМетаданных (строку) | | Запрос, РезультатЗапроса | Передавать результат (структура/массив) |
testing
MUST use BEFORE making a judgment about the cause of a conflict, a test failure, or an artifact dispute. Defines the end-to-end verification method L1→L6 and the classification of the first broken link.
development
MUST use AFTER a work cycle with ≥2 iterations (wrote → error → fixed → success). Provides the retrospective procedure and the format for recording practice/anti-patterns in references/learned-patterns.md or {project}/.context/learned-patterns.md.
tools
MUST use WHEN you are writing reusable knowledge into RLM (pattern / architectural decision / stable domain fact) OR reading it before a non-trivial task/solution in the domain. Provides the breakdown of native-push vs RLM-pull, tools for writing and reading RLM, H-MEM levels, and hygiene.
testing
MUST use WHEN the task is classified as simple (< 20 lines, 1 file, no new metadata objects, no architectural decisions). Provides a short cycle of 3 steps with a guard on the self path and mandatory verify.