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