Как правильно реализовать контроль состояния сотрудника при проведении документа «Увольнение»

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

В практике начинающих разработчиков 1С часто возникает задача: ограничить возможность проведения документа, если данные в базе не соответствуют бизнес-логике. В нашем случае мы разберем классический пример — запрет на увольнение сотрудника, который уже уволен или вовсе не числится в организации. Мы проанализируем типичные ошибки при сравнении типов данных, изучим работу с запросами и виртуальными таблицами, а также рассмотрим профессиональные приемы обеспечения целостности данных.

Анализ основной ошибки: сравнение типов данных

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


Если Документы.Увольнение.НайтиПоРеквизиту("Сотрудник", Сотрудник) <> РезультатЗапроса Тогда
    Отказ = Истина;
КонецЕсли;

В чем здесь кроется подвох? Метод НайтиПоРеквизиту возвращает объект типа Ссылка (на документ или пустую ссылку). В то же время метод запроса Выполнить() возвращает объект типа РезультатЗапроса. Это совершенно разные по своей природе объекты. Ссылка — это идентификатор записи в таблице документов, а результат запроса — это временный объект в памяти, содержащий выборку данных. Их прямое сравнение через оператор «не равно» (<>) всегда будет давать Истина, так как типы не совпадают. Для решения задачи нам нужно сравнивать не сами объекты, а конкретные значения полей или проверять наличие данных как таковых.

Решение через запрос к регистру сведений

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

Рассмотрим вариант запроса, который проверяет, числится ли сотрудник работающим:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   СведенияОСотрудниках.Сотрудник КАК Сотрудник
    |ИЗ
    |   РегистрСведений.СведенияОСотрудниках КАК СведенияОСотрудниках
    |ГДЕ
    |   СведенияОСотрудниках.Сотрудник = &Сотрудник
    |   И НЕ СведенияОСотрудниках.ПризнакУвольнения";

Запрос.УстановитьПараметр("Сотрудник", Сотрудник);
РезультатЗапроса = Запрос.Выполнить();

Если РезультатЗапроса.Пустой() Тогда
    Отказ = Истина;
    Сообщить("Выбранное лицо не является активным сотрудником или уже уволено.");
КонецЕсли;

Здесь мы используем метод Пустой() объекта РезультатЗапроса. Если запрос не нашел ни одной записи, удовлетворяющей условиям (сотрудник есть И он НЕ уволен), значит, проведение документа должно быть заблокировано.

Использование виртуальной таблицы «СрезПоследних»

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

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   СрезПоследних.ПризнакУвольнения КАК Уволен
    |ИЗ
    |   РегистрСведений.СведенияОСотрудниках.СрезПоследних(&Период, Сотрудник = &Сотрудник) КАК СрезПоследних";

Запрос.УстановитьПараметр("Сотрудник", Сотрудник);
Запрос.УстановитьПараметр("Период", Дата); // Дата текущего документа

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
    // Сотрудник вообще не найден в регистре
    Отказ = Истина;
    Сообщить("Сотрудник не найден в базе!");
Иначе
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    Если Выборка.Уволен Тогда
        Отказ = Истина;
        Сообщить("Сотрудник уже был уволен ранее!");
    КонецЕсли;
КонецЕсли;

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

Проблема перепроведения документа

Если мы оставим код в таком виде, то при попытке перепровести уже существующий документ «Увольнение», система выдаст ошибку. Почему? Потому что наш же документ уже сделал запись в регистр о том, что сотрудник уволен, и запрос увидит этот статус.

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


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

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

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

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

Проанализируем пример установки блокировки перед выполнением запроса:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СведенияОСотрудниках");
ЭлементБлокировки.УстановитьПараметр("Сотрудник", Сотрудник);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

// Далее следует выполнение запроса и проверка условий

Рекомендации по разделению проверок

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

Таким образом, мы выяснили, что для корректного сравнения результата запроса с реквизитами необходимо использовать методы проверки выборки (Пустой(), Следующий()), правильно работать с типами данных и учитывать специфику виртуальных таблиц при работе с регистрами сведений.

← На главную