Как получить актуальные остатки для нового (незаписанного) документа в 1С 7.7?

Программист 1С v7.7 Управленческий учет Торговля и дистрибуция
← На главную

При разработке и кастомизации конфигураций на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются со специфическим поведением механизмов временного расчета регистров. Типичный сценарий: пользователь создает новый документ (например, ЗаявкаНаСклад) на основании другого проведенного документа (например, ПредварительнаяЗаявка). Документ заполняется данными, но еще не записан в базу данных. В этот момент на форме нового документа нажимают кнопку получения остатков (например, для проверки резервов или свободного наличия на складе), но система возвращает некорректные остатки — движения документа-основания просто не учитываются.

В этой статье мы подробно разберем, почему возникает эта проблема, как устроена физика временного расчета регистров в 1С 7.7 и какими способами можно решить задачу, особенно если у вас нет возможности или желания изменять общий глобальный модуль конфигурации.

Выясняем причину: почему остатки «врут» для незаписанного документа?

Проанализируем ситуацию с точки зрения работы платформы. Когда документ только создается (в том числе методом ВводНаОсновании), он существует только в оперативной памяти компьютера пользователя. В базе данных его еще нет, поэтому встроенный метод Выбран() возвращает для него значение 0.

Обычно в типовых или самописных конфигурациях получение остатков переносится в глобальный модуль. Функция расчета остатков часто содержит следующий блок кода:


Если _Док.Выбран() = 0 Тогда
    // Новый документ
    Если _Док.ДатаДок < ПолучитьДатуТА() Тогда 
        РегОстатки.ВременныйРасчет(1);
        ОбщРег.РассчитатьРегистрыНа(_Док.ДатаДок);    
    КонецЕсли;
ИначеЕсли _Док.СравнитьТА() < 1 Тогда
    // Записанный документ
    РегОстатки.ВременныйРасчет(1);
    ОбщРег.РассчитатьРегистрыНа(_Док.ТекущийДокумент());    
КонецЕсли;

Разберем по шагам, почему этот код работает некорректно для новых документов:

  1. Ограничение метода РассчитатьРегистрыНа(): Этот метод при передаче в него даты (например, _Док.ДатаДок) производит расчет остатков строго на начало дня (на момент времени 00:00:00 указанной даты). Это означает, что абсолютно все движения документов, сделанные в этот же день (включая движения нашего документа-основания), в расчет не попадут.
  2. Почему для записанного документа все работает правильно? Когда документ уже записан в базу (Выбран() = 1), код идет по другой ветке и рассчитывает регистры на конкретную позицию документа в хронологической последовательности: ОбщРег.РассчитатьРегистрыНа(_Док.ТекущийДокумент()). В этом случае система видит все движения, которые были совершены физически до этого документа в течение дня.
  3. Сдвиг даты как подтверждение гипотезы: Если у нового документа временно сдвинуть дату на один день вперед, то остатки внезапно становятся верными. Это происходит потому, что расчет выполняется на начало следующего дня, благодаря чему все движения текущего дня (включая документ-основание) успешно попадают во временный расчет.

Ниже рассмотрим несколько практических вариантов решения этой проблемы в модуле формы вашего документа без изменения глобального модуля.

Решение 1. Временный сдвиг даты документа («Быстрый хак»)

Если вам категорически нельзя менять глобальный модуль, но необходимо вернуть корректные остатки при нажатии кнопки на форме незаписанного документа, мы можем прибегнуть к хитрости. Перед вызовом глобальной функции мы временно увеличим дату нашего документа на один день, а сразу после получения остатков вернем исходную дату назад.

Посмотрим на пример реализации этого метода в модуле формы документа:


Процедура ПоОстаткам()
    
    // Сохраняем исходную дату документа
    СтараяДата = ДатаДок;
    НовыйДокумент = 0;
    
    Если Выбран() = 0 Тогда
        НовыйДокумент = 1;
        // Временно сдвигаем дату на день вперед, чтобы захватить движения сегодняшнего дня
        ДатаДок = ДатаДок + 1; 
    КонецЕсли;
    
    // Вызываем оригинальную функцию из глобального модуля
    _Остатки = гсСформироватьОстаткиАдресаХранения(Контекст);
    
    // Возвращаем дату документа в исходное состояние
    Если НовыйДокумент = 1 Тогда
        ДатаДок = СтараяДата;
    КонецЕсли;
    
    // Дальнейший код обработки полученной таблицы остатков...
    
КонецПроцедуры

Преимущества: Простота реализации, полное отсутствие изменений в глобальном модуле.

Недостатки: Если документ-основание был введен будущим числом (хотя это редкий сценарий), сдвиг на +1 день может не сработать идеально. Тем не менее, для 99% стандартных бизнес-процессов этот способ полностью решает проблему.

Решение 2. Локальное переопределение функции получения остатков

Более методологически верным способом будет отказ от использования глобальной функции в пользу локального расчета непосредственно в модуле формы вашего документа. Мы можем скопировать алгоритм расчета остатков в форму документа и адаптировать его под работу с незаписанными объектами.

Разберем, как должен выглядеть правильный расчет остатков с учетом документа-основания:


Функция ЛокальныйРасчетОстатков()
    
    РегОстатки = СоздатьОбъект("Регистр.ОстаткиТоваров");
    ОбщРег = СоздатьОбъект("Регистры");
    
    Если Выбран() = 0 Тогда
        // Документ новый и еще не записан
        Если ПустоеЗначение(ДокументОснование) = 0 Тогда
            // Если есть документ-основание, рассчитываем остатки строго ПОЗИЦИИ этого документа
            РегОстатки.ВременныйРасчет(1);
            ОбщРег.РассчитатьРегистрыПо(ДокументОснование);
        Иначе
            // Если основания нет, рассчитываем на ТА (точку актуальности) или на конец текущего дня
            РегОстатки.ВременныйРасчет(1);
            ОбщРег.РассчитатьРегистрыПо(ДатаДок); 
        EndIf;
    Иначе
        // Документ уже записан в базу данных
        РегОстатки.ВременныйРасчет(1);
        ОбщРег.РассчитатьРегистрыНа(ТекущийДокумент());
    КонецЕсли;
    
    // Здесь выполняем непосредственное получение остатков из регистра
    // Например: РегОстатки.ВыгрузитьИтоги(ТЗОстатков, ...);
    
    Возврат ТЗОстатков;
    
КонецФункции

Обратите внимание на использование метода РассчитатьРегистрыПо(). В отличие от РассчитатьРегистрыНа(), этот метод производит расчет включительно по указанную границу (то есть включая движения самого документа-основания).

Решение 3. Использование механизма запросов (альтернативный обходной путь)

Если ручной временной расчет регистров вызывает сложности, можно прибегнуть к получению остатков через объект Запрос. Конструкция запросов в 1С 7.7 умеет самостоятельно управлять временным расчетом под капотом, избавляя программиста от необходимости вызывать методы позиционирования регистров вручную.

Рассмотрим пример построения такого запроса:


Процедура ПолучитьОстаткиЧерезЗапрос()
    
    Запрос = СоздатьОбъект("Запрос");
    ТекстЗапроса = 
    "//{{ЗАПРОС(Остатки)
    |Период с '01.01.1980' по ДокументОснование;
    |Фирма = Регистр.ОстаткиТоваров.Фирма;
    |Товар = Регистр.ОстаткиТоваров.Товар;
    |Склад = Регистр.ОстаткиТоваров.Склад;
    |Остаток = Регистр.ОстаткиТоваров.ОстатокТовара;
    |Функция КонОст = КонОст(Остаток);
    |Группировка Товар без групп;
    |Условие(Склад = ВыбранныйСклад);
    |//}}ЗАПРОС";
    
    Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Обходим результаты запроса и заполняем форму
    Пока Запрос.Группировка(1) = 1 Цикл
        // Добавление строк в табличную часть или заполнение остатков
    КонецЦикла;
    
КонецПроцедуры

В тексте запроса мы четко указываем границу расчета периода: по ДокументОснование. Запрос автоматически рассчитает итоги на момент времени сразу после проведения вашего документа-основания, что гарантирует получение абсолютно точных и актуальных остатков на момент ввода нового документа.

← На главную