using System.Reflection.Emit;
|
using AutoMapper;
|
using Dm.filter;
|
using MailKit.Search;
|
using Mapster;
|
using Newtonsoft.Json;
|
using Newtonsoft.Json.Serialization;
|
using Org.BouncyCastle.Asn1.Ocsp;
|
using Org.BouncyCastle.Crypto;
|
using SqlSugar;
|
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.Basic;
|
using WIDESEA_DTO.CalcOut;
|
using WIDESEA_DTO.ReturnMES;
|
using WIDESEA_IBasicService;
|
using WIDESEA_IOutboundService;
|
using WIDESEA_IRecordService;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
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;
|
|
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)
|
{
|
_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;
|
}
|
|
#region 出库分配
|
/// <summary>
|
/// 分拣出库操作
|
/// </summary>
|
/// <param name="request">分拣出库请求</param>
|
/// <returns>分拣出库响应</returns>
|
public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
|
{
|
WebResponseContent content = WebResponseContent.Instance;
|
|
PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
// 1. 计算出库数量逻辑
|
OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request);
|
|
if (!calculationResult.CanOutbound)
|
{
|
content = WebResponseContent.Instance.Error("无法处理拣货出库:" + calculationResult.ErrorMessage);
|
_unitOfWorkManage.RollbackTran();
|
return content;
|
}
|
|
// 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_Task> tasks = new List<Dt_Task>();
|
foreach (var materielCalc in calculationResult.MaterielCalculations)
|
{
|
(List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
|
|
foreach (var item in materielPickedDetails.OutStockLockInfo)
|
{
|
Dt_OutStockLockInfo? outStockLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
|
if (outStockLockInfo != null)
|
{
|
outStockLockInfo = item;
|
Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
|
if (task != null)
|
{
|
outStockLockInfo.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);
|
}
|
foreach (var item in materielPickedDetails.Tasks)
|
{
|
Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
|
if (task == null)
|
{
|
tasks.Add(item);
|
}
|
}
|
|
pickedDetails.AddRange(materielPickedDetails.PickedDetails);
|
|
decimal allallocatedQuantity = Math.Min(materielCalc.UnallocatedQuantity, materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity));
|
materielCalc.UnallocatedQuantity = allallocatedQuantity;
|
// 更新出库单明细(增加锁定数量,不增加已出数量)
|
foreach (var detail in materielCalc.Details)
|
{
|
if (allallocatedQuantity <= 0) break;
|
|
decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity);
|
if (lockQuantity < materielCalc.UnallocatedQuantity)
|
{
|
detail.LockQuantity += lockQuantity; // 增加锁定数量 不更新 OverOutQuantity 和 OrderDetailStatus,因为还没有实际出库
|
outboundOrderDetails.Add(detail);
|
materielCalc.UnallocatedQuantity -= lockQuantity;
|
}
|
else
|
{
|
detail.LockQuantity += materielCalc.UnallocatedQuantity;
|
outboundOrderDetails.Add(detail);
|
break;
|
}
|
}
|
}
|
|
// 3. 更新出库单状态为出库中(表示已有任务分配)
|
UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
|
|
// 4. 更新出库单明细锁定数量
|
_detailRepository.UpdateData(outboundOrderDetails);
|
|
// 5. 更新库存状态
|
UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
|
|
// 6. 更新货位状态
|
UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt());
|
|
// 7. 更新库存详情
|
UpdateOutStockLockInfo(outStockLockInfos);
|
|
// 8. 添加任务数据
|
_taskRepository.AddData(tasks);
|
|
_unitOfWorkManage.CommitTran();
|
|
response.Success = true;
|
response.Message = "分拣任务分配成功";
|
response.Tasks = tasks; // 返回第一个任务号
|
response.PickedDetails = pickedDetails; // 返回第一个分拣明细
|
content = WebResponseContent.Instance.OK("分拣任务分配成功", response);
|
return content;
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
|
content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message);
|
}
|
return content;
|
}
|
|
/// <summary>
|
/// 计算出库数量逻辑
|
/// </summary>
|
/// <param name="request"></param>
|
/// <returns></returns>
|
public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
|
{
|
OutboundCalculationDTO result = new();
|
|
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 (outboundOrder.IsBatch == 1 && request.DetailIds.Count == 1)
|
//{
|
// selectedDetails = _detailRepository.QueryData(x => x.OrderId == selectedDetails.First().OrderId && x.WarehouseCode == selectedDetails.First().WarehouseCode && x.MaterielCode == selectedDetails.First().MaterielCode && x.BatchNo == selectedDetails.First().BatchNo && x.SupplyCode == selectedDetails.First().SupplyCode);
|
//}
|
|
|
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;
|
return result;
|
}
|
catch (Exception ex)
|
{
|
result.CanOutbound = false;
|
result.ErrorMessage = ex.Message;
|
return result;
|
}
|
|
}
|
|
/// <summary>
|
/// 多出库单明细时,按物料分组计算出库数量
|
/// </summary>
|
/// <param name="selectedDetails"></param>
|
/// <returns></returns>
|
private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails)
|
{
|
// 按物料分组:物料编号、批次号、供应商编号、仓库编号
|
List<MaterielOutboundCalculationDTO> materielGroups = 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();
|
|
return materielGroups;
|
}
|
|
/// <summary>
|
/// 处理物料的任务生成
|
/// </summary>
|
/// <param name="outboundOrder">出库订单</param>
|
/// <param name="materielCalc">按物料的出库计算结果</param>
|
/// <param name="request">分拣出库请求</param>
|
/// <param name="factoryArea"></param>
|
/// <returns></returns>
|
/// <exception cref="Exception"></exception>
|
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_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
|
|
if (!stockQuery.Any())
|
{
|
throw new Exception($"物料 {materielCalc.MaterielCode} 对应的库存不存在");
|
}
|
|
// 批量计算总可用库存数量
|
(Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) data = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
|
|
// 可用库存数量映射
|
Dictionary<int, decimal> availableStockMap = data.AvailableStockMap;
|
|
// 物料总可用库存数量
|
decimal totalAvailableStock = availableStockMap.Values.Sum();
|
|
// 已锁定库存数量映射
|
Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = data.LockStockMap;
|
|
// 验证总可用库存是否满足出库需求
|
//if (totalAvailableStock < materielCalc.UnallocatedQuantity)
|
//{
|
// throw new Exception($"物料 {materielCalc.MaterielCode} 可用库存 {totalAvailableStock} 不足出库数量 {materielCalc.UnallocatedQuantity}");
|
//}
|
// 需分配数量
|
decimal remainingQuantity = Math.Min(totalAvailableStock, materielCalc.UnallocatedQuantity);
|
|
// 需分配数量
|
//decimal remainingQuantity = materielCalc.UnallocatedQuantity;
|
|
// 已分配的托盘列表
|
List<string> allocatedPallets = new List<string>();
|
|
// 获取第一个出库明细
|
Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First();
|
|
// 预加载库存明细和锁定记录
|
List<int> stockIds = stockQuery.Select(x => x.Id).ToList();
|
Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details);
|
|
// 记录每个托盘的实际分配量
|
Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>();
|
|
List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
|
foreach (var stock in stockQuery)
|
{
|
if (remainingQuantity <= 0) break;
|
|
// 当前库存可用数量
|
decimal availableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
|
if (availableQuantity <= 0) continue;
|
|
// 计算该托盘可分配数量
|
decimal allocateQuantity = Math.Min(remainingQuantity, availableQuantity);
|
(decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) actualAllocated = AllocateStockQuantity(stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, lockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()), stockDetailMap);
|
|
// 本次分配的数量
|
decimal actualAllocatedQuantity = actualAllocated.ActualAllocatedQuantity;
|
|
if (actualAllocatedQuantity > 0)
|
{
|
allocatedPallets.Add(stock.PalletCode);
|
palletAllocations[stock.PalletCode] = actualAllocatedQuantity; // 记录实际分配量
|
remainingQuantity -= actualAllocatedQuantity;
|
lockInfoList.AddRange(actualAllocated.LockInfoList);
|
}
|
}
|
|
foreach (var palletCode in allocatedPallets)
|
{
|
Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode);
|
|
// 获取实际分配的数量
|
decimal actualAllocatedQuantity = palletAllocations.GetValueOrDefault(palletCode, 0);
|
|
// 计算分配后剩余的库存数量
|
decimal originalAvailableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
|
decimal remainingStockQuantity = Math.Max(0, originalAvailableQuantity - actualAllocatedQuantity);
|
|
pickedDetails.Add(new PickedStockDetailDTO
|
{
|
PalletCode = palletCode,
|
MaterielCode = materielCalc.MaterielCode,
|
OutboundQuantity = actualAllocatedQuantity, // 本次实际分配的出库量
|
RemainingQuantity = remainingStockQuantity, // 分配后剩余的可用库存
|
LocationCode = stock.LocationCode,
|
OutStockLockInfos = lockInfoList
|
});
|
|
Dt_OutStockLockInfo? outStockLockInfo = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode);
|
int taskNum = outStockLockInfo?.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 == stock.PalletCode) == null) generatedTasks.Add(task);
|
}
|
|
return (pickedDetails, generatedTasks, lockInfoList);
|
}
|
|
/// <summary>
|
/// 生成出库任务
|
/// </summary>
|
/// <param name="stockInfo"></param>
|
/// <param name="taskType"></param>
|
/// <param name="outStation"></param>
|
/// <returns></returns>
|
public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation)
|
{
|
|
Dt_Task task = new()
|
{
|
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,
|
};
|
|
return task;
|
}
|
|
/// <summary>
|
/// 构建库存查询条件(包含库存信息和库存明细)
|
/// </summary>
|
/// <param name="materielCalc"></param>
|
/// <param name="factoryArea"></param>
|
/// <returns></returns>
|
private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
|
{
|
// 基础查询条件:物料编号、批次号(如果提供)、库存数量>0
|
ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0);
|
|
// 根据条件添加供应商编号匹配(不为空时才需要匹配)
|
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() /*|| x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
|
|
// 在内存中关联数据
|
foreach (var stockInfo in stockInfos)
|
{
|
stockInfo.Details = new List<Dt_StockInfoDetail>();
|
stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
|
}
|
|
return stockInfos;
|
}
|
|
/// <summary>
|
/// 批量获取托盘可用库存信息
|
/// </summary>
|
/// <param name="stockInfos">库存信息列表</param>
|
/// <param name="materielCode">物料编号</param>
|
/// <returns>返回值为(库存主键,可用数量)字典</returns>
|
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>
|
/// 分配库存
|
/// </summary>
|
/// <param name="stockInfo">库存信息</param>
|
/// <param name="allocateQuantity">要分配的数量</param>
|
/// <param name="availableQuantity">可分配的数量</param>
|
/// <param name="outboundOrder">出库单</param>
|
/// <param name="detail">出库单明细</param>
|
/// <param name="request"></param>
|
/// <param name="lockInfos"></param>
|
/// <param name="stockDetailMap"></param>
|
/// <returns></returns>
|
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 actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity); // 实际分配数量
|
List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
|
if (actualAllocatedQuantity > 0)
|
{
|
//检查目标位置一致性:如果托盘已有锁定记录且目标位置不同,则不允许分配
|
if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation))
|
{
|
if (!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)
|
{
|
// 追加当前明细ID到OrderDetailIds字段(避免重复)
|
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);
|
|
// 更新分配出库量
|
decimal beforeAssignQuantity = totalAllocatedQuantity; // 本次分配前的总累计量
|
lockInfo.AssignQuantity += actualAllocatedQuantity; // 本次分配数量
|
lockInfo.AllocatedQuantity = beforeAssignQuantity; // 记录本次分配前的总累计量
|
|
lockInfoList.Add(lockInfo);
|
}
|
else
|
{
|
// 创建新的锁定记录(使用预加载的库存明细)
|
decimal originalQuantity = 0;
|
if (stockDetailMap?.ContainsKey(stockInfo.Id) == true)
|
{
|
originalQuantity = stockDetailMap[stockInfo.Id].Sum(x => x.StockQuantity);
|
}
|
|
// 获取该物料在该订单中的所有明细ID
|
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), // 记录该物料的所有明细ID
|
OrderType = outboundOrder.OrderType,
|
BatchNo = detail.BatchNo,
|
MaterielCode = detail.MaterielCode,
|
MaterielName = detail.MaterielName,
|
StockId = stockInfo.Id,
|
OrderQuantity = allDetailIds.SelectMany(id => 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 // 未关联具体明细ID
|
};
|
lockInfoList.Add(lockInfo);
|
}
|
}
|
return (actualAllocatedQuantity, lockInfoList);
|
}
|
|
/// <summary>
|
/// 计算该托盘累计已分配数量
|
/// </summary>
|
/// <param name="lockInfos"></param>
|
/// <param name="stockId"></param>
|
/// <param name="materielCode"></param>
|
/// <returns></returns>
|
private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
|
{
|
// 查询该托盘该物料在所有锁定记录中的最大已分配数量
|
List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
|
x.StockId == stockId &&
|
x.MaterielCode == materielCode);
|
|
if (lockRecords == null || !lockRecords.Any())
|
{
|
return 0;
|
}
|
|
// 返回累计已分配数量
|
return lockRecords.Sum(x => x.AssignQuantity);
|
}
|
|
/// <summary>
|
/// 更新出库单状态
|
/// </summary>
|
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;
|
_outboundRepository.UpdateData(outboundOrder);
|
return true;
|
}
|
catch
|
{
|
return false;
|
}
|
}
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="palletCodes"></param>
|
/// <param name="status"></param>
|
/// <returns></returns>
|
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;
|
}
|
}
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="locationCodes"></param>
|
/// <param name="status"></param>
|
/// <returns></returns>
|
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;
|
}
|
}
|
|
/// <summary>
|
///
|
/// </summary>
|
/// <param name="outStockLockInfos"></param>
|
/// <returns></returns>
|
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);
|
}
|
|
bool isMatMixed = stockInfo.Details.GroupBy(x => new
|
{
|
x.MaterielCode,
|
x.MaterielName,
|
x.BatchNo,
|
x.SupplyCode,
|
x.WarehouseCode
|
}).Count() > 1;
|
|
if (isMatMixed)
|
{
|
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);
|
}
|
|
// 找出已分配的订单明细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 != 0)
|
{
|
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);
|
}
|
}
|
|
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;
|
|
foreach (var stockDetail in stockInfoDetails)
|
{
|
if (itemQuantity >= stockDetail.StockQuantity - stockDetail.OutboundQuantity)
|
{
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = stockDetail.Barcode,
|
Qty = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = stockDetail?.Unit ?? ""
|
};
|
|
itemQuantity -= (stockDetail.StockQuantity - stockDetail.OutboundQuantity);
|
stockDetail.OutboundQuantity = stockDetail.StockQuantity;
|
barcodesList.Add(barcodes);
|
|
if (itemQuantity <= 0) break;
|
}
|
else
|
{
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = stockDetail.Barcode,
|
Qty = itemQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = stockDetail?.Unit ?? ""
|
};
|
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);
|
item.OverOutQuantity = item.LockQuantity;
|
item.CurrentDeliveryQty = 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);
|
}
|
|
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 (CheckOutboundOrderCompleted(request.OrderNo))
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
|
|
if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.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);
|
|
// 删除库存明细记录
|
_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 != 0)
|
{
|
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 != 0)
|
{
|
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 != 0)
|
{
|
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);
|
item.OverOutQuantity = item.LockQuantity;
|
item.CurrentDeliveryQty = item.LockQuantity;
|
}
|
|
updateDetails.Add(item);
|
|
List<Barcodes> barcodesList = new List<Barcodes>();
|
Barcodes barcodes = new Barcodes
|
{
|
Barcode = request.Barcode,
|
Qty = barcodeQuantity,
|
SupplyCode = stockDetail?.SupplyCode ?? "",
|
BatchNo = stockDetail?.BatchNo ?? "",
|
Unit = stockDetail?.Unit ?? ""
|
};
|
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 (CheckOutboundOrderCompleted(request.OrderNo))
|
{
|
UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库完成.ObjToInt());
|
|
if (outboundOrder.OrderType != OutOrderTypeEnum.InternalAllocat.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;
|
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>
|
private (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>
|
private 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,
|
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,
|
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);
|
}
|
|
#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();
|
|
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.OrderNo == OrderNo &&
|
x.StockId == stock.Id &&
|
x.PalletCode == palletCode);
|
|
if (lockInfo != null && lockInfo.SortedQuantity != lockInfo.AssignQuantity)
|
{
|
return content.Error($"托盘{palletCode}库存未拣选完不允许回库");
|
}
|
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);
|
|
var newTask = new Dt_Task()
|
{
|
CurrentAddress = stations[station],
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
OrderNo = OrderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[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);
|
}
|
}
|
|
#endregion
|
}
|
}
|