技术开发 频道

制造业ERP10年从业小结

【IT168 技术文档】

    本人在坛子中潜水多年,非常感谢ITPUB所提供的交流与学习的机会,从中获益良多却自问没有什么贡献。这几年一直想做一个专属于自己的总结,比如在制造业中,究竟有些什么难点,例如ECN、Alternative Part等,以及可能的解决方案,又有哪些功能、报表是制造业非常重视和常用的。只是根据自己的经验来说,权当分享。希望各位朋友多多发表意见,献花拍砖一律不拘。

    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)报表可以让用户选择针对哪一个仓库/区位进行分析

    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 (总账)待续

    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

    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);
                }
            }
        }
    }
}

    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产生的计划采购订单中的物料相应取消对等的数量。在这个功能上线之后至今,为公司节省了数十万美元(通过使用旧料,如果不将旧料用完,则非常可能变成呆料而必须报废)。

    转载引用等请注明作者(Vincent Liao)及出处(ITPUB)。

    原文链接:http://www.itpub.net/viewthread.php?tid=1157858&extra=page=1%26amp%3Bfilter=digest

0
相关文章