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