Мы остановились на построении дерева цен.
В дерево добавляем следующие колонки:
- Номенклатура
- Характеристика
- ЕдиницаИзмерения
- ВидНоменклатуры
- ХарактеристикаЦО
- СерияЦО
- УпаковкаЦО
- ЦеноваяГруппа
В цикле по ВидЦены из ВыбранныеЦены добавляются колонки:
- Валюта
- СтараяЦена
- Формула
- ПроцентИзменения
- СуммаИзменения
- ИзмененаВручную
- ФормулаИзмененаВручную
- ИзмененаАвтоматически
- ЗапретРедактирования
- Упаковка (если ИспользоватьУпаковкиНоменклатуры)
И тут нежданная радость. Если тип дерева - из интерфеса, то исполняется та самая простыня кода, которая даже выглядит страшно.
Но мы - на сервере, и дерево у нас - просто дерево.
Поэтому мы копируем дерево с колонками в сформированную ранее структуру и возвращаемся. Ура!
***
Лирическое отступление - 2. Здесь, на ИС, нашелся мануал для пользователя. Особенно приятна заключительная фраза там:
Вот мы с Вами и рассмотрели все нюансы, подводные камни.
И пожелание удачного внедрения - пользователю.
Что действительно порадовало, можно ожидать единого механизма ценообразования в УТ и ERP. С учетом себестоимости продукции для ERP, разумеется.
***
Однако продолжим наши раскопки.
Мы вернулись с деревом цен из УстановкаЦенСервер.ПостроитьДеревоЦен
Теперь процедура УстановитьЦены вручает нам параметры и отправляет рассчитать цены (наконец-то!)
//	рассчитать цены
ПараметрыРасчета = Новый Структура();
ПараметрыРасчета.Вставить("ВидыЦен",				МассивВидовЦен);
ПараметрыРасчета.Вставить("ТолькоВыделенныеСтроки",	Ложь);
ПараметрыРасчета.Вставить("ТолькоНезаполненные",	Ложь);
ПараметрыРасчета.Вставить("ЗагрузкаСтарыхЦен",		Ложь);
ПараметрыРасчета.Вставить("ОкруглениеРучныхЦен",	Ложь);
ПараметрыРасчета.Вставить("РасчетПоФормулам",		Истина);
	
УстановкаЦенСервер.РассчитатьЦены(СтруктураФормы, ПараметрыРасчета);
Количества букв в этой процедуре не много, но, как и ожидалось, это не конечная глубина вложенности. Начиная с создания кеша (без проверки на Неопределено :) ).
// Рассчитывает цены в таблице цен
//
// Параметры:
//  Форма - см. ПостроитьДеревоЦен.Форма
//  ПараметрыРасчета - Структура - Параметры расчета
//  КэшДанных - см. ИнициализироватьСтруктуруКэшаДанных
//
Процедура РассчитатьЦены(Форма, ПараметрыРасчета, КэшДанных = Неопределено) Экспорт
	
	Форма.Модифицированность = Истина;
	
	КэшДанных = ИнициализироватьСтруктуруКэшаДанных(КэшДанных);
	
	МассивВидовЦен = Новый Массив;
	Для Каждого ВидЦены Из ПараметрыРасчета.ВидыЦен Цикл
		Если ТипЗнч(ВидЦены) = Тип("Структура") Тогда
			МассивВидовЦен.Добавить(УстановкаЦенКлиентСервер.НайтиСтрокуВидаЦен(Форма.ВыбранныеЦены, ВидЦены.ВидЦены));
		Иначе
			МассивВидовЦен.Добавить(УстановкаЦенКлиентСервер.НайтиСтрокуВидаЦен(Форма.ВыбранныеЦены, ВидЦены));
		КонецЕсли;
	КонецЦикла;
	
	Если Не ПараметрыРасчета.ТолькоВыделенныеСтроки Тогда
		
		ТаблицаНоменклатуры = СоздатьТаблицуНоменклатурыПоДеревуЦен(Форма, Ложь);
		
	Иначе
		
		ТаблицаНоменклатуры = СоздатьТаблицуНоменклатуры(Форма);
		Для Каждого ВыделеннаяСтрока Из Форма.Элементы.ДеревоЦен.ВыделенныеСтроки Цикл
			СтрокаТаблицыЦен           = Форма.ДеревоЦен.НайтиПоИдентификатору(ВыделеннаяСтрока);
			НоваяСтрока                = ТаблицаНоменклатуры.Добавить();
			ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТаблицыЦен);
			Если Форма.ИспользуетсяЦенообразование25 Тогда
				НоваяСтрока.УпаковкаЦОДляСвязи = ?(СтрокаТаблицыЦен.УпаковкаЦО = СтрокаТаблицыЦен.ЕдиницаИзмерения, Справочники.УпаковкиЕдиницыИзмерения.ПустаяСсылка(), СтрокаТаблицыЦен.УпаковкаЦО);
				НоваяСтрока.СерияЦОДляСвязи    = ?(СтрокаТаблицыЦен.СерияЦО.Предопределенный, Справочники.СерииНоменклатурыДляЦенообразования.ПустаяСсылка(), СтрокаТаблицыЦен.СерияЦО);
			КонецЕсли;
		КонецЦикла;
		
	КонецЕсли;
	
	Если ПараметрыРасчета.ЗагрузкаСтарыхЦен Тогда
		
		СтруктураПараметров = Новый Структура();
		СтруктураПараметров.Вставить("МассивСтрокВидовЦен",  МассивВидовЦен);
		СтруктураПараметров.Вставить("ДатаДокумента",        ПараметрыРасчета.ДатаСтарыхЦен);
		СтруктураПараметров.Вставить("ПроцентИзмененияЦены", ПараметрыРасчета.ПроцентИзмененияЦены);
		СтруктураПараметров.Вставить("ПрименятьОкругление",  ПараметрыРасчета.ПрименятьОкругление);
		СтруктураПараметров.Вставить("ТолькоНезаполненные",  ПараметрыРасчета.ТолькоНезаполненные);
	
		ЗагрузитьЗначенияБазовыхЦен(
			Форма,
			ТаблицаНоменклатуры,
			КэшДанных,
			СтруктураПараметров);
			
	КонецЕсли;
	
	Если ПараметрыРасчета.ОкруглениеРучныхЦен Тогда
		ПрименитьОкруглениеКРучнымЦенам(Форма, ТаблицаНоменклатуры, МассивВидовЦен, КэшДанных);
	КонецЕсли;
	
	Если ПараметрыРасчета.РасчетПоФормулам Тогда
		
		Если Не ПараметрыРасчета.ЗагрузкаСтарыхЦен Тогда 
			ЗагрузитьЗначенияБазовыхЦен(Форма, ТаблицаНоменклатуры, КэшДанных);
		КонецЕсли;
		ВычислитьЦеныПоДаннымИБ(Форма, ТаблицаНоменклатуры, МассивВидовЦен, ПараметрыРасчета.ТолькоНезаполненные, КэшДанных);
		
		РассчитатьВычисляемыеЦены(
			Форма,
			ТаблицаНоменклатуры,
			КэшДанных,
			?(Не ПараметрыРасчета.ЗагрузкаСтарыхЦен, МассивВидовЦен, Неопределено),
			ПараметрыРасчета.ТолькоНезаполненные,
			,
			ПараметрыРасчета.ВидыЦен);
		
	КонецЕсли;
	
	Если Форма.РассчитыватьАвтоматически Тогда
		// Список видов цен, которые, которые зависят от изменяемых
		ЗависимыеЦены = ПолучитьСтрокиНастроекЗависимыхВидовЦен(Форма, МассивВидовЦен);
	
		РассчитатьВычисляемыеЦены(
			Форма,
			ТаблицаНоменклатуры,
			КэшДанных,
			ЗависимыеЦены,
			ПараметрыРасчета.ТолькоНезаполненные);
			
	КонецЕсли;
		
	Если ТипЗнч(Форма.ДеревоЦен) = Тип("ДанныеФормыДерево") И ПараметрыРасчета.РасчетПоФормулам Тогда
		Для Каждого ВидЦеныДляОбработки Из МассивВидовЦен Цикл //ДанныеФормыЭлементКоллекции
			УстановитьПометкуИзмененныхФормул(Форма,,ВидЦеныДляОбработки.Ссылка);
		КонецЦикла;
	КонецЕсли;
КонецПроцедуры
Создается таблица с номенклатурой.
Подгружаются (считываются из базы) старые значения цен.
Загадочное "округление ручных цен" - даже знать не хочу, что это.
И - па-бам! - РасчетПоФормулам... две процедуры нам в стек
ВычислитьЦеныПоДаннымИБ(Форма, ТаблицаНоменклатуры, МассивВидовЦен, ПараметрыРасчета.ТолькоНезаполненные, КэшДанных);
		
РассчитатьВычисляемыеЦены(
			Форма,
			ТаблицаНоменклатуры,
			КэшДанных,
			?(Не ПараметрыРасчета.ЗагрузкаСтарыхЦен, МассивВидовЦен, Неопределено),
			ПараметрыРасчета.ТолькоНезаполненные,
			,
			ПараметрыРасчета.ВидыЦен);
ВычислитьЦены вызывается в скриптах 7 раз, 5 - из модуля УстановкаЦенСервер (в том числе из нашей точки), дважды - из формы документа УстановкаЦенНоменклатуры.
Приводить ее листинг не будем, поскольку в ней в цикле по виду цены вызывается ВычислитьЗначенияЦеныПоДаннымИБ2_5.
В последней получаем схему компоновки данных (определенную для вида цены), проверяем ее на вариант ФО_20 и(!) ФО_25, дописываем текст запроса схемы компоновки, добавляем поля и связи по флагам опций (реально функциональных), загружаем настройки, определяем группировки, параметры.
Устанавливаем отбор по сегменту Номенклатуры.
И, наконец, выполняем запрос и выводим в таблицу значений ДанныеОтчета.
Если я правильно помню всю пройденную цепочку, это вообще первый раз мы обратились к базе запросом. В цикле, ага. А как тут без цикла, если схема компоновки у каждого вида цены - своя. Но если она настолько "своя", то как мы сумели каждую из них обработать одним и тем же куском кода?
Терзают автора смутные подозрения.
Отдельно получаем КоэффициентыУпаковокНоменклатурыДереваТоваров.
И - да, да, вы правы - вызываем очередной уровень вложенности: ЗагрузитьЦеныИзТаблицыЗначений.
Оправдывая титул "надменного и неприятного", предложу полюбоваться изящностью выбора "наценивать-не-наценивать":
Если СтрокаВидЦены.СпособЗаданияЦены = Перечисления.СпособыЗаданияЦен.НаценкаНаЦенуПоступления 
Или СтрокаВидЦены.СпособЗаданияЦены = Перечисления.СпособыЗаданияЦен.НаценкаНаЦенуВводаОстатков
Или СтрокаВидЦены.СпособЗаданияЦены = Перечисления.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоКонкурентам 
Или СтрокаВидЦены.СпособЗаданияЦены = Перечисления.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоПоставщикам 
Или СтрокаВидЦены.СпособЗаданияЦены = Перечисления.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоСебестоимости Тогда
СтруктураПараметров.Вставить("Наценивать", Истина);
ЗагрузитьЦеныИзТаблицыЗначений(
	Форма,
	ДанныеОтчета,
	ТаблицаКоэффициентовУпаковокНоменклатуры,
	КэшДанных,
	СтрокаВидЦены,
	СтруктураПараметров);
Иначе
СтруктураПараметров.Вставить("Наценивать", Ложь);
ЗагрузитьЦеныИзТаблицыЗначений(Форма,
	ДанныеОтчета,
	ТаблицаКоэффициентовУпаковокНоменклатуры,
	КэшДанных,
	СтрокаВидЦены,
	СтруктураПараметров);
КонецЕсли;
Нет, никаких претензий, чисто эстетика, что вы! Короче, я бы этот кусок сделал так:
Запрос = Новый Запрос("Выбрать &СрокаСпособ В (Значение(Перечисление.СпособыЗаданияЦен.НаценкаНаЦенуПоступления),
|                                              Значение(Перечисление.СпособыЗаданияЦен.НаценкаНаЦенуВводаОстатков),
|                                              Значение(Перечисление.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоКонкурентам),
|                                              Значение(Перечисление.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоПоставщикам),
|                                              Значение(Перечисление.СпособыЗаданияЦен.ЗаполнятьПоДаннымИБПоСебестоимости))
| КАК  НацениватьНеНаценивать");
Запрос.УстановитьПараметр("СрокаСпособ",СтрокаВидЦены.СпособЗаданияЦены);
ВыборкаЗапроса = Запрос.Выполнить().Выбрать();
ВыборкаЗапроса.Следующий();
СтруктураПараметров.Вставить("Наценивать", ВыборкаЗапроса.НацениватьНеНаценивать);
	ЗагрузитьЦеныИзТаблицыЗначений(Форма,
		ДанныеОтчета,
		ТаблицаКоэффициентовУпаковокНоменклатуры,
		КэшДанных,
		СтрокаВидЦены,
		СтруктураПараметров);
Если кто-либо из критиков опять потребует замеров (см.комментарии), пусть сделает.
Только пусть сначала вынесет этот запрос из цикла по виду цены, а потом замеряет, сколько его душеньке будет угодно.
А нас ждет ЗагрузитьЦеныИзТаблицыЗначений. И это будет последний листинг в цепочке, которую автор сюда вытаскивает.
Нет, обойдемся без листинга, ничего, требующего особой внимательности там нет. Разве что вот это:
СтрокаТаблицыЦен = НайтиСтрокуДереваЦен(Форма, СтрокаИсточник, КэшДанных);
Для двух переданных параметров ищется вхождение СтрокаИсточник в дереве, входящем в форму.
Воздержусь от комментария, я же белый и приятный.
В общем, там пороги срабатывания, наценки, упаковки, валюта (опять же!).
Теперь рассмотрим РассчитатьВычисляемыеЦены
Там в цикле по Номенклатуре - цикл по ВидЦены, по "БазоваяЦена Из ВидЦены.ВлияющиеЦены":
ЗначениеЦены = ОбщегоНазначения.ВычислитьВБезопасномРежиме(Формула, ПараметрыДляВычисления);
... еще один вложенный уровень. Но там уже просто
Вычислить(Выражение);
**
Рассчитав цены, мы попадаем в попытку (и это здесь наш совсем-совсем последний шаг).
Документы.УстановкаЦенНоменклатуры.ЗаписатьИзмененияЦенНаСервере(СтруктураФормы, ТекстКомментария, 1);
Там в транзакции выполняется цикл проведения сохраненных в СтруктураФормы документов.
**
Итак, что мы увидели?
Во-первых, надо еще посмотреть, какие бывают формулы для Вычислить(Выражение).
Во-вторых, посмотреть, какие бывают схемы (шаблоны схем?) СКД. И почему их обрабатывают в цикле.
Без этого говорить о какой-либо оптимизации не приходится.
В-третьих, надо изобразить диаграмму исследованной нами здесь цепочки вложенных процедур. А то вот так, пробежавшись по тексту, в голове сложилось, конечно, общее представление о том, что происходит, но нужна картинка.
Итак, это план:
- формулы
- схемы СКД
- диаграмма с процедурами обновления цен
- и еще одна диаграмма с объектами конфигурации.
Вступайте в нашу телеграмм-группу Инфостарт
