При настройке правил обмена в конфигурации «Конвертация данных 2.1» разработчики часто сталкиваются с ситуацией, когда в базе-источнике отсутствует справочник единиц измерения или упаковок, либо структура хранения данных кардинально отличается от базы-приемника (например, при переносе из нетиповых конфигураций в Управление торговлей 11). Попытка создать подчиненный элемент непосредственно в обработчике ПослеЗагрузки номенклатуры на стороне приемника обычно завершается системной ошибкой: объект-владелец еще не записан в базу данных, и внешняя ссылка отсутствует.
Разберем по шагам, почему возникает эта ошибка, какие архитектурные особенности конфигураций 1С следует учитывать и как реализовать корректный перенос данных несколькими способами.
Выясним причину ошибки, возникающей при вызове кода создания подчиненного справочника внутри обработчика ПослеЗагрузки правила конвертации объекта (ПКО) Номенклатура.
Когда отрабатывает событие ПослеЗагрузки, объект Номенклатура находится в памяти, реквизиты заполнены, но метод физической записи в базу данных еще не выполнился. Если в этот момент попытаться создать элемент подчиненного справочника и передать ему Объект.Ссылка, платформа 1С или СУБД вернет ошибку целостности данных, так как внешний ключ ссылается на еще не существующую запись в таблице.
Использование методов ПолучитьСсылкуНового() или УстановитьСсылкуНового() в данном контексте не решает проблему: эти методы лишь резервируют GUID в оперативной памяти сеанса, но физически запись в таблице базы данных не появляется до завершения транзакции записи владельца.
Попытка принудительно вызвать Объект.Записать() до создания подчиненного элемента также часто приводит к исключениям из-за прикладных проверок конфигурации-приемника (например, модули типовой конфигурации проверяют заполненность обязательных реквизитов ставки НДС, вида номенклатуры или самой базовой единицы). Чтобы подавить такой контроль при вынужденной записи, необходимо явно указывать свойство Объект.ОбменДанными.Загрузка = Истина, однако это все равно является обходным решением.
Проанализируем структуру базы-приемника на примере конфигурации Управление торговлей 11. В этой конфигурации классификатор единиц измерения (ОКЕИ) и справочник упаковок объединены в общую таблицу УпаковкиЕдиницыИзмерения.
Важно учитывать ключевое методическое правило: базовые единицы хранения номенклатуры хранятся без владельца (реквизит Владелец пустой). Реквизит Номенклатура.ЕдиницаИзмерения должен ссылаться именно на такой общий классификаторный элемент. Владелец (конкретная номенклатура или набор упаковок) заполняется только для упаковок (индивидуальных или типовых). Таким образом, если номенклатуре требуется назначить лишь базовую единицу хранения (например, «шт»), создавать подчиненную запись не требуется вовсе.
Если стоит задача сформировать подчиненную запись упаковок или индивидуальных единиц измерения через стандартный конвейер КД, оптимально использовать ПКО без источника, передавая ссылку владельца через параметр ИсходящиеДанные.
Разберем по шагам настройку правил обмена:
УпаковкиЕдиницыИзмерения. Поле «Источник» оставляем пустым, в поле «Приемник» указываем справочник УпаковкиЕдиницыИзмерения.Владелец. Источник оставляем пустым, взводим флаг Получить из входящих данных и отмечаем его как поле поиска.Номенклатура в обработчике ПослеВыгрузки программно инициируем выгрузку зависимого правила через метод ВыгрузитьПоПравилу.Посмотрим на пример кода в обработчике ПослеВыгрузки ПКО Номенклатура:
ИсходящиеДанные = Новый Структура("Владелец");
ИсходящиеДанные.Владелец = Источник.Ссылка;
ВыгрузитьПоПравилу(,, ИсходящиеДанные, , "УпаковкиЕдиницыИзмерения");
В ПКО УпаковкиЕдиницыИзмерения в обработчике ПослеЗагрузки заполняем реквизиты создаваемой упаковки по классификатору:
ЕдиницаШтука = Справочники.УпаковкиЕдиницыИзмерения.НайтиПоКоду("796");
Объект.ЕдиницаИзмерения = ЕдиницаШтука;
Объект.КоличествоУпаковок = 1;
Объект.МеждународноеСокращение = ЕдиницаШтука.МеждународноеСокращение;
Объект.НаименованиеПолное = ЕдиницаШтука.НаименованиеПолное;
Объект.Наименование = ЕдиницаШтука.Наименование;
Объект.ТипУпаковки = ЕдиницаШтука.ТипУпаковки;
После этого возвращаемся в ПКО Номенклатура и в обработчике ПослеЗагрузки связываем записанную номенклатуру с созданной единицей:
Объект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Товар;
Если НЕ ЗначениеЗаполнено(Объект.ЕдиницаИзмерения) Тогда
Единицы = Справочники.УпаковкиЕдиницыИзмерения.Выбрать(, Объект.Ссылка);
Если Единицы.Следующий() Тогда
Объект.ЕдиницаИзмерения = Единицы.Ссылка;
Объект.ЕдиницаДляОтчетов = Единицы.Ссылка;
Объект.КоэффициентЕдиницыДляОтчетов = 1;
КонецЕсли;
КонецЕсли;
Рассмотрим более простой и методически верный вариант для Управление торговлей 11, когда для номенклатуры требуется только базовая единица хранения (например, «штука»). Поскольку общесистемные единицы не подчинены номенклатуре, создавать подчиненный элемент не нужно.
В обработчике ПослеЗагрузки ПКО Номенклатура достаточно найти общесистемный элемент классификатора по коду и напрямую присвоить его реквизитам объекта:
БазоваяЕдиница = Справочники.УпаковкиЕдиницыИзмерения.НайтиПоКоду("796"); // Код штуки по ОКЕИ
Если ЗначениеЗаполнено(БазоваяЕдиница) Тогда
Объект.ЕдиницаИзмерения = БазоваяЕдиница;
Если НЕ ЗначениеЗаполнено(Объект.ЕдиницаДляОтчетов) Тогда
Объект.ЕдиницаДляОтчетов = БазоваяЕдиница;
Объект.КоэффициентЕдиницыДляОтчетов = 1;
КонецЕсли;
КонецЕсли;
Этот способ исключает необходимость настраивать вспомогательные ПКО, выгружать структуры через ВыгрузитьПоПравилу и выполнять лишние запросы к базе данных при загрузке.
Проанализируем ситуацию, когда необходимо массово сформировать подчиненные элементы в сложной структуре, избежав блокировок и ошибок поиска незаписанных владельцев. В этом случае применяется двухстадийная загрузка:
ПередЗагрузкойДанных инициализируем массив в таблице параметров:
Параметры.Вставить("СписокСозданнойНоменклатуры", Новый Массив);
Номенклатура в обработчике ПослеЗагрузки фиксируем ссылку на успешно загруженный объект:
Параметры.СписокСозданнойНоменклатуры.Добавить(Объект.Ссылка);
ПослеЗагрузкиВсехДанных обходим накопленную коллекцию. На этом этапе все элементы номенклатуры гарантированно записаны в информационную базу:
ЕдиницаШтука = Справочники.УпаковкиЕдиницыИзмерения.НайтиПоКоду("796");
Для Каждого СсылкаНоменклатура Из Параметры.СписокСозданнойНоменклатуры Цикл
// Проверяем, создана ли уже упаковка для данной номенклатуры
Выборка = Справочники.УпаковкиЕдиницыИзмерения.Выбрать(, СсылкаНоменклатура);
Если НЕ Выборка.Следующий() Тогда
НоваяУпаковка = Справочники.УпаковкиЕдиницыИзмерения.СоздатьЭлемент();
НоваяУпаковка.ОбменДанными.Загрузка = Истина;
НоваяУпаковка.Владелец = СсылкаНоменклатура;
НоваяУпаковка.ЕдиницаИзмерения = ЕдиницаШтука;
НоваяУпаковка.КоличествоУпаковок = 1;
НоваяУпаковка.Наименование = "Штука";
НоваяУпаковка.Записать();
КонецЕсли;
КонецЦикла;
Использование обработчика ПослеЗагрузкиВсехДанных обеспечивает надежность переноса, исключает ошибки незаписанных ссылок и снижает нагрузку на транзакции при обмене большими массивами номенклатуры.