В процессе разработки часто возникает задача: при проведении документа нужно узнать, какие значения были у товаров (или других номенклатурных позиций) в прошлый раз. Это может быть предыдущая цена, индивидуальный номер, остаток или любой другой параметр. Сложность заключается в том, что «предыдущий раз» для каждого товара из списка может быть в разных документах, созданных в разное время.
Рассмотрим подробно несколько стратегий решения этой задачи: от простых запросов к документам до создания полноценной системы хранения истории на базе регистров сведений. Проанализируем плюсы и минусы каждого подхода и разберем примеры кода.
Как показывает практика и обсуждение, наиболее надежный и производительный способ — это создание периодического регистра сведений. В таком регистре измерением будет выступать Продукт (или Номенклатура), а ресурсом — то значение, историю которого нам нужно хранить (например, ИндивидуальныйНомер).
Рассмотрим преимущества этого метода:
СрезПоследних оптимизирована для быстрого получения актуальных данных на любую точку во времени.Посмотрим на пример запроса, который позволяет получить предпоследнее значение. Чтобы получить данные «до» текущего документа, мы используем параметр Период в виртуальной таблице, передавая туда Момент Времени текущего документа с исключением (через объект Граница).
Проанализируем пример кода для получения данных в модуле проведения:
// Сначала подготовим список продуктов из табличной части
СписокПродуктов = ЭтотОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ИсторияЗначенийСрезПоследних.Номенклатура КАК Номенклатура,
| ИсторияЗначенийСрезПоследних.ИндивидуальныйНомер КАК ПредыдущийНомер,
| ИсторияЗначенийСрезПоследних.Регистратор КАК ПредыдущийДокумент
|ИЗ
| РегистрСведений.ИсторияЗначений.СрезПоследних(&МоментВремени, Номенклатура В (&СписокПродуктов)) КАК ИсторияЗначенийСрезПоследних";
// Используем Границу, чтобы не захватить движения текущего документа, если они уже записаны
ГраницаЗапроса = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("МоментВремени", ГраницаЗапроса);
Запрос.УстановитьПараметр("СписокПродуктов", СписокПродуктов);
РезультатЗапроса = Запрос.Выполнить();
Если по каким-то причинам создавать отдельный регистр нельзя, можно попытаться «выудить» данные напрямую из табличных частей других документов того же типа. Разберем, как это работает, и выясним основные риски.
Основная идея заключается в том, чтобы для каждого товара из текущего документа найти документ с максимальной датой, которая меньше даты текущего документа. Рассмотрим пример построения такого запроса:
Пример логики запроса:
ВЫБРАТЬ
РеализацияТовары.Номенклатура КАК Номенклатура,
МАКСИМУМ(РеализацияТовары.Ссылка.МоментВремени) КАК МаксМомент
ПОМЕСТИТЬ ВТ_ПоследниеМоменты
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
РеализацияТовары.Номенклатура В (&СписокНоменклатуры)
И РеализацияТовары.Ссылка.МоментВремени < &ТекущийМомент
СГРУППИРОВАТЬ ПО
РеализацияТовары.Номенклатура;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ПоследниеМоменты.Номенклатура,
РеализацияТовары.ИндивидуальныйНомер КАК ПредыдущийЗначение
ИЗ
ВТ_ПоследниеМоменты КАК ВТ_ПоследниеМоменты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ПО ВТ_ПоследниеМоменты.Номенклатура = РеализацияТовары.Номенклатура
И ВТ_ПоследниеМоменты.МаксМомент = РеализацияТовары.Ссылка.МоментВремени
Важное предостережение: Этот метод крайне медленный. Табличные части документов в базе данных хранятся в отдельных таблицах, и поиск по ним без указания конкретной Ссылка заставляет СУБД сканировать огромные массивы данных. Если документов тысячи, проведение будет занимать очень много времени.
Если задача стоит не в поиске данных по всей истории, а в том, чтобы понять, что изменилось в конкретно этом документе при его перезаписи, мы можем использовать разницу между данными в памяти и данными в базе.
Разберем ситуацию: пользователь открыл документ, изменил номер в строке и нажал «Записать». В этот момент:
ЭтотОбъект — содержит новые, еще не сохраненные данные.ЭтотОбъект.Ссылка — ведет к версии документа, которая всё еще лежит в базе данных (старое состояние).Рассмотрим, как это реализовать программно:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если ЗначениеЗаполнено(Ссылка) Тогда
// Получаем старую таблицу товаров из базы данных
СтараяТЧ = Ссылка.Товары.Выгрузить();
Для Каждого СтрокаТЧ Из Товары Цикл
ПараметрыОтбора = Новый Структура("КлючСвязи", СтрокаТЧ.КлючСвязи);
НайденныеСтроки = СтараяТЧ.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
СтароеЗначение = НайденныеСтроки[0].ИндивидуальныйНомер;
Если СтароеЗначение <> СтрокаТЧ.ИндивидуальныйНомер Тогда
// Выполняем логику при изменении значения
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Этот метод идеален для логирования изменений или проверки прав доступа при редактировании полей, но он не поможет, если данные нужно искать в других (предыдущих) документах.
Если ваша конфигурация построена на базе Библиотеки Стандартных Подсистем (БСП), рассмотрите возможность включения штатного механизма версионирования объектов. Проанализируем, как это работает:
При каждом сохранении документа система делает «слепок» его состояния в специальный регистр сведений ВерсииОбъектов. Вы можете программно обратиться к этому регистру, извлечь XML-данные предыдущей версии и сравнить их с текущими. Однако это достаточно тяжелая операция, которую не рекомендуется использовать непосредственно в транзакции проведения документа.
Выясним причину, почему разработчики чаще всего выбирают именно периодический регистр сведений:
МАКСИМУМ(Дата) и вложенными соединениями.Если вам нужно реализовать сравнение состояний, создайте регистр сведений, сделайте его Периодическим (с периодичностью «В пределах секунды» или «По позиции регистратора») и записывайте в него данные в процедуре ОтработкаПроведения вашего документа. При следующем проведении (или проведении другого документа) вы легко получите эти данные через СрезПоследних.