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