Почему программно добавленные движения документа не отображаются в регистрах?

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

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

Мы проанализируем основные методы работы с движениями и выявим наиболее частые ошибки, которые приводят к "невидимости" движений.

Основные подходы к работе с движениями документа

В системе 1С:Предприятие существует два основных способа программного управления движениями документа. Мы рассмотрим каждый из них подробнее.

  1. Через свойство Движения объекта документа.

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

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

    • Как это работает:

      1. Для каждого нужного регистра в коллекции Движения необходимо установить свойство Записывать в значение Истина. Например: Движения.ИмяРегистра.Записывать = Истина;.

      2. После этого мы добавляем новые записи в набор через метод Движения.ИмяРегистра.Добавить() и устанавливаем значения полей этих записей.

      3. Платформа автоматически запишет эти движения в базу данных при завершении процедуры ОбработкаПроведения, если флаг Записывать остался Истина.

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

  2. Через создание набора записей регистра напрямую (РегистрыНакопления.ИмяРегистра.СоздатьНаборЗаписей()).

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

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

    • Как это работает (ключевые шаги):

      1. Нам необходимо явно создать объект НаборЗаписей для нужного регистра. Например:

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

        
        НаборЗаписей.Отбор.Регистратор.Значение = СсылкаНаДокумент;
        
      3. После установки отбора мы можем вызвать метод НаборЗаписей.Прочитать() для загрузки существующих движений, если их нужно модифицировать или проверить наличие. Этот шаг не всегда обязателен, если мы просто хотим удалить старые движения и записать новые.

      4. Для добавления новых движений в набор используем метод НаборЗаписей.Добавить(), после чего заполняем поля движения.

      5. Затем вызываем метод НаборЗаписей.Записать(), чтобы сохранить изменения в базе данных.

Частые причины "невидимости" движений

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

  1. Случайное удаление движений.

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

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

      Пример кода, который приведет к удалению всех движений документа:

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

      В этом случае, даже если до этого были какие-то движения, они будут удалены, и мы не увидим ничего. Метод Прочитать() перед Очистить() не требуется для удаления, так как Записать() с пустым набором и отбором самостоятельно удалит соответствующие записи.

  2. Неправильное использование Записать() в процедуре ОбработкаПроведения.

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

  3. Отсутствие или некорректный отбор при работе с прямым набором записей.

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

  4. Использование метода ПолучитьОбъект() в цикле или когда достаточно ссылки.

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

    Например, в коде из темы форума:

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

    Здесь мы видим попытку получить объект в цикле и затем записать движения через свойство Движения. Если этот код выполняется вне ОбработкаПроведения, то запись движений через МДок.Движения может быть неэффективной или некорректной, так как документ МДок может быть не записан или неактуален. Гораздо надежнее использовать прямой набор записей с отбором по ссылке.

Подробно разбираем решение: Как правильно добавлять и удалять движения

Давайте рассмотрим правильные подходы к управлению движениями в зависимости от сценария.

Сценарий 1: Добавление (перезапись) движений в процедуре ОбработкаПроведения

Когда мы работаем внутри процедуры ОбработкаПроведения, мы используем свойство Движения объекта документа.

  1. Устанавливаем флаг записи для регистра:

    
    Движения.ИмяРегистра.Записывать = Истина;
    

    Это сообщит платформе, что по этому регистру необходимо сформировать и записать движения.

  2. Очищаем существующие движения (если требуется полная перезапись):

    
    Движения.ИмяРегистра.Очистить();
    

    Этот метод очищает набор записей в памяти, относящийся к текущему документу. Платформа при записи автоматически удалит старые движения и запишет новые.

  3. Добавляем новые записи и заполняем их:

    
    НоваяЗапись = Движения.ИмяРегистра.Добавить();
    НоваяЗапись.Период = ДатаДокумента;
    НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход; // Или Расход
    НоваяЗапись.Измерение1 = ЗначениеИзмерения1;
    НоваяЗапись.Ресурс1 = ЗначениеРесурса1;
    // Добавляем другие записи по аналогии
    
  4. Завершение: В конце процедуры ОбработкаПроведения платформа автоматически запишет все сформированные движения, для которых был установлен флаг Записывать = Истина.

Сценарий 2: Добавление или удаление движений вне процедуры ОбработкаПроведения (например, в обработке)

Для таких случаев мы используем прямой набор записей регистра.

  1. Создаем набор записей:

    
    НаборЗаписей = РегистрыНакопления.ИмяРегистра.СоздатьНаборЗаписей();
    
  2. Устанавливаем отбор по регистратору (это критически важно!):

    
    НаборЗаписей.Отбор.Регистратор.Значение = СсылкаНаДокумент;
    

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

  3. Для удаления всех движений документа по регистру:

    Если наша цель — полностью удалить все существующие движения для данного документа в этом регистре, мы поступаем так:

    
    НаборЗаписей.Очистить(); // Очищаем набор в памяти
    НаборЗаписей.Записать(); // Записываем пустой набор, что приводит к удалению всех записей по отбору
    

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

  4. Для добавления новых движений (с перезаписью старых):

    Если нам нужно заменить старые движения новыми (аналогично проведению документа), мы сначала очищаем, а затем добавляем:

    
    НаборЗаписей.Очистить(); // Удаляем старые движения
    // Добавляем новые записи
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Период = ДатаДокумента;
    НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход;
    // ... заполняем другие поля
    НаборЗаписей.Записать(); // Записываем новые движения
    
  5. Для добавления движений без удаления старых:

    В некоторых случаях нам может потребоваться добавить движения к уже существующим, не удаляя старые. Для этого используем параметр Замещать метода Записать(), установив его в Ложь:

    
    // Сначала, возможно, читаем существующие, чтобы их не потерять, если нужно
    // НаборЗаписей.Прочитать();
            
    // Добавляем новые записи в уже существующий набор (или в пустой, если не читали)
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Период = ДатаДокумента;
    НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход;
    // ...
            
    НаборЗаписей.Записать(Ложь); // Записываем новые записи, не удаляя старые
    

    Будьте внимательны с этим подходом, так как это может привести к дублированию, если не контролировать уникальность записей.

  6. Для изменения существующих движений:

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

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

Рекомендации по производительности

Давайте также обратим внимание на производительность при работе с движениями:

Мы видим, что проблема "невидимости" движений часто кроется в неправильном понимании или использовании методов Очистить() и Записать(), особенно в сочетании с отбором по регистратору. Всегда помните о контексте, в котором вы работаете с движениями (внутри ОбработкаПроведения или вне ее), и выбирайте соответствующий подход. Правильное использование НаборЗаписей.Отбор.Регистратор.Значение является ключевым элементом для точной и безопасной работы с движениями документа.

← К списку