Как получить предыдущее состояние данных по товарам из прошлых документов?

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

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

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

Метод 1. Использование периодического регистра сведений (Оптимальный путь)

Как показывает практика и обсуждение, наиболее надежный и производительный способ — это создание периодического регистра сведений. В таком регистре измерением будет выступать Продукт (или Номенклатура), а ресурсом — то значение, историю которого нам нужно хранить (например, ИндивидуальныйНомер).

Рассмотрим преимущества этого метода:

  1. Скорость: Виртуальная таблица СрезПоследних оптимизирована для быстрого получения актуальных данных на любую точку во времени.
  2. Гибкость: Мы можем получить состояние каждого продукта, даже если они в последний раз фигурировали в разных документах с разницей в несколько месяцев.
  3. Целостность: Платформа 1С сама берет на себя управление хронологией в периодических регистрах.

Посмотрим на пример запроса, который позволяет получить предпоследнее значение. Чтобы получить данные «до» текущего документа, мы используем параметр Период в виртуальной таблице, передавая туда Момент Времени текущего документа с исключением (через объект Граница).

Проанализируем пример кода для получения данных в модуле проведения:


// Сначала подготовим список продуктов из табличной части
СписокПродуктов = ЭтотОбъект.Товары.ВыгрузитьКолонку("Номенклатура");

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

// Используем Границу, чтобы не захватить движения текущего документа, если они уже записаны
ГраницаЗапроса = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("МоментВремени", ГраницаЗапроса);
Запрос.УстановитьПараметр("СписокПродуктов", СписокПродуктов);

РезультатЗапроса = Запрос.Выполнить();

Метод 2. Прямой запрос к табличным частям документов

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

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

  1. Сначала помещаем список нужных товаров во временную таблицу.
  2. Соединяем её с табличной частью документов, фильтруя по дате и моменту времени.
  3. Группируем результат, чтобы найти максимум по дате для каждой позиции.

Пример логики запроса:


ВЫБРАТЬ
    РеализацияТовары.Номенклатура КАК Номенклатура,
    МАКСИМУМ(РеализацияТовары.Ссылка.МоментВремени) КАК МаксМомент
ПОМЕСТИТЬ ВТ_ПоследниеМоменты
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ГДЕ
    РеализацияТовары.Номенклатура В (&СписокНоменклатуры)
    И РеализацияТовары.Ссылка.МоментВремени < &ТекущийМомент
СГРУППИРОВАТЬ ПО
    РеализацияТовары.Номенклатура;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ПоследниеМоменты.Номенклатура,
    РеализацияТовары.ИндивидуальныйНомер КАК ПредыдущийЗначение
ИЗ
    ВТ_ПоследниеМоменты КАК ВТ_ПоследниеМоменты
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
        ПО ВТ_ПоследниеМоменты.Номенклатура = РеализацияТовары.Номенклатура
        И ВТ_ПоследниеМоменты.МаксМомент = РеализацияТовары.Ссылка.МоментВремени

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

Метод 3. Сравнение «Объект vs Ссылка» в событии ПередЗаписью

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

Разберем ситуацию: пользователь открыл документ, изменил номер в строке и нажал «Записать». В этот момент:

Рассмотрим, как это реализовать программно:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    Если ЗначениеЗаполнено(Ссылка) Тогда
        // Получаем старую таблицу товаров из базы данных
        СтараяТЧ = Ссылка.Товары.Выгрузить();
        
        Для Каждого СтрокаТЧ Из Товары Цикл
            ПараметрыОтбора = Новый Структура("КлючСвязи", СтрокаТЧ.КлючСвязи);
            НайденныеСтроки = СтараяТЧ.НайтиСтроки(ПараметрыОтбора);
            
            Если НайденныеСтроки.Количество() > 0 Тогда
                СтароеЗначение = НайденныеСтроки[0].ИндивидуальныйНомер;
                Если СтароеЗначение <> СтрокаТЧ.ИндивидуальныйНомер Тогда
                    // Выполняем логику при изменении значения
                КонецЕсли;
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Этот метод идеален для логирования изменений или проверки прав доступа при редактировании полей, но он не поможет, если данные нужно искать в других (предыдущих) документах.

Метод 4. Платформенное версионирование (БСП)

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

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

Подведение итогов и выбор решения

Выясним причину, почему разработчики чаще всего выбирают именно периодический регистр сведений:

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

← На главную