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

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

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

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

Разбор проблемы: Почему возникает ошибка уникальности?

Проанализируем ситуацию: разработчик пытается из документа РеализацияТоваровУслуг записать данные в регистр сведений. При этом он использует объект НаборЗаписей. Ошибка возникает из-за непонимания того, что является «ключом» записи в подчиненном регистре.

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

  1. Регистратор (ссылка на документ).
  2. НомерСтроки (целое число).

Если вы создаете набор записей программно и добавляете в него строки, не заполняя поле НомерСтроки, платформа по умолчанию присваивает всем строкам значение «0». Как только вы пытаетесь добавить вторую строку в этот же набор с тем же номером строки, система выдает ошибку, так как два объекта с ключом «Регистратор + 0» существовать не могут.

Решение №1: Правильное использование НабораЗаписей (ручной метод)

Рассмотрим правильный алгоритм работы с объектом РегистрСведенийНаборЗаписей. Основная ошибка, которую мы часто видим в коде — это создание и запись набора внутри цикла перебора строк табличной части. Это не только приводит к ошибкам уникальности, но и катастрофически замедляет работу системы, так как на каждой итерации происходит обращение к базе данных.

Сделаем правильно: вынесем создание набора за пределы цикла и обеспечим уникальность номеров строк. Разберем пример кода:


// 1. Создаем набор записей один раз до начала цикла
НаборЗаписей = РегистрыСведений.АкцияСкидки3.СоздатьНаборЗаписей();

// 2. Устанавливаем отбор по текущему документу (регистратору)
НаборЗаписей.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка);

// Счетчик для обеспечения уникальности записей в наборе
Счетчик = 0;

// 3. Перебираем строки табличной части документа
Для Каждого СтрокаТЧ Из Товары Цикл
    
    // Проверяем условие, нужно ли делать запись для данной строки
    Если СтрокаТЧ.Скидка = Истина Тогда
        
        Счетчик = Счетчик + 1;
        
        // Добавляем новую запись в набор
        НоваяЗапись = НаборЗаписей.Добавить();
        
        // Заполняем стандартные реквизиты
        НоваяЗапись.Период = ЭтотОбъект.Дата;
        НоваяЗапись.Регистратор = ЭтотОбъект.Ссылка;
        НоваяЗапись.НомерСтроки = Счетчик; // ОБЯЗАТЕЛЬНО для уникальности
        
        // Заполняем измерения и ресурсы
        НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
        НоваяЗапись.Контрагент = ЭтотОбъект.Контрагент;
        НоваяЗапись.ПроцентСкидки = СтрокаТЧ.ПроцентСкидки;
        
    КонецЕсли;
    
КонецЦикла;

// 4. Записываем весь набор целиком ОДНИМ вызовом
// Параметр Истина означает замещение старых записей этого регистратора новыми
НаборЗаписей.Записать(Истина);

Проанализируем важные моменты в этом коде:

Решение №2: Использование коллекции «Движения» (Рекомендуемый метод)

Для документов в системе 1С:Предприятие существует штатный механизм работы с записями регистров — коллекция Движения. Использование этого метода считается «золотым стандартом», так как платформа берет на себя часть работы по управлению транзакциями и отборами.

Выясним, как это реализовать в процедуре ОбработкаПроведения:


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

Преимущества этого метода: вам не нужно вручную создавать объект НаборЗаписей и устанавливать отбор по регистратору. Платформа понимает, что Движения.ИмяРегистра — это и есть набор записей, жестко связанный с текущим документом.

Технические нюансы и «подводные камни»

Рассмотрим подробнее несколько важных аспектов, которые часто упускают из виду:

1. Место вызова кода. Программисты часто пытаются записывать движения в процедуре ПриЗаписи. Это плохая практика для регистров, подчиненных регистратору. Если запись произойдет в ПриЗаписи, а затем проведение документа сорвется (например, из-за нехватки остатков), записи в регистре могут остаться («сиротские записи»), если не прописана логика их удаления. Всегда используйте ОбработкаПроведения для формирования движений.

2. Периодичность. Если ваш регистр имеет периодичность (например, «В пределах секунды»), то поле Период должно быть заполнено. Для подчиненных регистров Период почти всегда совпадает с Дата документа. Если вы попытаетесь записать дату, отличную от даты документа, в некоторых конфигурациях или версиях платформы это может вызвать конфликт логики или ошибку прав доступа.

3. Метод Прочитать(). В некоторых сообщениях форума предлагается использовать НаборЗаписей.Прочитать(). Рассмотрим, нужно ли это. Если вы используете Записать(Истина), то предварительное чтение не требуется — платформа просто перезапишет данные. Прочитать() полезен только тогда, когда вы хотите дополнить уже существующие записи документа в регистре, не удаляя старые.

4. НомерСтроки в запросах. Если вы планируете в дальнейшем анализировать данные регистра, помните, что НомерСтроки в регистре сведений не обязательно совпадает с номером строки в табличной части документа (так как в регистр могут попасть не все строки). Если вам нужна жесткая связь, добавьте в регистр дополнительный реквизит КлючСвязи.

Резюме для разработчика

Чтобы успешно работать с подчиненным периодическим регистром сведений, придерживайтесь следующих правил:

  1. Никогда не вызывайте метод Записать() внутри цикла перебора строк.
  2. Всегда обеспечивайте уникальность записей внутри набора. Самый простой способ — инкрементальное заполнение поля НомерСтроки.
  3. Используйте штатный механизм Движения.ИмяРегистра.Добавить() в процедуре ОбработкаПроведения. Это наиболее безопасный и производительный путь.
  4. Помните, что ключевые поля (измерения + период + регистратор + номер строки) должны быть уникальными. Если вам нужно записать несколько разных значений для одной номенклатуры в рамках одного документа, убедитесь, что у них разные номера строк или разные измерения.

Соблюдение этих простых принципов позволит вам создавать стабильные и быстрые механизмы учета в системе 1С:Предприятие.

← На главную