using Admin.NET.Application.CommonHelper; using Admin.NET.Application.Entity; using Admin.NET.Application.TransferDto; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Spreadsheet; using Furion.DatabaseAccessor; using System.Collections.Generic; using System.Data; using System.Linq; namespace Admin.NET.Application; /// /// 容器分拣信息服务 /// [ApiDescriptionSettings(ApplicationConst.WmsTaskGroupName, Order = 100)] public class WmsIssueService : IDynamicApiController, ITransient { /// /// 是否正在执行下发方法 /// private static bool isRuning_Fun_Issue = false; private static SemaphoreSlim semaphoreSlimIssue = new SemaphoreSlim(1, 1); private readonly UserManager _userManager; private readonly SqlSugarRepository _wmsContainerSortRep; private readonly SqlSugarRepository _v_wms_stock_quanRep; private readonly SqlSugarRepository _v_wms_stock_quan_for_useRep; private readonly SqlSugarRepository _wmsOrderSortDetailsRep; private readonly SqlSugarRepository _wmsOrderSortRep; private readonly SqlSugarRepository _wmsOrderMovementDetailsRep; private readonly SqlSugarRepository _wmsOrderMovementRep; private readonly SqlSugarRepository _wmsTaskRep; private readonly SqlSugarRepository _wmsRecordPredetermineDispenseRep; private readonly SqlSugarRepository _wmsStockQuanRep; private readonly SqlSugarRepository _wmsRecordTransRep; private readonly SqlSugarRepository _WmsBaseBusinessTypeRep; private readonly SqlSugarRepository _wmsPlaceRep; private readonly SqlSugarRepository _wmsLogActionRep; private readonly SqlSugarRepository _wmsConfigUnshelveStrategyRep; private readonly SqlSugarRepository _wmsConfigUnshelveStrategyChooseRep; private readonly SqlSugarRepository _wmsConfigUnshelveStrategyRangeRep; private readonly SqlSugarRepository _v_wms_config_unshelve_strategy_rangeRep; private readonly SqlSugarRepository _v_empty_placeRep; private readonly SqlSugarRepository _wmsBasePlaceRep; public WmsIssueService( UserManager userManager, SqlSugarRepository wmsStockQuanRep, SqlSugarRepository wmsRecordPredetermineDispenseRep, SqlSugarRepository wmsContainerSortRep, SqlSugarRepository v_wms_stock_quanRep, SqlSugarRepository v_wms_stock_quan_for_useRep, SqlSugarRepository wmsOrderSortDetailsRep, SqlSugarRepository wmsOrderSortRep, SqlSugarRepository wmsOrderMovementDetailsRep, SqlSugarRepository wmsOrderMovementRep, SqlSugarRepository wmsTaskRep, SqlSugarRepository wmsRecordTransRep, SqlSugarRepository WmsBaseBusinessTypeRep, SqlSugarRepository wmsPlaceRep, SqlSugarRepository wmsLogActionRep, SqlSugarRepository wmsConfigUnshelveStrategyRep, SqlSugarRepository wmsConfigUnshelveStrategyChooseRep, SqlSugarRepository wmsConfigUnshelveStrategyRangeRep, SqlSugarRepository v_empty_placeRep, SqlSugarRepository wmsBasePlaceRep, SqlSugarRepository v_wms_config_unshelve_strategy_rangeRep ) { _userManager = userManager; _wmsStockQuanRep = wmsStockQuanRep; _wmsContainerSortRep = wmsContainerSortRep; _v_wms_stock_quanRep = v_wms_stock_quanRep; _v_wms_stock_quan_for_useRep = v_wms_stock_quan_for_useRep; _wmsOrderSortDetailsRep = wmsOrderSortDetailsRep; _wmsOrderSortRep = wmsOrderSortRep; _wmsOrderMovementDetailsRep = wmsOrderMovementDetailsRep; _wmsOrderMovementRep = wmsOrderMovementRep; _wmsTaskRep = wmsTaskRep; _wmsRecordPredetermineDispenseRep = wmsRecordPredetermineDispenseRep; _wmsRecordTransRep = wmsRecordTransRep; _WmsBaseBusinessTypeRep = WmsBaseBusinessTypeRep; _wmsPlaceRep = wmsPlaceRep; _wmsLogActionRep = wmsLogActionRep; _wmsConfigUnshelveStrategyRep = wmsConfigUnshelveStrategyRep; _wmsConfigUnshelveStrategyChooseRep = wmsConfigUnshelveStrategyChooseRep; _wmsConfigUnshelveStrategyRangeRep = wmsConfigUnshelveStrategyRangeRep; _v_empty_placeRep = v_empty_placeRep; _wmsBasePlaceRep = wmsBasePlaceRep; _v_wms_config_unshelve_strategy_rangeRep = v_wms_config_unshelve_strategy_rangeRep; } /// /// 下发 /// /// /// [HttpPost] [ApiDescriptionSettings(Name = "Issue")] [Description("WmsIssue/Issue")] [UnitOfWork] public async Task Issue(List input) { //if (input.Count > 1) throw Oops.Oh("波次单下发数量超过1"); try { if (isRuning_Fun_Issue) { throw Oops.Oh("程序正忙,请稍后再试"); } await semaphoreSlimIssue.WaitAsync(); isRuning_Fun_Issue = true; return await TaskIssue(input); } catch (Exception ex) { throw Oops.Oh(ex.Message); } finally { semaphoreSlimIssue.Release(); isRuning_Fun_Issue = false; } } #region 私有方法 /// /// 波次单下发 /// /// /// private async Task TaskIssue(List input) { /* * 1. 获取所有要下发的波次单、明细 * 2.校验波次单是否存在、单据状态、波次单明细剩余可下发数(考虑一个波次单明细是否可以多次下发) * 3.获取所有要下发的下架单、明细 * 4.校验下架单是否存在、单据状态 * 5.根据库位、批次、容器查询库存(查询库存视图:根据容器分组) * 7.计算可用库存=实际库存-锁定库存, 校验库存是否足够下发。 * 8.库存根据容器 库位 入库时间排序(使用遵循先进先出原则) * 目前库存没有 排属性的应用 * 9.循环库存,判断每个容器的物料是否足够下发,不够就循环下一个容器。 * 10.校验库存容器是否存在未完成的调度任务,存在就不能下发 * * 11.更新波次单 */ //下架策略 /** *1.获取所有物料 生效的 下架策略 *2. 根据优先级循环处理策略 *3.先进先出(天):先按照入库时间(年-月-日)格式排序 升序 *4.先进先出(小时):先按照入库时间(年-月-日-时)格式排序 升序 *5.满托推荐:按照 容器号分组 5.1 物料在一个容器库存正好= 下发的库存的 先出 5.2 物料在一个容器库存> 下发的库存的先出 5.3 物料在一个容器库存< 下发的库存的先出 *6.半托推荐:按照 容器号分组 ,库存最少的先出 * * * */ //新增事务记录 List addWmsRecordTransList = new List(); //新增操作履历 List addWmsLogActionList = new List(); var addWmsTaskList = new List();//新增的下架任务 var addWmsContainerSortList = new List();//新增的分拣信息 var updateWmsOrderSortDetailsList = new List();//修改的波次单明细 var updateWmsOrderSortList = new List();//修改的波次单 //TODO 添加事务记录 List sortNoList = input.Select(x => x.SortNo).ToList(); if (sortNoList.Distinct().Count() > 1) { throw Oops.Oh("一次只能操作一个波次单下发"); } // 获取所有波次单 //Expression> predicate = u => sortNoList.Contains(u.SortNo); var allWmsOrderSortList = await _wmsOrderSortRep.GetListAsync(u => sortNoList.Contains(u.SortNo)); if (allWmsOrderSortList?.Count <= 0) { throw Oops.Oh("波次单不存在"); } //获取波次单所有明细 var allOrderSortDetails = await _wmsOrderSortDetailsRep.GetListAsync(u => sortNoList.Contains(u.SortNo)); if (allOrderSortDetails?.Count <= 0) { throw Oops.Oh("波次单明细不存在"); } //本次下发波次单明细 var currentOrderSortDetails = allOrderSortDetails.Where(u => input.Select(x => x.SortDetailsId).Contains(u.Id)).ToList(); if (currentOrderSortDetails?.Count <= 0) { throw Oops.Oh("本次下发的波次单明细不存在"); } // 获取业务类型 BusinessTypeEnum businessTypeEnum = BusinessTypeEnum.波次下发; var wmsBaseBusinessType = BusinessTypeHelper.GetBusinessTypeInfoFromDB((int)businessTypeEnum, _WmsBaseBusinessTypeRep); var movementDetailsKeyList = currentOrderSortDetails.Select(x => x.RelationNo + x.RelationNoLineNumber).ToList(); List materialCodeList = currentOrderSortDetails.Select(x => x.MaterialCode).Distinct().ToList(); var db_movementDetailsList = await _wmsOrderMovementDetailsRep.GetListAsync(x => movementDetailsKeyList.Contains(x.MovementNo + x.LineNumber)); var queryOrderIds = db_movementDetailsList.Select(x => x.MovementId).Distinct().ToList(); var orderMovementList = await _wmsOrderMovementRep.GetListAsync(x => queryOrderIds.Contains(x.Id)); //查询 所有该物料的 分配表的所有可用数据 var db_all_wmsRecordPredetermineDispenseList = await _wmsRecordPredetermineDispenseRep.GetListAsync(x => x.PDRecordType == PDRecordTypeEnum.分配 && x.PDRecordStatus == PDRecordStatusEnum.已分配 && materialCodeList.Contains(x.MaterialCode) ); ////获取可操作下发的库存信息-(未排除锁定库存)库存状态已上架、质检状态合格、非虚拟库位 //var queryParam = ExpressionHelper.GetStockQuanForIssueOutTask(); //queryParam.And(x => materialCodeList.Contains(x.MaterialCode)); //var allStockQuanList = await _v_wms_stock_quanRep.GetListAsync(queryParam); ////根据物料编码获取物料跟踪码的可用库存信息-已排除掉分配、下发锁定库存 var allStockQuanUseList = await _v_wms_stock_quan_for_useRep.GetListAsync(x => materialCodeList.Contains(x.MaterialCode)); var allContainerCodeList = allStockQuanUseList.Select(s => s.ContainerCode).ToList(); //获取所有库存视图信息 添加事务记录用 List allStockQuanViewList = await _v_wms_stock_quanRep.GetListAsync(x => allContainerCodeList.Contains(x.ContainerCode)); if (allStockQuanViewList?.Count <= 0) { throw Oops.Oh("所有物料都没有库存"); } //获取库存库位相信 var placeCodeList = allStockQuanViewList.Select(s => s.PlaceCode).Distinct().ToList(); var allPlaeList = await _wmsPlaceRep.GetListAsync(u => placeCodeList.Contains(u.PlaceCode) && u.IsDelete == false); #region 验证 var validateInput = GetWmsOrderMovementDetailsForIssueInput(input, currentOrderSortDetails, allWmsOrderSortList, orderMovementList, db_movementDetailsList); await StockQuanHelper.ValdiateStock(1, validateInput, _wmsOrderMovementDetailsRep, _v_wms_stock_quan_for_useRep); #endregion var containerCodeList = allStockQuanUseList.Select(s => s.ContainerCode).ToList(); //获取库存未完成的调度任务 var allTaskList = await _wmsTaskRep.GetListAsync(x => x.TaskStatus != TaskStatusEnum.已完成 && x.TaskStatus != TaskStatusEnum.已取消 && x.IsDelete == false && containerCodeList.Contains(x.ContainerCode)); //获取物料下架策略 #region 获取物料下架策略 //获取生效的下架策略物料范围 List allUnshelveStrategyRangeList = await ConfigUnshelveStrategyHelper.GetUseUnshelveStrategyRangeList(_v_wms_config_unshelve_strategy_rangeRep, materialCodeList); //获取生效的下架策略选项 List allUnshelveStrategyChooseList = await ConfigUnshelveStrategyHelper.GetUnshelveStrategyChooseListByRange(_wmsConfigUnshelveStrategyChooseRep, allUnshelveStrategyRangeList); #endregion foreach (var inputItem in input) { var item = currentOrderSortDetails.FirstOrDefault(x => x.Id == inputItem.SortDetailsId); WmsOrderSort sortOrder = allWmsOrderSortList.FirstOrDefault(p => p.SortNo == item.SortNo); //TODO 校验波次单状态 if (sortOrder == null) { throw Oops.Oh("本次下发的波次单不存在"); } WmsOrderMovement movementOrder = orderMovementList.FirstOrDefault(p => p.OrderNo == item.RelationNo); if (movementOrder == null) { throw Oops.Oh("本次下发关联的下架单不存在"); } //TODO 校验下架单状态 var sortMovementDetails = db_movementDetailsList.FirstOrDefault(f => f.MovementNo == item.RelationNo && f.LineNumber == item.RelationNoLineNumber); if (sortMovementDetails == null) { throw Oops.Oh("本次下发关联的下架单明细不存在"); } if (string.IsNullOrWhiteSpace(movementOrder.ToPlaceCode) && string.IsNullOrWhiteSpace(movementOrder.ToAreaCode)) { throw Oops.Oh($"{movementOrder.OrderTypeName}类型移动单{movementOrder.OrderNo}目标库区、目标库位不能同时为空"); } if (string.IsNullOrWhiteSpace(movementOrder.ToPlaceCode)) { var handle = FindEmptyPlaceServiceFactory.GetHandle(MaterialClassifyFlagEnum.物料, _v_empty_placeRep, _wmsBasePlaceRep, _wmsTaskRep); var emptyPlaceList = await handle.MainFindMultiEmptyLocation(new FindEmptyPlaceInput() { AreaList = new List() { movementOrder.ToAreaCode } }); var returnPlaceCode = emptyPlaceList.FirstOrDefault(); if (returnPlaceCode == null) { throw Oops.Oh($"库区{movementOrder.ToAreaCode}没有可推荐库位!"); } movementOrder.ToPlaceCode = returnPlaceCode.PlaceCode; movementOrder.ToPlaceName = returnPlaceCode.PlaceName; } #region 处理下发 #region 验证下发需求数 decimal issueQuantity = item.IssueQuantity ?? 0M;//波次单的历史下发数量 decimal allQuantity = item.Quantity - issueQuantity;//当前未下发数量 decimal occQuantity = inputItem.SendQuantity;//当前实际下发数量 if (allQuantity <= 0) { throw Oops.Oh("波次单" + item.SortNo + "行号" + item.LineNumber + "物料编号" + item.MaterialCode + $"已全部下发"); } if (occQuantity > allQuantity) { throw Oops.Oh("波次单" + item.SortNo + "行号" + item.LineNumber + "物料编号" + item.MaterialCode + $"本次下发需求数量{occQuantity}大于未下发数{allQuantity}"); } #endregion // 根据物料找出库存列表中数量大于0的库存 var allMaterialStockQuanList = allStockQuanUseList.Where(x => (x.Quantity) > 0 && x.MaterialCode == item.MaterialCode) //// 如果传入了物料批次号,则筛选出批次号相同的库存项 //.Where(!string.IsNullOrEmpty(item.Batch), x => x.Batch == item.Batch) //.Where(!string.IsNullOrEmpty(item.ErpCode), x => x.ErpCode == item.ErpCode) .ToList(); //处理后未下发的数量,多个同一个物料多个库存数据的情况,循环处理下发 decimal currentIssueQuantity = occQuantity; #region 处理分配 //分配原则:1、优先按照分配的库存搞 2、其次按照入库时间搞 var find_db_wmsRecordPredetermineDispenseList = db_all_wmsRecordPredetermineDispenseList.Where(x => (x.MovementNo + x.MovementLineNumber) == (sortMovementDetails.MovementNo + sortMovementDetails.LineNumber)).ToList(); foreach (var preDisRecord in find_db_wmsRecordPredetermineDispenseList) { var aa = allMaterialStockQuanList.Where(x => x.SNCode == preDisRecord.SNCode).FirstOrDefault(); if (aa == null) { throw Oops.Oh("物料编号" + preDisRecord.MaterialCode + $"的分配记录(跟踪码{preDisRecord.SNCode})在库存中不存在"); } if (aa.Quantity < preDisRecord.Quantity) { throw Oops.Oh("物料编号" + preDisRecord.MaterialCode + $",跟踪码{preDisRecord.SNCode}的库存数量{aa.Quantity}小于分配数量{preDisRecord.Quantity},请检查"); } var _remark = $"波次下发,分配抵消,波次单{inputItem.SortNo}"; decimal changeQty = 0; if (preDisRecord.Quantity >= currentIssueQuantity) { changeQty = currentIssueQuantity; currentIssueQuantity = 0; } else { changeQty = preDisRecord.Quantity; currentIssueQuantity = currentIssueQuantity - changeQty;//剩下的待下发的数量 } preDisRecord.Quantity -= changeQty; //扣减库存 aa.Quantity -= changeQty; aa.ActionRemark = _remark; if (preDisRecord.Quantity == 0) { //修改分配表 PredetermineDispenseHelper.SetPDRecordStatus(preDisRecord, PDRecordStatusEnum.已取消); } preDisRecord.Remarks = _remark; //修改移动单上的预配数 if ((sortMovementDetails.PredetermineQuantity - changeQty) < 0) { sortMovementDetails.PredetermineQuantity = 0; } else { sortMovementDetails.PredetermineQuantity -= changeQty; } CommonDoForIssue(_WmsBaseBusinessTypeRep, addWmsTaskList, aa, item, changeQty, movementOrder, sortMovementDetails, addWmsContainerSortList, sortOrder); } #endregion var sortStockQuanList = GetSortStockQuanListByStrategy(allUnshelveStrategyRangeList, allUnshelveStrategyChooseList, item, db_all_wmsRecordPredetermineDispenseList, sortMovementDetails, currentIssueQuantity, allMaterialStockQuanList); var old_Count = allMaterialStockQuanList.Count(); allMaterialStockQuanList = sortStockQuanList.Adapt>();//复制排序后的对象 var new_Count = allMaterialStockQuanList.Count(); if (old_Count != new_Count) { throw Oops.Oh($"排序后数量不对,排序前数量{old_Count},排序后数量{new_Count}"); } ////同一个物料尽量从同一个库位取 //if (allMaterialStockQuanList?.Count() <= 0) //{ // //缺料 // throw Oops.Oh("物料编号" + item.MaterialCode + $"库存数量不足,下发需求数量:{occQuantity},库存可用数量:0"); //} ////这里的Quantity 没有排除掉其他单据的分配锁定库存 //var usedQty = allMaterialStockQuanList.Where(o => o.MaterialCode == item.MaterialCode).Sum(x => x.Quantity); //if (usedQty < occQuantity) //{ // throw Oops.Oh("物料编号" + item.MaterialCode + $"库存数量不足,下发需求数量:{occQuantity},库存可用数量:{usedQty}"); //} foreach (var stockQuanItem in allMaterialStockQuanList) { //别的单子占用该跟踪号的数量,需要排除掉 别人占用的分配记录数据 decimal other_PredetermineQuery_Qty = GetOtherPredetermineQuery(db_all_wmsRecordPredetermineDispenseList, sortMovementDetails, stockQuanItem); //库位属性是 封存和禁出的,不能操作下架 if (stockQuanItem.PlaceStatus == PlaceStatusEnum.封存) { throw Oops.Oh($"库位编号{stockQuanItem.PlaceCode}库位属性是{PlaceStatusEnum.封存.GetDescription()}"); } if (stockQuanItem.PlaceStatus == PlaceStatusEnum.禁出) { throw Oops.Oh($"库位编号{stockQuanItem.PlaceCode}库位属性是{PlaceStatusEnum.禁出.GetDescription()}"); } //校验容器是否 有未完成的任务。 if (allTaskList.Any(a => a.ContainerCode.Equals(stockQuanItem.ContainerCode))) { throw Oops.Oh($"容器{stockQuanItem.ContainerCode}存在未完成的调用任务"); } if (currentIssueQuantity > 0) { //当前物料可用库存 var currentUsedQuantity = stockQuanItem.Quantity - other_PredetermineQuery_Qty; currentUsedQuantity = currentUsedQuantity <= 0 ? 0 : currentUsedQuantity; if (currentUsedQuantity <= 0) { continue; } //循环库存信息 处理下发数 decimal changQty = 0M;//实际本次分拣数量 if (currentUsedQuantity >= currentIssueQuantity)//下发数量小于等于库存 { changQty = currentIssueQuantity; currentIssueQuantity = 0;//全部下发,不在处理 } else { //当前下发数未能完全下发,累计下发数 changQty = currentUsedQuantity; currentIssueQuantity = currentIssueQuantity - currentUsedQuantity; } //修改移动单上的预配数 if ((sortMovementDetails.PredetermineQuantity - changQty) < 0) { sortMovementDetails.PredetermineQuantity = 0; } else { sortMovementDetails.PredetermineQuantity -= changQty; } CommonDoForIssue(_WmsBaseBusinessTypeRep, addWmsTaskList, stockQuanItem, item, changQty, movementOrder, sortMovementDetails, addWmsContainerSortList, sortOrder); if (currentIssueQuantity == 0) { break; } } else { break; } } if (currentIssueQuantity > 0) { throw Oops.Oh("物料编号" + item.MaterialCode + $"库存数量不足"); } //更新波次单的已下发数量 decimal lastIssueQuantity = issueQuantity + occQuantity;//波次单的历史下发数量 item.IssueQuantity += occQuantity;//累计下发数 item.OffShelvesQuantity += occQuantity;//下发完成就认为已下架 //更新波次单明细的状态 OrderHelper.updateOrderSortDetailsStatus(item); updateWmsOrderSortDetailsList.Add(item); //更新下架单明细的 下架数 sortMovementDetails.OffShelvesQuantity += occQuantity; #endregion } foreach (var item in allWmsOrderSortList) { var updateOrderSortDetails = allOrderSortDetails.Where(w => w.SortId == item.Id).ToList(); ////计算 波次单单的状态 OrderHelper.UpdatOrderSortStatus(updateOrderSortDetails, item); updateWmsOrderSortList.Add(item); } if (addWmsContainerSortList?.Count <= 0) { throw Oops.Oh("没有可下发的物料,库存全部不足"); } if (addWmsTaskList?.Count <= 0) { throw Oops.Oh("下架任务创建失败"); } //把容器上的库存都锁定 var queryContainerCodeList = addWmsContainerSortList.Select(x => x.ContainerCode).Distinct().ToList(); var stockQunList = await _wmsStockQuanRep.GetListAsync(x => queryContainerCodeList.Contains(x.ContainerCode)); foreach (var item in stockQunList) { string recordTransRemarks = $"波次下发"; LockInfo freezeInfo = new LockInfo() { LockReason = "波次下发", LockTime = DateTime.Now, LockUser = _userManager.RealName,//登录人的真实姓名 LockStatus = LockStatusEnum.已锁定,// }; StockQuanHelper.UpdateStockLockStatus(item, freezeInfo); #region 转移库存添加事务、操作日志 TransferOtherDetail transferOtherDetail = new TransferOtherDetail() { RelationNo = sortNoList[0],//波次单号 RelationNoLineNumber = string.Empty, Remarks = recordTransRemarks, }; //根据库存跟踪码视图可以查询到信息 //获取源库存信息 var sourceStockView = StockQuanHelper.GetVMmsStockQuan(allStockQuanViewList, item.SNCode); //update by liuwq 20240730 //获取目标库位 //波次下发 库存没有变更库位,目标库位还是源库位,调度任务处理后才会变更库位 var oldToPlace = BaseInfoHelper.GetPlace(sourceStockView.PlaceCode, allPlaeList); //新增事务记录 WmsRecordTrans addWmsRecordTrans = LogRecordHelper.CreateWmsRecordTrans(wmsBaseBusinessType, sourceStockView, item, oldToPlace, transferOtherDetail); addWmsRecordTransList.Add(addWmsRecordTrans); //新增操作日志 WmsLogAction wareActionLog = LogActionHelper.CreateWmsLogAction(item.Id, recordTransRemarks, $"锁定库存跟踪码{item.SNCode}"); addWmsLogActionList.Add(wareActionLog); #endregion } // var _tenant = _wmsContainerSortRep.AsTenant(); try { #region 事务内执行操作 await _wmsContainerSortRep.InsertRangeAsync(addWmsContainerSortList); await _wmsTaskRep.InsertRangeAsync(addWmsTaskList); await _wmsOrderSortRep.UpdateRangeAsync(updateWmsOrderSortList); await _wmsOrderSortDetailsRep.UpdateRangeAsync(updateWmsOrderSortDetailsList); //新增事务记录、收货记录、操作日志 await _wmsLogActionRep.InsertRangeAsync(addWmsLogActionList); await _wmsRecordTransRep.InsertRangeAsync(addWmsRecordTransList); //更新庫存 await _wmsRecordPredetermineDispenseRep.UpdateRangeAsync(db_all_wmsRecordPredetermineDispenseList);//更新分配表 //把容器上的库存都锁定 await _wmsStockQuanRep.UpdateRangeAsync(stockQunList); await _wmsOrderMovementDetailsRep.UpdateRangeAsync(db_movementDetailsList); #endregion // await _tenant.CommitTranAsync(); } catch (Exception ex) { // await _tenant.RollbackTranAsync(); throw; } return 1; } /// /// 根据策略排序 /// /// /// /// /// /// /// /// 原始的对象 /// private static List GetSortStockQuanListByStrategy(List allUnshelveStrategyRangeList, List allUnshelveStrategyChooseList, WmsOrderSortDetails item, List db_all_wmsRecordPredetermineDispenseList, WmsOrderMovementDetails sortMovementDetails, decimal currentIssueQuantity, List org_allMaterialStockQuanList) { //这里不才走变更库存信息,仅仅排序,所以必须是复制对象,而不是直接修改对象 List allMaterialStockQuanList = org_allMaterialStockQuanList.Adapt>();//复制对象 foreach (var item2 in allMaterialStockQuanList) { item2.HanlerQty = item2.Quantity; } //策略是否可用 bool isUseStrategy = false; List findStockQuanList = new List(); List currentUnshelveStrategyRangeList = allUnshelveStrategyRangeList.Where(w => w.MaterialCode == item.MaterialCode).ToList(); List currentUnshelveStrategyChooseList = new List(); if (currentUnshelveStrategyRangeList?.Count > 0) { if (currentUnshelveStrategyRangeList.Where(w => w.MaterialCode == item.MaterialCode).Select(s => s.StrategyCode).Distinct().Count() > 1) { throw Oops.Oh($"物料编号" + item.MaterialCode + "存在多个下架策略,请检查"); } //当前物料下架策略选项-- 根据优先级升序排序 级别越小 越优先 var allChooseList = allUnshelveStrategyChooseList.OrderBy(x => x.Priority).Where(w => w.StrategyCode == currentUnshelveStrategyRangeList[0].StrategyCode && w.IsDelete == false).ToList(); if (allChooseList?.Count <= 0) { //throw Oops.Oh($"下架策略{currentUnshelveStrategyRangeList[0].StrategyCode}物料编号" + item.MaterialCode + "下架策略选项不存在"); return UseDefaultOrderForIssue(allMaterialStockQuanList, ref findStockQuanList); } currentUnshelveStrategyChooseList.AddRange(allChooseList); //if (currentUnshelveStrategyChooseList.Count() > 1) //{ // throw Oops.Oh($"下架策略{currentUnshelveStrategyRangeList[0].StrategyCode}物料编号" + item.MaterialCode + "存在多个下架策略选项,请检查"); //} isUseStrategy = true; } //有下架策略 if (isUseStrategy == true) { List groupStockQuanList = new List(); //库存数使用Quantity ,不使用可用库存AvailableQty //循环扣减掉其他单据的分配占用的库存数,上面先完成了分逻辑,这里拿到的就是当前单据物料可以使用的库存数 foreach (var allItem in allMaterialStockQuanList) { //别的单子占用该跟踪号的数量,需要排除掉 别人占用的分配记录数据 decimal other_PredetermineQuery_Qty = GetOtherPredetermineQuery(db_all_wmsRecordPredetermineDispenseList, sortMovementDetails, allItem); allItem.HanlerQty -= other_PredetermineQuery_Qty; } var firstUnshelveStrategyChoose = currentUnshelveStrategyChooseList[0].StrategyOption; StrategyOptionEnum secondUnshelveStrategyChoose = 0; if (currentUnshelveStrategyChooseList.Count >= 2) { secondUnshelveStrategyChoose = currentUnshelveStrategyChooseList[1].StrategyOption; } //共 7种情况 switch (firstUnshelveStrategyChoose) { case StrategyOptionEnum.整拖优先: if (secondUnshelveStrategyChoose != 0) { if (secondUnshelveStrategyChoose == StrategyOptionEnum.先进先出_天) { FullContiner_FIFOByDay_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } else { throw Oops.Oh($"策略有问题,第二项不是 先进先出_天"); } } else { FullContiner_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } break; case StrategyOptionEnum.零散优先: if (secondUnshelveStrategyChoose != 0) { if (secondUnshelveStrategyChoose == StrategyOptionEnum.先进先出_天) { HalfContiner_FIFOByDay_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } else { throw Oops.Oh($"策略有问题,第二项不是 先进先出_天"); } } else { HalfContiner_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } break; case StrategyOptionEnum.先进先出_天: if (secondUnshelveStrategyChoose != 0) { if (secondUnshelveStrategyChoose == StrategyOptionEnum.整拖优先) { FIFOByDay_FullContiner_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } else if (secondUnshelveStrategyChoose == StrategyOptionEnum.零散优先) { FIFOByDay_HalfContiner_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } else { throw Oops.Oh($"策略有问题,第二项不是 满托推荐或半满推荐"); } } else { FIFOByDay_OrderForIssue(allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } break; default: throw Oops.Oh($"暂不支持"); } } else//没有下架策略 { return UseDefaultOrderForIssue(allMaterialStockQuanList, ref findStockQuanList); } return findStockQuanList; } /// /// 默认的下架策略-先进先出 /// /// /// /// private static List UseDefaultOrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList) { var lastResult = allMaterialStockQuanList.OrderBy(o => o.RecordInsertTime).ToList(); findStockQuanList.AddRange(lastResult); return findStockQuanList; } #region 满托推荐=>先入先出(天)的下架策略 /// /// 满托推荐=>先入先出(天)的下架策略 (2) /// /// /// /// /// private static List FullContiner_FIFOByDay_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_FullContiner_First_OrderForIssue(2, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// 满托推荐的下架策略 (1) /// /// /// /// /// private static List FullContiner_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_FullContiner_First_OrderForIssue(1, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// 满托推荐的下架策略 (1) /// 满托推荐=>先入先出(天)的下架策略 (2) /// /// 1:满托推荐的下架策略 2:满托推荐=>先入先出(天)的下架策略 /// /// /// /// private static List Common_FullContiner_First_OrderForIssue(int flag, List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { var group = allMaterialStockQuanList.GroupBy(g => new { g.ContainerCode, g.MaterialCode }); var lastResult1 = group.Where(w => w.Sum(x => x.Quantity) == currentIssueQuantity); if (flag == 2) { lastResult1 = lastResult1.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult1) { findStockQuanList.AddRange(item.ToList()); } var lastResult2 = group.Where(w => w.Sum(x => x.Quantity) > currentIssueQuantity).OrderByDescending(x => x.Sum(y => y.Quantity)); if (flag == 2) { lastResult2 = lastResult2.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult2) { findStockQuanList.AddRange(item.ToList()); } var lastResult3 = group.Where(w => w.Sum(x => x.Quantity) < currentIssueQuantity).OrderByDescending(x => x.Sum(y => y.Quantity)); if (flag == 2) { lastResult3 = lastResult3.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult3) { findStockQuanList.AddRange(item.ToList()); } return findStockQuanList; } #endregion #region 半满推荐=>先入先出(天)的下架策略 /// /// 半满推荐=>先入先出(天)的下架策略 (2) /// /// /// /// /// private static List HalfContiner_FIFOByDay_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_HalfContiner_First_OrderForIssue(2, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// ///半满推荐的下架策略 (1) /// /// /// /// /// private static List HalfContiner_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_HalfContiner_First_OrderForIssue(1, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// (1)半满推荐的下架策略 /// (2) 半满推荐=>先入先出(天)的下架策略 /// /// /// /// /// /// private static List Common_HalfContiner_First_OrderForIssue(int flag, List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { var group = allMaterialStockQuanList.GroupBy(g => new { g.ContainerCode, g.MaterialCode }); var lastResult3 = group.Where(w => w.Sum(x => x.Quantity) < currentIssueQuantity).OrderBy(x => x.Sum(y => y.Quantity)); if (flag == 2) { lastResult3 = lastResult3.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult3) { findStockQuanList.AddRange(item.ToList()); } var lastResult1 = group.Where(w => w.Sum(x => x.Quantity) == currentIssueQuantity); if (flag == 2) { lastResult1 = lastResult1.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult1) { findStockQuanList.AddRange(item.ToList()); } var lastResult2 = group.Where(w => w.Sum(x => x.Quantity) > currentIssueQuantity).OrderBy(x => x.Sum(y => y.Quantity)); if (flag == 2) { lastResult2 = lastResult2.OrderBy(x => x.Min(y => Convert.ToInt32(y.RecordInsertTime.ToString("yyyyMMdd")))); } foreach (var item in lastResult2) { findStockQuanList.AddRange(item.ToList()); } return findStockQuanList; } #endregion #region 先入先出(天)的 /// /// 只有 先入先出(天) /// /// /// /// /// private static List FIFOByDay_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_FIFOByDay_First_OrderForIssue(0, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// 先入先出(天)=>其次满托 /// /// /// /// /// private static List FIFOByDay_FullContiner_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_FIFOByDay_First_OrderForIssue(1, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// 先入先出(天)=>其次半托 /// /// /// /// /// private static List FIFOByDay_HalfContiner_OrderForIssue(List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { return Common_FIFOByDay_First_OrderForIssue(2, allMaterialStockQuanList, ref findStockQuanList, currentIssueQuantity); } /// /// 公共-先入先出(天)的 /// /// 0:只有FIFODay 1:其次满托 2:其次半托 /// /// /// /// private static List Common_FIFOByDay_First_OrderForIssue(int flag, List allMaterialStockQuanList, ref List findStockQuanList, decimal currentIssueQuantity) { var groupByDay = allMaterialStockQuanList.GroupBy(g => new { g.RecordInsertTime.Date }); //groupByDay 是按照 入库时间(天)分组 if (flag == 2 || flag == 1) { foreach (var item in groupByDay) { //在每个(天)分组中,再寻找相同天的优先满托或半托出库的 if (flag == 1) { Common_HalfContiner_First_OrderForIssue(1, item.ToList(), ref findStockQuanList, currentIssueQuantity); } if (flag == 2) { Common_HalfContiner_First_OrderForIssue(1, item.ToList(), ref findStockQuanList, currentIssueQuantity); } } } else { groupByDay = groupByDay.OrderBy(o => o.Key.Date); foreach (var item in groupByDay) { findStockQuanList.AddRange(item.ToList()); } } return findStockQuanList; } #endregion /// /// 根据分拣明细获取下架策略选项 /// /// /// /// /// private static List GetUnshelveStrategyChooseListBySortDetails(List allUnshelveStrategyRangeList, List allUnshelveStrategyChooseList, WmsOrderSortDetails item) { List currentUnshelveStrategyRangeList = allUnshelveStrategyRangeList.Where(w => w.MaterialCode == item.MaterialCode).ToList(); List currentUnshelveStrategyChooseList = new List(); if (currentUnshelveStrategyRangeList?.Count > 0) { if (currentUnshelveStrategyRangeList.Where(w => w.MaterialCode == item.MaterialCode).Select(s => s.StrategyCode).Distinct().Count() > 1) { throw Oops.Oh($"物料编号" + item.MaterialCode + "存在多个下架策略,请检查"); } //当前物料下架策略选项-- 根据优先级升序排序 级别越小 越优先 var allChooseList = allUnshelveStrategyChooseList.OrderBy(x => x.Priority).Where(w => w.StrategyCode == currentUnshelveStrategyRangeList[0].StrategyCode && w.IsDelete == false).ToList(); if (currentUnshelveStrategyChooseList?.Count <= 0) { throw Oops.Oh($"下架策略{currentUnshelveStrategyRangeList[0].StrategyCode}物料编号" + item.MaterialCode + "下架策略选项不存在"); } ////if (currentUnshelveStrategyChooseList.Count() > 1) ////{ //// throw Oops.Oh($"下架策略{currentUnshelveStrategyRangeList[0].StrategyCode}物料编号" + item.MaterialCode + "存在多个下架策略选项,请检查"); ////} currentUnshelveStrategyChooseList.AddRange(allChooseList); } return currentUnshelveStrategyChooseList; } private static decimal GetOtherPredetermineQuery(List db_all_wmsRecordPredetermineDispenseList, WmsOrderMovementDetails sortMovementDetails, v_wms_stock_quan_for_use stockQuanItem) { return db_all_wmsRecordPredetermineDispenseList.Where(x => (x.MovementNo + x.MovementLineNumber) != (sortMovementDetails.MovementNo + sortMovementDetails.LineNumber) && x.MaterialCode == stockQuanItem.MaterialCode && x.SNCode == stockQuanItem.SNCode ).Sum(x => x.Quantity); } /// /// 获取校验下发参数对象 /// /// /// /// /// /// /// private static List GetWmsOrderMovementDetailsForIssueInput(List input, List currentOrderSortDetails, List allWmsOrderSortList, List orderMovementList, List movementDetailsList ) { List result = new List(); foreach (var inputItem in input) { var item = currentOrderSortDetails.FirstOrDefault(x => x.Id == inputItem.SortDetailsId); WmsOrderSort sortOrder = allWmsOrderSortList.FirstOrDefault(p => p.SortNo == item.SortNo); //TODO 校验波次单状态 if (sortOrder == null) { throw Oops.Oh("本次下发的波次单不存在"); } WmsOrderMovement movementOrder = orderMovementList.FirstOrDefault(p => p.OrderNo == item.RelationNo); if (movementOrder == null) { throw Oops.Oh("本次下发关联的下架单不存在"); } //TODO 校验下架单状态 var sortMovementDetails = movementDetailsList.FirstOrDefault(f => f.MovementNo == item.RelationNo && f.LineNumber == item.RelationNoLineNumber); if (sortMovementDetails == null) { throw Oops.Oh("本次下发关联的下架单明细不存在"); } WmsOrderMovementDetailsForIssueInput res = sortMovementDetails.Adapt(); res.SendQuantity = inputItem.SendQuantity; result.Add(res); } return result; } /// /// 波次下發統一處理 /// /// /// /// /// /// /// /// private static void CommonDoForIssue(SqlSugarRepository _WmsBaseBusinessTypeRep, List addWmsTaskList, v_wms_stock_quan_for_use stockQuanItem, WmsOrderSortDetails item, decimal changeQty, WmsOrderMovement movementOrder, WmsOrderMovementDetails sortMovementDetails, List addWmsContainerSortList, WmsOrderSort sortOrder) { //创建锁定库存 var addWmsStockQuanLock = CommonCreateWmsStockQuanLock(item, changeQty, stockQuanItem); //创建分拣信息 var addWmsContainerSort = CommonCreateWmsContainerSort(item, movementOrder, sortMovementDetails, changeQty, addWmsStockQuanLock); addWmsContainerSortList.Add(addWmsContainerSort); //创建下架调度任务 var addWmsTask = CommonCreateWmsTask(_WmsBaseBusinessTypeRep, sortOrder.SortNo, movementOrder, stockQuanItem); //同一个容易创建一个 下架任务即可 CreateWmsTask(addWmsTaskList, addWmsTask, stockQuanItem); } /// /// 公共創建調度任務 /// /// /// /// private static void CreateWmsTask(List addWmsTaskList, WmsTask addWmsTask, v_wms_stock_quan_for_use stockQuanItem) { //同一个容易创建一个 下架任务即可 if (!addWmsTaskList.Any(a => a.ContainerCode == stockQuanItem.ContainerCode)) { addWmsTaskList.Add(addWmsTask); } } /// /// 创建下架调度任务公共方法 /// /// /// /// /// /// private static WmsTask CommonCreateWmsTask(SqlSugarRepository _WmsBaseBusinessTypeRep, string orderNo, WmsOrderMovement movementOrder, v_wms_stock_quan_for_use stockQuanItem) { string toPlaceCode = movementOrder.ToPlaceCode; string toAreaCode = movementOrder.ToAreaCode; var businessTypeInfo = BusinessTypeHelper.GetBusinessTypeInfoFromDB((int)movementOrder.BusinessType, _WmsBaseBusinessTypeRep); return new WmsTask() { MoveType = businessTypeInfo.MoveType, MoveTypeName = businessTypeInfo.MoveTypeName, BusinessType = businessTypeInfo.BusinessTypeValue, BusinessTypeName = businessTypeInfo.BusinessTypeName, ContainerCode = stockQuanItem.ContainerCode, SourcePlaceCode = stockQuanItem.PlaceCode,//下架单指定的源库位 ToPlaceCode = toPlaceCode,//下架单指定的目标库位 ToAreaCode = toAreaCode,//下架单指定的目标库区 IsFlagFinish = false, OrderNo = orderNo,//创建任务的单据号 RelationNo = movementOrder.OrderNo,//下架单号 TaskDescribe = businessTypeInfo.BusinessTypeName, TaskStatus = TaskStatusEnum.新建, TaskStatusName = TaskStatusEnum.新建.GetDescription(), TaskNo = Yitter.IdGenerator.YitIdHelper.NextId().ToString(), TaskPriority = 0, IssueTime = DateTime.Now, TaskName = businessTypeInfo.BusinessTypeName }; } /// /// 创建锁定库存信息公共方法 /// /// 锁定库存信息集合 /// 波次单明细对象 /// 下发数 /// 库存视图对象 private static StockQuanLockOutput CommonCreateWmsStockQuanLock(WmsOrderSortDetails item, decimal occQuantity, v_wms_stock_quan_for_use stockQuanItem) { return new StockQuanLockOutput() { ContainerCode = stockQuanItem.ContainerCode, ContainerName = stockQuanItem.ContainerName, MaterialCode = stockQuanItem.MaterialCode, MaterialName = stockQuanItem.MaterialName, Quantity = occQuantity, Batch = stockQuanItem.Batch, SupplierBatch = stockQuanItem.SupplierBatch, SNCode = stockQuanItem.SNCode, ErpCode = stockQuanItem.ErpCode, AreaCode = stockQuanItem.AreaCode, AreaName = stockQuanItem.AreaName, PlaceCode = stockQuanItem.PlaceCode, PlaceName = stockQuanItem.PlaceName, SortNo = item.SortNo, SortNoLineNumber = item.LineNumber, RelationNoLineNumber = item.RelationNoLineNumber, RelationNo = item.RelationNo, }; } /// /// 创建分拣信息公共方法 /// /// 分拣信息集合 /// 波次单明细对象 /// 移动单对象 /// 移动单明细对象 /// 下发数 /// 锁定库存对象 private static WmsContainerSort CommonCreateWmsContainerSort(WmsOrderSortDetails item, WmsOrderMovement movementOrder, WmsOrderMovementDetails sortMovementDetails, decimal occQuantity, StockQuanLockOutput stockQuanLock) { return new WmsContainerSort() { StockQuanLockId = 0, ContainerCode = stockQuanLock.ContainerCode, MaterialCode = stockQuanLock.MaterialCode, MaterialName = stockQuanLock.MaterialName, Quantity = occQuantity, ErpCode = stockQuanLock.ErpCode, RelationNo = item.RelationNo, RelationNoLineNumber = item.RelationNoLineNumber, RelationDetailsId = sortMovementDetails.Id, Batch = stockQuanLock.Batch, SupplierBatch = stockQuanLock.SupplierBatch, SNCode = stockQuanLock.SNCode, SortDetailsId = item.Id, SortNo = item.SortNo, SortNoLineNumber = item.LineNumber, SortStatus = OrderStatusEnum.新建, SortStatusName = OrderStatusEnum.新建.GetDescription(), RelationOrderType = movementOrder.OrderType, RelationOrderTypeName = movementOrder.OrderType.GetDescription() }; } #endregion }