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