Почему в ОбработкаПроведения движения по регистру пустые, а в базе они есть?

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

При разработке и отладке механизмов проведения документов в конфигурациях на обычных формах (таких как «Управление торговлей 10.3», «Управление производственным предприятием 1.3» или «Комплексная автоматизация 1.1») программисты часто сталкиваются с крайне загадочным поведением платформы «1С:Предприятие».

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

Самое интересное начинается дальше. Если мы продолжим выполнение кода, завершим проведение документа и откроем отчет по движениям в пользовательском режиме, мы увидим, что движения по регистру ПартииТоваровНаСкладах на самом деле есть и они заполнены корректно. Более того, если во время отладки в самом конце процедуры ОбработкаПроведения выполнить запрос к этому регистру с отбором по ссылке нашего документа, запрос вернет все строки движений!

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

Причина №1: Использование обособленного набора записей (СоздатьНаборЗаписей)

Главная причина «невидимости» движений в локальной коллекции Движения документа кроется в том, каким именно объектом выполняется запись данных в базу данных.

В типовых конфигурациях 1С, использующих партионный учет (например, УТ 10.3), алгоритмы списания партий вынесены в специализированные общие модули. Основной метод, отвечающий за расчет себестоимости и проведение по партиям, — это процедура ДвижениеПартийТоваров в общем модуле УправлениеЗапасамиПартионныйУчет.

Этот механизм спроектирован как универсальное решение. Оно должно работать одинаково эффективно в двух принципиально разных сценариях:

  1. При интерактивном или программном проведении конкретного документа пользователем.
  2. При массовом перепроведении документов или восстановлении последовательности партионного учета с помощью обработки «Проведение по партиям».

Когда обработка «Проведение по партиям» выполняет восстановление последовательности, она работает непосредственно со ссылками на документы. У нее нет в оперативной памяти загруженных объектов ДокументОбъект с их локальными контекстами и коллекциями Движения. Чтобы код списания партий был единым и не зависел от контекста вызова, разработчики фирмы «1С» реализовали запись движений по регистру ПартииТоваровНаСкладах через создание независимого объекта набора записей.

Схематично этот код выглядит следующим образом:


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

// Выполняется расчет партий, заполняется таблица движений
// ... (алгоритмы расчета FIFO/LIFO) ...

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

Поскольку физическая запись в таблицы СУБД производится через сторонний объект НаборЗаписей, локальная коллекция текущего документа (доступная через ЭтотОбъект.Движения.ПартииТоваровНаСкладах) в памяти отладчика ничего об этом действии не знает. Для платформы эта коллекция остается неинициализированной и пустой.

Причина №2: Особенности логики Расхода (Реализация) и Прихода (Поступление)

Проанализируем, почему в документе ПоступлениеТоваровУслуг (Приход) движения по партиям видны в отладчике сразу, а в документе РеализацияТоваровУслуг (Расход) — нет. Это связано с разным подходом к формированию движений:

Почему запрос внутри транзакции возвращает движения?

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

Это абсолютно стандартное и логичное поведение СУБД и платформы «1С:Предприятие»:

  1. В начале проведения документа платформа автоматически открывает транзакцию базы данных.
  2. Когда в коде общего модуля вызывается метод НаборЗаписей.Записать(), платформа отправляет физические SQL-команды INSERT в таблицы базы данных.
  3. Эти данные записываются во временное пространство СУБД. Они еще не зафиксированы окончательно (коммит транзакции произойдет только после успешного завершения процедуры ОбработкаПроведения).
  4. Любой запрос, который мы выполняем в рамках этой же транзакции (в том же самом сеансе отладки), имеет полный доступ к этим измененным данным. СУБД позволяет читать свои собственные незафиксированные изменения внутри одной транзакции. Именно поэтому запрос возвращает строки движений, в то время как локальное свойство Движения.ПартииТоваровНаСкладах в памяти остается пустым.

Как проверить и увидеть движения в отладчике

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

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

Для этого в самом конце процедуры ОбработкаПроведения (или в табло отладчика) достаточно выполнить метод Прочитать() для конкретного регистра:


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

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

Проанализируем этот процесс на практическом примере кода, который можно временно встроить для целей отладки:


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

Влияние режима отложенного списания партий

При анализе партионного учета обязательно нужно учитывать настройки учетной политики вашей информационной базы. В конфигурациях уровня УТ 10.3 существует важный флаг: «Списывать партии при проведении документов».

Рассмотрим, как эта настройка меняет логику проведения документов:

Резюме и рекомендации

Подведем итог нашему исследованию:

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