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