制造业 ERP 10年从业小结 (不断更新中)
本人在坛子中潜水多年,非常感谢ITPUB所提供的交流与学习的机会,从中获益良多却自问没有什么贡献。这几年一直想做一个专属于自己的总结,比如在制造业中,究竟有些什么难点,例如ECN, Alternative Part等等以及可能的解决方案,又有哪些功能、报表是制造业非常重视和常用的。只是根据自己的经验来说,权当分享。希望各位朋友多多发表意见,献花拍砖一律不拘。
转载引用等请注明作者(Vincent Liao)及出处(ITPUB)。
Part I: Critical ERP Reports
1.Slow Moving Report (Dead Stock Report) / 低流动性存货报表(呆料报表)
根据企业的定义,对当前存货进行流动性分析。例如:基于当前日期,6个月内没有流动的存货定义为低流动性存货(Slow Moving Stocks)。而把1年内都没有流动的存货定义为呆料(Obsolete Stocks) 。
重点/难点:
1)日期范围的确定
2)流动的区分:一般是以调整性交易(Movement) 作为判断标准。如果在定义的时间区间内没有调整性交易即为低流动性存货。调整性交易一般包括采购及其他方式入库和生产消耗及出货/出库等。调整性交易会造成在公司层面上的存货增加或减少。而其他例如转移性交易(Transfer) 则为非流动性交易,不应纳入此报表的考虑范围。转移性交易一般是指存货在不同仓库/区位的转移,不会造成在公司层面上的存货增加或减少,而只是从左口袋转到右口袋的概念。另外像盘点调整,报废交易等均不应纳入流动性计算。
3)在报表中不仅要列出低流动性存货的数量,还要列出金额
4)报表可以让用户选择针对哪一个仓库/区位进行分析,以及针对怎样的流动性(存货交易)进行分析。
转载引用等请注明作者(Vincent Liao)及出处(ITPUB)。
Part I: Critical ERP Reports
1.Slow Moving Report (Dead Stock Report) / 低流动性存货报表(呆料报表)
根据企业的定义,对当前存货进行流动性分析。例如:基于当前日期,6个月内没有流动的存货定义为低流动性存货(Slow Moving Stocks)。而把1年内都没有流动的存货定义为呆料(Obsolete Stocks) 。
重点/难点:
1)日期范围的确定
2)流动的区分:一般是以调整性交易(Movement) 作为判断标准。如果在定义的时间区间内没有调整性交易即为低流动性存货。调整性交易一般包括采购及其他方式入库和生产消耗及出货/出库等。调整性交易会造成在公司层面上的存货增加或减少。而其他例如转移性交易(Transfer) 则为非流动性交易,不应纳入此报表的考虑范围。转移性交易一般是指存货在不同仓库/区位的转移,不会造成在公司层面上的存货增加或减少,而只是从左口袋转到右口袋的概念。另外像盘点调整,报废交易等均不应纳入流动性计算。
3)在报表中不仅要列出低流动性存货的数量,还要列出金额
4)报表可以让用户选择针对哪一个仓库/区位进行分析,以及针对怎样的流动性(存货交易)进行分析。
2.Stock Aging Report /货龄报表
货龄分析的主要目的是让企业了解其存货的年龄结构,一般分为原材料的货龄分析和成品的货龄分析。例如:基于当前日期,针对某一物料,分别报告1个月前进来的存货数量,3个月前进来的存货数量,6个月前进来的存货数量,以及1年前进来的存货数量等。
重点/难点:
1)日期范围的确定,根据企业的物料类型及采购和生产周期等进行分析后设定出来
2)何谓货龄?即存货的年龄,一般是基于先进先出的假设,将存货在某一日期的结存数量为依据往前倒推计算,针对每一笔存货增加的交易(Move In)进行比对,得出所定义的货龄区间的对应存货数量。具体计算过程请参见以下举例。
3)货龄计算举例:
已知数据:
Item A
2009-01-01采购 1000 pcs
2009-01-07发出 500 pcs
2009-01-28采购 1200 pcs
2009-02-10发出 1000 pcs
2009-03-28采购 800 pcs
2009-04-01发出 200 pcs
------------------------------------
2009-04-28结存 1300 pcs
计算过程:
第一步:以2009-04-28的结存为计算基础,即在4月28日有存货1300 pcs
第二步:往前倒推,找到于3月28日发生的第一笔存货增加的交易(在本例中为采购收货,下同),数量为800 pcs。根据先进先出的假设,目前的存货为1300,那么这1300当中,有800是在3月28日进来的。所以属于1个月货龄的情况。剩余的500 pcs是在此之前进来的。
第三步:再继续往前倒推,找到于1月28日发生的第二笔存货增加的交易,数量为1200 pcs。由此可推断第二步中剩余的500 pcs是跟这一笔交易有关。即500 pcs属于3个月货龄的情况。因全部数量已经分配完毕,所以不再继续倒推计算。
第四步:产生报告如下:
Item On-Hand Aging (1 Month) Aging (3 Months) Aging (6 Months) Aging (1 Year)
----------------------------------------------------------------------------------------------------------------------------------
A 1300 800 500 0 0
4)在报表中不仅要列出针对每一货龄区间的存货的数量,还要列出金额
5)报表可以让用户选择针对哪一个仓库/区位进行分析
货龄分析的主要目的是让企业了解其存货的年龄结构,一般分为原材料的货龄分析和成品的货龄分析。例如:基于当前日期,针对某一物料,分别报告1个月前进来的存货数量,3个月前进来的存货数量,6个月前进来的存货数量,以及1年前进来的存货数量等。
重点/难点:
1)日期范围的确定,根据企业的物料类型及采购和生产周期等进行分析后设定出来
2)何谓货龄?即存货的年龄,一般是基于先进先出的假设,将存货在某一日期的结存数量为依据往前倒推计算,针对每一笔存货增加的交易(Move In)进行比对,得出所定义的货龄区间的对应存货数量。具体计算过程请参见以下举例。
3)货龄计算举例:
已知数据:
Item A
2009-01-01采购 1000 pcs
2009-01-07发出 500 pcs
2009-01-28采购 1200 pcs
2009-02-10发出 1000 pcs
2009-03-28采购 800 pcs
2009-04-01发出 200 pcs
------------------------------------
2009-04-28结存 1300 pcs
计算过程:
第一步:以2009-04-28的结存为计算基础,即在4月28日有存货1300 pcs
第二步:往前倒推,找到于3月28日发生的第一笔存货增加的交易(在本例中为采购收货,下同),数量为800 pcs。根据先进先出的假设,目前的存货为1300,那么这1300当中,有800是在3月28日进来的。所以属于1个月货龄的情况。剩余的500 pcs是在此之前进来的。
第三步:再继续往前倒推,找到于1月28日发生的第二笔存货增加的交易,数量为1200 pcs。由此可推断第二步中剩余的500 pcs是跟这一笔交易有关。即500 pcs属于3个月货龄的情况。因全部数量已经分配完毕,所以不再继续倒推计算。
第四步:产生报告如下:
Item On-Hand Aging (1 Month) Aging (3 Months) Aging (6 Months) Aging (1 Year)
----------------------------------------------------------------------------------------------------------------------------------
A 1300 800 500 0 0
4)在报表中不仅要列出针对每一货龄区间的存货的数量,还要列出金额
5)报表可以让用户选择针对哪一个仓库/区位进行分析
3.Stock Availability and Shortage Report / 存货可用性及短缺报表
此报表的主要目的是,在没有运行MRP的情况下,或者在MRP的结果无法得到总览的情况下,在某一限定的时间区间内,将所有物料(或选定物料)的需求与供应进行比较计算,得出可用存货数量和短缺情况,提供给物料管理部门参考。
重点/难点:
1)销售预测与销售订单的考虑及对冲
2)成品、半成品存货的考虑
3)成品、半成品需求的展开(展BOM)
4)需求的范围定义,即哪些是属于需求
5)供应的范围定义,即哪些是属于供应
6)仓库/区位的层次结构,即考虑的优先顺序。比如说,某一仓库/区位的存货最先拿来供应,另一仓库/区位的存货次之,以此类推。存货可用性及短缺都按此层次结构逐一计算和反映。
7)不良品一般单列出来仅供参考及人为决定,不参与系统自动计算
8)待检品及在检品一般纳入供应的范围
9)计算物料短缺既可考虑需求与供应的时间对应,也可不考虑时间关系。例如将于2009-5-20送货的在途PO是否可以作为2009-04-29日生产的工单需求的供应。一般来说在MTO的环境下不考虑时间关系,尽一切能力来安排供应以配合需求。
4.Material Status Report / 物料状况表
根据当前存货数量情况,针对未来不同时间点(例如每周,每10天,每15天,每月等)分别考虑其在途PO数量及工单等需求数量,计算存货剩余数量或短缺情况。此报表一般只考虑原材料。
重点/难点:
1)未来时间点的定义
2)提供其他辅助信息,例如采购提前期,单价,Where-used (Affected Model) ,采购员组别等
3)此报表一般采用灵活的参数式设计,由用户自定义时间点,然后据此自动分段计算
5.Receive and Issue Stock Total Report / 物料收发汇总表
计算指定时间区间内的物料总收入与总发出数量。包括期初数,收入数,发出数,以及结存数。仓库利用此报表核对他们的收发记录账。
重点/难点:
1)时间区间的选择
2)仓库/区位的选择
3)期初数的计算
4)特殊交易的排除,例如在有些系统中在途订单或工单也会有交易记录在交易表中,需要排除这些记录。另外,有些专用于冲销的调整性交易记录,比如,昨天收入(Move In) 1000 pcs,发现做错了,于是今天又发出(Move Out) 1000 pcs以作冲销,这样的情况也需要通过特殊的标记将其排除。注:对于采购入库类的交易,可以采用负数收货来处理冲销,但对于本身就是调整性交易(Movement)记录则无法按此处理。
6.财务相关报表(待续)
6.1 Accounts Payable (应付)
6.1.1 AP Aging Report / 应付账龄报表
以本位币计算的应付账龄分析报表。
重点/难点:
1) 余额截止日期的选择
2) 起始日期的选择
3) 以供应商发票到期日计算还是以交易日期计算
4) 时间间隔(即账龄分析时段)的选择
5) 可以选择需要进行分析的供应商范围
6.1.2 AP Aging Report Multi Currency / 多币别应付账龄报表
以本位币和原币计算的应付账龄分析报表。
重点/难点:
1) 余额截止日期的选择
2) 起始日期的选择
3) 以供应商发票到期日计算还是以交易日期计算
4) 时间间隔(即账龄分析时段)的选择
5) 可以选择需要进行分析的供应商范围
6.1.3 Vendor Balance List / 供应商余额表
针对每一个供应商的应付余额报表。
重点/难点:
1) 余额截止日期的选择(即并不是只能以当前日期开始计算,而是具有回溯功能)
2) 可以选择供应商范围进行查询/打印
6.1.4 Vendor Account Statement / 供应商对账单
针对每一个供应商的已付、应付情况报表,用于与供应商进行对账。
重点/难点:
1) 起始与截止日期的选择
2) 可以选择供应商范围进行查询/打印
3) 可以结合付款排期表产生报表
6.1.5 Prepayment Report / 供应商预付款报表
针对每一个供应商的预付款情况的报表。
重点/难点:
1) 截止日期的选择
2) 可以选择供应商范围进行查询/打印
6.1.6 Vendor Transactions / 供应商交易报表
针对每一个供应商的交易(从财务的角度讲即发票)的报表。
重点/难点:
1) 可以选择Open的或者全部的供应商发票
2) 对于汇率的处理,可以选择是以调汇日期计算还是以发票日期计算
3) 可以选择供应商范围进行查询/打印
6.1.7 AP Open Packing Slip Report / 供应商已送货但未开发票报表
针对那些已经送货但未开发票的情况的报表。
重点/难点:
1) 可以选择明细或者汇总表
2) 可以选择供应商范围进行查询/打印
6.2 Accounts Receivable (应收)待续
6.3 General Ledger (总账)待续
此报表的主要目的是,在没有运行MRP的情况下,或者在MRP的结果无法得到总览的情况下,在某一限定的时间区间内,将所有物料(或选定物料)的需求与供应进行比较计算,得出可用存货数量和短缺情况,提供给物料管理部门参考。
重点/难点:
1)销售预测与销售订单的考虑及对冲
2)成品、半成品存货的考虑
3)成品、半成品需求的展开(展BOM)
4)需求的范围定义,即哪些是属于需求
5)供应的范围定义,即哪些是属于供应
6)仓库/区位的层次结构,即考虑的优先顺序。比如说,某一仓库/区位的存货最先拿来供应,另一仓库/区位的存货次之,以此类推。存货可用性及短缺都按此层次结构逐一计算和反映。
7)不良品一般单列出来仅供参考及人为决定,不参与系统自动计算
8)待检品及在检品一般纳入供应的范围
9)计算物料短缺既可考虑需求与供应的时间对应,也可不考虑时间关系。例如将于2009-5-20送货的在途PO是否可以作为2009-04-29日生产的工单需求的供应。一般来说在MTO的环境下不考虑时间关系,尽一切能力来安排供应以配合需求。
4.Material Status Report / 物料状况表
根据当前存货数量情况,针对未来不同时间点(例如每周,每10天,每15天,每月等)分别考虑其在途PO数量及工单等需求数量,计算存货剩余数量或短缺情况。此报表一般只考虑原材料。
重点/难点:
1)未来时间点的定义
2)提供其他辅助信息,例如采购提前期,单价,Where-used (Affected Model) ,采购员组别等
3)此报表一般采用灵活的参数式设计,由用户自定义时间点,然后据此自动分段计算
5.Receive and Issue Stock Total Report / 物料收发汇总表
计算指定时间区间内的物料总收入与总发出数量。包括期初数,收入数,发出数,以及结存数。仓库利用此报表核对他们的收发记录账。
重点/难点:
1)时间区间的选择
2)仓库/区位的选择
3)期初数的计算
4)特殊交易的排除,例如在有些系统中在途订单或工单也会有交易记录在交易表中,需要排除这些记录。另外,有些专用于冲销的调整性交易记录,比如,昨天收入(Move In) 1000 pcs,发现做错了,于是今天又发出(Move Out) 1000 pcs以作冲销,这样的情况也需要通过特殊的标记将其排除。注:对于采购入库类的交易,可以采用负数收货来处理冲销,但对于本身就是调整性交易(Movement)记录则无法按此处理。
6.财务相关报表(待续)
6.1 Accounts Payable (应付)
6.1.1 AP Aging Report / 应付账龄报表
以本位币计算的应付账龄分析报表。
重点/难点:
1) 余额截止日期的选择
2) 起始日期的选择
3) 以供应商发票到期日计算还是以交易日期计算
4) 时间间隔(即账龄分析时段)的选择
5) 可以选择需要进行分析的供应商范围
6.1.2 AP Aging Report Multi Currency / 多币别应付账龄报表
以本位币和原币计算的应付账龄分析报表。
重点/难点:
1) 余额截止日期的选择
2) 起始日期的选择
3) 以供应商发票到期日计算还是以交易日期计算
4) 时间间隔(即账龄分析时段)的选择
5) 可以选择需要进行分析的供应商范围
6.1.3 Vendor Balance List / 供应商余额表
针对每一个供应商的应付余额报表。
重点/难点:
1) 余额截止日期的选择(即并不是只能以当前日期开始计算,而是具有回溯功能)
2) 可以选择供应商范围进行查询/打印
6.1.4 Vendor Account Statement / 供应商对账单
针对每一个供应商的已付、应付情况报表,用于与供应商进行对账。
重点/难点:
1) 起始与截止日期的选择
2) 可以选择供应商范围进行查询/打印
3) 可以结合付款排期表产生报表
6.1.5 Prepayment Report / 供应商预付款报表
针对每一个供应商的预付款情况的报表。
重点/难点:
1) 截止日期的选择
2) 可以选择供应商范围进行查询/打印
6.1.6 Vendor Transactions / 供应商交易报表
针对每一个供应商的交易(从财务的角度讲即发票)的报表。
重点/难点:
1) 可以选择Open的或者全部的供应商发票
2) 对于汇率的处理,可以选择是以调汇日期计算还是以发票日期计算
3) 可以选择供应商范围进行查询/打印
6.1.7 AP Open Packing Slip Report / 供应商已送货但未开发票报表
针对那些已经送货但未开发票的情况的报表。
重点/难点:
1) 可以选择明细或者汇总表
2) 可以选择供应商范围进行查询/打印
6.2 Accounts Receivable (应收)待续
6.3 General Ledger (总账)待续
Part II: Critical Program Logic
1.BOM Explosion / 展BOM
BOM表的结构抽象化之后一般如下:
A
|
--------------------------
| |
B(2) C(1)
|
-----------------------
| |
D(1) E(1)
|
--------------------
| |
F(1) G(1)
Table Name: BOM
BOMId ItemId ItemType BOMType BOMQty FromDate ToDate
A B BOM Item 2
A C Item Item 1 2009-05-31
B D BOM Phantom 1
B E Item Item 1
D F Item Item 1
D G Item Item 1
经典的展BOM程序如下:
void BOMExplosion(_TopBOMId, _BOMId, _BOMDate = today(), _BOMQty = 1)
{
while select BOM
where BOM.BOMId == _BOMId
&& (!BOM.FromDate || BOM.FromDate <= _BOMDate)
&& (!BOM.ToDate || BOM.ToDate >= _BOMDate)
{
if (BOM.ItemType != BOM)
{
TmpBOM.TopBOMId = _TopBOMId;
TmpBOM.ItemId = BOM.ItemId;
TmpBOM.BOMQty = BOM.BOMQty * _BOMQty;
TmpBOM.insert();
}
else
{
if (BOM.BOMType == Item)
{
TmpBOM.TopBOMId = _TopBOMId;
TmpBOM.ItemId = BOM.ItemId;
TmpBOM.BOMQty = BOM.BOMQty * _BOMQty;
TmpBOM.insert();
}
BOMExplosion(_TopBOMId, BOM.ItemId, _BOMDate, BOM.BOMQty * _BOMQty);
}
}
}
根据以上程序展开的BOM结果如下:
Table Name: TmpBOM
TopBOMId ItemId BOMQty
A B 2
A C 1
A D 2
A E 2
A F 2
A G 2
1.BOM Explosion / 展BOM
BOM表的结构抽象化之后一般如下:
A
|
--------------------------
| |
B(2) C(1)
|
-----------------------
| |
D(1) E(1)
|
--------------------
| |
F(1) G(1)
Table Name: BOM
BOMId ItemId ItemType BOMType BOMQty FromDate ToDate
A B BOM Item 2
A C Item Item 1 2009-05-31
B D BOM Phantom 1
B E Item Item 1
D F Item Item 1
D G Item Item 1
经典的展BOM程序如下:
void BOMExplosion(_TopBOMId, _BOMId, _BOMDate = today(), _BOMQty = 1)
{
while select BOM
where BOM.BOMId == _BOMId
&& (!BOM.FromDate || BOM.FromDate <= _BOMDate)
&& (!BOM.ToDate || BOM.ToDate >= _BOMDate)
{
if (BOM.ItemType != BOM)
{
TmpBOM.TopBOMId = _TopBOMId;
TmpBOM.ItemId = BOM.ItemId;
TmpBOM.BOMQty = BOM.BOMQty * _BOMQty;
TmpBOM.insert();
}
else
{
if (BOM.BOMType == Item)
{
TmpBOM.TopBOMId = _TopBOMId;
TmpBOM.ItemId = BOM.ItemId;
TmpBOM.BOMQty = BOM.BOMQty * _BOMQty;
TmpBOM.insert();
}
BOMExplosion(_TopBOMId, BOM.ItemId, _BOMDate, BOM.BOMQty * _BOMQty);
}
}
}
根据以上程序展开的BOM结果如下:
Table Name: TmpBOM
TopBOMId ItemId BOMQty
A B 2
A C 1
A D 2
A E 2
A F 2
A G 2
2.Where-used (Top Where-used) / 用途(父件产品及顶层产品)
Where-used一般用于查询物料的用途,即在哪些BOM中用到了该物料。而Top Where-used则用于查询该物料所用于的最终产品信息。在制造业中,这些都是非常有用的资料,比如在一个ECN中需要更改一个物料,那么通过Top Where-used即可以马上知道这个ECN会影响到哪些产品。
在Where-used的算法中,关键是是否要考虑BOM的版本,以及生效/失效日期等。
以下是一个经典的Where-used (Top Where-used) 的示例程序。其中用到了Container来储存信息,其实用数据表(Table) 也是同样可以的。
void Whereused(sourceItemId, bomDate = today())
{
BOM BOM_A, BOM_B;
BOMVersion bomVersion;
Set tmpContainer, tmpWhereusedItems, tmpWhereusedTopItems;
SetEnumerator se;
ItemId tmpItemId;
;
// Initialization
tmpContainer = new Set(Types::String);
tmpWhereusedItems = new Set(Types::String);
tmpWhereusedTopItems = new Set(Types::String);
// Get all the active boms
while select BOM_A
where BOM_A.ItemId == sourceItemId
&& (!BOM_A.ToDate || BOM_A.ToDate >= bomDate)
&& (!BOM_A.FromDate || BOM_A.FromDate <= bomDate)
{
select firstonly bomVersion
where bomVersion.BOMId == BOM_A.BOMId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion
&& (!tmpWhereusedItems.in(bomVersion.ItemId)
&& !tmpContainer.in(bomVersion.ItemId)))
{
tmpContainer.add(bomVersion.ItemId);
}
}
//loop the program while the temporary container have ItemId
se = tmpContainer.getEnumerator();
while (!tmpContainer.empty())
{
// get the first one
se.moveNext();
tmpItemId = se.current();
// remove -> reduce the element count
tmpContainer.remove(tmpItemId);
tmpWhereusedItems.add(tmpItemId);
se.reset();
select firstonly BOM_B
where BOM_B.ItemId == tmpItemId
&& (!BOM_B.ToDate || BOM_B.ToDate >= bomDate)
&& (!BOM_B.FromDate || BOM_B.FromDate <= bomDate);
if (BOM_B) //if the tmpItemId is still used in other boms
{
//get all ItemIds which consists the tmpItemId
while select BOM_B
where BOM_B.ItemId == tmpItemId
&& (!BOM_B.ToDate || BOM_B.ToDate >= bomDate)
&& (!BOM_B.FromDate || BOM_B.FromDate <= bomDate)
{
select firstonly bomVersion
where bomVersion.BOMId == BOM_B.BOMId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion
&& (!tmpWhereusedItems.in(bomVersion.ItemId)
&& !tmpContainer.in(bomVersion.ItemId)))
{
// for SET, there will ensure no duplication
tmpContainer.add(bomVersion.ItemId);
}
}
}
else // if the tmpItemId is not used in other boms, this is the topest layer ItemId
{
select firstonly bomVersion
where bomVersion.ItemId == tmpItemId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion)
{
// get the ItemId, and put it to another container
{
tmpWhereusedTopItems.add(tmpItemId);
}
}
}
}
}
Where-used一般用于查询物料的用途,即在哪些BOM中用到了该物料。而Top Where-used则用于查询该物料所用于的最终产品信息。在制造业中,这些都是非常有用的资料,比如在一个ECN中需要更改一个物料,那么通过Top Where-used即可以马上知道这个ECN会影响到哪些产品。
在Where-used的算法中,关键是是否要考虑BOM的版本,以及生效/失效日期等。
以下是一个经典的Where-used (Top Where-used) 的示例程序。其中用到了Container来储存信息,其实用数据表(Table) 也是同样可以的。
void Whereused(sourceItemId, bomDate = today())
{
BOM BOM_A, BOM_B;
BOMVersion bomVersion;
Set tmpContainer, tmpWhereusedItems, tmpWhereusedTopItems;
SetEnumerator se;
ItemId tmpItemId;
;
// Initialization
tmpContainer = new Set(Types::String);
tmpWhereusedItems = new Set(Types::String);
tmpWhereusedTopItems = new Set(Types::String);
// Get all the active boms
while select BOM_A
where BOM_A.ItemId == sourceItemId
&& (!BOM_A.ToDate || BOM_A.ToDate >= bomDate)
&& (!BOM_A.FromDate || BOM_A.FromDate <= bomDate)
{
select firstonly bomVersion
where bomVersion.BOMId == BOM_A.BOMId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion
&& (!tmpWhereusedItems.in(bomVersion.ItemId)
&& !tmpContainer.in(bomVersion.ItemId)))
{
tmpContainer.add(bomVersion.ItemId);
}
}
//loop the program while the temporary container have ItemId
se = tmpContainer.getEnumerator();
while (!tmpContainer.empty())
{
// get the first one
se.moveNext();
tmpItemId = se.current();
// remove -> reduce the element count
tmpContainer.remove(tmpItemId);
tmpWhereusedItems.add(tmpItemId);
se.reset();
select firstonly BOM_B
where BOM_B.ItemId == tmpItemId
&& (!BOM_B.ToDate || BOM_B.ToDate >= bomDate)
&& (!BOM_B.FromDate || BOM_B.FromDate <= bomDate);
if (BOM_B) //if the tmpItemId is still used in other boms
{
//get all ItemIds which consists the tmpItemId
while select BOM_B
where BOM_B.ItemId == tmpItemId
&& (!BOM_B.ToDate || BOM_B.ToDate >= bomDate)
&& (!BOM_B.FromDate || BOM_B.FromDate <= bomDate)
{
select firstonly bomVersion
where bomVersion.BOMId == BOM_B.BOMId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion
&& (!tmpWhereusedItems.in(bomVersion.ItemId)
&& !tmpContainer.in(bomVersion.ItemId)))
{
// for SET, there will ensure no duplication
tmpContainer.add(bomVersion.ItemId);
}
}
}
else // if the tmpItemId is not used in other boms, this is the topest layer ItemId
{
select firstonly bomVersion
where bomVersion.ItemId == tmpItemId
&& bomVersion.Active == true
&& bomVersion.Approved == true
&& (!bomVersion.FromDate || bomVersion.FromDate <= bomDate)
&& (!bomVersion.ToDate || bomVersion.ToDate >= bomDate);
if (bomVersion)
{
// get the ItemId, and put it to another container
{
tmpWhereusedTopItems.add(tmpItemId);
}
}
}
}
}
Part III: Alternative Part
1. 为什么会有替代料的概念?
在制造行业替代料是一个很普遍的问题。根据我的总结,出现替代料的情况至少有以下几种原因(但不限于):
1)供应原因:供应商无法及时供应原定的物料,需要使用替代料
2)成本原因:替代料的成本与主料的成本不一致,根据市场需要决定用哪一种料
3)质量原因:替代料的质量与主料的质量不一致,根据市场需要决定用哪一种料
4)合规原因:在不同的地区,对某一物料的法规要求不一致,例如有铅无铅(RoHS)
5)清理旧料库存原因:某一旧的物料仍然可用,将旧料用完再用新料(一般在不影响产品功能的前提下)
2. 替代料处理的难点
在N年前曾在ITPUB上讨论过替代料的解决方案,但没有正解。现在看来很多的主流ERP产品仍然没有完善的解决方案。我们现在的公司是通过自己二次开发来解决的。那为什么处理替代料就那么难呢?个人认为其难点有以下几点:
1)替代关系:替代关系不仅仅是1对1的,还有多对多的,比如,我一个按钮需要配一个盖,这是一种组合(比如颜色的要求等)。那么当这个按钮有替代的时候,它的盖子也是需要一并替换的。绝大多数的ERP都处理不了这一点。
2)优先顺序:替代关系可能有多个,比如A既可由B替代,又可由C替代,等等
3)替代层迭:替代关系可能不止一层,可以有多层替代,例如A可由B替代,而B又可由C替代,等等
4)替代时机:替代时机的选择也是一个问题,有的替代是在主料没有库存的时候;有的替代是在旧料仍有库存的时候;有的替代是在主料的成本高于替代料的成本的时候;有的替代是取决于产品的销往地,例如销往欧洲的产品就必须是无铅的,但是销往非洲的仍然可以含铅,等等
3. 解决方案
在我目前的公司,我们通过自主的二次开发解决了多对多的旧新料(替代料)问题。通过设置替代料关系表,在MRP运算之后马上执行替代料计算,通过供需必须平衡的基本原则,将现有的生产工单和MRP产生的计划生产工单中的物料换成替代料(旧料),同时将采购订单和MRP产生的计划采购订单中的物料相应取消对等的数量。在这个功能上线之后至今,为公司节省了数十万美元(通过使用旧料,如果不将旧料用完,则非常可能变成呆料而必须报废)。
1. 为什么会有替代料的概念?
在制造行业替代料是一个很普遍的问题。根据我的总结,出现替代料的情况至少有以下几种原因(但不限于):
1)供应原因:供应商无法及时供应原定的物料,需要使用替代料
2)成本原因:替代料的成本与主料的成本不一致,根据市场需要决定用哪一种料
3)质量原因:替代料的质量与主料的质量不一致,根据市场需要决定用哪一种料
4)合规原因:在不同的地区,对某一物料的法规要求不一致,例如有铅无铅(RoHS)
5)清理旧料库存原因:某一旧的物料仍然可用,将旧料用完再用新料(一般在不影响产品功能的前提下)
2. 替代料处理的难点
在N年前曾在ITPUB上讨论过替代料的解决方案,但没有正解。现在看来很多的主流ERP产品仍然没有完善的解决方案。我们现在的公司是通过自己二次开发来解决的。那为什么处理替代料就那么难呢?个人认为其难点有以下几点:
1)替代关系:替代关系不仅仅是1对1的,还有多对多的,比如,我一个按钮需要配一个盖,这是一种组合(比如颜色的要求等)。那么当这个按钮有替代的时候,它的盖子也是需要一并替换的。绝大多数的ERP都处理不了这一点。
2)优先顺序:替代关系可能有多个,比如A既可由B替代,又可由C替代,等等
3)替代层迭:替代关系可能不止一层,可以有多层替代,例如A可由B替代,而B又可由C替代,等等
4)替代时机:替代时机的选择也是一个问题,有的替代是在主料没有库存的时候;有的替代是在旧料仍有库存的时候;有的替代是在主料的成本高于替代料的成本的时候;有的替代是取决于产品的销往地,例如销往欧洲的产品就必须是无铅的,但是销往非洲的仍然可以含铅,等等
3. 解决方案
在我目前的公司,我们通过自主的二次开发解决了多对多的旧新料(替代料)问题。通过设置替代料关系表,在MRP运算之后马上执行替代料计算,通过供需必须平衡的基本原则,将现有的生产工单和MRP产生的计划生产工单中的物料换成替代料(旧料),同时将采购订单和MRP产生的计划采购订单中的物料相应取消对等的数量。在这个功能上线之后至今,为公司节省了数十万美元(通过使用旧料,如果不将旧料用完,则非常可能变成呆料而必须报废)。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10500555/viewspace-618330/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/10500555/viewspace-618330/