Как запросом 1С извлечь срок хранения номенклатуры в днях по методу ФИФО?

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

В процессе работы с материальными запасами в 1С часто возникает необходимость проанализировать «возраст» товаров на складе. Например, требуется узнать, сколько дней лежит конкретная партия товара и в каком количестве, учитывая, что списание товаров происходит по методу ФИФО (First In, First Out). Рассмотрим, как решить эту задачу на примере конфигурации «Управление производственным предприятием» (УПП 1.3), используя исключительно язык запросов 1С без пост-обработки в коде 1С:Предприятие.

Постановка задачи и логика расчета

Проанализируем тестовый пример. Предположим, по складу №1 происходили следующие движения номенклатуры «Товар1»:

  1. 01.03.2018 — Приход: 100 шт.
  2. 06.03.2018 — Приход: 150 шт.
  3. 08.03.2018 — Расход (списание): 50 шт.

Списание выполняется по ФИФО, следовательно, списанные 50 штук уходят из первой поступившей партии от 01.03.2018. На дату 10.03.2018 необходимо получить следующий результат остатков с расчетом срока хранения:

  1. Партия от 01.03.2018: Остаток 50 шт., хранится 10 дней (разница между 01.03.2018 и 10.03.2018).
  2. Партия от 06.03.2018: Остаток 150 шт., хранится 5 дней (разница между 06.03.2018 и 10.03.2018).

Выясним главную сложность: в регистре накопления ТоварыНаСкладах нет измерения партий (в отличие от регистра ПартииТоваровНаСкладах). Нам необходимо искусственно сопоставить текущий физический остаток товара с историей его приходов за весь период, используя механизм нарастающего итога.

Алгоритм построения запроса через нарастающие итоги

Разберем решение по шагам. Для распределения остатка по поступившим партиям в обратном или прямом порядке требуется:

  1. Сформировать таблицу текущих остатков товаров на заданную дату с фильтром по позициям, имеющим положительный остаток (КоличествоОстаток > 0).
  2. Сформировать таблицу приходных документов (регистраторов), увеличивающих остаток номенклатуры на складе.
  3. Посчитать нарастающий итог приходов по каждому товару и складу, отсортировав движения по моменту времени.
  4. Соединить остатки с нарастающим итогом приходов и с помощью математических выражений вычислить, какая часть партии осталась не списанной.
  5. Рассчитать количество дней хранения с помощью функции РАЗНОСТЬДАТ между датой документа оприходования и целевой датой.

Решение задачи с помощью запроса 1С

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


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ТоварыНаСкладах.Номенклатура КАК Номенклатура,
    ТоварыНаСкладах.Склад КАК Склад,
    ТоварыНаСкладах.Регистратор КАК Регистратор,
    СУММА(ТоварыНаСкладах.Количество) КАК Количество,
    ТоварыНаСкладах.Период КАК Период
ПОМЕСТИТЬ втОборот
ИЗ
    РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах
ГДЕ
    ТоварыНаСкладах.Активность
    И ТоварыНаСкладах.Количество > 0
    И ТоварыНаСкладах.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
    И ТоварыНаСкладах.Период <= &НаДату
    И (ТоварыНаСкладах.Номенклатура, ТоварыНаСкладах.Склад) В
        (ВЫБРАТЬ
            втДанные.Номенклатура,
            втДанные.Склад
        ИЗ
            втДанные)
СГРУППИРОВАТЬ ПО
    ТоварыНаСкладах.Номенклатура,
    ТоварыНаСкладах.Склад,
    ТоварыНаСкладах.Регистратор,
    ТоварыНаСкладах.Период
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    втОборот.Номенклатура КАК Номенклатура,
    втОборот.Склад КАК Склад,
    втОборот.Регистратор КАК Регистратор,
    втОборот.Количество КАК Количество,
    СУММА(втОборот1.Количество) КАК КоличествоНакопленное
ПОМЕСТИТЬ втОборотСНарастающимИтогом
ИЗ
    втОборот КАК втОборот
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОборот КАК втОборот1
        ПО втОборот.Номенклатура = втОборот1.Номенклатура
            И втОборот.Склад = втОборот1.Склад
            И втОборот.Период <= втОборот1.Период
СГРУППИРОВАТЬ ПО
    втОборот.Номенклатура,
    втОборот.Склад,
    втОборот.Регистратор,
    втОборот.Количество
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВложенныйЗапрос.Склад КАК Склад,
    ВложенныйЗапрос.Номенклатура КАК Номенклатура,
    ВложенныйЗапрос.Регистратор КАК Приход,
    РАЗНОСТЬДАТ(ВложенныйЗапрос.Регистратор.Дата, &НаДату, ДЕНЬ) КАК СрокХраненияВДнях,
    ВложенныйЗапрос.Количество - ВЫБОР
        КОГДА втДанные.КоличествоОстаток < ВложенныйЗапрос.КоличествоНакопленное
            ТОГДА ВложенныйЗапрос.КоличествоНакопленное - втДанные.КоличествоОстаток
        ИНАЧЕ 0
    КОНЕЦ КАК КоличествоОстатокПартии
ИЗ
    втДанные КАК втДанные
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ втОборотСНарастающимИтогом КАК ВложенныйЗапрос
        ПО втДанные.Номенклатура = ВложенныйЗапрос.Номенклатура
            И втДанные.Склад = ВложенныйЗапрос.Склад
ГДЕ
    ВложенныйЗапрос.Количество - ВЫБОР
        КОГДА втДанные.КоличествоОстаток < ВложенныйЗапрос.КоличествоНакопленное
            ТОГДА ВложенныйЗапрос.КоличествоНакопленное - втДанные.КоличествоОстаток
        ИНАЧЕ 0
    КОНЕЦ > 0

Детальный анализ работы запроса

Проанализируем особенности реализации этого пакета запросов:

  1. Таблица втДанные: Фиксирует итоговые складские остатки на дату отчета. Мы сразу отсекаем позиции с нулевым или отрицательным остатком, чтобы не производить лишних соединений.
  2. Таблица втОборот: Отбирает только приходные движения (ВидДвиженияНакопления.Приход). Это позволяет сгруппировать обороты по регистраторам поступления.
  3. Таблица втОборотСНарастающимИтогом: Ключевой шаг алгоритма. Мы соединяем таблицу оборотов саму с собой по условию втОборот.Период <= втОборот1.Период. В результате получаем нарастающую сумму поступившего товара начиная с самых свежих партий к более старым.
  4. Расчет оставшегося количества партии: В финальной выборке мы сравниваем общий остаток втДанные.КоличествоОстаток с нарастающей суммой поступлений. Если нарастающая сумма превышает остаток, то партия была списана частично. Мы вычитаем разницу, оставляя ровно то количество, которое физически числится на складе.
  5. Расчет срока хранения: Используется встроенная функция РАЗНОСТЬДАТ(ВложенныйЗапрос.Регистратор.Дата, &НаДату, ДЕНЬ), которая возвращает точное число дней, прошедших с момента оприходования товара.

Важные технические нюансы и ограничения

При использовании данного подхода на реальных рабочих базах следует учитывать следующие моменты:

1. Производительность при соединении таблиц
Соединение таблицы оборотов самой с собой для вычисления нарастающего итога создаёт квадратичную нагрузку ($O(N^2)$). На больших объемах данных за длительный период запрос может выполняться длительное время. Чтобы оптимизировать работу, рекомендуется ограничивать период выборки приходов (например, брать приходы только за последние 1-2 года).

2. Учет документов перемещения
В регистре ТоварыНаСкладах при перемещении товара между складами создается запись прихода, где регистратором выступает документ ПеремещениеТоваров. В этом случае запрос посчитает срок хранения с момента поступления товара на конкретный склад, а не с момента его первоначального оприходования на предприятие. Если вам требуется видеть историю с учетом первоначальной партии производства или закупки, рекомендуется использовать регистр ПартииТоваровНаСкладах и его измерение ДокументОприходования.

← На главную