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