Как запретить закрепление водителя за несколькими машинами в один день в 1С?

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

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

Многие начинающие разработчики сталкиваются с трудностями при проектировании структуры метаданных для такой задачи, что приводит к ошибкам вида Запись с такими ключевыми полями существует или, наоборот, к появлению дублей. Рассмотрим подробно, как правильно спроектировать регистр сведений и реализовать программную проверку.

Основные ошибки при проектировании регистра

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

Давайте разберем, как платформа 1С определяет уникальность записи в регистре сведений:

  1. Ключ уникальности записи состоит из комбинации значений всех Измерений + Периода (если регистр периодический).
  2. Если мы делаем измерениями и Водителя, и Машину, то для системы следующие две записи являются разными:
    • Запись 1: 01.01.2024 | Иванов | Грузовик
    • Запись 2: 01.01.2024 | Иванов | Легковая

С точки зрения платформы, эти записи не противоречат друг другу, так как комбинация "Иванов + Грузовик" не равна "Иванов + Легковая". Именно поэтому такой подход не позволяет автоматически контролировать занятость водителя.

Правильная архитектура Регистра сведений

Чтобы реализовать правило "Один водитель — одна машина в день", нам необходимо изменить структуру регистра сведений. Ключевым полем, определяющим уникальность, должен остаться только Водитель (в рамках периода).

Рассмотрим правильную настройку свойств регистра сведений в конфигураторе:

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

При такой структуре, если в базе уже есть запись на 01.01.2024 по водителю "Иванов", попытка записать туда же водителя "Иванов" (даже с другой машиной) вызовет ошибку платформы на уровне базы данных, так как ключевое поле (Измерение "Водитель") уже занято.

Программная реализация проверки

Хотя правильная структура регистра сама по себе предотвратит дублирование, полагаться только на системную ошибку (исключение) — плохая практика с точки зрения пользовательского опыта (UX). Пользователь увидит непонятное техническое сообщение.

Разработаем алгоритм проверки в модуле объекта документа, в процедуре ОбработкаПроведения. Наша цель — вежливо сообщить пользователю о занятости водителя и отменить проведение.

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

Рассмотрим код решения:


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

    // 4. Если проверки пройдены — записываем движения
    // Важно: если регистр подчиненный, движения формируются здесь
    Движения.ЗанятостьВодителей.Записывать = Истина;
    Движение = Движения.ЗанятостьВодителей.Добавить();
    Движение.Период = НачалоДня(Дата);
    Движение.Водитель = Водитель;
    Движение.ТранспортноеСредство = Машина; // Это Ресурс
    
КонецПроцедуры

Разберем ключевые моменты кода

Использование НачалоДня(Дата)
Поскольку мы хотим контролировать уникальность в рамках всего дня, крайне важно приводить дату документа к началу дня. Если этого не сделать, документ от 12:00 и документ от 14:00 будут иметь разные периоды (если регистр имеет периодичность "по позиции регистратора" или "секунда"), и проверка не сработает. При периодичности регистра "В пределах дня" платформа сама отбрасывает время, но в запросе лучше явно указывать начало дня для однозначности.

Условие Регистратор <> &Ссылка
Это критически важное условие. Когда вы перепроводите уже существующий документ, запись в регистре уже может существовать (созданная этим же документом ранее). Без этого условия документ будет "находить сам себя" и блокировать собственное перепроведение. Однако, стоит помнить: если регистр подчиненный, то при начале проведения платформа обычно автоматически очищает старые движения этого документа. В таком случае условие Регистратор <> &Ссылка служит дополнительной страховкой, либо используется, если мы читаем данные "Среза последних" в независимом режиме.

Почему нельзя искать по Документам?
Иногда возникает идея искать дубли не в регистре, а прямым запросом к таблице документов Документ.Накладная. Этого делать не следует по нескольким причинам:

Резюме

Для решения задачи "Один водитель — одна машина в день" выполним следующие шаги:

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

Такой подход обеспечивает надежность данных и корректное поведение системы даже при высоких нагрузках.

← На главную