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