При организации учета услуг ответственного хранения или расчете оборачиваемости перед разработчиком возникает задача: определить точное количество дней нахождения каждой партии товара на складе, а также рассчитать стоимость хранения за выбранный период. При этом стандартные механизмы могут давать погрешности, если товар поступил и был отгружен в один и тот же день или перемещался между внутренними складами организации.
Разберем методику и структуру запроса в 1С, позволяющего корректно учесть суточные остатки, внешние обороты и рассчитать сумму к оплате за услуги хранения.
Для корректного биллинга хранения и анализа сроков нахождения номенклатуры на складе необходимо учесть несколько правил:
Рассмотрим, как зафиксировать факт хранения единицы товара в конкретные сутки. При подневном анализе стандартной таблицы остатков и оборотов день с однодневным оборотом (приход и расход в один день) покажет нулевой начальный и нулевой конечный остатки, что приведет к потере данных о хранении.
Чтобы этого избежать, объем хранимого товара в течение суток рассчитывается по следующему принципу: к начальному остатку на дату прибавляется объем внешнего прихода за этот же день (либо берется формула КонечныйОстаток + Расход).
Проанализируем пример движения товара по дням:
2 + 3 = 5 шт.5 + 0 = 5 шт. (списание в конце дня, факт хранения зафиксирован).0 шт.0 + 4 = 4 шт.Разберем по шагам текст запроса, который формирует подневную сетку, сопоставляет движения по регистру ПартииТоваровНаСкладах и рассчитывает итоговую сумму с учетом тарифной сетки из регистра сведений.
ВЫБРАТЬ
РегламентированныйПроизводственныйКалендарь.ДатаКалендаря КАК ДатаКалендаря,
Движения.Номенклатура КАК Номенклатура,
Движения.СерияНоменклатуры КАК СерияНоменклатуры,
СУММА(ВЫБОР
КОГДА Движения.Период = &ДатаНачалаЗапроса
ТОГДА Движения.КоличествоКонечныйОстаток
ИНАЧЕ ВЫБОР
КОГДА Движения.Период <= РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
ТОГДА Движения.КоличествоОборот
ИНАЧЕ 0
КОНЕЦ
КОНЕЦ) КАК КонечныйОстаток,
Движения.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры
ПОМЕСТИТЬ ВТ_ОстаткиНаНачало
ИЗ
РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК РегламентированныйПроизводственныйКалендарь,
РегистрНакопления.ПартииТоваровНаСкладах.ОстаткиИОбороты(&ДатаНачалаЗапроса, &ДатаОкончанияЗапроса, День, , ) КАК Движения
ГДЕ
РегламентированныйПроизводственныйКалендарь.ДатаКалендаря <= &ДатаОкончанияЗапроса
И РегламентированныйПроизводственныйКалендарь.ДатаКалендаря >= &ДатаНачалаЗапроса
СГРУППИРОВАТЬ ПО
Движения.Номенклатура,
Движения.ХарактеристикаНоменклатуры,
Движения.СерияНоменклатуры,
РегламентированныйПроизводственныйКалендарь.ДатаКалендаря
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Обороты.Период, ДЕНЬ) КАК Период,
Обороты.Номенклатура КАК Номенклатура,
Обороты.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
Обороты.СерияНоменклатуры КАК СерияНоменклатуры,
СУММА(ВЫБОР
КОГДА Обороты.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
ТОГДА Обороты.Количество
ИНАЧЕ Обороты.Количество * -1
КОНЕЦ) КАК Количество
ПОМЕСТИТЬ ВТ_Обороты
ИЗ
РегистрНакопления.ПартииТоваровНаСкладах КАК Обороты
ГДЕ
Обороты.Период >= &ДатаНачалаЗапроса
И Обороты.Период <= &ДатаОкончанияЗапроса
И НЕ Обороты.КодОперации В (&СписокОпераций)
СГРУППИРОВАТЬ ПО
Обороты.Номенклатура,
Обороты.ХарактеристикаНоменклатуры,
Обороты.СерияНоменклатуры,
НАЧАЛОПЕРИОДА(Обороты.Период, ДЕНЬ)
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВложенныйЗапрос.ДатаКалендаря КАК ДатаКалендаря,
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ЕСТЬNULL(Расценки.Цена, 0) КАК Цена
ПОМЕСТИТЬ ВТ_РасценкиНаХранение
ИЗ
(ВЫБРАТЬ
ВТ_ОстаткиНаНачало.Номенклатура КАК Номенклатура,
МАКСИМУМ(Расценки.Период) КАК Период,
ВТ_ОстаткиНаНачало.ДатаКалендаря КАК ДатаКалендаря
ИЗ
ВТ_ОстаткиНаНачало КАК ВТ_ОстаткиНаНачало
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РасценкиНаХранение КАК Расценки
ПО ВТ_ОстаткиНаНачало.ДатаКалендаря >= Расценки.Период
И ВТ_ОстаткиНаНачало.Номенклатура = Расценки.Номенклатура
СГРУППИРОВАТЬ ПО
ВТ_ОстаткиНаНачало.Номенклатура,
ВТ_ОстаткиНаНачало.ДатаКалендаря) КАК ВложенныйЗапрос
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РасценкиНаХранение КАК Расценки
ПО ВложенныйЗапрос.Период = Расценки.Период
И ВложенныйЗапрос.Номенклатура = Расценки.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ОстаткиНаНачало.ДатаКалендаря КАК ДатаКалендаря,
ВТ_ОстаткиНаНачало.Номенклатура КАК Номенклатура,
ВТ_ОстаткиНаНачало.СерияНоменклатуры КАК СерияНоменклатуры,
ВТ_ОстаткиНаНачало.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
ВТ_ОстаткиНаНачало.КонечныйОстаток - ЕСТЬNULL(ВТ_Обороты.Количество, 0) + ВЫБОР
КОГДА ЕСТЬNULL(ВТ_Обороты.Количество, 0) > 0
ТОГДА ЕСТЬNULL(ВТ_Обороты.Количество, 0)
ИНАЧЕ 0
КОНЕЦ КАК ХранилосьНаСкладе,
(ВТ_ОстаткиНаНачало.КонечныйОстаток - ЕСТЬNULL(ВТ_Обороты.Количество, 0) + ВЫБОР
КОГДА ЕСТЬNULL(ВТ_Обороты.Количество, 0) > 0
ТОГДА ЕСТЬNULL(ВТ_Обороты.Количество, 0)
ИНАЧЕ 0
КОНЕЦ) * (ВТ_РасценкиНаХранение.Цена / ДЕНЬ(КОНЕЦПЕРИОДА(ВТ_ОстаткиНаНачало.ДатаКалендаря, МЕСЯЦ))) КАК КОплатеЗаДень
ИЗ
ВТ_ОстаткиНаНачало КАК ВТ_ОстаткиНаНачало
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Обороты КАК ВТ_Обороты
ПО ВТ_ОстаткиНаНачало.ДатаКалендаря = ВТ_Обороты.Период
И ВТ_ОстаткиНаНачало.Номенклатура = ВТ_Обороты.Номенклатура
И ВТ_ОстаткиНаНачало.СерияНоменклатуры = ВТ_Обороты.СерияНоменклатуры
И ВТ_ОстаткиНаНачало.ХарактеристикаНоменклатуры = ВТ_Обороты.ХарактеристикаНоменклатуры
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_РасценкиНаХранение КАК ВТ_РасценкиНаХранение
ПО ВТ_ОстаткиНаНачало.Номенклатура = ВТ_РасценкиНаХранение.Номенклатура
И ВТ_ОстаткиНаНачало.ДатаКалендаря = ВТ_РасценкиНаХранение.ДатаКалендаря
УПОРЯДОЧИТЬ ПО
Номенклатура,
СерияНоменклатуры,
ХарактеристикаНоменклатуры,
ДатаКалендаря
Посмотрим на ключевые нюансы, которые необходимо учесть при внедрении данного запроса в рабочую конфигурацию:
РегистрСведений.РегламентированныйПроизводственныйКалендарь. Если в информационной базе календарь заполнен не на все будущие или прошлые периоды, запрос вернет неполные данные. Рекомендуется перед выполнением запроса передавать массив дат за требуемый период через ТаблицаЗначений в менеджер временных таблиц.ВТ_Обороты установлен фильтр НЕ Обороты.КодОперации В (&СписокОпераций). В параметр &СписокОпераций необходимо передать значение перечисления КодыОперацийПартииТоваров.Перемещение (или аналогичные внутренние операции), чтобы исключить задвоение оборотов при перемещении между складами.ПартииТоваровНаСкладах могут формироваться с задержкой, если списание партий выполняется регламентной обработкой Проведение по партиям. Перед формированием биллинга убедитесь, что партионный учет восстановлен на конец анализируемого периода.ПериодДень, указав границы дат отчета. СКД автоматически сгенерирует недостающие дни без просадки производительности СУБД.