При разработке алгоритмов расчета вознаграждений или переноса настроек часто возникает задача: собрать данные из справочника и виртуальных таблиц оборотов, а затем записать полученные результаты в независимый регистр сведений. Однако на практике программисты иногда сталкиваются с ситуацией, когда измерение (например, ссылка на менеджера) успешно записывается в регистр, а ресурсы (сумма продаж, процент вознаграждения) остаются нулевыми или пустыми.
Рассмотрим подробно эту проблему, разберем исходный код обработки, проанализируем ошибки при работе с объектами записи и выясним, как правильно формировать запрос и заполнять набор записей.
Проанализируем ситуацию на конкретном примере. Требуется программно расчитать вознаграждение менеджеров за выбранный месяц на основании данных регистра накопления Продажи.Обороты и табличной части справочника Менеджеры.НастройкаВознаграждения, после чего записать итоговые значения в независимый регистр сведений НастройкаВознаграждения.
Посмотрим на исходный код серверной процедуры обработки:
&НаСервере
Процедура ОборотСуммаПриИзмененииНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Менеджеры.Ссылка КАК Ссылка,
| ЕСТЬNULL(ПродажиОбороты.СуммаОборот, 0) КАК СуммаОборот
| ПОМЕСТИТЬ ОборотыМенеджеровВЗаданномПериоде
|ИЗ
| Справочник.Менеджеры КАК Менеджеры
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, Месяц, ) КАК ПродажиОбороты
| ПО (ПродажиОбороты.Менеджер = Менеджеры.Ссылка);
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЕСТЬNULL(ТабМенеджеры.ПроцентВознаграждения, 0) КАК ПроцентВознаграждения,
| ОборотыМенеджеровВЗаданномПериоде.Ссылка КАК Ссылка,
| ОборотыМенеджеровВЗаданномПериоде.СуммаОборот * ЕСТЬNULL(ТабМенеджеры.ПроцентВознаграждения,0) / 100 КАК Вознаграждение
|ПОМЕСТИТЬ НастройкиВознагражденияНижеСуммыОборота
|ИЗ
| ОборотыМенеджеровВЗаданномПериоде КАК ОборотыМенеджеровВЗаданномПериоде
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Менеджеры.НастройкаВознаграждения КАК ТабМенеджеры
| ПО (ОборотыМенеджеровВЗаданномПериоде.Ссылка = ТабМенеджеры.Ссылка
| И ОборотыМенеджеровВЗаданномПериоде.СуммаОборот >= ЕСТЬNULL(ТабМенеджеры.СуммаПродажЗаМесяц, 0));
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| НастройкиВознагражденияНижеСуммыОборота.Ссылка КАК Ссылка,
| МАКСИМУМ(НастройкиВознагражденияНижеСуммыОборота.ПроцентВознаграждения) КАК ПроцентВознаграждения,
| МАКСИМУМ(НастройкиВознагражденияНижеСуммыОборота.Вознаграждение) КАК Вознаграждение
|ИЗ
| НастройкиВознагражденияНижеСуммыОборота КАК НастройкиВознагражденияНижеСуммыОборота
|
|СГРУППИРОВАТЬ ПО
| НастройкиВознагражденияНижеСуммыОборота.Ссылка";
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(Объект.Период));
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Объект.Период));
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
НаборЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Менеджеры.Установить(ВыборкаДетальныеЗаписи.Ссылка);
НаборЗаписей.Отбор.Период.Установить(НачалоМесяца(Объект.Период));
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = НачалоМесяца(Объект.Период);
НоваяЗапись.Менеджеры = ВыборкаДетальныеЗаписи.Ссылка;
НоваяЗапись.СуммаПродаж = ВыборкаДетальныеЗаписи.Вознаграждение;
НоваяЗапись.ПроцентВознаграждения = ВыборкаДетальныеЗаписи.ПроцентВознаграждения;
НаборЗаписей.Записать();
КонецЦикла;
КонецПроцедуры
В результате работы данного кода в регистре сведений создаются записи с заполненными менеджерами, однако ресурсы СуммаПродаж и ПроцентВознаграждения остаются равными нулю.
Проанализируем логику выполнения запроса и найдем две ключевые причины возникновения данной проблемы:
Продажи.Обороты за период между НачалоПериода и КонецПериода нет записей по конкретному менеджеру, значение поля СуммаОборот становится равным 0.
ПО (ОборотыМенеджеровВЗаданномПериоде.Ссылка = ТабМенеджеры.Ссылка
И ОборотыМенеджеровВЗаданномПериоде.СуммаОборот >= ЕСТЬNULL(ТабМенеджеры.СуммаПродажЗаМесяц, 0))
СуммаОборот < СуммаПродажЗаМесяц), левое соединение возвращает NULL. Из-за использования ЕСТЬNULL(ТабМенеджеры.ПроцентВознаграждения, 0) итоговое вознаграждение рассчитывается как 0 * 0 / 100 = 0. Запрос честно возвращает менеджера с нулевыми показателями вознаграждения, которые затем записываются в регистр.
В процессе поиска решения разработчики часто пытаются заменить СоздатьНаборЗаписей() на СоздатьМенеджерЗаписей():
МенеджерЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьМенеджерЗаписей();
Если вы получаете исключение Метод объекта не обнаружен (СоздатьМенеджерЗаписей), это указывает на одну из следующих причин:
МенеджерЗаписи, внесение изменений возможно исключительно через документы или объект НаборЗаписей.Создавать и записывать НаборЗаписей на каждой итерации цикла — некорректно с точки зрения производительности, так как это вызывает лишние обращения к СУБД и отдельную транзакцию на каждого менеджера.
Рассмотрим более эффективный и правильный вариант с заполнением набора записей напрямую из результата запроса без использования цикла:
&НаСервере
Процедура ЗаписатьВознагражденияНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| &Период КАК Период,
| НастройкиВознаграждения.Ссылка КАК Менеджеры,
| МАКСИМУМ(НастройкиВознаграждения.ПроцентВознаграждения) КАК ПроцентВознаграждения,
| МАКСИМУМ(НастройкиВознаграждения.Вознаграждение) КАК СуммаПродаж
|ИЗ
| (...) КАК НастройкиВознаграждения
|ГДЕ
| НастройкиВознаграждения.Вознаграждение > 0
|СГРУППИРОВАТЬ ПО
| НастройкиВознаграждения.Ссылка";
Запрос.УстановитьПараметр("Период", НачалоМесяца(Объект.Период));
Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Объект.Период));
Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(Объект.Период));
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Создаем единый набор записей на весь период
НаборЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(НачалоМесяца(Объект.Период));
// Загружаем готовые данные из таблицы значений
НаборЗаписей.Загрузить(ТаблицаРезультата);
// Записываем набор за одну транзакцию
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Разберем ключевые изменения:
Период, Менеджеры, ПроцентВознаграждения, СуммаПродаж) строго совпадают с именами измерений и ресурсов регистра сведений.ГДЕ НастройкиВознаграждения.Вознаграждение > 0, чтобы отсечь менеджеров, не выполнивших план продаж.НаборЗаписей.Загрузить(), что позволяет избежать циклов и существенно ускоряет выполнение обработки.