using HH.WMS.Common.Algorithm.Out;
|
using HH.WMS.Entitys;
|
using HH.WMS.Entitys.Basic;
|
using HH.WMS.Entitys.Common;
|
using HH.WMS.Entitys.Entitys;
|
using Newtonsoft.Json;
|
using System;
|
using System.Collections.Generic;
|
using System.ComponentModel;
|
using System.Linq;
|
using System.Reflection;
|
using System.Text;
|
using System.Threading.Tasks;
|
|
namespace HH.WMS.Common
|
{
|
public static class CommonExtension
|
{
|
#region 克隆对象
|
/// <summary>
|
/// 克隆对象
|
/// </summary>
|
/// <typeparam name="T"></typeparam>
|
/// <param name="t"></param>
|
/// <returns></returns>
|
/// <history>[HanHe(zh)] CREATED 2018/09/16</history>
|
public static T DeepCloneObject<T>(this T t) where T : class
|
{
|
T model = System.Activator.CreateInstance<T>(); //实例化一个T类型对象
|
PropertyInfo[] propertyInfos = model.GetType().GetProperties(); //获取T对象的所有公共属性
|
foreach (PropertyInfo propertyInfo in propertyInfos)
|
{
|
//判断值是否为空,如果空赋值为null见else
|
if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
|
{
|
//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换
|
NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType);
|
//将convertsionType转换为nullable对的基础基元类型
|
propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(t), nullableConverter.UnderlyingType), null);
|
}
|
else
|
{
|
propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(t), propertyInfo.PropertyType), null);
|
}
|
}
|
return model;
|
}
|
public static IList<T> DeepCloneList<T>(this IList<T> tList) where T : class
|
{
|
IList<T> listNew = new List<T>();
|
foreach (var item in tList)
|
{
|
T model = System.Activator.CreateInstance<T>(); //实例化一个T类型对象
|
PropertyInfo[] propertyInfos = model.GetType().GetProperties(); //获取T对象的所有公共属性
|
foreach (PropertyInfo propertyInfo in propertyInfos)
|
{
|
//判断值是否为空,如果空赋值为null见else
|
if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
|
{
|
//如果convertsionType为nullable类,声明一个NullableConverter类,该类提供从Nullable类到基础基元类型的转换
|
NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType);
|
//将convertsionType转换为nullable对的基础基元类型
|
propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(item), nullableConverter.UnderlyingType), null);
|
}
|
else
|
{
|
propertyInfo.SetValue(model, Convert.ChangeType(propertyInfo.GetValue(item), propertyInfo.PropertyType), null);
|
}
|
}
|
listNew.Add(model);
|
}
|
return listNew;
|
}
|
#endregion
|
|
#region 批分仓库量通用方法
|
/// <summary>
|
/// 批分仓库量通用方法
|
/// </summary>
|
/// <param name="tStockList"></param>
|
/// <param name="beList"></param>
|
/// <param name="bType"></param>
|
/// <param name="generateOut">是否生成出库单</param>
|
/// <param name="sbField"></param>
|
/// <returns></returns>
|
public static OperateResult BatchesStockQty(this List<TN_WM_B_STOCK_QTYEntity> tStockList, List<BatchesEntity> beList,
|
BatchesType bType, StockBatchesField sbField, List<string> lstStrate, bool generateOut = false, bool isDropQty = false)
|
{
|
//锁定明细(反批分分配量时记录)
|
BatchesResult bResult = new BatchesResult();
|
foreach (var b in beList)
|
{
|
//需要生成出库单时,过滤
|
if (generateOut && b.IsFilter)
|
continue;
|
//匹配五要素 // p.CN_S_OWNER.Trim().Equals(b.Owner.Trim()) &&
|
var cis = tStockList.Where(p =>
|
p.CN_S_ITEM_CODE.Equals(b.ItemCode) &&
|
p.CN_S_ITEM_STATE.Equals(b.ItemState) &&
|
(string.IsNullOrEmpty(b.ItemArrivalLot) || b.ItemArrivalLot.Equals(p.CN_S_LOT_NO)) &&
|
(string.IsNullOrEmpty(b.ItemProductionLot) || b.ItemProductionLot.Equals(p.CN_S_PRODUCTION_BATCH)));
|
|
//暂时先倒序生产批次 then 到货批次 后续取策略
|
var cisList = cis.OrderBy(p => p.CN_S_LOT_NO).ToList();
|
|
foreach (string stegy in lstStrate)
|
{
|
//逐个策略进行计算
|
switch (stegy)
|
{
|
case "FirstInFirstOut":
|
cisList = cisList.OrderBy(o => o.CN_S_LOT_NO).ToList();
|
break;
|
case "FirstWarrantFirstOut":
|
var lstEmptyPBC = cisList.Where(o => string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).ToList();
|
var lstFilterItem = cisList.Where(o => !string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).OrderBy(o => o.CN_S_PRODUCTION_BATCH).ToList();
|
lstFilterItem.AddRange(lstEmptyPBC);
|
cisList = lstFilterItem;
|
break;
|
}
|
}
|
|
if (!cisList.Any())
|
return OperateResult.Error("库存中未找到物料:" + b.ItemCode);
|
//待批数
|
var qty = b.Qty;
|
if (qty == 0) continue;
|
switch (bType)
|
{
|
#region 正批分
|
//正批分
|
case BatchesType.ForwardBatch:
|
for (int i = 0; i < cisList.Count; i++)
|
{
|
//批分编辑量
|
if (sbField == StockBatchesField.EditingQty)
|
{
|
//可用量 = 库存量-分配量-编辑量
|
var availableQty = cisList[i].CN_F_QUANTITY - cisList[i].CN_F_ALLOC_QTY - cisList[i].CN_F_EDITING_QTY;
|
if (availableQty <= 0) continue;
|
if (availableQty >= qty)
|
{
|
cisList[i].CN_F_EDITING_QTY += qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_EDITING_QTY += availableQty;
|
qty -= availableQty;
|
continue;
|
}
|
}
|
//批分分配量
|
else if (sbField == StockBatchesField.AllocationQty)
|
{
|
//可用量 = 库存量-分配量
|
var availableQty = cisList[i].CN_F_QUANTITY - cisList[i].CN_F_ALLOC_QTY;// -cisList[i].CN_F_EDITING_QTY;
|
if (availableQty <= 0) continue;
|
if (isDropQty)
|
{
|
if (availableQty >= qty)
|
{
|
cisList[i].CN_F_QUANTITY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_QUANTITY -= availableQty;
|
qty -= availableQty;
|
continue;
|
}
|
}
|
else
|
{
|
if (availableQty >= qty)
|
{
|
cisList[i].CN_F_ALLOC_QTY += qty;
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, qty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_ALLOC_QTY += availableQty;
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, availableQty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
qty -= availableQty;
|
continue;
|
}
|
}
|
}
|
}
|
break;
|
#endregion
|
|
#region 反批分
|
//反批分
|
case BatchesType.ReverseBatch:
|
for (int i = cisList.Count - 1; i >= 0; i--)
|
{
|
//批分编辑量
|
if (sbField == StockBatchesField.EditingQty)
|
{
|
var editingQty = cisList[i].CN_F_EDITING_QTY;
|
if (editingQty <= 0) continue;
|
if (editingQty >= qty)
|
{
|
cisList[i].CN_F_EDITING_QTY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_EDITING_QTY = 0;
|
qty -= editingQty;
|
continue;
|
}
|
}
|
//批分分配量 (反批分分配量,相当于批分出库明细了)
|
else if (sbField == StockBatchesField.AllocationQty)
|
{
|
var allocQty = cisList[i].CN_F_ALLOC_QTY;
|
if (allocQty <= 0) continue;
|
if (allocQty >= qty)
|
{
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, qty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
cisList[i].CN_F_ALLOC_QTY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, allocQty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
cisList[i].CN_F_ALLOC_QTY = 0;
|
qty -= allocQty;
|
continue;
|
}
|
}
|
}
|
break;
|
#endregion
|
}
|
#region 批分失败
|
|
if (qty > 0)
|
{
|
switch (bType)
|
{
|
case BatchesType.ForwardBatch:
|
switch (sbField)
|
{
|
case StockBatchesField.EditingQty:
|
case StockBatchesField.AllocationQty:
|
return OperateResult.Error("物料批分失败:物料编码:" + b.ItemCode + "库存量不足");
|
}
|
break;
|
case BatchesType.ReverseBatch:
|
switch (sbField)
|
{
|
case StockBatchesField.EditingQty:
|
return OperateResult.Error("物料反批分失败:物料编码:" + b.ItemCode + "编辑量不足");
|
case StockBatchesField.AllocationQty:
|
return OperateResult.Error("物料反批分失败:物料编码:" + b.ItemCode + "分配量不足");
|
}
|
break;
|
}
|
}
|
#endregion
|
}
|
|
#region 生成出库单
|
if (generateOut)
|
{
|
//若是反批分分配量,生成出库单信息
|
IEnumerable<IGrouping<string, BatchesEntity>> beGroup = beList.GroupBy(p => p.OpNo);
|
foreach (var be in beGroup)
|
{
|
//出库订单
|
TN_WM_OUT_MSTEntity outMst = be.AsEnumerable().ToList()[0].OutMst;
|
//修改出库订单主子表为‘待出库’
|
outMst.CN_S_STATE = Constants.State_WaitOut;
|
if (isDropQty)
|
outMst.CN_S_STATE = Constants.State_Completed;
|
outMst.OutDtlList.ForEach(p =>
|
{
|
p.CN_S_STATE = outMst.CN_S_STATE;
|
});
|
bResult.OutMstList.Add(outMst);
|
// 生成出库单号
|
UserRuleEntity user = new UserRuleEntity()
|
{
|
OrgCode = "0",
|
RuleCode = Constants.Rule_Out
|
};
|
string outNo = user.GenerateNo();
|
//将锁定库存改成根据出库单来锁
|
//if (bResult.OutLockingDtlList.FindAll(x => x.CN_S_FROM_NO == be.Key).Any())
|
//{
|
// foreach (var _updateStockDtl in bResult.OutLockingDtlList)
|
// {
|
// if (_updateStockDtl.CN_S_FROM_NO.Equals(be.Key))
|
// _updateStockDtl.CN_S_FROM_NO = outNo;
|
// }
|
//}
|
//非一进一出,生成出库单
|
var notAutoInOut = outMst.OutDtlList.FindAll(p => !p.CN_C_AUTO_INOUT);
|
if (notAutoInOut.Any())
|
{
|
var reduceInventoryMstEntity = GetReduceInventoryMst(outNo, Constants.Rule_OutOrder, outMst);
|
reduceInventoryMstEntity.DTLEntity.AddRange(GetReduceInventoryDtl(outNo, notAutoInOut));
|
bResult.ReduceInventoryMstList.Add(reduceInventoryMstEntity);
|
}
|
//生成一进一出出库单
|
var autoInOut = outMst.OutDtlList.FindAll(p => p.CN_C_AUTO_INOUT);
|
if (autoInOut.Any())
|
{
|
var reduceInventoryMstEntity = GetReduceInventoryMst(outNo, Constants.Auto_InOut, outMst);
|
reduceInventoryMstEntity.DTLEntity.AddRange(GetReduceInventoryDtl(outNo, autoInOut));
|
bResult.ReduceInventoryMstList.Add(reduceInventoryMstEntity);
|
}
|
}
|
}
|
#endregion
|
|
return OperateResult.Succeed(null, bResult);
|
}
|
#endregion
|
|
#region 出库单主表
|
/// <summary>
|
/// 出库单主表
|
/// </summary>
|
/// <param name="opNo"></param>
|
/// <param name="opClass"></param>
|
/// <param name="outMst"></param>
|
/// <returns></returns>
|
private static TN_WM_REDUCE_INVENTORY_MSTEntity GetReduceInventoryMst(string opNo, string opClass, TN_WM_OUT_MSTEntity outMst)
|
{
|
var mst = new TN_WM_REDUCE_INVENTORY_MSTEntity()
|
{
|
CN_GUID = Guid.NewGuid().ToString(),
|
CN_S_OP_NO = opNo,
|
CN_S_OP_TYPE = outMst.CN_S_OP_TYPE,
|
CN_S_STOCK_CODE = outMst.CN_S_STOCK_CODE,
|
CN_T_OPERATE = outMst.CN_T_OP_DATE,
|
CN_S_OP_FROM = Constants.Rule_OutOrder,
|
CN_S_OP_CLASS = opClass,
|
CN_S_FROM_NO = outMst.CN_S_OP_NO,
|
CN_S_STATE = Constants.State_WaitOut,
|
CN_S_NOTE = outMst.CN_S_NOTE,
|
CN_S_AUDITOR = outMst.CN_S_AUDITOR,
|
CN_T_AUDIT_TIME = outMst.CN_T_AUDIT_TIME,
|
CN_S_AUDIT_REMARK = outMst.CN_S_AUDIT_REMARK,
|
CN_S_CREATOR = outMst.CN_S_CREATOR,
|
CN_S_CREATOR_BY = outMst.CN_S_CREATOR_BY,
|
CN_T_CREATE = DateTime.Now,
|
CN_S_MODIFY = outMst.CN_S_MODIFY,
|
CN_S_MODIFY_BY = outMst.CN_S_MODIFY_BY,
|
CN_T_MODIFY = DateTime.Now,
|
CN_C_PRINT = "N",
|
};
|
mst.DTLEntity = new List<TN_WM_REDUCE_INVENTORY_DTLEntity>();
|
return mst;
|
}
|
#endregion
|
|
#region 出库单子表
|
/// <summary>
|
/// 出库单子表
|
/// </summary>
|
/// <param name="opNo"></param>
|
/// <param name="outDtl"></param>
|
/// <returns></returns>
|
private static List<TN_WM_REDUCE_INVENTORY_DTLEntity> GetReduceInventoryDtl(string opNo, List<TN_WM_OUT_DTLEntity> outDtlList)
|
{
|
var dtlList = new List<TN_WM_REDUCE_INVENTORY_DTLEntity>();
|
foreach (var outDtl in outDtlList)
|
{
|
var dtl = new TN_WM_REDUCE_INVENTORY_DTLEntity()
|
{
|
CN_GUID = Guid.NewGuid().ToString(),
|
CN_S_OP_NO = opNo,
|
CN_N_ROW_NO = outDtl.CN_N_ROW_NO,
|
CN_S_SERIAL_NO = outDtl.CN_S_SERIAL_NO,
|
CN_S_OWNER = "",
|
CN_S_ITEM_CODE = outDtl.CN_S_ITEM_CODE,
|
CN_S_ITEM_NAME = outDtl.CN_S_ITEM_NAME,
|
CN_S_ITEM_STATE = outDtl.CN_S_ITEM_STATE,
|
CN_S_LOT_CODE = outDtl.CN_S_LOT_CODE,
|
CN_F_QUANTITY = outDtl.CN_F_QUANTITY,
|
CN_S_NOTE = outDtl.CN_S_NOTE,
|
CN_S_STATE = outDtl.CN_S_STATE,
|
CN_S_FIGURE_NO = outDtl.CN_S_FIGURE_NO,
|
CN_S_MODEL = outDtl.CN_S_MODEL,
|
CN_S_MEASURE_UNIT = outDtl.CN_S_MEASURE_UNIT,
|
CN_F_SALE_PRICE = outDtl.CN_F_SALE_PRICE,
|
CN_F_SALE_MONEY = outDtl.CN_F_SALE_MONEY,
|
CN_C_IS_BALANCE = "N",
|
CN_C_IS_SCRAP = "N"
|
};
|
dtlList.Add(dtl);
|
}
|
return dtlList;
|
}
|
#endregion
|
|
#region 根据分拣单批分库区(不通用)
|
/// <summary>
|
/// 根据分拣单批分库区(不通用)
|
/// </summary>
|
/// <param name="tAreaList"></param>
|
/// <param name="beList"></param>
|
/// <returns></returns>
|
public static OperateResult BatchesAreaQty(this List<TN_WM_B_AREA_QTYEntity> tAreaList, List<TN_WM_SORTING_LISTEntity> sortingList, List<string> lstStrate, bool reverseBatch = false)
|
{
|
var sortingDtlList = new List<TN_WM_SORTING_DTLEntity>();
|
sortingList.ForEach(p =>
|
{
|
sortingDtlList.AddRange(p.SortingDtlList);
|
});
|
if (sortingDtlList.Any())
|
{
|
foreach (var s in sortingDtlList)
|
{
|
var cia = tAreaList.Where(p =>
|
p.CN_S_ITEM_CODE.Equals(s.CN_S_ITEM_CODE) &&
|
p.CN_S_ITEM_STATE.Equals(s.CN_S_ITEM_STATE) &&
|
p.CN_S_OWNER.Trim().Equals(s.CN_S_OWNER.Trim()) &&
|
(string.IsNullOrEmpty(s.CN_S_LOT_NO) || s.CN_S_LOT_NO.Equals(p.CN_S_LOT_NO)) &&
|
(string.IsNullOrEmpty(s.CN_S_PRODUCTION_BATCH) || s.CN_S_PRODUCTION_BATCH.Equals(p.CN_S_PRODUCTION_BATCH)));
|
|
//暂时先倒序生产批次 then 到货批次 后续取策略
|
var ciaList = cia.OrderBy(p => p.CN_S_LOT_NO).ToList();
|
|
foreach (string stegy in lstStrate)
|
{
|
//逐个策略进行计算
|
switch (stegy)
|
{
|
case "FirstInFirstOut":
|
ciaList = ciaList.OrderBy(o => o.CN_S_LOT_NO).ToList();
|
break;
|
case "FirstWarrantFirstOut":
|
var lstEmptyPBC = ciaList.Where(o => string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).ToList();
|
var lstFilterItem = ciaList.Where(o => !string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).OrderBy(o => o.CN_S_PRODUCTION_BATCH).ToList();
|
lstFilterItem.AddRange(lstEmptyPBC);
|
ciaList = lstFilterItem;
|
break;
|
}
|
}
|
|
if (!ciaList.Any())
|
return OperateResult.Error("库区中未找到物料:" + s.CN_S_ITEM_CODE);
|
//待批数
|
var qty = s.CN_F_QUANTITY;
|
if (qty == 0) continue;
|
|
foreach (var area in ciaList)
|
{
|
if (reverseBatch)
|
{
|
var currentCount = area.CN_F_ALLOC_QTY;
|
if (currentCount <= 0) continue;
|
if (currentCount >= qty)
|
{
|
area.CN_F_QUANTITY -= qty;
|
area.CN_F_ALLOC_QTY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
area.CN_F_QUANTITY -= currentCount;
|
area.CN_F_ALLOC_QTY = 0;
|
qty -= currentCount;
|
continue;
|
}
|
}
|
else
|
{
|
//当前可用量
|
var currentCount = area.CN_F_QUANTITY + area.CN_F_PLANNED_QTY - area.CN_F_ALLOC_QTY;
|
if (currentCount <= 0) continue;
|
if (currentCount >= qty)
|
{
|
area.CN_F_ALLOC_QTY += qty;
|
//outLockList.Add(GetOutLockingDtlEntity(area, s.CN_S_SORTING_NO, qty));
|
qty = 0;
|
break;
|
}
|
else
|
{
|
area.CN_F_ALLOC_QTY += currentCount;
|
//outLockList.Add(GetOutLockingDtlEntity(area, s.CN_S_SORTING_NO, currentCount));
|
qty -= currentCount;
|
continue;
|
}
|
}
|
}
|
if (qty > 0)
|
{
|
if (reverseBatch)
|
{
|
return OperateResult.Error("物料反批分失败:物料编码:" + s.CN_S_ITEM_CODE + "在库区中分配量不足");
|
}
|
else
|
{
|
return OperateResult.Error("物料批分失败:物料编码:" + s.CN_S_ITEM_CODE + "在库区中库存量不足");
|
}
|
}
|
}
|
return OperateResult.Succeed();
|
}
|
return OperateResult.Error("未找到分拣数据");
|
}
|
#endregion
|
|
#region 根据算法返回结果批分库区(不加分配量,直接减库存量)
|
/// <summary>
|
/// 根据算法返回结果批分库区(不加分配量,直接减库存量)
|
/// </summary>
|
/// <param name="tAreaList"></param>
|
/// <param name="outAreaResultList"></param>
|
/// <returns></returns>
|
public static OperateResult BatchesAreaQty(this List<TN_WM_B_AREA_QTYEntity> tAreaList, List<OutAreaResultEntity> outAreaResultList, List<string> lstStrate)
|
{
|
foreach (var item in outAreaResultList)
|
{
|
if (!item.lstItem.Any())
|
return OperateResult.Error("未找到库区:" + item.areaCode + "中的物料");
|
foreach (var s in item.lstItem)
|
{
|
var cia = tAreaList.Where(p =>
|
p.CN_S_ITEM_CODE.Equals(s.itemCode) &&
|
p.CN_S_ITEM_STATE.Equals(item.itemState) &&
|
p.CN_S_OWNER.Trim().Equals(item.ownerName.Trim()) &&
|
(string.IsNullOrEmpty(item.batchCode) || item.batchCode.Equals(p.CN_S_LOT_NO)) &&
|
(string.IsNullOrEmpty(item.prodBatchCode) || item.prodBatchCode.Equals(p.CN_S_PRODUCTION_BATCH)));
|
//暂时先倒序生产批次 then 到货批次 后续取策略
|
var ciaList = cia.OrderBy(p => p.CN_S_LOT_NO).ToList();
|
foreach (string stegy in lstStrate)
|
{
|
//逐个策略进行计算
|
switch (stegy)
|
{
|
case "FirstInFirstOut":
|
ciaList = ciaList.OrderBy(o => o.CN_S_LOT_NO).ToList();
|
break;
|
case "FirstWarrantFirstOut":
|
var lstEmptyPBC = ciaList.Where(o => string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).ToList();
|
var lstFilterItem = ciaList.Where(o => !string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).OrderBy(o => o.CN_S_PRODUCTION_BATCH).ToList();
|
lstFilterItem.AddRange(lstEmptyPBC);
|
ciaList = lstFilterItem;
|
break;
|
}
|
}
|
if (!ciaList.Any())
|
return OperateResult.Error("库区中未找到物料:" + s.itemCode);
|
//待批数
|
var qty = s.itemQty;
|
if (qty == 0) continue;
|
|
foreach (var area in ciaList)
|
{
|
//当前可用量
|
var currentCount = area.CN_F_QUANTITY + area.CN_F_PLANNED_QTY - area.CN_F_ALLOC_QTY;
|
if (currentCount <= 0) continue;
|
if (currentCount >= qty)
|
{
|
//area.CN_F_ALLOC_QTY += qty;
|
area.CN_F_QUANTITY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
//area.CN_F_ALLOC_QTY += currentCount;
|
area.CN_F_QUANTITY -= currentCount;
|
qty -= currentCount;
|
continue;
|
}
|
}
|
if (qty > 0)
|
{
|
return OperateResult.Error("物料批分失败:物料编码:" + s.itemCode + "在库区中库存量不足");
|
}
|
}
|
}
|
return OperateResult.Succeed();
|
}
|
#endregion
|
|
//public static OperateResult DropTrayItemQty(this List<BatchesEntity> beList)
|
//{
|
// var trayItemMstList = BLLCreator<DapperDAL<TN_WM_B_TRAY_ITEM_MSTEntity>>().GetList(new
|
// {
|
// CN_S_TRAY_CODE = sortingResult.Select(x => x.CN_S_TRAY_CODE).ToList(),
|
// CN_S_ITEM_CODE = sortingResult.Select(x => x.CN_S_ITEM_CODE).ToList()
|
// });
|
//}
|
|
#region 批分托盘物料分配量通用方法
|
/// <summary>
|
/// 批分托盘物料分配量通用方法
|
/// </summary>
|
/// <param name="trayItemList"></param>
|
/// <param name="beList"></param>
|
/// <param name="bType"></param>
|
/// <returns></returns>
|
public static OperateResult BatchesTrayItemQty(this List<TN_WM_B_TRAY_ITEM_MSTEntity> trayItemList, List<BatchesEntity> beList, BatchesType bType)
|
{
|
foreach (var b in beList)
|
{
|
//匹配托盘和物料编码
|
var cis = trayItemList.Where(p =>
|
p.CN_S_ITEM_CODE.Equals(b.ItemCode) &&
|
p.CN_S_TRAY_CODE.Equals(b.TrayCode));
|
if (!cis.Any())
|
return OperateResult.Error("批分失败,托盘:" + b.TrayCode + "中未找到物料:" + b.ItemCode);
|
|
var cisList = cis.OrderBy(p => p.CN_S_LOT_NO).ToList();
|
//待批数
|
var qty = b.Qty;
|
if (qty == 0) continue;
|
switch (bType)
|
{
|
#region 正批分
|
//正批分
|
case BatchesType.ForwardBatch:
|
for (int i = 0; i < cisList.Count; i++)
|
{
|
//可用量 = 库存量-分配量-编辑量
|
var availableQty = cisList[i].CN_F_QUANTITY - cisList[i].CN_F_ALLOC_QTY;
|
if (availableQty <= 0) continue;
|
if (availableQty >= qty)
|
{
|
cisList[i].CN_F_ALLOC_QTY += qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_ALLOC_QTY += availableQty;
|
qty -= availableQty;
|
continue;
|
}
|
}
|
break;
|
#endregion
|
|
#region 反批分
|
//反批分
|
case BatchesType.ReverseBatch:
|
for (int i = cisList.Count - 1; i >= 0; i--)
|
{
|
var allocQty = cisList[i].CN_F_ALLOC_QTY;
|
if (allocQty <= 0) continue;
|
if (allocQty >= qty)
|
{
|
cisList[i].CN_F_ALLOC_QTY -= qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
cisList[i].CN_F_ALLOC_QTY = 0;
|
qty -= allocQty;
|
continue;
|
}
|
}
|
break;
|
#endregion
|
}
|
#region 批分失败
|
//批分失败
|
if (qty > 0)
|
{
|
switch (bType)
|
{
|
case BatchesType.ForwardBatch:
|
return OperateResult.Error("批分失败,托盘:" + b.TrayCode + "中物料:" + b.ItemCode + "不足");
|
case BatchesType.ReverseBatch:
|
return OperateResult.Error("反批分失败,托盘:" + b.TrayCode + "中物料:" + b.ItemCode + "分配量不足");
|
}
|
}
|
#endregion
|
}
|
return OperateResult.Succeed();
|
}
|
#endregion
|
|
#region 获取锁定实体
|
/// <summary>
|
/// 获取锁定实体
|
/// </summary>
|
/// <param name="stockEntity">仓库实体</param>
|
/// <param name="opNo">锁定单据号</param>
|
/// <param name="lockNum">锁定数量</param>
|
/// <returns></returns>
|
private static TN_WM_B_OUTLOCKING_DTLEntity GetOutLockingDtlEntity(TN_WM_B_STOCK_QTYEntity stockEntity, string opNo, decimal lockNum)
|
{
|
return new TN_WM_B_OUTLOCKING_DTLEntity()
|
{
|
CN_GUID = Guid.NewGuid().ToString(),
|
CN_S_OP_FROM = Constants.Rule_OutOrder,
|
CN_S_FROM_NO = opNo,
|
CN_S_STOCK_CODE = stockEntity.CN_S_STOCK_CODE,
|
CN_S_OWNER = stockEntity.CN_S_OWNER.Trim(),
|
CN_S_ITEM_CODE = stockEntity.CN_S_ITEM_CODE,
|
CN_S_ITEM_NAME = stockEntity.CN_S_ITEM_NAME,
|
CN_S_ITEM_STATE = stockEntity.CN_S_ITEM_STATE,
|
CN_S_LOT_NO = stockEntity.CN_S_LOT_NO,
|
CN_F_QUANTITY = lockNum,
|
CN_T_LOCK = DateTime.Now,
|
CN_S_TABLE_ID = stockEntity.CN_GUID,
|
CN_S_TABLE_TYPE = "1",
|
CN_S_STATE = Constants.State_Executing,
|
CN_F_SUM_USE = 0,
|
CN_T_USE = DateTime.Now,
|
CN_F_SUM_CANCEL = 0,
|
CN_T_CANCEL = DateTime.Now,
|
};
|
}
|
private static TN_WM_B_OUTLOCKING_DTLEntity GetOutLockingDtlEntity(TN_WM_B_AREA_QTYEntity areaEntity, string opNo, decimal lockNum)
|
{
|
return new TN_WM_B_OUTLOCKING_DTLEntity()
|
{
|
CN_GUID = Guid.NewGuid().ToString(),
|
CN_S_OP_FROM = Constants.Rule_SortingNo,
|
CN_S_FROM_NO = opNo,
|
CN_S_STOCK_CODE = areaEntity.CN_S_STOCK_CODE,
|
CN_S_OWNER = areaEntity.CN_S_OWNER.Trim(),
|
CN_S_ITEM_CODE = areaEntity.CN_S_ITEM_CODE,
|
CN_S_ITEM_NAME = areaEntity.CN_S_ITEM_NAME,
|
CN_S_ITEM_STATE = areaEntity.CN_S_ITEM_STATE,
|
CN_S_LOT_NO = areaEntity.CN_S_LOT_NO,
|
CN_F_QUANTITY = lockNum,
|
CN_T_LOCK = DateTime.Now,
|
CN_S_TABLE_ID = areaEntity.CN_GUID,
|
CN_S_TABLE_TYPE = "2",
|
CN_S_STATE = Constants.State_Executing,
|
CN_F_SUM_USE = 0,
|
CN_T_USE = DateTime.Now,
|
CN_F_SUM_CANCEL = 0,
|
CN_T_CANCEL = DateTime.Now,
|
};
|
}
|
#endregion
|
|
#region 生成单号
|
/// <summary>
|
/// 生成单号
|
/// </summary>
|
/// <param name="user"></param>
|
/// <returns></returns>
|
public static string GenerateNo(this UserRuleEntity user)
|
{
|
//string postData = "{\"appCode\":\"" + Constants.appCode + "\",\"ruleName\":\"" + user.RuleCode + "\",\"orgId\":\"" + user.OrgCode + "\",\"orgFlag\":\"" + user.OrgFlag + "\"}";
|
var postData = new
|
{
|
appCode = "AutoWMS",
|
ruleName = user.RuleCode,
|
orgId = 0,
|
orgFlag = ""
|
};
|
|
return WebApiManager.HttpAutoBom_Post("api/BillRule/GenBillNo", JsonConvert.SerializeObject(postData));
|
}
|
#endregion
|
|
#region 批分分配量
|
/// <summary>
|
/// 批分分配量
|
/// </summary>
|
/// <param name="tStockList"></param>
|
/// <param name="beList"></param>
|
/// <param name="lstStrate"></param>
|
/// <returns></returns>
|
public static OperateResult BatchesStockAllocQty(this List<TN_WM_B_STOCK_QTYEntity> tStockList, List<BatchesEntity> beList, List<string> lstStrate)
|
{
|
try
|
{
|
//锁定明细(反批分分配量时记录)
|
BatchesResult bResult = new BatchesResult();
|
//记录有缺货的出库单
|
List<TN_WM_OUT_MSTEntity> shortageList = new List<TN_WM_OUT_MSTEntity>();
|
foreach (var b in beList)
|
{
|
//匹配五要素
|
var cis = tStockList.Where(p =>
|
p.CN_S_ITEM_CODE.Equals(b.ItemCode) &&
|
p.CN_S_ITEM_STATE.Equals(b.ItemState) &&
|
p.CN_S_OWNER.Trim().Equals(b.Owner.Trim()) &&
|
(string.IsNullOrEmpty(b.ItemArrivalLot) || b.ItemArrivalLot.Equals(p.CN_S_LOT_NO)) &&
|
(string.IsNullOrEmpty(b.ItemProductionLot) || b.ItemProductionLot.Equals(p.CN_S_PRODUCTION_BATCH)));
|
|
//暂时先倒序生产批次 then 到货批次 后续取策略
|
var cisList = cis.OrderBy(p => p.CN_S_LOT_NO).ToList();
|
|
foreach (string stegy in lstStrate)
|
{
|
//逐个策略进行计算
|
switch (stegy)
|
{
|
case "FirstInFirstOut":
|
cisList = cisList.OrderBy(o => o.CN_S_LOT_NO).ToList();
|
break;
|
case "FirstWarrantFirstOut":
|
var lstEmptyPBC = cisList.Where(o => string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).ToList();
|
var lstFilterItem = cisList.Where(o => !string.IsNullOrEmpty(o.CN_S_PRODUCTION_BATCH)).OrderBy(o => o.CN_S_PRODUCTION_BATCH).ToList();
|
lstFilterItem.AddRange(lstEmptyPBC);
|
cisList = lstFilterItem;
|
break;
|
}
|
}
|
|
if (!cisList.Any())
|
return OperateResult.Error("库存中未找到物料:" + b.ItemCode);
|
//待批数
|
var qty = b.Qty;
|
if (qty == 0) continue;
|
|
for (int i = 0; i < cisList.Count; i++)
|
{
|
//可用量 = 库存量-分配量
|
var availableQty = cisList[i].CN_F_QUANTITY - cisList[i].CN_F_ALLOC_QTY;// -cisList[i].CN_F_EDITING_QTY;
|
if (availableQty <= 0) continue;
|
|
if (availableQty >= qty)
|
{
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, qty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
cisList[i].CN_F_ALLOC_QTY += qty;
|
qty = 0;
|
break;
|
}
|
else
|
{
|
//记录锁定明细
|
var outLockingDtlEntity = GetOutLockingDtlEntity(cisList[i], b.OpNo, availableQty);
|
bResult.OutLockingDtlList.Add(outLockingDtlEntity);
|
cisList[i].CN_F_ALLOC_QTY += availableQty;
|
qty -= availableQty;
|
continue;
|
}
|
}
|
|
#region 批分失败
|
|
if (qty > 0)
|
{
|
Log.Info("存在缺货", "出库单:" + b.OpNo + ",缺货物料:" + b.ItemCode + ",缺货数量:" + qty);
|
//如果没记录过该出库单的缺货记录
|
if (!shortageList.Exists(e => e.CN_S_OP_NO.Equals(b.OpNo)))
|
{
|
shortageList.Add(b.OutMst);
|
}
|
var shortageOutEntity = shortageList.Find(f => f.CN_S_OP_NO == b.OpNo)
|
.OutDtlList.Find(x =>
|
x.CN_S_ITEM_CODE == b.ItemCode &&
|
x.CN_S_ITEM_STATE == b.ItemState &&
|
x.CN_S_OWNER.Trim() == b.Owner.Trim() &&
|
x.CN_S_LOT_CODE == b.ItemArrivalLot &&
|
x.CN_S_PRODUCTION_BATCH == b.ItemProductionLot);
|
|
Log.Info("存在缺货 shortageOutEntity", JsonConvert.SerializeObject(shortageOutEntity));
|
if (shortageOutEntity != null)
|
{
|
//缺货数量
|
shortageOutEntity.CN_F_SHORTAGE_QTY = qty;
|
}
|
}
|
|
#endregion
|
}
|
|
#region 生成出库单
|
//若是反批分分配量,生成出库单信息
|
IEnumerable<IGrouping<string, BatchesEntity>> beGroup = beList.GroupBy(p => p.OpNo);
|
foreach (var be in beGroup)
|
{
|
//出库订单
|
TN_WM_OUT_MSTEntity outMst = be.AsEnumerable().ToList()[0].OutMst;
|
//当前出库单是否缺货的物料,
|
var shortageCurrentOut = shortageList.Find(f => f.CN_S_OP_NO.Equals(be.Key));
|
//存在缺货的物料 & 生成新的发货单
|
if (shortageCurrentOut != null)
|
{
|
outMst.OutDtlList = shortageCurrentOut.OutDtlList.FindAll(p => p.CN_F_SHORTAGE_QTY < p.CN_F_QUANTITY);
|
//GenerateNewOut(outMst, true);
|
//全部缺货
|
if (!outMst.OutDtlList.Any())
|
{
|
//当前出库单为全部缺货
|
shortageCurrentOut.CN_S_STATE = Constants.State_Shortage;
|
continue;
|
}
|
else
|
{
|
//当前出库单设为取消
|
shortageCurrentOut.CN_B_CANCEL = true;
|
//缺货的部分物料生成新的发货单
|
var shortageNewOut = GenerateNewOut(outMst, true);
|
//不缺货的部分物料生成新的发货单
|
outMst = GenerateNewOut(outMst, false);
|
//记录新增的有缺货的发货单
|
bResult.ShortageOutList.Add(shortageNewOut);
|
bResult.ShortageOutList.Add(outMst);
|
//锁定明细改为新的发货单来锁
|
var oldOutLocking = bResult.OutLockingDtlList.FindAll(x => x.CN_S_FROM_NO == shortageCurrentOut.CN_S_OP_NO);
|
if (oldOutLocking.Any())
|
{
|
oldOutLocking.ForEach(f =>
|
{
|
f.CN_S_FROM_NO = outMst.CN_S_OP_NO;
|
});
|
}
|
}
|
//记录之前的出库单,取消 || 全部缺货
|
bResult.CancelAndAllShortageOut.Add(shortageCurrentOut);
|
}
|
|
//修改出库订单主子表为‘待出库’
|
outMst.CN_S_STATE = Constants.State_WaitOut;
|
|
outMst.OutDtlList.ForEach(p =>
|
{
|
p.CN_S_STATE = outMst.CN_S_STATE;
|
});
|
//if (outMst.CN_S_OUT_TYPE != Constants.OutType_BigOut)
|
//{
|
// var bigOut = GenerateBigOut(ref outMst, bResult.CancelAndAllShortageOut);
|
// if (bigOut != null)
|
// {
|
// //锁定明细把大件的排除掉
|
// var bigItems = bResult.OutLockingDtlList.FindAll(f => f.CN_S_FROM_NO == bigOut.CN_S_SPLIT_FROM && bigOut.OutDtlList.Exists(e => e.CN_S_ITEM_CODE == f.CN_S_ITEM_CODE));
|
// bigItems.ForEach(e =>
|
// {
|
// bResult.OutLockingDtlList.Remove(e);
|
// });
|
// bResult.ShortageOutList.Add(bigOut);
|
// //如果当前单据也是拆分的单据,并且全部是大件时
|
// if (!string.IsNullOrEmpty(outMst.CN_S_SPLIT_FROM) && !outMst.OutDtlList.Any())
|
// {
|
// var exists = bResult.ShortageOutList.Find(f => f.CN_S_OP_NO == outMst.CN_S_OP_NO);
|
// if (exists != null)
|
// bResult.ShortageOutList.Remove(exists);
|
// }
|
// }
|
//}
|
if (outMst.OutDtlList.Any())
|
{
|
bResult.OutMstList.Add(outMst);
|
// 生成出库单号
|
UserRuleEntity user = new UserRuleEntity()
|
{
|
OrgCode = "0",
|
RuleCode = Constants.Rule_Out
|
};
|
string outNo = user.GenerateNo();
|
|
//发货单子表明细
|
if (outMst.OutDtlList.Any())
|
{
|
//减少库存业务主表
|
var reduceInventoryMstEntity = GetReduceInventoryMst(outNo, Constants.Rule_OutOrder, outMst);
|
//减少库存业务子表
|
reduceInventoryMstEntity.DTLEntity.AddRange(GetReduceInventoryDtl(outNo, outMst.OutDtlList));
|
//记录减少库存业务表
|
bResult.ReduceInventoryMstList.Add(reduceInventoryMstEntity);
|
}
|
}
|
}
|
#endregion
|
|
return OperateResult.Succeed(null, bResult);
|
}
|
catch (Exception ex)
|
{
|
return OperateResult.Error(ex.Message);
|
}
|
}
|
#endregion
|
|
private static TN_WM_OUT_MSTEntity GenerateBigOut(ref TN_WM_OUT_MSTEntity currentOutEntity, List<TN_WM_OUT_MSTEntity> cancelList)
|
{
|
List<TN_WM_OUT_DTLEntity> bigDtlList = currentOutEntity.OutDtlList.FindAll(a => a.CN_C_BIG);
|
if (bigDtlList.Any())
|
{
|
TN_WM_OUT_MSTEntity newOutMst = currentOutEntity.Clone() as TN_WM_OUT_MSTEntity;
|
newOutMst.CN_GUID = Guid.NewGuid().ToString();
|
string postData = "{\"appCode\":\"" + Constants.appCode + "\",\"ruleName\":\"" + Constants.Rule_OutOrder + "\",\"orgId\":\"0\",\"orgFlag\":\"\"}";
|
string orderNo = WebApiManager.HttpAutoBom_Post("api/BillRule/GenBillNo", postData);
|
newOutMst.CN_S_OP_NO = orderNo;
|
newOutMst.CN_S_STATE = Constants.State_Audit;
|
bigDtlList.ForEach(e =>
|
{
|
e.CN_GUID = Guid.NewGuid().ToString();
|
e.CN_S_OP_NO = orderNo;
|
e.CN_S_ITEM_STATE = newOutMst.CN_S_STATE;
|
e.CN_S_MSTGUID = newOutMst.CN_GUID;
|
});
|
newOutMst.CN_S_SPLIT_FROM = currentOutEntity.CN_S_OP_NO;
|
newOutMst.CN_S_OUT_TYPE = Constants.OutType_BigOut;
|
newOutMst.OutDtlList = bigDtlList;
|
if (!string.IsNullOrEmpty(currentOutEntity.CN_S_SPLIT_FROM))
|
{
|
//之前单据是拆分后的单据
|
newOutMst.CN_S_SPLIT_FROM = currentOutEntity.CN_S_SPLIT_FROM;
|
}
|
else
|
{
|
//取消之前的单据
|
currentOutEntity.CN_B_CANCEL = true;
|
cancelList.Add(currentOutEntity);
|
}
|
//全部都是大件
|
if (currentOutEntity.OutDtlList.Sum(s => s.CN_F_QUANTITY) == bigDtlList.Sum(s => s.CN_F_QUANTITY))
|
{
|
currentOutEntity.OutDtlList = new List<TN_WM_OUT_DTLEntity>();
|
}
|
else
|
{
|
//非大件
|
currentOutEntity.OutDtlList = currentOutEntity.OutDtlList.FindAll(a => !a.CN_C_BIG);
|
}
|
return newOutMst;
|
}
|
return null;
|
}
|
|
#region 取消当前发货单,返回新的发货单
|
/// <summary>
|
/// 取消当前发货单,返回新的发货单
|
/// </summary>
|
/// <param name="oldOutEntity"></param>
|
/// <param name="isShortage">是否返回缺货的</param>
|
/// <returns></returns>
|
private static TN_WM_OUT_MSTEntity GenerateNewOut(TN_WM_OUT_MSTEntity oldOutEntity, bool isShortage)
|
{
|
TN_WM_OUT_MSTEntity newOutMst = oldOutEntity.Clone() as TN_WM_OUT_MSTEntity;
|
newOutMst.CN_GUID = Guid.NewGuid().ToString();
|
string postData = "{\"appCode\":\"" + Constants.appCode + "\",\"ruleName\":\"" + Constants.Rule_OutOrder + "\",\"orgId\":\"0\",\"orgFlag\":\"\"}";
|
string orderNo = WebApiManager.HttpAutoBom_Post("api/BillRule/GenBillNo", postData);
|
newOutMst.CN_S_OP_NO = orderNo;
|
newOutMst.CN_S_SPLIT_FROM = oldOutEntity.CN_S_OP_NO;
|
newOutMst.CN_S_STATE = isShortage ? Constants.State_Shortage : Constants.State_WaitOut;
|
newOutMst.OutDtlList = new List<TN_WM_OUT_DTLEntity>();
|
foreach (var dtl in oldOutEntity.OutDtlList)
|
{
|
TN_WM_OUT_DTLEntity newOutDtl = dtl.Clone() as TN_WM_OUT_DTLEntity;
|
newOutDtl.CN_GUID = Guid.NewGuid().ToString();
|
newOutDtl.CN_S_OP_NO = newOutMst.CN_S_OP_NO;
|
newOutDtl.CN_S_MSTGUID = newOutMst.CN_GUID;
|
newOutDtl.CN_S_STATE = newOutMst.CN_S_STATE;
|
//生成缺货的明细
|
if (isShortage)
|
{
|
if (dtl.CN_F_SHORTAGE_QTY > 0)
|
{
|
|
newOutMst.CN_S_OUT_TYPE = Constants.OutType_ShortageOut;
|
newOutDtl.CN_F_QUANTITY = dtl.CN_F_SHORTAGE_QTY;
|
}
|
}
|
else//生成新的明细
|
{
|
if (dtl.CN_F_QUANTITY > dtl.CN_F_SHORTAGE_QTY)
|
{
|
newOutDtl.CN_F_QUANTITY = dtl.CN_F_QUANTITY - dtl.CN_F_SHORTAGE_QTY;
|
}
|
}
|
if (newOutDtl.CN_F_QUANTITY > 0)
|
newOutMst.OutDtlList.Add(newOutDtl);
|
}
|
return newOutMst;
|
}
|
#endregion
|
}
|
}
|