Работа с накладными в Envision

Накладные












Главная » Ресурсы » Здесь

Анализ товарных запасов может быть осложнен из-за накладных (BOM). При использовании BOM продаваемые единицы товара, как правило, не соответствуют закупаемым, так как компания либо производит товары из закупленного сырья, либо комплектует его в наборы. Все наборы или готовые изделия создаются из определенного набора деталей, количество каждой из которых известно. Для оптимизации запасов запчастей обычно требуется перевести исходный спрос, выраженный в наборах или готовых изделиях, в спрос на запчасти. Функция billOfMaterials() в Envision предназначена специально для таких случаев.


Обзор таблицы спецификации

Накладные (bill of materials — BOM) или спецификации изделий представляют собой список расходных материалов, компонентов, подузлов, запчастей и т. д., необходимых для производства изделия (набора) с указанием их количества. Самая простая таблица BOM имеет три столбца:

  • Bundle — идентификатор набора или готового изделия.
  • Part — идентификатор одного из компонентов набора.
  • Quantity — содержит количество единиц конкретного компонента набора.

На практике таблицы BOM часто бывают рекурсивными — набор может состоять из других наборов. Таким образом, для определения конечного списка деталей, необходимых для того или иного набора, таблицу BOM необходимо обрабатывать рекурсивно. Если в таблице BOM есть циклическая зависимость, значит она составлена неправильно. Циклическая зависимость показывает, что набор состоит из самого набора + некоторые другие запчасти, а данное выражение не имеет смысла.

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

Синтаксис функции extend.billOfMaterials()

Цель функции billOfMaterials() — перевод спроса с уровня наборов на уровень деталей. Данное преобразование выполняется с помощью таблицы спецификации, которая позволяет прописать компоненты для всех наборов. Синтаксис функции extend.billOfMaterials() выглядит так:
table T = extend.billOfMaterials(
  Item: J.Id
  Part: B.PartId
  Quantity: B.Quantity
  DemandId: O.OrderId
  DemandValue: O.Quantity)

// показано, как получить дату и как экспортировать 'T'
T.DemandDate = same(O.Date) by O.OrderId at T.DemandId 
show table "Translated" with J.Id, T.DemandDate, T.Quantity
Вы должны получить три таблицы: J, B и O. Таблица J обычно представляет собой таблицу наименований, однако это необязательное требование. Таблица B — это таблица спецификации, и она должна быть расширением таблицы J, то есть типа [Id, *], если J является таблицей наименований. Таблица O должна отражать историю спроса, обычно она привязана к таблице заказов. Таблица O также должна быть расширением таблицы J.

Ниже перечислены аргументы функции:
  • Item — используется для идентификации наборов или запчастей в соответствии с исходной таблицей спроса.
  • Part — используется для идентификации столбца со списком деталей в таблице BOM.
  • Quantity — используется для идентификации столбца с количеством деталей в таблице BOM. Данный аргумент отражает количество единиц B.PartId, которые участвуют в продаже или обслуживании набора, указанного в J.Id.
  • DemandId — используется для сохранения связей между исходной таблицей O и ее расширением T.
  • DemandValue — количество единиц товара в соответствии с исходной таблицей спроса.

Итоговая таблица является расширением таблицы J со всеми соответствующими привязками. Таблица T содержит два поля, которые уже заполнены:
  • T.DemandId — позволяет в таблице T найти исходные строки из таблицы O.
  • T.Quantity — значения берутся из таблицы спецификации.

Очень часто полезно указать дату в таблице T. Это можно сделать с помощью оператора by-at, как показано в строке под блоком billOfMaterials() в примере выше.

Функция extend.billOfMaterials() поддерживает рекурсивные наборы, то есть наименование может появляться в таблице B и как компонент, и как набор. Функция выдает ошибку, если в таблице спецификации обнаруживаются циклические зависимости.

Примеры с готовым набором данных

Следующий сценарий можно выполнить, используя образец набора данных. Сценарий переводит исходную таблицу Orders в новую таблицу T, которая отражает тот же спрос, только выраженный в деталях.
read "/sample/Lokad_Items.tsv" with "Ref" as Id
read "/sample/Lokad_Orders.tsv" as Orders with "Ref" as Id
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with "Bundle" as Id

Orders.Uid = concat(rank(Orders.1)) // 'concat()' to get a 'text' vector

table T = extend.billOfMaterials(
  Item: Id
  Part: BOM.Part
  Quantity: BOM.Quantity
  DemandId: Orders.Uid
  DemandValue: Orders.Quantity)

T.DemandDate = same(Orders.Date) by Orders.Uid at T.DemandId

// присвоение деталям цен реализации
T.NetAmount = same(Orders.NetAmount) by Orders.Uid at T.DemandId
T.NetAmount = T.NetAmount * (BuyPrice * T.Quantity / sum(BuyPrice * T.Quantity) by T.DemandId)

// присвоение наборам цен закупки
Orders.Cost = sum(BuyPrice * T.Quantity) by T.DemandId at Orders.Uid

show table "Expanded" with Id, T.DemandDate, T.Quantity, T.NetAmount

Образец набора данных не содержит идентификатора строк в таблице Orders, поэтому мы создаем его в строке 5. Он нужен только для последующего соединения таблиц. Код в строке 5 нужен лишь для присвоения каждой строке таблицы Orders уникального номера и к преобразованию этого номера в текст посредством функции concat().

Зачастую при продаже наборов у отдельных деталей нет "настоящей" цены реализации. Можно установить произвольную цену реализации для каждой детали, однако рыночная цена все-таки определяется на уровне наборов. Тем не менее с точки зрения оптимизации запасов крайне важно назначить цену реализации деталей. В противном случае невозможно будет рассчитать прибыль от каждой детали — а ведь именно ради прибыли детали и закупаются.

Таким образом, при установке цены реализации деталей мы исходим из цены реализации набора и "доле" рассматриваемой детали в нем. Под «долей» мы понимаем стоимость детали в наборе по сравнению со стоимостью всего набора. В строках 17-18 показано, как выполнять такие расчеты: T.NetAmount — доля чистой прибыли от набора, привязанная к определенной детали. Цена детали будет изменяться в зависимости от того, будет ли продан набор. Обычно окончательную цену реализации детали можно получить, высчитав среднее значение данной цены, скажем, за последние три месяца.

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

Упрощение накладных

Рекурсивные накладные обрабатывать сложнее, чем нерекурсивные. Однако с точки зрения управления информационными ресурсами, рекурсивные накладные обслуживать проще. Таким образом, иногда полезно бывает упростить накладные — то есть, удалить все рекурсивные зависимости из таблицы. Это можно сделать с помощью сценария, приведенного ниже.
read "/sample/Lokad_Items.tsv" with "Ref" as Id
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with "Bundle" as Id

table T = extend.billOfMaterials(
  Item: Id
  Part: BOM.Part
  Quantity: BOM.Quantity
  DemandId: Id
  DemandValue: 1)

where T.DemandId != Id // удаление простых записей
  show table "Flattened BOM" with T.DemandId as "Bundle", Id as "Part", T.Quantity
Мы применяем функцию billOfMaterials() непосредственно к таблице наименований, а затем фильтруем простые записи (то есть наборы, состоящие из одной детали — самого набора). После фильтрации мы получаем упрощенную таблицу спецификации.