В практике разработки на платформе 1С:Предприятие 7.7 (особенно в конфигурации "Торговля и Склад 9.2") часто возникает сложная задача: при проведении документа необходимо получать актуальные остатки по регистру с учетом тех движений, которые уже были сформированы текущим документом в предыдущих строках. Это критично для документов, где в рамках одной транзакции происходит и приход, и расход одной и той же номенклатуры (например, возврат товара от покупателя и его последующая реализация в одном чеке или отчете).
Рассмотрим подробно, как заставить механизм ВременныйРасчет видеть свежезаписанные движения и почему стандартные методы иногда ведут себя непредсказуемо.
Для начала проанализируем базовый алгоритм, который обычно используется в модуле проведения документа для партионного учета. Чтобы система понимала, что мы хотим работать с виртуальными итогами на конкретный момент времени, а не на текущую Точку Актуальности (ТА), мы используем объект Регистры.
Проанализируем стандартную последовательность команд:
// Создаем объект для работы с временными расчетами
ВремРегистры = СоздатьОбъект("Регистры");
ВремПартии = ВремРегистры.ПартииНаличие;
// Включаем режим временного расчета
ВремПартии.ВременныйРасчет(1);
ВремРегистры.Актуальность(1); // Ключевой флаг для учета новых движений
// Рассчитываем итоги на момент времени перед документом
ВремРегистры.РассчитатьРегистрыНа(ТекущийДокумент());
Метод Актуальность(1) теоретически должен обеспечивать то, что каждое вызванное в коде ДвижениеПриход() или ДвижениеРасход() сразу же меняет состояние временных итогов. Однако при проведении "задним числом" (когда документ находится до ТА) разработчики часто сталкиваются с тем, что ВыгрузитьИтоги() во второй или третьей строке документа не видит изменений от первой строки.
Выясним причину возможной путаницы между этими методами, так как от них зависит точка отсчета для временного расчета:
ОчиститьДвижения(), то входящий остаток будет одинаковым для обоих методов, но РассчитатьРегистрыНа является более логичным стандартом для формирования новых движений "с нуля".Важно понимать: если вы используете Актуальность(1), то даже при старте с точки "НА", каждое новое движение должно математически добавляться к результату. Если этого не происходит, проблема кроется в особенностях кэширования или наложения фильтров.
Проанализируем ситуацию, когда в цикле по номенклатуре используется УстановитьЗначениеФильтра(). В 1С 7.7 временный расчет — это динамическая структура в оперативной памяти (или временных таблицах БД). Когда вы меняете фильтр по Номенклатура, система выполняет перепозиционирование во временных итогах.
Если внутри цикла последовательность строк выглядит так:
1. Номенклатура А — Расход
2. Номенклатура Б — Приход
3. Номенклатура А — Расход
То при обработке третьей строки (снова А) фильтр накладывается заново. В некоторых версиях движка 1С 7.7 (особенно на SQL-версиях) происходит сброс курсора временной таблицы, и движения, которые еще не были физически записаны в базу данных (транзакция ведь не завершена), могут выпасть из области видимости метода ВыгрузитьИтоги().
Как отмечают опытные разработчики, полагаться на 100% корректность Актуальность(1) внутри сложного цикла проведения опасно. Рассмотрим более надежный и производительный метод, который часто применяется в высоконагруженных системах на 7.7.
Суть метода заключается в том, чтобы не обращаться к регистру за итогами на каждой итерации цикла, а работать с локальной Таблицей Значений. Разберем этот алгоритм по шагам:
НайтиЗначение() или предварительную индексацию).Посмотрим на пример реализации такого подхода:
// 1. Подготовка данных
ТаблНоменклатурыДокумента.Выгрузить(СписокНоменклатуры, "Номенклатура");
// 2. Получаем остатки один раз на весь документ
ВремРегистры.РассчитатьРегистрыНа(ТекущийДокумент());
ВремПартии.УстановитьЗначениеФильтра("Номенклатура", СписокНоменклатуры, 2); // Фильтр по списку
ВремПартии.ВыгрузитьИтоги(ТЗ_Остатки, 1, 1);
// 3. Основной цикл
ТаблНоменклатурыДокумента.ВыбратьСтроки();
Пока ТаблНоменклатурыДокумента.ПолучитьСтроку() = 1 Цикл
// Ищем остаток в ТЗ_Остатки вместо обращения к регистру
СтрокаОст = 0;
Если ТЗ_Остатки.НайтиЗначение(ТаблНоменклатурыДокумента.Номенклатура, СтрокаОст, "Номенклатура") = 1 Тогда
ТЗ_Остатки.ПолучитьСтрокуПоНомеру(СтрокаОст);
// Распределяем и формируем движения...
// Регистр.ПартииНаличие.ДвижениеРасход(...);
// 4. ВАЖНО: Сразу корректируем нашу ТЗ
ТЗ_Остатки.Количество = ТЗ_Остатки.Количество - СписанноеКолво;
КонецЕсли;
КонецЦикла;
Рассмотрим подробнее, почему этот способ лучше прямого использования временных расчетов в цикле:
ВыгрузитьИтоги() или СводныйИтог() в каждой строке документа (если их сотни) существенно замедляет проведение. Работа с ТЗ в памяти происходит мгновенно.Для корректной работы с движениями текущего документа в 1С 7.7:
1. Всегда используйте ВремРегистры.Актуальность(1), если пытаетесь работать через стандартные методы регистров.
2. Помните, что РассчитатьРегистрыНа(ТекДок) при очищенных движениях — это точка "ноль" для вашего документа.
3. Если вы сталкиваетесь с необъяснимым поведением итогов при проведении задним числом, переходите на алгоритм с единоразовой выгрузкой остатков в Таблицу Значений и их ручной корректировкой в процессе обработки строк. Это классический прием, который гарантирует точность партионного учета в любых условиях.