Почему не работает проверка при проведении документа в 1С и он проводится «молча»?

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом IT и автоматизация бизнеса
← На главную

Каждый начинающий разработчик в среде «1С:Предприятие» рано или поздно сталкивается с ситуацией, когда написанный им код в процедуре ОбработкаПроведения просто игнорируется системой. Документ записывается, проводится, в регистры пишутся новые движения, но никаких сообщений об ошибках не выводится, а установленный флаг Отказ = Истина словно не существует. При этом логика кода кажется абсолютно верной.

В этой статье мы подробно разберем, почему возникает такое «молчаливое» поведение системы, проанализируем типичные ошибки при написании запросов контроля дублей, рассмотрим транзакционные нюансы платформы «1С:Предприятие 8» и научимся писать оптимальный и архитектурно правильный код для проверки данных.


Выясняем первопричину: почему код проведения не выполняется?

Если при попытке проведения документа система не выдает вообще никаких сообщений (даже отладочных, добавленных вами для проверки вроде «тут вернулась ложь»), это означает, что управление физически не доходит до вашей процедуры ОбработкаПроведения. Давайте проанализируем основные причины, почему платформа игнорирует этот код.

1. Код написан в модуле формы, а не в модуле объекта.

Это самая распространенная ошибка. Разработчик открывает форму документа, переходит во вкладку «Модуль» и пишет код там. Однако обработчик события ОбработкаПроведения — это строго серверное событие объекта метаданных, которое должно располагаться исключительно в Модуле объекта документа. Код, написанный в модуле формы, платформа просто не увидит при проведении, так как проведение может выполняться программно (например, при групповом перепроведении документов), когда формы вообще не создаются и не открываются.

2. Отсутствует привязка обработчика к событию.

Мало просто написать процедуру с правильным именем в модуле объекта. Ее необходимо явно связать с событием платформы. Чтобы проверить это, выполните следующие шаги:

  1. Откройте конфигуратор и найдите ваш документ в дереве метаданных.
  2. Нажмите на него правой кнопкой мыши и выберите «Открыть модуль объекта».
  3. В окне свойств документа (вызывается по Alt + Enter на корневом элементе документа) перейдите на вкладку «Прочее».
  4. Найдите строку «Обработка проведения» и нажмите на иконку с лупой. Платформа должна автоматически позиционироваться на вашей процедуре в модуле объекта. Если этого не произошло или открылась пустая процедура, значит, связь была нарушена.

3. Выполняется запись, а не проведение.

Обратите внимание на то, какую кнопку нажимает пользователь в интерфейсе. Если нажимается кнопка «Записать», то документ просто сохраняется в базе данных. При этом срабатывают события ПередЗаписью и ПриЗаписи, но транзакция проведения не запускается, и процедура ОбработкаПроведения не вызывается вовсе.

4. Ошибки серверной отладки.

Если вы пытаетесь локализовать проблему с помощью точек останова, но отладчик не останавливается на коде проведения, убедитесь, что на сервере 1С включен режим отладки. В клиент-серверном варианте работы базы данных код модуля объекта выполняется на стороне сервера приложений. Если служба сервера «1С:Предприятия» запущена без ключа -debug, отладчик на клиенте будет игнорировать серверные процедуры.


Анализ и исправление логики запроса в регистр

Предположим, мы исправили привязку события, и код начал выполняться. Однако проверка все равно не срабатывает, и запрос всегда возвращает пустой результат, из-за чего функция контроля возвращает Ложь. Давайте выясним, какие подводные камни скрываются в самом запросе.

Рассмотрим исходный запрос, который использовался для поиска уже принятого сотрудника:


ВЫБРАТЬ
    КадровыеПеремещения.Период
ИЗ
    РегистрСведений.КадровыеПеремещения КАК КадровыеПеремещения
ГДЕ
    КадровыеПеремещения.Сотрудник = &Сотрудник
    И КадровыеПеремещения.ПричинаИзмененияСостояния = ЗНАЧЕНИЕ(Перечисление.ПричиныИзмененияСостояний.ПриемНаРаботу)

В этой конструкции кроется несколько потенциальных проблем:

1. Несоответствие типов данных.
В конфигурациях 1С (особенно в кадровых решениях, таких как «Зарплата и Управление Персоналом») часто разделяются понятия «Физические лица» (справочник СправочникСсылка.ФизическиеЛица) и «Сотрудники» (справочник СправочникСсылка.Сотрудники). Если измерение Сотрудник в регистре сведений имеет тип физического лица, а реквизит вашего документа — это ссылка на справочник сотрудников (или наоборот), то сравнение в секции ГДЕ всегда будет давать ложный результат. Система никогда не сопоставит эти разнотипные ссылки, даже если имена людей совпадают. Проверьте типы данных в конфигураторе.

2. Проблема списочных документов (сотрудник в табличной части).
Если документ «Прием на работу» содержит табличную часть со списком принимаемых сотрудников, то обращение к реквизиту Сотрудник напрямую из шапки вернет пустую ссылку (поскольку такого реквизита в шапке просто нет). В этом случае запрос отработает по пустой ссылке и ничего не найдет. Для списочных документов логику нужно переписывать так, чтобы проверять всех сотрудников из табличной части в одном запросе с помощью оператора В (&МассивСотрудников).

3. Ловушка перепроведения (текущий документ в результатах запроса).
Если вы пытаетесь перепровести уже существующий и ранее проведенный документ, то запрос обязательно найдет запись в регистре, которую сделал этот же самый документ. В результате проверка заблокирует проведение собственного документа. Чтобы избежать этой ситуации, необходимо исключать текущий документ-регистратор из проверки с помощью условия:

И КадровыеПеремещения.Регистратор <> &Ссылка


Оптимизация кода и повышение производительности (Best Practices)

Давайте проанализируем код функции, предложенный в сообщении №5:


Результат = Запрос.Выполнить().Выгрузить();
Сообщить("результат запроса вернул " + Результат.Количество);
Если Результат.Количество = 0 Тогда
    Возврат Ложь;
Иначе
    Возврат Истина;
КонецЕсли;

С точки зрения стандартов разработки фирмы «1С» этот код содержит грубые ошибки оптимизации:

Посмотрим на оптимизированный вариант функции проверки:


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

Архитектурно правильное решение: где выполнять проверку?

Проведение документа в 1С — это тяжелая транзакционная операция. Внутри транзакции проведения таблицы базы данных блокируются СУБД для обеспечения целостности данных. Если выполнять ресурсоемкие проверки (например, поисковые запросы к регистрам) внутри транзакции проведения, это может привести к серьезным проблемам с параллельной работой пользователей (возникнут избыточные ожидания на блокировках и взаимоблокировки).

Поэтому в платформе 1С предусмотрено разделение проверок на два типа:

  1. Интерактивные проверки (до транзакции). Выполняются в обработчике ОбработкаПроверкиЗаполнения объекта документа. Это идеальное место для проверки корректности заполнения реквизитов и бизнес-логики (например, «принят ли уже этот человек»). Если проверка не пройдена, транзакция записи даже не начнется, что экономит ресурсы сервера и СУБД.
  2. Транзакционный контроль (внутри проведения). Используется только тогда, когда важна защита от «гонки данных» (race condition) — ситуации, когда два оператора одновременно пытаются принять на работу одного и того же сотрудника в разные документы. В этом случае проверка в ОбработкаПроведения должна обязательно сопровождаться установкой управляемой блокировки на регистр сведений по конкретному сотруднику, чтобы второй документ ожидал завершения транзакции первого.

Разберем оба варианта реализации.


Вариант 1. Реализация через «ОбработкаПроверкиЗаполнения» (Рекомендуемый)

Этот метод позволяет выдать пользователю красивое предупреждение и запретить проведение еще до начала транзакции записи. Перенесем логику проверки в этот обработчик модуля объекта.


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

Вариант 2. Реализация в «ОбработкаПроведения» с управляемой блокировкой

Если вам критически важна защита от одновременного ввода данных разными пользователями, проверку нужно оставить в проведении, но переписать ее с учетом транзакционных правил и управляемых блокировок. Рассмотрим этот шаблон кода в модуле объекта документа:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Установка управляемой блокировки для защиты от параллельной записи
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.КадровыеПеремещения");
    ЭлементБлокировки.УстановитьПараметр("Сотрудник", Сотрудник);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    Блокировка.Заблокировать();
    
    // 2. Проверка наличия записи в регистре с исключением текущего документа
    Если СотрудникПринятНаРаботу(Сотрудник, Ссылка) Тогда
        
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Не удалось провести документ! Сотрудник уже принят на работу.";
        Сообщение.Сообщить();
        
        Отказ = Истина;
        Возврат;
        
    КонецЕсли;
    
    // 3. Формирование движений документа
    Движения.КадровыеПеремещения.Записывать = Истина;
    
    // Перед записью новых движений очистим старые, если это необходимо
    Движения.КадровыеПеремещения.Очистить();
    
    Движение = Движения.КадровыеПеремещения.Добавить();
    Движение.Период = Дата;
    Движение.Сотрудник = Сотрудник;
    Движение.Должность = Должность;
    Движение.Подразделение = Подразделение;
    Движение.Оклад = Оклад;
    Движение.ПричинаИзмененияСостояния = Перечисления.ПричиныИзмененияСостояний.ПриемНаРаботу;
    
КонецПроцедуры

Проанализируем этот код шаг за шагом:

  1. Мы создали объект БлокировкаДанных и заблокировали запись в регистре по конкретному Сотруднику. Теперь любой другой сеанс, пытающийся провести документ по этому же сотруднику в этот же момент, будет ожидать завершения нашей транзакции.
  2. Мы вызвали оптимизированную функцию СотрудникПринятНаРаботу, передав туда Ссылка текущего документа, чтобы система не заблокировала перепроведение самого себя.
  3. Для вывода сообщения мы применили объект СообщениеПользователю вместо устаревшей функции Сообщить(), что гарантирует доставку уведомления на клиентскую часть в управляемом интерфейсе.

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

← На главную