При разработке и отладке механизмов проведения документов в конфигурациях на обычных формах (таких как «Управление торговлей 10.3», «Управление производственным предприятием 1.3» или «Комплексная автоматизация 1.1») программисты часто сталкиваются с крайне загадочным поведением платформы «1С:Предприятие».
Представим ситуацию: мы открываем процедуру ОбработкаПроведения, например, в документе РеализацияТоваровУслуг. Ставим точку останова в самом конце процедуры, чтобы проанализировать сформированные движения. Мы смотрим на коллекцию Движения.ПартииТоваровНаСкладах и видим, что она абсолютно пуста (количество элементов равно нулю). При этом по остальным регистрам накопления движения успешно заполнены.
Самое интересное начинается дальше. Если мы продолжим выполнение кода, завершим проведение документа и откроем отчет по движениям в пользовательском режиме, мы увидим, что движения по регистру ПартииТоваровНаСкладах на самом деле есть и они заполнены корректно. Более того, если во время отладки в самом конце процедуры ОбработкаПроведения выполнить запрос к этому регистру с отбором по ссылке нашего документа, запрос вернет все строки движений!
Как такое возможно? Почему в базе данных записи уже физически существуют и доступны для чтения запросом, а локальная коллекция Движения документа в памяти отладчика пуста? Разберем эту классическую архитектурную особенность платформы 1С во всех технических подробностях.
Главная причина «невидимости» движений в локальной коллекции Движения документа кроется в том, каким именно объектом выполняется запись данных в базу данных.
В типовых конфигурациях 1С, использующих партионный учет (например, УТ 10.3), алгоритмы списания партий вынесены в специализированные общие модули. Основной метод, отвечающий за расчет себестоимости и проведение по партиям, — это процедура ДвижениеПартийТоваров в общем модуле УправлениеЗапасамиПартионныйУчет.
Этот механизм спроектирован как универсальное решение. Оно должно работать одинаково эффективно в двух принципиально разных сценариях:
Когда обработка «Проведение по партиям» выполняет восстановление последовательности, она работает непосредственно со ссылками на документы. У нее нет в оперативной памяти загруженных объектов ДокументОбъект с их локальными контекстами и коллекциями Движения. Чтобы код списания партий был единым и не зависел от контекста вызова, разработчики фирмы «1С» реализовали запись движений по регистру ПартииТоваровНаСкладах через создание независимого объекта набора записей.
Схематично этот код выглядит следующим образом:
// Создается независимый объект набора записей напрямую из менеджера регистра
НаборЗаписей = РегистрыНакопления.ПартииТоваровНаСкладах.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
// Выполняется расчет партий, заполняется таблица движений
// ... (алгоритмы расчета FIFO/LIFO) ...
// Записи записываются напрямую в базу данных
НаборЗаписей.Записать();
Поскольку физическая запись в таблицы СУБД производится через сторонний объект НаборЗаписей, локальная коллекция текущего документа (доступная через ЭтотОбъект.Движения.ПартииТоваровНаСкладах) в памяти отладчика ничего об этом действии не знает. Для платформы эта коллекция остается неинициализированной и пустой.
Проанализируем, почему в документе ПоступлениеТоваровУслуг (Приход) движения по партиям видны в отладчике сразу, а в документе РеализацияТоваровУслуг (Расход) — нет. Это связано с разным подходом к формированию движений:
Движения.ПартииТоваровНаСкладах внутри модуля документа. Платформа запишет их автоматически по окончании транзакции проведения.ТоварыНаСкладах), чтобы временно отразить расход на складе. После этого выполняется сложный запрос по остаткам партий с учетом только что записанных данных, рассчитывается себестоимость, и результаты записываются через обособленный СоздатьНаборЗаписей(). Локальная коллекция документа при этом не задействуется.Рассмотрим ситуацию, которая часто вводит разработчиков в ступор: транзакция проведения документа еще не завершена (мы стоим на точке останова в конце ОбработкаПроведения), но наш запрос по ссылке документа успешно находит все движения в регистре ПартииТоваровНаСкладах.
Это абсолютно стандартное и логичное поведение СУБД и платформы «1С:Предприятие»:
НаборЗаписей.Записать(), платформа отправляет физические SQL-команды INSERT в таблицы базы данных.ОбработкаПроведения).Движения.ПартииТоваровНаСкладах в памяти остается пустым.Выяснив причину поведения платформы, мы можем легко проверить это предположение на практике во время отладки. Разберем по шагам, как заставить платформу показать движения в локальной коллекции документа.
Если нам критически важно проанализировать сформированные движения по партиям непосредственно в теле процедуры ОбработкаПроведения, мы можем принудительно заставить объект документа перечитать данные из базы данных в свой локальный буфер.
Для этого в самом конце процедуры ОбработкаПроведения (или в табло отладчика) достаточно выполнить метод Прочитать() для конкретного регистра:
// Принудительно считываем записанные сторонним набором данные в локальную коллекцию документа
Движения.ПартииТоваровНаСкладах.Прочитать();
После вызова этого метода платформа выполнит внутренний запрос к базе данных, получит только что записанные обособленным набором записи по нашему регистратору и заполнит ими коллекцию Движения.ПартииТоваровНаСкладах. Теперь в отладчике мы сможем детально изучить каждую строку движений, проверить суммы, количества и аналитику списания партий.
Проанализируем этот процесс на практическом примере кода, который можно временно встроить для целей отладки:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// ... Основной код проведения документа ...
// Вызов общего модуля партионного учета, который делает скрытую запись
УправлениеЗапасамиПартионныйУчет.ДвижениеПартийТоваров(Ссылка, Движения);
// Точка останова здесь: Движения.ПартииТоваровНаСкладах.Количество() будет равно 0
// Принудительное чтение данных из БД для анализа
Движения.ПартииТоваровНаСкладах.Прочитать();
// Теперь Движения.ПартииТоваровНаСкладах содержит все сделанные движения!
Для Каждого Движение Из Движения.ПартииТоваровНаСкладах Цикл
Сообщить("Списана партия: " + Движение.ДокументОприходования + " на сумму: " + Движение.Стоимость);
КонецЦикла;
КонецПроцедуры
При анализе партионного учета обязательно нужно учитывать настройки учетной политики вашей информационной базы. В конфигурациях уровня УТ 10.3 существует важный флаг: «Списывать партии при проведении документов».
Рассмотрим, как эта настройка меняет логику проведения документов:
ПартииТоваровНаСкладах не формируются вообще — ни через обособленный набор, ни через локальную коллекцию. Вместо этого система делает записи в простой технический регистр сведений СписанныеТовары. Сами партии и себестоимость рассчитываются позже (например, в конце дня или месяца) специальной регламентной обработкой «Проведение по партиям».СоздатьНаборЗаписей() эти движения "прячутся" от локальной коллекции документа до вызова метода Прочитать().Подведем итог нашему исследованию:
Движения.[ИмяРегистра] в конце проведения — это не ошибка платформы, а следствие использования независимого объекта НаборЗаписей в коде общих модулей.Движения.ИмяРегистра.Прочитать().ПередЗаписью и проанализировать стек вызовов.