В процессе разработки кадрового учета часто возникает задача автоматизации жизненного цикла сотрудника. Например, нам необходимо, чтобы система самостоятельно меняла статус в документе КП_ДокументыПоСтажерам в зависимости от даты трудоустройства или наличия оценки руководителя. Рассмотрим, как правильно реализовать такую логику, избежав типичных ошибок начинающих разработчиков, таких как избыточная запись объектов и неверная последовательность условий.
Для корректной работы алгоритма мы должны четко определить приоритеты статусов. В нашем примере действуют следующие правила:
Проанализируем первоначальный подход. В исходном коде использовались идущие подряд блоки Если...КонецЕсли. Это приводило к тому, что одно условие могло перезаписывать результат другого. Также критической ошибкой является выполнение запросов внутри цикла — это многократно замедляет работу системы. Кроме того, вызов метода Записать() без проверки того, изменился ли статус на самом деле, создает ненужную нагрузку на базу данных и блокировки таблиц.
Разберем правильный подход к организации цикла обработки документов. Вместо множества независимых условий мы используем единую конструкцию Если...ИначеЕсли. Это гарантирует, что для одного документа сработает только одна ветка логики.
Процедура КП_ОбновлениеСтатусовСтажеров() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.СтатусСтажера КАК СтатусТекущий,
| Док.ДатаТрудоустройства КАК ДатаТрудоустройства,
| Док.ФИО КАК ФизЛицо
|ИЗ
| Документ.КП_ДокументыПоСтажерам КАК Док
|ГДЕ
| Док.Проведен = ИСТИНА
| И Док.ПометкаУдаления = ЛОЖЬ";
РезультатЗапроса = Запрос.Выполнить().Выбрать();
Пока РезультатЗапроса.Следующий() Цикл
// Получаем объект только если планируем его изменять
КарточкаСтажера = РезультатЗапроса.Ссылка.ПолучитьОбъект();
// Проверяем наличие листа оценки (функцию разберем ниже)
ЛистОценкиЗаполнен = ПроверитьНаличиеЛистаОценки(КарточкаСтажера.ФИО);
НовыйСтатус = Неопределено;
ТекущаяДатаСеанса = ТекущаяДата(); // Оптимизация: фиксируем время
// Определяем приоритетный статус
Если ЛистОценкиЗаполнен Тогда
НовыйСтатус = Перечисления.КП_СтатусСтажера.ПрошёлИспытатльныйСрок;
ИначеЕсли ЗначениеЗаполнено(КарточкаСтажера.ДатаТрудоустройства)
И НачалоДня(КарточкаСтажера.ДатаТрудоустройства) <= НачалоДня(ТекущаяДатаСеанса) Тогда
НовыйСтатус = Перечисления.КП_СтатусСтажера.НаИспытательномСроке;
ИначеЕсли ЗначениеЗаполнено(КарточкаСтажера.ДатаТрудоустройства)
И НачалоДня(КарточкаСтажера.ДатаТрудоустройства) > НачалоДня(ТекущаяДатаСеанса) Тогда
НовыйСтатус = Перечисления.КП_СтатусСтажера.ПланируетсяВыход;
КонецЕсли;
// Важный момент: записываем объект только при изменении реквизита
Если НовыйСтатус <> Неопределено И КарточкаСтажера.СтатусСтажера <> НовыйСтатус Тогда
КарточкаСтажера.СтатусСтажера = НовыйСтатус;
Попытка
КарточкаСтажера.Записать(РежимЗаписиДокумента.Запись);
Исключение
ЗаписьЖурналаРегистрации("ОбновлениеСтатусов", УровеньЖурналаРегистрации.Ошибка, , ,
"Не удалось записать документ: " + КарточкаСтажера.Ссылка + " Ошибка: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Обратим внимание на использование функции НачалоДня(). В 1С тип Дата всегда содержит время. Если мы сравним "01.10.2023 00:00:00" (дата из документа) и "01.10.2023 15:40:00" (текущее время), оператор "меньше" вернет Истина, хотя календарный день еще не закончился. Чтобы статус На испытательном сроке устанавливался корректно именно в день выхода, всегда приводите обе даты к началу дня.
Вспомогательная функция ПроверитьНаличиеЛистаОценки должна возвращать булево значение. Однако помните, что поиск по строковому полю (ФИО) является ненадежным. Рекомендуется использовать ссылки на справочник ФизическиеЛица.
Функция ПроверитьНаличиеЛистаОценки(СотрудникСсылка)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Оценка.Ссылка
|ИЗ
| Справочник.КП_ОценкаСотрудника КАК Оценка
|ГДЕ
| Оценка.Сотрудник = &Сотрудник";
Запрос.УстановитьПараметр("Сотрудник", СотрудникСсылка);
Возврат НЕ Запрос.Выполнить().Пустой();
КонецФункции
Рассмотрим, как профессиональный разработчик оптимизировал бы этот код. Вместо того чтобы для каждого стажера вызывать функцию с отдельным запросом, мы можем сразу в основном запросе соединить документы с таблицей оценок. Это называется Левое соединение. Если оценка есть — поле в результате запроса будет заполнено, если нет — будет NULL.
Проанализируем фрагмент такого запроса:
ВЫБРАТЬ
Док.Ссылка КАК Ссылка,
Док.СтатусСтажера КАК СтатусСтажера,
Док.ДатаТрудоустройства КАК ДатаТрудоустройства,
ЕСТЬNULL(Оценка.Ссылка, ЗНАЧЕНИЕ(Справочник.КП_ОценкаСотрудника.ПустаяСсылка)) КАК ЛистОценки
ИЗ
Документ.КП_ДокументыПоСтажерам КАК Док
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КП_ОценкаСотрудника КАК Оценка
ПО Док.ФИО = Оценка.Сотрудник
ГДЕ
Док.Проведен
Использование такого запроса позволит выполнить процедуру в десятки раз быстрее, так как обращение к базе данных произойдет всего один раз, независимо от количества стажеров в списке.
Если данный код планируется запускать через РегламентныеЗадания, необходимо учитывать, что они выполняются на сервере. В этом случае нельзя использовать методы типа Предупреждение() или Вопрос(). Также следует оборачивать запись объекта в блок Попытка...Исключение, так как документ может быть заблокирован пользователем, открывшим его в этот момент в интерфейсе. Использование ЗаписьЖурналаРегистрации поможет вам отследить такие ситуации без прерывания работы всего алгоритма.
Для надежной работы автоматической смены статусов мы применили:
ИначеЕсли, исключающую двойную трактовку статуса.Записать(), что экономит ресурсы сервера.Следуя этим правилам, вы создадите стабильный и производительный механизм управления состояниями объектов в вашей конфигурации 1С.