Procesamiento de listas de materiales con Envision

Lista de materiales











Inicio » Recursos » Aquí

El análisis de inventario puede complicarse por la presencia de la lista de materiales (BOM). Cuando existe una BOM, los artículos que se venden o que se ofrecen como servicio generalmente no son los mismos que se compran, ya que tiene lugar en el medio una operación de empaquetado o fabricación. Cada paquete o ensamblaje puede producirse a partir de una lista de piezas, cada una de las cuales corresponde a una cantidad específica del paquete. Para optimizar el nivel de stock de las piezas, generalmente es necesario convertir la demanda original expresada en paquetes o ensamblajes en términos de una demanda expresada en piezas. La función billOfMaterials() en Envision está adaptada específicamente para este caso de uso.


Resumen de la tabla de Lista de Materiales

Una lista de materiales (BOM) o estructura de producto es una lista de materias primas, subensamblajes, ensamblajes intermedios, subcomponentes o piezas, y las cantidades necesarias de cada uno para fabricar un producto final, es decir, el paquete. En su forma más simple, una tabla de BOM incluye tres columnas:

  • La columna Bundle, que identifica el paquete o el producto final.
  • La columna Part, que identifica una de las piezas de un paquete.
  • La columna Quantity, que cuenta la cantidad de unidades necesarias de una pieza específica dentro de un paquete.

En la práctica, las tablas de BOM son a menudo recurrentes: un paquete puede componerse de otros paquetes. Por lo tanto, para identificar las piezas finales que se requieren para un determinado paquete, la tabla BOM tiene que procesarse de modo recurrente. Si se halla una dependencia circular dentro de la tabla de BOM, la tabla es incongruente. Una dependencia circular establece básicamente que un paquete se compone del paquete mismo más algunas otras piezas, algo que no tiene sentido.

Por convención, se supone que un artículo que no se encuentra en la tabla de BOM es un producto final; por lo tanto, ese artículo no requiere procesamiento especial y debería dejarse como está.

Sintaxis de extend.billOfMaterials()

El objetivo de la función billOfMaterials() es convertir la demanda en el nivel de paquete a una demanda expresada en el nivel de pieza. Esta conversión se controla a través de la tabla de lista de materiales, que especifica la composición de cada paquete. La sintaxis de extend.billOfMaterials() es la siguiente:
table T = extend.billOfMaterials(
  Item: J.Id
  Part: B.PartId
  Quantity: B.Quantity
  DemandId: O.OrderId
  DemandValue: O.Quantity)

// ilustra cómo obtener la fecha y el modo de exportar 'T'
T.DemandDate by T.DemandId = same(O.Date) by O.OrderId 
show table "Details" with Id, T.DemandDate, T.Quantity
Se espera que haya tres tablas: J, B y O. La tabla J generalmente es la tabla de artículos, pero no es un requisito obligatorio. La tabla B está pensada como la tabla de la lista de materiales y se espera que sea una extensión de la tabla J, es decir, de tipo (Id, *) cuando J es la tabla de artículos. Se espera que la tabla O sea el historial de demanda, generalmente aludiendo a la tabla de pedidos. También se espera que la tabla O sea una extensión de la tabla J.

Los argumentos de la función son los siguiente:
  • El argumento Item se utiliza para identificar los paquetes o las piezas como se encuentran en la tabla de demanda original.
  • El argumento Part se utiliza para identificar la columna de la pieza en la tabla de BOM.
  • El argumento Quantity se utiliza para identificar la columna de cantidad en la tabla de BOM. Representa el número de unidades de B.PartId involucradas al vender o proporcionar un conjunto identificado por J.Id.
  • El argumento DemandId se utiliza para preservar una relación entre la tabla original O y su extensión T.
  • El argumento DemandValue es la cantidad de unidades presente en la tabla de demanda original.

La tabla resultante es una extensión de la tabla J, con las afinidades adecuadas. La tabla T contiene dos campos ya completos:
  • T.DemandId, que está pensada para ofrecer un modo de identificar en la tabla T las líneas que se originan en la tabla O.
  • T.Quantity, que se obtiene implementando la lista de materiales.

A menudo, resulta útil introducir una fecha en la tabla T. Esto puede realizarse con una instrucción "left-by" como se ilustra en la línea debajo del bloque billOfMaterials() más arriba.

La función extend.billOfMaterials() admite paquetes recurrentes, lo que significa que un artículo puede aparecer como componente o como paquete dentro de la tabla B. Naturalmente, la función falla y genera un error si se encuentran dependencias cíclicas en la tabla de la lista de materiales.

Ilustración con un conjunto de datos de ejemplo

El siguiente script puede ejecutarse directamente utilizando un conjunto de datos de ejemplo. El script traduce la tabla Orders original a una nueva tabla T que representa la misma demanda, solo que re-expresada en el nivel de pieza.
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()' para obtener un vector 'text'

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

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

// asignación de un precio de venta a las piezas
T.NetAmount by T.DemandId = same(Orders.NetAmount) by Orders.Uid
T.NetAmount = T.NetAmount * (BuyPrice * T.Quantity / sum(BuyPrice * T.Quantity) by T.DemandId)

// asignación de un precio de compra a los paquetes
Orders.Cost by Orders.Uid = sum(BuyPrice * T.Quantity) by T.DemandId

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

El conjunto de datos de ejemplo no contiene un identificador de línea en la tabla Orders, de modo que creamos este identificador en la línea 5 con el único objetivo de unir tablas más adelante. La única finalidad de la línea 5 es asignar un número único a cada línea de la tabla Orders, y luego convertir este número en texto a través de la función concat().

A menudo, cuando se venden los paquetes, las piezas no tiene un precio de venta real. Y, si bien es posible asignar un precio de venta arbitrario a cada pieza, el precio de mercado se define fundamentalmente en el nivel de paquete. Aún así, desde un punto de vista de optimización del inventario, es importante asignar un precio de venta a las piezas; de lo contrario, no es posible calcular el margen bruto de la pieza, que es precisamente el motivo por el cual se almacenó cada pieza en un principio.

Por lo tanto, para asignar un precio de venta a las piezas, podemos establecer que cada pieza tendrá un precio de venta proporcional al precio de venta del paquete multiplicado por el peso de la pieza dentro del paquete (que sería el costo de la pieza dentro del paquete comparado con el costo total del paquete). Las líneas 17 y 18 ilustran cómo se puede realizar ese cálculo: T.NetAmount es la fracción del monto neto del paquete asignado a la pieza. Es probable que el precio de la pieza varíe entre diferentes transacciones de acuerdo con el paquete que se vende. Generalmente, el precio de venta final de la pieza se obtiene haciendo el promedio del precio de la pieza sobre, digamos, los últimos 3 meses de transacciones.

Para poder asignar un precio de compra a los paquetes, el cálculo es mucho más sencillo, ya que no requiere ninguna suposición adicional. Lo único que hay que hacer es sumar el precio de compra de las piezas mientras se aplica el múltiplo correcto para sus respectivas cantidades. La línea 21 ilustra cómo puede hacerse ese cálculo.

Aplanamiento de la lista de materiales

Las listas de materiales recurrentes son más complicadas de procesar que las no recurrentes. Aún así, desde un punto de vista de gestión de datos, una lista de materiales recurrente generalmente es más fácil de mantener. Por lo tanto, puede resultar útil aplanar una lista de materiales o, dicho de otro modo, eliminar todas las dependencias recurrentes de la tabla. Este aplanamiento se puede realizar con el script a continuación.
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 // eliminación de entradas triviales
  show table "Flattened BOM" with T.DemandId as "Bundle", Id as "Part", T.Quantity
Aplicamos la billOfMaterials() directamente a la tabla del artículo y luego filtramos las entradas triviales donde los paquetes contienen una sola pieza: el paquete mismo. La tabla filtrada es la lista de materiales aplanada.