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