При разработке и кастомизации конфигураций на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются со специфическим поведением механизмов временного расчета регистров. Типичный сценарий: пользователь создает новый документ (например, ЗаявкаНаСклад) на основании другого проведенного документа (например, ПредварительнаяЗаявка). Документ заполняется данными, но еще не записан в базу данных. В этот момент на форме нового документа нажимают кнопку получения остатков (например, для проверки резервов или свободного наличия на складе), но система возвращает некорректные остатки — движения документа-основания просто не учитываются.
В этой статье мы подробно разберем, почему возникает эта проблема, как устроена физика временного расчета регистров в 1С 7.7 и какими способами можно решить задачу, особенно если у вас нет возможности или желания изменять общий глобальный модуль конфигурации.
Проанализируем ситуацию с точки зрения работы платформы. Когда документ только создается (в том числе методом ВводНаОсновании), он существует только в оперативной памяти компьютера пользователя. В базе данных его еще нет, поэтому встроенный метод Выбран() возвращает для него значение 0.
Обычно в типовых или самописных конфигурациях получение остатков переносится в глобальный модуль. Функция расчета остатков часто содержит следующий блок кода:
Если _Док.Выбран() = 0 Тогда
// Новый документ
Если _Док.ДатаДок < ПолучитьДатуТА() Тогда
РегОстатки.ВременныйРасчет(1);
ОбщРег.РассчитатьРегистрыНа(_Док.ДатаДок);
КонецЕсли;
ИначеЕсли _Док.СравнитьТА() < 1 Тогда
// Записанный документ
РегОстатки.ВременныйРасчет(1);
ОбщРег.РассчитатьРегистрыНа(_Док.ТекущийДокумент());
КонецЕсли;
Разберем по шагам, почему этот код работает некорректно для новых документов:
_Док.ДатаДок) производит расчет остатков строго на начало дня (на момент времени 00:00:00 указанной даты). Это означает, что абсолютно все движения документов, сделанные в этот же день (включая движения нашего документа-основания), в расчет не попадут.Выбран() = 1), код идет по другой ветке и рассчитывает регистры на конкретную позицию документа в хронологической последовательности: ОбщРег.РассчитатьРегистрыНа(_Док.ТекущийДокумент()). В этом случае система видит все движения, которые были совершены физически до этого документа в течение дня.Ниже рассмотрим несколько практических вариантов решения этой проблемы в модуле формы вашего документа без изменения глобального модуля.
Если вам категорически нельзя менять глобальный модуль, но необходимо вернуть корректные остатки при нажатии кнопки на форме незаписанного документа, мы можем прибегнуть к хитрости. Перед вызовом глобальной функции мы временно увеличим дату нашего документа на один день, а сразу после получения остатков вернем исходную дату назад.
Посмотрим на пример реализации этого метода в модуле формы документа:
Процедура ПоОстаткам()
// Сохраняем исходную дату документа
СтараяДата = ДатаДок;
НовыйДокумент = 0;
Если Выбран() = 0 Тогда
НовыйДокумент = 1;
// Временно сдвигаем дату на день вперед, чтобы захватить движения сегодняшнего дня
ДатаДок = ДатаДок + 1;
КонецЕсли;
// Вызываем оригинальную функцию из глобального модуля
_Остатки = гсСформироватьОстаткиАдресаХранения(Контекст);
// Возвращаем дату документа в исходное состояние
Если НовыйДокумент = 1 Тогда
ДатаДок = СтараяДата;
КонецЕсли;
// Дальнейший код обработки полученной таблицы остатков...
КонецПроцедуры
Преимущества: Простота реализации, полное отсутствие изменений в глобальном модуле.
Недостатки: Если документ-основание был введен будущим числом (хотя это редкий сценарий), сдвиг на +1 день может не сработать идеально. Тем не менее, для 99% стандартных бизнес-процессов этот способ полностью решает проблему.
Более методологически верным способом будет отказ от использования глобальной функции в пользу локального расчета непосредственно в модуле формы вашего документа. Мы можем скопировать алгоритм расчета остатков в форму документа и адаптировать его под работу с незаписанными объектами.
Разберем, как должен выглядеть правильный расчет остатков с учетом документа-основания:
Функция ЛокальныйРасчетОстатков()
РегОстатки = СоздатьОбъект("Регистр.ОстаткиТоваров");
ОбщРег = СоздатьОбъект("Регистры");
Если Выбран() = 0 Тогда
// Документ новый и еще не записан
Если ПустоеЗначение(ДокументОснование) = 0 Тогда
// Если есть документ-основание, рассчитываем остатки строго ПОЗИЦИИ этого документа
РегОстатки.ВременныйРасчет(1);
ОбщРег.РассчитатьРегистрыПо(ДокументОснование);
Иначе
// Если основания нет, рассчитываем на ТА (точку актуальности) или на конец текущего дня
РегОстатки.ВременныйРасчет(1);
ОбщРег.РассчитатьРегистрыПо(ДатаДок);
EndIf;
Иначе
// Документ уже записан в базу данных
РегОстатки.ВременныйРасчет(1);
ОбщРег.РассчитатьРегистрыНа(ТекущийДокумент());
КонецЕсли;
// Здесь выполняем непосредственное получение остатков из регистра
// Например: РегОстатки.ВыгрузитьИтоги(ТЗОстатков, ...);
Возврат ТЗОстатков;
КонецФункции
Обратите внимание на использование метода РассчитатьРегистрыПо(). В отличие от РассчитатьРегистрыНа(), этот метод производит расчет включительно по указанную границу (то есть включая движения самого документа-основания).
Если ручной временной расчет регистров вызывает сложности, можно прибегнуть к получению остатков через объект Запрос. Конструкция запросов в 1С 7.7 умеет самостоятельно управлять временным расчетом под капотом, избавляя программиста от необходимости вызывать методы позиционирования регистров вручную.
Рассмотрим пример построения такого запроса:
Процедура ПолучитьОстаткиЧерезЗапрос()
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Остатки)
|Период с '01.01.1980' по ДокументОснование;
|Фирма = Регистр.ОстаткиТоваров.Фирма;
|Товар = Регистр.ОстаткиТоваров.Товар;
|Склад = Регистр.ОстаткиТоваров.Склад;
|Остаток = Регистр.ОстаткиТоваров.ОстатокТовара;
|Функция КонОст = КонОст(Остаток);
|Группировка Товар без групп;
|Условие(Склад = ВыбранныйСклад);
|//}}ЗАПРОС";
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
// Обходим результаты запроса и заполняем форму
Пока Запрос.Группировка(1) = 1 Цикл
// Добавление строк в табличную часть или заполнение остатков
КонецЦикла;
КонецПроцедуры
В тексте запроса мы четко указываем границу расчета периода: по ДокументОснование. Запрос автоматически рассчитает итоги на момент времени сразу после проведения вашего документа-основания, что гарантирует получение абсолютно точных и актуальных остатков на момент ввода нового документа.