Почему пропадают записи из подчиненного регистра сведений при проведении документа?

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

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

Анализ типичных ошибок: почему стираются данные

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


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

Выясним причины некорректного поведения системы в данном сценарии:

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

Правильная архитектура: разделение проверки и записи

Разберем по шагам, как необходимо проектировать логику проведения документов, контролирующих занятость ресурсов. Процесс разделяется на два строгих этапа:

  1. Проверка возможности проведения: поиск пересечений интервалов времени и занятости рабочих мест с помощью одного запроса к базе данных без изменения существующих данных.
  2. Формирование движений: создание записей в регистре через стандартное свойство документа Движения только в том случае, если флаг Отказ остался равным Ложь.

Шаг 1. Контроль пересечений и занятости через запрос

Посмотрим на пример запроса, который сопоставляет строки документа с уже зарегистрированными записями в регистре сведений. При этом крайне важно соблюдать следующие правила:

  1. Исключать движения текущего документа (условие Регистратор <> &Ссылка), иначе при перепроведении документ заблокирует сам себя.
  2. Проверять строгое пересечение интервалов времени. Если окончание предыдущего приема и начало следующего могут совпадать (стык в стык), условия на границы времени должны быть строгими.
  3. Передавать табличную часть документа в запрос через параметр (выгруженную таблицу значений) во временную таблицу, чтобы проверять именно текущие, еще не записанные в базу данные документа.

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

Запрос.УстановитьПараметр("ТабПриемов", ТабПриемов.Выгрузить());
Запрос.УстановитьПараметр("ТекущийДокумент", Ссылка);

РезультатЗапроса = Запрос.Выполнить();

Если Не РезультатЗапроса.Пустой() Тогда
    Выборка = РезультатЗапроса.Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщить(СтрШаблон(
            "Рабочее место занято! Кабинет: %1, кресло: %2 занято документом %3 в период с %4 по %5",
            Выборка.Кабинет,
            Выборка.НомерКресла,
            Выборка.Регистратор,
            Выборка.ЗанятоС,
            Выборка.ЗанятоПо
        ));
        Отказ = Истина;
    КонецЦикла;
КонецЕсли;

Шаг 2. Формирование движений в регистре сведений

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


// 2. Формирование движений документа
Если Не Отказ Тогда

    Движения.ПроверкаРабочегоМеста.Записывать = Истина;
    
    Для Каждого ТекСтрока ИЗ ТабПриемов Цикл
        Движение = Движения.ПроверкаРабочегоМеста.Добавить();
        Движение.Кабинет        = ТекСтрока.Кабинет;
        Движение.Кресло         = ТекСтрока.НомерКресла;
        Движение.ВремяНачала    = ТекСтрока.ВремяНачала;
        Движение.ВремяОкончания = ТекСтрока.ВремяОкончания;
    КонецЦикла;
    
КонецЕсли;

Защита от параллельной записи (управляемые блокировки)

В многопользовательской среде два оператора могут одновременно нажать кнопку проведения для пересекающихся по времени приемов. Чтобы исключить ситуацию race condition, перед выполнением контрольного запроса накладываем управляемую исключительную блокировку на проверяемые данные:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.ДляОбласти("РегистрСведений.ПроверкаРабочегоМеста");
ЭлементБлокировки.ИсточникДанных = ТабПриемов;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Кабинет", "Кабинет");
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Кресло", "НомерКресла");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

Такой подход обеспечивает целостность информации, исключает случайное стирание записей других документов и гарантирует корректную работу конфигурации под нагрузкой.

← На главную