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