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