При разработке конфигураций в среде 1С:Предприятие часто возникает задача автоматизации ввода данных. Классический пример — автоматическое заполнение реквизита Курс в зависимости от выбранного пользователем Семестра в табличной части документа (например, в учебном плане). Автоматизация таких действий ускоряет работу пользователей и минимизирует вероятность возникновения ошибок ручного ввода.
В этой статье мы подробно разберем, как правильно реализовать данный функционал в управляемом приложении 1С v8.3. Мы рассмотрим несколько подходов: от простого клиент-серверного алгоритма до изящного решения математическим путем, а также проанализируем, как спроектировать эту связь на уровне архитектуры базы данных в соответствии с лучшими практиками разработки.
Представим, что у нас есть документ «Учебный план» с табличной частью «Дисциплины». В этой табличной части присутствуют две колонки: «Семестр» и «Курс». Нам необходимо сделать так, чтобы при интерактивном изменении значения в поле «Семестр» программа мгновенно рассчитывала и подставляла соответствующее значение в поле «Курс» по следующему правилу:
Для реализации этой задачи нам потребуется использовать механизм событий формы. Конкретно нас интересует событие ПриИзменении поля ввода семестра на управляемой форме.
---
Рассмотрим ситуацию, когда и «Семестры», и «Курсы» созданы в конфигураторе как объекты типа ПеречислениеСсылка. Это стандартный учебный сценарий. Для реализации автозаполнения нам необходимо связать элемент формы с программным кодом.
Проанализируем по шагам процесс создания обработчика события:
Дисциплины) и выберем поле ввода ДисциплиныСеместр.ПриИзменении.Разберем, почему мы выбираем именно бесконтекстный серверный вызов. При использовании директивы &НаСервереБезКонтекста на сервер передаются только явно указанные параметры, а не вся форма целиком. Это значительно снижает объем трафика между клиентом и сервером и ускоряет работу интерфейса.
Посмотрим на пример правильного кода, который решает поставленную задачу:
&НаСервереБезКонтекста
Функция ПолучитьКурсПоСеместру(Знач Семестр)
// Проверяем, заполнено ли переданное значение семестра
Если Не ЗначениеЗаполнено(Семестр) Тогда
Возврат ПредопределенноеЗначение("Перечисление.Курсы.ПустаяСсылка");
КонецЕсли;
КурсСеместра = ПредопределенноеЗначение("Перечисление.Курсы.ПустаяСсылка");
// Сопоставляем семестры и курсы через логические ветвления
Если Семестр = Перечисления.Семестры.ПервыйСеместр
ИЛИ Семестр = Перечисления.Семестры.ВторойСеместр Тогда
КурсСеместра = Перечисления.Курсы.ПервыйКурс;
ИначеЕсли Семестр = Перечисления.Семестры.ТретийСеместр
ИЛИ Семестр = Перечисления.Семестры.ЧетвертыйСеместр Тогда
КурсСеместра = Перечисления.Курсы.ВторойКурс;
ИначеЕсли Семестр = Перечисления.Семестры.ПятыйСеместр
ИЛИ Семестр = Перечисления.Семестры.ШестойСеместр Тогда
КурсСеместра = Перечисления.Курсы.ТретийКурс;
ИначеЕсли Семестр = Перечисления.Семестры.СедьмойСеместр
ИЛИ Семестр = Перечисления.Семестры.ВосьмойСеместр Тогда
КурсСеместра = Перечисления.Курсы.ЧетвертыйКурс;
КонецЕсли;
Возврат КурсСеместра;
КонецФункции
&НаКлиенте
Процедура ДисциплиныСеместрПриИзменении(Элемент)
// Получаем текущую строку табличной части
СтрокаТаблицы = Элементы.Дисциплины.ТекущиеДанные;
// Обязательно проверяем, существует ли строка, чтобы избежать ошибок при случайном вызове
Если СтрокаТаблицы = Неопределено Тогда
Возврат;
КонецЕсли;
// Передаем значение семестра на сервер и записываем результат в колонку Курс
СтрокаТаблицы.Курс = ПолучитьКурсПоСеместру(СтрокаТаблицы.Семестр);
КонецПроцедуры
Важная деталь: Обратите внимание на проверку СтрокаТаблицы = Неопределено. Если пользователь попытается вызвать обработчик события в пустой таблице (что маловероятно, но технически возможно), программа не аварийно завершится с ошибкой, а корректно прекратит выполнение процедуры.
---
Написание длинных конструкций Если... ИначеЕсли для каждого семестра — это не самый эффективный способ кодирования. Что делать, если в учебном заведении планируется внедрить 12, 14 или более семестров? Код придется постоянно дописывать вручную.
Проанализируем математическую закономерность. Каждый курс состоит ровно из двух семестров. В платформе 1С значения перечисления имеют свой порядковый индекс, получить который можно с помощью метода менеджера перечисления Индекс(). Индексация начинается с 0:
Цел(0/2) = 0, Цел(1/2) = 0. Это соответствует первому элементу перечисления Курсы (индекс 0 — ПервыйКурс).Цел(2/2) = 1, Цел(3/2) = 1. Это соответствует второму элементу перечисления Курсы (индекс 1 — ВторойКурс).Используя эту логику, мы можем написать универсальный код, который будет корректно работать для любого количества семестров без единого условия ИначеЕсли:
&НаСервереБезКонтекста
Функция ПолучитьКурсПоСеместруОптимизированный(Знач Семестр)
Если Не ЗначениеЗаполнено(Семестр) Тогда
Возврат ПредопределенноеЗначение("Перечисление.Курсы.ПустаяСсылка");
КонецЕсли;
// Получаем индекс текущего семестра в списке перечисления
ИндексСеместра = Перечисления.Семестры.Индекс(Семестр);
// Вычисляем индекс курса делением на 2 с отсечением дробной части
ИндексКурса = Цел(ИндексСеместра / 2);
// Проверяем, существует ли курс с полученным индексом в системе
Если ИндексКурса < Перечисления.Курсы.Количество() Тогда
Возврат Перечисления.Курсы.Получить(ИндексКурса);
Иначе
Возврат ПредопределенноеЗначение("Перечисление.Курсы.ПустаяСсылка");
КонецЕсли;
КонецФункции
Этот метод значительно сокращает объем кодовой базы и делает систему устойчивой к добавлению новых значений в перечисления «Семестры» и «Курсы» через конфигуратор.
---
Опытные разработчики 1С сразу отметят, что использование перечислений для хранения курсов и семестров — это не лучшее архитектурное решение. Перечисления являются статичными объектами метаданных. Пользователь в режиме «1С:Предприятие» не может самостоятельно добавить новое значение или изменить структуру подчиненности семестров курсам.
Выясним причину, почему правильнее использовать Справочники:
Рассмотрим, как правильно спроектировать эту структуру:
Курсы (элементы: «1 курс», «2 курс» и т.д.).Семестры (элементы: «1 семестр», «2 семестр» и т.д.).Семестры создадим реквизит Курс с типом связи СправочникСсылка.Курсы.В режиме пользователя для каждого семестра один раз заполняется его соответствие курсу. Теперь наш программный код автозаполнения в документе становится невероятно простым, надежным и полностью независимым от изменений структуры обучения:
&НаСервереБезКонтекста
Функция ПолучитьКурсИзСправочникаСеместров(Знач СеместрСсылка)
// Возвращаем реквизит "Курс" непосредственно из элемента справочника "Семестры"
Возврат СеместрСсылка.Курс;
КонецФункции
&НаКлиенте
Процедура ДисциплиныСеместрПриИзменении(Элемент)
СтрокаТаблицы = Элементы.Дисциплины.ТекущиеДанные;
Если СтрокаТаблицы <> Неопределено И ЗначениеЗаполнено(СтрокаТаблицы.Семестр) Тогда
// Получаем привязанный курс напрямую из справочника через серверный вызов
СтрокаТаблицы.Курс = ПолучитьКурсИзСправочникаСеместров(СтрокаТаблицы.Семестр);
КонецЕсли;
КонецПроцедуры
---
Давайте разберем основные ошибки, которые допускают новички при попытке решить подобную задачу (многие из них встречались в сообщениях на форуме):
Возврат Семестр; вместо переменной, содержащей рассчитанный курс. Внимательно следите за тем, что именно возвращает ваша функция на клиент. Возвращаемый тип данных должен строго соответствовать типу реквизита приемника.СтрокаТаблицы.Курс = ИмяПроцедуры(Параметр);, платформа выдаст ошибку компиляции. Для вычислений с возвратом значения всегда используйте конструкцию Функция ... КонецФункции.&НаСервере там, где можно применить &НаСервереБезКонтекста, сильно замедляет работу интерфейса. Старайтесь передавать только необходимые параметры в бесконтекстные функции.ЗначениеЗаполнено() это может привести к нелогичному поведению системы или ошибкам выполнения.Применяя данные подходы и учитывая правила проектирования архитектуры 1С, вы сможете писать понятный, быстрый и легко поддерживаемый программный код для автоматизации любых бизнес-процессов.