Работа с накладными в 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.Oid // Oid — это OrderId
  DemandValue: O.Quantity)

// показано, как получить дату
// и как экспортировать T
T.DemandDate = same(O.Date) by O.Oid 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 O with
  "Ref" as Id
  "NetAmount" as NetA : number
  "OrderId" as Oid : number
read "/sample/Lokad_BOM.tsv" as BOM[Id, *] with
  "Bundle" as Id

// concat() испольуется для получения текстового вектора
O.Uid = concat(rank() sort[0.1])

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

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

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

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

show table "Expanded" with
  Id
  T.DemandDate
  T.Quantity
  T.NetA

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

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

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

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

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

Рекурсивные накладные обрабатывать сложнее, чем нерекурсивные. Однако с точки зрения управления информационными ресурсами, рекурсивные накладные обслуживать проще. Таким образом, иногда полезно бывает упростить накладные — то есть, удалить все рекурсивные зависимости из таблицы. Это можно сделать с помощью сценария, приведенного ниже.
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() непосредственно к таблице наименований, а затем фильтруем простые записи (то есть наборы, состоящие из одной детали — самого набора). После фильтрации мы получаем упрощенную таблицу спецификации.