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