При разработке механизмов проведения документов или ручной корректировке движений в 1С:Предприятии программисты часто сталкиваются с системной ошибкой: «Ошибка записи! Не установлен отбор по регистратору (Регистр накопления: ...)». Эта проблема возникает из-за специфики работы объектов типа НаборЗаписей. В отличие от независимых регистров сведений, регистры накопления всегда жестко привязаны к документу-регистратору.
В данной статье мы подробно разберем, почему возникает эта ошибка, проанализируем типичный неудачный код и выстроим правильный алгоритм работы с регистрами накопления.
Давайте проанализируем ситуацию: платформа 1С требует, чтобы любой набор записей регистра накопления имел установленный фильтр по полю Регистратор. Это необходимо, потому что физически записи в таблице регистра хранятся с обязательной ссылкой на документ, который их породил. Если вы пытаетесь добавить запись в набор или вызвать метод Записать(), не указав, к какому именно документу относятся эти строки, система блокирует операцию из соображений целостности данных.
Рассмотрим основные сценарии, когда установка отбора «не срабатывает»:
Добавить()) до того, как был установлен отбор по регистратору.Установить(), попадает Неопределено или пустая ссылка.Посмотрим на пример кода, который часто приводит к подобным проблемам. Такой разбор напоминает процесс качественного Code-review. Проанализируем фрагмент, где запись производится внутри цикла:
// Фрагмент проблемного кода
Пока ВыборкаПартия.Следующий И СколькоСписать <> 0 Цикл
НаборЗаписей = РегистрыНакопления.Себестоимость.СоздатьНаборЗаписей();
// Ошибка может быть здесь, если отбор не установлен ПЕРЕД добавлением
Движение = НаборЗаписей.ДобавитьРасход();
Движение.Регистратор = ВыборкаПартия.Ссылка; // Это присвоение НЕ является установкой отбора!
НаборЗаписей.Записать();
КонецЦикла;
В этом примере допущены сразу три серьезные ошибки:
Движение.Регистратор = Ссылка не заменяет установку отбора для всего набора НаборЗаписей.Отбор.Регистратор.Установить(Ссылка).Записать() по умолчанию работает в режиме замещения. Это значит, что каждая следующая итерация цикла будет удалять записи, созданные на предыдущей итерации для этого же регистратора.Разберем по шагам, как правильно организовать запись движений. Выясним, какая последовательность команд гарантирует отсутствие ошибок.
Во-первых, создание объекта набора записей и установка отбора должны происходить вне цикла перебора строк (товаров или партий), если все движения относятся к одному документу. Если документов несколько, набор пересоздается для каждого нового регистратора.
Во-вторых, обязательная последовательность методов выглядит так:
СоздатьНаборЗаписей() — создаем пустой объект-контейнер.Отбор.Регистратор.Установить(Ссылка) — КРИТИЧЕСКИ ВАЖНО сделать это первым делом.Прочитать() — вызываем, если нужно сохранить существующие движения документа и добавить к ним новые. Если нужно полностью перезаписать движения — пропускаем.Добавить() — добавляем строки в коллекцию.Записать() — физически сохраняем изменения в базе данных.Рассмотрим подробнее, как должен выглядеть оптимизированный и рабочий код процедуры расчета себестоимости. Мы вынесем работу с регистрами на нужный уровень вложенности.
&НаСервере
Процедура РассчитатьСебестоимостьНаСервере()
Для каждого Стр Из Объект.СписокНакладных Цикл
// Создаем запрос для получения данных (текст запроса опустим для краткости)
// ...
// Создаем набор записей ОДИН РАЗ для документа-накладной
НакладнаяСсылка = Стр.Накладные;
Если НакладнаяСсылка.Пустая() Тогда
Продолжить;
КонецЕсли;
НаборЗаписей = РегистрыНакопления.Себестоимость.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(НакладнаяСсылка);
// Получаем результат запроса
Результат = Запрос.Выполнить();
ВыборкаИтог = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаИтог.Следующий() Цикл
СколькоСписать = ВыборкаИтог.Количество;
ВыборкаПартия = ВыборкаИтог.Выбрать();
Пока ВыборкаПартия.Следующий() И СколькоСписать > 0 Цикл
СписатьСПартии = Мин(СколькоСписать, ВыборкаПартия.КоличествоОстаток);
// Добавляем запись в коллекцию набора
Движение = НаборЗаписей.ДобавитьРасход();
Движение.Период = ТекущаяДата();
Движение.Номенклатура = ВыборкаПартия.Номенклатура;
Движение.Партия = ВыборкаПартия.Партия;
// Регистратор подставится автоматически из отбора набора записей при записи
Движение.Количество = СписатьСПартии;
Движение.Стоимость = (ВыборкаПартия.СтоимостьОстаток / ВыборкаПартия.КоличествоОстаток) * СписатьСПартии;
СколькоСписать = СколькоСписать - СписатьСПартии;
КонецЦикла;
КонецЦикла;
// Записываем ВСЕ движения документа одним вызовом
Попытка
НаборЗаписей.Записать(Истина); // Истина - замещать существующие записи
Исключение
Сообщить("Ошибка при записи документа: " + НакладнаяСсылка + " - " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Проанализируем еще несколько важных моментов, которые помогут избежать ошибок в будущем:
1. Проверка на регистратора в метаданных.
Если ошибка сохраняется даже при наличии кода НаборЗаписей.Отбор.Регистратор.Установить(Ссылка), убедитесь в конфигураторе, что ваш документ отмечен как «Регистратор» на вкладке «Регистраторы» у соответствующего регистра накопления. Если галочка не стоит, платформа не позволит создать набор записей по этому документу.
2. Использование свойства «Значение».
Иногда вместо метода Установить() используют прямое присвоение:
НаборЗаписей.Отбор.Регистратор.Значение = НакладнаяСсылка;
НаборЗаписей.Отбор.Регистратор.Использование = Истина;
Этот способ идентичен методу Установить(), но более громоздок. Мы рекомендуем использовать стандартный метод Установить().
3. Влияние режима записи.
При работе в транзакции (например, в модуле объекта документа в процедуре ОбработкаПроведения) платформа сама управляет наборами записей. В этом случае лучше использовать свойство документа Движения.Себестоимость. Если же вы пишете внешнюю обработку (как в примере выше), то явное создание НаборЗаписей — единственный верный путь.
Таким образом, для решения проблемы «Не установлен отбор по регистратору» достаточно строго соблюдать последовательность: создание набора -> установка отбора по ссылке -> формирование записей -> однократная запись набора.