При разработке сложных механизмов автоматизации в 1С, особенно когда речь идет о цепочках (каскадах) документов в таких конфигурациях, как 1С:ERP, 1С:Управление торговлей 11 или 1С:Комплексная автоматизация 2, программисты часто сталкиваются с неочевидным поведением платформы. Одной из самых коварных ошибок является: «Попытка использования закрытого менеджера временных таблиц». Рассмотрим детально, почему она возникает и как правильно модифицировать код, чтобы процесс проведения проходил без сбоев.
Представим сценарий: в рамках одной транзакции мы программно создаем документ ПеремещениеТоваров, затем на его основании РасходныйОрдерНаТовары со статусом «К отбору», следом — ОтборРазмещениеТоваров. После того как документ отбора проведен, нам необходимо вернуться к уже созданному объекту РасходныйОрдерНаТовары, изменить его статус на «Отгружен» и провести его повторно. Именно на этом этапе (второй вызов метода Записать(РежимЗаписиДокумента.Проведение)) система выдает ошибку.
Проанализируем внутренние механизмы платформы 1С. В типовых конфигурациях процесс проведения крайне оптимизирован. Для этого используется свойство документа ДополнительныеСвойства. При первом вызове метода Записать() в режиме проведения система инициализирует специальный объект — Менеджер временных таблиц (МВТ). Этот менеджер сохраняется в ДополнительныхСвойствах (обычно внутри структуры с именем ДляПроведения), чтобы модули наборов записей регистров могли эффективно обращаться к подготовленным данным.
Критический момент: как только первая процедура проведения завершается, платформа автоматически закрывает этот менеджер временных таблиц для освобождения ресурсов. Однако ссылка на этот уже «мертвый» объект остается в оперативной памяти в переменной вашего объекта документа. Когда вы вызываете Записать() второй раз для того же самого экземпляра объекта, программный код типовой конфигурации видит готовую структуру в ДополнительныхСвойствах, пытается к ней обратиться и падает с ошибкой, так как МВТ уже закрыт.
Рассмотрим пример кода, который чаще всего приводит к данной проблеме. Мы видим использование явной транзакции и последовательную запись документов:
НачатьТранзакцию();
Попытка
// Создаем и проводим первый документ
ДокументПТ = СоздатьПеремещениеСКладовойХранения(ОбъектПБЗ, ПБЗДанные);
ДокументПТ.Записать(РежимЗаписиДокумента.Проведение);
// Создаем и проводим Расходный ордер (статус "К отбору")
ДокументПОНТ = СоздатьРасходныйОрдерСКладовойХранения(ДокументПТ);
ДокументПОНТ.Записать(РежимЗаписиДокумента.Проведение);
// Создаем Отбор/Размещение
ДокументОРТ = СоздатьОтборРазмещениеСКладовойХранения(ДокументПОНТ, ПБЗДанные.НомераДеталей, ОбъектПБЗ);
ДокументОРТ.Статус = Перечисления.СтатусыОтборовРазмещенийТоваров.ВыполненоБезОшибок;
ДокументОРТ.Записать(РежимЗаписиДокумента.Проведение);
// ПОПЫТКА ПОВТОРНОГО ПРОВЕДЕНИЯ ТОГО ЖЕ ОБЪЕКТА
ДокументПОНТ.Статус = Перечисления.СтатусыРасходныхОрдеров.Отгружен;
// ОШИБКА возникнет в следующей строке:
ДокументПОНТ.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Проблема здесь заключается в том, что переменная ДокументПОНТ хранит в себе состояние ДополнительныхСвойств, оставшееся после первого проведения. При ручном проведении (интерактивно через форму) такая ошибка не возникает, потому что форма каждый раз инициализирует новый контекст или перечитывает объект, очищая кэши.
Это самый надежный и «чистый» способ. Перед тем как вносить изменения в документ для повторного проведения, мы должны получить свежий экземпляр объекта из базы данных по его ссылке. Это гарантирует, что ДополнительныеСвойства будут пустыми, а все служебные структуры инициализируются заново.
Разберем, как изменить код:
// ... (начало кода совпадает)
ДокументОРТ.Записать(РежимЗаписиДокумента.Проведение);
// Получаем ссылку на наш ордер
СсылкаНаОрдер = ДокументПОНТ.Ссылка;
// ПЕРЕПОЛУЧАЕМ ОБЪЕКТ
ДокументДляОтгрузки = СсылкаНаОрдер.ПолучитьОбъект();
ДокументДляОтгрузки.Статус = Перечисления.СтатусыРасходныхОрдеров.Отгружен;
// Теперь проведение пройдет успешно
ДокументДляОтгрузки.Записать(РежимЗаписиДокумента.Проведение);
// ...
Использование метода ПолучитьОбъект() полностью устраняет риск использования «битых» ссылок на менеджеры временных таблиц в памяти.
Если по каким-то причинам вы не хотите повторно обращаться к базе данных (хотя в рамках транзакции это происходит очень быстро благодаря кэшу), можно принудительно очистить служебную структуру. Посмотрим на этот метод подробнее:
// Перед вторым проведением очищаем кэш проведения
ДокументПОНТ.ДополнительныеСвойства.Очистить();
// Теперь можно менять реквизиты и проводить
ДокументПОНТ.Статус = Перечисления.СтатусыРасходныхОрдеров.Отгружен;
ДокументПОНТ.Записать(РежимЗаписиДокумента.Проведение);
Важное примечание: метод Очистить() удаляет вообще все дополнительные свойства. Если ваш алгоритм или другие подписки на события полагаются на какие-то специфические данные в этой коллекции, будьте осторожны. В типовых конфигурациях чаще всего достаточно удалить только структуру ДляПроведения или РезультатПроведения, но полная очистка является универсальным лекарством от ошибки МВТ.
Выясним причину, почему ошибка чаще всего проявляется именно в модуле набора записей регистра Товары на складах. В современных версиях 1С:ERP и УТ логика контроля остатков (особенно на ячеистых складах) вынесена в общие модули проведения. Система формирует огромную временную таблицу со всеми движениями документа и передает её в Менеджер временных таблиц.
Когда выполняется код в модуле набора записей:
ДополнительныхСвойствах.Запрос.Выполнить() с использованием этого МВТ приводит к аварийному завершению сеанса или генерации исключения.Проанализируем общие правила, которые помогут избежать подобных проблем в будущем:
ДокументОбъект для проведения в разных статусах.ПередЗаписью модуля документа. Проверьте содержимое ЭтотОбъект.ДополнительныеСвойства. Если там есть структуры, созданные системой при предыдущем шаге — это и есть источник проблемы.Записать(РежимЗаписиДокумента.Проведение) платформа по умолчанию пытается выполнить оперативное проведение, если дата документа совпадает с текущей. Попробуйте явно указывать РежимПроведенияДокумента.Неоперативный, если бизнес-логика это позволяет, иногда это меняет состав подготавливаемых временных таблиц.Таким образом, основной способ решения проблемы — это обеспечение чистоты объекта перед каждым вызовом процедуры проведения. Либо через ПолучитьОбъект(), либо через ДополнительныеСвойства.Очистить(). Это гарантирует, что каждый этап проведения будет начинаться «с чистого листа» с точки зрения менеджера временных таблиц платформы 1С.