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