Все, кто плотно занимались производственным учетом в конфигурации УПП, знают о вопросе разузлования продукции по спецификации. В случае использования сложных спецификаций, с использованием формул, узлов, время на разузлование тратится очень много. А необходимость в разузлованиях возникает довольно часто.
И в голову пришла мысль — почему бы не использовать кэширование полученной на выходе таблицы материалов, чтобы при последующих разузлованиях продукции использовать ее, а не производить заново весь расчет. Скорость разузлования предсказуемо вырастет на порядок и больше (в зависимости от сложности спецификаций)!
Но все оказалось очень непросто..
Очевидно, что для хранения и использования результата разузлования нужен регистр сведений.
В качестве измерений будут объект разузлования (Номенклатура, Характеристика) и спецификация.
Ресурс — таблица материалов, в которой будут храниться рассчитанные по спецификации материалы.
Однако выяснилось, что результат разузлования при расчете зависит еще и от параметров, таких как КоличествоУровнейРазузлования, ПараметрыВыпуска и т.д.
Соответственно нужно добавлять еще одно измерение. Было решено, что оптимальнее формировать уникальный строковый хэш параметров (модули функций формирования хэша содержатся в приложенной к статье конфигурации). В результате регистр выглядит так:
Регистр сведений КЭШ_Разузлование
Измерения:
— Номенклатура
— ХарактеристикаНоменклатуры
— Спецификация
— ХэшПараметров (тип Строка, 100)
Ресурсы:
— ТаблицаМатериалов (тип ХранилищеЗначения)
Т.е. в регистре хранится готовая таблица материалов для объекта разузлования по определенной спецификации и определенным параметрам расчета.
Перейдем к коду. Непосредственно разузлование продукции производится в УПП (1.2, 1.3) в общем модуле "РазузлованиеНоменклатуры" в функции "РазузловатьНоменклатуру"
Добавим в начале функции следующий код. Задача — получить хэш параметров разузлования. Причем из параметров необходимо исключить те параметры, которые не влияют на результат разузлования (например такой параметр как ДатаСпецификации). Также рекомендую добавить константу "ИспользоватьКэш", чтобы можно было регулировать использование данного механизма (пригодится при начальной отладке).
Функция РазузловатьНоменклатуру(Источник, Результат = Неопределено, Параметры = Неопределено) Экспорт
Если Константы.ИспользоватьКэш.Получить() Тогда
КопияПараметры = Новый Структура();
Для Каждого Параметр Из Параметры Цикл
// Исключаем параметры, не влияющие на результат разузлования
Если Параметр.Ключ = "ДатаСпецификации" Тогда
Продолжить;
КонецЕсли;
КопияПараметры.Вставить(Параметр.Ключ, Параметр.Значение);
КонецЦикла;
стрXML = СериализоватьОбъектXDTO(КопияПараметры);
ХэшПараметров= ХэшПараметров(стрXML);
КонецЕсли;
….
Функции СериализоватьОбъектXDTO и ХэшПараметров содержатся в конфигурации, прилагаемой к статье.
В процессе разработки выяснилось, что таблицу материалов (результат разузлования) необходимо формировать из расчета на 1 шт объекта разузлования (чтобы в дальнейшем при разузловании например 30 штук продукции, просто умножить каждое количество материала из исходной таблицы на 30).
Для этого было принято решение, если в данную функцию РазузловатьНоменклатуру передано количество продукции для разузлования не равное 1 (это определяется по параметру передаваемому в функцию Источник.Количество), выполнить вначале разузлование на 1 единицу и сохранить результат разузлования в наш регистр. Причем повторное разузлование не будет выполняться далее, т.к. эту же таблицу мы уже и сможем использовать (будет понятно ниже).
Т.е. добавляем к нашему коду следующее:
Если Источник.Количество <> 1 Тогда
УжеРазузловано = Ложь;
НаборЗаписей = РегистрыСведений.Кэш_Разузлование.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Источник.Номенклатура);
НаборЗаписей.Отбор.ХарактеристикаНоменклатуры.Установить(Источник.ХарактеристикаНоменклатуры);
НаборЗаписей.Отбор.Спецификация.Установить(Источник.Спецификация);
НаборЗаписей.Отбор.ХэшПараметров.Установить(ХэшПараметров);
НаборЗаписей.Прочитать();
УжеРазузловано = (НаборЗаписей.Количество() <> 0);
Если Не УжеРазузловано Тогда
// Создадим копии структур Источник и Параметры для расчета на 1 единицу
КопияИсточник= Новый Структура;
Для Каждого ЭлементСтруктуры Из Источник Цикл
КопияИсточник.Вставить(ЭлементСтруктуры.Ключ, ЭлементСтруктуры.Значение);
КонецЦикла;
КопияПараметры = Новый Структура;
Для Каждого ЭлементСтруктуры Из Параметры Цикл
КопияПараметры.Вставить(ЭлементСтруктуры.Ключ, ЭлементСтруктуры.Значение);
КонецЦикла;
// Вот здесь укажем что надо разузловать на 1 единицу объекта разузлования
КопияИсточник.Количество = 1;
// Выполним разузлование на 1 единицу (причем результат разузлования на 1 единицу
// будет записан в наш регистр в конце данной функции (это будет описано чуть ниже)
Рез = РазузловатьНоменклатуру(КопияИсточник, Результат, КопияПараметры);
КонецЕсли;
И ниже собственно производим считывание из нашего регистра таблицы материалов (из расчета на 1 шт) для данного объекта разузлования по данной спецификации для данного набора параметров. Если таблица есть, выполним пересчет количества материалов в ней на переданное количество и возвратим таблицу материалов как результат разузлования.
// Типовой код функции
ИнициализацияПараметров(Параметры);
ИнициализацияРезультатаРазузлования(Результат, Источник);
МассивОшибок = Новый Массив;
Уровень = 0;
// Типовой код функции
НаборЗаписей = РегистрыСведений.Кэш_Разузлование.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Источник.Номенклатура);
НаборЗаписей.Отбор.ХарактеристикаНоменклатуры.Установить(Источник.ХарактеристикаНоменклатуры);
НаборЗаписей.Отбор.Спецификация.Установить(Источник.Спецификация);
НаборЗаписей.Отбор.ХэшПараметров.Установить(ХэшПараметров);
НаборЗаписей.Прочитать();
// Если есть таблица материалов ее и вернем
Если НаборЗаписей.Количество() <> 0 Тогда
ЗаписьРегистра = НаборЗаписей[0];
ТаблицаРазузлования = ЗаписьРегистра.ТаблицаМатериалов.Получить();
// Пересчитаем количество материалов
Если Источник.Количество <> 1 Тогда
Для Каждого СтрМатериал Из ТаблицаРазузлования Цикл
СтрМатериал.Количество = СтрМатериал.Количество * Источник.Количество;
КонецЦикла;
КонецЕсли;
Результат.ИсходныеКомплектующие = ТаблицаРазузлования ;
Возврат МассивОшибок;
КонецЕсли;
Вот и все, осталось только в самом конце функции РазузловатьНоменклатуру (куда при данном алгоритме мы попадем только если разузлование еще не было выполнено) добавить код, записывающий в наш регистр сформированную таблицу материалов (на 1 шт).
……..
Если Константы.ИспользоватьКэш.Получить() И Источник.Количество = 1 И МассивОшибок.Количество() = 0 Тогда
Менеджер = РегистрыСведений.Кэш_Разузлование.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(Менеджер, Источник);
Менеджер.ТаблицаМатериалов = Новый ХранилищеЗначения(Результат.ИсходныеКомплектующие, Новый СжатиеДанных(9));
Менеджер.ХэшПараметров = ХэшПараметров ;
Менеджер.Записать();
КонецЕсли;
Возврат МассивОшибок;
КонецФункции
В результат функция РазузловатьНоменклатуру выглядит так:
Функция РазузловатьНоменклатуру(Источник, Результат = Неопределено, Параметры = Неопределено) Экспорт
Если Константы.ИспользоватьКэш.Получить() Тогда
КопияПараметры = Новый Структура();
Для Каждого Параметр Из Параметры Цикл
Если Параметр.Ключ = "ДатаСпецификации" Тогда
Продолжить;
КонецЕсли;
КопияПараметры.Вставить(Параметр.Ключ, Параметр.Значение);
КонецЦикла;
стрXML = СериализоватьОбъектXDTO(КопияПараметры);
ХэшПараметров = ХЭШ(стрXML);
// разузлуем на 1 шт, чтобы потом взять из кэша
Если Источник.Количество <> 1 Тогда
УжеРазузловано = Ложь;
НаборЗаписей = РегистрыСведений.Кэш_Разузлование.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Источник.Номенклатура);
НаборЗаписей.Отбор.ХарактеристикаНоменклатуры.Установить(Источник.ХарактеристикаНоменклатуры);
НаборЗаписей.Отбор.Спецификация.Установить(Источник.Спецификация);
НаборЗаписей.Отбор.ХэшПараметров.Установить(ХэшПараметров);
НаборЗаписей.Прочитать();
УжеРазузловано = (НаборЗаписей.Количество() <> 0);
Если Не УжеРазузловано Тогда
// Создадим копии структур Источник и Параметры для расчета на 1 единицу
КопияИсточник= Новый Структура;
Для Каждого ЭлементСтруктуры Из Источник Цикл
КопияИсточник.Вставить(ЭлементСтруктуры.Ключ, ЭлементСтруктуры.Значение);
КонецЦикла;
КопияПараметры = Новый Структура;
Для Каждого ЭлементСтруктуры Из Параметры Цикл
КопияПараметры.Вставить(ЭлементСтруктуры.Ключ, ЭлементСтруктуры.Значение);
КонецЦикла;
// Вот здесь укажем что надо разузловать на 1 единицу объекта разузлования
КопияИсточник.Количество = 1;
// Выполним разузлование на 1 единицу (причем результат разузлования на 1 единицу
// будет записан в наш регистр в конце данной функции (это будет описано чуть ниже)
Рез = РазузловатьНоменклатуру(КопияИсточник, Результат, КопияПараметры);
КонецЕсли;
КонецЕсли;
// Типовой код УПП
ИнициализацияПараметров(Параметры);
ИнициализацияРезультатаРазузлования(Результат, Источник);
МассивОшибок = Новый Массив;
Уровень = 0;
// Типовой код УПП
НаборЗаписей = РегистрыСведений.Кэш_Разузлование.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Номенклатура.Установить(Источник.Номенклатура);
НаборЗаписей.Отбор.ХарактеристикаНоменклатуры.Установить(Источник.ХарактеристикаНоменклатуры);
НаборЗаписей.Отбор.Спецификация.Установить(Источник.Спецификация);
НаборЗаписей.Отбор.ХэшПараметров.Установить(ХэшПараметров);
НаборЗаписей.Прочитать();
// Если есть таблица материалов ее и вернем
Если НаборЗаписей.Количество() <> 0 Тогда
ЗаписьРегистра = НаборЗаписей[0];
ТаблицаРазузлования = ЗаписьРегистра.ТаблицаМатериалов.Получить();
// Пересчитаем количество материалов
Если Источник.Количество <> 1 Тогда
Для Каждого СтрМатериал Из ТаблицаРазузлования Цикл
СтрМатериал.Количество = СтрМатериал.Количество * Источник.Количество;
КонецЦикла;
КонецЕсли;
Результат.ИсходныеКомплектующие = ТаблицаРазузлования ;
Возврат МассивОшибок;
КонецЕсли;
КонецЕсли;
ИнициализацияПараметров(Параметры);
ИнициализацияРезультатаРазузлования(Результат, Источник);
МассивОшибок = Новый Массив;
Уровень = 0;
// типовой код УПП..
// типовой код УПП..
// типовой код УПП..
// Запишем результат разузлования в регистр
Если Константы.ИспользоватьКэш.Получить() И Источник.Количество = 1 И МассивОшибок.Количество() = 0 Тогда
Менеджер = РегистрыСведений.Кэш_Разузлование.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(Менеджер, Источник);
Менеджер.ТаблицаМатериалов = Новый ХранилищеЗначения(Результат.ИсходныеКомплектующие, Новый СжатиеДанных(9));
Менеджер.ХэшПараметров = ХэшПараметров ;
Менеджер.Записать();
КонецЕсли;
Возврат МассивОшибок;
КонецФункции // ВыполнитьРазузлование()
Но это еще не все…
Результат разузлования может измениться в результате внесения изменений в спецификацию, в узел спецификации либо в свойство характеристики, которое используется в формулах расчета.
Поэтому крайне важно производить выборочную очистку нашего регистра в этих случаях.
Т.е. при изменении спецификации — очищаем все записи регистра по данной спецификации (выполняем в модуле справочника СпецификацииНоменклатуры).
При изменении узла спецификации — очищаем все записи регистра по данному узлу и по спецификации, в которой используется этот узел (выполняем в модуле справочника СпецификацииНоменклатуры).
При изменении свойства характеристики — очищаем все записи регистра по данной характеристике (выполняем в модуле набора записей регистра сведений ЗначенияСвойствОбъектов).
Это пожалуй самый "узкий" момент, т.к. ошибка здесь чревата получением некорректных данных из регистра. Но результат того стоит, поверьте))
Во вложенной обработке приведены примеры использующихся у нас механизмов очистки записей регистра для всех этих случаям.
На этом все. Согласен, что вариант решения небезупречный, скорее всего потребует доработки. Но надеюсь, что он окажется полезен кому-то.
Открыт для критики и предложений по усовершенствованию! Спасибо)
Не проще разузлование делать в несколько потоков, (ФоновыеЗадания использовать) ?