Как исправить ошибку «Попытка использования закрытого менеджера временных таблиц» при программном перепроведении документа?

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

При разработке сложных механизмов автоматизации в 1С, особенно когда речь идет о цепочках (каскадах) документов в таких конфигурациях, как 1С:ERP, 1С:Управление торговлей 11 или 1С:Комплексная автоматизация 2, программисты часто сталкиваются с неочевидным поведением платформы. Одной из самых коварных ошибок является: «Попытка использования закрытого менеджера временных таблиц». Рассмотрим детально, почему она возникает и как правильно модифицировать код, чтобы процесс проведения проходил без сбоев.

Анализ ситуации: почему возникает ошибка?

Представим сценарий: в рамках одной транзакции мы программно создаем документ ПеремещениеТоваров, затем на его основании РасходныйОрдерНаТовары со статусом «К отбору», следом — ОтборРазмещениеТоваров. После того как документ отбора проведен, нам необходимо вернуться к уже созданному объекту РасходныйОрдерНаТовары, изменить его статус на «Отгружен» и провести его повторно. Именно на этом этапе (второй вызов метода Записать(РежимЗаписиДокумента.Проведение)) система выдает ошибку.

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

Критический момент: как только первая процедура проведения завершается, платформа автоматически закрывает этот менеджер временных таблиц для освобождения ресурсов. Однако ссылка на этот уже «мертвый» объект остается в оперативной памяти в переменной вашего объекта документа. Когда вы вызываете Записать() второй раз для того же самого экземпляра объекта, программный код типовой конфигурации видит готовую структуру в ДополнительныхСвойствах, пытается к ней обратиться и падает с ошибкой, так как МВТ уже закрыт.

Разбор программного кода с ошибкой

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


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

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

    // Создаем Отбор/Размещение
    ДокументОРТ = СоздатьОтборРазмещениеСКладовойХранения(ДокументПОНТ, ПБЗДанные.НомераДеталей, ОбъектПБЗ);
    ДокументОРТ.Статус = Перечисления.СтатусыОтборовРазмещенийТоваров.ВыполненоБезОшибок;
    ДокументОРТ.Записать(РежимЗаписиДокумента.Проведение);

    // ПОПЫТКА ПОВТОРНОГО ПРОВЕДЕНИЯ ТОГО ЖЕ ОБЪЕКТА
    ДокументПОНТ.Статус = Перечисления.СтатусыРасходныхОрдеров.Отгружен;
    // ОШИБКА возникнет в следующей строке:
    ДокументПОНТ.Записать(РежимЗаписиДокумента.Проведение); 

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

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

Решение №1: Переполучение объекта документа

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

Разберем, как изменить код:


// ... (начало кода совпадает)
ДокументОРТ.Записать(РежимЗаписиДокумента.Проведение);

// Получаем ссылку на наш ордер
СсылкаНаОрдер = ДокументПОНТ.Ссылка;

// ПЕРЕПОЛУЧАЕМ ОБЪЕКТ
ДокументДляОтгрузки = СсылкаНаОрдер.ПолучитьОбъект();

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

Использование метода ПолучитьОбъект() полностью устраняет риск использования «битых» ссылок на менеджеры временных таблиц в памяти.

Решение №2: Очистка дополнительных свойств

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


// Перед вторым проведением очищаем кэш проведения
ДокументПОНТ.ДополнительныеСвойства.Очистить();

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

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

Особенности работы регистра «Товары на складах»

Выясним причину, почему ошибка чаще всего проявляется именно в модуле набора записей регистра Товары на складах. В современных версиях 1С:ERP и УТ логика контроля остатков (особенно на ячеистых складах) вынесена в общие модули проведения. Система формирует огромную временную таблицу со всеми движениями документа и передает её в Менеджер временных таблиц.

Когда выполняется код в модуле набора записей:

  1. Система проверяет наличие МВТ в ДополнительныхСвойствах.
  2. Если он там есть (а он там остался от прошлого проведения), система пытается выполнить запрос к временным таблицам этого менеджера.
  3. Так как платформа уже пометила этот менеджер как «Закрытый» (Closed), любая попытка выполнить Запрос.Выполнить() с использованием этого МВТ приводит к аварийному завершению сеанса или генерации исключения.

Рекомендации по проектированию каскадного проведения

Проанализируем общие правила, которые помогут избежать подобных проблем в будущем:

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

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

← На главную