Привет! Если вы только начинаете свой путь в разработке на 1С, то рано или поздно столкнетесь с двумя событиями, которые звучат почти одинаково: ПередЗаписью и ПриЗаписи.
На первый взгляд кажется, что разница невелика? И то, и другое происходит, когда мы нажимаем кнопку сохранения. Но для платформы 1С разница огромна, и непонимание этой разницы — частая причина багов, бесконечных циклов и ошибок в логике.
Давайте разберем эту тему на пальцах, как это работает под капотом и когда какое событие нужно использовать. Мы будем говорить о событиях в модуле объекта (например, в модуле документа или справочника), потому что они срабатывают всегда — будь то интерактивная запись или программная.
Как работает сохранение в 1С: метафора с посылкой
Представьте, что вы отправляете посылку на почте. Процесс записи объекта в базу данных очень похож на эту процедуру.
Событие ПередЗаписью — это момент, когда вы стоите у столика и пакуете коробку. Вы можете доложить туда вещи, пересчитать их стоимость, написать на самой коробке адрес маркером. Если вы вдруг передумали отправлять или поняли, что забыли что-то важное, вы можете просто остановиться и забрать коробку домой (отменить запись). Но самой посылки для почтовой системы еще не существует, у нее нет трек-номера.
Событие ПриЗаписи — это момент, когда вы уже передали коробку оператору. Оператор взвесил ее, внес в компьютер и выдал вам чек с трек-номером (в 1С это называется Ссылка). Посылка уже в системе. Вы больше не можете вскрыть коробку и доложить туда вещи. Зато, имея на руках трек-номер, вы можете оформить дополнительные услуги: например, записать в отдельный журнал страховку на этот трек-номер.
А теперь перейдем от посылок к реальному коду.
Событие ПередЗаписью: меняем, дозаполняем, отказываемся от записи
Это событие срабатывает до того, как платформа физически обновит данные в таблице базы данных. В этот момент объект полностью находится в оперативной памяти сервера.
Золотое правило: используйте ПередЗаписью для изменения реквизитов самого объекта или для проверок, после которых запись нужно запретить.
Например, мы можем дозаполнить документ, указав Автора или какое-то другие вычисляемые реквизиты. Или можем отказаться от записи, если какое-нибудь вычисляемое условие этого требует.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения) // 1. Меняем сам объект. Заполняем автора, если он пустой Если ПустаяСтрока(Автор) Тогда Автор = Пользователи.ТекущийПользователь(); КонецЕсли; // 2. Рассчитываем сумму документа СуммаДокумента = 0; Для Каждого СтрокаТовары Из Товары Цикл СтрокаТовары.Сумма = СтрокаТовары.Цена * СтрокаТовары.Количество; СуммаДокумента = СуммаДокумента + СтрокаТовары.Сумма; КонецЦикла; // 3. Проверяем условие и отменяем запись, если что-то не так Если СуммаДокумента < 0 Тогда Сообщить("Сумма документа не может быть отрицательной!"); Отказ = Истина; // Вот эта строчка прерывает весь процесс сохранения КонецЕсли; КонецПроцедуры
Обратите внимание на параметр Отказ. Если мы установим его в Истина, платформа поймет: «Ага, разработчик забраковал этот объект, сохранять в базу не будем».
Чего нельзя делать в ПередЗаписью? Не следует здесь создавать и записывать другие объекты (например, движения по регистрам или подчиненные справочники). Почему? Потому что наш главный объект еще не записан. Вдруг после вашего кода произойдет сбой, или платформа не сможет записать объект из-за конфликта блокировок? Ваш подчиненный объект останется в базе «сиротой», ссылаясь на пустоту.
Примечание. Мы, конечно, можем сгенерировать ссылку нового специальным образом, присвоить ее еще незаписанному объекту, но сейчас мы рассматриваем основной, базовый сценарий.
Событие ПриЗаписи: работаем с внешним миром
Это событие срабатывает, когда платформа уже сформировала SQL-запрос, выполнила его и положила данные нашего объекта в базу. У нашего объекта (даже если он совсем новый) появилась полноценная Ссылка — тот самый трек-номер.
Весь процесс ПриЗаписи происходит внутри транзакции. Транзакция — это гарантия: либо в базу запишется всё (и сам документ, и то, что мы накодим в этом событии), либо не запишется ничего.
Золотое правило: используйте ПриЗаписи для изменения других данных в базе, которые связаны с нашим объектом.
Например, нам нужно при сохранении карточки клиента сделать запись в независимый регистр сведений, чтобы зафиксировать историю изменений.
Процедура ПриЗаписи(Отказ) // Создаем набор записей для независимого регистра НаборЗаписей = РегистрыСведений.ИсторияИзмененийКлиентов.СоздатьНаборЗаписей(); // Фильтруем набор по ссылке на НАШ текущий объект НаборЗаписей.Отбор.Клиент.Установить(Ссылка); НоваяЗапись = НаборЗаписей.Добавить(); НоваяЗапись.Период = ТекущаяДата(); НоваяЗапись.Клиент = Ссылка; // На момент срабатывания ПриЗаписи Ссылка уже гарантированно существует НоваяЗапись.Событие = "Обновление данных карточки"; // Записываем регистр. Если тут будет ошибка, // транзакция отменится, и сам справочник тоже не сохранится. НаборЗаписей.Записать(); КонецПроцедуры
Главная ошибка начинающих в ПриЗаписи: попытка изменить реквизиты самого объекта.
Если вы напишете в ПриЗаписи строку вроде СуммаДокумента = 1000;, в оперативной памяти сумма изменится. Но в базу данных она не попадет! Потому что поезд уже ушел, платформа уже записала объект в базу строчкой выше. Чтобы эта сумма попала в базу, вам придется вызвать метод ЭтотОбъект.Записать(). А вызов записи внутри события записи приведет к тому, что событие вызовется снова. Потом снова. И так до тех пор, пока сервер 1С не упадет от нехватки памяти (это называется бесконечная рекурсия).
Поэтому: внутри ПриЗаписи сам объект мы только читаем, но не меняем!
После жмем ОК и проверяем в пользовательском режиме. Убеждаемся, что теперь фильтруются только договоры нужного контрагента. Если выбрать контрагента, который отличается от контрагента в договоре, то поле Договор очистится, т.к. при настройках по умолчанию подставляется режим изменения «очищать».
Немного про клиент-серверное взаимодействие
Важно понимать, где физически выполняется этот код. Модуль объекта — это всегда сервер. Когда пользователь нажимает кнопку «Записать» в клиентском приложении (на форме), данные формы летят на сервер, превращаются там в объект, и только тогда начинают срабатывать наши события.
Поэтому в событиях ПередЗаписью и ПриЗаписи модуля объекта нельзя задавать вопросы пользователю. Нельзя написать Вопрос(«Вы уверены?», РежимДиалогаВопрос.ДаНет). Сервер 1С — это суровый вычислительный центр где-то в стойке, у него нет монитора и мышки, он не умеет показывать окошки.
Если вам нужно спросить пользователя, используйте события модуля формы (они так и называются, ПередЗаписью на клиенте). Там вы задаете вопрос, и если пользователь жмет «Да», отдаете команду платформе: «Всё ок, отправляй данные на сервер, пусть там отрабатывают серверные события модуля объекта».
Заключение
Запомните простую шпаргалку:
Нужно поменять реквизит, что-то посчитать внутри документа или проверить правильность заполнения? Ваш выбор — ПередЗаписью.
Нужно сделать запись в регистр, создать подчиненный документ или обновить данные в другом справочнике, опираясь на текущий документ? Ваш выбор — ПриЗаписи, потому что вам нужна Ссылка.