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

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

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

Анализ типичных ошибок в коде

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


// Пример ошибочного кода
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               | ПроверкаРабочегоМеста.Кресло,
               | ПроверкаРабочегоМеста.Кабинет,
               | ПроверкаРабочегоМеста.ВремяНачала,
               | ПроверкаРабочегоМеста.ВремяОкончания
               |ИЗ
               | РегистрыСведений.ПроверкаРабочегоМеста КАК ПроверкаРабочегоМеста";
Результат = Запрос.Выполнить;
Выборка = Результат.Выбрать;

Пока Выборка.Следующий Цикл
    НаборЗаписей = РегистрыСведений.ПроверкаРабочегоМеста.СоздатьНаборЗаписей;
    НаборЗаписей.Отбор.Кресло.Значение = Выборка.Кресло;
    НаборЗаписей.Отбор.Кабинет.Значение = Выборка.Кабинет;
    НаборЗаписей.Отбор.ВремяНачала.Значение = Выборка.ВремяНачала;
    НаборЗаписей.Отбор.ВремяОкончания.Значение = Выборка.ВремяОкончания;
    НаборЗаписей.Прочитать;
    
    Для каждого Запись Из НаборЗаписей Цикл
        Запись.Кресло = "Кресло занято";
        Запись.Кабинет = " ";
        Запись.ВремяНачала= " ";
        Запись.ВремяОкончания= " ";
    КонецЦикла;
    НаборЗаписей.Записать;
КонецЦикла;

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

  1. Синтаксические ошибки вызова методов: Конструкции Запрос.Выполнить и РегистрыСведений.ПроверкаРабочегоМеста.СоздатьНаборЗаписей написаны без скобок вызова (). Платформа воспринимает их как обращение к свойствам, что приводит к некорректной работе.
  2. Ограничения отбора подчиненного регистра: Если регистр сведений подчинен регистратору, свойство Отбор у объекта РегистрСведенийНаборЗаписей содержит единственное доступное поле — Регистратор. Устанавливать отбор по измерениям (Кресло, Кабинет) платформа запрещает. При обращении к ним будет вызвано исключение «Поле объекта не обнаружено».
  3. Попытка фильтрации по ресурсам: Поля ВремяНачала и ВремяОкончания являются ресурсами. Свойство Отбор набора записей в принципе не поддерживает отбор по ресурсам и реквизитам даже в независимых регистрах.
  4. Несоответствие типов данных: Если измерение Кресло имеет ссылочный тип (например, СправочникСсылка.Кресла), а ресурс — тип Дата, им нельзя присваивать строковые значения "Кресло занято" или пустые пробелы " ". Для очистки ссылок используется пустая ссылка, а для дат — пустая дата Дата('00010101').
  5. Недопустимость прямой модификации поля «Регистратор»: Поле Регистратор в строке набора доступно только для чтения. Присваивание ему новых значений (например, Запись.Регистратор = 0;) вызывает ошибку времени выполнения.

Решение 1. Штатная работа с движениями в модуле объекта документа

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

Штатный способ формирования записей — перенос логики в модуль объекта документа. В этом случае обращение к набору записей происходит напрямую через коллекцию Движения:


// Модуль объекта документа-регистратора
Процедура ОбработкаПроведения(Отказ, Режим)
    
    // Очищаем старые движения и взводим флаг записи
    Движения.ПроверкаРабочегоМеста.Записывать = Истина;
    Движения.ПроверкаРабочегоМеста.Очистить();
    
    // Заполняем движения на основании данных табличной части или реквизитов документа
    Для Каждого Стр Из ТабличнаяЧастьСеансы Цикл
        Движение = Движения.ПроверкаРабочегоМеста.Добавить();
        Движение.Врач = Стр.Врач;
        Движение.Кабинет = Стр.Кабинет;
        Движение.Кресло = Стр.Кресло;
        Движение.ДатаПриема = НачалоДня(Стр.ВремяНачала);
        Движение.ВремяНачала = Стр.ВремяНачала;
        Движение.ВремяОкончания = Стр.ВремяОкончания;
    КонецЦикла;
    
КонецПроцедуры

Решение 2. Программное чтение и перезапись набора записей по регистратору

Если необходимо программно прочитать или скорректировать записи регистра для конкретного документа из внешней обработки или серверной процедуры формы, отбор устанавливается строго по ссылке на документ-регистратор:


&НаСервере
Процедура ИзменитьЗаписиПоДокументу(СсылкаНаДокумент)
    
    Набор = РегистрыСведений.ПроверкаРабочегоМеста.СоздатьНаборЗаписей();
    Набор.Отбор.Регистратор.Установить(СсылкаНаДокумент);
    
    // Считываем текущие записи из базы данных
    Набор.Прочитать();
    
    Для Каждого Запись Из Набор Цикл
        // Корректируем ресурсы записи, соблюдая типы данных
        Запись.ВремяНачала = ТекущаяДата();
    КонецЦикла;
    
    // Фиксируем изменения в базе данных
    Набор.Записать();
    
КонецПроцедуры

Решение 3. Проверка занятости кресла через запрос (контроль пересечения интервалов)

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


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

Важное условие пересечения: интервалы пересекаются тогда и только тогда, когда ВремяНачала < &ВремяОкончания И ВремяОкончания > &ВремяНачала. Если запрос возвращает хотя бы одну строку, рабочее место уже занято другим приемом.

Рекомендации по структуре метаданных

Обратите внимание на режим записи регистра сведений. Подчинение регистратору оправдано, когда фиксация занятости жестко привязана к первичным документам (например, документу «Запись на прием»). Если требуется хранить долгосрочное закрепление рабочего места за сотрудником, логичнее перевести регистр в режим Периодический (периодичность «В пределах дня» или «Позиция регистратора») либо сделать его Независимым. В независимом регистре появляется возможность использовать точечный отбор по любым измерениям и обращаться к объекту СоздатьМенеджерЗаписи().

← На главную