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