form-patterns

$npx mdskill add SteelMorgan/1c-agent-based-dev-framework/form-patterns

Teaches agents to write efficient 1C managed form modules by minimizing server calls and selecting optimal directives.

  • Helps reduce network latency and serialization overhead in client-server interactions.
  • Integrates with 1C platform directives like &НаСервереБезКонтекста for database access.
  • Uses a decision tree to prioritize directives based on database needs and form context.
  • Delivers results through code examples and structured rules for implementation guidance.

SKILL.md

.github/skills/form-patternsView on GitHub ↗
---
name: form-patterns
description: Паттерны модуля формы (клиент-серверное взаимодействие). Этот навык учит агента правильно писать код модулей управляемых форм 1С.
---

# Паттерны модуля формы (клиент-серверное взаимодействие)

**Ключевой принцип:** Минимизировать количество и объём серверных вызовов. Каждый вызов `&НаСервере` — сетевой round-trip + сериализация полного контекста формы.

---

## Правило 1: Иерархия директив — предпочитайте безконтекстные вызовы

### Порядок предпочтения (от лучшего к худшему)

```
1. &НаКлиентеНаСервереБезКонтекста  — чистые вычисления, обе среды
2. &НаСервереБезКонтекста            — обращение к БД, без контекста формы
3. &НаСервере                        — нужен доступ к реквизитам формы
4. &НаКлиенте                        — интерактивная логика (диалоги, навигация)
```

### Правило принятия решения

```
Нужно ли обращаться к базе данных?
├── Нет → &НаКлиенте или &НаКлиентеНаСервереБезКонтекста
└── Да →
    Нужен ли доступ к реквизитам формы?
    ├── Нет → &НаСервереБезКонтекста (передаём только параметры)
    └── Да → &НаСервере (передаётся весь контекст)
```

```bsl
// Чистое вычисление — обе среды
&НаКлиентеНаСервереБезКонтекста
Функция РассчитатьСумму(Количество, Цена, СтавкаНДС)
    СуммаБезНДС = Количество * Цена;
    СуммаНДС = СуммаБезНДС * СтавкаНДС / 100;
    Возврат СуммаБезНДС + СуммаНДС;
КонецФункции

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

// Нужен доступ к реквизитам формы
&НаСервере
Процедура ПересчитатьИтогиНаСервере()
    Объект.СуммаДокумента = Объект.Товары.Итог("Сумма");
    Объект.СуммаНДС = Объект.Товары.Итог("СуммаНДС");
КонецПроцедуры
```

---

## Правило 2: Минимизация серверных вызовов — батчинг данных

Каждый серверный вызов ~100 мс (сериализация + round-trip + десериализация). Три вызова = 300 мс задержки. Один вызов, возвращающий все данные, = 100 мс.

```bsl
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    ДанныеЗаполнения = ПолучитьДанныеЗаполненияПоКонтрагенту(Объект.Контрагент, Объект.Дата);

    Объект.Договор = ДанныеЗаполнения.Договор;
    Объект.Организация = ДанныеЗаполнения.Организация;
    Объект.Валюта = ДанныеЗаполнения.Валюта;
    КурсВалюты = ДанныеЗаполнения.Курс;
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьДанныеЗаполненияПоКонтрагенту(КонтрагентСсылка, ДатаДокумента)

    Результат = Новый Структура;

    РеквизитыКонтрагента = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
        КонтрагентСсылка, "ДоговорПоУмолчанию, ОсновнаяОрганизация");

    Результат.Вставить("Договор", РеквизитыКонтрагента.ДоговорПоУмолчанию);
    Результат.Вставить("Организация", РеквизитыКонтрагента.ОсновнаяОрганизация);

    Если ЗначениеЗаполнено(РеквизитыКонтрагента.ДоговорПоУмолчанию) Тогда
        ВалютаДоговора = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
            РеквизитыКонтрагента.ДоговорПоУмолчанию, "ВалютаВзаиморасчетов");
        Результат.Вставить("Валюта", ВалютаДоговора);
        Результат.Вставить("Курс", РаботаСКурсамиВалют.ПолучитьКурсВалюты(ВалютаДоговора, ДатаДокумента));
    Иначе
        Результат.Вставить("Валюта", Неопределено);
        Результат.Вставить("Курс", 1);
    КонецЕсли;

    Возврат Результат;

КонецФункции
```

---

## Правило 3: ДанныеФормыВЗначение / ЗначениеВДанныеФормы

На сервере формы данные объекта — не настоящий объект, а `ДанныеФормы*`. Для вызова методов модуля объекта нужно преобразование.

| Сценарий | Нужно преобразование? |
|----------|----------------------|
| Чтение реквизитов формы | Нет — `Объект.Реквизит` работает напрямую |
| Вызов методов модуля объекта | Да |
| Передача объекта в общий модуль | Да — общие модули работают с настоящими объектами |

```bsl
&НаСервере
Процедура ЗаполнитьПоУмолчаниюНаСервере()
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    ДокументОбъект.ЗаполнитьТоварыПоУмолчанию();
    // ОБЯЗАТЕЛЬНО: преобразовать обратно, иначе изменения не отразятся на форме!
    ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
```

### Типичная ошибка — забыли ЗначениеВРеквизитФормы

```bsl
// ПЛОХО: изменения потеряны!
&НаСервере
Процедура ЗаполнитьНаСервере()
    ДокументОбъект = РеквизитФормыВЗначение("Объект");
    ДокументОбъект.ЗаполнитьТаблицу();
    // ЗАБЫЛИ: ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
```

---

## Правило 4: Обработчики событий формы — порядок и назначение

### Порядок событий при открытии формы

```
1. ПриСозданииНаСервере     — форма создаётся на сервере, данные загружены
2. ПриОткрытии              — форма отобразилась на клиенте
```

### Порядок событий при записи

```
1. ПередЗаписьюНаКлиенте    — клиент: можно отменить (Отказ = Истина)
2. ПередЗаписьюНаСервере     — сервер: последняя проверка
3. ПриЗаписиНаСервере        — сервер: в той же транзакции
4. ПослеЗаписиНаСервере      — сервер: обновление формы
5. ПослеЗаписи               — клиент: оповещение
```

```bsl
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    Если Параметры.Свойство("ЗначенияЗаполнения") Тогда
        ЗаполнитьЗначенияСвойств(Объект, Параметры.ЗначенияЗаполнения);
    КонецЕсли;
    Элементы.ГруппаСклад.Видимость = (Объект.ВидОперации <>
        Перечисления.ВидыОпераций.Услуга);
    ОбновитьСписокВыбораДоговоров();
КонецПроцедуры

// ПередЗаписьюНаКлиенте — проверки, требующие подтверждения пользователя
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    Если Объект.СуммаДокумента > 1000000 Тогда
        Если Не ПараметрыЗаписи.Свойство("ПодтверждениеСуммы") Тогда
            Отказ = Истина;
            ПоказатьВопрос(
                Новый ОписаниеОповещения("ПослеПодтвержденияСуммы", ЭтотОбъект, ПараметрыЗаписи),
                НСтр("ru = 'Сумма документа превышает 1 000 000. Продолжить?'"),
                РежимДиалогаВопрос.ДаНет);
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

// ПослеЗаписи — оповещения на клиенте
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    Оповестить("Запись_РеализацияТоваровУслуг",
        Новый Структура("Ссылка", Объект.Ссылка), ЭтотОбъект);
КонецПроцедуры
```

---

## Правило 5: Динамические списки — настройка и оптимизация

Динамический список автоматически реализует пагинацию, поиск, сортировку.

```bsl
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Отборы через КомпоновкаДанных (пользователь сможет изменять)
    ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
        СписокДокументов,
        "Организация",
        Организация,
        ВидСравненияКомпоновкиДанных.Равно,
        ,
        ЗначениеЗаполнено(Организация));
КонецПроцедуры
```

### Произвольный запрос для динамического списка

```bsl
СписокДокументов.ПроизвольныйЗапрос = Истина;
СписокДокументов.ТекстЗапроса =
"ВЫБРАТЬ
|   Реализация.Ссылка,
|   Реализация.Дата,
|   Реализация.Номер,
|   Реализация.Контрагент,
|   Реализация.СуммаДокумента,
|   ЕСТЬNULL(Задолженность.СуммаОстаток, 0) КАК ОстатокЗадолженности
|ИЗ
|   Документ.РеализацияТоваровУслуг КАК Реализация
|       ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК Задолженность
|       ПО Реализация.Контрагент = Задолженность.Контрагент
|           И Реализация.Договор = Задолженность.Договор
|{ГДЕ
|   Реализация.Дата >= &ДатаНачала}";
```

Правила: не загружайте все данные; отборы через КомпоновкуДанных, не через WHERE; не используйте УПОРЯДОЧИТЬ ПО в произвольном запросе.

---

## Правило 6: Условное оформление — программная настройка

```bsl
&НаСервере
Процедура УстановитьУсловноеОформление()

    УсловноеОформление.Элементы.Очистить();

    ЭлементУО = УсловноеОформление.Элементы.Добавить();

    ЭлементОтбора = ЭлементУО.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ДатаОплаты");
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Меньше;
    ЭлементОтбора.ПравоеЗначение = ТекущаяДатаСеанса();
    ЭлементОтбора.Использование = Истина;

    ЭлементОтбора2 = ЭлементУО.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора2.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Оплачен");
    ЭлементОтбора2.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтбора2.ПравоеЗначение = Ложь;
    ЭлементОтбора2.Использование = Истина;

    ЭлементУО.Оформление.УстановитьЗначениеПараметра("ЦветФона", WebЦвета.МисттиРоуз);

    ПолеОформления = ЭлементУО.Поля.Элементы.Добавить();
    ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("СписокДокументов");

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

---

## Правило 7: Асинхронные диалоги вместо модальных

Модальные вызовы (`Предупреждение()`, `Вопрос()`) **запрещены** в веб-клиенте. Используйте `ОписаниеОповещения`.

Стандарт ИТС: «Ограничения на использование модальных методов».

```bsl
&НаКлиенте
Процедура УдалитьСтрокуТоваров(Команда)
    Если Элементы.Товары.ТекущаяСтрока = Неопределено Тогда
        Возврат;
    КонецЕсли;

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

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

---

## Правило 8: Открытие форм и обработка результата

```bsl
&НаКлиенте
Процедура ОткрытьФормуПодбораТоваров(Команда)

    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("Организация", Объект.Организация);
    ПараметрыФормы.Вставить("Склад", Объект.Склад);
    ПараметрыФормы.Вставить("Дата", Объект.Дата);
    ПараметрыФормы.Вставить("МножественныйВыбор", Истина);

    ОткрытьФорму("Обработка.ПодборТоваров.Форма",
        ПараметрыФормы,
        ЭтотОбъект,
        ,,,
        Новый ОписаниеОповещения("ПослеПодбораТоваров", ЭтотОбъект));

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

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

    Для Каждого ДанныеТовара Из РезультатПодбора Цикл
        НоваяСтрока = Объект.Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, ДанныеТовара);
    КонецЦикла;

    Модифицированность = Истина;
КонецПроцедуры
```

---

## Правило 9: Управление видимостью — группировать изменения

Все изменения видимости/доступности — в одном серверном вызове, чтобы избежать «дёрганья» интерфейса.

```bsl
&НаСервере
Процедура УправлениеВидимостью()
    ЭтоУслуга = Объект.ВидОперации = Перечисления.ВидыОпераций.Услуга;

    Элементы.Склад.Видимость = НЕ ЭтоУслуга;
    Элементы.ТоварыКоличество.Видимость = НЕ ЭтоУслуга;
    Элементы.ТоварыЕдиницаИзмерения.Видимость = НЕ ЭтоУслуга;
    Элементы.ГруппаДоставка.Видимость = НЕ ЭтоУслуга;

    Элементы.Контрагент.ТолькоПросмотр = Объект.Проведен;
КонецПроцедуры

&НаКлиенте
Процедура ВидОперацииПриИзменении(Элемент)
    УправлениеВидимостью(); // Один серверный вызов для всех изменений
КонецПроцедуры
```

---

## Правило 10: Работа с табличными частями — пересчёт на клиенте если возможно

```bsl
// Изменение количества — пересчёт суммы на клиенте
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    ТекущаяСтрока.Сумма = РассчитатьСумму(
        ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры

// Изменение номенклатуры — нужен сервер для получения данных
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    ДанныеНоменклатуры = ПолучитьДанныеНоменклатуры(ТекущаяСтрока.Номенклатура);

    ТекущаяСтрока.ЕдиницаИзмерения = ДанныеНоменклатуры.ЕдиницаИзмерения;
    ТекущаяСтрока.Цена = ДанныеНоменклатуры.Цена;
    ТекущаяСтрока.СтавкаНДС = ДанныеНоменклатуры.СтавкаНДС;
    ТекущаяСтрока.Сумма = РассчитатьСумму(
        ТекущаяСтрока.Количество, ТекущаяСтрока.Цена, ТекущаяСтрока.СтавкаНДС);
КонецПроцедуры
```

---

## Правило 11: Оповещения между формами

```bsl
// Форма документа — после записи
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
    Оповестить("Запись_РеализацияТоваровУслуг",
        Новый Структура("Ссылка, Проведен", Объект.Ссылка, Объект.Проведен),
        ЭтотОбъект);
КонецПроцедуры

// Форма списка — обработка оповещения
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    Если ИмяСобытия = "Запись_РеализацияТоваровУслуг" Тогда
        Элементы.Список.Обновить();
    КонецЕсли;
КонецПроцедуры
```

---

## Правило 12: Передаваемые типы данных между клиентом и сервером

### Типы, передаваемые свободно

Примитивные (Строка, Число, Дата, Булево), Ссылки, Перечисления, Структура, Соответствие, Массив, ФиксированныеКоллекции, ХранилищеЗначения.

### Типы, НЕ передаваемые (серверные)

| Тип | Альтернатива |
|-----|-------------|
| ТаблицаЗначений | ДанныеФормыКоллекция (через реквизиты формы) |
| ДеревоЗначений | ДанныеФормыДерево (через реквизиты формы) |
| ОбъектМетаданных | Передавать ИмяМетаданных (строку) |
| Запрос, РезультатЗапроса | Передавать результат (структура/массив) |

---
depends_on: []
---

More from SteelMorgan/1c-agent-based-dev-framework

SkillDescription
1c-ai-agent-cliCLI 1C BSL Agent Framework — tools/install.py (clone, install). Используй при клонировании репозитория, установке компонентов в проект, настройке IDE (Cursor, Claude Code, Windsurf, VS Code+Continue).
agent-debugПаттерн отладочных сообщений для 1С BSL. Используй, когда стандартная диагностика (event-log, скриншоты) не даёт понять фактическое поведение системы — нужно вставить временные точки логирования в код, запустить тест и проанализировать записи ЖР.
agent-developmentCreate custom subagents for specialized AI tasks. Use when the user wants to create a new type of subagent, set up task-specific agents, configure code reviewers, debuggers, or domain-specific assistants with custom prompts.
agent-development-ext>
agent-git-workflowStandardizes git workflow for the AI agent in the sandbox devcontainer: work in agent/<task>-<yyyymmdd>, integrate via agent, never push to main/master, open PRs via GitHub CLI. Use when the user asks to create branches, push changes, open PRs, or follow this sandbox repo setup.
auto-skill-bootstrapDeterministic helper to inventory existing project skills, detect missing capability coverage, search skills.sh via Skills CLI, and (optionally) install missing skills under a trust policy. Uses skills-manifest.json + state.json to stay idempotent across changing requirements.
code-navigationНавигация по коду (Code Navigation). Навык учит агента **эффективно перемещаться по BSL-коду** с помощью LSP (Language Server Protocol).
config-operationsОперации с конфигурацией 1С (CF) — init, info, edit, validate. Используй при создании конфигурации, анализе структуры, изменении свойств и ChildObjects, валидации Configuration.xml.
epf-buildСобрать EPF/ERF из XML-исходников. Используй после внесения временных диагностических правок в разобранную обработку.
epf-dumpРазобрать EPF/ERF в XML-исходники. Используй, когда нужно быстро получить исходный код внешней обработки или отчета для анализа и временной модификации.