using AutoMapper;
|
using Dm.filter;
|
using MailKit.Search;
|
using Mapster;
|
using Microsoft.IdentityModel.Tokens;
|
using Newtonsoft.Json;
|
using Newtonsoft.Json.Serialization;
|
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
|
using Org.BouncyCastle.Asn1.Ocsp;
|
using Org.BouncyCastle.Crypto;
|
using SqlSugar;
|
using System;
|
using System.Reflection.Emit;
|
using WIDESEA_BasicService;
|
using WIDESEA_Common.CommonEnum;
|
using WIDESEA_Common.LocationEnum;
|
using WIDESEA_Common.OrderEnum;
|
using WIDESEA_Common.StockEnum;
|
using WIDESEA_Common.TaskEnum;
|
using WIDESEA_Core;
|
using WIDESEA_Core.BaseRepository;
|
using WIDESEA_Core.CodeConfigEnum;
|
using WIDESEA_Core.Helper;
|
using WIDESEA_DTO.Base;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.CalcOut;
|
using WIDESEA_DTO.Outbound;
|
using WIDESEA_DTO.ReturnMES;
|
using WIDESEA_IBasicService;
|
using WIDESEA_IOutboundService;
|
using WIDESEA_IRecordService;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
using WIDESEA_Model.Models.Basic;
|
using WIDESEA_Model.Models.Check;
|
using static HslCommunication.Profinet.Knx.KnxCode;
|
|
namespace WIDESEA_OutboundService
|
{
|
public partial class OutboundService : IOutboundService
|
{
|
private readonly IMapper _mapper;
|
public IUnitOfWorkManage _unitOfWorkManage { get; }
|
|
public IOutboundOrderDetailService OutboundOrderDetailService { get; }
|
|
public IOutboundOrderService OutboundOrderService { get; }
|
|
public IOutStockLockInfoService OutboundStockLockInfoService { get; }
|
|
private readonly ISqlSugarClient Db;
|
private readonly IBasicService _basicService;
|
|
|
private readonly IRepository<Dt_OutboundOrderDetail> _detailRepository;
|
private readonly IRepository<Dt_OutboundOrder> _outboundRepository;
|
private readonly IRepository<Dt_OutStockLockInfo> _outboundLockInfoRepository;
|
private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
|
private readonly IRepository<Dt_StockInfoDetail> _stockDetailRepository;
|
private readonly IRepository<Dt_LocationInfo> _locationInfoRepository;
|
private readonly IRepository<Dt_StockQuantityChangeRecord> _stockChangeRepository;
|
private readonly IRepository<Dt_StockInfoDetail_Hty> _stockDetailHistoryRepository;
|
private readonly IFeedbackMesService _feedbackMesService;
|
private readonly IRepository<Dt_Task> _taskRepository;
|
private readonly ILocationInfoService _locationInfoService;
|
private readonly IESSApiService _eSSApiService;
|
private readonly IRepository<Dt_AllocateOrder> _allocateOrderRepository;
|
private readonly IRepository<Dt_AllocateMaterialInfo> _allocateMaterialInfoRepository;
|
public readonly IRepository<Dt_InboundOrderDetail> _inboundOrderDetailRepository;
|
public readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
|
public readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
|
public readonly IRepository<Dt_LocationType> _locationTypeRepository;
|
|
private Dictionary<string, string> stations = new Dictionary<string, string>
|
{
|
{"2-1","2-9" },
|
{"3-1","3-9" },
|
};
|
|
private Dictionary<string, string> movestations = new Dictionary<string, string>
|
{
|
{"2-1","2-5" },
|
{"3-1","3-5" },
|
};
|
|
public OutboundService(IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_OutboundOrderDetail> detailRepository, IRepository<Dt_OutboundOrder> outboundRepository, IRepository<Dt_OutStockLockInfo> outboundLockInfoRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_StockInfoDetail> stockDetailRepository, IRepository<Dt_StockQuantityChangeRecord> stockChangeRepository, IRepository<Dt_StockInfoDetail_Hty> stockDetailHistoryRepository, IBasicService basicService, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IFeedbackMesService feedbackMesService, IRepository<Dt_Task> taskRepository, ILocationInfoService locationInfoService, IESSApiService eSSApiService, IRepository<Dt_AllocateOrder> allocateOrderRepository, IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_LocationType> locationTypeRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository)
|
{
|
_mapper = mapper;
|
_unitOfWorkManage = unitOfWorkManage;
|
Db = _unitOfWorkManage.GetDbClient();
|
OutboundOrderDetailService = outboundOrderDetailService;
|
OutboundOrderService = outboundOrderService;
|
OutboundStockLockInfoService = outboundStockLockInfoService;
|
_detailRepository = detailRepository;
|
_outboundRepository = outboundRepository;
|
_outboundLockInfoRepository = outboundLockInfoRepository;
|
_stockInfoRepository = stockInfoRepository;
|
_stockDetailRepository = stockDetailRepository;
|
_locationInfoRepository = basicService.LocationInfoService.Repository;
|
_stockChangeRepository = stockChangeRepository;
|
_stockDetailHistoryRepository = stockDetailHistoryRepository;
|
_basicService = basicService;
|
_feedbackMesService = feedbackMesService;
|
_taskRepository = taskRepository;
|
_locationInfoService = locationInfoService;
|
_eSSApiService = eSSApiService;
|
_allocateOrderRepository = allocateOrderRepository;
|
_allocateMaterialInfoRepository = allocateMaterialInfoRepository;
|
_inboundOrderDetailRepository = inboundOrderDetailRepository;
|
_inboundOrderRepository = inboundOrderRepository;
|
_locationTypeRepository = locationTypeRepository;
|
_warehouseAreaRepository = warehouseAreaRepository;
|
}
|
|
public WebResponseContent PrintFromData (string barcode)
|
{
|
var detail = _inboundOrderDetailRepository.QueryFirst(x => x.Barcode == barcode);
|
if(detail == null)
|
{
|
return WebResponseContent.Instance.Error();
|
}
|
|
var inbound = _inboundOrderRepository.QueryFirst(x=>x.Id == detail.OrderId);
|
if(inbound == null)
|
{
|
return WebResponseContent.Instance.Error();
|
}
|
var printFormData = new PrintFromDataDTO {
|
materialCode = detail.MaterielCode,
|
materialName = detail.MaterielName,
|
materialSpec = detail.Unit,
|
batchNo = detail.BatchNo,
|
pruchaseOrderNo = inbound.UpperOrderNo,
|
factoryArea = inbound.FactoryArea,
|
suplierCode = detail.SupplyCode,
|
quantity = detail.BarcodeQty
|
};
|
|
return WebResponseContent.Instance.OK(data:printFormData);
|
}
|
|
#region 出库分配
|
/// <summary>
|
/// 分拣出库操作
|
/// </summary>
|
/// <param name="request">分拣出库请求</param>
|
/// <returns>分拣出库响应</returns>
|
public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
|
{
|
WebResponseContent content = WebResponseContent.Instance;
|
PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
|
decimal totalNeedAllocate = 0; // 总需求分配量
|
decimal totalActualAllocate = 0; // 实际总分配量
|
string targetWarehouse = string.Empty;// 目标仓库
|
string targetLocationCode = string.Empty; // 目标货位
|
bool isWholeCaseOutbound = false; // 是否整箱出库
|
List<string> wholeCasePallets = new List<string>();
|
Dictionary<string, string> palletLocationMap = new Dictionary<string, string>();
|
Dictionary<string, bool> palletIsWholeCaseMap = new Dictionary<string, bool>();
|
int? targetLocationType = null;
|
|
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
// 1. 计算出库数量逻辑
|
OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request);
|
if (!calculationResult.CanOutbound)
|
{
|
content = WebResponseContent.Instance.Error("无法处理拣货出库:" + calculationResult.ErrorMessage);
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
// 记录总需求数量
|
totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity);
|
|
|
// 2. 处理物料分配
|
List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
|
Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder;
|
List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
|
List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
|
foreach (var materielCalc in calculationResult.MaterielCalculations)
|
{
|
var materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
|
// 计算当前物料实际分配量
|
decimal actualAllocatedQuantity = materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity);
|
actualAllocatedQuantity = Math.Min(actualAllocatedQuantity, materielCalc.UnallocatedQuantity);
|
totalActualAllocate += actualAllocatedQuantity; // 累加至总实际分配量
|
materielCalc.UnallocatedQuantity = materielCalc.UnallocatedQuantity - actualAllocatedQuantity;
|
|
// 处理出库锁定记录
|
foreach (var item in materielPickedDetails.OutStockLockInfo)
|
{
|
Dt_OutStockLockInfo? existLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
|
if (existLockInfo != null)
|
{
|
existLockInfo = item;
|
Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
|
if (task != null) existLockInfo.TaskNum = task.TaskNum;
|
}
|
else
|
{
|
Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
|
if (task != null) item.TaskNum = task.TaskNum;
|
materielCalc.OutStockLockInfos.Add(item);
|
}
|
outStockLockInfos.Add(item);
|
|
if (outboundOrder.OrderType == 117)
|
{
|
// 匹配当前单据的锁定记录
|
if (outboundOrder.OrderNo == item.OrderNo)
|
{
|
// 查询库存信息
|
var stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == item.PalletCode);
|
if (stockInfo == null)
|
{
|
content = WebResponseContent.Instance.Error($"托盘{item.PalletCode}未查询到库存信息,无法处理整箱出库");
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
|
// 计算库存总量,判断是否整箱(增加0值保护)
|
decimal stockQuantity = _stockDetailRepository.QueryData(x => x.StockId == stockInfo.Id).Sum(x => x.StockQuantity);
|
if (stockQuantity > 0 && stockQuantity == item.AssignQuantity)
|
{
|
// 标记当前托盘为整箱(核心修复:按托盘维度记录状态)
|
if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode))
|
{
|
palletIsWholeCaseMap.Add(item.PalletCode, true);
|
}
|
else
|
{
|
palletIsWholeCaseMap[item.PalletCode] = true;
|
}
|
|
// 目标仓库/货位类型只查询一次(性能优化+空值保护)
|
if (string.IsNullOrEmpty(targetWarehouse))
|
{
|
targetWarehouse = GetToWarehouseByOrderNo(request.OrderNo);
|
if (string.IsNullOrEmpty(targetWarehouse))
|
{
|
content = WebResponseContent.Instance.Error("智仓调智仓整箱出库单据未配置目标仓库");
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
|
// 替换First()为FirstOrDefault(),避免空值异常
|
string warehouseAreaName = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>()
|
.Where(x => x.Code == targetWarehouse)
|
.Select(x => x.Name)
|
.First();
|
if (string.IsNullOrEmpty(warehouseAreaName))
|
{
|
content = WebResponseContent.Instance.Error($"目标仓库{targetWarehouse}未查询到对应的库区名称");
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
|
int? locationType = _locationTypeRepository.Db.Queryable<Dt_LocationType>()
|
.Where(x => string.Equals(x.LocationTypeDesc, warehouseAreaName, StringComparison.OrdinalIgnoreCase))
|
.Select(x => x.LocationType)
|
.First();
|
if (!locationType.HasValue)
|
{
|
content = WebResponseContent.Instance.Error($"库区{warehouseAreaName}未匹配到对应的货位类型");
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
targetLocationType = locationType.Value;
|
}
|
|
// 分配目标货位(每个托盘独立货位)
|
if (!palletLocationMap.ContainsKey(item.PalletCode) && targetLocationType.HasValue)
|
{
|
Dt_LocationInfo locationInfo = _locationInfoService.AssignLocation(targetLocationType.Value);
|
if (locationInfo == null || string.IsNullOrEmpty(locationInfo.LocationCode))
|
{
|
content = WebResponseContent.Instance.Error($"货位类型{targetLocationType.Value}未分配到可用货位(托盘:{item.PalletCode})");
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
palletLocationMap.Add(item.PalletCode, locationInfo.LocationCode);
|
}
|
|
// 加入整箱托盘列表
|
if (!wholeCasePallets.Contains(item.PalletCode))
|
{
|
wholeCasePallets.Add(item.PalletCode);
|
}
|
}
|
else
|
{
|
if (!palletIsWholeCaseMap.ContainsKey(item.PalletCode))
|
{
|
palletIsWholeCaseMap.Add(item.PalletCode, false);
|
}
|
else
|
{
|
palletIsWholeCaseMap[item.PalletCode] = false;
|
}
|
}
|
}
|
}
|
}
|
|
// 处理任务
|
foreach (var item in materielPickedDetails.Tasks)
|
{
|
if (outboundOrder.OrderType == 117
|
&& palletIsWholeCaseMap.ContainsKey(item.PalletCode)
|
&& palletIsWholeCaseMap[item.PalletCode]
|
&& palletLocationMap.ContainsKey(item.PalletCode))
|
{
|
item.TaskType = (int)TaskTypeEnum.Relocation;
|
item.TargetAddress = palletLocationMap[item.PalletCode];
|
}
|
if (tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode) == null)
|
tasks.Add(item);
|
}
|
|
// 汇总分拣明细
|
pickedDetails.AddRange(materielPickedDetails.PickedDetails);
|
|
// 按实际分配量更新单据锁定数量
|
decimal remainingToLock = actualAllocatedQuantity;
|
foreach (var detail in materielCalc.Details)
|
{
|
if (remainingToLock <= 0) break;
|
decimal maxLockableQty = 0;
|
if (detail.LockQuantity > detail.OverOutQuantity && detail.OverOutQuantity > 0)
|
{
|
maxLockableQty = detail.OrderQuantity - detail.LockQuantity;
|
}
|
else if(detail.OverOutQuantity > 0)
|
{
|
maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity;
|
}
|
else
|
{
|
maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
|
}
|
if (maxLockableQty <= 0) continue;
|
decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty);
|
detail.LockQuantity += currentLockQty;
|
outboundOrderDetails.Add(detail);
|
remainingToLock -= currentLockQty;
|
}
|
}
|
|
// 3. 批量更新状态(原有逻辑不变)
|
UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
|
_detailRepository.UpdateData(outboundOrderDetails);
|
if (pickedDetails.Any())
|
{
|
UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
|
UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt());
|
}
|
//重检单不拣选,去掉锁定记录回库,再次组盘时扣除原条码
|
if (outboundOrder.OrderType != InOrderTypeEnum.ReCheck.ObjToInt())
|
{
|
UpdateOutStockLockInfo(outStockLockInfos);
|
}
|
|
if (tasks.Any()) _taskRepository.AddData(tasks);
|
|
if (outboundOrder.OrderType == 117 && wholeCasePallets.Any())
|
{
|
foreach (var palletCode in wholeCasePallets)
|
{
|
var completeReq = new OutboundCompletePalletRequestDTO
|
{
|
OrderNo = request.OrderNo,
|
PalletCode = palletCode
|
};
|
|
var res = CompleteOutboundWithPallet(completeReq);
|
if (!res.Status)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return res;
|
}
|
}
|
}
|
|
_unitOfWorkManage.CommitTran();
|
|
// 4. 构造响应:区分「全部分配」和「部分分配」
|
string responseMsg = totalActualAllocate == totalNeedAllocate
|
? "分拣任务分配成功"
|
: $"分拣任务分配完成(实际分配{totalActualAllocate},需求{totalNeedAllocate},库存不足部分未分配)";
|
Dt_OutboundOrder outboundOrder1 = _outboundRepository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderNo == request.OrderNo).Includes(x=>x.Details).First();
|
if(totalActualAllocate == 0 && !outboundOrder1.Details.Any(x=>x.LockQuantity >0))
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.未开始);
|
return WebResponseContent.Instance.Error("分配库存数量为0,无法出库");
|
}
|
response.Success = true;
|
response.Message = responseMsg;
|
response.Tasks = tasks;
|
response.PickedDetails = pickedDetails;
|
content = WebResponseContent.Instance.OK(responseMsg, response);
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message);
|
}
|
return content;
|
}
|
|
|
/// <summary>
|
/// 根据单据号获取目标仓库
|
/// </summary>
|
/// <param name="orderNo"></param>
|
/// <returns></returns>
|
public String GetToWarehouseByOrderNo(string orderNo)
|
{
|
var order =_allocateOrderRepository.QueryFirst(x => x.OrderNo == orderNo);
|
return order.ToWarehouse;
|
}
|
|
/// <summary>
|
/// 计算出库数量逻辑(原有逻辑不变)
|
/// </summary>
|
public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
|
{
|
OutboundCalculationDTO result = new OutboundCalculationDTO();
|
try
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
|
if (outboundOrder == null)
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = $"出库单 {request.OrderNo} 不存在";
|
return result;
|
}
|
|
result.FactoryArea = outboundOrder.FactoryArea;
|
List<Dt_OutboundOrderDetail> selectedDetails = new List<Dt_OutboundOrderDetail>();
|
if (request.DetailIds == null || !request.DetailIds.Any())
|
{
|
selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
|
}
|
else
|
{
|
selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
|
}
|
|
if (!selectedDetails.Any())
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = $"未找到选择的出库明细信息";
|
return result;
|
}
|
if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty))
|
{
|
List<int> selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList();
|
result.CanOutbound = false;
|
result.ErrorMessage = $"出库明细信息{string.Join(",", selectDetailIds)}已分配完成";
|
return result;
|
}
|
|
outboundOrder.Details = selectedDetails;
|
result.OutboundOrder = outboundOrder;
|
result.SelectedDetails = selectedDetails;
|
|
if (outboundOrder.IsBatch == 0 || request.DetailIds.Count != 1)
|
{
|
result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList());
|
}
|
else
|
{
|
if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0)
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = "单明细出库时必须指定出库数量且大于0";
|
return result;
|
}
|
|
decimal lockQuantity = selectedDetails.Sum(x => x.LockQuantity);
|
decimal orderQuantity = selectedDetails.Sum(x => x.OrderQuantity);
|
decimal moveQuantity = selectedDetails.Sum(x => x.MoveQty);
|
decimal overQuantity = selectedDetails.Sum(x => x.OverOutQuantity);
|
Dt_OutboundOrderDetail? singleDetail = selectedDetails.First();
|
|
if (orderQuantity - lockQuantity - moveQuantity < request.OutboundQuantity.Value || orderQuantity - overQuantity - moveQuantity < request.OutboundQuantity.Value)
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = $"本次出库数量 {request.OutboundQuantity.Value} 超过可出库数量 {orderQuantity - lockQuantity - moveQuantity}";
|
return result;
|
}
|
|
decimal inputQuantity = request.OutboundQuantity.Value;
|
List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
|
foreach (var item in selectedDetails)
|
{
|
inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity);
|
outboundOrderDetails.Add(item);
|
if (inputQuantity <= 0) break;
|
}
|
|
result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
|
{
|
new MaterielOutboundCalculationDTO
|
{
|
MaterielCode = singleDetail.MaterielCode,
|
MaterielName = singleDetail.MaterielName,
|
BatchNo = singleDetail.BatchNo,
|
SupplyCode = singleDetail.SupplyCode,
|
WarehouseCode = singleDetail.WarehouseCode,
|
TotalOrderQuantity = orderQuantity - moveQuantity,
|
TotalOverOutQuantity = overQuantity,
|
AssignedQuantity = lockQuantity,
|
UnallocatedQuantity = request.OutboundQuantity.Value,
|
MovedQuantity = moveQuantity,
|
Details = outboundOrderDetails
|
}
|
};
|
outboundOrder.Details = outboundOrderDetails;
|
}
|
result.CanOutbound = true;
|
}
|
catch (Exception ex)
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = ex.Message;
|
}
|
return result;
|
}
|
|
/// <summary>
|
/// 按物料分组计算出库数量(原有逻辑不变)
|
/// </summary>
|
private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails)
|
{
|
return selectedDetails
|
.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.BatchNo,
|
x.SupplyCode,
|
x.WarehouseCode
|
})
|
.Select(g => new MaterielOutboundCalculationDTO
|
{
|
MaterielCode = g.Key.MaterielCode,
|
BatchNo = g.Key.BatchNo,
|
SupplyCode = g.Key.SupplyCode,
|
WarehouseCode = g.Key.WarehouseCode,
|
TotalOrderQuantity = g.Sum(x => x.OrderQuantity - x.MoveQty),
|
TotalOverOutQuantity = g.Sum(x => x.OverOutQuantity),
|
AssignedQuantity = g.Sum(x => x.LockQuantity),
|
UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty),
|
MovedQuantity = g.Sum(x => x.MoveQty),
|
Details = g.ToList(),
|
OutStockLockInfos = _outboundLockInfoRepository.QueryData(x =>
|
x.MaterielCode == g.Key.MaterielCode &&
|
x.BatchNo == g.Key.BatchNo &&
|
x.OrderType == (int)outboundOrder.OrderType &&
|
x.OrderNo == outboundOrder.OrderNo)
|
})
|
.ToList();
|
}
|
|
/// <summary>
|
/// 处理物料任务生成(核心:有多少分多少+移除库存不足异常)
|
/// </summary>
|
private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(
|
Dt_OutboundOrder outboundOrder,
|
MaterielOutboundCalculationDTO materielCalc,
|
PickingOutboundRequestDTO request,
|
string factoryArea)
|
{
|
List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
|
List<Dt_Task> generatedTasks = new List<Dt_Task>(); // 初始空任务列表
|
List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
|
decimal remainingQuantity = materielCalc.UnallocatedQuantity;
|
|
// 1. 优先处理指定库存明细
|
if (request.StockDetailIds?.Any() == true)
|
{
|
var specifiedResult = AllocateSpecifiedStockDetails(outboundOrder, materielCalc, request, factoryArea, remainingQuantity);
|
pickedDetails.AddRange(specifiedResult.PickedDetails);
|
lockInfoList.AddRange(specifiedResult.LockInfoList);
|
remainingQuantity -= specifiedResult.ActualAllocatedQuantity;
|
|
// ===== 核心修复:指定库存分配后,立即生成任务(无论剩余量多少)=====
|
if (specifiedResult.PickedDetails.Any()) // 有指定库存分配结果就生成任务
|
{
|
var specifiedTasks = GenerateTasksForSpecifiedStock(specifiedResult.PickedDetails, request.OutboundTargetLocation);
|
generatedTasks.AddRange(specifiedTasks); // 添加指定库存任务到总任务列表
|
}
|
|
// 剩余量<=0时,直接返回(已生成指定库存任务)
|
if (remainingQuantity <= 0)
|
{
|
return (pickedDetails, generatedTasks, lockInfoList);
|
}
|
}
|
|
// 2. 处理剩余数量的自动分配(原有逻辑不变,自动分配的任务会追加到generatedTasks)
|
List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
|
var allocatedPalletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
|
stockQuery = stockQuery.Where(x => !allocatedPalletCodes.Contains(x.PalletCode)).ToList();
|
|
var stockData = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
|
decimal totalAutoAvailable = stockData.AvailableStockMap.Values.Sum();
|
decimal autoAllocateQuantity = Math.Min(remainingQuantity, totalAutoAvailable);
|
remainingQuantity -= autoAllocateQuantity;
|
|
if (autoAllocateQuantity > 0 && stockQuery.Any())
|
{
|
Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First();
|
Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details);
|
Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>();
|
|
foreach (var stock in stockQuery)
|
{
|
if (autoAllocateQuantity <= 0) break;
|
decimal availableQuantity = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
|
if (availableQuantity <= 0) continue;
|
|
// 自动分配时查询托盘总库存(原有优化逻辑不变)
|
decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
|
x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
|
).Sum(x => x.StockQuantity);
|
|
decimal allocateQuantity = Math.Min(autoAllocateQuantity, availableQuantity);
|
var actualAllocated = AllocateStockQuantity(
|
stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail,
|
request, stockData.LockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()),
|
stockDetailMap, palletMaterielTotalStock);
|
|
if (actualAllocated.ActualAllocatedQuantity > 0)
|
{
|
palletAllocations[stock.PalletCode] = actualAllocated.ActualAllocatedQuantity;
|
autoAllocateQuantity -= actualAllocated.ActualAllocatedQuantity;
|
lockInfoList.AddRange(actualAllocated.LockInfoList);
|
}
|
}
|
|
// 生成自动分配的任务(追加到generatedTasks,不会重复)
|
foreach (var palletCode in palletAllocations.Keys)
|
{
|
Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode);
|
decimal actualQty = palletAllocations[palletCode];
|
decimal originalAvailable = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
|
|
pickedDetails.Add(new PickedStockDetailDTO
|
{
|
PalletCode = palletCode,
|
MaterielCode = materielCalc.MaterielCode,
|
OutboundQuantity = actualQty,
|
RemainingQuantity = Math.Max(0, originalAvailable - actualQty),
|
LocationCode = stock.LocationCode,
|
OutStockLockInfos = lockInfoList.Where(x => x.PalletCode == palletCode).ToList()
|
});
|
|
int taskNum = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode)?.TaskNum ??
|
Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
|
Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation);
|
// 过滤重复任务(指定库存的托盘已生成,不会重复添加)
|
if (generatedTasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
|
generatedTasks.Add(task);
|
}
|
}
|
|
// 返回「指定库存任务+自动分配任务」的合并列表
|
return (pickedDetails, generatedTasks, lockInfoList);
|
}
|
|
/// <summary>
|
/// 分配指定库存明细(核心优化:查询托盘物料总库存并传递)
|
/// </summary>
|
private (decimal ActualAllocatedQuantity, List<PickedStockDetailDTO> PickedDetails, List<Dt_OutStockLockInfo> LockInfoList) AllocateSpecifiedStockDetails(
|
Dt_OutboundOrder outboundOrder,
|
MaterielOutboundCalculationDTO materielCalc,
|
PickingOutboundRequestDTO request,
|
string factoryArea,
|
decimal needAllocateQuantity)
|
{
|
List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
|
List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
|
decimal actualAllocated = 0;
|
|
List<Dt_StockInfoDetail> specifiedStockDetails = _stockDetailRepository.QueryData(
|
x => request.StockDetailIds.Contains(x.Id)
|
&& x.MaterielCode == materielCalc.MaterielCode
|
&& x.StockQuantity > 0
|
&& (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动冻结 || x.Status == (int)StockStatusEmun.手动解锁 || x.Status == (int)StockStatusEmun.过期));
|
|
if (!specifiedStockDetails.Any())
|
{
|
throw new Exception($"指定库存明细ID [{string.Join(",", request.StockDetailIds)}] 不存在或不可用");
|
}
|
|
List<int> stockIds = specifiedStockDetails.Select(x => x.StockId).Distinct().ToList();
|
List<Dt_StockInfo> specifiedStocks = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id));
|
Dictionary<int, Dt_StockInfo> stockMap = specifiedStocks.ToDictionary(x => x.Id);
|
|
foreach (var stockDetail in specifiedStockDetails)
|
{
|
if (needAllocateQuantity <= 0) break;
|
if (!stockMap.ContainsKey(stockDetail.StockId)) continue;
|
|
Dt_StockInfo stock = stockMap[stockDetail.StockId];
|
decimal availableQty = stockDetail.StockQuantity;
|
decimal allocateQty = Math.Min(needAllocateQuantity, availableQty);
|
if (allocateQty <= 0) continue;
|
|
// ===== 核心优化1:查询该托盘下当前物料的总库存 =====
|
decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
|
x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
|
).Sum(x => x.StockQuantity); // 该托盘该物料的总库存(而非本次分配量)
|
|
var lockInfos = materielCalc.OutStockLockInfos
|
.Where(x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode)
|
.ToList();
|
|
// ===== 传递总库存到AllocateStockQuantity =====
|
var allocateResult = AllocateStockQuantity(
|
stock, allocateQty, availableQty, outboundOrder, materielCalc.Details.First(),
|
request, lockInfos,
|
new Dictionary<int, List<Dt_StockInfoDetail>> { { stock.Id, new List<Dt_StockInfoDetail> { stockDetail } } },
|
palletMaterielTotalStock // 新增:传入托盘物料总库存
|
);
|
|
if (allocateResult.ActualAllocatedQuantity > 0)
|
{
|
pickedDetails.Add(new PickedStockDetailDTO
|
{
|
PalletCode = stock.PalletCode,
|
MaterielCode = materielCalc.MaterielCode,
|
OutboundQuantity = allocateResult.ActualAllocatedQuantity,
|
RemainingQuantity = Math.Max(0, availableQty - allocateResult.ActualAllocatedQuantity),
|
LocationCode = stock.LocationCode,
|
OutStockLockInfos = allocateResult.LockInfoList
|
});
|
|
actualAllocated += allocateResult.ActualAllocatedQuantity;
|
needAllocateQuantity -= allocateResult.ActualAllocatedQuantity;
|
lockInfoList.AddRange(allocateResult.LockInfoList);
|
}
|
}
|
|
return (actualAllocated, pickedDetails, lockInfoList);
|
}
|
|
/// <summary>
|
/// 为指定库存生成任务(原有逻辑不变)
|
/// </summary>
|
private List<Dt_Task> GenerateTasksForSpecifiedStock(List<PickedStockDetailDTO> pickedDetails, string outboundTargetLocation)
|
{
|
List<Dt_Task> tasks = new List<Dt_Task>();
|
var palletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
|
|
foreach (var palletCode in palletCodes)
|
{
|
Dt_StockInfo stock = _stockInfoRepository.QueryFirst(x => x.PalletCode == palletCode);
|
if (stock == null) continue;
|
|
int taskNum = pickedDetails.First(x => x.PalletCode == palletCode)
|
.OutStockLockInfos.FirstOrDefault()?.TaskNum ??
|
Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
|
|
Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, outboundTargetLocation);
|
if (tasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
|
tasks.Add(task);
|
}
|
|
return tasks;
|
}
|
|
/// <summary>
|
/// 构建库存查询条件(原有逻辑不变)
|
/// </summary>
|
private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
|
{
|
ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0
|
&& (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
|
|
if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
|
stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode);
|
if (!string.IsNullOrEmpty(materielCalc.WarehouseCode))
|
stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode);
|
if (!string.IsNullOrEmpty(factoryArea))
|
stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea);
|
if (!string.IsNullOrEmpty(materielCalc.BatchNo))
|
stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo);
|
|
List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
|
List<string> locationCodes = _locationInfoRepository.QueryData(x =>
|
(x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() || x.LocationStatus == LocationStatusEnum.Lock.ObjToInt())
|
&& x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList();
|
|
List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList();
|
List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x =>
|
stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt())
|
&& !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
|
|
foreach (var stockInfo in stockInfos)
|
{
|
stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
|
}
|
|
return stockInfos;
|
}
|
|
/// <summary>
|
/// 批量获取托盘可用库存信息(原有逻辑不变)
|
/// </summary>
|
private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(
|
MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos)
|
{
|
List<int> stockIds = stockInfos.Select(x => x.Id).ToList();
|
Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>();
|
Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>();
|
|
List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos
|
.Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList();
|
|
foreach (var stockInfo in stockInfos)
|
{
|
decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
|
List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData
|
.Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
|
decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
|
|
availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity);
|
lockStockMap[stockInfo.Id] = outStockLockInfos;
|
}
|
|
return (availableStockMap, lockStockMap);
|
}
|
|
/// <summary>
|
/// 分配库存(核心优化:OriginalQuantity赋值为托盘物料总库存)
|
/// </summary>
|
private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(
|
Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity,
|
Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail,
|
PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos,
|
Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null,
|
decimal palletMaterielTotalStock = 0 // 新增:托盘物料总库存参数
|
)
|
{
|
decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity);
|
List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
|
|
if (actualAllocatedQuantity > 0)
|
{
|
// 检查目标位置一致性
|
if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation)
|
&& !string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase))
|
{
|
return (0, lockInfoList);
|
}
|
|
Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x =>
|
x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt()
|
&& x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo);
|
|
if (lockInfo != null)
|
{
|
List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>();
|
if (!currentIds.Contains(detail.Id.ToString()))
|
{
|
currentIds.Add(detail.Id.ToString());
|
lockInfo.OrderDetailIds = string.Join(",", currentIds);
|
}
|
|
decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
|
lockInfo.AssignQuantity += actualAllocatedQuantity;
|
lockInfo.AllocatedQuantity = totalAllocatedQuantity;
|
if (palletMaterielTotalStock > 0)
|
lockInfo.OriginalQuantity = palletMaterielTotalStock;
|
lockInfoList.Add(lockInfo);
|
}
|
else
|
{
|
decimal originalQuantity = palletMaterielTotalStock;
|
|
List<string> allDetailIds = outboundOrder.Details.Where(x =>
|
x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
|
&& x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
|
&& x.WarehouseCode == detail.WarehouseCode)
|
.Select(x => x.Id.ToString()).ToList();
|
|
decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
|
|
lockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = request.OrderNo,
|
OrderDetailIds = string.Join(",", allDetailIds),
|
OrderType = outboundOrder.OrderType,
|
BatchNo = detail.BatchNo,
|
MaterielCode = detail.MaterielCode,
|
MaterielName = detail.MaterielName,
|
StockId = stockInfo.Id,
|
OrderQuantity = allDetailIds.Count > 1
|
? outboundOrder.Details.Where(x =>
|
x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
|
&& x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
|
&& x.WarehouseCode == detail.WarehouseCode).Sum(x => x.OrderQuantity)
|
: detail.OrderQuantity,
|
OriginalQuantity = originalQuantity, // 现在赋值为托盘物料总库存
|
AssignQuantity = actualAllocatedQuantity,
|
AllocatedQuantity = totalAllocatedQuantity,
|
LocationCode = stockInfo.LocationCode,
|
PalletCode = stockInfo.PalletCode,
|
Unit = detail.Unit,
|
OutboundTargetLocation = request.OutboundTargetLocation,
|
Status = OutLockStockStatusEnum.已分配.ObjToInt(),
|
SupplyCode = detail.SupplyCode,
|
WarehouseCode = detail.WarehouseCode,
|
FactoryArea = outboundOrder.FactoryArea,
|
TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(),
|
OrderDetailId = 0
|
};
|
lockInfoList.Add(lockInfo);
|
}
|
}
|
|
return (actualAllocatedQuantity, lockInfoList);
|
}
|
|
/// <summary>
|
/// 计算该托盘累计已分配数量(原有逻辑不变)
|
/// </summary>
|
private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
|
{
|
List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
|
x.StockId == stockId && x.MaterielCode == materielCode);
|
|
return lockRecords?.Sum(x => x.AssignQuantity) ?? 0;
|
}
|
|
/// <summary>
|
/// 生成出库任务(原有逻辑不变)
|
/// </summary>
|
public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation)
|
{
|
return new Dt_Task
|
{
|
CurrentAddress = stockInfo.LocationCode,
|
Grade = 0,
|
PalletCode = stockInfo.PalletCode,
|
NextAddress = outStation,
|
Roadway = "",
|
SourceAddress = stockInfo.LocationCode,
|
TargetAddress = outStation,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = taskType.ObjToInt(),
|
TaskNum = taskNum,
|
PalletType = stockInfo.PalletType,
|
WarehouseId = stockInfo.WarehouseId,
|
};
|
}
|
public bool UpdateOutboundOrderStatus(string orderNo, int status)
|
{
|
try
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
|
if (outboundOrder == null) return false;
|
outboundOrder.OrderStatus = status;
|
if(outboundOrder.CreateType == OrderCreateTypeEnum.CreateInSystem.ObjToInt())
|
{
|
outboundOrder.ReturnToMESStatus = 5;
|
}
|
_outboundRepository.UpdateData(outboundOrder);
|
return true;
|
}
|
catch
|
{
|
return false;
|
}
|
}
|
|
public bool UpdateStockStatus(List<string> palletCodes, int status)
|
{
|
try
|
{
|
List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode));
|
stockInfos.ForEach(stockInfo => stockInfo.StockStatus = status);
|
_stockInfoRepository.UpdateData(stockInfos);
|
return true;
|
}
|
catch
|
{
|
return false;
|
}
|
}
|
|
public bool UpdateLocationStatus(List<string> locationCodes, int status)
|
{
|
try
|
{
|
List<Dt_LocationInfo> locationInfos = _locationInfoRepository.QueryData(x => locationCodes.Contains(x.LocationCode));
|
locationInfos.ForEach(x => x.LocationStatus = status);
|
_locationInfoRepository.UpdateData(locationInfos);
|
return true;
|
}
|
catch
|
{
|
return false;
|
}
|
}
|
|
public bool UpdateOutStockLockInfo(List<Dt_OutStockLockInfo> outStockLockInfos)
|
{
|
try
|
{
|
List<Dt_OutStockLockInfo> updateData = outStockLockInfos.Where(x => x.Id > 0).ToList();
|
_outboundLockInfoRepository.UpdateData(updateData);
|
List<Dt_OutStockLockInfo> addData = outStockLockInfos.Where(x => x.Id <= 0).ToList();
|
_outboundLockInfoRepository.AddData(addData);
|
return true;
|
}
|
catch
|
{
|
return false;
|
}
|
}
|
#endregion
|
|
#region 整箱出库
|
public WebResponseContent CompleteOutboundWithPallet(OutboundCompletePalletRequestDTO request)
|
{
|
WebResponseContent content = WebResponseContent.Instance;
|
|
OutboundCompleteResponseDTO response = new();
|
try
|
{
|
// 1. 根据托盘号查找库存信息
|
Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == request.PalletCode).Includes(x => x.Details).First();
|
if (stockInfo == null)
|
{
|
response.Success = false;
|
response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
if (!stockInfo.Details.Any())
|
{
|
response.Success = false;
|
response.Message = $"托盘 {request.PalletCode} 对应的库存明细不存在";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
// 2. 查找出库单信息
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
|
if (outboundOrder == null)
|
{
|
response.Success = false;
|
response.Message = $"出库单 {request.OrderNo} 不存在";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
Dt_StockInfoDetail stockInfoDetail = stockInfo.Details.First();
|
|
// 3. 查找锁定记录
|
Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
|
x.OrderNo == request.OrderNo &&
|
x.StockId == stockInfo.Id &&
|
x.MaterielCode == stockInfoDetail.MaterielCode &&
|
x.PalletCode == stockInfo.PalletCode);
|
|
if (lockInfo == null || lockInfo.AssignQuantity <= 0)
|
{
|
response.Success = false;
|
response.Message = $"该库存没有分配出库量,托盘号:{request.PalletCode}";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
//bool isMatMixed = stockInfo.Details.GroupBy(x => new
|
//{
|
// x.MaterielCode,
|
// x.MaterielName,
|
// x.BatchNo,
|
// x.SupplyCode,
|
// x.WarehouseCode
|
//}).Count() > 1;
|
bool isMatMixed = false;
|
|
bool includeBatchNo = !string.IsNullOrEmpty(lockInfo.BatchNo);
|
bool includeSupplyCode = !string.IsNullOrEmpty(lockInfo.SupplyCode);
|
|
if (includeBatchNo && includeSupplyCode)
|
{
|
isMatMixed = stockInfo.Details.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.MaterielName,
|
x.BatchNo,
|
x.SupplyCode,
|
x.WarehouseCode
|
}).Count() > 1;
|
}
|
else if (includeBatchNo && !includeSupplyCode)
|
{
|
isMatMixed = stockInfo.Details.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.MaterielName,
|
x.BatchNo,
|
x.WarehouseCode
|
}).Count() > 1;
|
}
|
else if (!includeBatchNo && includeSupplyCode)
|
{
|
isMatMixed = stockInfo.Details.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.MaterielName,
|
x.SupplyCode,
|
x.WarehouseCode
|
}).Count() > 1;
|
}
|
else
|
{
|
isMatMixed = stockInfo.Details.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.MaterielName,
|
x.WarehouseCode
|
}).Count() > 1;
|
}
|
|
if (isMatMixed)
|
{
|
response.Success = false;
|
response.Message = $"混料托盘 {request.PalletCode} 不能整箱出库";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
// 找出已分配的订单明细Id
|
List<int> detailIds = new List<int>();
|
string[] ids = lockInfo.OrderDetailIds.Split(",");
|
foreach (string id in ids)
|
{
|
if (int.TryParse(id, out int detailId))
|
{
|
detailIds.Add(detailId);
|
}
|
}
|
// 4. 查找出库单明细信息
|
List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockInfoDetail, detailIds);
|
if (!outboundOrderDetails.Any())
|
{
|
response.Success = false;
|
response.Message = $"未找到匹配的出库单明细,物料:{stockInfoDetail.MaterielCode},批次:{stockInfoDetail.BatchNo}";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
decimal totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
|
|
// 5. 计算实际出库量
|
decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockInfo.Details, outboundOrderDetails, lockInfo);// 需出库量
|
|
if (actualOutboundQuantity <= 0)
|
{
|
decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity;
|
decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity;
|
decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);
|
|
response.Success = false;
|
response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
if (lockInfo.AssignQuantity != totalStockQuantity)
|
{
|
response.Success = false;
|
response.Message = $"无法出库,托盘号:{request.PalletCode},库存量:{totalStockQuantity},分配量:{lockInfo.AssignQuantity}";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
// 6. 开启事务
|
_unitOfWorkManage.BeginTran();
|
try
|
{
|
// 整箱出库无需拆包
|
PerformFullOutboundOperation(stockInfo, request, lockInfo.TaskNum.GetValueOrDefault());
|
|
if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
|
if (allocateOrder != null)
|
{
|
List<Dt_AllocateMaterialInfo> allocateMaterialInfos = new List<Dt_AllocateMaterialInfo>();
|
foreach (var item in stockInfo.Details)
|
{
|
Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo()
|
{
|
Barcode = item.Barcode,
|
BatchNo = item.BatchNo,
|
FactoryArea = item.FactoryArea,
|
MaterialCode = item.MaterielCode,
|
MaterialName = item.MaterielName,
|
OrderId = outboundOrder.Id,
|
OrderNo = outboundOrder.OrderNo,
|
Quantity = item.StockQuantity,
|
SupplyCode = item.SupplyCode,
|
Unit = item.Unit,
|
WarehouseCode = allocateOrder.ToWarehouse
|
};
|
allocateMaterialInfos.Add(allocateMaterialInfo);
|
}
|
_allocateMaterialInfoRepository.AddData(allocateMaterialInfos);
|
}
|
}
|
else if(outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
|
{
|
List<Dt_AllocateMaterialInfo> allocateMaterialInfos = new List<Dt_AllocateMaterialInfo>();
|
foreach (var item in stockInfo.Details)
|
{
|
Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo()
|
{
|
Barcode = item.Barcode??"",
|
BatchNo = item.BatchNo,
|
FactoryArea = item.FactoryArea,
|
MaterialCode = item.MaterielCode,
|
MaterialName = item.MaterielName,
|
OrderId = outboundOrder.Id,
|
OrderNo = outboundOrder.OrderNo,
|
Quantity = item.StockQuantity,
|
SupplyCode = item.SupplyCode??"",
|
Unit = item.Unit,
|
WarehouseCode = item.WarehouseCode??""
|
};
|
allocateMaterialInfos.Add(allocateMaterialInfo);
|
}
|
_allocateMaterialInfoRepository.AddData(allocateMaterialInfos);
|
}
|
|
decimal allocatedQuantity = actualOutboundQuantity;
|
List<Dt_OutboundOrderDetail> updateDetails = new();
|
foreach (var item in outboundOrderDetails)
|
{
|
if (allocatedQuantity <= 0) break;
|
|
|
//if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
|
//{
|
// item.OverOutQuantity += allocatedQuantity;
|
// allocatedQuantity = 0;
|
//}
|
//else
|
//{
|
// allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
|
// item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
|
//}
|
List<Barcodes> barcodesList = new List<Barcodes>();
|
List<Dt_StockInfoDetail> stockInfoDetails = stockInfo.Details.Where((x => x.StockQuantity > x.OutboundQuantity)).ToList();
|
|
decimal itemQuantity = item.LockQuantity - item.OverOutQuantity;
|
decimal unitbarcodeQuantity;
|
foreach (var stockDetail in stockInfoDetails)
|
{
|
|
|
if (itemQuantity >= stockDetail.StockQuantity - stockDetail.OutboundQuantity)
|
{
|
unitbarcodeQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
|
UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, unitbarcodeQuantity);
|
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = stockDetail.Barcode,
|
Qty = currentResult.ToQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = currentResult.ToUnit ?? ""
|
};
|
|
itemQuantity -= (stockDetail.StockQuantity - stockDetail.OutboundQuantity);
|
stockDetail.OutboundQuantity = stockDetail.StockQuantity;
|
barcodesList.Add(barcodes);
|
|
if (itemQuantity <= 0) break;
|
}
|
else
|
{
|
UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, itemQuantity);
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = stockDetail.Barcode,
|
Qty = currentResult.ToQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = currentResult.ToUnit ?? ""
|
};
|
stockDetail.OutboundQuantity += itemQuantity;
|
barcodesList.Add(barcodes);
|
break;
|
}
|
}
|
|
decimal barcodeQuantity = allocatedQuantity;
|
|
if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
|
{
|
item.OverOutQuantity += allocatedQuantity;
|
item.CurrentDeliveryQty += allocatedQuantity;
|
allocatedQuantity = 0;
|
}
|
else
|
{
|
barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
|
allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
|
if(item.ReturnToMESStatus == 0)
|
{
|
item.CurrentDeliveryQty = item.LockQuantity;
|
}
|
else
|
{
|
item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity;
|
}
|
item.OverOutQuantity = item.LockQuantity;
|
}
|
|
updateDetails.Add(item);
|
|
|
if (!string.IsNullOrEmpty(item.ReturnJsonData))
|
{
|
barcodesList.AddRange(JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>());
|
}
|
|
JsonSerializerSettings settings = new JsonSerializerSettings
|
{
|
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
};
|
item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
|
//重拣出库不需要回传
|
if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt())
|
{
|
item.ReturnJsonData = "";
|
}
|
}
|
|
lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
|
|
if (lockInfo.SortedQuantity == lockInfo.AssignQuantity)
|
{
|
_outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
|
}
|
else
|
{
|
// 更新锁定记录
|
_outboundLockInfoRepository.UpdateData(lockInfo);
|
}
|
|
// 更新出库单明细的已出库数量
|
_detailRepository.UpdateData(updateDetails);
|
|
// 更新锁定记录的累计已出库数量(需要更新该托盘该物料的所有相关记录)
|
//UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
|
|
// 提交事务
|
_unitOfWorkManage.CommitTran();
|
|
response.Success = true;
|
response.Message = "出库完成";
|
response.UpdatedDetails = updateDetails;
|
|
if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
|
{
|
Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
|
|
Func<Dt_OutStockLockInfo, bool> wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode;
|
|
List<Dt_OutStockLockInfo> stockLockInfos = _outboundLockInfoRepository.QueryData(x =>
|
x.OrderNo == request.OrderNo &&
|
x.MaterielCode == stockInfoDetail.MaterielCode).Where(supWhere).Where(wareWhere).ToList();
|
if (stockLockInfos != null && stockLockInfos.Any())
|
{
|
_outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除);
|
}
|
|
outboundOrderDetails.FirstOrDefault().OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
|
_detailRepository.UpdateData(outboundOrderDetails);
|
}
|
|
// 检查出库单是否完成
|
if (CheckOutboundOrderCompleted(request.OrderNo))
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
|
|
if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt()&& outboundOrder.OrderType!= InOrderTypeEnum.ReCheck.ObjToInt())
|
{
|
_feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
|
}
|
|
}
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
response.Success = false;
|
response.Message = $"出库处理失败:{ex.Message}";
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
|
content = WebResponseContent.Instance.OK(data: response);
|
}
|
catch (Exception ex)
|
{
|
content = WebResponseContent.Instance.Error("处理出库完成失败:" + ex.Message);
|
}
|
return content;
|
}
|
|
/// <summary>
|
/// 计算实际出库数量
|
/// </summary>
|
private decimal CalculateActualOutboundQuantity(List<Dt_StockInfoDetail> stockDetails, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo)
|
{
|
decimal availableOutboundQuantity = lockInfo.AssignQuantity;
|
decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity;
|
|
return Math.Min(
|
Math.Min(availableOutboundQuantity, detailRemainingQuantity),
|
stockDetails.Sum(x => x.StockQuantity));
|
}
|
|
/// <summary>
|
/// 执行完整出库操作(不拆包)
|
/// </summary>
|
private void PerformFullOutboundOperation(Dt_StockInfo stockInfo, OutboundCompletePalletRequestDTO request, int taskNum)
|
{
|
List<Dt_StockInfoDetail_Hty> historyRecords = new List<Dt_StockInfoDetail_Hty>();
|
List<Dt_StockQuantityChangeRecord> changeRecords = new List<Dt_StockQuantityChangeRecord>();
|
foreach (var item in stockInfo.Details)
|
{
|
// 保存库存明细到历史记录
|
Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty
|
{
|
SourceId = item.Id,
|
OperateType = "出库完成",
|
InsertTime = DateTime.Now,
|
StockId = item.StockId,
|
MaterielCode = item.MaterielCode,
|
MaterielName = item.MaterielName,
|
OrderNo = item.OrderNo,
|
BatchNo = item.BatchNo,
|
ProductionDate = item.ProductionDate,
|
EffectiveDate = item.EffectiveDate,
|
SerialNumber = item.SerialNumber,
|
StockQuantity = item.StockQuantity,
|
OutboundQuantity = item.StockQuantity,
|
Status = item.Status,
|
Unit = item.Unit,
|
InboundOrderRowNo = item.InboundOrderRowNo,
|
SupplyCode = item.SupplyCode,
|
FactoryArea = item.FactoryArea,
|
WarehouseCode = item.WarehouseCode,
|
Barcode = item.Barcode,
|
CreateDate = item.CreateDate,
|
Creater = item.Creater,
|
Remark = $"整箱出库完成删除,条码:{request.PalletCode},原数量:{item.StockQuantity},出库数量:{item.StockQuantity},操作者:{request.Operator}"
|
};
|
historyRecords.Add(historyRecord);
|
|
// 记录库存变动
|
Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
|
{
|
StockDetailId = item.Id,
|
PalleCode = stockInfo.PalletCode,
|
MaterielCode = item.MaterielCode,
|
MaterielName = item.MaterielName,
|
BatchNo = item.BatchNo,
|
OriginalSerilNumber = item.Barcode,
|
NewSerilNumber = "",
|
OrderNo = request.OrderNo,
|
TaskNum = taskNum,
|
ChangeType = (int)StockChangeTypeEnum.Outbound,
|
ChangeQuantity = -item.StockQuantity,
|
BeforeQuantity = item.StockQuantity,
|
AfterQuantity = 0,
|
SupplyCode = item.SupplyCode,
|
WarehouseCode = item.WarehouseCode,
|
Remark = $"整箱出库完成删除库存明细,条码:{request.PalletCode},出库数量:{item.StockQuantity},操作者:{request.Operator}"
|
};
|
changeRecords.Add(changeRecord);
|
}
|
_stockDetailHistoryRepository.AddData(historyRecords);
|
|
// 删除库存明细记录
|
var orderNo =_outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
|
if(orderNo.OrderType != 117)
|
{
|
_stockDetailRepository.DeleteData(stockInfo.Details);
|
}
|
_stockChangeRepository.AddData(changeRecords);
|
}
|
|
|
#endregion
|
|
#region 拣选
|
/// <summary>
|
/// 出库完成处理(扫描条码扣减库存)
|
/// </summary>
|
/// <param name="request">出库完成请求</param>
|
/// <returns>出库完成响应</returns>
|
public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request)
|
{
|
WebResponseContent content = WebResponseContent.Instance;
|
|
OutboundCompleteResponseDTO response = new();
|
try
|
{
|
// 1. 根据托盘号查找库存信息
|
Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode);
|
if (stockInfo == null)
|
{
|
response.Success = false;
|
response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在";
|
return WebResponseContent.Instance.Error($"托盘号 {request.PalletCode} 对应的库存不存在");
|
}
|
|
// 2. 根据条码查找库存明细
|
Dt_StockInfoDetail stockDetail = _stockDetailRepository.QueryFirst(x => x.Barcode == request.Barcode);
|
if (stockDetail == null)
|
{
|
response.Success = false;
|
response.Message = $"条码 {request.Barcode} 对应的库存明细不存在";
|
return WebResponseContent.Instance.Error($"条码 {request.Barcode} 对应的库存明细不存在");
|
}
|
|
// 3. 验证库存明细与托盘是否匹配
|
if (stockDetail.StockId != stockInfo.Id)
|
{
|
response.Success = false;
|
response.Message = $"条码 {request.Barcode} 不属于托盘号 {request.PalletCode} 的库存明细";
|
return WebResponseContent.Instance.Error($"条码 {request.Barcode} 不属于托盘号 {request.PalletCode} 的库存明细");
|
}
|
|
// 4. 查找出库单信息
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
|
if (outboundOrder == null)
|
{
|
response.Success = false;
|
response.Message = $"出库单 {request.OrderNo} 不存在";
|
return WebResponseContent.Instance.Error($"出库单 {request.OrderNo} 不存在");
|
}
|
|
// 5. 查找锁定记录
|
Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
|
x.OrderNo == request.OrderNo &&
|
x.StockId == stockInfo.Id &&
|
x.MaterielCode == stockDetail.MaterielCode &&
|
x.PalletCode == stockInfo.PalletCode);
|
|
if (lockInfo == null || lockInfo.AssignQuantity <= 0)
|
{
|
response.Success = false;
|
response.Message = $"该库存没有分配出库量,条码:{request.Barcode}";
|
return WebResponseContent.Instance.Error($"该库存没有分配出库量,条码:{request.Barcode}");
|
}
|
|
// 找出已分配的订单明细Id
|
List<int> detailIds = new List<int>();
|
string[] ids = lockInfo.OrderDetailIds.Split(",");
|
foreach (string id in ids)
|
{
|
if (int.TryParse(id, out int detailId))
|
{
|
detailIds.Add(detailId);
|
}
|
}
|
// 6. 查找出库单明细信息
|
List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockDetail, detailIds);
|
if (!outboundOrderDetails.Any())
|
{
|
response.Success = false;
|
response.Message = $"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}";
|
return WebResponseContent.Instance.Error($"未找到匹配的出库单明细,物料:{stockDetail.MaterielCode},批次:{stockDetail.BatchNo}");
|
}
|
|
// 7. 计算实际出库量
|
decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockDetail, outboundOrderDetails, lockInfo);// 需出库量
|
|
if (actualOutboundQuantity <= 0)
|
{
|
decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity;
|
decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity;
|
decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);
|
|
response.Success = false;
|
response.Message = $"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}";
|
return WebResponseContent.Instance.Error($"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},已出库:{totalAllocatedQuantity},分配量:{lockInfo.AssignQuantity},明细剩余:{detailRemainingQuantity}");
|
}
|
|
//if (actualOutboundQuantity + lockInfo.SortedQuantity > lockInfo.AssignQuantity)
|
//{
|
// response.Success = false;
|
// response.Message = $"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},出库量{actualOutboundQuantity + lockInfo.SortedQuantity}大于分配量{lockInfo.AssignQuantity}";
|
// return WebResponseContent.Instance.Error($"无法出库,条码:{request.Barcode},库存:{stockDetail.StockQuantity},出库量{actualOutboundQuantity + lockInfo.SortedQuantity}大于分配量{lockInfo.AssignQuantity}");
|
//}
|
|
// 8. 判断是否需要拆包(当出库数量小于库存数量时需要拆包)
|
bool isUnpacked = actualOutboundQuantity < stockDetail.StockQuantity;
|
List<MaterialCodeReturnDTO> returnDTOs = new List<MaterialCodeReturnDTO>();
|
string newBarcode = string.Empty;
|
// 9. 开启事务
|
_unitOfWorkManage.BeginTran();
|
try
|
{
|
decimal beforeQuantity = stockDetail.StockQuantity; // 原始库存量
|
|
Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo();
|
|
// 根据是否拆包执行不同的操作
|
if (isUnpacked)
|
{
|
(string NewBarcode, List<MaterialCodeReturnDTO> MaterialCodeReturnDTOs) result = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault(), outboundOrder.Id, outboundOrder.OrderNo);
|
|
returnDTOs = result.MaterialCodeReturnDTOs;
|
newBarcode = result.NewBarcode;
|
|
MaterialCodeReturnDTO returnDTO = returnDTOs.First(x => x.Barcode == newBarcode);
|
|
if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt()||outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
allocateMaterialInfo = new Dt_AllocateMaterialInfo()
|
{
|
Barcode = returnDTO.Barcode,
|
BatchNo = returnDTO.BatchNo,
|
FactoryArea = returnDTO.FactoryArea,
|
MaterialCode = returnDTO.MaterialCode,
|
MaterialName = returnDTO.MaterialName,
|
OrderId = outboundOrder.Id,
|
OrderNo = outboundOrder.OrderNo,
|
Quantity = returnDTO.Quantity,
|
SupplyCode = returnDTO.SuplierCode,
|
Unit = stockDetail.Unit
|
};
|
}
|
|
}
|
else
|
{
|
PerformFullOutboundOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault());
|
|
if (outboundOrder.OrderType == InOrderTypeEnum.ReCheck.ObjToInt() || outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
allocateMaterialInfo = new Dt_AllocateMaterialInfo()
|
{
|
Barcode = stockDetail.Barcode,
|
BatchNo = stockDetail.BatchNo,
|
FactoryArea = stockDetail.FactoryArea,
|
MaterialCode = stockDetail.MaterielCode,
|
MaterialName = stockDetail.MaterielName,
|
OrderId = outboundOrder.Id,
|
OrderNo = outboundOrder.OrderNo,
|
Quantity = stockDetail.StockQuantity,
|
SupplyCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit
|
};
|
}
|
|
}
|
|
// 判断是否是智仓调智仓单
|
if ( outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
Dt_AllocateOrder allocateOrder = _allocateOrderRepository.QueryFirst(x => x.OrderNo == outboundOrder.OrderNo);
|
if (allocateOrder != null)
|
{
|
allocateMaterialInfo.WarehouseCode = allocateOrder.ToWarehouse;
|
|
_allocateMaterialInfoRepository.AddData(allocateMaterialInfo);
|
}
|
}
|
|
decimal allocatedQuantity = actualOutboundQuantity;
|
List<Dt_OutboundOrderDetail> updateDetails = new();
|
foreach (var item in outboundOrderDetails)
|
{
|
if (allocatedQuantity <= 0) break;
|
|
//if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity)
|
//{
|
// item.OverOutQuantity += allocatedQuantity;
|
// allocatedQuantity = 0;
|
//}
|
//else
|
//{
|
// allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity);
|
// item.OverOutQuantity = item.OrderQuantity - item.MoveQty;
|
//}
|
|
decimal barcodeQuantity = allocatedQuantity;
|
|
if (item.LockQuantity - item.OverOutQuantity >= allocatedQuantity)
|
{
|
item.OverOutQuantity += allocatedQuantity;
|
item.CurrentDeliveryQty += allocatedQuantity;
|
allocatedQuantity = 0;
|
}
|
else
|
{
|
barcodeQuantity = item.LockQuantity - item.OverOutQuantity;
|
allocatedQuantity -= (item.LockQuantity - item.OverOutQuantity);
|
if (item.ReturnToMESStatus == 0)
|
{
|
item.CurrentDeliveryQty = item.LockQuantity;
|
}
|
else
|
{
|
item.CurrentDeliveryQty += item.LockQuantity - item.OverOutQuantity;
|
}
|
item.OverOutQuantity = item.LockQuantity;
|
}
|
|
if (item.OverOutQuantity == item.OrderQuantity)
|
{
|
item.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
|
}
|
updateDetails.Add(item);
|
|
List<Barcodes> barcodesList = new List<Barcodes>();
|
UnitConvertResultDTO currentResult = _basicService.UnitQuantityConvert(item.MaterielCode, item.Unit, item.BarcodeUnit, barcodeQuantity);
|
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = isUnpacked ? newBarcode : stockDetail?.Barcode,
|
Qty = currentResult.ToQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = currentResult.ToUnit ?? ""
|
};
|
if (!string.IsNullOrEmpty(item.ReturnJsonData))
|
{
|
barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(item.ReturnJsonData) ?? new List<Barcodes>();
|
}
|
barcodesList.Add(barcodes);
|
JsonSerializerSettings settings = new JsonSerializerSettings
|
{
|
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
};
|
item.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, settings);
|
}
|
|
lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity;
|
|
if (lockInfo.SortedQuantity >= lockInfo.AssignQuantity)
|
{
|
_outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.自动完成);
|
}
|
else
|
{
|
// 更新锁定记录
|
_outboundLockInfoRepository.UpdateData(lockInfo);
|
}
|
|
// 更新出库单明细的已出库数量
|
_detailRepository.UpdateData(updateDetails);
|
|
// 更新锁定记录的累计已出库数量(需要更新该托盘该物料的所有相关记录)
|
//UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity);
|
|
// 提交事务
|
_unitOfWorkManage.CommitTran();
|
|
// 构建返回信息
|
ScannedStockDetailDTO scannedDetail = new ScannedStockDetailDTO
|
{
|
StockDetailId = stockDetail.Id,
|
PalletCode = stockInfo.PalletCode,
|
MaterielCode = stockDetail.MaterielCode,
|
MaterielName = stockDetail.MaterielName,
|
BatchNo = stockDetail.BatchNo,
|
OriginalBarcode = request.Barcode,
|
BeforeQuantity = beforeQuantity,
|
AfterQuantity = isUnpacked ? actualOutboundQuantity : 0,
|
ChangeQuantity = -actualOutboundQuantity,
|
IsUnpacked = isUnpacked,
|
MaterialCodes = returnDTOs
|
};
|
|
response.Success = true;
|
response.Message = "出库完成";
|
response.ScannedDetail = scannedDetail;
|
response.UpdatedDetails = updateDetails;
|
|
if (!string.IsNullOrEmpty(newBarcode))
|
{
|
// 物料新条码回传
|
_feedbackMesService.BarcodeFeedback(newBarcode);
|
}
|
|
if (CheckOutboundOrderDetailCompletedByMatCode(request.OrderNo, lockInfo.MaterielCode, outboundOrderDetails))
|
{
|
Func<Dt_OutStockLockInfo, bool> supWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().SupplyCode) ? true : x.SupplyCode == outboundOrderDetails.First().SupplyCode;
|
|
Func<Dt_OutStockLockInfo, bool> wareWhere = x => string.IsNullOrEmpty(outboundOrderDetails.First().WarehouseCode) ? true : x.WarehouseCode == outboundOrderDetails.First().WarehouseCode;
|
|
List<Dt_OutStockLockInfo> stockLockInfos = _outboundLockInfoRepository.QueryData(x =>
|
x.OrderNo == request.OrderNo &&
|
x.MaterielCode == stockDetail.MaterielCode).Where(supWhere).Where(wareWhere).ToList();
|
if (stockLockInfos != null && stockLockInfos.Any())
|
{
|
_outboundLockInfoRepository.DeleteAndMoveIntoHty(stockLockInfos, WIDESEA_Core.Enums.OperateTypeEnum.自动删除);
|
}
|
}
|
|
|
// 检查出库单是否完成
|
if (CheckOutboundOrderCompleted(request.OrderNo))
|
{
|
if(outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
|
|
if (outboundOrder.CreateType != OrderCreateTypeEnum.CreateInSystem.ObjToInt())
|
{
|
_feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
|
}
|
}
|
}
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
response.Success = false;
|
response.Message = $"出库处理失败:{ex.Message}";
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
|
content = WebResponseContent.Instance.OK(data: response);
|
}
|
catch (Exception ex)
|
{
|
content = WebResponseContent.Instance.Error("处理出库完成失败:" + ex.Message);
|
}
|
return content;
|
}
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="orderId"></param>
|
/// <param name="stockDetail"></param>
|
/// <returns></returns>
|
private List<Dt_OutboundOrderDetail> FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail, List<int> detailIds)
|
{
|
List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
|
x.OrderId == orderId &&
|
x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity && detailIds.Contains(x.Id));
|
|
// 精确匹配:处理null值的批次、供应商、仓库
|
List<Dt_OutboundOrderDetail> exactMatches = details.Where(x =>
|
(string.IsNullOrEmpty(x.BatchNo)) || x.BatchNo == stockDetail.BatchNo
|
).Where(x =>
|
(string.IsNullOrEmpty(x.SupplyCode)) || x.SupplyCode == stockDetail.SupplyCode
|
).Where(x =>
|
(string.IsNullOrEmpty(x.WarehouseCode)) || x.WarehouseCode == stockDetail.WarehouseCode
|
).ToList();
|
|
return exactMatches;
|
}
|
|
/// <summary>
|
/// 计算实际出库数量
|
/// </summary>
|
private decimal CalculateActualOutboundQuantity(Dt_StockInfoDetail stockDetail, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo)
|
{
|
// decimal availableOutboundQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity;
|
|
decimal availableOutboundQuantity = lockInfo.AssignQuantity;
|
decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity;
|
|
return Math.Min(
|
Math.Min(availableOutboundQuantity, detailRemainingQuantity),
|
stockDetail.StockQuantity);
|
}
|
|
/// <summary>
|
/// 执行拆包操作
|
/// </summary>
|
/// <param name="stockDetail"></param>
|
/// <param name="stockInfo"></param>
|
/// <param name="actualOutboundQuantity"></param>
|
/// <param name="request"></param>
|
/// <param name="beforeQuantity"></param>
|
/// <param name="taskNum"></param>
|
/// <returns></returns>
|
public (string NewBarcode, List<MaterialCodeReturnDTO> MaterialCodeReturnDTOs) PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
|
decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum, int orderId, string orderNo)
|
{
|
string newBarcode = GenerateNewBarcode();
|
|
string remark = $"拆包记录,原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库条码:{newBarcode}, 出库数量:{actualOutboundQuantity},回库条码:{request.Barcode},回库数量:{stockDetail.StockQuantity - actualOutboundQuantity},操作者:{request.Operator}";
|
|
List<Dt_MaterialCodeInfo> materialCodeInfos = CreateMaterialCodeInfos(stockDetail, newBarcode, actualOutboundQuantity, remark, taskNum, orderId, orderNo);
|
|
List<MaterialCodeReturnDTO> returnDTOs = _mapper.Map<List<MaterialCodeReturnDTO>>(materialCodeInfos);
|
|
// 保存原始库存明细到历史记录
|
Dt_StockInfoDetail_Hty originalHistoryRecord = new Dt_StockInfoDetail_Hty
|
{
|
SourceId = stockDetail.Id,
|
OperateType = "拆包-原始记录",
|
InsertTime = DateTime.Now,
|
StockId = stockDetail.StockId,
|
MaterielCode = stockDetail.MaterielCode,
|
MaterielName = stockDetail.MaterielName,
|
OrderNo = stockDetail.OrderNo,
|
BatchNo = stockDetail.BatchNo,
|
ProductionDate = stockDetail.ProductionDate,
|
EffectiveDate = stockDetail.EffectiveDate,
|
SerialNumber = stockDetail.SerialNumber,
|
StockQuantity = stockDetail.StockQuantity,
|
OutboundQuantity = stockDetail.OutboundQuantity,
|
Status = stockDetail.Status,
|
Unit = stockDetail.Unit,
|
InboundOrderRowNo = stockDetail.InboundOrderRowNo,
|
SupplyCode = stockDetail.SupplyCode,
|
Creater = stockDetail.Creater,
|
CreateDate = stockDetail.CreateDate,
|
FactoryArea = stockDetail.FactoryArea,
|
WarehouseCode = stockDetail.WarehouseCode,
|
Remark = $"拆包前原始记录,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
|
};
|
_stockDetailHistoryRepository.AddData(originalHistoryRecord);
|
|
// 保存剩余部分到历史记录
|
decimal remainingQuantity = stockDetail.StockQuantity - actualOutboundQuantity;
|
if (remainingQuantity > 0)
|
{
|
// 更新原库存明细
|
stockDetail.StockQuantity = remainingQuantity;
|
//stockDetail.Barcode = newBarcode;
|
stockDetail.Remark = $"拆包后更新,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新数量:{remainingQuantity},操作者:{request.Operator}";
|
_stockDetailRepository.UpdateData(stockDetail);
|
}
|
|
// 记录拆包变动
|
Dt_StockQuantityChangeRecord unpackChangeRecord = new Dt_StockQuantityChangeRecord
|
{
|
StockDetailId = stockDetail.Id,
|
PalleCode = stockInfo.PalletCode,
|
MaterielCode = stockDetail.MaterielCode,
|
MaterielName = stockDetail.MaterielName,
|
BatchNo = stockDetail.BatchNo,
|
OriginalSerilNumber = request.Barcode,
|
NewSerilNumber = newBarcode,
|
OrderNo = request.OrderNo,
|
TaskNum = taskNum,
|
ChangeType = (int)StockChangeTypeEnum.Outbound,
|
ChangeQuantity = -actualOutboundQuantity,
|
BeforeQuantity = beforeQuantity,
|
AfterQuantity = beforeQuantity - actualOutboundQuantity,
|
SupplyCode = stockDetail.SupplyCode,
|
WarehouseCode = stockDetail.WarehouseCode,
|
Remark = $"拆包出库,出库单号:{orderNo},出库单主键:{orderId},原条码:{request.Barcode},新条码:{newBarcode},出库数量:{actualOutboundQuantity},剩余:{remainingQuantity},操作者:{request.Operator}"
|
};
|
_stockChangeRepository.AddData(unpackChangeRecord);
|
|
return (newBarcode, returnDTOs);
|
}
|
|
/// <summary>
|
/// 执行完整出库操作(不拆包)
|
/// </summary>
|
public void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo,
|
decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum)
|
{
|
// 保存库存明细到历史记录
|
Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty
|
{
|
SourceId = stockDetail.Id,
|
OperateType = "出库完成",
|
InsertTime = DateTime.Now,
|
StockId = stockDetail.StockId,
|
Barcode = stockDetail.Barcode,
|
MaterielCode = stockDetail.MaterielCode,
|
MaterielName = stockDetail.MaterielName,
|
OrderNo = stockDetail.OrderNo,
|
BatchNo = stockDetail.BatchNo,
|
ProductionDate = stockDetail.ProductionDate,
|
EffectiveDate = stockDetail.EffectiveDate,
|
SerialNumber = stockDetail.SerialNumber,
|
StockQuantity = stockDetail.StockQuantity,
|
OutboundQuantity = stockDetail.OutboundQuantity + actualOutboundQuantity,
|
Status = stockDetail.Status,
|
Unit = stockDetail.Unit,
|
InboundOrderRowNo = stockDetail.InboundOrderRowNo,
|
SupplyCode = stockDetail.SupplyCode,
|
FactoryArea = stockDetail.FactoryArea,
|
Creater = stockDetail.Creater,
|
CreateDate = stockDetail.CreateDate,
|
WarehouseCode = stockDetail.WarehouseCode,
|
ValidDate = stockDetail.ValidDate,
|
Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
|
};
|
_stockDetailHistoryRepository.AddData(historyRecord);
|
|
// 删除库存明细记录
|
_stockDetailRepository.DeleteData(stockDetail);
|
|
// 记录库存变动
|
Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord
|
{
|
StockDetailId = stockDetail.Id,
|
PalleCode = stockInfo.PalletCode,
|
MaterielCode = stockDetail.MaterielCode,
|
MaterielName = stockDetail.MaterielName,
|
BatchNo = stockDetail.BatchNo,
|
OriginalSerilNumber = request.Barcode,
|
NewSerilNumber = "",
|
OrderNo = request.OrderNo,
|
TaskNum = taskNum,
|
ChangeType = (int)StockChangeTypeEnum.Outbound,
|
ChangeQuantity = -actualOutboundQuantity,
|
BeforeQuantity = beforeQuantity,
|
AfterQuantity = 0,
|
SupplyCode = stockDetail.SupplyCode,
|
WarehouseCode = stockDetail.WarehouseCode,
|
Remark = $"出库完成删除库存明细,条码:{request.Barcode},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
|
};
|
_stockChangeRepository.AddData(changeRecord);
|
}
|
|
/// <summary>
|
/// 生成新的条码
|
/// </summary>
|
/// <returns>新条码</returns>
|
private string GenerateNewBarcode()
|
{
|
// 使用时间戳和随机数生成唯一条码
|
string newBarcode = string.Empty;
|
|
newBarcode = _basicService.CreateCodeByRule(RuleCodeEnum.NewBarcodeRule.ToString());
|
|
return newBarcode;
|
}
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="stockDetail"></param>
|
/// <param name="newBarcode"></param>
|
/// <param name="splitQuantity"></param>
|
/// <param name="afterQuantity"></param>
|
/// <param name="remark"></param>
|
/// <returns></returns>
|
private List<Dt_MaterialCodeInfo> CreateMaterialCodeInfos(Dt_StockInfoDetail stockDetail, string newBarcode, decimal splitQuantity, string remark, int taskNum, int orderId, string orderNo)
|
{
|
List<Dt_MaterialCodeInfo> materialCodeInfos = new List<Dt_MaterialCodeInfo>();
|
|
Dt_MaterielInfo? materielInfo = _basicService.MaterielInfoService.Repository.QueryFirst(x => x.MaterielCode == stockDetail.MaterielCode);
|
|
Dt_MaterialCodeInfo outMaterialCodeInfo = new Dt_MaterialCodeInfo()
|
{
|
AfterQuantity = splitQuantity,
|
BatchNo = stockDetail.BatchNo,
|
FactoryArea = stockDetail.FactoryArea,
|
MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName,
|
MaterialSpec = materielInfo?.MaterielSpec ?? "",
|
MaterialCode = stockDetail.MaterielCode,
|
NewBarcode = newBarcode,
|
OldBarcode = stockDetail.Barcode,
|
OriginalQuantity = stockDetail.StockQuantity,
|
PruchaseOrderNo = stockDetail.OrderNo,
|
SuplierCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit,
|
Date = DateTime.Now.ToString("yyyy-MM-dd"),
|
Remark = remark,
|
WarehouseCode = stockDetail.WarehouseCode,
|
OrderNo = orderNo,
|
OrderId = orderId,
|
ReturnStatus = 0
|
};
|
materialCodeInfos.Add(outMaterialCodeInfo);
|
|
Dt_MaterialCodeInfo returnMaterialCodeInfo = new Dt_MaterialCodeInfo()
|
{
|
AfterQuantity = stockDetail.StockQuantity - splitQuantity,
|
BatchNo = stockDetail.BatchNo,
|
FactoryArea = stockDetail.FactoryArea,
|
MaterialName = materielInfo?.MaterielName ?? stockDetail.MaterielName,
|
MaterialSpec = materielInfo?.MaterielSpec ?? "",
|
MaterialCode = stockDetail.MaterielCode,
|
NewBarcode = stockDetail.Barcode,
|
OldBarcode = stockDetail.Barcode,
|
OriginalQuantity = stockDetail.StockQuantity,
|
PruchaseOrderNo = stockDetail.OrderNo,
|
SuplierCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit,
|
Date = DateTime.Now.ToString("yyyy-MM-dd"),
|
Remark = remark,
|
WarehouseCode = stockDetail.WarehouseCode,
|
OrderNo = orderNo,
|
OrderId = orderId,
|
ReturnStatus = 0
|
};
|
|
materialCodeInfos.Add(returnMaterialCodeInfo);
|
|
_basicService.MaterielCodeInfoService.Repository.AddData(materialCodeInfos);
|
return materialCodeInfos;
|
}
|
|
/// <summary>
|
/// 更新该托盘该物料的所有锁定记录的累计已出库数量
|
/// </summary>
|
/// <param name="stockId">库存ID</param>
|
/// <param name="materielCode">物料编号</param>
|
/// <param name="batchNo">批次号</param>
|
/// <param name="actualOutboundQuantity">本次实际出库数量</param>
|
/// <returns></returns>
|
private void UpdateLockInfoAllocatedQuantity(int stockId, string materielCode, string batchNo, decimal actualOutboundQuantity)
|
{
|
// 查询该托盘该物料的所有锁定记录
|
List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
|
x.StockId == stockId &&
|
x.MaterielCode == materielCode &&
|
x.BatchNo == batchNo);
|
|
if (lockRecords != null && lockRecords.Any())
|
{
|
// 更新所有相关记录的AllocatedQuantity
|
foreach (var record in lockRecords)
|
{
|
record.AllocatedQuantity += actualOutboundQuantity;
|
}
|
|
// 批量更新
|
_outboundLockInfoRepository.UpdateData(lockRecords);
|
}
|
}
|
|
/// <summary>
|
/// 检查出库单是否完成
|
/// </summary>
|
public bool CheckOutboundOrderCompleted(string orderNo)
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
|
if (outboundOrder == null) return false;
|
|
List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
|
|
// 检查所有明细的已出数量是否都等于单据数量
|
return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
|
}
|
|
/// <summary>
|
/// 检查出库单明细是否完成
|
/// </summary>
|
public bool CheckOutboundOrderDetailCompletedByMatCode(string orderNo, string materialCode, List<Dt_OutboundOrderDetail> outboundOrderDetails)
|
{
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(materialCode) || outboundOrderDetails == null || !outboundOrderDetails.Any())
|
return false;
|
|
// 查询主订单,不存在直接返回false
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
|
if (outboundOrder == null) return false;
|
|
var firstDetail = outboundOrderDetails.FirstOrDefault();
|
string supplyCode = firstDetail.SupplyCode;
|
string warehouseCode = firstDetail.WarehouseCode;
|
List<int> ids = outboundOrderDetails.Select(x => x.Id).ToList();
|
|
List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
|
x.OrderId == outboundOrder.Id
|
&& x.MaterielCode == materialCode
|
&& ids.Contains(x.Id)
|
&& (string.IsNullOrEmpty(supplyCode) || x.SupplyCode == supplyCode)
|
&& (string.IsNullOrEmpty(warehouseCode) || x.WarehouseCode == warehouseCode)
|
);
|
|
if (!details.Any()) return false;
|
|
return details.All(x => x.OverOutQuantity >= x.OrderQuantity - x.MoveQty);
|
}
|
|
#endregion
|
|
#region 取空箱
|
public async Task<WebResponseContent> EmptyBox(string palletCode)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
|
|
Dt_Task task = _taskRepository.QueryFirst(x => x.PalletCode == palletCode);
|
if (task != null)
|
{
|
return WebResponseContent.Instance.Error("任务信息列表存在该托盘的任务信息,不可取走空箱,请检查任务是否完成");
|
}
|
|
if (stock == null)
|
{
|
return content.Error($"未找到托盘{palletCode}库存信息");
|
}
|
if (stock.Details.Count > 0)
|
{
|
return content.Error($"托盘{palletCode}还存在库存信息不允许取走");
|
}
|
Dt_StockInfo_Hty stockInfo_Hty = stock.Adapt<Dt_StockInfo_Hty>();
|
stockInfo_Hty.SourceId = stock.Id;
|
stockInfo_Hty.OperateType = "取空箱";
|
stockInfo_Hty.InsertTime = DateTime.Now;
|
|
_unitOfWorkManage.BeginTran();
|
await _outboundRepository.Db.InsertNav(stockInfo_Hty).IncludesAllFirstLayer().ExecuteCommandAsync();
|
await _stockInfoRepository.DeleteDataByIdAsync(stock.Id);
|
_unitOfWorkManage.CommitTran();
|
return content.OK();
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return content.Error(ex.Message);
|
}
|
}
|
|
#endregion
|
|
#region 回库
|
public async Task<WebResponseContent> ReturnToWarehouse(string palletCode, string OrderNo, string station)
|
{
|
WebResponseContent content = new WebResponseContent();
|
try
|
{
|
var stock = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.PalletCode == palletCode).FirstAsync();
|
|
if (stock == null)
|
{
|
return content.Error($"未找到托盘{palletCode}库存信息不允许回库");
|
}
|
|
if (stock.Details.Count <= 0)
|
{
|
stock.PalletType = (int)PalletTypeEnum.Empty;
|
stock.StockStatus = (int)StockStatusEmun.入库确认;
|
stock.LocationCode = "";
|
}
|
else if (stock.Details.Count > 0)
|
{
|
Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
|
x.StockId == stock.Id &&
|
x.PalletCode == palletCode);
|
|
if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity)
|
{
|
// 1. 计算需要回滚的总数量
|
decimal? rollbackTotalQuantity = lockInfo.AssignQuantity - lockInfo.SortedQuantity;
|
// 确保回滚数量为正数
|
if (rollbackTotalQuantity <= 0)
|
{
|
// 没有需要回滚的数量
|
stock.StockStatus = (int)StockStatusEmun.入库确认;
|
stock.LocationCode = "";
|
}
|
|
try
|
{
|
//处理OrderDetailIds,分割并转换为ID列表
|
List<long> orderDetailIds = new List<long>();
|
if (!string.IsNullOrEmpty(lockInfo.OrderDetailIds))
|
{
|
orderDetailIds = lockInfo.OrderDetailIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
.Select(id =>
|
{
|
if (long.TryParse(id.Trim(), out long result))
|
{
|
return result;
|
}
|
return 0; // 无效ID标记为0
|
})
|
.Where(id => id > 0)
|
.OrderByDescending(id => id)
|
.ToList();
|
}
|
|
if (orderDetailIds.Count == 0)
|
{
|
return WebResponseContent.Instance.Error("单据锁定出库单明细Id无效,检查锁定出库数据是否正确");
|
}
|
|
//查询对应的订单明细
|
List<Dt_OutboundOrderDetail> orderDetails = _outboundRepository.Db.Queryable<Dt_OutboundOrderDetail>()
|
.Where(x => orderDetailIds.Contains(x.Id))
|
.ToList();
|
|
if (orderDetails.Count == 0)
|
{
|
return WebResponseContent.Instance.Error("未找到可回滚明细,请检查出库单明细");
|
}
|
|
decimal remainingRollbackQty = (decimal)rollbackTotalQuantity;
|
foreach (var detail in orderDetails)
|
{
|
if (remainingRollbackQty <= 0)
|
{
|
break;
|
}
|
|
// 计算该明细的可回滚数量
|
decimal availableRollbackQty = detail.LockQuantity - detail.OverOutQuantity - detail.MoveQty;
|
|
availableRollbackQty = Math.Max(0, availableRollbackQty);
|
|
if (availableRollbackQty <= 0)
|
{
|
continue; // 该明细无可回滚数量,跳过
|
}
|
|
// 计算本次实际回滚数量(取可回滚数量和剩余需要回滚数量的较小值)
|
decimal actualRollbackQty = Math.Min(availableRollbackQty, remainingRollbackQty);
|
|
detail.LockQuantity -= actualRollbackQty;
|
detail.LockQuantity = Math.Max(0, detail.LockQuantity);
|
|
_detailRepository.UpdateData(detail);
|
|
//更新剩余需要回滚的数量
|
remainingRollbackQty -= actualRollbackQty;
|
}
|
|
if (remainingRollbackQty > 0)
|
{
|
return WebResponseContent.Instance.Error($"剩余回滚数量{remainingRollbackQty}");
|
}
|
_outboundLockInfoRepository.DeleteAndMoveIntoHty(lockInfo, WIDESEA_Core.Enums.OperateTypeEnum.人工删除);
|
}
|
catch (Exception ex)
|
{
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
stock.StockStatus = (int)StockStatusEmun.入库确认;
|
stock.LocationCode = "";
|
}
|
|
var task = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (task != null)
|
{
|
return content.Error($"托盘{palletCode}存在任务回库失败!");
|
}
|
|
// 分配新货位
|
var newLocation = _locationInfoService.AssignLocation(stock.LocationType);
|
|
if(newLocation == null)
|
{
|
return WebResponseContent.Instance.Error("没有空闲库位可回库");
|
}
|
|
var newTask = new Dt_Task()
|
{
|
CurrentAddress = stations.GetValueOrDefault(station) ?? "",
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
OrderNo = OrderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations.GetValueOrDefault(station) ?? "",
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = (int)TaskStatusEnum.New,
|
TaskType = stock.Details.Count > 0 ? (int)TaskTypeEnum.InPick : (int)TaskTypeEnum.InEmpty,
|
PalletType = stock.PalletType,
|
WarehouseId = stock.WarehouseId
|
};
|
_stockInfoRepository.UpdateData(stock);
|
_taskRepository.AddData(newTask);
|
|
var moveResult = await _eSSApiService.MoveContainerAsync(new MoveContainerRequest
|
{
|
slotCode = movestations[station],
|
containerCode = palletCode
|
});
|
return content.OK();
|
|
}
|
catch (Exception ex)
|
{
|
return content.Error(ex.Message);
|
}
|
}
|
|
public WebResponseContent RecheckPicking(RecheckPickingDTO pickingDTO)
|
{
|
try
|
{
|
Dt_ReCheckOrder reCheckOrder = _outboundRepository.Db.Queryable<Dt_ReCheckOrder>().Where(x => x.OrderNo == pickingDTO.orderNo && x.Result == 0).First();
|
if(reCheckOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到该待重拣的单据{pickingDTO.orderNo}");
|
}
|
Dt_StockInfoDetail stockInfoDetail = _stockDetailRepository.QueryFirst(x=>x.Barcode == pickingDTO.barCode && x.Status == StockStatusEmun.手动冻结.ObjToInt() );
|
if(stockInfoDetail == null)
|
{
|
return WebResponseContent.Instance.Error($"未在库存中找到该冻结/隔离条码 {pickingDTO.barCode}");
|
}
|
if (stockInfoDetail.MaterielCode != reCheckOrder.MaterielCode || stockInfoDetail.BatchNo != reCheckOrder.BatchNo)
|
{
|
return WebResponseContent.Instance.Error("该条码的物料编码和批次和该重检单不符");
|
}
|
stockInfoDetail.OrderNo = pickingDTO.orderNo;
|
stockInfoDetail.Status = StockStatusEmun.重检中.ObjToInt();
|
var currentRemark = _outboundRepository.Db.Queryable<Dt_OutboundOrder>()
|
.Where(x => x.OrderNo == pickingDTO.orderNo)
|
.Select(x => x.Remark)
|
.First();
|
|
string newRemark;
|
if (string.IsNullOrWhiteSpace(currentRemark))
|
{
|
newRemark = pickingDTO.barCode;
|
}
|
else
|
{
|
var existingCodes = currentRemark.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
.Select(s => s.Trim())
|
.ToList();
|
|
if (!existingCodes.Contains(pickingDTO.barCode))
|
{
|
existingCodes.Add(pickingDTO.barCode);
|
newRemark = string.Join(",", existingCodes);
|
}
|
else
|
{
|
newRemark = currentRemark;
|
}
|
}
|
_outboundRepository.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => x.Remark == newRemark)
|
.SetColumns(x=>x.OrderStatus == (int)OutOrderStatusEnum.出库完成)
|
.Where(x => x.OrderNo == pickingDTO.orderNo)
|
.ExecuteCommand();
|
_stockDetailRepository.UpdateData(stockInfoDetail);
|
|
return WebResponseContent.Instance.OK();
|
}
|
catch(Exception ex)
|
{
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
#endregion
|
|
#region 撤销拣选
|
/// <summary>
|
/// 撤销拣选条码(反向回滚出库拣选操作)
|
/// </summary>
|
/// <param name="request">撤销拣选请求(至少包含条码、订单号、托盘号)</param>
|
/// <returns>撤销响应</returns>
|
public WebResponseContent ReversePicking(ReversePickingRequestDTO request)
|
{
|
WebResponseContent content = WebResponseContent.Instance;
|
ReversePickingResponseDTO response = new ReversePickingResponseDTO();
|
|
try
|
{
|
if (string.IsNullOrWhiteSpace(request.Barcode) || string.IsNullOrWhiteSpace(request.OrderNo) || string.IsNullOrWhiteSpace(request.PalletCode))
|
{
|
response.Success = false;
|
response.Message = "条码、订单号、托盘号不能为空";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode);
|
if (stockInfo == null)
|
{
|
response.Success = false;
|
response.Message = $"托盘号 {request.PalletCode} 对应的库存不存在";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
|
if (outboundOrder == null)
|
{
|
response.Success = false;
|
response.Message = $"出库单 {request.OrderNo} 不存在";
|
return WebResponseContent.Instance.Error(response.Message);
|
}
|
|
Dt_StockInfoDetail_Hty historyDetail = _stockDetailHistoryRepository.QueryFirst(x =>
|
x.Barcode == request.Barcode &&
|
|
(x.OperateType == "出库完成" || x.OperateType == "拆包-原始记录"));
|
|
if(historyDetail != null)
|
{
|
double minutesDiff = (DateTime.Now - historyDetail.InsertTime).TotalMinutes;
|
if (minutesDiff >= 30)
|
{
|
return WebResponseContent.Instance.Error($"条码{request.Barcode}已无法撤销");
|
}
|
}
|
|
Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
|
x.OrderNo == request.OrderNo &&
|
x.StockId == stockInfo.Id &&
|
x.MaterielCode == historyDetail.MaterielCode &&
|
x.PalletCode == stockInfo.PalletCode);
|
|
_unitOfWorkManage.BeginTran();
|
try
|
{
|
if(lockInfo == null)
|
{
|
return WebResponseContent.Instance.Error("该托盘已全部拣选完,不允许撤销");
|
}
|
bool isUnpack = historyDetail.OperateType == "拆包-原始记录";
|
if (isUnpack)
|
{
|
return WebResponseContent.Instance.Error("该条码已拆包,不允许撤销");
|
}
|
else
|
{
|
ReverseFullOutboundOperation(historyDetail, stockInfo, request);
|
}
|
RollbackOutboundOrderDetails(historyDetail.MaterielCode, request.OrderNo, historyDetail.StockQuantity, stockInfo.Id);
|
|
if (lockInfo != null)
|
{
|
lockInfo.SortedQuantity = Math.Max(0, (decimal)(lockInfo.SortedQuantity - historyDetail.StockQuantity));
|
|
if (lockInfo.SortedQuantity == 0)
|
{
|
_outboundLockInfoRepository.UpdateData(lockInfo);
|
}
|
else
|
{
|
_outboundLockInfoRepository.UpdateData(lockInfo);
|
}
|
}
|
|
_stockDetailHistoryRepository.DeleteData(historyDetail);
|
|
DeleteStockChangeRecord(request.Barcode, request.OrderNo);
|
|
if (!CheckOutboundOrderCompleted(request.OrderNo))
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库中.ObjToInt());
|
}
|
|
_unitOfWorkManage.CommitTran();
|
|
response.Success = true;
|
response.Message = $"条码 {request.Barcode} 撤销拣选成功";
|
response.Barcode = request.Barcode;
|
response.RestoredQuantity = historyDetail.StockQuantity;
|
|
content = WebResponseContent.Instance.OK(data: response);
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
response.Success = false;
|
response.Message = $"撤销拣选失败:{ex.Message}";
|
content = WebResponseContent.Instance.Error(response.Message);
|
}
|
}
|
catch (Exception ex)
|
{
|
response.Success = false;
|
response.Message = $"处理撤销拣选失败:{ex.Message}";
|
content = WebResponseContent.Instance.Error(response.Message);
|
}
|
|
return content;
|
}
|
|
/// <summary>
|
/// 撤销拆包出库操作(反向恢复拆包前状态)
|
/// </summary>
|
private void ReverseUnpackOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request, Dt_OutboundOrder outboundOrder)
|
{
|
Dt_StockInfoDetail currentDetail = _stockDetailRepository.QueryFirst(x =>
|
x.Barcode == request.Barcode &&
|
x.StockId == stockInfo.Id &&
|
x.MaterielCode == historyDetail.MaterielCode);
|
|
if (currentDetail != null)
|
{
|
currentDetail.StockQuantity = historyDetail.StockQuantity;
|
currentDetail.Remark = $"撤销拆包拣选,恢复原始数量:{historyDetail.StockQuantity},操作者:{request.Operator}";
|
_stockDetailRepository.UpdateData(currentDetail);
|
}
|
else
|
{
|
Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail
|
{
|
StockId = historyDetail.StockId,
|
MaterielCode = historyDetail.MaterielCode,
|
MaterielName = historyDetail.MaterielName,
|
Barcode =historyDetail.Barcode,
|
OrderNo = historyDetail.OrderNo,
|
BatchNo = historyDetail.BatchNo,
|
ProductionDate = historyDetail.ProductionDate,
|
EffectiveDate = historyDetail.EffectiveDate,
|
SerialNumber = historyDetail.SerialNumber,
|
StockQuantity = historyDetail.StockQuantity,
|
OutboundQuantity = historyDetail.OutboundQuantity,
|
Status = historyDetail.Status,
|
Unit = historyDetail.Unit,
|
InboundOrderRowNo = historyDetail.InboundOrderRowNo,
|
SupplyCode = historyDetail.SupplyCode,
|
Creater = request.Operator,
|
CreateDate = DateTime.Now,
|
FactoryArea = historyDetail.FactoryArea,
|
WarehouseCode = historyDetail.WarehouseCode,
|
Remark = $"撤销拆包拣选,重新创建库存明细,条码:{request.Barcode},操作者:{request.Operator}"
|
};
|
_stockDetailRepository.AddData(restoreDetail);
|
}
|
|
List<Dt_MaterialCodeInfo> materialCodeInfos = _basicService.MaterielCodeInfoService.Repository.QueryData(x =>
|
x.OldBarcode == request.Barcode &&
|
x.OrderNo == request.OrderNo);
|
if (materialCodeInfos.Any())
|
{
|
_basicService.MaterielCodeInfoService.Repository.DeleteData(materialCodeInfos);
|
}
|
|
if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x =>
|
x.Barcode == request.Barcode &&
|
x.OrderNo == request.OrderNo);
|
if (allocateMaterialInfo != null)
|
{
|
_allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 撤销完整出库操作(恢复被删除的库存明细)
|
/// </summary>
|
private void ReverseFullOutboundOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request)
|
{
|
Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail
|
{
|
|
StockId = historyDetail.StockId,
|
MaterielCode = historyDetail.MaterielCode,
|
MaterielName = historyDetail.MaterielName,
|
Barcode = historyDetail.Barcode,
|
OrderNo = historyDetail.OrderNo,
|
BatchNo = historyDetail.BatchNo,
|
ProductionDate = historyDetail.ProductionDate,
|
EffectiveDate = historyDetail.EffectiveDate,
|
SerialNumber = historyDetail.SerialNumber,
|
StockQuantity = historyDetail.StockQuantity,
|
OutboundQuantity = historyDetail.OutboundQuantity - historyDetail.StockQuantity,
|
Status = historyDetail.Status,
|
Unit = historyDetail.Unit,
|
InboundOrderRowNo = historyDetail.InboundOrderRowNo,
|
SupplyCode = historyDetail.SupplyCode,
|
Creater = request.Operator,
|
CreateDate = DateTime.Now,
|
FactoryArea = historyDetail.FactoryArea,
|
WarehouseCode = historyDetail.WarehouseCode,
|
Remark = $"撤销完整出库拣选,恢复库存明细,条码:{request.Barcode},操作者:{request.Operator}"
|
};
|
|
_stockDetailRepository.AddData(restoreDetail);
|
|
if (request.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
|
{
|
Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x =>
|
x.Barcode == request.Barcode &&
|
x.OrderNo == request.OrderNo);
|
if (allocateMaterialInfo != null)
|
{
|
_allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo);
|
}
|
}
|
}
|
|
/// <summary>
|
/// 回滚出库单明细(扣减已出库数量)
|
/// </summary>
|
private void RollbackOutboundOrderDetails(string materielCode, string orderNo, decimal rollbackQuantity, int stockId)
|
{
|
Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
|
List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
|
x.OrderId == outboundOrder.Id &&
|
x.MaterielCode == materielCode &&
|
x.OverOutQuantity > 0);
|
|
if (!details.Any()) return;
|
|
decimal remainingRollbackQty = rollbackQuantity;
|
foreach (var detail in details)
|
{
|
if (remainingRollbackQty <= 0) break;
|
|
decimal rollbackQty = Math.Min(remainingRollbackQty, detail.OverOutQuantity);
|
|
detail.OverOutQuantity -= rollbackQty;
|
detail.CurrentDeliveryQty -= rollbackQty;
|
|
if (detail.OrderDetailStatus == (int)OrderDetailStatusEnum.Over && detail.OverOutQuantity < detail.OrderQuantity - detail.MoveQty)
|
{
|
detail.OrderDetailStatus = (int)OrderDetailStatusEnum.New;
|
}
|
|
if (!string.IsNullOrEmpty(detail.ReturnJsonData))
|
{
|
List<Barcodes> barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(detail.ReturnJsonData) ?? new List<Barcodes>();
|
var targetBarcode = barcodesList.FirstOrDefault(x => x.Barcode == materielCode);
|
if (targetBarcode != null)
|
{
|
barcodesList.Remove(targetBarcode);
|
detail.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, new JsonSerializerSettings
|
{
|
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
});
|
}
|
}
|
|
_detailRepository.UpdateData(detail);
|
remainingRollbackQty -= rollbackQty;
|
}
|
}
|
|
/// <summary>
|
/// 删除库存变动记录(拣选时生成的)
|
/// </summary>
|
private void DeleteStockChangeRecord(string barcode, string orderNo)
|
{
|
Dt_StockQuantityChangeRecord changeRecord = _stockChangeRepository.QueryFirst(x =>
|
x.OriginalSerilNumber == barcode &&
|
x.OrderNo == orderNo &&
|
x.ChangeType == (int)StockChangeTypeEnum.Outbound);
|
if (changeRecord != null)
|
{
|
_stockChangeRepository.DeleteData(changeRecord);
|
}
|
}
|
|
|
#endregion
|
|
|
}
|
}
|