При программной работе с регистрами сведений в конфигурации "1С:Зарплата и управление персоналом 3", особенно с теми, которые связаны с данными физических лиц, можно столкнуться с неочевидной ошибкой. Вы пытаетесь добавить новую запись, корректно заполняете все поля, но при вызове метода Записать() система выдает сообщение: "Запись не верна! Значение поля не соответствует установленному отбору". При этом отладчик может показывать, что значения полей перед самой записью меняются, что приводит к конфликту.
Рассмотрим эту ситуацию на конкретном примере и разберем, почему она возникает и как её правильно решить.
Предположим, у нас есть задача добавить запись в непериодический независимый регистр сведений НаградыФизическихЛиц. Измерениями регистра являются ФизическоеЛицо и НомерПоПорядку. В регистре уже есть запись для сотрудника "Иванов Иван" с номером 1. Мы хотим добавить ему вторую награду с номером 2.
Первоначальный код может выглядеть так:
&НаСервере
Процедура ЗагрузитьНаградыФизЛиц_НаСервере()
Сотрудник = Справочники.ФизическиеЛица.НайтиПоКоду("0000004372");
МенеджерЗаписи = РегистрыСведений.НаградыФизическихЛиц.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ФизическоеЛицо = Сотрудник;
МенеджерЗаписи.НомерПоПорядку = 2; // Устанавливаем номер 2
МенеджерЗаписи.Награда = Справочники.Награды.НайтиПоНаименованию("Грамота");
// В этой строке возникает ошибка
МенеджерЗаписи.Записать();
КонецПроцедуры
При выполнении этого кода мы получаем ошибку, в которой система сообщает, что значение поля НомерПоПорядку не соответствует отбору и указывает на существующую запись с номером 1. Более того, если в отладчике проверить значение МенеджерЗаписи.НомерПоПорядку прямо перед вызовом Записать(), оно будет равно 2, но в момент выполнения записи оно каким-то образом меняется на 1.
Причина такого поведения кроется в типовых механизмах контроля целостности данных, встроенных в конфигурацию ЗУП. В частности, за это отвечает подписка на событие ПроверитьЗаписиДанныхФизическихЛиц. Эта подписка срабатывает перед записью данных в регистры, связанные с физическими лицами, и выполняет дополнительные проверки. В нашем случае, она, вероятно, некорректно обрабатывает добавление новой записи и "сбрасывает" значение одного из ключевых полей, что и приводит к конфликту и ошибке.
Попытка использовать НаборЗаписей вместо МенеджераЗаписи приведет к той же ошибке, так как проблема не в объекте, который мы используем для записи, а в обработчике события, который срабатывает в любом случае.
// Этот код также вызовет ошибку
Сотрудник = Справочники.ФизическиеЛица.НайтиПоКоду("0005001");
НаборЗаписей = РегистрыСведений.НаградыФизическихЛиц.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ФизическоеЛицо.Установить(Сотрудник);
// Даже если мы явно установим отбор по новому номеру
НаборЗаписей.Отбор.НомерПоПорядку.Установить(2);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.ФизическоеЛицо = Сотрудник;
НоваяЗапись.НомерПоПорядку = 2;
НоваяЗапись.Награда = Справочники.Награды.НайтиПоНаименованию("Грамота");
НаборЗаписей.Записать(Истина); // Ошибка возникнет здесь
Существует несколько способов обойти эту проверку. Рассмотрим их от наименее к наиболее предпочтительному.
Самый прямолинейный, но и самый опасный способ — временно или навсегда отключить в конфигураторе подписку на событие ПроверитьЗаписиДанныхФизическихЛиц для объекта РегистрСведений.НаградыФизическихЛиц.
Почему это плохая идея?
Этот метод можно использовать только для диагностики, чтобы убедиться, что проблема именно в подписке, но не стоит оставлять его в рабочей системе.
Платформа 1С предоставляет специальный режим ОбменДанными.Загрузка, который предназначен для ситуаций, когда данные загружаются из внешних источников (например, при обмене между базами). В этом режиме отключается большинство проверок, выполняемых при записи объектов, включая проверки в подписках на события.
Чтобы его использовать, нужно перед записью установить соответствующее свойство для набора записей.
Пример кода:
НаборЗаписей = РегистрыСведений.НаградыФизическихЛиц.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.ФизическоеЛицо.Установить(Сотрудник);
// Включаем режим загрузки, чтобы обойти проверки
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НоваяЗапись = НаборЗаписей.Добавить();
// ...заполнение полей новой записи...
НоваяЗапись.ФизическоеЛицо = Сотрудник;
НоваяЗапись.НомерПоПорядку = 2;
// ...
НаборЗаписей.Записать();
Этот способ работает и является более безопасным, чем отключение подписки, так как он действует точечно, только на время выполнения вашего кода. Однако он отключает все проверки, а не только ту, которая нам мешает, что в некоторых случаях может быть избыточным.
Наиболее правильным, современным и безопасным методом является использование механизма дополнительных свойств. Это специальная коллекция, доступная у многих объектов (документов, наборов записей), которая позволяет передать параметры из вашего кода внутрь обработчиков событий (в том числе в подписки).
Разработчики типовых конфигураций предусмотрели такие ситуации и добавили в свои проверки анализ определенных ключей в этих дополнительных свойствах. Если нужный ключ найден, проверка пропускается.
Для нашей ситуации необходимо перед записью добавить в коллекцию ДополнительныеСвойства ключ с именем "НеПроверятьЗаписиДанныхФизическихЛиц".
Итоговый, правильный код:
&НаСервере
Процедура ЗагрузитьНаградыФизЛиц_НаСервере()
Сотрудник = Справочники.ФизическиеЛица.НайтиПоКоду("0000004372");
НаборЗаписей = РегистрыСведений.НаградыФизическихЛиц.СоздатьНаборЗаписей();
// Важно установить отбор по одному из измерений,
// чтобы не затереть записи по другим физлицам
НаборЗаписей.Отбор.ФизическоеЛицо.Установить(Сотрудник);
// Читаем существующие записи, чтобы не удалить их, а дополнить
НаборЗаписей.Прочитать();
// Добавляем новую запись
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.ФизическоеЛицо = Сотрудник;
НоваяЗапись.НомерПоПорядку = 2;
НоваяЗапись.Награда = Справочники.Награды.НайтиПоНаименованию("Грамота");
// *** Ключевой момент! ***
// Сообщаем подписке на событие, что эту проверку нужно пропустить
НаборЗаписей.ДополнительныеСвойства.Вставить("НеПроверятьЗаписиДанныхФизическихЛиц", Истина);
// Записываем набор. Параметр Ложь (или не указывать) означает,
// что записи будут добавлены к существующим в рамках отбора.
// Если указать Истина, существующие записи по отбору будут замещены.
НаборЗаписей.Записать();
КонецПроцедуры
Этот метод является предпочтительным, потому что:
Таким образом, при возникновении подобных "загадочных" ошибок при программной записи данных в типовых конфигурациях, в первую очередь стоит проверить наличие подписок на события для изменяемого объекта и поискать в документации или типовом коде, как можно корректно обойти ненужные проверки с помощью механизма дополнительных свойств.