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