В процессе разработки учетных систем, особенно в логистике и управлении автотранспортом, часто возникает задача контроля «наложений» времени. Типичный пример: один и тот же водитель не может физически находиться в двух разных рейсах одновременно. Если оператор по ошибке вводит данные, которые создают пересечение периодов, система должна заблокировать запись документа или выдать предупреждение.
В этой статье мы подробно разберем, как математически правильно вычислить пересечение интервалов, как составить эффективный запрос в 1С и какие нюансы следует учитывать при работе в многопользовательском режиме.
Прежде чем переходить к написанию кода, давайте определимся с логическим условием. Пусть у нас есть два интервала:
Начало1 — Конец1Начало2 — Конец2Многие начинающие программисты пытаются описать все возможные варианты пересечения (когда начало второго внутри первого, когда конец второго внутри первого, когда второй полностью поглощает первый и т.д.). Это приводит к громоздким конструкциям с множеством условий ИЛИ, как мы видим в сообщении №13 форума.
Однако существует изящная и короткая формула, которая описывает любое пересечение:
Начало1 < Конец2 И Конец1 > Начало2
Рассмотрим, почему это работает. Интервалы не пересекаются только в двух случаях:
Конец1 <= Начало2).Начало1 >= Конец2).Отрицание этих двух условий (по закону де Моргана) дает нам универсальную формулу пересечения. Важно учитывать граничные условия. Как отмечено в сообщении №3, если один рейс заканчивается в 17:30, а другой начинается ровно в 17:30 — это нормально. В таком случае мы используем строгие знаки «меньше» и «больше». Если же даже «стыковка» в одну секунду запрещена, знаки следует заменить на нестрогие (<= и >=).
Часто проверка требуется внутри одного документа, например, в «Разнарядке» или «Путевом листе», где в табличной части перечислено несколько ходок одного водителя. Проанализируем решение, предложенное в сообщении №13. Автор использует внутреннее соединение таблицы самой с собой.
Разберем по шагам, как построить такой запрос:
Водитель = Водитель).НомерСтроки <> НомерСтроки).Пример оптимизированного запроса для проверки пересечений внутри табличной части:
ВЫБРАТЬ
ТЧ1.НомерСтроки КАК НомерСтрокиА,
ТЧ2.НомерСтроки КАК НомерСтрокиБ,
ТЧ1.Водитель КАК Водитель
ИЗ
Документ.Разнарядка.Разнарядка КАК ТЧ1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.Разнарядка.Разнарядка КАК ТЧ2
ПО ТЧ1.Ссылка = ТЧ2.Ссылка
И ТЧ1.Водитель = ТЧ2.Водитель
И ТЧ1.НомерСтроки < ТЧ2.НомерСтроки
ГДЕ
ТЧ1.Ссылка = &СсылкаНаДокумент
И ТЧ1.ДатаВыезда < ТЧ2.ДатаВозврата
И ТЧ1.ДатаВозврата > ТЧ2.ДатаВыезда
И ТЧ1.Водитель <> ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка)
Обратите внимание на условие ТЧ1.НомерСтроки < ТЧ2.НомерСтроки. Использование знака «меньше» вместо «не равно» позволяет избежать дублирования результатов (система не будет выдавать две ошибки: «Строка 1 пересекается со строкой 2» и «Строка 2 пересекается со строкой 1»).
Проверка только внутри одного документа недостаточна. Водитель мог быть назначен на рейс в другом документе, который был проведен ранее. Для полноценного контроля нам нужно проанализировать все существующие записи в базе данных.
Рассмотрим алгоритм комплексной проверки в модуле документа (процедура ПередЗаписью или ОбработкаПроверкиЗаполнения):
В специализированных решениях, таких как «1С:Управление автотранспортом» (УАТ), информацию о занятости лучше брать из регистров сведений, например, уатЗанятостьВодителей. Это работает быстрее, так как данные в регистрах проиндексированы по измерениям (Водитель, ТС).
Проанализируем критически важный аспект: многопользовательскую работу. Представим ситуацию: два диспетчера одновременно открыли разные документы «Разнарядка» и ставят Иванова на одно и то же время. Если оба нажмут «Провести» одновременно, запрос на проверку пересечений у обоих вернет пустой результат, так как данные еще не записаны в базу. В итоге мы получим два проведенных документа с ошибкой.
Чтобы этого избежать, выясним, как работают управляемые блокировки. Перед выполнением запроса проверки необходимо установить исключительную блокировку на объект «Водитель» в заданном интервале времени.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.уатЗанятостьВодителей");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьПараметр("Водитель", ТекущийВодитель);
// Устанавливаем диапазон времени, который хотим заблокировать
ЭлементБлокировки.ИспользоватьИзДанных("Период", СписокПериодовДокумента);
Блокировка.Заблокировать();
Только после вызова Блокировка.Заблокировать() можно выполнять запрос. Если другой пользователь уже начал проводить документ по этому водителю, текущий сеанс подождет завершения транзакции соседа и увидит актуальные данные.
Выдавать ошибку в момент записи — это надежно, но не всегда удобно для пользователя. Рассмотрим, как сделать интерфейс более дружелюбным:
ПриИзменении для полей даты и водителя. Программу можно настроить так, чтобы она сразу подсвечивала строку красным, если обнаружено пересечение, не дожидаясь попытки записи.Использование этих методов значительно сократит количество ошибок ввода и повысит общую эффективность работы транспортного отдела.
Мы проанализировали различные подходы к решению задачи пересечения временных интервалов. Подведем итог: наиболее эффективным методом является использование лаконичной математической формулы в запросе 1С с обязательным использованием механизма управляемых блокировок. Если вы работаете с типовой конфигурацией (УАТ), всегда старайтесь использовать существующие регистры занятости ресурсов вместо прямого обращения к табличным частям других документов.