Часто возникает задача — предоставить пользователю возможность настроить расписание для какого-либо объекта системы (например, для вида технического обслуживания оборудования), а затем использовать это расписание в обработках для автоматического создания документов, выстраивая эффективный конвейер обработки задач. Разберем, как правильно сохранить объект РасписаниеРегламентногоЗадания, чтобы при повторном открытии формы настройки загружались ранее введенные данные, и как избежать распространенных ошибок при этом.
Основная проблема заключается в том, что объект РасписаниеРегламентногоЗадания нельзя напрямую записать в реквизит справочника. Его необходимо предварительно преобразовать (сериализовать) в формат, пригодный для хранения в базе данных, например, в строку. Рассмотрим несколько надежных способов это сделать.
Это универсальный и надежный метод, который подходит для многих объектов 1С. Суть его в том, чтобы преобразовать объект расписания в XML-строку для сохранения и выполнить обратное преобразование при чтении. Для этого используются функции ЗначениеВСтрокуXML и ЗначениеИзСтрокиXML.
Рассмотрим по шагам, как это реализовать в форме элемента справочника.
РасписаниеСтрока с типом Строка неограниченной длины. Это важно, так как XML-представление может быть довольно длинным.Посмотрим на пример кода. Команда НастроитьРасписание вызывает процедуру, которая открывает стандартный диалог.
&НаКлиенте
Процедура НастроитьРасписание(Команда)
// Переменная для хранения объекта расписания
Перем пРасписание;
// Если реквизит с расписанием еще не заполнен, создаем новый пустой объект
Если ПустаяСтрока(Объект.РасписаниеСтрока) Тогда
пРасписание = Новый РасписаниеРегламентногоЗадания();
// Если расписание уже было сохранено, восстанавливаем объект из XML-строки
Иначе
пРасписание = ЗначениеИзСтрокиXML(Объект.РасписаниеСтрока);
КонецЕсли;
// Создаем и показываем диалог настройки, передавая в него текущее расписание
Диалог = Новый ДиалогРасписанияРегламентногоЗадания(пРасписание);
Диалог.Показать(Новый ОписаниеОповещения("ОткрытьРасписаниеЗавершение", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьРасписаниеЗавершение(НовоеРасписание, Контекст) Экспорт
// Если пользователь нажал "Отмена" в диалоге, ничего не делаем
Если НовоеРасписание = Неопределено Тогда
Возврат;
КонецЕсли;
// Преобразуем (сериализуем) полученный из диалога объект расписания в XML-строку
// и записываем в реквизит объекта
Объект.РасписаниеСтрока = ЗначениеВСтрокуXML(НовоеРасписание);
КонецПроцедуры
// Эти функции лучше вынести в общий модуль с флагами "Сервер" и "Вызов сервера"
&НаСервереБезКонтекста
Функция ЗначениеВСтрокуXML(Значение) Экспорт
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Значение);
Возврат ЗаписьXML.Закрыть();
КонецФункции
&НаСервереБезКонтекста
Функция ЗначениеИзСтрокиXML(СтрокаXML) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
Значение = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
Возврат Значение;
КонецФункции
Важный момент: Если при выполнении кода возникает ошибка Ошибка разбора XML... AttValue: ' expected, это почти всегда означает, что XML-строка, которую вы пытаетесь прочитать, повреждена или обрезана. Убедитесь, что реквизит, в котором вы храните строку (в нашем примере Объект.РасписаниеСтрока), имеет тип Строка неограниченной длины.
ВСтроку() и ИзСтроки()Для объекта РасписаниеРегламентногоЗадания платформа предоставляет специализированные методы для преобразования в строку и обратно. Этот способ проще, так как не требует написания дополнительных функций сериализации. Если же вам требуется сформировать строку в особом формате для внешних систем, можно использовать конструктор Cron расписания.
Код будет выглядеть немного иначе.
&НаКлиенте
Процедура НастроитьРасписание(Команда)
Перем пРасписание;
пРасписание = Новый РасписаниеРегламентногоЗадания();
// Если строка с расписанием заполнена, пытаемся восстановить из нее объект
Если НЕ ПустаяСтрока(Объект.РасписаниеСтрока) Тогда
// Метод ИзСтроки() выполняется на самом объекте
пРасписание.ИзСтроки(Объект.РасписаниеСтрока);
КонецЕсли;
Диалог = Новый ДиалогРасписанияРегламентногоЗадания(пРасписание);
Диалог.Показать(Новый ОписаниеОповещения("ОткрытьРасписаниеЗавершение", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьРасписаниеЗавершение(НовоеРасписание, Контекст) Экспорт
Если НовоеРасписание = Неопределено Тогда
Возврат;
КонецЕсли;
// Используем встроенный метод объекта для получения его строкового представления
Объект.РасписаниеСтрока = НовоеРасписание.ВСтроку();
КонецПроцедуры
Этот подход более лаконичен и предназначен специально для работы с расписаниями. Строка, генерируемая методом ВСтроку(), также является довольно длинной, поэтому требование к реквизиту (строка неограниченной длины) сохраняется.
ХранилищеЗначенияЧтобы полностью избежать проблем с преобразованием в строку, обрезанием и ошибками парсинга, можно использовать реквизит с типом ХранилищеЗначения. Этот тип данных предназначен для хранения любых сериализуемых объектов 1С. Платформа сама позаботится о корректном сохранении и восстановлении объекта.
РасписаниеХранилище с типом ХранилищеЗначения.
&НаКлиенте
Процедура НастроитьРасписание(Команда)
// Получаем расписание с сервера
ПолучитьРасписаниеССервера();
КонецПроцедуры
&НаСервере
Процедура ПолучитьРасписаниеССервера()
// Получаем объект из хранилища. Если хранилище пустое, .Получить() вернет Неопределено
пРасписание = Объект.РасписаниеХранилище.Получить();
// Если расписания еще нет, создаем новое
Если пРасписание = Неопределено Тогда
пРасписание = Новый РасписаниеРегламентногоЗадания();
КонецЕсли;
// Открываем диалог на клиенте после получения данных
ОткрытьДиалогНаКлиенте(пРасписание);
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьДиалогНаКлиенте(пРасписание)
Диалог = Новый ДиалогРасписанияРегламентногоЗадания(пРасписание);
Диалог.Показать(Новый ОписаниеОповещения("ОткрытьРасписаниеЗавершение", ЭтотОбъект));
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьРасписаниеЗавершение(НовоеРасписание, Контекст) Экспорт
Если НовоеРасписание = Неопределено Тогда
Возврат;
КонецЕсли;
// Вызываем серверную процедуру для сохранения объекта в хранилище
СохранитьРасписаниеНаСервере(НовоеРасписание);
КонецПроцедуры
&НаСервере
Процедура СохранитьРасписаниеНаСервере(НовоеРасписание)
// Помещаем объект в хранилище. Платформа сама выполнит сериализацию.
Объект.РасписаниеХранилище = Новый ХранилищеЗначения(НовоеРасписание);
КонецПроцедуры
Использование ХранилищеЗначения является предпочтительным методом, так как он наиболее надежен и избавляет от ручного управления преобразованиями.
После того как расписание сохранено, его можно использовать в регламентном задании или обработке. Алгоритм действий следующий:
РасписаниеРегламентногоЗадания одним из способов, описанных выше.ДниНедели, ДниМесяца, Месяцы и т.д.Этот алгоритм обычно реализуется в регламентном задании, которое запускается, например, раз в день, и проверяет все объекты, для которых нужно создать документы по их индивидуальным расписаниям. При реализации таких механизмов «с нуля» полезно изучить фоновые задания, отслеживание результата и индикацию прогресса без БСП.