В процессе работы с материальными запасами в 1С часто возникает необходимость проанализировать «возраст» товаров на складе. Например, требуется узнать, сколько дней лежит конкретная партия товара и в каком количестве, учитывая, что списание товаров происходит по методу ФИФО (First In, First Out). Рассмотрим, как решить эту задачу на примере конфигурации «Управление производственным предприятием» (УПП 1.3), используя исключительно язык запросов 1С без пост-обработки в коде 1С:Предприятие.
Проанализируем тестовый пример. Предположим, по складу №1 происходили следующие движения номенклатуры «Товар1»:
Списание выполняется по ФИФО, следовательно, списанные 50 штук уходят из первой поступившей партии от 01.03.2018. На дату 10.03.2018 необходимо получить следующий результат остатков с расчетом срока хранения:
Выясним главную сложность: в регистре накопления ТоварыНаСкладах нет измерения партий (в отличие от регистра ПартииТоваровНаСкладах). Нам необходимо искусственно сопоставить текущий физический остаток товара с историей его приходов за весь период, используя механизм нарастающего итога.
Разберем решение по шагам. Для распределения остатка по поступившим партиям в обратном или прямом порядке требуется:
КоличествоОстаток > 0).РАЗНОСТЬДАТ между датой документа оприходования и целевой датой.Рассмотрим итоговый текст запроса, позволяющий решить поставленную задачу:
ВЫБРАТЬ
взОстатки.Номенклатура КАК Номенклатура,
взОстатки.Склад КАК Склад,
взОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ втДанные
ИЗ
(ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.Склад КАК Склад,
СУММА(ТоварыНаСкладахОстатки.КоличествоОстаток) КАК КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(&НаДату, ) КАК ТоварыНаСкладахОстатки
СГРУППИРОВАТЬ ПО
ТоварыНаСкладахОстатки.Номенклатура,
ТоварыНаСкладахОстатки.Склад) КАК взОстатки
ГДЕ
взОстатки.КоличествоОстаток > 0
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ТоварыНаСкладах.Номенклатура КАК Номенклатура,
ТоварыНаСкладах.Склад КАК Склад,
ТоварыНаСкладах.Регистратор КАК Регистратор,
СУММА(ТоварыНаСкладах.Количество) КАК Количество,
ТоварыНаСкладах.Период КАК Период
ПОМЕСТИТЬ втОборот
ИЗ
РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах
ГДЕ
ТоварыНаСкладах.Активность
И ТоварыНаСкладах.Количество > 0
И ТоварыНаСкладах.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
И ТоварыНаСкладах.Период <= &НаДату
И (ТоварыНаСкладах.Номенклатура, ТоварыНаСкладах.Склад) В
(ВЫБРАТЬ
втДанные.Номенклатура,
втДанные.Склад
ИЗ
втДанные)
СГРУППИРОВАТЬ ПО
ТоварыНаСкладах.Номенклатура,
ТоварыНаСкладах.Склад,
ТоварыНаСкладах.Регистратор,
ТоварыНаСкладах.Период
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
втОборот.Номенклатура КАК Номенклатура,
втОборот.Склад КАК Склад,
втОборот.Регистратор КАК Регистратор,
втОборот.Количество КАК Количество,
СУММА(втОборот1.Количество) КАК КоличествоНакопленное
ПОМЕСТИТЬ втОборотСНарастающимИтогом
ИЗ
втОборот КАК втОборот
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОборот КАК втОборот1
ПО втОборот.Номенклатура = втОборот1.Номенклатура
И втОборот.Склад = втОборот1.Склад
И втОборот.Период <= втОборот1.Период
СГРУППИРОВАТЬ ПО
втОборот.Номенклатура,
втОборот.Склад,
втОборот.Регистратор,
втОборот.Количество
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВложенныйЗапрос.Склад КАК Склад,
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ВложенныйЗапрос.Регистратор КАК Приход,
РАЗНОСТЬДАТ(ВложенныйЗапрос.Регистратор.Дата, &НаДату, ДЕНЬ) КАК СрокХраненияВДнях,
ВложенныйЗапрос.Количество - ВЫБОР
КОГДА втДанные.КоличествоОстаток < ВложенныйЗапрос.КоличествоНакопленное
ТОГДА ВложенныйЗапрос.КоличествоНакопленное - втДанные.КоличествоОстаток
ИНАЧЕ 0
КОНЕЦ КАК КоличествоОстатокПартии
ИЗ
втДанные КАК втДанные
ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОборотСНарастающимИтогом КАК ВложенныйЗапрос
ПО втДанные.Номенклатура = ВложенныйЗапрос.Номенклатура
И втДанные.Склад = ВложенныйЗапрос.Склад
ГДЕ
ВложенныйЗапрос.Количество - ВЫБОР
КОГДА втДанные.КоличествоОстаток < ВложенныйЗапрос.КоличествоНакопленное
ТОГДА ВложенныйЗапрос.КоличествоНакопленное - втДанные.КоличествоОстаток
ИНАЧЕ 0
КОНЕЦ > 0
Проанализируем особенности реализации этого пакета запросов:
втДанные: Фиксирует итоговые складские остатки на дату отчета. Мы сразу отсекаем позиции с нулевым или отрицательным остатком, чтобы не производить лишних соединений.втОборот: Отбирает только приходные движения (ВидДвиженияНакопления.Приход). Это позволяет сгруппировать обороты по регистраторам поступления.втОборотСНарастающимИтогом: Ключевой шаг алгоритма. Мы соединяем таблицу оборотов саму с собой по условию втОборот.Период <= втОборот1.Период. В результате получаем нарастающую сумму поступившего товара начиная с самых свежих партий к более старым.втДанные.КоличествоОстаток с нарастающей суммой поступлений. Если нарастающая сумма превышает остаток, то партия была списана частично. Мы вычитаем разницу, оставляя ровно то количество, которое физически числится на складе.РАЗНОСТЬДАТ(ВложенныйЗапрос.Регистратор.Дата, &НаДату, ДЕНЬ), которая возвращает точное число дней, прошедших с момента оприходования товара.При использовании данного подхода на реальных рабочих базах следует учитывать следующие моменты:
1. Производительность при соединении таблиц
Соединение таблицы оборотов самой с собой для вычисления нарастающего итога создаёт квадратичную нагрузку ($O(N^2)$). На больших объемах данных за длительный период запрос может выполняться длительное время. Чтобы оптимизировать работу, рекомендуется ограничивать период выборки приходов (например, брать приходы только за последние 1-2 года).
2. Учет документов перемещения
В регистре ТоварыНаСкладах при перемещении товара между складами создается запись прихода, где регистратором выступает документ ПеремещениеТоваров. В этом случае запрос посчитает срок хранения с момента поступления товара на конкретный склад, а не с момента его первоначального оприходования на предприятие. Если вам требуется видеть историю с учетом первоначальной партии производства или закупки, рекомендуется использовать регистр ПартииТоваровНаСкладах и его измерение ДокументОприходования.