Как победить ошибку "Ошибка записи! Не установлен отбор по регистратору" в 1С

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

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

Архитектурные причины ограничения платформы 1С

Для того чтобы понять логику платформы, проанализируем внутреннее устройство регистров накопления. В отличие от справочников или документов, запись регистра накопления является необъектной сущностью. Это значит, что у отдельной строки в таблице базы данных (будь то MS SQL, PostgreSQL или IBM DB2) нет собственного уникального идентификатора (UUID/GUID).

Уникальность записей в таблицах движений СУБД обеспечивается составным кластерным индексом, в который на уровне ядра платформы жестко включены следующие поля:

  1. Регистратор (ссылка на документ, породивший запись).
  2. Период (дата и время совершения движения).
  3. НомерСтроки (целочисленный индекс, определяющий порядок записи внутри конкретного регистратора).

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

Анализ опасных решений и заблуждений

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

Решение 1: Паттерн "Универсальный документ корректировки"

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

Проанализируем программную реализацию этого паттерна. Мы создаем документ КорректировкаРегистраТМЦ, у которого нет собственных реквизитов, кроме стандартных. На управляемую форму этого документа выносим динамическую таблицу, связанную с набором записей регистра накопления ТМЦ. При записи формы мы принудительно устанавливаем отбор по ссылке текущего документа:


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

Решение 2: Программное создание скрытых технических документов "на лету"

Если бизнес-аналитик категорически настаивает на том, чтобы пользователи не видели никаких списков документов, мы можем полностью скрыть этот механизм на уровне программного кода. Пользователь будет вносить записи в специальную обработку, которая имитирует регистр ТМЦ, а система при сохранении будет генерировать скрытые технические документы.

Выясним, как правильно реализовать данный алгоритм без создания конфликта блокировок СУБД. На каждую транзакцию ручного ввода мы будем программно создавать новый уникальный облегченный документ-регистратор:


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

Решение 3: Переход на независимый регистр сведений

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

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

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


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

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

← На главную