Как получить остатки после проведения документа до его фактической записи в 1С?

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

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

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

Метод 1: Использование транзакции и преобразование данных формы

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

Разберем по шагам, как это реализовать программно. Проблема работы напрямую с формой заключается в том, что метод ЭтаФорма.Записать() жестко привязан к контексту и может вызывать нежелательные визуальные обновления или проверки. Правильнее будет работать с объектом на стороне сервера.

Проанализируем пример кода, который позволяет выполнить эту операцию:


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

КонецФункции

Важный нюанс: при использовании ДанныеФормыВЗначение мы получаем копию данных. После отмены транзакции ссылка в прикладном объекте ДокументОбъект может остаться заполненной (если мы использовали УстановитьСсылкуНового), однако в самой форме (в реквизите Объект) ссылка останется пустой, что и требовалось по условию задачи.

Метод 2: Работа со ссылкой нового объекта

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

Проанализируем особенности этого механизма:

  1. Метод Документы.ИмяДокумента.ПолучитьСсылку() создает уникальный идентификатор (GUID), который зарезервирован для этого объекта.
  2. Если мы применим УстановитьСсылкуНового(НужнаяСсылка), то даже до записи в БД система будет считать, что у объекта есть ссылка.
  3. Это критически важно для механизмов, которые делают запросы к регистрам, используя Регистратор = &Ссылка. Без этого движения в транзакции могут не «привязаться» корректно к документу.

Метод 3: Математическое моделирование без транзакций (Best Practice)

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

Разберем альтернативный, более «чистый» архитектурно подход — расчет через временные таблицы в запросе:

Вместо реального проведения мы можем передать данные из табличной части документа в запрос. Схема выглядит так:

Рассмотрим пример реализации такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеФормы ИЗ &ТЗ_Товары КАК ТЗ_Товары;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |   ЕСТЬNULL(РегОстатки.КоличествоОстаток, 0) + ЕСТЬNULL(ВТ.Количество, 0) КАК Прогноз
    |ИЗ
    |   ВТ_ДанныеФормы КАК ВТ
    |ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_ДанныеФормы)) КАК РегОстатки
    |ПО ВТ.Номенклатура = РегОстатки.Номенклатура";

Запрос.УстановитьПараметр("ТЗ_Товары", Объект.Товары.Выгрузить());
Запрос.УстановитьПараметр("Период", Объект.Дата);
Результат = Запрос.Выполнить().Выгрузить();

Сравнение подходов и рекомендации

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

Подведем итоги по реализации:

  1. Если логика проведения простая — всегда используйте запрос к виртуальным таблицам остатков с подстановкой данных из формы. Это быстрее и не создает блокировок.
  2. Если нужно именно «прогнать» документ через все подсистемы — используйте транзакцию на сервере с обязательным вызовом ОтменитьТранзакцию() в блоке Попытка...Исключение.
  3. Помните, что редактировать свойство Ссылка напрямую у ДанныеФормыСтруктура нельзя. Работайте через ДанныеФормыВЗначение, чтобы получить полноценный объект.
  4. Следите за моментом времени. Прогнозный остаток должен считаться на дату документа. Если пользователь изменил дату в форме, расчет нужно инициировать заново (событие ПриИзменении даты).

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

← На главную