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

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

В практике разработчика 1С часто возникает задача: при проведении документа необходимо зафиксировать определенные данные в регистре сведений. Если регистр является подчиненным регистратору, это накладывает определенные правила на процесс написания кода. В данной статье мы подробно разберем, как правильно реализовать передачу данных из табличной части документа в регистр, избежав типичных ошибок, таких как отсутствие отбора по регистратору или дублирование записей.

Поймем разницу: Подчиненный vs Независимый регистр

Прежде чем переходить к коду, проанализируем структуру регистра. Если в свойствах регистра сведений параметр Режим записи установлен в значение Подчинение регистратору, это означает, что каждая запись в регистре жестко связана с документом. В таком случае система автоматически добавляет в регистр поле Регистратор.

Важно запомнить: для работы с подчиненным регистром мы не можем использовать МенеджерЗаписи. Попытка вызвать СоздатьМенеджерЗаписи() приведет к ошибке, так как менеджер предназначен только для независимых регистров, где запись осуществляется по набору ключевых измерений без привязки к документу. Для подчиненных регистров используется исключительно НаборЗаписей.

Разбираем метод использования НабораЗаписей

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

Шаг 1. Создание объекта набора записей. Нам необходимо создать программный объект, который будет представлять совокупность записей нашего регистра, относящихся к текущему документу.

Шаг 2. Установка отбора. Это критически важный момент. Платформа 1С требует, чтобы при работе с набором записей подчиненного регистра обязательно был установлен отбор по конкретному регистратору (ссылке на наш документ).

Шаг 3. Заполнение данных в цикле. Мы перебираем строки табличной части и добавляем их в набор, если они соответствуют условию.

Шаг 4. Запись набора. Финальный этап — сохранение данных в базу данных.

Посмотрим на пример правильной реализации:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Создаем набор записей для нашего регистра
    НаборЗаписей = РегистрыСведений.АкцииСкидки.СоздатьНаборЗаписей();
    
    // Устанавливаем отбор по текущему документу. 
    // "ЭтотОбъект.Ссылка" или просто "Ссылка" передает идентификатор текущего документа.
    НаборЗаписей.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка);
    
    // Перебираем товары из табличной части документа
    Для Каждого СтрокаТовара Из Товары Цикл
        
        // Проверяем условие: была ли скидка в этой строке
        Если СтрокаТовара.Скидка > 0 Тогда
            
            // Добавляем новую строку в набор записей регистра
            НоваяЗапись = НаборЗаписей.Добавить();
            
            // Заполняем обязательные поля
            НоваяЗапись.Регистратор = ЭтотОбъект.Ссылка;
            НоваяЗапись.Период      = ЭтотОбъект.Дата; // Так как регистр периодический
            
            // Заполняем измерения и ресурсы регистра из данных строки
            НоваяЗапись.Номенклатура = СтрокаТовара.Номенклатура;
            НоваяЗапись.ПроцентСкидки = СтрокаТовара.Скидка;
            НоваяЗапись.Контрагент    = ЭтотОбъект.Контрагент;
            
        КонецЕсли;
        
    КонецЦикла;
    
    // Записываем весь набор в базу данных. 
    // Параметр Истина означает, что старые записи этого документа в регистре будут замещены новыми.
    НаборЗаписей.Записалать(Истина);

КонецПроцедуры

Почему возникает ошибка «Поле объекта не обнаружено (Регистратор)»?

Если вы столкнулись с тем, что система не видит поле Регистратор в отборе, выясним причину. Обычно это происходит в двух случаях:

  1. Регистр сведений настроен как Независимый. Проверьте вкладку «Основные» в свойствах регистра в конфигураторе.
  2. Вы пытаетесь установить отбор не у объекта НаборЗаписей, а у другого объекта или в контексте, где поле недоступно.

Также обратите внимание на сообщение «Ошибка записи! Не установлен отбор по регистратору». Платформа 1С защищает целостность данных: вы не можете записать набор записей подчиненного регистра «в пустоту» или сразу для всех документов. Вы обязаны явно указать, к какому именно документу относятся эти записи через НаборЗаписей.Отбор.Регистратор.Установить(Ссылка).

Оптимизация: использование коллекции Движения

Рассмотрим более современный и правильный способ, рекомендуемый фирмой 1С. У каждого документа есть свойство Движения. Если вы в конфигураторе на вкладке «Движения» отметили ваш регистр сведений, то вам не нужно вручную создавать набор записей и устанавливать отбор.

Проанализируем, как упрощается код при использовании коллекции Движения:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Очищаем движения регистра перед записью новых (если это необходимо)
    Движения.АкцииСкидки.Записывать = Истина;
    Движения.АкцииСкидки.Очистить();
    
    Для Каждого СтрокаТовара Из Товары Цикл
        
        Если СтрокаТовара.Скидка > 0 Тогда
            
            // Платформа сама знает, что Регистратор = Ссылка на этот документ
            НоваяЗапись = Движения.АкцииСкидки.Добавить();
            
            НоваяЗапись.Период       = Дата;
            НоваяЗапись.Номенклатура = СтрокаТовара.Номенклатура;
            НоваяЗапись.ПроцентСкидки = СтрокаТовара.Скидка;
            
        КонецЕсли;
        
    КонецЦикла;
    
    // В конце процедуры записи будут сохранены автоматически, 
    // так как мы установили Движения.АкцииСкидки.Записывать = Истина;

КонецПроцедуры

Преимущества этого метода:

Проблема производительности: Запись внутри цикла

Разберем критическую ошибку, которую часто совершают начинающие разработчики: вызов метода Записать() внутри цикла Для Каждого. Проанализируем ситуацию: если в документе 100 строк со скидкой, и вы вызываете Записать() на каждой итерации, система сделает 100 обращений к базе данных. Это крайне неэффективно.

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

Как избежать ошибки «Запись с такими ключевыми полями существует»?

Если при перепроведении документа вы получаете ошибку дублирования записей, выясним причину. Это происходит из-за того, что вы используете метод НаборЗаписей.Записать(Ложь). Второй параметр (Замещать) в значении Ложь велит системе не удалять старые записи, а попытаться добавить новые «сверху».

Для документов правильным является использование Записать(Истина) или просто Записать() без параметров (так как Истина стоит по умолчанию). В этом случае 1С сначала удалит из регистра все записи, принадлежащие данному документу-регистратору, а затем запишет актуальные данные. Это гарантирует отсутствие «мусора» и дублей в регистре при изменении и перепроведении документа.

Особенности периодичности

Поскольку наш регистр периодический, поле Период является обязательным для заполнения. В подчиненных регистрах значение периода, как правило, должно совпадать с датой документа. Если вы не заполните НоваяЗапись.Период, система может подставить пустую дату, что приведет к логическим ошибкам в отчетах (например, скидка будет числиться «началом времен»). Всегда явно присваивайте Период = СсылкаДокумента.Дата или Период = Дата (если код в модуле объекта).

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

← На главную