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