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

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом Управленческий учет
← К списку

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

Разберем исходную проблему и ее проявления

Автор проблемы описал следующий сценарий:

  1. Первое проведение документа: Записи регистра сведений успешно создаются.
  2. Второе проведение (перепроведение) документа: Записи по этому регистру удаляются.
  3. Третье проведение документа: Записи снова создаются.

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

Анализируем исходный код

Предположим, у нас есть процедура, формирующая движения, и вспомогательная процедура для удаления старых записей:


Процедура СформироватьДвиженияИсторииДополнительныхПоказателейШтатногоРасписания(Регистратор) Экспорт
    // Удаляем старые движения регистра
    УдалитьСтарыеЗаписиРегистра(Регистратор);

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

    // Добавляем новые записи
    Для каждого СтрокаТЧ из Регистратор.Позиции Цикл
        НоваяЗапись = НаборЗаписейДобавление.Добавить();

        НоваяЗапись.Регистратор = Регистратор;
        НоваяЗапись.Дата = Дата;

        Если Не СтрокаТЧ.Позиция.Закрыта Тогда
            НоваяЗапись.Используется = Истина;
        Иначе
            НоваяЗапись.Используется = Ложь;
        КонецЕсли;

        НоваяЗапись.ПозицияШтатногоРасписания = СтрокаТЧ.Позиция;
        НоваяЗапись.КлассПозицииШтатногоРасписания = СтрокаТЧ.КлассПозицииШтатногоРасписания;
        НоваяЗапись.ПА_Категория = СтрокаТЧ.ПА_Категория;
        НоваяЗапись.ПА_Участок = СтрокаТЧ.ПА_Участок;
        НоваяЗапись.ПА_РазрядКатегорияИТР = СтрокаТЧ.ПА_РазрядКатегорияИТР;
    КонецЦикла;
    НаборЗаписейДобавление.Записать();
КонецПроцедуры

Процедура УдалитьСтарыеЗаписиРегистра(Регистратор)
    НаборЗаписейУдаления = РегистрыСведений.ИсторияДополнительныхПоказателейШтатногоРасписания.СоздатьНаборЗаписей();
    НаборЗаписейУдаления.Отбор.Регистратор.Установить(Регистратор);
    НаборЗаписейУдаления.Прочитать();
    НаборЗаписейУдаления.Записать();
КонецПроцедуры

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

Давайте проанализируем ошибки в представленном коде:

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

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

  2. Ошибочная процедура удаления: Процедура УдалитьСтарыеЗаписиРегистра(Регистратор) также содержит логическую ошибку, которая усугубляет проблему. В ней мы создаем набор записей, устанавливаем отбор по регистратору, читаем записи (НаборЗаписейУдаления.Прочитать()) и сразу же их записываем (НаборЗаписейУдаления.Записать()). Если мы хотим удалить записи, то после Прочитать() необходимо явно очистить набор записей методом НаборЗаписейУдаления.Очистить(), а уже потом вызывать Записать(). В противном случае, если записи были прочитаны, они просто перезапишутся теми же данными.

    Однако, для подчиненных регистров сведений, вызов НаборЗаписей.Записать() с установленным отбором по регистратору (даже если набор пуст или содержит старые записи) приводит к тому, что все старые записи для данного регистратора удаляются, а затем записываются те, что находятся в текущем наборе. Это объясняет, почему при повторном проведении записи могли "удаляться" — по сути, они замещались пустым набором (если в процедуре формирования движений не было новых записей, или они не успевали сформироваться до вызова Записать() в процедуре удаления).

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

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

Правильный подход к работе с подчиненными регистрами сведений

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

  1. Автоматическое управление движениями: Платформа 1С по умолчанию (если свойство регистра "Удаление движений" установлено в "Удалять автоматически при отмене проведения" или "Удалять автоматически") берет на себя ответственность за удаление старых движений документа перед вызовом процедуры ОбработкаПроведения. Это гарантирует, что при каждом проведении документа вы начинаете с "чистого листа" для движений именно этого документа.

  2. Целостность данных: Использование коллекции Движения документа обеспечивает, что все операции с записями регистра, подчиненного регистратору, выполняются в рамках транзакции проведения документа и корректно связываются с этим документом.

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

Решение проблемы: Корректируем код

Мы выяснили, что ключ к решению проблемы — это использование объекта Регистратор.Движения. Давайте посмотрим, как будет выглядеть исправленный код.

  1. Удаляем процедуру УдалитьСтарыеЗаписиРегистра: Она нам больше не понадобится, так как платформа сама позаботится об удалении старых движений.

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

Вот как будет выглядеть исправленная процедура СформироватьДвиженияИсторииДополнительныхПоказателейШтатногоРасписания:


Процедура СформироватьДвиженияИсторииДополнительныхПоказателейШтатногоРасписания(Регистратор) Экспорт
    // Платформа 1С сама очистит старые движения документа перед вызовом этой процедуры
    // или при записи нового набора движений, если регистр подчинен регистратору.
    // Поэтому явное удаление старых записей не требуется.

    // Корректное обращение к набору записей подчиненного регистра сведений
    НаборЗаписейДобавление = Регистратор.Движения.ИсторияДополнительныхПоказателейШтатногоРасписания;
    // Отбор по регистратору устанавливается автоматически при таком подходе,
    // но мы можем явно очистить набор, чтобы гарантировать запись только новых данных.
    НаборЗаписейДобавление.Очистить();

    // Определяем дату в зависимости от типа документа
    Перем Дата;
    Если ТипЗнч(Регистратор) = Тип("ДокументСсылка.ИзменениеШтатногоРасписания") Тогда
        Дата = Регистратор.ДатаВступленияВСилу;
    ИначеЕсли ТипЗнч(Регистратор) = Тип("ДокументСсылка.УтверждениеШтатногоРасписания") Тогда
        Дата = Регистратор.МесяцВступленияВСилу;
    КонецЕсли;

    // Добавляем новые записи
    Для каждого СтрокаТЧ из Регистратор.Позиции Цикл
        НоваяЗапись = НаборЗаписейДобавление.Добавить();

        НоваяЗапись.Регистратор = Регистратор; // Регистратор обычно заполняется автоматически, но явное указание не повредит
        НоваяЗапись.Дата = Дата;

        Если Не СтрокаТЧ.Позиция.Закрыта Тогда
            НоваяЗапись.Используется = Истина;
        Иначе
            НоваяЗапись.Используется = Ложь;
        КонецЕсли;

        НоваяЗапись.ПозицияШтатногоРасписания = СтрокаТЧ.Позиция;
        НоваяЗапись.КлассПозицииШтатногоРасписания = СтрокаТЧ.КлассПозицииШтатногоРасписания;
        НоваяЗапись.ПА_Категория = СтрокаТЧ.ПА_Категория;
        НоваяЗапись.ПА_Участок = СтрокаТЧ.ПА_Участок;
        НоваяЗапись.ПА_РазрядКатегорияИТР = СтрокаТЧ.ПА_РазрядКатегорияИТР;
    КонецЦикла;

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

Обратите внимание на ключевые изменения:

Таким образом, мы видим, что решение, предложенное в сообщении 12 на форуме — "Создал новые через НаборЗаписейДобавление = Регистратор.Движения.ИсторияДополнительныхПоказателейШтатногоРасписания; и все заработало" — является абсолютно верным и соответствует рекомендуемой методике работы с подчиненными регистрами сведений в 1С.

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

← К списку