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