Как перенести данные из справочника и регистра оборотов в независимый регистр сведений в 1С?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке алгоритмов расчета вознаграждений или переноса настроек часто возникает задача: собрать данные из справочника и виртуальных таблиц оборотов, а затем записать полученные результаты в независимый регистр сведений. Однако на практике программисты иногда сталкиваются с ситуацией, когда измерение (например, ссылка на менеджера) успешно записывается в регистр, а ресурсы (сумма продаж, процент вознаграждения) остаются нулевыми или пустыми.

Рассмотрим подробно эту проблему, разберем исходный код обработки, проанализируем ошибки при работе с объектами записи и выясним, как правильно формировать запрос и заполнять набор записей.

Проблема: данные записываются частично

Проанализируем ситуацию на конкретном примере. Требуется программно расчитать вознаграждение менеджеров за выбранный месяц на основании данных регистра накопления Продажи.Обороты и табличной части справочника Менеджеры.НастройкаВознаграждения, после чего записать итоговые значения в независимый регистр сведений НастройкаВознаграждения.

Посмотрим на исходный код серверной процедуры обработки:


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

    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(Объект.Период));
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Объект.Период));

    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();

    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл 
        НаборЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Менеджеры.Установить(ВыборкаДетальныеЗаписи.Ссылка); 
        НаборЗаписей.Отбор.Период.Установить(НачалоМесяца(Объект.Период));

        НоваяЗапись = НаборЗаписей.Добавить();
        НоваяЗапись.Период = НачалоМесяца(Объект.Период);
        НоваяЗапись.Менеджеры = ВыборкаДетальныеЗаписи.Ссылка; 
        НоваяЗапись.СуммаПродаж = ВыборкаДетальныеЗаписи.Вознаграждение; 
        НоваяЗапись.ПроцентВознаграждения = ВыборкаДетальныеЗаписи.ПроцентВознаграждения;

        НаборЗаписей.Записать(); 
    КонецЦикла; 
КонецПроцедуры

В результате работы данного кода в регистре сведений создаются записи с заполненными менеджерами, однако ресурсы СуммаПродаж и ПроцентВознаграждения остаются равными нулю.

Выясняем причины незаполнения ресурсов

Проанализируем логику выполнения запроса и найдем две ключевые причины возникновения данной проблемы:

  1. Отсутствие оборотов за указанный период: Если в регистре Продажи.Обороты за период между НачалоПериода и КонецПериода нет записей по конкретному менеджеру, значение поля СуммаОборот становится равным 0.
  2. Условие соединения в запросе: Во втором пакете запроса выполняется соединение с табличной частью настройки:
    
    ПО (ОборотыМенеджеровВЗаданномПериоде.Ссылка = ТабМенеджеры.Ссылка
        И ОборотыМенеджеровВЗаданномПериоде.СуммаОборот >= ЕСТЬNULL(ТабМенеджеры.СуммаПродажЗаМесяц, 0))
    
    Если менеджер не набрал минимальную сумму продаж (СуммаОборот < СуммаПродажЗаМесяц), левое соединение возвращает NULL. Из-за использования ЕСТЬNULL(ТабМенеджеры.ПроцентВознаграждения, 0) итоговое вознаграждение рассчитывается как 0 * 0 / 100 = 0. Запрос честно возвращает менеджера с нулевыми показателями вознаграждения, которые затем записываются в регистр.

Ошибка «Метод объекта не обнаружен (СоздатьМенеджерЗаписей)»

В процессе поиска решения разработчики часто пытаются заменить СоздатьНаборЗаписей() на СоздатьМенеджерЗаписей():


МенеджерЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьМенеджерЗаписей();

Если вы получаете исключение Метод объекта не обнаружен (СоздатьМенеджерЗаписей), это указывает на одну из следующих причин:

  1. Регистр сведений является **подчиненным регистратору** (в свойствах установлен режим записи «Подчинение регистратору»). У таких регистров физически нет объекта МенеджерЗаписи, внесение изменений возможно исключительно через документы или объект НаборЗаписей.
  2. Код выполняется в контексте, где данный метод недоступен, либо допущена опечатка в наименовании регистра сведений.

Оптимизация и правильная запись данных

Создавать и записывать НаборЗаписей на каждой итерации цикла — некорректно с точки зрения производительности, так как это вызывает лишние обращения к СУБД и отдельную транзакцию на каждого менеджера.

Рассмотрим более эффективный и правильный вариант с заполнением набора записей напрямую из результата запроса без использования цикла:


&НаСервере
Процедура ЗаписатьВознагражденияНаСервере()
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    &Период КАК Период,
        |    НастройкиВознаграждения.Ссылка КАК Менеджеры,
        |    МАКСИМУМ(НастройкиВознаграждения.ПроцентВознаграждения) КАК ПроцентВознаграждения,
        |    МАКСИМУМ(НастройкиВознаграждения.Вознаграждение) КАК СуммаПродаж
        |ИЗ
        |    (...) КАК НастройкиВознаграждения
        |ГДЕ
        |    НастройкиВознаграждения.Вознаграждение > 0
        |СГРУППИРОВАТЬ ПО
        |    НастройкиВознаграждения.Ссылка";

    Запрос.УстановитьПараметр("Период", НачалоМесяца(Объект.Период));
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(Объект.Период));
    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(Объект.Период));

    ТаблицаРезультата = Запрос.Выполнить().Выгрузить();

    // Создаем единый набор записей на весь период
    НаборЗаписей = РегистрыСведений.НастройкаВознаграждения.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Период.Установить(НачалоМесяца(Объект.Период));
    
    // Загружаем готовые данные из таблицы значений
    НаборЗаписей.Загрузить(ТаблицаРезультата);
    
    // Записываем набор за одну транзакцию
    НаборЗаписей.Записать(Истина);
КонецПроцедуры

Разберем ключевые изменения:

  1. Псевдонимы полей в итоговом пакете запроса (Период, Менеджеры, ПроцентВознаграждения, СуммаПродаж) строго совпадают с именами измерений и ресурсов регистра сведений.
  2. В запрос добавлено условие отбора ГДЕ НастройкиВознаграждения.Вознаграждение > 0, чтобы отсечь менеджеров, не выполнивших план продаж.
  3. Используется метод НаборЗаписей.Загрузить(), что позволяет избежать циклов и существенно ускоряет выполнение обработки.
← На главную