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