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