Как исключить влияние собственных движений документа при его перепроведении в 1С?

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

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

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

Причина проблемы и поведение платформы по умолчанию

Поведение платформы в момент начала транзакции проведения регулируется свойством документа «Удаление движений» (вкладка «Движения» в конфигураторе). По умолчанию там установлено значение «Удалять автоматически при отмене проведения».

При такой настройке во время перепроведения старые движения не удаляются платформой перед запуском процедуры ОбработкаПроведения. Они остаются в базе данных СУБД до тех пор, пока новые движения принудительно не перезапишут их в конце транзакции. Именно поэтому стандартный запрос остатков на момент времени документа вернет данные, которые уже учитывают его собственные («старые») движения.

Рассмотрим по шагам основные методики, которые позволяют обойти эту проблему и получить «чистые» остатки.

Способ 1. Новая методика проведения (Стандарт фирмы «1С»)

Это самый современный, производительный и рекомендуемый стандарт разработки. При использовании новой методики проведения проблема «отмены проведения самого себя» исчезает физически, так как контроль остатков переносится с начала процедуры на ее конец.

Проанализируем алгоритм новой методики:

  1. Запись движений без предварительной проверки: Мы сразу формируем новые движения на основании данных документа и записываем их в базу данных, устанавливая свойство Записывать = Истина. При записи новые движения автоматически затирают старые записи этого же документа в СУБД.
  2. Контроль остатков «после» записи: Мы выполняем запрос к таблице остатков, проверяя, не ушли ли остатки в минус (меньше нуля) после того, как мы провели документ.
  3. Откат транзакции при ошибке: Если обнаружен отрицательный остаток, мы выводим пользователю сообщение об ошибке, устанавливаем параметр Отказ = Истина и прекращаем выполнение. Платформа автоматически отменит всю транзакцию, вернув базу данных к исходному состоянию до начала проведения.

Посмотрим на пример реализации новой методики проведения в коде:


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

Преимущества метода: Максимальное быстродействие, отсутствие избыточных блокировок СУБД, минимальное количество чтений/записей таблиц.

Способ 2. Очистка движений через запись пустого набора

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

В управляемом приложении коллекция Движения в памяти при входе в ОбработкаПроведения по умолчанию пуста (она не заполнена данными из БД). Чтобы стереть старые движения из таблиц базы данных, достаточно принудительно записать этот пустой набор в СУБД.

Разберем пример очистки движений конкретного регистра:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Физически удаляем старые движения этого документа из БД для конкретного регистра
    Движения.ТоварыНаСкладах.Записать();
    
    // Теперь мы можем делать стандартный запрос остатков — старые движения не помешают
    Запрос = Новый Запрос;
    // ... Обычный запрос остатков на МоментВремени() ...
    
КонецПроцедуры

Если документ делает движения по множеству регистров, можно очистить их все одной строкой:

Движения.Записать();

Важный нюанс: Этот метод выполняет дополнительное обращение к СУБД для удаления строк. При высокой нагрузке на систему это может приводить к избыточным блокировкам, поэтому использовать его следует только тогда, когда это действительно необходимо.

Способ 3. Исключение движений с помощью объекта Граница

Если мы не хотим принудительно очищать базу данных перед расчетом остатков, мы можем исключить движения документа непосредственно в запросе. Для этого используется объект Граница с параметром ВидГраницы.Исключая.

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


// Создаем границу времени, исключающую влияние текущего документа
ГраницаКонтроля = Новый Граница(МоментВремени(), ВидГраницы.Исключая);

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТоварыНаСкладахОстатки.Номенклатура,
    |   ТоварыНаСкладахОстатки.КоличествоОстаток
    |ИЗ
    |   РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";

Запрос.УстановитьПараметр("Период", ГраницаКонтроля);
Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура);

Ограничения метода: Данный метод отлично работает при проведении документов «задним числом». Однако, если в одну и ту же секунду записано несколько документов по одному и тому же складу/товару, граница Исключая может не учесть документы, которые стоят в последовательности раньше нашего документа, но имеют то же время.

Способ 4. Изменение свойств метаданных (Удалять автоматически)

Мы можем переложить всю работу по очистке движений на саму платформу 1С. Для этого в свойствах документа на вкладке «Движения» нужно установить значение свойства «Удаление движений» в положение «Удалять автоматически».

В этом режиме платформа перед вызовом процедуры ОбработкаПроведения выполняет транзакционный запрос к СУБД и полностью очищает таблицы движений по этому документу. Разработчику не нужно писать дополнительный код для очистки.

Почему этот способ не рекомендуется использовать в высоконагруженных системах?
При интерактивном перепроведении документов платформа делает «лишнюю» операцию удаления. Если пользователь в итоге откажется от проведения документа (например, не хватило остатка), платформа сначала удалит движения, а потом будет вынуждена откатывать транзакцию. Это создает колоссальную и бесполезную нагрузку на СУБД и приводит к взаимоблокировкам в многопользовательском режиме.

Критические ошибки при попытке отменить проведение «самого себя»

Попытки решить эту задачу некорректными методами часто приводят к падению системы или логическим ошибкам. Проанализируем самые популярные ошибки разработчиков:

Резюме

Для качественной и быстрой работы конфигурации всегда отдавайте предпочтение новой методике проведения (Способ 1). Она позволяет полностью избежать проблем с искажением остатков собственными движениями документа, минимизирует количество транзакционных запросов к СУБД и обеспечивает максимальную параллельность работы пользователей в системе. К ручной очистке движений (Способ 2) или использованию границ (Способ 3) следует прибегать только при невозможности перевода логики на новые стандарты.

← На главную