Обработчики событий модуля объекта 1С делят работу так: ОбработкаПроверкиЗаполнения проверяет данные вне транзакции, ПередЗаписью дозаполняет объект и выполняет критичные проверки уже в транзакции, ПриЗаписи пишет связанные данные в другие объекты, ОбработкаЗаполнения отвечает за заполнение нового объекта и ввод на основании. Во всех обработчиках записи первым делом проверяется ОбменДанными.Загрузка, а параметр Отказ только устанавливается в Истина, но никогда не сбрасывается.

Ниже — практический разбор этих правил по стандартам разработки фирмы «1С» с примерами кода и типичными ошибками, которые я регулярно нахожу при аудите доработок.

Порядок событий при записи объекта

Чтобы понимать, куда класть код, нужно держать в голове последовательность. Для документа, записываемого интерактивно с проведением, она такая:

ШагОбработчикТранзакцияЧто делать
1ОбработкаПроверкиЗаполненияНетПроверки корректности заполнения
2ПередЗаписьюДаДозаполнение, сравнение со старыми значениями, критичные проверки
3Запись в базуДаДелает платформа
4ПриЗаписиДаЗапись связанных данных в другие объекты
5ОбработкаПроведенияДаФормирование движений
Обработчики событий модуля объекта 1С: проверка вне транзакции и шаги внутри неё
Обработчики событий модуля объекта 1С: проверка вне транзакции и шаги внутри неё

Главный вывод из таблицы: всё, что выполняется в шагах 2–5, держит транзакцию и блокировки. Чем больше там кода, тем дольше остальные пользователи ждут. Если проверка может выполниться до транзакции, её место в шаге 1.

ПередЗаписью: дозаполнение и критичные проверки

Что уместно в ПередЗаписью:

  • заполнение реквизитов, которые вычисляются из других (суммы, служебные поля, представления);
  • проверки, без которых данные в системе станут несогласованными;
  • сравнение текущих значений с теми, что сейчас лежат в базе.

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

1С
Процедура ПередЗаписью(Отказ)

	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	Если Не ЭтоНовый() Тогда
		ПрежниеЗначения = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка, "Наименование, ИНН");
		Если ПрежниеЗначения.ИНН <> ИНН Тогда
			// ИНН изменили: например, сбрасываем признак проверки контрагента.
			ПроверенВСервисе = Ложь;
		КонецЕсли;
	КонецЕсли;

КонецПроцедуры

Обратите внимание на ЭтоНовый(): у нового объекта читать нечего, а лишний запрос внутри транзакции — это лишнее время.

Почему проверка ОбменДанными.Загрузка идёт первой

Стандарт требует, чтобы все действия в ПередЗаписью выполнялись после проверки ОбменДанными.Загрузка. При загрузке данных через обмен объект должен записаться точно таким, каким пришёл из узла-источника: правила обмена уже заполнили его. Любое дозаполнение в этом режиме:

  • замедляет обмен, иногда в разы на больших пакетах;
  • может перезаписать данные, пришедшие из другой базы, локальными значениями по умолчанию;
  • ломает проверки, которые зависят от ещё не загруженных объектов.

Для документов сигнатура отличается, но правило то же:

1С
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	// Предварительные проверки и дозаполнение реквизитов.
	СуммаДокумента = Товары.Итог("Сумма");

КонецПроцедуры

ПриЗаписи: только связанные данные

К моменту ПриЗаписи объект уже записан в базу данных. Отсюда два правила:

  1. Не менять реквизиты самого объекта. Изменения не попадут в базу без повторной записи, а повторная запись из обработчика записи — прямой путь к рекурсии и ошибкам.
  2. Здесь место для записи связанных данных: регистров сведений, зависимых справочников, очередей обмена, у которых нужна уже существующая ссылка.

И здесь тоже первой строкой стоит проверка ОбменДанными.Загрузка: связанные данные при обмене приходят своими объектами.

ОбработкаПроверкиЗаполнения: проверки вне транзакции

Это основное место для проверок корректности. Обычную обязательность реквизита задают в метаданных свойством «Проверка заполнения» — кода не нужно. Обработчик нужен, когда:

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

Для условной обязательности стандарт рекомендует не изобретать свои схемы, а управлять массивом ПроверяемыеРеквизиты. В БСП для удаления реквизитов из массива есть готовый метод:

1С
Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)

	НепроверяемыеРеквизиты = Новый Массив;

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

	ОбщегоНазначения.УдалитьНепроверяемыеРеквизитыИзМассива(ПроверяемыеРеквизиты, НепроверяемыеРеквизиты);

	Если ЗначениеЗаполнено(ДатаОкончания) И ДатаОкончания < ДатаНачала Тогда
		ОбщегоНазначения.СообщитьПользователю(
			НСтр("ru = 'Дата окончания не может быть раньше даты начала.'"), ,
			"ДатаОкончания", "Объект", Отказ);
	КонецЕсли;

КонецПроцедуры

Так условность видна в одном месте, и свойство «Проверка заполнения» в метаданных продолжает отражать реальную картину.

Ловушка: программная запись

ОбработкаПроверкиЗаполнения не вызывается при программной записи объекта методом Записать(). Если вашу проверку обходят обработки загрузки, обмены или чужой код — данные пройдут без неё. Поэтому критерий выбора такой:

  • проверка нужна для удобства пользователя и качества ввода — ОбработкаПроверкиЗаполнения;
  • без проверки нарушится связность данных (например, движения по складу уйдут в минус там, где это недопустимо) — проверка в транзакции: ПередЗаписью, ПриЗаписи или ОбработкаПроведения.

При программной записи вызвать проверку можно явно, методом ПроверитьЗаполнение() до Записать().

Отдельный случай — подсистема обмена данными БСП: при отложенном проведении документов после загрузки обработчик проверки вызывается, и часть проверок можно пропустить, анализируя ДополнительныеСвойства.ОтложенноеПроведение.

Запросы внутри проверки

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

ОбработкаЗаполнения: значения по умолчанию и ввод на основании

Обработчик вызывается при создании нового объекта, в том числе при вводе на основании. Рекомендуемая структура:

1С
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)

	// 1. Специальное заполнение по типу данных заполнения.
	ТипДанных = ТипЗнч(ДанныеЗаполнения);
	Если ТипДанных = Тип("ДокументСсылка.ЗаказКлиента") Тогда
		ЗаполнитьПоЗаказуКлиента(ДанныеЗаполнения);
	ИначеЕсли ТипДанных = Тип("Структура") Тогда
		ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения);
	КонецЕсли;

	// 2. Общее заполнение того, что осталось пустым.
	Если Не ЗначениеЗаполнено(Ответственный) Тогда
		Ответственный = Пользователи.ТекущийПользователь();
	КонецЕсли;

КонецПроцедуры

Процедура ЗаполнитьПоЗаказуКлиента(Заказ)

	Если Не ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Заказ, "Проведен") Тогда
		ВызватьИсключение НСтр("ru = 'Ввод на основании непроведённого заказа невозможен.'");
	КонецЕсли;

	// Заполнение шапки и табличных частей по заказу.

КонецПроцедуры

Что здесь важно по стандарту:

  • Ограничения ввода на основании (запрет для групп справочника, для непроведённых документов) проверяются именно в ОбработкаЗаполнения, а не в отдельных командах.
  • Отказ сообщаем исключением, а не сообщением: при программном создании объекта сообщение никто не увидит, а исключение остановит выполнение.
  • Простые значения по умолчанию задавайте в метаданных свойством «Значение заполнения»: платформа подставит его сама, если реквизит остался пустым и СтандартнаяОбработка не сброшена.

Параметр Отказ: как не потерять ошибку

На одно событие могут реагировать сам модуль объекта, несколько подписок, расширения. Каждый из них может выставить Отказ. Поэтому:

1С
// Неправильно: сбросит Отказ, выставленный ранее другой проверкой.
Отказ = ЕстьОшибкиЗаполнения();

// Правильно.
Если ЕстьОшибкиЗаполнения() Тогда
	Отказ = Истина;
КонецЕсли;

// Тоже правильно.
Отказ = Отказ Или ЕстьОшибкиЗаполнения();

То же касается СтандартнаяОбработка и похожих параметров: их только устанавливают в нужное значение, не «возвращают обратно».

И второе правило: отказ без объяснения недопустим. Пользователь, увидевший «Не удалось записать документ», не поймёт, что делать, а разбор займёт у поддержки лишний час. Сообщение должно называть причину и поле:

1С
Если ДоступныйОстаток < Количество Тогда
	ТекстСообщения = СтрШаблон(НСтр("ru = 'Не хватает %1 %2 товара «%3» на складе «%4».'"),
		Количество - ДоступныйОстаток, ЕдиницаИзмерения, Номенклатура, Склад);
	ОбщегоНазначения.СообщитьПользователю(ТекстСообщения, , "Товары", "Объект", Отказ);
КонецЕсли;

СообщитьПользователю с последним параметром сам установит Отказ = Истина.

Типичные ошибки на аудите

  • Нет проверки ОбменДанными.Загрузка — обмен тормозит и перетирает данные.
  • Тяжёлые запросы в ПередЗаписью там, где хватило бы проверки вне транзакции.
  • Изменение реквизитов в ПриЗаписи с повторным Записать().
  • Важная бизнес-проверка только в ОбработкаПроверкиЗаполнения — обработка загрузки её обходит.
  • Отказ = Ложь или Отказ = Функция() в одной из подписок.
  • Отказ = Истина без сообщения.

Если в вашей конфигурации накопились подобные доработки, их обычно видно при аудите кода 1С, а исправляются они точечно в рамках доработки 1С.

Итог

  • Проверки — в ОбработкаПроверкиЗаполнения, в транзакцию — только то, что защищает целостность данных.
  • ПередЗаписью и ПриЗаписи начинаются с Если ОбменДанными.Загрузка Тогда Возврат;.
  • В ПриЗаписи объект уже записан: меняем только связанные данные.
  • Ограничения ввода на основании — в ОбработкаЗаполнения через ВызватьИсключение.
  • Отказ только устанавливаем в Истина и всегда объясняем причину.