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