Как правильно прочитать и проверить подчиненный регистр сведений в 1С

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

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

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

Ошибки при установке отбора в подчиненном регистре сведений

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

Ошибка записи! Не установлен отбор по регистратору (Регистр сведений: ...)

В платформе 1С действует жесткое системное ограничение: для объекта РегистрСведенийНаборЗаписей, подчиненного регистратору, отбор программно может быть установлен только по одному полю — Регистратор. Установить отбор по измерениям (например, Кабинет, Кресло или Дата) через свойства НаборЗаписей.Отбор невозможно.

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


НаборЗаписей = РегистрыСведений.ПроверкаРабочегоМеста.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ВыбранныйРегистратор);
НаборЗаписей.Прочитать();

Для Каждого Запись Из НаборЗаписей Цикл
    // Чтение или изменение реквизитов записи
    Сообщить(Строка(Запись.ВремяНачала) + " - " + Строка(Запись.ВремяОкончания));
КонецЦикла;

// Запись набора выполняется только при необходимости модификации движений
НаборЗаписей.Записать();

Если требуется прочитать данные с фильтрацией по конкретным измерениям (например, по креслу или кабинету), использовать набор записей не следует. В таких случаях мы используем объект Запрос, где условия накладываются свободно в секции ГДЕ.

Архитектура записи движений: документ и подчиненный регистр

Выясним причину, почему перезапись набора записей вручную через НаборЗаписей.Записать() в модулях форм или перед записью документа приводит к коллизиям. Подчиненный регистр сведений предназначен для хранения движений документов. Жизненный цикл этих записей должен управляться проведением:

  1. Запись в регистр должна выполняться в процедуре ОбработкаПроведения модуля документа через коллекцию Движения.ПроверкаРабочегоМеста.
  2. Интерактивные проверки в форме (событие ПередЗаписью) или модуле объекта не должны модифицировать регистр вручную. Они должны только считывать существующие данные запросом для выявления пересечений.
  3. Если документ снимается с проведения или отменяется, платформа автоматически очищает его движения по регистратору, что гарантирует целостность базы.

Корректная проверка пересечения интервалов времени

Когда требуется исключить назначение двух сотрудников на одно рабочее место в одно и то же время, важно корректно составить математическое условие пересечения отрезков времени. Попытка перечислять множество условий с операторами И и ИЛИ часто приводит к пропускам пересечений.

Два интервала времени [Начало1, Окончание1] и [Начало2, Окончание2] гарантированно пересекаются тогда и только тогда, когда выполняется строгое условие:

Начало1 < Окончание2 И Окончание1 > Начало2

Если соприкосновение интервалов "стык в стык" (например, один прием заканчивается в 14:00, а второй начинается в 14:00) также считается недопустимым, строгие знаки заменяются на нестрогие (<= и >=).

Кроме того, при повторном проведении документа необходимо исключать его собственные движения из выборки с помощью условия Регистр.Регистратор <> &Ссылка, иначе документ будет блокировать сам себя.

Пошаговый пример реализации контроля занятости в ОбработкеПроведения

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

  1. Передаем табличную часть документа во временную таблицу запроса.
  2. Индексируем временную таблицу по проверяемым полям (Кабинет, Кресло).
  3. Соединяем временную таблицу с регистром сведений по совпадению рабочего места и условию пересечения временных интервалов.
  4. Устанавливаем управляемую блокировку данных для исключения конкурентных записей при одновременной работе пользователей.
  5. Если найдены пересечения — информируем пользователя и выставляем флаг Отказ = Истина.

Посмотрим на пример процедуры проведения в модуле объекта документа:


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

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

Оптимизация структуры метаданных

Для упрощения логики расписания рассмотрим две архитектурные рекомендации:

  1. Подчинение справочников: Если в каждом кабинете есть свои уникальные кресла (Кресло 1, Кресло 2), целесообразно сделать справочник Кресла подчиненным справочнику Кабинеты. Тогда кабинет является владельцем кресла, и хранить кабинет отдельным измерением в регистре больше не требуется — он однозначно получается из реквизита Кресло.Владелец.
  2. Квантование времени (слоты): Вместо непрерывных отрезков произвольной длины можно использовать независимый регистр сведений с периодичностью (например, слоты по 15 или 30 минут). В таком регистре измерением выступает Кресло и ВремяСлота. Это исключает сложные проверки пересечений интервалов: попытка двух документов записать данные на один и тот же слот будет пресекаться платформой автоматически по контролю уникальности измерений.
← На главную