IP BomQty
iP_BomQty - это функция, которая раскладывает по уровням BOM и считает сколько нужно сырья на каждом уровне для получения конечного продукта в заданном количестве по нормам расходов. Расчет производится в точности до последних знаков после запятой как в Libero Manufacturing Order.
Изначально она была предназначена для сверки BOM с 1С при переходе.
На входе функции:
- готовый продукт или полуфабрикат,
- дата BOM(используется при хранении истории бомов, если истории не ведется, то можно любую дату),
- количество готового продукта или полуфабриката,
- последний параметр показывает нужно ли высчитывать сырье для полуфабрикатов с типом Компонент. 0 - не учитывать 'CO', 1 - учитывать 'CO'. (например, если есть полуфабрикаты фантомы, то для них нужно считать сырье, а если полуфабрикат изготавливается заранее и лежит уже готовый на складе, то считать на него сырье не нужно, достаточно показать сколько нужно этого полуфабриката для конечного продукта)
На выходе функции:
- конечный продукт,
- уровень BOM,
- полуфабрикат, изготавливаемый на уровне,
- необходимое количество этого полуфабриката на уровне для изготовления конечного продукта в заданном на входе количестве,
- ID строки BOM,
- сырье для изготовления полуфабриката на уровне,
- тип компонента,
- норма расхода,
- количество сырья, необходимое для изготовления полуфабриката на уровне в заданном количестве,
CREATE OR REPLACE FUNCTION ip_bomqty(IN numeric, IN timestamp without time zone, IN numeric, IN numeric)
RETURNS TABLE(sel_product_id numeric,
levelno numeric,
m_product_id numeric,
bomqty numeric,
pp_product_bomline_id numeric,
m_productbom_id numeric,
componenttype character,
qtybom numeric,
qtyrequired numeric) AS
$BODY$
/** @author @kinerix Anna Smirnova */
DECLARE L0 iP_BomQtyTypeCOPH[];
DECLARE L1 iP_BomQtyTypeCOPH[];
DECLARE L2 iP_BomQtyTypeCOPH[];
DECLARE LNo numeric := 0;
DECLARE Sel_Prod numeric := $1;
DECLARE QtyReq numeric := $3;
DECLARE P_date date := $2::date;
DECLARE P_COPH numeric := $4; /**when 0 then exclude raw from product with componenttype 'CO', because it is manufactured in other order*/
BEGIN
/**Before need to create custom type in this database*/
/** create type iP_BomQtyTypeCOPH as (Sel_Product_ID numeric,
LevelNo numeric,
M_Product_ID numeric,
BomQty numeric,
PP_Product_BomLine_ID numeric,
M_ProductBom_ID numeric,
ComponentType character(2),
QtyBom numeric,
QtyRequired numeric);*/
L0 = (select ARRAY[(select array_agg(array_to_string(ARRAY[(Sel_Prod,
LNo,
cast(0 as numeric),
QtyReq,
cast(0 as numeric),
Sel_Prod,
'',
cast(1 as numeric),
QtyReq )]::iP_BomQtyTypeCOPH[], ', ')))]::iP_BomQtyTypeCOPH[]);
L1 = L0;
L2 = L0;
LOOP
L1 = (select ARRAY[(select array_agg(array_to_string(ARRAY[(
Sel_Prod,
LNo+1,
t0.M_Product_ID,
t2.QtyRequired,
t1.pp_product_bomline_ID,
t1.M_Product_ID,
t1.ComponentType,
t1.QtyBom,
cast(t1.QtyBom*t2.QtyRequired as numeric(19,5))
)]::iP_BomQtyTypeCOPH[], ', '))
from pp_product_bomline t1
inner join pp_product_bom t0 on t1.pp_product_bom_ID = t0.pp_product_bom_ID
inner join ( select * from unnest(L1) v0
where case when P_COPH=0
then v0.ComponentType<>'CO'
else v0.ComponentType=v0.ComponentType end ) t2
on t2.M_ProductBom_ID = t0.M_Product_ID and t2.LevelNo = LNo
where cast(t1.validfrom as date)<=P_date
and (case when t1.validto is null then '2999-12-31' else cast(t1.validto as date) end)>=P_date
and t1.ComponentType <> 'VA'
and t0.m_product_ID in (select h.M_ProductBom_ID from unnest(L1) h where h.LevelNo = LNo)
)]::iP_BomQtyTypeCOPH[]);
LNo = LNo+1;
L2 = (select ARRAY[(select array_agg(array_to_string(ARRAY[( L2_t.Sel_Product_ID,
L2_t.LevelNo,
L2_t.M_Product_ID,
L2_t.BomQty,
L2_t.PP_Product_BomLine_ID,
L2_t.M_ProductBom_ID,
L2_t.ComponentType,
L2_t.QtyBom,
L2_t.QtyRequired)]::iP_BomQtyTypeCOPH[], ', '))
from
(select t0.Sel_Product_ID, t0.LevelNo, t0.M_Product_ID, t0.BomQty, t0.PP_Product_BomLine_ID,
t0.M_ProductBom_ID, t0.ComponentType, t0.QtyBom, t0.QtyRequired from unnest(L2) t0
union all
select t1.Sel_Product_ID, t1.LevelNo, t1.M_Product_ID, t1.BomQty, t1.PP_Product_BomLine_ID,
t1.M_ProductBom_ID, t1.ComponentType, t1.QtyBom, t1.QtyRequired from unnest(L1) t1) L2_t )]::iP_BomQtyTypeCOPH[]);
EXIT WHEN LNo = 10;
--- EXIT WHEN coalesce(L1,array[0]) = array[cast(0 as numeric)];
--- EXIT WHEN coalesce((select count(*)::numeric from unnest(L1)),0) = cast(0 as numeric);
--- sometimes this exit from loop work's, sometimes - not. You can use HARD for levels of BOM for example LNo = 10 to exit from loop 100%
END LOOP;
RETURN QUERY
select d.Sel_Product_ID,
d.LevelNo,
d.M_Product_ID,
d.BomQty,
d.PP_Product_BomLine_ID,
d.M_ProductBom_ID,
d.ComponentType,
d.QtyBom,
d.QtyRequired from unnest(L2) d;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION ip_bomqty(numeric, timestamp without time zone, numeric, numeric)
OWNER TO adempiere;
