При работе с механизмами платформы «1С:Предприятие» разработчики часто сталкиваются с классической архитектурной особенностью: при попытке установить отбор по какому-либо измерению (например, по номенклатуре) в наборе записей регистра сведений, который подчинен регистратору, система выдает ошибку «Поле не обнаружено» или «Метод не доступен для данного объекта» при попытке добавить новое поле в отбор.
В этой статье мы подробно разберем, почему платформа ведет себя именно так, проанализируем ограничения СУБД и рассмотрим все возможные пути обхода этой проблемы — от классических методов работы с наборами записей в памяти до революционных возможностей платформы, появившихся в версии 8.3.26.
Для начала давайте разберемся, почему возникает эта ошибка. Согласно Синтакс-Помощнику и внутренней архитектуре платформы 1С, для регистров сведений, у которых в конфигураторе установлен режим записи «Подчинение регистратору», отбор набора записей возможен только по регистратору. Свойства отбора по измерениям или периоду у такого набора записей попросту отсутствуют.
Это связано с тем, как СУБД хранит и перезаписывает данные. На уровне базы данных все движения одного документа-регистратора представляют собой единую неделимую логическую группу (гранулу данных). Когда мы записываем набор записей с установленным отбором по конкретному регистратору, платформа выполняет транзакционную операцию: она полностью удаляет все старые записи этого регистратора в таблице БД и записывает новые из нашего набора. Если бы платформа позволила наложить отбор по номенклатуре внутри этого регистратора, это привело бы к частичной перезаписи движений документа, нарушению целостности транзакций и потенциальному зацикливанию блокировок.
Если мы работаем на версиях платформы ниже 8.3.26, выполнить точечную перезапись в базе данных напрямую невозможно. Нам придется действовать по классической схеме: прочитать весь набор записей по регистратору в оперативную память, скорректировать нужные строки в коде и записать весь набор обратно в базу данных.
Давайте рассмотрим пошагово, как реализовать этот алгоритм на практике. Предположим, у нас есть документ-источник и нам нужно скорректировать цену только для определенной номенклатуры.
// 1. Создаем набор записей регистра сведений
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
// 2. Устанавливаем обязательный отбор по регистратору
НаборЗаписей.Отбор.Регистратор.Установить(Источник.ДокументОснование);
// 3. Считываем все существующие движения этого документа из базы данных в память
НаборЗаписей.Прочитать();
// 4. Выполняем обход прочитанных записей и корректируем нужные строки
// Допустим, нам нужно изменить цену для конкретного товара
ТоварДляИзменения = СтрТовары.Номенклатура;
НоваяЦена = СтрТовары.Цена;
ЗаписьНайдена = Ложь;
Для Каждого СтрокаЗаписи Из НаборЗаписей Цикл
Если СтрокаЗаписи.Номенклатура = ТоварДляИзменения Тогда
СтрокаЗаписи.Цена = НоваяЦена;
ЗаписьНайдена = Истина;
КонецЕсли;
КонецЦикла;
// 5. Если запись для этого товара отсутствовала в движениях, добавим ее вручную
Если Не ЗаписьНайдена Тогда
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Регистратор = Источник.ДокументОснование;
НоваяЗапись.Номенклатура = ТоварДляИзменения;
НоваяЗапись.Цена = НоваяЦена;
// Заполняем остальные обязательные измерения и ресурсы
НоваяЗапись.Период = Источник.ДокументОснование.Дата;
КонецЕсли;
// 6. Записываем весь измененный пакет данных обратно в СУБД.
// По умолчанию второй параметр Замещать равен Истина, что полностью обновит движения регистратора.
НаборЗаписей.Записать();
Этот метод полностью рабочий и используется повсеместно. Однако у него есть существенные минусы, о которых должен помнить разработчик:
Разработчики платформы 1С услышали многолетние просьбы программистов и, начиная с версии 8.3.26, представили принципиально новый функционал — расширенные режимы замещения в наборах записей.
Теперь мы можем точечно обновлять или удалять конкретные строки в подчиненном регистре сведений без полной перезаписи всей «гранулы» движений регистратора! Для этого используется новое свойство РасширенныеРежимыЗамещения и системное перечисление РежимЗамещения.
Давайте проанализируем, как с помощью этой новой возможности точечно изменить строку в подчиненном регистре:
// Создаем набор записей
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Источник.ДокументОснование);
// Активируем использование расширенных режимов замещения
НаборЗаписей.РасширенныеРежимыЗамещения = Истина;
// Теперь нам доступно редактирование системного поля НомерСтроки.
// Мы можем точечно обратиться к конкретной строке движений.
НоваяСтрока = НаборЗаписей.Добавить();
НоваяСтрока.НомерСтроки = 5; // Указываем номер строки, которую хотим перезаписать
НоваяСтрока.Номенклатура = СтрТовары.Номенклатура;
НоваяСтрока.Цена = СтрТовары.НоваяЦена;
// Записываем набор, используя новый режим замещения - Обновление.
// При этом обновятся ТОЛЬКО те строки, номера которых мы явно указали в наборе.
// Остальные записи регистратора в СУБД останутся нетронутыми!
НаборЗаписей.Записать(РежимЗамещения.Обновление);
Для удаления конкретной строки, например, строки под номером 3, мы можем использовать режим удаления:
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Источник.ДокументОснование);
НаборЗаписей.РасширенныеРежимыЗамещения = Истина;
СтрокаДляУдаления = НаборЗаписей.Добавить();
СтрокаДляУдаления.НомерСтроки = 3; // Строка, которую нужно удалить из СУБД
// Платформа точечно удалит строку №3 и автоматически перенумерует последующие записи в БД
НаборЗаписей.Записать(РежимЗамещения.Удаление);
Использование этого режима позволяет экономить колоссальные ресурсы СУБД на крупных базах данных, минимизируя трафик между сервером приложений и СУБД, а также исключая избыточные блокировки таблиц.
Если в вашей конфигурации регулярно возникает бизнес-требование изменять записи в обход стандартного проведения документа-регистратора (как в случае с документом КорректировкаПриобретения, который пытается вклиниться в чужие движения цен), это явный маркер архитектурного просчета. Рассмотрим, как можно решить эту проблему концептуально на уровне проектирования конфигурации.
Вариант А: Перевод регистра сведений в режим «Независимый»
Если цены номенклатуры должны легко изменяться различными обработками, импортом прайс-листов или корректировками без жесткой привязки к конкретному документу поставки, то регистр логично сделать независимым. В этом случае для точечной работы с записями мы сможем использовать объект МенеджерЗаписи, который позволяет прочитать, изменить и записать одну-единственную строку по ключевым измерениям:
МенеджерЗаписи = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Номенклатура = СтрТовары.Номенклатура;
МенеджерЗаписи.Период = ТекущаяДата();
МенеджерЗаписи.Прочитать();
МенеджерЗаписи.Цена = НоваяЦена;
МенеджерЗаписи.Записать();
При этом типе записи полностью отсутствует блокировка других строк, но разработчику придется самостоятельно писать код для очистки или обновления записей при удалении первичных документов.
Вариант Б: Добавление корректирующего документа в список регистраторов
Вместо того чтобы из документа КорректировкаПриобретения пытаться переписать движения исходного поступления, правильнее сделать саму КорректировкуПриобретения полноценным регистратором для регистра сведений. В свойствах регистра при этом настраивается периодичность (например, «По позиции регистратора»). В результате корректировка будет делать свои собственные, новые движения на более позднюю дату, отражая актуальную цену, не повреждая и сохраняя историческую достоверность первичной операции.
Вариант В: Использование универсального корректирующего регистратора
В крупных тиражных решениях от 1С (таких как ERP, Комплексная автоматизация) часто применяется механизм универсального регистратора. Создается специальный служебный документ (например, КорректировкаЗаписейРегистров), который программно наполняется нужными записями движений и проводится. Это обеспечивает ссылочную целостность базы данных и оставляет понятный аудиторский след для аналитиков и бухгалтеров.