Как исправить ошибку «Не установлен отбор по регистратору» при программном формировании движений в регистре накопления?

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

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

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

Выясняем причину: почему возникает ошибка, если отбор установлен в коде?

Проанализируем исходный код процедуры. На первый взгляд, отбор устанавливается корректно:


НаборЗаписей = РегистрыНакопления.ТоварыНаСкладах.СоздатьНаборЗаписей();      
НаборЗаписей.Отбор.Регистратор.Установить(ВыбранныйРегистраторСсылка);

Тем не менее платформа выдает ошибку при попытке выполнить метод НаборЗаписей.Записать(). Корень проблемы кроется не в самом коде, а в метаданных конфигурации 1С. В системе реализован жесткий контроль целостности данных: делать записи в регистр накопления могут только те документы, которые явно объявлены его регистраторами.

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

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

Избыточность заполнения реквизита «Регистратор» в строках набора

Обратим внимание на следующую строку в цикле заполнения движений:

НоваяЗапись.Регистратор = ВыбранныйРегистраторСсылка;

Эта строка является абсолютно лишней. Архитектура платформы 1С устроена так, что при установке отбора на набор записей по конкретному регистратору: НаборЗаписей.Отбор.Регистратор.Установить(...), платформа автоматически подставляет это значение во все новые строки, которые добавляются в данный набор. Более того, изменить регистратор вручную внутри конкретной строки набора на значение, отличное от установленного в отборе, система все равно не позволит. Мы можем смело удалить эту строку, чтобы сделать код чище и производительнее.

Выбор правильного метода: СоздатьНаборЗаписей() против коллекции Движения

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

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

Управление замещением записей и новые возможности платформы 1С

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

Если вам требуется не перезаписать, а дополнить уже существующие движения новыми строками, используйте вызов метода с параметром Ложь:

НаборЗаписей.Записать(Ложь);

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

Программная защита от ошибок: проверка метаданных

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


ДокументМетаданные = ВыбранныйРегистраторСсылка.Метаданные();
РегистрМетаданные = Метаданные.РегистрыНакопления.ТоварыНаСкладах;

Если Не РегистрМетаданные.Регистраторы.Содержит(ДокументМетаданные) Тогда
    ВызватьИсключение "Документ " + ДокументМетаданные.Имя + " не назначен регистратором для регистра " + РегистрМетаданные.Имя;
КонецЕсли;

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

Блокировка данных и проблема нового документа

При программном формировании движений вне транзакции проведения документа крайне важно учитывать два критических фактора:

  1. Проверка записи документа: Если в качестве регистратора передается ссылка на новый, еще не записанный в базу данных документ, то ВыбранныйРегистраторСсылка будет либо пустой, либо будет содержать неопределенную ссылку, которой физически еще нет в таблицах СУБД. Попытка записать движения по такому документу вызовет ошибку. Перед формированием записей обязательно проверяйте, записан ли документ-источник с помощью функции ЗначениеЗаполнено().
  2. Блокировка данных (DataLocking): В многопользовательской среде при записи движений вне проведения высока вероятность возникновения блокировок. Рекомендуется явно накладывать управляемую блокировку на регистр перед выполнением записи набора, чтобы избежать ошибок совместного доступа.

Разбираем пример надежного и правильного кода

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


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

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

← На главную