手动配货
不知道配货流程的朋友可以看一下前面的文章链接: 深入浅出WMS之出库流程里面有对出库的解释说明,其中也有对配货的解释。前端页面也可以在前面的那篇文章中看到,这里我们来说一下后端部分。
查
手动配货是选中出库单的某条数据,然后点击手动配货,这个时候前端会把这个出库单的guid传给后端,这个时候我们有guid了,就可以去出库单表里去找到这条数据,取出其中的订单号、批次号等等信息。这个时候我们要做的就很简单,去库存里找符合我们可以出库的信息。
public async Task<PageData> QueryPeiH(int pages, int limit, string matno, string batch, string cflag){PageData pageData = new PageData();var list = _fsql.Select<LogMstore, LOGMATERIAL,LOGSTORAGE>().LeftJoin((a, b, c) => a.MATNO == b.MATNO).LeftJoin((a, b, c) => a.ADDRE == c.ADDRE);if (!string.IsNullOrWhiteSpace(matno)){list = list.Where((a,b,c) => a.MATNO == matno);}if (!string.IsNullOrWhiteSpace(batch)){list = list.Where((a, b, c) => a.BATCH == batch);}if (!string.IsNullOrWhiteSpace(cflag)){list = list.Where((a, b, c) => a.CFLAG == cflag);}//在这里添加我们对库存信息的限制条件list = list.Where((a, b, c) => Convert.ToDouble(a.QUANT) - Convert.ToDouble(a.QUANTOUT) > 0);list = list.Where((a, b, c) => c.PkFlag != "PK");//if (limit <= 0){//pageData.pageData = await list.Count(out var total).ToListAsync();//pageData.total = (int)total;pageData.pageData = await list.Count(out var total).ToListAsync((a, b, c) => new {a.MATNO,b.MNAME,b.SZNO,a.BATCH,keyong = Convert.ToDouble(a.QUANT) - Convert.ToDouble(a.QUANTOUT),a.ADDRE,a.PALNO,a.SECTN,a.CFLAG});pageData.total = (int)total;}else{pageData.pageData = await list.Count(out var total).Skip((pages - 1) * limit).Take(limit).ToListAsync((a, b, c) => new {a.MATNO,b.MNAME,b.SZNO,a.BATCH,keyong = Convert.ToDouble(a.QUANT) - Convert.ToDouble(a.QUANTOUT),a.ADDRE,a.PALNO,a.SECTN,a.CFLAG});pageData.total = (int)total;}return pageData;}
当我们查到所有符合条件的库存信息后,把数据返回给前端。这个时候也就是当前端选中出库单点击手动配货按钮时,会弹出一个页面,也就是我们上面执行的查询结果。这个时候又操作人员来进行货位分配。例如我们要出750个A货物,然后有符合出库的8个托盘,每个托盘上有100个A物品,这个时候我们手动配货是可以选择每个托盘出多少的,比如说第一个托盘出90个,第二个托盘出80等。当配完货后会生成相对应的出库任务,那么接下来我们看一下这个业务是怎么用代码来实现的。
配
[HttpPost]public async Task<IActionResult> QueryPeiH(List<LogMstore> logMstores){return Ok(await _erpOutService.PeiHuo2(logMstores));}
这个是我们的配货接口,这个时候前端会发一个list过来,写list的目的是为了把配货的数据一次性发给后端,而不是一直调用接口。
这个时候可以看到我们调用了server层。我们来看一下server层对业务的处理。
public async Task<ResultData> PeiHuo2(List<LogMstore> logMstores){try{//循环listLOGOUTTASK lOGOUTTASK = new LOGOUTTASK();for (int i = 0; i < logMstores.Count; i++){string quant = logMstores[i].QUANTOUT;string palno = logMstores[i].PALNO;string ordno = logMstores[i].DEMO1;string itmno = logMstores[i].DEMO2;string keyong = logMstores[i].DEMO3;//通过订单号和行号查询出库表var ErpOut = await _erpOutRepository.QueryMI(ordno, itmno);//通过托盘号查询库存表var OutMstore = await _mstoreRepository.QueryPalno(palno);#region 赋值给出库任务做添加lOGOUTTASK.ErpoutId = ErpOut.Id.ToString();lOGOUTTASK.TICNO = "";lOGOUTTASK.SEQNO = "";lOGOUTTASK.INTASKID = OutMstore.INTASKID;lOGOUTTASK.ORDNO = ordno;lOGOUTTASK.ITMNO = itmno;lOGOUTTASK.RECTYPE = ErpOut.RECTYPE;lOGOUTTASK.MATNO = ErpOut.MATNO;lOGOUTTASK.MUNIT = ErpOut.MUNIT;lOGOUTTASK.QUANT = quant;lOGOUTTASK.QUANT0 = keyong;lOGOUTTASK.QuantQy = "0";lOGOUTTASK.JIAN = ErpOut.JIAN;lOGOUTTASK.STANO = "0";lOGOUTTASK.ADDRESRC = OutMstore.ADDRE;lOGOUTTASK.ADDREDESC = "-";lOGOUTTASK.PALNO = OutMstore.PALNO;lOGOUTTASK.MstoreId = OutMstore.MstoreId.ToString();lOGOUTTASK.CFLAG = ErpOut.CFLAG;lOGOUTTASK.BATCH = ErpOut.BATCH;lOGOUTTASK.LOTNO = ErpOut.LOTNO;lOGOUTTASK.CUSTOM = ErpOut.CUSTOM;lOGOUTTASK.CustAddress = "-";lOGOUTTASK.WORKS = "-";lOGOUTTASK.STORE = ErpOut.STORE;lOGOUTTASK.VCDSCR = ErpOut.VCDSCR;lOGOUTTASK.PONO = ErpOut.PONO;lOGOUTTASK.POITEM = ErpOut.POITEM;lOGOUTTASK.STYPE = "N";lOGOUTTASK.ATTACHMENT = "N";lOGOUTTASK.SECTN = ErpOut.SECTN;lOGOUTTASK.PRDAT = ErpOut.PRDAT;lOGOUTTASK.QUDAT = ErpOut.QUDAT;lOGOUTTASK.PRICE = "-";lOGOUTTASK.KEEPER = "-";lOGOUTTASK.PRNNO = "-";lOGOUTTASK.TKDAT = DateTime.Now;lOGOUTTASK.COMDAT = DateTime.Now;lOGOUTTASK.IFDO = "O";lOGOUTTASK.OPUSER = ErpOut.OPUSER;lOGOUTTASK.USERID = "";lOGOUTTASK.OutPort = "";lOGOUTTASK.OutportJ = "";lOGOUTTASK.Deviceno = "";lOGOUTTASK.DEMO1 = ordno;lOGOUTTASK.DEMO2 = itmno;lOGOUTTASK.DEMO3= logMstores[i].DEMO3;lOGOUTTASK.Tasktype = "N";lOGOUTTASK.DEMO19 = "自建";lOGOUTTASK.DEMO24 = ErpOut.DEMO24;#endregionif (Convert.ToDouble(OutMstore.QUANT) <Convert.ToDouble(quant)){throw new Exception("库存数量不足!");}if (await _erpOutRepository.PeiHuo(lOGOUTTASK) == 0){resultData.code = 0;resultData.message = "配货成功!";}}}catch (Exception ex){resultData.code = 1;resultData.message = ex.Message;}return resultData;}
在这里面我们又调用了一层,因为这层是处理业务的,会有另外一层专门写增删改查等操作,这样的话也是为了提高代码的利用率,这也符合我们写代码的原则“高内聚,低耦合”。
public async Task<int> PeiHuo(LOGOUTTASK lOGOUTTASK){try{var ErpOut = await _fsql.Select<LogErpOut>().Where(x => x.ORDNO == lOGOUTTASK.ORDNO && x.ITMNO == lOGOUTTASK.ITMNO).FirstAsync();string quant0 = ErpOut.QUANT0;var Pdian = await _fsql.Select<LOGSTORAGE>().Where(x => x.ADDRE == lOGOUTTASK.ADDRESRC).FirstAsync();if (Pdian.PkFlag == "PK") return 6001;quant0 = (Convert.ToDouble(quant0) + Convert.ToDouble(lOGOUTTASK.QUANT)).ToString();_fsql.Transaction(() => {//添加出库任务var inrows = _fsql.Insert(lOGOUTTASK).ExecuteAffrows();if (inrows <= 0) throw new Exception("出库任务添加失败!");//var erpin = _fsql.Select<LogErpOut>().Where(x => x.Id == Convert.ToDecimal(lOGOUTTASK.ErpoutId)).First();//修改出库单已配货数量var uprows = _fsql.Update<LogErpOut>().Set(x =>x.QUANT0 == quant0).Set(x => x.IfDo == "O").Where(x => x.ORDNO == lOGOUTTASK.ORDNO && x.ITMNO == lOGOUTTASK.ITMNO).ExecuteAffrows();if (uprows <= 0) throw new Exception("配货数量回写失败!");//库存表可用数量加上});return 0;}catch (Exception ex){return 1;}}
这里我们需要使用到事务,以防数据出现错误。手动配货的相比较自动配货来说比较简单,了解业务之后就可以一步一步的往下写。而且一般来说不会出现什么错误,只需要多注意数据的增减。例如配货成功后出库单的已配货数量,未配货数量,出库任务的出库数量,库存的剩余数量,可用数量等等。假如对这个配货还不太懂的话,可以看一下前面的文章,里面有出库的整个流程,其中对配货也有一定说明。接下来我们来说一下重点-自动配货。
自动配货
自动配货前端写起来比较简单,因为只需要写一个按钮就可以。自动配货相对手动配货来说第一步的查询是一样的,只不过区别是手动配货需要我们把查出来的数据返回给前端,让操作人员进行分配,然后再把分配完的数据再传给后端。自动配货的话就是我们查出来数据后,我们后端自己处理。
上面的是我初次写的自动配货,如今已弃用,下面的是优化过的。还是那么一个简简单单的接口,只需要前端给我们传一个guid,我们就去server层自己玩。说明我写在下面代码的注释里,方便大家了解流程。
public async Task<ResultData> AutomaticPicking(string guid){ResultData resultData = new ResultData(){code = 0,message = "success",};try{List<LogMstore> logMstores = new List<LogMstore>();var ErpOut = await _erpOutRepository.Queryguid(guid);//根据guid查询出库单string quantout = ErpOut.QUANT;//出库单计划出库数量string matno = ErpOut.MATNO;string batch = ErpOut.BATCH;string cflag = ErpOut.CFLAG;//查询符合条件的库存信息var logmstore = await _mstoreRepository.QueryZiDong(1, 20, matno, batch, cflag);//序列化查询符合条件的库存信息var json = JsonConvert.SerializeObject(logmstore.pageData, Formatting.Indented);var newdynamicData = JsonConvert.DeserializeObject<List<dynamic>>(json);//未配货数量 = 计划数量 - 已配货数量Double UnshippedQuantity = Convert.ToDouble(ErpOut.QUANT) - Convert.ToDouble(ErpOut.QUANT0);//符合条件的数据循环插入到List集合中for (int i = 0; i < newdynamicData.Count; i++){//如果大于0说明是整托出库,例如750个的货,每个托盘100个,那么前7个托盘就是整托出库,最后的一个托盘就是拣选出库,因为我们只需要50,剩下的50还需要再回库。if((UnshippedQuantity - Convert.ToDouble(newdynamicData[i].QUANT)) >= 0){UnshippedQuantity = UnshippedQuantity - Convert.ToDouble(newdynamicData[i].QUANT);logMstores.Add(new LogMstore{ADDRE = newdynamicData[i].ADDRE,//货位地址BATCH = newdynamicData[i].BATCH,//批次号CFLAG = newdynamicData[i].CFLAG,DEMO1 = ErpOut.ORDNO,//订单号DEMO2 = ErpOut.ITMNO,//行号MATNO = newdynamicData[i].MATNO,//物料编码PALNO = newdynamicData[i].PALNO,QUDAT = newdynamicData[i].QUDAT,SECTN = newdynamicData[i].SECTN,//QUANT = newdynamicData[i].QUANT //库存数量QUANTOUT = newdynamicData[i].QUANT});}else{logMstores.Add(new LogMstore{ADDRE = newdynamicData[i].ADDRE,//货位地址BATCH = newdynamicData[i].BATCH,//批次号CFLAG = newdynamicData[i].CFLAG,DEMO1 = ErpOut.ORDNO,//订单号DEMO2 = ErpOut.ITMNO,//行号MATNO = newdynamicData[i].MATNO,//物料编码PALNO = newdynamicData[i].PALNO,QUDAT = newdynamicData[i].QUDAT,SECTN = newdynamicData[i].SECTN,//QUANT = newdynamicData[i].QUANT //库存数量QUANTOUT = UnshippedQuantity.ToString()});break;} }await ZiDong(logMstores);//这一步和手动配货一样}catch (Exception ex){resultData.code = 1;resultData.message = ex.Message;}return resultData;}
自动配货的话一般有先入先出原则,也有随机出库原则等等,这个都是根据甲方需求来写。先入先出原则比较简单,查询的时候按时间进行排序查询结果就可以。这里我们用的也是先入先出原则。自动配货的话一般来说比较常用。