Сразу скажу, я не собирался этого делать. Собирался я выложить здесь некоторый отчетик за стартманей несколько, но немножко застрял на процедуре заполнения табличной части документа "Оказание услуг".
По всей видимости, это профессиональная деформация. Нет, в боевых условиях я легко прохожу мимо того, что меня не касается. Но сейчас торопиться совершенно некуда, поэтому пусть будет.
Итак. Есть штатная фича для заполнения указанной табличной части некими услугами, повторяющимися из месяца в месяц.
По кнопе на форме документа...
... управление передается в следующую процедуру модуля документа:
Процедура ЗаполнитьПоВидуВзаиморасчетов() Экспорт
ВалютаРегламентированногоУчета = Константы.ВалютаРегламентированногоУчета.Получить();
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ДатаСреза", Дата);
Запрос.УстановитьПараметр("Организация", Организация);
Запрос.УстановитьПараметр("ВидДоговора", Перечисления.ВидыДоговоровКонтрагентов.СПокупателем);
Запрос.УстановитьПараметр("ВидВзаиморасчетов", ВидВзаиморасчетов);
Запрос.УстановитьПараметр("ВалютаРегламентированногоУчета", ВалютаРегламентированногоУчета);
Запрос.Текст =
"ВЫБРАТЬ
| ДоговорыКонтрагентов.Владелец КАК Контрагент,
| ДоговорыКонтрагентов.Владелец.Наименование КАК НаименованиеКонтрагента,
| ДоговорыКонтрагентов.Ссылка КАК ДоговорКонтрагента,
| ДоговорыКонтрагентов.ТипЦен КАК ТипЦен,
| ДоговорыКонтрагентов.ТипЦен.ЦенаВключаетНДС КАК ЦенаВключаетНДС
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.ПометкаУдаления = ЛОЖЬ
| И ДоговорыКонтрагентов.ЭтоГруппа = ЛОЖЬ
| И ДоговорыКонтрагентов.Организация = &Организация
| И ДоговорыКонтрагентов.ВидДоговора = &ВидДоговора
| И ДоговорыКонтрагентов.ВидВзаиморасчетов = &ВидВзаиморасчетов
| И ДоговорыКонтрагентов.ВалютаВзаиморасчетов = &ВалютаРегламентированногоУчета
| И ДоговорыКонтрагентов.СрокДействия = ДАТАВРЕМЯ(1, 1, 1)
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ДоговорыКонтрагентов.Владелец,
| ДоговорыКонтрагентов.Владелец.Наименование,
| ДоговорыКонтрагентов.Ссылка,
| ДоговорыКонтрагентов.ТипЦен,
| ДоговорыКонтрагентов.ТипЦен.ЦенаВключаетНДС
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
|ГДЕ
| ДоговорыКонтрагентов.ПометкаУдаления = ЛОЖЬ
| И ДоговорыКонтрагентов.ЭтоГруппа = ЛОЖЬ
| И ДоговорыКонтрагентов.Организация = &Организация
| И ДоговорыКонтрагентов.ВидДоговора = &ВидДоговора
| И ДоговорыКонтрагентов.ВидВзаиморасчетов = &ВидВзаиморасчетов
| И ДоговорыКонтрагентов.ВалютаВзаиморасчетов = &ВалютаРегламентированногоУчета
| И НАЧАЛОПЕРИОДА(ДоговорыКонтрагентов.СрокДействия, МЕСЯЦ) >= НАЧАЛОПЕРИОДА(&ДатаСреза, МЕСЯЦ)
|
|УПОРЯДОЧИТЬ ПО
| НаименованиеКонтрагента";
ТаблицаДоговоров = Запрос.Выполнить().Выгрузить();
ЦеныНоменклатуры = Новый Соответствие;
Если ЗначениеЗаполнено(Номенклатура) Тогда
МассивТиповЦен = ОбщегоНазначения.ВыгрузитьКолонку(ТаблицаДоговоров, "ТипЦен", Истина);
Для Каждого ТипЦен Из МассивТиповЦен Цикл
Цена = 0;
Если ЗначениеЗаполнено(ТипЦен) Тогда
Цена = Ценообразование.ПолучитьЦенуНоменклатуры(Номенклатура,
ТипЦен, Дата, ВалютаРегламентированногоУчета, 1, 1);
КонецЕсли;
ЦеныНоменклатуры.Вставить(ТипЦен, Цена);
КонецЦикла;
КонецЕсли;
Для каждого СтрокаТаблицы Из ТаблицаДоговоров Цикл
НоваяСтрока = Контрагенты.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаТаблицы);
Документы.ОказаниеУслуг.ЗаполнитьСчетаУчетаВСтрокеТабличнойЧасти(ЭтотОбъект, НоваяСтрока, "Контрагенты");
НоваяСтрока.Количество = 1;
Если ЗначениеЗаполнено(СтрокаТаблицы.ТипЦен)
И ЦеныНоменклатуры.Получить(СтрокаТаблицы.ТипЦен) <> Неопределено Тогда
НоваяСтрока.Цена = УчетНДСКлиентСервер.ПересчитатьЦенуПриИзмененииФлаговНалогов(
ЦеныНоменклатуры.Получить(СтрокаТаблицы.ТипЦен),
СтрокаТаблицы.ЦенаВключаетНДС,
СуммаВключаетНДС,
УчетНДСВызовСервераПовтИсп.ПолучитьСтавкуНДС(СтавкаНДС));
ОбработкаТабличныхЧастейКлиентСервер.РассчитатьСуммуТабЧасти(НоваяСтрока);
НоваяСтрока.СуммаНДС = УчетНДСКлиентСервер.РассчитатьСуммуНДС(
НоваяСтрока.Сумма,
СуммаВключаетНДС,
УчетНДСВызовСервераПовтИсп.ПолучитьСтавкуНДС(СтавкаНДС));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Это БП версии 3.0.150.29, так, на всякий случай. А платформа у меня сейчас 8.3.23.2040.
Что надо отметить, так это две вещи. Во-первых, это не пример того, что надо всё бросить и оптимизировать. В сколько-нибудь нагруженных системах вклад этого куска кода исчезающе мал.
Во-вторых, это не вот какие неумехи программисты вендора, а я весь в белом. Это не так. Вообще, с практикой работы 1сником приходит понимание, что критика чужого кода - это самое последнее, чем ты можешь заняться, и то при условии, что хочешь произвести о себе самом не самое лучшее впечатление.
Так что, прошу рассматривать представленный кейс не как руководство к действию, но как частное неавторитетное мнение надменного и очень неприятного человека.
А так, с учетом вышеизложенного, имеем классику жанра: запрос и в цикле по результату - пост-обработку. Причем здесь - целых ДВА (!) подряд цикла.
Но, по порядочку.
В запрос передаются пять параметров., из них три - необходимы, они определены в вызывающей форме. Но два из них, ВидДоговора и ВалютаРегламентированногоУчета должны быть определены в теле запроса, а не через точку до него. Да, повторюсь, влияние на производительность ничтожно, можно сказать - "ловля блох". Но есть вещи, за которые глаз цепляется сам, если в твоей жизни было такое, что склад вставал и шел курить, поминая "эту вашу одинес". Два этих параметра здесь - это два обращения к базе данных, когда наш main запрос еще даже не начал формироваться.
Это было "А". "Б" у нас будут замечания не к производительности, но к бизнес-логике запроса.
Во-первых. Задействован реквизит справочника СрокДействия. Он должен быть либо не заполнен, либо быть больше, чем начало месяца даты заполняемого документа. Условие ИЛИ известно тем, что про него говорят "очень тормозит". Наверное, поэтому здесь решили по два раза выполнить запросы к таблице ДоговорыКонтрагентов в соединении с Контрагенты и ТипыЦен. Так, наверное, быстрее. Мы не будем прогонять это решение через тесты, просто сделаем по-своему.
Во-вторых. НЕ задействован реквизит Дата ("Дата договора"). То есть, если пользователь создаст карточку договора, который начнет действовать через полгода, то все эти полгода он будет вручную удалять строчки из ОказанияУслуг. Если, конечно, заметит эти левые строчки.
Мы сделаем так, что этот реквизит либо пустой, либо не больше, чем конец месяца даты заполняемого документа. Проверку условия СрокДействия>=Дата оставим на совести алгоритма записи карточки договора.
В результате творческого порыва, запрос получается таким:
ВЫБРАТЬ
| ДАТАВРЕМЯ(1, 1, 1) КАК ПустаяДата,
| НАЧАЛОПЕРИОДА(&ДатаСреза, МЕСЯЦ) КАК НачалоМесяца,
| КОНЕЦПЕРИОДА(&ДатаСреза, МЕСЯЦ) КАК КонецМесяца
|ПОМЕСТИТЬ Даты
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ДоговорыКонтрагентов.Ссылка КАК Ссылка,
| ДоговорыКонтрагентов.Владелец КАК Владелец,
| ДоговорыКонтрагентов.ТипЦен КАК ТипЦен,
| ДоговорыКонтрагентов.Дата КАК Дата,
| ДоговорыКонтрагентов.СрокДействия КАК СрокДействия
|ПОМЕСТИТЬ Договоры
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК ДоговорыКонтрагентов
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Константа.ВалютаРегламентированногоУчета КАК ВалютаРегламентированногоУчета
| ПО ДоговорыКонтрагентов.ВалютаВзаиморасчетов = ВалютаРегламентированногоУчета.Значение
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Даты КАК Д
| ПО (ДоговорыКонтрагентов.Дата = Д.ПустаяДата
| ИЛИ ДоговорыКонтрагентов.Дата <= Д.КонецМесяца)
| И (ДоговорыКонтрагентов.Дата = Д.ПустаяДата
| ИЛИ ДоговорыКонтрагентов.Дата >= Д.НачалоМесяца)
|ГДЕ
| НЕ ДоговорыКонтрагентов.ПометкаУдаления
| И ДоговорыКонтрагентов.Организация = &Организация
| И ДоговорыКонтрагентов.ВидДоговора = ЗНАЧЕНИЕ(Перечисление.ВидыДоговоровКонтрагентов.СПокупателем)
| И ДоговорыКонтрагентов.ВидВзаиморасчетов = &ВидВзаиморасчетов
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Договоры.Ссылка КАК ДоговорКонтрагента,
| Договоры.Владелец КАК Контрагент,
| Договоры.ТипЦен КАК ТипЦен,
| КА.Наименование КАК НаименованиеКонтрагента,
| ТЦ.ЦенаВключаетНДС КАК ЦенаВключаетНДС
|ИЗ
| Договоры КАК Договоры
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТипыЦенНоменклатуры КАК ТЦ
| ПО Договоры.ТипЦен = ТЦ.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК КА
| ПО Договоры.Владелец = КА.Ссылка
Как видите, я вставил сюда дважды "ИЛИ" в условие внутреннего соединения, дважды потому, что включил в бизнес-логику дату договора. Мой внутренний голос говорит, что так лучше. Где правда, покажет объективный контроль.
Но это еще не все.
Я исключил условие "НЕ ЭтоГруппа".
За свою практику мне ни одного раза не довелось встречаться с тем, чтобы пользователи использовали папки в справочнике ДоговорыКонтрагентов.
Но это не значит, что они не могут попробовать, вендор оставил им это:
Признаюсь, это я увидел в первый раз (как многому надо еще научиться!).
И, конечно, сразу же и создал папку...
.., и попробовал в нее перенести элемент...
... но у меня ничего не получилось. Полагаю, что не получится и у пользователей. Поэтому просто выкинул "ЭтоГруппа" из запроса.
Но и это еще не все!!!
В первом цикле обхода результата запроса (при заполненной номенклатуре) выполняется следующий код:
Функция ПолучитьЦенуНоменклатуры(Номенклатура, ТипЦен, Дата, Валюта = Неопределено, Курс = 0, Кратность = 1) Экспорт
ПолученнаяЦена = 0;
Если Не ПравоДоступа("Чтение", Метаданные.РегистрыСведений.ЦеныНоменклатуры) Тогда
Возврат ПолученнаяЦена;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Дата", Дата);
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);
Запрос.УстановитьПараметр("ТипЦен", ТипЦен);
Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
| ЦеныНоменклатуры.Цена,
| ЦеныНоменклатуры.Валюта
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура = &Номенклатура И ТипЦен = &ТипЦен) КАК ЦеныНоменклатуры";
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
ПолученнаяЦена = Выборка.Цена;
ВалютаЦены = Выборка.Валюта;
КонецЕсли;
Если НЕ (ВалютаЦены = Валюта) И НЕ (Валюта = Неопределено) И НЕ (ВалютаЦены = Неопределено) Тогда
СтруктураКурсаЦены = РаботаСКурсамиВалют.ПолучитьКурсВалюты(ВалютаЦены, Дата);
ПолученнаяЦена = РаботаСКурсамиВалютБПКлиентСервер.ПересчитатьИзВалютыВВалюту(
ПолученнаяЦена, ВалютаЦены, Валюта,
СтруктураКурсаЦены.Курс, Курс,
СтруктураКурсаЦены.Кратность, Кратность);
ПолученнаяЦена = ОкруглитьЦену(ПолученнаяЦена, ТипЦен);
ИначеЕсли Валюта = Неопределено Тогда
Валюта = ВалютаЦены;
КонецЕсли;
Возврат ПолученнаяЦена;
КонецФункции // ПолучитьЦенуНоменклатуры()
Поместим выход нашего запроса во временную таблицу и соединим ее с ценовым регистром. Валюта регламентированного учета у нас рубли (а у вас?), поэтому все эти ваши ПересчитатьИзВалютыВВалюту мы просто игнорируем.
Окончание нашего запроса теперь выглядит так:
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Договоры.Ссылка КАК ДоговорКонтрагента,
| Договоры.Владелец КАК Контрагент,
| Договоры.ТипЦен КАК ТипЦен,
| КА.Наименование КАК НаименованиеКонтрагента,
| ТЦ.ЦенаВключаетНДС КАК ЦенаВключаетНДС
|ПОМЕСТИТЬ ВыбранныеДоговоры
|ИЗ
| Договоры КАК Договоры
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ТипыЦенНоменклатуры КАК ТЦ
| ПО Договоры.ТипЦен = ТЦ.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК КА
| ПО Договоры.Владелец = КА.Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|УНИЧТОЖИТЬ Договоры
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВыбранныеДоговоры.ДоговорКонтрагента КАК ДоговорКонтрагента,
| ВыбранныеДоговоры.Контрагент КАК Контрагент,
| ВыбранныеДоговоры.ТипЦен КАК ТипЦен,
| ВыбранныеДоговоры.НаименованиеКонтрагента КАК НаименованиеКонтрагента,
| ВыбранныеДоговоры.ЦенаВключаетНДС КАК ЦенаВключаетНДС,
| isnull(ЦеныНоменклатуры.Цена, 0) КАК Цена
|ИЗ
| ВыбранныеДоговоры КАК ВыбранныеДоговоры
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ДатаСреза, Номенклатура = &Номенклатура) КАК ЦеныНоменклатуры
| ПО ВыбранныеДоговоры.ТипЦен = ЦеныНоменклатуры.ТипЦен
От первого цикла запросов мы избавились.
Что происходит во втором?
Во-первых, заполняются счета учета.
Во-вторых, если в договоре указан тип цен (и есть номенклатура), то цена, сумма и сумма НДС пересчитываются в зависимости от флага ЦенаВключаетНДС в типе цены.
Ну, вы уже поняли :))))
Если бы передо мной стояла задача добиться от этого куска кода высокой производительности, я бы поступил со вторым циклом точно так же, как с первым. А именно, перенес бы логику вызываемых процедур ЗаполнитьСчетаУчетаВСтрокеТабличнойЧасти, ПересчитатьЦенуПриИзмененииФлаговНалогов, РассчитатьСуммуТабЧасти и РассчитатьСуммуНДС в тело запроса, а потом загрузил бы результат в заполняемый документ.
На этом всё, благодарю за внимание. Если где-нибудь увидите ошибку, пинайте не слишком больно, пожалуйста. А если, на что я очень надеюсь, вы получили какую-то пользу от моего старания, ... ну, вы поняли :))))