При доработке типовых документов часто возникает необходимость дать пользователю возможность выбрать не одно значение ссылочного типа, а целый список — например, сразу несколько подразделений для формирования табеля учета рабочего времени или расчетных ведомостей. Начинающие разработчики нередко пытаются добавить на форму реквизит с типом СписокЗначений и сталкиваются с тем, что после закрытия и повторного открытия документа выбранные данные бесследно исчезают. Проанализируем, почему это происходит, какие типичные ошибки допускаются при попытке обойти проблему и как правильно реализовать постоянное хранение списка значений в объекте конфигурации.
Разберем фундаментальное отличие между реквизитами объекта и реквизитами формы в платформе 1С:Предприятие 8.3:
Попытка включить свойство Автоматическое сохранение данных формы в настройках для решения этой задачи также является ошибочной. Данный механизм платформы сохраняет пользовательские настройки формы в системном хранилище и привязывается к текущему пользователю и его рабочему месту, а не к конкретному экземпляру документа в информационной базе. В результате получается неприятный побочный эффект: пользователь открывает совершенно другой документ, а в нем отображается тот же самый список подразделений, который был выбран в прошлый раз.
В попытках быстро решить задачу разработчики иногда выбирают обходные пути, которые порождают трудноуловимые ошибки в коде и архитектурные проблемы.
Первая распространенная ошибка — попытка сериализовать список значений в реквизит с типом ХранилищеЗначения непосредственно из клиентского контекста:
&НаСервере
Функция ПолучитьЗначениеХЗ()
Возврат ХранилищеПодразделений.Получить();
КонецФункции
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
ХранилищеПодразделений = Новый ХранилищеЗначения(СписокЗначенийПодразделений);
КонецПроцедуры
Выясним причину ошибок в этом фрагменте:
ХранилищеЗначения доступен только на сервере. При попытке вызвать обращение к методам или свойствам хранилища на тонком клиенте компилятор 1С выдаст исключение: Переменная не определена.ПриЗаписиНаСервере контекст формы не содержит прямой переменной объекта. Присваивание должно выполняться строго через параметр ТекущийОбъект: ТекущийОбъект.ХранилищеПодразделений = ....Вторая опасная альтернатива — сохранение списка ссылок в обычную строку через метод ЗначениеВСтрокуВнутр(). Такой способ категорически не рекомендуется использовать для ссылочных данных. Он полностью разрушает ссылочную целостность базы данных: если подразделение будет помечено на удаление и удалено стандартной обработкой, платформа не сможет отследить ссылку внутри текстовой строки. В результате в документе останется битая внутренняя ссылка (битый GUID). Кроме того, по значениям из строки невозможно построить запрос, выполнить соединение таблиц или настроить быстрый отбор в журнале документов.
Наиболее надежный, производительный и архитектурно правильный способ хранения списка ссылок в документе — добавление специализированной табличной части. В типовых конфигурациях, таких как ЗУП 3.1, добавление табличной части к стандартному документу ТабельУчетаРабочегоВремени следует выполнять через расширение конфигурации с отключенным безопасным режимом, чтобы не снимать документ с поддержки.
Рассмотрим по шагам реализацию табличной части Подразделения с реквизитом Подразделение (тип СправочникСсылка.ПодразделенияОрганизаций).
Если по условиям задачи требуется сохранить удобный для пользователя интерфейс с отдельным полем выбора и списком, проанализируем рабочий код обработчиков формы.
Посмотрим на процедуру добавления подразделения по кнопке формы:
&НаКлиенте
Процедура ДобавитьПодразделение(Команда)
Если ЗначениеЗаполнено(Объект.Подразделение) Тогда
Если СписокЗначенийПодразделений.НайтиПоЗначению(Объект.Подразделение) = Неопределено Тогда
СписокЗначенийПодразделений.Добавить(Объект.Подразделение);
СтрокаТЧПодразделения = Объект.Подразделения.Добавить();
СтрокаТЧПодразделения.Подразделение = Объект.Подразделение;
Иначе
Сообщить("Данное подразделение уже добавлено в список");
КонецЕсли;
Объект.Подразделение = ПредопределенноеЗначение("Справочник.ПодразделенияОрганизаций.ПустаяСсылка");
КонецЕсли;
КонецПроцедуры
Обратим внимание на важную деталь: для очистки поля ввода ссылочного реквизита на клиенте необходимо использовать метод ПредопределенноеЗначение или присваивать Неопределено. Конструкция вида Объект.Подразделение.Пустая() недопустима, так как метод Пустая() возвращает логический тип (булево), что приведет к ошибке несоответствия типов данных.
Для корректного отображения ранее сохраненных строк табличной части при открытии документа перенесем инициализацию на сторону сервера. Выполнять заполнение в обработчике ПриОткрытии на клиенте не рекомендуется, так как это порождает лишние серверные вызовы и не срабатывает при стандартном перечитывании объекта. Оптимальное место — серверный обработчик ПриЧтенииНаСервере:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
СписокЗначенийПодразделений.Очистить();
Для Каждого СтрокаТЧ Из Объект.Подразделения Цикл
СписокЗначенийПодразделений.Добавить(СтрокаТЧ.Подразделение);
КонецЦикла;
КонецПроцедуры
Рассмотрим наиболее лаконичный подход, рекомендуемый стандартами разработки 1С. В большинстве случаев дублирующий реквизит формы с типом СписокЗначений вовсе не требуется. Достаточно перетащить саму табличную часть Объект.Подразделения в элементы формы в виде элемента управления Таблица.
В этом случае платформа автоматически решает все сопутствующие задачи:
Модифицированность = Истина при любых манипуляциях пользователя со списком.