生产订单开工批号匹配问题

生产订单开工批号匹配问题

客户需求

Bom子项的发料方式为【开工倒冲】时,开工后,生成领料单时子项匹配不到对应的批号!客户的批号基本是手工维护,在做期初或者杂收单等单据时已维护号批号,需要根据拣货规则取出当前生产订单备料表料品对应的批号!

开发方式

开工后会自动创建领料单,在领料单的setDefaultValue事件,去匹配对应的批号,拆行或不拆行

错误点

  1. 不拆行没问题,但是只符合当前批号库存满足需求量的情况
  2. 需要拆行的话,错误层出不穷!

拆行错误展示

错误信息

//错误代码展示:
//备份表体信息
IssueDocLine hisIss = holder.IssueDocLines[0];
//删除原先所有表体
 for (int curIdx = holder.IssueDocLines.Count - 1; curIdx >= 0; curIdx--)
 {
     holder.IssueDocLines.RemoveAt(curIdx);
 }
 //重新定义表体行!!
 IssueDocLine iss1 = IssueDocLine.Create(holder);
 long iss1ID = iss1.ID;
 //从备份的hislss复制到新的iss1
Base.BusinessEntityHelper.CopyTo(hisIss, iss1, false);
 LotMaster lotMaster2 = LotMaster.Finder.Find(" LotCode = 'Test01'");
 iss1.LotMaster= lotMaster2;
 iss1.ID = iss1ID;
 iss1.IssueQty = 80;
 iss1.IssuedQty = 80;
 iss1.LineNum = 10;
 Session.Current.InList(iss1);
 IssueDocLine iss2 = IssueDocLine.Create(holder);
 long iss2ID = iss2.ID;
//从备份的hislss复制到新的iss2
 Base.BusinessEntityHelper.CopyTo(hisIss, iss2, false);
 iss2.ID = iss2ID;
 iss2.LineNum = 20;
 iss2.IssueQty = 20;
 iss2.IssuedQty = 20;
 LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = 'Test002'");
 iss2.LotMaster = lotMaster;
 Session.Current.InList(iss2);

错误点:

  1. 删除了之前的行记录
  2. 重新赋值了ID
    在这里插入图片描述
LotMaster lotMaster2 = LotMaster.Finder.Find(" LotCode = 'Test01'");
holder.IssueDocLines[0].LotMaster = lotMaster2;
holder.IssueDocLines[0].IssueQty = 80;
holder.IssueDocLines[0].IssuedQty = 80;
IssueDocLine iss2 = IssueDocLine.Create(holder);
long iss2ID = iss2.ID;
Base.BusinessEntityHelper.CopyTo(holder.IssueDocLines[0], iss2, false);
iss2.ID = iss2ID;
iss2.LineNum = 20;
iss2.IssueQty = 20;
iss2.IssuedQty = 20;
LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = 'Test002'");
iss2.LotMaster = lotMaster;
Session.Current.InList(iss2);

错误点
3. 这里其实代码没错误!,但是最严重的错误就是,setDefalutValue会再给赋值的情况下不断地进入此方法,所以每进入一次,则插入一条记录,导致提示行号重复!!!

详细代码



namespace UFIDA.U9.Cust.SongYuan.LingLiaoPluginBE
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using UFIDA.U9.Base;
    using UFSoft.UBF.Business;
    using UFIDA.U9.Complete.RCVRpt;
    using UFSoft.UBF.Util.DataAccess;
    using UFSoft.UBF.Util.Log;
    using UFSoft.UBF.Util.Context;
    using UFIDA.U9.CBO.SCM;
    using UFSoft.UBF.Transactions;
    using UFSoft.UBF.Eventing;
    using UFIDA.U9.MO.MO;
    using UFIDA.U9.Complete.CompleteRpt;
    using UFIDA.U9.MO.SFC.DispatchOrder;
    using UFIDA.U9.MO.Issue;
    using UFIDA.U9.CBO.SCM.Item;
    using UFIDA.U9.CBO.MFG.BOM;
    using UFIDA.U9.PM.Rcv;
    using System.Linq;
    using UFIDA.U9.Complete.Enums;
    using UFIDA.U9.InvDoc.TransferIn;
    using UFIDA.U9.Lot;
    using UFIDA.U9.InvDoc.MiscRcv;
    using UFIDA.U9.InvDoc.Enums;
    using UFIDA.U9.MO.Enums;
    using UFIDA.U9.CBO.SCM.Warehouse;
    using UFIDA.U9.Base.Organization;
    using UFIDA.U9.InvDoc.WhInit;
    using UFIDA.U9.InvDoc.TransferForm;
    using System.Data;
    using UFSoft.UBF.Sys.Database;
    using UFIDA.U9.Base.Profile;

    public partial class ShipUpdatedExtend : IEventSubscriber
    {
        ILogger logger = LoggerManager.GetLogger("=======赋值完工倒冲物料批号================");
        public long issueDocID;
        public void Notify(object[] args)
        {        
            #region 从事件参数中取得当前业务实体													 
            if (args == null || args.Length == 0 || !(args[0] is UFSoft.UBF.Business.EntityEvent))
                return;
            BusinessEntity.EntityKey key = ((UFSoft.UBF.Business.EntityEvent)args[0]).EntityKey;
            if (key == null)
                return;
            IssueDoc holder = key.GetEntity() as IssueDoc;
            if (holder == null)
                return;
            //判断第几次进来! 如果当前领料行大于备料行,代表进行过拆行操作,则直接退出
            MOPickList.EntityList validateMOPickList = MOPickList.Finder.FindAll(" MO = '" + holder.IssueDocLines[0].MO.ID + "'");

            if (holder.IssueDocLines.Count > validateMOPickList.Count)
            {
                return;
            }
            int docLineC = holder.IssueDocLines.Count;
            //这里要对holder.IssueDocLines先赋值到另一个对象,再遍历另一个对象,否则后面拆行后holder.IssueDocLines值会不断增加,陷入死循环!!
            for (int b = 0;b < docLineC; b++)
            {
                //
                MO mo = holder.IssueDocLines[b].MO;
                if (mo == null)
                {
                    logger.Error("生产订单获取为空");
                    return;
                }
                ItemMaster item = holder.IssueDocLines[b].Item;
                if (item == null)
                {
                    logger.Error("料品获取为空,生产订单单号:" + holder.IssueDocLines[b].MO.DocNo);
                    return;
                }
                //判断物料的批号参数是否为空,为空进行下一个备料的批号匹配 2023-06-27
                if (item.InventoryInfo == null || item.InventoryInfo.LotParam == null)
                {
                    continue;
                }
                //查找批号:
                //1. 先判断Bom里是否是完工倒冲
                //查找生产订单的Bom母项
                BOMMaster bOMMaster = mo.BOMMaster;
                if (bOMMaster == null)
                {
                    logger.Error("Bom母项获取为空,生产订单单号:" + holder.IssueDocLines[b].MO.DocNo);
                    return;
                }
                //获取母项子项 -- 2023 08 25修改 不是根据Bom子项来做 ,而是根据备料表来做
                MOPickList.EntityList mOPickList = MOPickList.Finder.FindAll(" MO = '" + mo.ID + "'");
                if (mOPickList == null || mOPickList.Count == 0)
                {
                    logger.Error("备料获取为空,生产订单单号:" + holder.IssueDocLines[b].MO.DocNo);
                    return;
                }
                UFIDA.U9.Lot.LotMaster rcvLot = null;
                foreach (MOPickList mOPick in mOPickList)
                {
                    //判断子项发料方式是否为开工倒冲
                    if (mOPick.IssueStyle != null && UFIDA.U9.CBO.MFG.Enums.IssueStyleEnum.StartWorkingPull == mOPick.IssueStyle)
                    {
                        //判断领料表体此行物料是否是同一物料 -- 
                        if (item.ID == mOPick.ItemMaster.ID)
                        {

                            //判断料品为采购件或者是制造件
                            if (ItemTypeAttributeEnum.PurchasePart == item.ItemFormAttribute)
                            {
                                //按照顺序,期初库存-》生产退料—》杂收-》调入-》收货-》形态转换

                                //20230-8-25新逻辑
                                //开发逻辑:遍历所有的单子,按照单子和时间排序创建一个Map,Map存储批号,批号对应的库存,然后遍历Map,
                                //1. 先判断map的所有库存加起来是否满足需求量,满足继续遍历,不满足则直接提示错误
                                //2. 如果单个批号满足库存需求,则直接赋值批号,结束逻辑
                                //3. 如果单个不满足,则一直遍历,遍历一次加一行
                                //result的值为目前料品匹配单子里的所有有库存的批号,和批号对应的库存量
                                Dictionary<string, decimal> result = new Dictionary<string, decimal>();
                                result = getAllLotInfo(item, mOPick.SupplyWh, "采购");
                                chaihang(holder, mOPick, result, b);
                            }
                            else if (ItemTypeAttributeEnum.MakePart == item.ItemFormAttribute)
                            {
                                //按照顺序,期初库存-》生产退料—》杂收-》调入-》入库-》形态转换

                                //20230-8-25新逻辑
                                //开发逻辑:遍历所有的单子,按照单子和时间排序创建一个Map,Map存储批号,批号对应的库存,然后遍历Map,
                                //1. 先判断map的所有库存加起来是否满足需求量,满足继续遍历,不满足则直接提示错误
                                //2. 如果单个批号满足库存需求,则直接赋值批号,结束逻辑
                                //3. 如果单个不满足,则一直遍历,遍历一次加一行
                                Dictionary<string, decimal> result = new Dictionary<string, decimal>();
                                result = getAllLotInfo(item, mOPick.SupplyWh, "制造");
                                chaihang(holder, mOPick, result, b);
                            }
                        }
                    }
                }
            }

        }

        private void chaihang(IssueDoc holder, MOPickList mOPick, Dictionary<string, decimal> result, int b)
        {
            #region 执行上面第一步
            decimal xiancunliang = 0;
            foreach (var ele in result)
            {
                xiancunliang += ele.Value;
            }
            //查找当前Bom子项对应的备料信息的实际需求数量
            if (xiancunliang < mOPick.ActualReqQty)
            {
                //总现存量小于需求量
                logger.Error("现存量为" + xiancunliang + ", 不满足需求量:" + mOPick.ActualReqQty);
                return;
            }
            #endregion 第一步执行结束
            #region 执行第二,三步
            //拣货规则:map为按照时间顺序获取的批号和现存量,从中取得满足需求量的批号。有两种情况
            // 1. 先进先出,从头开发遍历,如果一开始现存量就能直接满足需求量,则不需要拆行,直接赋值批号即可
            // 2. 若前面现存量未能满足需求量,则需要拆行,将当前的批号和库存量赋值给当前的领料行,后面继续遍历批号,加行赋值批号
            //算法需求:按照顺序找到能满足需求量的批号信息
            //最终批号结果
            Dictionary<string, decimal> endResult = new Dictionary<string, decimal>();
            //创建一个值,用于记录已经放入endResult里的批号的库存量总和
            decimal allowXianCun = 0;
            for (int i = 0; i < result.Count; i++)
            {
                if (i == 0 && result.ElementAt(0).Value >= mOPick.ActualReqQty)
                {
                    //如果第一个批号库存量大于需求量,代表已经满足算法条件,跳出
                    endResult.Add(result.ElementAt(0).Key, result.ElementAt(0).Value);
                    break;
                }
                else
                {
                    if (allowXianCun >= mOPick.ActualReqQty)
                    {
                        break;
                    }
                    endResult.Add(result.ElementAt(i).Key, result.ElementAt(i).Value);
                    allowXianCun += result.ElementAt(i).Value;
                }
            }
            if (endResult.Count == 1)
            {
                //查找批号
                LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(0).Key + "'");
                holder.IssueDocLines[b].LotMaster = lotMaster;
            }
            else
            {
                //首先确定当前领料行号最大为多少 按照排序最后一样行号最大
                int lineNum = holder.IssueDocLines[holder.IssueDocLines.Count - 1].LineNum + 10;
                //赋值此次需求量,每次生成领料单行扣减,便于最后一个领料行只占用剩余需求量的库存,而不是赋值整个批号的库存
                decimal xuqiuliang = mOPick.ActualReqQty;
                for (int a = 0; a < result.Count; a++)
                {
                    //第一行赋值原来行的批号
                    if (a == 0)
                    {
                        //查找批号
                        LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(0).Key + "'");
                        //第一行
                        holder.IssueDocLines[b].LotMaster = lotMaster;
                        holder.IssueDocLines[b].IssueQty = endResult[endResult.ElementAt(0).Key];
                        holder.IssueDocLines[b].IssuedQty = endResult[endResult.ElementAt(0).Key];
                        holder.IssueDocLines[b].IssuedQtyByWhUOM = holder.IssueDocLines[b].WhUOM.Round.GetRoundValue(holder.IssueDocLines[b].IssuedQty * holder.IssueDocLines[b].IBUToSBURate);
                        holder.IssueDocLines[b].IssuedQtyByCoUOM = holder.IssueDocLines[b].CoUOM.Round.GetRoundValue(holder.IssueDocLines[b].IssuedQtyByWhUOM * holder.IssueDocLines[b].IBUToCBURate);
                        xuqiuliang -= holder.IssueDocLines[b].IssueQty;
                        continue;
                    }
                    //新增领料行
                    IssueDocLine newIssueLine = IssueDocLine.Create(holder);
                    Base.BusinessEntityHelper.CopyTo(holder.IssueDocLines[b], newIssueLine, false);
                    //举例:假如批号三个一共 80 100 100 需求量为200,第一次遍历赋值数量为批号数量80,xuqiuliang = 200-80 = 120
                    //第二次遍历判断xuqiuliang-批号库存量 = 120 -100 = 20,第二次遍历赋值数量为批号数量100
                    //第二次遍历判断xuqiuliang-批号库存量 = 20 -100 = -80,第二次遍历赋值数量为剩余需求数量20
                    if (xuqiuliang - endResult[endResult.ElementAt(a).Key] > 0)
                    {
                        newIssueLine.IssueQty = endResult[endResult.ElementAt(a).Key];
                        newIssueLine.IssuedQty = endResult[endResult.ElementAt(a).Key];
                    }
                    else
                    {
                        newIssueLine.IssueQty = xuqiuliang;
                        newIssueLine.IssuedQty = xuqiuliang;
                    }
                    newIssueLine.IssuedQtyByWhUOM = newIssueLine.WhUOM.Round.GetRoundValue(newIssueLine.IssuedQty * newIssueLine.IBUToSBURate);
                    newIssueLine.IssuedQtyByCoUOM = newIssueLine.CoUOM.Round.GetRoundValue(newIssueLine.IssuedQtyByWhUOM * newIssueLine.IBUToCBURate);
                    newIssueLine.LineNum = lineNum;
                    //查找批号
                    LotMaster lotMaster2 = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(a).Key + "'");
                    newIssueLine.LotMaster = lotMaster2;

                    Session.Current.InList(newIssueLine);

                    lineNum += 10;
                }
            }

            #endregion 第二步执行结束
        }

        private decimal getKeYong(string itemCode, string wareCode, string lotCode)
        {

            var dataSet = new DataSet();
            string org = UFIDA.U9.Base.Context.LoginOrg.Code;
            string sql = "select " +
                " A5.[Name] as [Wh_Name], " +
                " A.[ItemInfo_ItemCode] as [Item_ItemCode], " +
                " A.[LotInfo_LotCode] as [TempLotCode],  " +
                " sum( case  when ((((A.[IsProdCancel] = 1) or (A.[MO_EntityID] != 0)) or A.[ProductDate] is not null) or (A.[WP_EntityID] != 0)) " +
                " then A.[StoreQty] else convert(decimal(24,9),0) end ) as [NotUseQty]," +
                " sum((((A.[StoreQty] - A.[ResvStQty]) - A.[ResvOccupyStQty]) - " +
                " case  when ((((A.[IsProdCancel] = 1) or (A.[MO_EntityID] != 0)) or A.[ProductDate] is not null) or (A.[WP_EntityID] != 0)) " +
                " then A.[StoreQty] else convert(decimal(24,9),0) end )) as [CanUseQty], " +
                " sum((A.[StoreQty] + A.[ToRetStQty])) as [BalQty]," +
                " sum((A.[StoreMainQty] + A.[ToRetStMainQty])) as [BalQty_Main], " +
                " sum((((((A.[StoreQty] - A.[ResvStQty]) - A.[ResvOccupyStQty]) - " +
                " case  when ((((A.[IsProdCancel] = 1) or (A.[MO_EntityID] != 0)) or A.[ProductDate] is not null) or (A.[WP_EntityID] != 0))" +
                " then A.[StoreQty] else convert(decimal(24,9),0) end ) + A.[SupplyQtySU]) - A.[DemandQtySU])) as [Temp_PAB] " +
                " from  InvTrans_WhQoh as A " +
                " left join [CBO_Wh] as A1 on (A.[Wh] = A1.[ID])" +
                " left join [CBO_ItemMaster] as A2 on (A.[ItemInfo_ItemID] = A2.[ID])  " +
                " left join [Base_UOM] as A3 on (A.[StoreUOM] = A3.[ID]) " +
                " left join [Base_Organization] as A4 on (A.[LogisticOrg] = A4.[ID]) " +
                " left join [CBO_Wh_Trl] as A5 on (A5.SysMlFlag = 'zh-CN') and (A1.[ID] = A5.[ID])" +
                " where  ((((A2.[Name] is not null and (A2.[Name] != '')) and (A4.[Code] = N'"+ org + "'))" +
                " and (A1.[Code] = N'" + wareCode + "')) and (A.[ItemInfo_ItemCode] = N'" + itemCode + "')) and A.[LotInfo_LotCode]  = '" + lotCode + "'" +
                "group by A5.[Name], A.[ItemInfo_ItemCode], isnull( case  when A2.[ItemFormAttribute] in (16, 22) " +
                " then A.[ItemInfo_ItemName] else A2.[Name] end ,''), A.[LotInfo_LotCode], A3.[Round_Precision]";
            DataAccessor.RunSQL(DatabaseManager.GetCurrentConnection(), sql.ToString(), null, out dataSet);

            decimal kyl = decimal.Parse(dataSet.Tables[0].Rows[0][7] == null? "0":dataSet.Tables[0].Rows[0][7].ToString());
            return kyl;

        }
        public Dictionary<string, decimal> getAllLotInfo(ItemMaster item, Warehouse supplyWareHouse,String type) {
            Dictionary<string, decimal> endDic = new Dictionary<string, decimal>() ;
            Dictionary<string, decimal> dic1 = getWhInitLineDic(item, supplyWareHouse);
            Dictionary<string, decimal> dic2 = getSCTLRotLotDic(item, supplyWareHouse);
            Dictionary<string, decimal> dic3 = getMiscRcvTransRotDic(item, supplyWareHouse);
            Dictionary<string, decimal> dic4 = getTransferInRotDic(item, supplyWareHouse);
            Dictionary<string, decimal> dic5 = new Dictionary<string, decimal>() ;
            if ("采购".Equals(type))
            {
               dic5 = getRcvRotDic(item, supplyWareHouse);
            }
            else {
                dic5 = getRcvRptRotLotDic(item, supplyWareHouse);
            }
            Dictionary<string, decimal> dic6 = getTransferFormDic(item, supplyWareHouse);
            if (dic1.Count > 0) {
                foreach (var ele in dic1) //拿到dict1
                {
                    if (ele.Value > 0) { 
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            if (dic2.Count > 0) {

                foreach (var ele in dic2) //拿到dict2
                {
                    if (ele.Value > 0)
                    {
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            if (dic3.Count > 0) {

                foreach (var ele in dic3) //拿到dict2
                {
                    if (ele.Value > 0)
                    {
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            if (dic4.Count > 0) {

                foreach (var ele in dic4) //拿到dict2
                {
                    if (ele.Value > 0)
                    {
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            if (dic5.Count > 0) {

                foreach (var ele in dic5) //拿到dict2
                {
                    if (ele.Value > 0)
                    {
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            if (dic6.Count > 0) {

                foreach (var ele in dic6) //拿到dict2
                {
                    if (ele.Value > 0)
                    {
                        endDic.Add(ele.Key, ele.Value);
                    }
                }
            }
            return endDic;
        }
        //根据库存期初单获取批号
        public Dictionary<string, decimal> getWhInitLineDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();
            WhInitLine.EntityList rcvLines = WhInitLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID +" and Wh = '" + warehouse.ID+"'");
            if (rcvLines == null || rcvLines.Count == 0) {
                logger.Error("库存期初单信息获取为空,料号:" + item.Code);
                return result;
            }
            //进入方法默认查到了列表
            List<WhInitLine> rcList = new List<WhInitLine>();
            foreach (WhInitLine rt in rcvLines)
            {
                WhInit re = rt.WhInit;
                if (INVDocStatus.Approved != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("库存期初单信息获取为空,或者未审核,料号:" + item.Code);
                return result;
            }
            //先进先出,所以顺序排序
            List<WhInitLine> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            foreach (WhInitLine whInitLine in endRcv) {
                if (whInitLine.LotMaster == null)
                {
                    logger.Error("库存期初单批号信息获取为空,料号:" + item.Code);
                    continue;
                }
                //1. 查询批号的现存量
                decimal xiancunliang =  getKeYong(item.Code, warehouse.Code, whInitLine.LotMaster.LotMaster.LotCode);
                //把有现存量的批号先都遍历出来
                if (xiancunliang > 0) { 
                    result.Add(whInitLine.LotMaster.LotMaster.LotCode, xiancunliang);
                }
            }
            return result;
        }
        //根据形态转换单获取批号
        public Dictionary<string, decimal> getTransferFormDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();
            TransferFormL.EntityList rcvLines = TransferFormL.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (rcvLines == null || rcvLines.Count == 0)
            {
                logger.Error("形态转换单信息获取为空,料号:" + item.Code);
                return result;
            }
            //进入方法默认查到了列表
            List<TransferFormL> rcList = new List<TransferFormL>();
            foreach (TransferFormL rt in rcvLines)
            {
                TransferForm re = rt.TransferForm;
                if (INVDocStatus.Approved != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("形态转换单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //先进先出,所以顺序排序,取第一个
            List<TransferFormL> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            foreach (TransferFormL whInitLine in endRcv)
            {
                if (whInitLine.LotInfo == null)
                {
                    logger.Error("形态转换单批号信息获取为空,料号:" + item.Code);
                    continue;
                }
                //1. 查询批号的现存量
                decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.LotInfo.LotMaster.LotCode);
                //把有现存量的批号先都遍历出来
                if (xiancunliang > 0)
                {
                    result.Add(whInitLine.LotInfo.LotMaster.LotCode, xiancunliang);
                }
            }
            return result;
        }
        //根据收货单获取批号
        public Dictionary<string, decimal> getRcvRotDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();
            Organization organization = UFIDA.U9.Base.Context.LoginOrg;
            RcvLine.EntityList rcvLines = RcvLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (rcvLines == null || rcvLines.Count == 0)
            {
                logger.Error("收货单信息获取为空,料号:" + item.Code);
                return result;
            }
            //进入方法默认查到了列表
            List<RcvLine> rcList = new List<RcvLine>();
            foreach (RcvLine rt in rcvLines)
            {
                Receivement re = rt.Receivement;
                if (RcvStatusEnum.Closed != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("收货单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //先进先出,所以顺序排序,取第一个
            List<RcvLine> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            foreach (RcvLine whInitLine in endRcv)
            {
                if (whInitLine.InvLot == null)
                {
                    logger.Error("收货单信息批号信息获取为空,料号:" + item.Code);
                    continue;
                }
                //1. 查询批号的现存量
                decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.InvLot.Code);
                //把有现存量的批号先都遍历出来
                if (xiancunliang > 0)
                {
                    result.Add(whInitLine.InvLot.Code, xiancunliang);
                }
            }
            return result;
        }
        //根据调入单获取批号
        public Dictionary<string, decimal> getTransferInRotDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();

            TransInLine.EntityList transferInList = TransInLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and TransInWh = '" + warehouse.ID + "'");
            if (transferInList == null || transferInList.Count == 0)
            {
                logger.Error("调入单单信息获取为空,料号:" + item.Code);
                return result;
            }
            else
            {
                List<TransInLine> transInLines = new List<TransInLine>();
                foreach (TransInLine rt in transferInList)
                {
                    TransferIn ti = rt.TransferIn;
                    if (TransInStatus.Approved != ti.Status)
                    {
                        continue;
                    }
                    transInLines.Add(rt);
                }
                if (transInLines.Count == 0)
                {
                    logger.Error("调入单单信息获取为空,或者未审核,料号:" + item.Code);
                    return null;
                }
                //先进先出,所以顺序排序,取第一个
                List<TransInLine> endRcv = transInLines.OrderBy(r => r.CreatedOn).ToList();
                foreach (TransInLine whInitLine in endRcv)
                {
                    if (whInitLine.LotInfo == null)
                    {
                        logger.Error("调入单单批号信息获取为空,料号:" + item.Code);
                        continue;
                    }
                    //1. 查询批号的现存量
                    decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.LotInfo.LotMaster.LotCode);
                    //把有现存量的批号先都遍历出来
                    if (xiancunliang > 0)
                    {
                        result.Add(whInitLine.LotInfo.LotMaster.LotCode, xiancunliang);
                    }
                }
                return result;
            }

        }

        //根据杂收单获取批号
        public Dictionary<string, decimal> getMiscRcvTransRotDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();

            MiscRcvTransL.EntityList mis = MiscRcvTransL.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (mis == null || mis.Count == 0)
            {
                logger.Error("杂收单单信息获取为空,料号:" + item.Code);
                return result;
            }
            else
            {
                List<MiscRcvTransL> miss = new List<MiscRcvTransL>();
                foreach (MiscRcvTransL rt in mis)
                {
                    MiscRcvTrans ti = rt.MiscRcvTrans;
                    if (INVDocStatus.Approved != ti.Status)
                    {
                        continue;
                    }
                    miss.Add(rt);
                }
                if (miss.Count == 0)
                {
                    logger.Error("杂收单信息获取为空,或者未审核,料号:" + item.Code);
                    return null;
                }
                //先进先出,所以顺序排序,取第一个
                List<MiscRcvTransL> endRcv = miss.OrderBy(r => r.CreatedOn).ToList();
                foreach (MiscRcvTransL whInitLine in endRcv)
                {
                    if (whInitLine.LotInfo == null)
                    {
                        logger.Error("杂收单批号信息获取为空,料号:" + item.Code);
                        continue;
                    }
                    //1. 查询批号的现存量
                    decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.LotInfo.LotMaster.LotCode);
                    //把有现存量的批号先都遍历出来
                    if (xiancunliang > 0)
                    {
                        result.Add(whInitLine.LotInfo.LotMaster.LotCode, xiancunliang);
                    }
                }
                return result;
            }
        }
        //根据生产退料单获取批号
        public Dictionary<string, decimal> getSCTLRotLotDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();

            //判断退料方式,退料理由不为空
            IssueDocLine.EntityList mis = IssueDocLine.Finder.FindAll(" RecedeReason is not null and RecedeReason != '' and  Item.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (mis == null || mis.Count == 0)
            {
                logger.Error("生产退料单信息获取为空,料号:" + item.Code);
                return result;
            }
            else
            {
                List<IssueDocLine> miss = new List<IssueDocLine>();
                foreach (IssueDocLine rt in mis)
                {
                    IssueDoc ti = rt.IssueDoc;
                    if (IssueTXNStateEnum.Closed != ti.DocState)
                    {
                        continue;
                    }
                    miss.Add(rt);
                }
                if (miss.Count == 0)
                {
                    logger.Error("生产退料单信息获取为空,或者未审核,料号:" + item.Code);
                    return result;
                }
                //先进先出,所以顺序排序,取第一个
                List<IssueDocLine> endRcv = miss.OrderBy(r => r.CreatedOn).ToList();
                foreach (IssueDocLine whInitLine in endRcv)
                {
                    if (whInitLine.LotMaster == null)
                    {
                        logger.Error("生产退料单批号信息获取为空,料号:" + item.Code);
                        continue;
                    }
                    //1. 查询批号的现存量
                    decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.LotMaster.LotCode);
                    //把有现存量的批号先都遍历出来
                    if (xiancunliang > 0)
                    {
                        result.Add(whInitLine.LotMaster.LotCode, xiancunliang);
                    }
                }
                return result;
            }
        }
        //根据成品入库获取批号
        public Dictionary<string, decimal> getRcvRptRotLotDic(ItemMaster item, Warehouse warehouse)
        {
            Dictionary<string, decimal> result = new Dictionary<string, decimal>();

            //制造件 - 批号取对应的生产订单的生产批号,此处取得是入库单的生产批号,因为和生产订单批号一一对应
            //1.查找对应的入库单
            RcvRptDocLine.EntityList rcvRptDocLine = RcvRptDocLine.Finder.FindAll(" Item.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (rcvRptDocLine.Count == 0)
            {
                logger.Error("入库单信息获取为空,料号:" + item.Code);
                return result;
            }
            List<RcvRptDocLine> rcList = new List<RcvRptDocLine>();
            foreach (RcvRptDocLine rt in rcvRptDocLine)
            {
                RcvRptDoc re = rt.RcvRptDoc;
                if (RcvRptDocStateEnum.Approved != re.DocState)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("入库单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //2.先进先出,所以顺序排序,取第一个
            List<RcvRptDocLine> endRcvList = rcList.OrderBy(r => r.CreatedOn).ToList();
            foreach (RcvRptDocLine whInitLine in endRcvList)
            {
                if (whInitLine.RcvLotMaster == null)
                {
                    logger.Error("入库单批号信息获取为空,料号:" + item.Code);
                    continue;
                }
                //1. 查询批号的现存量
                decimal xiancunliang = getKeYong(item.Code, warehouse.Code, whInitLine.RcvLotMaster.LotCode);
                //把有现存量的批号先都遍历出来
                if (xiancunliang > 0)
                {
                    result.Add(whInitLine.RcvLotMaster.LotCode, xiancunliang);
                }
            }
            return result;
        }











        //根据库存期初单获取批号
        public LotMaster getWhInitLineLotByItemInfo(ItemMaster item, Warehouse warehouse)
        {
            WhInitLine.EntityList rcvLines = WhInitLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (rcvLines == null || rcvLines.Count == 0)
            {
                logger.Error("库存期初单信息获取为空,料号:" + item.Code);
                return null;
            }
            //进入方法默认查到了列表
            List<WhInitLine> rcList = new List<WhInitLine>();
            foreach (WhInitLine rt in rcvLines)
            {
                WhInit re = rt.WhInit;
                if (INVDocStatus.Approved != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("库存期初单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //先进先出,所以顺序排序
            List<WhInitLine> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            return endRcv[0].LotMaster.LotMaster;
        }
        //根据形态转换单获取批号
        public LotMaster getTransferFormLLotByItemInfo(ItemMaster item, Warehouse warehouse)
        {
            TransferFormL.EntityList rcvLines = TransferFormL.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID +" and Wh = '" + warehouse.ID+"'");
            if (rcvLines == null || rcvLines.Count == 0) {
                logger.Error("形态转换单信息获取为空,料号:" + item.Code);
                return null;
            }
            //进入方法默认查到了列表
            List<TransferFormL> rcList = new List<TransferFormL>();
            foreach (TransferFormL rt in rcvLines)
            {
                TransferForm re = rt.TransferForm;
                if (INVDocStatus.Approved != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("形态转换单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //先进先出,所以顺序排序,取第一个
            List<TransferFormL> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            return endRcv[0].LotInfo.LotMaster;
        }
        //根据收货单获取批号
        public LotMaster getRcvRotLotByItemInfo(ItemMaster item, Warehouse warehouse)
        {
            Organization organization =  UFIDA.U9.Base.Context.LoginOrg;
            RcvLine.EntityList rcvLines = RcvLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID +" and Wh = '" + warehouse.ID+"'");
            if (rcvLines == null || rcvLines.Count == 0) {
                logger.Error("收货单信息获取为空,料号:" + item.Code);
                return null;
            }
            //进入方法默认查到了列表
            List<RcvLine> rcList = new List<RcvLine>();
            foreach (RcvLine rt in rcvLines)
            {
                Receivement re = rt.Receivement;
                if (RcvStatusEnum.Closed != re.Status)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("收货单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //先进先出,所以顺序排序,取第一个
            List<RcvLine> endRcv = rcList.OrderBy(r => r.CreatedOn).ToList();
            return endRcv[0].InvLot;
        }
        //根据调入单获取批号
        public LotMaster getTransferInRotLotByItemInfo(ItemMaster item, Warehouse warehouse)
        {
            TransInLine.EntityList transferInList = TransInLine.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID+ " and TransInWh = '" + warehouse .ID+ "'");
            if (transferInList == null || transferInList.Count == 0)
            {
                logger.Error("调入单单信息获取为空,料号:" + item.Code);
                return null;
            }
            else {
                List<TransInLine> transInLines = new List<TransInLine>();
                foreach (TransInLine rt in transferInList)
                {
                    TransferIn ti = rt.TransferIn;
                    if (TransInStatus.Approved != ti.Status)
                    {
                        continue;
                    }
                    transInLines.Add(rt);
                }
                if (transInLines.Count == 0)
                {
                    logger.Error("调入单单信息获取为空,或者未审核,料号:" + item.Code);
                    return null;
                }
                //先进先出,所以顺序排序,取第一个
                List<TransInLine> endRcv = transInLines.OrderBy(r => r.CreatedOn).ToList();
                if (endRcv[0].LotInfo == null) {
                    logger.Error("调入单单信息获取为空,料号:" + item.Code);
                    return null;
                }
                return endRcv[0].LotInfo.LotMaster;
            }

        }

        //根据杂收单获取批号
        public LotMaster getMiscRcvTransRotLotByItemInfo(ItemMaster item, Warehouse warehouse)
        {
            MiscRcvTransL.EntityList mis = MiscRcvTransL.Finder.FindAll(" ItemInfo.ItemID.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (mis == null || mis.Count == 0)
            {
                logger.Error("杂收单单信息获取为空,料号:" + item.Code);
                return null;
            }
            else
            {
                List<MiscRcvTransL> miss = new List<MiscRcvTransL>();
                foreach (MiscRcvTransL rt in mis)
                {
                    MiscRcvTrans ti = rt.MiscRcvTrans;
                    if (INVDocStatus.Approved != ti.Status)
                    {
                        continue;
                    }
                    miss.Add(rt);
                }
                if (miss.Count == 0)
                {
                    logger.Error("杂收单信息获取为空,或者未审核,料号:" + item.Code);
                    return null;
                }
                //先进先出,所以顺序排序,取第一个
                List<MiscRcvTransL> endRcv = miss.OrderBy(r => r.CreatedOn).ToList();
                if (endRcv[0].LotInfo == null)
                {
                    logger.Error("杂收单信息获取批号为空,料号:" + item.Code);
                    return null;
                }
                return endRcv[0].LotInfo.LotMaster;
            }
        }
        //根据生产退料单获取批号
        public LotMaster getSCTLRotLotByItemInfo(ItemMaster item,Warehouse warehouse)
        {
            //判断退料方式,退料理由不为空
            IssueDocLine.EntityList mis = IssueDocLine.Finder.FindAll(" RecedeReason is not null and RecedeReason != '' and  Item.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (mis == null || mis.Count == 0)
            {
                logger.Error("生产退料单信息获取为空,料号:" + item.Code);
                return null;
            }
            else
            {
                List<IssueDocLine> miss = new List<IssueDocLine>();
                foreach (IssueDocLine rt in mis)
                {
                    IssueDoc ti = rt.IssueDoc;
                    if (IssueTXNStateEnum.Closed != ti.DocState)
                    {
                        continue;
                    }
                    miss.Add(rt);
                }
                if (miss.Count == 0)
                {
                    logger.Error("生产退料单信息获取为空,或者未审核,料号:" + item.Code);
                    return null;
                }
                //先进先出,所以顺序排序,取第一个
                List<IssueDocLine> endRcv = miss.OrderBy(r => r.CreatedOn).ToList();
                return endRcv[0].LotMaster;
            }
        }
        //根据成品入库获取批号
        public LotMaster getRcvRptRotLotByItemInfo(ItemMaster item, Warehouse warehouse) 
        {
            //制造件 - 批号取对应的生产订单的生产批号,此处取得是入库单的生产批号,因为和生产订单批号一一对应
            //1.查找对应的入库单
            RcvRptDocLine.EntityList rcvRptDocLine = RcvRptDocLine.Finder.FindAll(" Item.ID = " + item.ID + " and Wh = '" + warehouse.ID + "'");
            if (rcvRptDocLine.Count == 0)
            {
                logger.Error("入库单信息获取为空,料号:" + item.Code);
                return null;
            }
            List<RcvRptDocLine> rcList = new List<RcvRptDocLine>();
            foreach (RcvRptDocLine rt in rcvRptDocLine)
            {
                RcvRptDoc re = rt.RcvRptDoc;
                if (RcvRptDocStateEnum.Approved != re.DocState)
                {
                    continue;
                }
                rcList.Add(rt);
            }
            if (rcList.Count == 0)
            {
                logger.Error("入库单信息获取为空,或者未审核,料号:" + item.Code);
                return null;
            }
            //2.先进先出,所以顺序排序,取第一个
            List<RcvRptDocLine> endRcvList = rcList.OrderBy(r => r.CreatedOn).ToList();
             return endRcvList[0].RcvLotMaster;
        }
    }
}

#endregion

代码核心逻辑

  1. 每一个getXXXDIc都是通过料品,存储地点按照先进先出顺序找到对应单子的行记录,并取到还有库存的批号!每一个方法都返回一个Map,存着批号和库存数量的对应关系
  2. getAllLotInfo对所有单子里查出来的Map进行组装,最后返回的就是这个料品对应的按照先进先出顺序排列的批号,库存对应
  3. 拆行逻辑单据摘出来

拆行算法逻辑

private void chaihang(IssueDoc holder, MOPickList mOPick, Dictionary<string, decimal> result, int b)
{
	//定义一个现存量值,遍历所有的Map,赋值当前料品对应的所有批号的库存量和
    decimal xiancunliang = 0;
    foreach (var ele in result)
    {
        xiancunliang += ele.Value;
    }
    //如果总库存数小于需求数,直接报错
    if (xiancunliang < mOPick.ActualReqQty)
    {
        //总现存量小于需求量
        logger.Error("现存量为" + xiancunliang + ", 不满足需求量:" + mOPick.ActualReqQty);
        return;
    }
    //拣货规则:
    //map为按照时间顺序获取的批号和现存量,从中取得满足需求量的批号。有两种情况
    // 1. 先进先出,从头开发遍历,如果一开始现存量就能直接满足需求量,则不需要拆行,直接赋值批号即可
    // 2. 若前面现存量未能满足需求量,则需要拆行,将当前的批号和库存量赋值给当前的领料行,后面继续遍历批号,加行赋值批号
    //算法需求:按照顺序找到能满足需求量的批号信息
    //因为料品返回的批号库存可能多于需求量,在这重新定义一个Map对象,用于保存满足此次需求量的所用的批号
    Dictionary<string, decimal> endResult = new Dictionary<string, decimal>();
    //创建一个值,用于记录已经放入endResult里的批号的库存量总和,通过这个总和可以判断,如果遍历过程中这个和大于等于需求量了,就可以退出赋值了
    decimal allowXianCun = 0;
    for (int i = 0; i < result.Count; i++)
    {
        if (i == 0 && result.ElementAt(0).Value >= mOPick.ActualReqQty)
        {
            //如果第一个批号库存量大于需求量,代表已经满足算法条件,跳出
            endResult.Add(result.ElementAt(0).Key, result.ElementAt(0).Value);
            break;
        }
        else
        {
            if (allowXianCun >= mOPick.ActualReqQty)
            {
                break;
            }
            endResult.Add(result.ElementAt(i).Key, result.ElementAt(i).Value);
            allowXianCun += result.ElementAt(i).Value;
        }
    }
    //如果只有一个批号,则代表第一个批号库存即可满足条件,直接赋值批号信息
    if (endResult.Count == 1)
    {
        //查找批号
        LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(0).Key + "'");
        holder.IssueDocLines[b].LotMaster = lotMaster;
    }
    else
    {
    	//到这,代表需要拆行了!
        //定义一个行号变量,用于拆行后新建行的行号赋值
        //赋值逻辑:确定当前领料行号最大为多少 按照排序最后一样行号最大
        int lineNum = holder.IssueDocLines[holder.IssueDocLines.Count - 1].LineNum + 10;
        //定义一个变量赋值此料品需求量,每次生成领料单行扣减,便于最后一个领料行只占用剩余需求量的库存,而不是赋值整个批号的库存
        //举例:假如批号三个一共 80 100 100 需求量为200,
        //第一次遍历赋值数量为批号数量80,xuqiuliang = 200-80 = 120
        //第二次遍历判断xuqiuliang-批号库存量 = 120 -100 = 20,第二次遍历赋值数量为批号数量100
        //第二次遍历判断xuqiuliang-批号库存量 = 20 -100 = -80,第二次遍历赋值数量为剩余需求数量20
        decimal xuqiuliang = mOPick.ActualReqQty;
        for (int a = 0; a < result.Count; a++)
        {
            //第一行赋值第一行的批号
            if (a == 0)
            {
                //查找批号
                LotMaster lotMaster = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(0).Key + "'");
                //第一行
                holder.IssueDocLines[b].LotMaster = lotMaster;
                //修改原有数量为批号数量
                holder.IssueDocLines[b].IssueQty = endResult[endResult.ElementAt(0).Key];
                //修改原有数量为批号数量
                holder.IssueDocLines[b].IssuedQty = endResult[endResult.ElementAt(0).Key];
                holder.IssueDocLines[b].IssuedQtyByWhUOM = holder.IssueDocLines[b].WhUOM.Round.GetRoundValue(holder.IssueDocLines[b].IssuedQty * holder.IssueDocLines[b].IBUToSBURate);
                holder.IssueDocLines[b].IssuedQtyByCoUOM = holder.IssueDocLines[b].CoUOM.Round.GetRoundValue(holder.IssueDocLines[b].IssuedQtyByWhUOM * holder.IssueDocLines[b].IBUToCBURate);
                //第一次遍历赋值数量为批号数量
                xuqiuliang -= endResult.ElementAt(0).Value;
                continue;
            }
            //新增领料行
            IssueDocLine newIssueLine = IssueDocLine.Create(holder);
            Base.BusinessEntityHelper.CopyTo(holder.IssueDocLines[b], newIssueLine, false);
            if (xuqiuliang - endResult[endResult.ElementAt(a).Key] > 0)
            {
                //当剩余的需求量减去当前批号的库存量大于0,代表当前批号库存扔不满足需求量,此时拆的行赋值数量为批号库存量
                newIssueLine.IssueQty = endResult[endResult.ElementAt(a).Key];
                newIssueLine.IssuedQty = endResult[endResult.ElementAt(a).Key];
            }
            else
            {
                //当剩余的需求量减去当前批号的库存量小于0,代表当前批号库存满足需求量,即拆到了当前物料的最后一行,此时拆的行赋值数量为剩余需求量库存量
                newIssueLine.IssueQty = xuqiuliang;
                newIssueLine.IssuedQty = xuqiuliang;
            }
            newIssueLine.IssuedQtyByWhUOM = newIssueLine.WhUOM.Round.GetRoundValue(newIssueLine.IssuedQty * newIssueLine.IBUToSBURate);
            newIssueLine.IssuedQtyByCoUOM = newIssueLine.CoUOM.Round.GetRoundValue(newIssueLine.IssuedQtyByWhUOM * newIssueLine.IBUToCBURate);
            newIssueLine.LineNum = lineNum;
            //查找批号
            LotMaster lotMaster2 = LotMaster.Finder.Find(" LotCode = '" + endResult.ElementAt(a).Key + "'");
            newIssueLine.LotMaster = lotMaster2;
            xuqiuliang -= endResult.ElementAt(a).Value;

            Session.Current.InList(newIssueLine);

            lineNum += 10;
        }
    }

    #endregion 第二步执行结束
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值