События объектов

События — это точки, в которых платформа «спрашивает» ваш код: всё ли в порядке, прежде чем записать, провести или удалить объект.

Обработчик события — процедура с зарезервированным именем в модуле объекта, которую платформа вызывает автоматически в определённый момент жизни объекта. Вы не вызываете её сами — её вызывает система, а вы лишь описываете, что должно произойти.

В прошлом уроке мы выяснили, что бизнес-правила объекта живут в модуле объекта. События — это и есть механизм, через который эти правила подключаются. Идея проста и мощная: платформа сама, в нужный момент, заглядывает в модуль объекта, и если находит там процедуру с определённым именем — выполняет её. Хотите вмешаться в запись документа? Опишите процедуру ПередЗаписью. Хотите проверить перед удалением? — ПередУдалением. Разберём главные события и, что критично, порядок их вызова.

Зачем перехватывать события

Без событий объект вёл бы себя одинаково для всех: записался — и записался. Но реальная учётная логика полна условий. Нельзя провести отгрузку, если товара нет на складе. Нельзя удалить контрагента, по которому есть долги. При записи заказа нужно автоматически посчитать сумму. Всё это — вмешательство в стандартное поведение, и события дают для него аккуратные, предсказуемые точки входа. Вы не переписываете платформу — вы вешаете свои проверки на её «крючки».

ПередЗаписью — последний рубеж проверки

Событие ПередЗаписью срабатывает, когда объект уже готов записаться, но в базу ещё ничего не попало. Это идеальное место для валидации. У обработчика есть параметр Отказ: присвоив ему Истина, вы отменяете всю запись целиком.

Процедура ПередЗаписью(Отказ)
    Если Сумма <= 0 Тогда
        Сообщить("Сумма документа должна быть больше нуля");
        Отказ = Истина;
        Возврат; // дальше проверять нет смысла
    КонецЕсли;

    Если Дата > ТекущаяДата() Тогда
        Сообщить("Нельзя проводить документ будущей датой");
        Отказ = Истина;
    КонецЕсли;
КонецПроцедуры

Обратите внимание на экранированные операторы сравнения: <= и >. В коде 1С это обычные «меньше-равно» и «больше», просто в HTML урока они записаны безопасно.

ПриЗаписи — данные уже в транзакции

Событие ПриЗаписи наступает после того, как объект записался, но пока ещё внутри той же транзакции. Здесь тоже есть Отказ, и если его выставить, транзакция откатится — запись как будто и не было. Разница с ПередЗаписью в том, что тут объект уже существует в базе, и можно, например, обновить связанные данные, зная его ссылку.

Процедура ПриЗаписи(Отказ)
    // логируем факт записи в регистр сведений
    Запись = РегистрыСведений.ЖурналИзменений.СоздатьМенеджерЗаписи();
    Запись.Документ = Ссылка;
    Запись.Дата = ТекущаяДата();
    Запись.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
    Запись.Записать();
КонецПроцедуры

ОбработкаПроведения — сердце документа

Для документов главное событие — ОбработкаПроведения. Проведение — это отражение документа в учёте: списание товара со склада, движение денег, начисление. В этом обработчике формируются движения по регистрам. Параметр Режим подсказывает, проводится документ оперативно или нет, а Отказ позволяет запретить проведение.

Процедура ОбработкаПроведения(Отказ, Режим)
    Движения.ОстаткиТоваров.Записывать = Истина;
    Для Каждого СтрокаТовары Из Товары Цикл
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
        Движение.Период = Дата;
        Движение.Номенклатура = СтрокаТовары.Номенклатура;
        Движение.Количество = СтрокаТовары.Количество;
    КонецЦикла;
КонецПроцедуры

Именно здесь документ перестаёт быть «просто записью» и начинает влиять на остатки, обороты, балансы. Если ОбработкаПроведения пуста — документ проводится, но в учёте ничего не меняется.

ПередУдалением — защита от потери данных

Событие ПередУдалением срабатывает при непосредственном удалении объекта. Здесь уместно запретить удаление, если объект где-то используется.

Процедура ПередУдалением(Отказ)
    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка
        |ИЗ Документ.РасходнаяНакладная
        |ГДЕ Контрагент = &Контрагент";
    Запрос.УстановитьПараметр("Контрагент", Ссылка);
    Если Не Запрос.Выполнить().Пустой() Тогда
        Сообщить("Нельзя удалить: по контрагенту есть накладные");
        Отказ = Истина;
    КонецЕсли;
КонецПроцедуры

Как это работает: порядок вызова

Порядок событий — не абстракция, а то, от чего зависит корректность программы. При записи проведённого документа платформа выполняет их строго последовательно:

СобытиеМомент
1ПередЗаписьюобъект готов, в базе ещё ничего нет
2ПриЗаписиобъект записан, транзакция открыта
3ОбработкаПроведенияформируются движения по регистрам

Всё это — внутри одной транзакции. Если на любом шаге выставить Отказ = Истина, транзакция откатывается целиком: ни запись, ни движения не сохранятся. Это свойство транзакционности бесценно — оно гарантирует, что в базе не окажется «полузаписанного» документа с движениями, но без шапки, или наоборот. Либо всё, либо ничего.

Отмена проведения устроена зеркально: платформа удаляет ранее сделанные движения, а вы можете подключиться к событию ОбработкаУдаленияПроведения, если нужно откатить что-то ещё.

Частые ошибки

  • Забывают Возврат после Отказ = Истина. Код продолжает выполняться, обращается к незаполненным данным и падает с новой ошибкой, маскируя настоящую причину. Выставили отказ — прервите обработчик.
  • Путают ПередЗаписью и ОбработкаПроведения. Движения по регистрам формируют только в ОбработкаПроведения. Попытка писать движения в ПередЗаписью либо не сработает, либо приведёт к дублям.
  • Тяжёлые запросы в обработчике записи. Каждое событие выполняется внутри транзакции и держит блокировки. Долгий запрос в ПередЗаписью тормозит всех, кто ждёт эти же данные.
  • Проверяют пользовательский ввод в ПередЗаписью, а не в форме. Технически работает, но пользователь узнаёт об ошибке слишком поздно — при попытке записи. Мелкие подсказки лучше давать сразу в форме, а в модуле объекта оставлять жёсткие бизнес-правила.

Итоги

  • Обработчик события — процедура с зарезервированным именем в модуле объекта; вызывает её платформа, а не вы.
  • ПередЗаписью — валидация до записи; ПриЗаписи — действия после записи в той же транзакции.
  • ОбработкаПроведения — формирование движений по регистрам, «отражение в учёте».
  • ПередУдалением — защита от удаления используемых объектов.
  • Параметр Отказ = Истина отменяет операцию и откатывает транзакцию целиком.
  • Порядок при проведении: ПередЗаписьюПриЗаписиОбработкаПроведения, всё в одной транзакции.
Проверьте себя
1. В каком обработчике события документа принято формировать движения по регистрам накопления (списание товара со склада и т. п.)?
AПередЗаписью
BПриЗаписи
CОбработкаПроведения
DПередУдалением
2. Что делает присвоение Отказ = Истина внутри обработчика ПередЗаписью?
AЗаписывает документ, но помечает его как ошибочный
BОтменяет запись целиком, транзакция откатывается
CПропускает только текущую строку табличной части
DНичего — параметр Отказ доступен лишь для чтения