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