Как правильно проверить, находится ли сотрудник уже в подразделении, при проведении кадрового перемещения в 1С?

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

При разработке механизмов кадрового учета часто возникает задача предотвратить ошибочные действия пользователя — например, перемещение сотрудника в то же самое подразделение, где он уже числится, или перевод уже уволенного сотрудника. Разберем, как правильно реализовать такие проверки в процедуре ОбработкаПроведения, почему стандартный запрос со СрезомПоследних может работать некорректно и как оптимизировать код для высокой производительности.

Анализ исходной задачи и типичные ошибки начинающих

Рассмотрим ситуацию: при проведении документа "Кадровое перемещение" нам нужно убедиться, что сотрудник не перемещается в то же подразделение, в котором он находится на текущий момент. В базовом варианте начинающие программисты часто создают отдельную функцию для каждой проверки. Проанализируем пример такой функции:


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

Несмотря на то что этот код может "работать" в простых условиях, он содержит несколько критических архитектурных изъянов, которые мы сейчас разберем и исправим.

Проблема 1: Использование секции ГДЕ вместо параметров виртуальной таблицы

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

Выясним причину: если мы используем ГДЕ, система сначала выбирает из базы данных последние записи по всем сотрудникам организации, формирует огромную временную таблицу в памяти и только потом начинает отсеивать лишних. Если же мы передаем отбор в скобках СрезПоследних(&Период, Сотрудник = &Сотрудник), SQL-сервер сразу выбирает только одну нужную запись. На больших базах данных разница в скорости может достигать десятков и сотен раз.

Проблема 2: Игнорирование периода (даты среза)

Проанализируем работу СрезПоследних без указания первого параметра. Если оставить скобки пустыми, 1С вернет самые актуальные данные на текущий момент времени (на "сейчас"). Это допустимо только при оперативной работе, но совершенно неприемлемо в ОбработкеПроведения.

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

Оптимизированное решение: объединение проверок в один запрос

Вместо того чтобы обращаться к базе данных трижды (проверка на увольнение, проверка на прием, проверка на подразделение), мы можем и должны объединить всё в один пакетный запрос. Это значительно снизит нагрузку на сервер.

Разберем, как это сделать правильно:

  1. Используем МоментВремени, чтобы видеть состояние базы строго "до" проведения текущего документа.
  2. Используем параметры виртуальной таблицы для фильтрации по сотруднику.
  3. Применим конструкцию ВЫБОР КОГДА... для формирования понятных флагов состояния.

Процедура ОбработкаПроведения(Отказ, РежимПроведения)

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

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

Продвинутые нюансы: Блокировки и Момент Времени

Посмотрим на ситуацию глубже. В многопользовательской среде два бухгалтера могут одновременно проводить документы по одному и тому же сотруднику. Чтобы избежать логических ошибок, перед выполнением запроса в транзакции проведения рекомендуется устанавливать управляемую блокировку.

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

Подведем итоги:

← На главную