Почему запрос к остаткам захватывает движения текущего документа и как это исправить

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке алгоритмов проведения документов в платформе 1С:Предприятие разработчики часто сталкиваются с ситуацией, когда запрос к виртуальной таблице остатков (регистра накопления или регистра бухгалтерии) возвращает данные с учетом движений самого проводимого документа. Попытки передать в качестве параметра периода конструкцию вида Новый Граница(МоментВремени, ВидГраницы.Исключая) или просто МоментВремени не дают желаемого результата. Разберем по шагам, почему возникает такое поведение платформы и как правильно построить контроль остатков.

Причины захвата движений документа

Проанализируем механику работы платформы при проведении документа, чтобы понять источник проблемы:

  1. Смещение даты документа при оперативном проведении. Если для документа включен режим оперативного проведения и в свойствах метаданных установлено свойство Удаление движений = «Не удалять автоматически», платформа при проведении сдвигает дату документа на текущее системное время (на точку актуальности). Если документ ранее уже был проведен, его старые проводки или движения остаются записанными на предыдущую отметку времени. При выполнении запроса на новый МоментВремени виртуальная таблица остатков считывает все движения, хронологически предшествующие этой новой точке, включая старые движения этого же документа.
  2. Особенности виртуальной таблицы остатков. Таблица Остатки принципиально устроена так, что в ней отсутствует поле Регистратор. Мы не можем написать в условии соединения или секции ГДЕ фильтр вида Регистратор <> &Ссылка, так как срез остатков рассчитывается интегрально по измерениям на указанный момент времени без детализации по конкретным документам-регистраторам.
  3. Особенности работы границ. Виртуальная таблица остатков по умолчанию рассчитывает срез на момент времени с исключением движений самого регистратора, если они находятся на этой же временной отметке. Однако если старые движения остались в прошлом из-за сдвига даты документа, передача Граница или МоментВремени уже не поможет их исключить.

Способ 1. Программная очистка движений перед контролем остатков

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

Посмотрим на пример реализации в модуле объекта документа:


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

После вызова Движения.Хозрасчетный.Записать() пустой набор физически перезаписывает старые движения в базе данных в рамках текущей транзакции проведения, благодаря чему запрос на МоментВремени() вернет остатки без искажений.

Способ 2. Контроль отрицательных остатков после записи (рекомендуемая методика)

Разберем современную и наиболее производительную методику проведения, рекомендованную стандартами разработки 1С. Вместо попыток предварительного чтения остатков мы сначала формируем и записываем движения документа, а затем проверяем базу на наличие отрицательных остатков.

Рассмотрим алгоритм по шагам:

  1. Формируем движения регистра на основании данных документа (табличных частей или реквизитов).
  2. Записываем сформированные движения в базу данных.
  3. Выполняем запрос к виртуальной таблице остатков на момент проведения документа (включая его движения) с отбором записей, у которых остаток стал меньше нуля.
  4. Если запрос вернул строки с отрицательными остатками — информируем пользователя об ошибке и устанавливаем флаг Отказ = Истина.

Посмотрим на пример реализации данного подхода:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Формируем движения по регистру
    Для Каждого СтрТЧ Из ТабличнаяЧасть Цикл
        Проводка = Движения.Хозрасчетный.Добавить();
        Проводка.СчетДт = ПланыСчетов.Хозрасчетный.АКЦ;
        Проводка.СчетКт = ПланыСчетов.Хозрасчетный.РасчетыСПокупателями;
        Проводка.Период = Дата;
        Проводка.Организация = Организация;
        Проводка.КоличествоДт = СтрТЧ.Количество;
        Проводка.Сумма = СтрТЧ.Сумма;
        // Заполнение субконто...
    КонецЦикла;
    
    // 2. Записываем сформированные движения
    Движения.Хозрасчетный.Записывать = Истина;
    Движения.Хозрасчетный.Записать();
    
    // 3. Проверяем остатки на наличие отрицательных значений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ХозрасчетныйОстатки.Субконто1 КАК Контрагент,
        |   ХозрасчетныйОстатки.Субконто2 КАК Договор,
        |   ХозрасчетныйОстатки.КоличествоОстатокДт КАК Количество,
        |   ХозрасчетныйОстатки.СуммаОстатокДт КАК Сумма
        |ИЗ
        |   РегистрБухгалтерии.Хозрасчетный.Остатки(
        |       &МоментВремени,
        |       Счет = &Счет,
        |       ,
        |       Организация = &Организация
        |   ) КАК ХозрасчетныйОстатки
        |ГДЕ
        |   ХозрасчетныйОстатки.КоличествоОстатокДт < 0
        |   ИЛИ ХозрасчетныйОстатки.СуммаОстатокДт < 0";
    
    // Передаем границу с включением движений текущего документа
    ГраницаПроведения = Новый Граница(МоментВремени(), ВидГраницы.Включая);
    Запрос.УстановитьПараметр("МоментВремени", ГраницаПроведения);
    Запрос.УстановитьПараметр("Счет", ПланыСчетов.Хозрасчетный.АКЦ);
    Запрос.УстановитьПараметр("Организация", Организация);
    
    Результат = Запрос.Выполнить();
    
    // 4. Анализируем результат проверки
    Если Не Результат.Пустой() Тогда
        Отказ = Истина;
        Выборка = Результат.Выбрать();
        Пока Выборка.Следующий() Цикл
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = СтрШаблон(
                "Недостаточно остатка по контрагенту %1 (договор %2). Нехватка: %3",
                Выборка.Контрагент,
                Выборка.Договор,
                -Выборка.Количество
            );
            Сообщение.Сообщить();
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

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

← На главную