Как реализовать автоматическое и инициативное удаление сотрудника в 1С без конфликта записей?

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

При проектировании систем назначения сотрудников на определенные роли (например, ответственный менеджер за клиента, куратор проекта, ведущий специалист) разработчики часто сталкиваются с бизнес-требованием: «Если сотрудник увольняется, его нужно автоматически убрать с роли через 3 дня, но у руководителя должна быть возможность в любой момент заменить его вручную».

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

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

---

Решение 1. Использование регламентного задания (наиболее надежный и чистый подход)

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

Алгоритм работы решения:

  1. Когда в кадровом учете проводится документ увольнения сотрудника, в регистре связей СвязиСотрудников никаких изменений не происходит. Сотрудник продолжает числиться за клиентом.
  2. В системе настраивается ежедневное регламентное задание, которое запускается в нерабочее время (например, ночью).
  3. Регламентное задание выполняет запрос к срезу последних регистра сведений и сопоставляет активных сотрудников с их датой увольнения из справочника Сотрудники (или кадровых регистров).
  4. Если регламентное задание находит сотрудника, у которого дата увольнения меньше или равна ТекущаяДата() - 3 дня, система программно создает новую запись в регистре сведений с текущей датой и пустым значением сотрудника.

Проанализируем программный код общего модуля, который будет вызываться регламентным заданием:


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

Преимущества подхода:

---

Решение 2. Непериодический регистр сведений с контролем интервалов (Даты начала и окончания)

Этот вариант базируется на совете из сообщения 9 («Добавьте период действия»). Вместо использования стандартного платформенного механизма периодичности регистра 1С (который умеет работать только с одной датой — Период), мы переводим регистр в статус непериодического и самостоятельно управляем интервалами дат.

Структура регистра сведений СвязиСотрудниковИнтервалы:

Как это работает по шагам:

  1. При первичном назначении сотрудника создается запись с ДатаНачала = Сегодня() и ДатаОкончания = 31.12.3999 (бесконечность).
  2. При увольнении сотрудника документ увольнения находит активную запись и меняет реквизит ДатаОкончания на ДатаУвольнения + 3 дня.
  3. При ручном назначении нового сотрудника система программно закрывает текущую активную запись (ставит ДатаОкончания = ТекущаяДата()) и открывает новую запись для нового сотрудника с ДатаНачала = ТекущаяДата().

Рассмотрим пример кода для ручного назначения нового сотрудника:


Процедура НазначитьНовогоСотрудникаСКонтролемИнтервалов(Клиент, Роль, НовыйСотрудник) Экспорт

    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СвязиСотрудниковИнтервалы");
    ЭлементБлокировки.УстановитьЗначение("Клиент", Клиент);
    ЭлементБлокировки.УстановитьЗначение("Роль", Роль);
    
    НачалоТранзакции();
    Попытка
        Блокировка.Заблокировать();
        
        // Шаг 1. Ищем и закрываем текущую активную запись
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |	Связи.ДатаНачала КАК ДатаНачала
            |ИЗ
            |	РегистрСведений.СвязиСотрудниковИнтервалы КАК Связи
            |ГДЕ
            |	Связи.Клиент = &Клиент
            |	И Связи.Роль = &Роль
            |	И Связи.ДатаОкончания > &ТекущаяДата";
        
        Запрос.УстановитьПараметр("Клиент", Клиент);
        Запрос.УстановитьПараметр("Роль", Роль);
        Запрос.УстановитьПараметр("ТекущаяДата", ТекущаяДата());
        
        Выборка = Запрос.Выполнить().Выбрать();
        
        Пока Выборка.Следующий() Цикл
            // Перезаписываем запись, устанавливая дату окончания в Текущую дату
            НаборЗаписей = РегистрыСведений.СвязиСотрудниковИнтервалы.СоздатьНаборЗаписей();
            НаборЗаписей.Отбор.Клиент.Установить(Клиент);
            НаборЗаписей.Отбор.Роль.Установить(Роль);
            НаборЗаписей.Отбор.ДатаНачала.Установить(Выборка.ДатаНачала);
            НаборЗаписей.Прочитать();
            
            Если НаборЗаписей.Количество() > 0 Тогда
                Запись = НаборЗаписей[0];
                Запись.ДатаОкончания = ТекущаяДата();
                НаборЗаписей.Записать(Истина);
            КонецЕсли;
        КонецЦикла;
        
        // Шаг 2. Создаем новую запись для нового сотрудника
        НовыйНабор = РегистрыСведений.СвязиСотрудниковИнтервалы.СоздатьНаборЗаписей();
        НовыйНабор.Отбор.Клиент.Установить(Клиент);
        НовыйНабор.Отбор.Роль.Установить(Роль);
        НовыйНабор.Отбор.ДатаНачала.Установить(ТекущаяДата());
        
        НоваяЗапись = НовыйНабор.Добавить();
        НоваяЗапись.Клиент = Клиент;
        НоваяЗапись.Роль = Роль;
        НоваяЗапись.ДатаНачала = ТекущаяДата();
        НоваяЗапись.Сотрудник = НовыйСотрудник;
        НоваяЗапись.ДатаОкончания = Дата("39991231235959");
        
        НовыйНабор.Записать(Истина);
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение ОписаниеОшибки();
    КонецПопытки;

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

Чтобы получить активного сотрудника на любую дату (например, на сегодня), мы пишем простой запрос:


|ГДЕ
|   &ВыбраннаяДата МЕЖДУ Связи.ДатаНачала И Связи.ДатаОкончания

Преимущества подхода:

---

Решение 3. Корректировка будущих движений (если архитектуру РС менять нельзя)

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

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

Проанализируем логику этого процесса в процедуре записи нового сотрудника:


Процедура ЗаписатьСотрудникаСКорректировкойБудущего(Клиент, Роль, НовыйСотрудник, ДатаНазначения) Экспорт

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

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

Преимущества подхода:

---

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

Мы проанализировали три различных подхода к решению коллизии отложенных изменений. Какой из них выбрать для вашей системы?

← На главную