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