В практике разработки на платформе 1С:Предприятие 8.3 (особенно в конфигурациях УПП 1.3 или КА 1.1) программисты часто сталкиваются с ситуацией, когда один и тот же программный код в подписке на событие ОбработкаПроведения работает безупречно для одного типа документов (например, "Заказ на производство"), но выдает ошибочные данные для другого (например, "Заказ поставщику"). Ситуация выглядит мистически: код идентичен, регистры схожи по структуре, но остатки определяются неверно, из-за чего статусы заказов не обновляются.
Рассмотрим детально, почему так происходит, и разберем механизмы платформы, которые влияют на этот процесс. Поймем, как правильно работать с виртуальными таблицами остатков внутри транзакции проведения.
Для начала проанализируем, в какой момент срабатывает подписка на событие ОбработкаПроведения. Это событие вызывается внутри транзакции записи документа, сразу после выполнения кода в модуле самого документа. Однако здесь кроется главная деталь: запись движений в базу данных может происходить в разные моменты времени в зависимости от настроек документа и логики его кода.
В 1С существует два основных способа записи движений:
ОбработкаПроведения, то есть уже после того, как отработает ваша подписка.Движения.ИмяРегистра.Записать(). Только после этого данные физически попадают в таблицы базы данных (в рамках текущей транзакции).Разберем ситуацию автора: в одном случае ("Заказ на производство") движения, вероятно, записываются внутри модуля документа до завершения процедуры, а во втором ("Заказ поставщику") — используется стандартный механизм записи после завершения. В итоге, когда подписка делает запрос к виртуальной таблице остатков, она видит данные без учета текущего документа, так как записи еще не зафиксированы в БД.
Даже если мы принудительно запишем движения, возникает вторая проблема — правильное определение момента времени в запросе. Проанализируем, как 1С работает с виртуальными таблицами. Когда мы обращаемся к таблице Остатки, нам нужно передать параметр Период. Если мы передаем просто дату или МоментВремени(), результат может быть непредсказуемым.
Посмотрим на типичную ошибку в коде запроса внутри подписки:
Запрос.УстановитьПараметр("Период", Источник.МоментВремени());
// Здесь запрос может вернуть остаток ДО текущего проведения,
// так как движения документа имеют тот же момент времени.
Для решения этой задачи в 1С используется специальный объект Граница. Выясним, в чем разница между включением и исключением момента времени.
Чтобы гарантировать, что запрос увидит движения текущего документа, мы должны убедиться, что они записаны. В самом начале нашей подписки на событие нам необходимо выполнить принудительную запись нужного регистра. Рассмотрим, как это реализовать правильно:
Процедура ПриПроведенииПодписка(Источник, Отказ, РежимПроведения) Экспорт
// Сначала записываем движения текущего документа по нужному регистру
Источник.Движения.ЗаказыПоставщикам.Записывать = Истина;
Источник.Движения.ЗаказыПоставщикам.Записать();
// Теперь движения в базе (внутри транзакции) и запрос их увидит
ВыполнитьПроверкуСтатусаЗаказа(Источник);
КонецПроцедуры
Обратите внимание на свойство Записывать = Истина. Это важно, так как если флаг не установлен, метод Записать() может проигнорировать выполнение, если платформа считает набор записей немодифицированным.
Теперь разберем, как правильно составить запрос, чтобы получить остаток после выполнения текущих движений. Нам поможет объект Граница с видом ВидГраницы.Включая. Это критически важно для корректного расчета остатка в оперативной памяти транзакции.
Рассмотрим пример правильной установки параметров запроса:
ГраницаОстатков = Новый Граница(Источник.МоментВремени(), ВидГраницы.Включая);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказыПоставщикамОстатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ЗаказыПоставщикам.Остатки(&Период, ЗаказПоставщику = &Заказ) КАК ЗаказыПоставщикамОстатки";
Запрос.УстановитьПараметр("Период", ГраницаОстатков);
Запрос.УстановитьПараметр("Заказ", Источник.Ссылка);
Результат = Запрос.Выполнить();
Использование ВидГраницы.Включая заставляет систему учитывать все записи, имеющие ту же дату и ту же позицию на временной оси (включая текущий документ). Если использовать ВидГраницы.Исключая, то текущие движения документа в остаток не попадут.
Проанализируем ситуацию глубже. Почему в одном случае работало без этих ухищрений? В типовых решениях (УПП, КА) документ "Заказ на производство" часто делает движения по регистру "Заказы на производство" с использованием определенных процедур в общем модуле, которые могут вызывать Движения.Записать() внутри себя для контроля остатков или последующих расчетов. В то же время "Заказ поставщика" может полагаться на стандартный механизм "Записывать выбранные" в свойствах документа.
Также стоит проверить структуру измерений. В регистре ЗаказыПоставщикам могут присутствовать дополнительные измерения, такие как Номенклатура, Спецификация или СтатусПартии. Если ваш запрос не учитывает эти разрезы (не группирует по ним или не фильтрует), вы можете получить "пустой" или "неверный" остаток из-за того, что данные распределены по разным аналитическим разрезам.
Для того чтобы подписка работала стабильно для любого регистра, рекомендуем придерживаться следующего алгоритма:
Источник.Движения.ИмяРегистра.Записать(). Это "протолкнет" текущие изменения в виртуальные таблицы.Новый Граница(Источник.МоментВремени(), ВидГраницы.Включая).Источник.Движения.ИмяРегистра. Если коллекция пуста в момент выполнения подписки, значит, логика заполнения движений находится "позже" по стеку вызовов или движения формируются в другом месте.Выясним еще одну деталь: если вы используете неоперативное проведение (проведение задним числом), платформа может вести себя иначе. При работе в прошлом периоде итоги не всегда обновляются мгновенно для всех механизмов. Однако использование Границы и явной записи НабораЗаписей снимает большинство этих проблем.
Таким образом, причина "неправильного" остатка в подписке — это почти всегда либо попытка чтения данных, которые еще не попали в БД, либо неверно указанный момент времени в параметрах виртуальной таблицы. Следуя приведенным выше инструкциям, вы сможете добиться идентичной и корректной работы кода для любых типов заказов в системе.