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