using Microsoft.Extensions.Logging;
|
using SqlSugar;
|
using SqlSugar.Extensions;
|
using System;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using System.Threading.Tasks;
|
using WIDESEA_BasicService;
|
using WIDESEA_Common.CommonEnum;
|
using WIDESEA_Common.OrderEnum;
|
using WIDESEA_Common.StockEnum;
|
using WIDESEA_Common.TaskEnum;
|
using WIDESEA_Core;
|
using WIDESEA_Core.BaseRepository;
|
using WIDESEA_Core.BaseServices;
|
using WIDESEA_Core.Enums;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.Outbound;
|
using WIDESEA_IAllocateService;
|
using WIDESEA_IBasicService;
|
using WIDESEA_IOutboundService;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
using WIDESEA_Model.Models.Basic;
|
using WIDESEA_Model.Models.Outbound;
|
using static WIDESEA_OutboundService.OutboundBatchPickingService;
|
|
namespace WIDESEA_OutboundService
|
{
|
public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundBatchPickingService
|
{
|
|
|
private readonly IUnitOfWorkManage _unitOfWorkManage;
|
public IRepository<Dt_PickingRecord> Repository => BaseDal;
|
|
private readonly IStockInfoService _stockInfoService;
|
private readonly IStockService _stockService;
|
private readonly IOutStockLockInfoService _outStockLockInfoService;
|
private readonly IStockInfoDetailService _stockInfoDetailService;
|
private readonly ILocationInfoService _locationInfoService;
|
private readonly IOutboundOrderDetailService _outboundOrderDetailService;
|
private readonly IOutboundOrderService _outboundOrderService;
|
private readonly ISplitPackageService _splitPackageService;
|
private readonly IRepository<Dt_Task> _taskRepository;
|
private readonly IESSApiService _eSSApiService;
|
private readonly IInvokeMESService _invokeMESService;
|
private readonly IDailySequenceService _dailySequenceService;
|
private readonly IAllocateService _allocateService;
|
private readonly IRepository<Dt_OutboundBatch> _outboundBatchRepository;
|
private readonly ILogger<OutboundPickingService> _logger;
|
|
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 OutboundBatchPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService,
|
IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService,
|
IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService,
|
IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_OutboundBatch> outboundBatchRepository) : base(BaseDal)
|
{
|
_unitOfWorkManage = unitOfWorkManage;
|
_stockInfoService = stockInfoService;
|
_stockService = stockService;
|
_outStockLockInfoService = outStockLockInfoService;
|
_stockInfoDetailService = stockInfoDetailService;
|
_locationInfoService = locationInfoService;
|
_outboundOrderDetailService = outboundOrderDetailService;
|
_splitPackageService = splitPackageService;
|
_outboundOrderService = outboundOrderService;
|
_taskRepository = taskRepository;
|
_eSSApiService = eSSApiService;
|
_logger = logger;
|
_invokeMESService = invokeMESService;
|
_dailySequenceService = dailySequenceService;
|
_allocateService = allocateService;
|
_outboundBatchRepository = outboundBatchRepository;
|
}
|
|
// <summary>
|
/// 获取托盘的锁定信息
|
/// </summary>
|
public async Task<List<PalletLockInfoDto>> GetPalletLockInfos(string orderNo, string palletCode)
|
{
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.Select(x => new
|
{
|
x.Id,
|
x.OrderNo,
|
x.BatchNo,
|
x.MaterielCode,
|
x.CurrentBarcode,
|
x.AssignQuantity,
|
x.PickedQty,
|
x.Status,
|
x.LocationCode,
|
x.PalletCode
|
}).ToListAsync();
|
|
var lockInfoDtos = lockInfos.Select(x => new PalletLockInfoDto
|
{
|
Id = x.Id,
|
OrderNo = x.OrderNo,
|
BatchNo = x.BatchNo,
|
MaterielCode = x.MaterielCode,
|
CurrentBarcode = x.CurrentBarcode,
|
AssignQuantity = x.AssignQuantity,
|
PickedQty = x.PickedQty,
|
Status = x.Status,
|
LocationCode = x.LocationCode,
|
PalletCode = x.PalletCode,
|
CanSplit = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.AssignQuantity - x.PickedQty > 0),
|
CanPick = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.PickedQty < x.AssignQuantity)
|
}).ToList();
|
|
return lockInfoDtos;
|
}
|
#region 查询方法
|
|
/// <summary>
|
/// 获取托盘的已拣选列表
|
/// </summary>
|
public async Task<List<PalletPickedInfoDto>> GetPalletPickedList(string orderNo, string palletCode)
|
{
|
var pickedList = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
!x.IsCancelled)
|
.Select(x => new PalletPickedInfoDto
|
{
|
Id = x.Id,
|
OrderNo = x.OrderNo,
|
OrderDetailId = x.OrderDetailId,
|
PalletCode = x.PalletCode,
|
Barcode = x.Barcode,
|
MaterielCode = x.MaterielCode,
|
PickedQty = x.PickQuantity,
|
PickTime = x.PickTime,
|
Operator = x.Operator,
|
LocationCode = x.LocationCode
|
})
|
.ToListAsync();
|
|
return pickedList;
|
}
|
|
/// <summary>
|
/// 获取托盘状态
|
/// </summary>
|
public async Task<PalletStatusDto> GetPalletStatus(string orderNo, string palletCode)
|
{
|
// 获取托盘的锁定信息
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
if (!lockInfos.Any())
|
{
|
return new PalletStatusDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Status = (int)PalletStatusEnum.无任务,
|
StatusText = "无任务",
|
TotalItems = 0,
|
CompletedItems = 0,
|
PendingItems = 0
|
};
|
}
|
|
var totalItems = lockInfos.Count;
|
var completedItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.拣选完成);
|
var pendingItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.出库中);
|
|
var status = PalletStatusEnum.拣选中;
|
if (pendingItems == 0 && completedItems > 0)
|
{
|
status = PalletStatusEnum.已完成;
|
}
|
else if (pendingItems > 0 && completedItems == 0)
|
{
|
status = PalletStatusEnum.未开始;
|
}
|
else if (pendingItems > 0 && completedItems > 0)
|
{
|
status = PalletStatusEnum.拣选中;
|
}
|
|
return new PalletStatusDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Status = (int)status,
|
StatusText = GetPalletStatusText(status),
|
TotalItems = totalItems,
|
CompletedItems = completedItems,
|
PendingItems = pendingItems
|
};
|
}
|
|
/// <summary>
|
/// 获取拆包信息
|
/// </summary>
|
public async Task<SplitPackageInfoDto> GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
|
{
|
// 查找锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.CurrentBarcode == barcode
|
//&& x.Status == (int)OutLockStockStatusEnum.出库中
|
)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
throw new Exception("未找到有效的锁定信息");
|
|
// 计算剩余可拆数量
|
var remainQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
return new SplitPackageInfoDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Barcode = barcode,
|
MaterielCode = lockInfo.MaterielCode,
|
RemainQuantity = remainQuantity,
|
AssignQuantity = lockInfo.AssignQuantity,
|
PickedQty = lockInfo.PickedQty
|
};
|
}
|
|
#endregion
|
|
#region 分批分拣
|
|
/// <summary>
|
/// 分批分拣确认
|
/// </summary>
|
public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 验证分拣请求
|
var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data;
|
|
// 检查是否需要自动拆包
|
var autoSplitResult = await CheckAndAutoSplitIfNeeded(lockInfo, stockDetail, palletCode);
|
if (autoSplitResult != null)
|
{
|
// 如果执行了自动拆包,重新获取最新的锁定信息和库存信息
|
var refreshedValidation = await ValidatePickingRequest(orderNo, palletCode, barcode);
|
if (!refreshedValidation.IsValid)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(refreshedValidation.ErrorMessage);
|
}
|
|
(lockInfo, orderDetail, stockDetail, batch) = refreshedValidation.Data;
|
|
|
var actualPickedQty = lockInfo.AssignQuantity;
|
var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
|
|
// 更新批次和订单数据
|
await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
|
|
// 记录拣选历史
|
await RecordPickingHistory(pickingResult, orderNo, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("自动拆包并分拣成功", autoSplitResult);
|
}
|
|
// 正常分拣流程(不需要自动拆包)
|
var normalPickedQty = lockInfo.AssignQuantity;
|
var normalPickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, normalPickedQty);
|
|
// 更新批次和订单数据
|
await UpdateBatchAndOrderData(batch, orderDetail, normalPickedQty, orderNo);
|
|
// 记录拣选历史
|
await RecordPickingHistory(normalPickingResult, orderNo, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("分拣成功", new
|
{
|
PickedQuantity = normalPickedQty,
|
Barcode = barcode,
|
MaterialCode = lockInfo.MaterielCode,
|
AutoSplitted = false
|
});
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"分拣失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"分拣失败:{ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 取消分拣
|
/// </summary>
|
public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 查找分拣记录
|
var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Barcode == barcode &&
|
!x.IsCancelled)
|
.OrderByDescending(x => x.PickTime)
|
.FirstAsync();
|
|
if (pickingRecord == null)
|
return WebResponseContent.Instance.Error("未找到分拣记录");
|
|
// 恢复锁定信息和库存
|
var revertResult = await RevertPickingData(pickingRecord);
|
|
// 更新批次和订单数据
|
await RevertBatchAndOrderData(pickingRecord, revertResult);
|
|
// 标记分拣记录为已取消
|
pickingRecord.IsCancelled = true;
|
pickingRecord.CancelTime = DateTime.Now;
|
pickingRecord.CancelOperator = App.User.UserName;
|
await Db.Updateable(pickingRecord).ExecuteCommandAsync();
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("取消分拣成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"取消分拣失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
|
}
|
}
|
#endregion
|
|
|
#region 取走空箱逻辑
|
|
|
|
/// <summary>
|
/// 验证空箱取走条件
|
/// </summary>
|
private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
|
{
|
// 获取托盘的所有锁定记录
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
if (!lockInfos.Any())
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
|
|
// 检查是否有未完成的锁定记录
|
var unfinishedLocks = lockInfos.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
|
|
if (unfinishedLocks.Any())
|
{
|
var unfinishedCount = unfinishedLocks.Count;
|
var unfinishedQty = unfinishedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
|
$"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
|
}
|
|
// 检查托盘上是否还有库存货物
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
|
if (stockInfo != null)
|
{
|
var remainingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockInfo.Id &&
|
x.Status == (int)StockStatusEmun.出库锁定 &&
|
x.StockQuantity > 0)
|
.ToListAsync();
|
|
if (remainingStock.Any())
|
{
|
var remainingQty = remainingStock.Sum(x => x.StockQuantity);
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
|
$"托盘上还有库存货物,数量{remainingQty},不能取走空箱");
|
}
|
}
|
|
// 获取已完成的锁定记录
|
var completedLocks = lockInfos.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
|
|
if (!completedLocks.Any())
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
|
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
|
}
|
|
/// <summary>
|
/// 清理已完成的锁定记录
|
/// </summary>
|
private async Task CleanupCompletedLocks(List<Dt_OutStockLockInfo> completedLocks)
|
{
|
foreach (var lockInfo in completedLocks)
|
{
|
// 标记锁定记录为已取走
|
lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 清理对应的库存记录状态
|
await CleanupStockInfo(lockInfo);
|
}
|
}
|
|
/// <summary>
|
/// 清理库存信息
|
/// </summary>
|
private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null && stockDetail.Status == (int)StockStatusEmun.出库完成)
|
{
|
stockDetail.Status = (int)StockStatusEmun.已清理;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
}
|
|
|
/// <summary>
|
/// 更新订单状态
|
/// </summary>
|
private async Task UpdateOrderStatusAfterPalletRemoval(string orderNo)
|
{
|
// 检查订单是否所有托盘都已完成
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo)
|
.ToListAsync();
|
|
var unfinishedPallets = allLocks
|
.GroupBy(x => x.PalletCode)
|
.Where(g => g.Any(x => x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中))
|
.ToList();
|
|
// 如果没有未完成的托盘,更新订单状态为出库完成
|
if (!unfinishedPallets.Any())
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = (int)OutOrderStatusEnum.出库完成,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
}
|
|
/// <summary>
|
/// 记录空箱取走历史
|
/// </summary>
|
private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
|
{
|
var removalRecord = new Dt_EmptyPalletRemoval
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
RemovalTime = DateTime.Now,
|
Operator = App.User.UserName,
|
CompletedItemsCount = completedLocks.Count,
|
TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
|
};
|
|
await Db.Insertable(removalRecord).ExecuteCommandAsync();
|
}
|
|
#endregion
|
|
private List<SplitResult> CreateSplitResults(Dt_OutStockLockInfo lockInfo, decimal splitQty, decimal remainQty, string newBarcode, string originalBarcode)
|
{
|
return new List<SplitResult>
|
{
|
new SplitResult
|
{
|
materialCode = lockInfo.MaterielCode,
|
supplierCode = lockInfo.SupplyCode,
|
quantityTotal = splitQty.ToString("F2"),
|
batchNumber = newBarcode,
|
batch = lockInfo.BatchNo,
|
factory = lockInfo.FactoryArea,
|
date = DateTime.Now.ToString("yyyy-MM-dd"),
|
},
|
new SplitResult
|
{
|
materialCode = lockInfo.MaterielCode,
|
supplierCode = lockInfo.SupplyCode,
|
quantityTotal = remainQty.ToString("F2"),
|
batchNumber = originalBarcode,
|
batch = lockInfo.BatchNo,
|
factory = lockInfo.FactoryArea,
|
date = DateTime.Now.ToString("yyyy-MM-dd"),
|
}
|
};
|
}
|
|
|
|
#region 手动拆包
|
|
/// <summary>
|
/// 手动拆包
|
/// </summary>
|
public async Task<WebResponseContent> ManualSplitPackage(string orderNo, string palletCode, string originalBarcode, decimal splitQuantity)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 验证拆包请求
|
var validationResult = await ValidateSplitRequest(orderNo, palletCode, originalBarcode, splitQuantity);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (lockInfo, stockDetail) = validationResult.Data;
|
|
// 执行拆包逻辑
|
var splitResult = await ExecuteManualSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("手动拆包成功", splitResult);
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"手动拆包失败 - OrderNo: {orderNo}, Barcode: {originalBarcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"手动拆包失败:{ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 验证拆包请求 - 增强分配数量控制
|
/// </summary>
|
private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest(
|
string orderNo, string palletCode, string originalBarcode, decimal splitQuantity)
|
{
|
_logger.LogInformation($"开始验证拆包请求 - 订单: {orderNo}, 托盘: {palletCode}, 原条码: {originalBarcode}, 拆包数量: {splitQuantity}");
|
|
// 查找锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.CurrentBarcode == originalBarcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
{
|
_logger.LogWarning($"未找到有效的锁定信息 - 订单: {orderNo}, 托盘: {palletCode}, 条码: {originalBarcode}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
|
}
|
|
_logger.LogInformation($"找到锁定信息 - 分配数量: {lockInfo.AssignQuantity}, 已拣选: {lockInfo.PickedQty}, 订单数量: {lockInfo.OrderQuantity}");
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == lockInfo.OrderDetailId);
|
|
if (orderDetail == null)
|
{
|
_logger.LogWarning($"未找到订单明细 - OrderDetailId: {lockInfo.OrderDetailId}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到订单明细");
|
}
|
|
_logger.LogInformation($"找到订单明细 - 已分配数量: {orderDetail.AllocatedQuantity}, 锁定数量: {orderDetail.LockQuantity}");
|
|
// 获取库存信息
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail == null)
|
{
|
_logger.LogWarning($"未找到库存信息 - 条码: {originalBarcode}, StockId: {lockInfo.StockId}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到对应的库存信息");
|
}
|
|
_logger.LogInformation($"找到库存信息 - 库存数量: {stockDetail.StockQuantity}, 出库数量: {stockDetail.OutboundQuantity}");
|
|
// 验证拆包数量不能大于库存数量
|
if (stockDetail.StockQuantity < splitQuantity)
|
{
|
_logger.LogWarning($"拆包数量大于库存数量 - 拆包数量: {splitQuantity}, 库存数量: {stockDetail.StockQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于库存数量,当前库存:{stockDetail.StockQuantity}");
|
}
|
|
// 验证拆包数量不能大于锁定信息的分配数量
|
if (lockInfo.AssignQuantity < splitQuantity)
|
{
|
_logger.LogWarning($"拆包数量大于分配数量 - 拆包数量: {splitQuantity}, 分配数量: {lockInfo.AssignQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于分配数量,当前分配数量:{lockInfo.AssignQuantity}");
|
}
|
|
// 验证拆包数量不能大于锁定信息的未拣选数量
|
decimal remainingToPick = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
if (remainingToPick < splitQuantity)
|
{
|
_logger.LogWarning($"拆包数量大于未拣选数量 - 拆包数量: {splitQuantity}, 未拣选数量: {remainingToPick}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包数量不能大于未拣选数量,当前未拣选:{remainingToPick}");
|
}
|
|
// 验证拆包后原锁定信息的分配数量不会为负数
|
if (lockInfo.AssignQuantity - splitQuantity < 0)
|
{
|
_logger.LogWarning($"拆包后分配数量为负数 - 当前分配数量: {lockInfo.AssignQuantity}, 拆包数量: {splitQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error($"拆包后分配数量不能为负数");
|
}
|
|
// 验证订单明细的分配数量是否足够
|
// 注意:手动拆包不会改变订单明细的分配数量,因为总分配数量不变
|
// 只是从一个锁定信息转移到另一个锁定信息
|
decimal totalLockAssignQuantity = await GetTotalLockAssignQuantity(orderDetail.Id);
|
if (orderDetail.AllocatedQuantity != totalLockAssignQuantity)
|
{
|
_logger.LogWarning($"订单明细分配数量与锁定信息不一致 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQuantity}");
|
// 这里不直接返回错误,因为拆包操作本身不会导致不一致,只是记录警告
|
}
|
|
_logger.LogInformation($"拆包验证通过 - 原条码: {originalBarcode}, 拆包数量: {splitQuantity}");
|
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
|
}
|
|
/// <summary>
|
/// 获取订单明细的所有锁定信息的总分配数量
|
/// </summary>
|
private async Task<decimal> GetTotalLockAssignQuantity(long orderDetailId)
|
{
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId)
|
.ToListAsync();
|
|
return lockInfos.Sum(x => x.AssignQuantity);
|
}
|
|
/// <summary>
|
/// 执行手动拆包逻辑 - 增强分配数量控制
|
/// </summary>
|
private async Task<List<SplitResult>> ExecuteManualSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal splitQuantity, string palletCode)
|
{
|
_logger.LogInformation($"开始执行手动拆包逻辑 - 原条码: {stockDetail.Barcode}, 拆包数量: {splitQuantity}");
|
|
// 验证拆包数量
|
if (lockInfo.AssignQuantity < splitQuantity)
|
{
|
throw new InvalidOperationException($"拆包数量超过锁定信息分配数量,拆包数量: {splitQuantity}, 分配数量: {lockInfo.AssignQuantity}");
|
}
|
|
// 生成新条码
|
string newBarcode = await GenerateNewBarcode();
|
|
// 计算剩余数量
|
decimal remainQty = lockInfo.AssignQuantity - splitQuantity;
|
|
// 创建新库存明细
|
var newStockDetail = new Dt_StockInfoDetail
|
{
|
StockId = stockDetail.StockId,
|
MaterielCode = stockDetail.MaterielCode,
|
OrderNo = stockDetail.OrderNo,
|
BatchNo = stockDetail.BatchNo,
|
StockQuantity = splitQuantity,
|
OutboundQuantity = 0,
|
Barcode = newBarcode,
|
Status = (int)StockStatusEmun.出库锁定,
|
SupplyCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit,
|
BarcodeQty = stockDetail.BarcodeQty,
|
BarcodeUnit = stockDetail.BarcodeUnit,
|
BusinessType = stockDetail.BusinessType,
|
InboundOrderRowNo = stockDetail.InboundOrderRowNo,
|
};
|
await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
|
|
// 更新原库存明细
|
stockDetail.StockQuantity -= splitQuantity;
|
if (stockDetail.StockQuantity < 0) stockDetail.StockQuantity = 0;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 创建新锁定信息
|
var newLockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = lockInfo.OrderNo,
|
OrderDetailId = lockInfo.OrderDetailId, // 绑定到同一个订单明细
|
OutboundBatchNo = lockInfo.OutboundBatchNo,
|
MaterielCode = lockInfo.MaterielCode,
|
MaterielName = lockInfo.MaterielName,
|
StockId = lockInfo.StockId,
|
OrderQuantity = splitQuantity,
|
AssignQuantity = splitQuantity,
|
PickedQty = 0,
|
LocationCode = lockInfo.LocationCode,
|
PalletCode = lockInfo.PalletCode,
|
TaskNum = lockInfo.TaskNum,
|
Status = (int)OutLockStockStatusEnum.出库中,
|
Unit = lockInfo.Unit,
|
SupplyCode = lockInfo.SupplyCode,
|
OrderType = lockInfo.OrderType,
|
CurrentBarcode = newBarcode,
|
IsSplitted = 1,
|
ParentLockId = lockInfo.Id,
|
Operator = App.User.UserName,
|
FactoryArea = lockInfo.FactoryArea,
|
lineNo = lockInfo.lineNo,
|
WarehouseCode = lockInfo.WarehouseCode,
|
BarcodeQty = lockInfo.BarcodeQty,
|
BarcodeUnit = lockInfo.BarcodeUnit,
|
};
|
|
await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
|
|
// 更新原锁定信息
|
lockInfo.AssignQuantity = remainQty;
|
lockInfo.OrderQuantity = remainQty;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 重要:手动拆包不改变订单明细的总分配数量
|
// 因为分配数量只是从一个锁定信息转移到另一个锁定信息
|
_logger.LogInformation($"手动拆包 - 订单明细总分配数量保持不变");
|
|
// 记录拆包历史
|
await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false);
|
|
// 创建拆包结果列表
|
var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
|
|
_logger.LogInformation($"手动拆包逻辑执行完成");
|
|
return splitResults;
|
}
|
/// <summary>
|
/// 验证拆包后数据一致性
|
/// </summary>
|
private async Task ValidateDataConsistencyAfterSplit(long orderDetailId, decimal expectedAllocatedQty, decimal expectedLockQty)
|
{
|
// 重新获取订单明细数据
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
return;
|
|
// 计算所有锁定信息的总分配数量
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId)
|
.ToListAsync();
|
|
decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
|
|
_logger.LogInformation($"数据一致性验证 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
|
// 如果数据不一致,记录警告
|
if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
|
{
|
_logger.LogWarning($"数据不一致 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
}
|
|
// 验证分配数量没有异常变化
|
if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
|
{
|
_logger.LogWarning($"分配数量异常变化 - 期望: {expectedAllocatedQty}, 实际: {orderDetail.AllocatedQuantity}");
|
}
|
|
if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
|
{
|
_logger.LogWarning($"锁定数量异常变化 - 期望: {expectedLockQty}, 实际: {orderDetail.LockQuantity}");
|
}
|
}
|
#endregion
|
|
|
|
#region 取消拆包
|
|
/// <summary>
|
/// 取消拆包
|
/// </summary>
|
public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string palletCode, string newBarcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 查找拆包记录并验证
|
var validationResult = await ValidateCancelSplitRequest(orderNo, palletCode, newBarcode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (splitRecord, newLockInfo, newStockDetail) = validationResult.Data;
|
|
// 2. 查找原始锁定信息
|
var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
|
|
// 3. 检查该条码是否被再次拆包
|
var childSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OriginalBarcode == newBarcode && !x.IsReverted)
|
.ToListAsync();
|
|
if (childSplitRecords.Any())
|
{
|
return WebResponseContent.Instance.Error("该条码已被再次拆包,请先取消后续的拆包操作");
|
}
|
|
// 4. 执行取消拆包逻辑
|
await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("取消拆包成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"取消拆包失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {newBarcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取消拆包失败:{ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 执行取消拆包逻辑
|
/// </summary>
|
private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord,
|
Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
|
Dt_StockInfoDetail newStockDetail)
|
{
|
_logger.LogInformation($"开始执行取消拆包逻辑 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}, 拆包数量: {splitRecord.SplitQty}");
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == originalLockInfo.OrderDetailId);
|
|
if (orderDetail == null)
|
throw new InvalidOperationException("未找到订单明细");
|
|
// 记录取消拆包前的关键数据
|
decimal originalOrderDetailAllocatedQty = orderDetail.AllocatedQuantity;
|
decimal originalOrderDetailLockQty = orderDetail.LockQuantity;
|
|
_logger.LogInformation($"取消拆包前数据 - 订单明细分配数量: {originalOrderDetailAllocatedQty}, 锁定数量: {originalOrderDetailLockQty}");
|
|
// 恢复原锁定信息
|
decimal originalAssignQtyBefore = originalLockInfo.AssignQuantity;
|
decimal originalOrderQtyBefore = originalLockInfo.OrderQuantity;
|
|
// 根据拆包类型决定如何恢复
|
if (splitRecord.IsAutoSplit)
|
{
|
// 自动拆包:原锁定信息保持不变,只需要删除新锁定信息
|
_logger.LogInformation($"取消自动拆包 - 原锁定信息保持不变");
|
}
|
else
|
{
|
// 手动拆包:恢复原锁定信息的分配数量
|
originalLockInfo.AssignQuantity += splitRecord.SplitQty;
|
originalLockInfo.OrderQuantity += splitRecord.SplitQty;
|
|
_logger.LogInformation($"取消手动拆包 - 恢复原锁定信息分配数量从 {originalAssignQtyBefore} 增加到 {originalLockInfo.AssignQuantity}");
|
}
|
|
// 如果原锁定信息的状态是拣选完成,需要重新设置为出库中
|
if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
|
{
|
originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
_logger.LogInformation($"原锁定信息状态从拣选完成恢复为出库中");
|
}
|
|
await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
|
|
// 恢复原库存明细
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
|
|
if (originalStock != null)
|
{
|
if (splitRecord.IsAutoSplit)
|
{
|
// 自动拆包:原库存明细保持不变
|
_logger.LogInformation($"取消自动拆包 - 原库存明细保持不变");
|
}
|
else
|
{
|
// 手动拆包:恢复原库存数量
|
decimal originalStockQtyBefore = originalStock.StockQuantity;
|
originalStock.StockQuantity += splitRecord.SplitQty;
|
|
_logger.LogInformation($"取消手动拆包 - 恢复原库存明细数量从 {originalStockQtyBefore} 增加到 {originalStock.StockQuantity}");
|
|
// 如果原库存状态是出库完成,需要重新设置为出库锁定
|
if (originalStock.Status == (int)StockStatusEmun.出库完成)
|
{
|
originalStock.Status = (int)StockStatusEmun.出库锁定;
|
_logger.LogInformation($"原库存状态从出库完成恢复为出库锁定");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
|
}
|
}
|
|
// 删除新锁定信息
|
_logger.LogInformation($"删除新锁定信息 - 条码: {newLockInfo.CurrentBarcode}, 分配数量: {newLockInfo.AssignQuantity}");
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == newLockInfo.Id)
|
.ExecuteCommandAsync();
|
|
// 删除新库存明细
|
_logger.LogInformation($"删除新库存明细 - 条码: {newStockDetail.Barcode}, 库存数量: {newStockDetail.StockQuantity}");
|
await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == newLockInfo.CurrentBarcode)
|
.ExecuteCommandAsync();
|
|
// 如果是自动拆包,需要减少订单明细的分配数量和锁定数量
|
if (splitRecord.IsAutoSplit)
|
{
|
decimal originalAllocatedBefore = orderDetail.AllocatedQuantity;
|
decimal originalLockBefore = orderDetail.LockQuantity;
|
|
orderDetail.AllocatedQuantity -= splitRecord.SplitQty;
|
orderDetail.LockQuantity -= splitRecord.SplitQty;
|
|
// 边界检查:确保数量不会为负数
|
if (orderDetail.AllocatedQuantity < 0)
|
{
|
_logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + splitRecord.SplitQty}, 减少: {splitRecord.SplitQty}");
|
orderDetail.AllocatedQuantity = 0;
|
}
|
if (orderDetail.LockQuantity < 0)
|
{
|
_logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + splitRecord.SplitQty}, 减少: {splitRecord.SplitQty}");
|
orderDetail.LockQuantity = 0;
|
}
|
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"取消自动拆包减少订单明细数量 - 分配数量从 {originalAllocatedBefore} 减少到 {orderDetail.AllocatedQuantity}");
|
_logger.LogInformation($"取消自动拆包减少订单明细数量 - 锁定数量从 {originalLockBefore} 减少到 {orderDetail.LockQuantity}");
|
}
|
|
// 标记拆包记录为已撤销
|
splitRecord.IsReverted = true;
|
splitRecord.RevertTime = DateTime.Now;
|
splitRecord.RevertOperator = App.User.UserName;
|
await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
|
_logger.LogInformation($"标记拆包记录为已撤销");
|
|
// 验证取消拆包后数据一致性
|
await ValidateDataConsistencyAfterCancelSplit(orderDetail.Id, originalOrderDetailAllocatedQty, originalOrderDetailLockQty, splitRecord.IsAutoSplit, splitRecord.SplitQty);
|
|
// 检查并更新批次和订单状态
|
await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
|
await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
|
|
_logger.LogInformation($"取消拆包逻辑执行完成");
|
}
|
/// <summary>
|
/// 验证取消拆包后数据一致性 - 最新版本
|
/// </summary>
|
private async Task ValidateDataConsistencyAfterCancelSplit(long orderDetailId, decimal originalAllocatedQty, decimal originalLockQty, bool isAutoSplit, decimal splitQuantity)
|
{
|
// 重新获取订单明细数据
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
return;
|
|
// 计算所有锁定信息的总分配数量
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId)
|
.ToListAsync();
|
|
decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
|
|
_logger.LogInformation($"取消拆包后数据一致性验证 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
|
// 根据拆包类型计算期望值
|
decimal expectedAllocatedQty;
|
decimal expectedLockQty;
|
|
if (isAutoSplit)
|
{
|
// 自动拆包取消:分配数量应该减少拆包数量
|
expectedAllocatedQty = originalAllocatedQty - splitQuantity;
|
expectedLockQty = originalLockQty - splitQuantity;
|
|
_logger.LogInformation($"取消自动拆包期望值 - 分配数量: {expectedAllocatedQty}, 锁定数量: {expectedLockQty}");
|
}
|
else
|
{
|
// 手动拆包取消:分配数量应该保持不变
|
expectedAllocatedQty = originalAllocatedQty;
|
expectedLockQty = originalLockQty;
|
|
_logger.LogInformation($"取消手动拆包期望值 - 分配数量: {expectedAllocatedQty}, 锁定数量: {expectedLockQty}");
|
}
|
|
// 边界检查:确保期望值不为负数
|
if (expectedAllocatedQty < 0)
|
{
|
_logger.LogWarning($"期望分配数量为负数,重置为0。计算值: {expectedAllocatedQty}");
|
expectedAllocatedQty = 0;
|
}
|
|
if (expectedLockQty < 0)
|
{
|
_logger.LogWarning($"期望锁定数量为负数,重置为0。计算值: {expectedLockQty}");
|
expectedLockQty = 0;
|
}
|
|
// 验证分配数量
|
if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
|
{
|
_logger.LogWarning($"取消拆包后分配数量异常 - 期望: {expectedAllocatedQty}, 实际: {orderDetail.AllocatedQuantity}");
|
}
|
|
// 验证锁定数量
|
if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
|
{
|
_logger.LogWarning($"取消拆包后锁定数量异常 - 期望: {expectedLockQty}, 实际: {orderDetail.LockQuantity}");
|
}
|
|
// 验证订单明细分配数量与锁定信息总分配数量的一致性
|
if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
|
{
|
_logger.LogWarning($"取消拆包后数据不一致 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
}
|
|
// 记录详细的一致性报告
|
_logger.LogInformation($"取消拆包数据一致性报告 - " +
|
$"订单明细分配数量: {orderDetail.AllocatedQuantity}, " +
|
$"订单明细锁定数量: {orderDetail.LockQuantity}, " +
|
$"锁定信息总分配数量: {totalLockAssignQty}, " +
|
$"拆包类型: {(isAutoSplit ? "自动" : "手动")}, " +
|
$"拆包数量: {splitQuantity}");
|
}
|
/// <summary>
|
/// 验证取消拆包请求
|
/// </summary>
|
private async Task<ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateCancelSplitRequest(
|
string orderNo, string palletCode, string newBarcode)
|
{
|
_logger.LogInformation($"开始验证取消拆包请求 - 订单: {orderNo}, 托盘: {palletCode}, 条码: {newBarcode}");
|
|
// 查找拆包记录
|
var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.NewBarcode == newBarcode &&
|
x.OrderNo == orderNo &&
|
!x.IsReverted)
|
.FirstAsync();
|
|
if (splitRecord == null)
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到拆包记录");
|
|
_logger.LogInformation($"找到拆包记录 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}, 拆包数量: {splitRecord.SplitQty}");
|
|
// 查找新锁定信息
|
var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == newBarcode &&
|
x.PalletCode == palletCode &&
|
x.OrderNo == orderNo)
|
.FirstAsync();
|
|
if (newLockInfo == null)
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到新锁定信息");
|
|
_logger.LogInformation($"找到新锁定信息 - 状态: {newLockInfo.Status}, 已拣选: {newLockInfo.PickedQty}, 分配数量: {newLockInfo.AssignQuantity}");
|
|
// 检查新条码是否已被分拣
|
var newBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == newBarcode && x.OrderNo == orderNo && !x.IsCancelled)
|
.ToListAsync();
|
|
if (newBarcodePickingRecords.Any())
|
{
|
var totalPickedQty = newBarcodePickingRecords.Sum(x => x.PickQuantity);
|
_logger.LogWarning($"新条码 {newBarcode} 已被分拣,总拣选数量: {totalPickedQty}");
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
|
$"新条码已被分拣(已拣选数量:{totalPickedQty}),请先取消分拣,然后再取消拆包");
|
}
|
|
// 检查原条码是否已被分拣
|
var originalBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == splitRecord.OriginalBarcode && x.OrderNo == orderNo && !x.IsCancelled)
|
.ToListAsync();
|
|
if (originalBarcodePickingRecords.Any())
|
{
|
var totalPickedQty = originalBarcodePickingRecords.Sum(x => x.PickQuantity);
|
_logger.LogWarning($"原条码 {splitRecord.OriginalBarcode} 已被分拣,总拣选数量: {totalPickedQty}");
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
|
$"原条码已被分拣(已拣选数量:{totalPickedQty}),请先取消分拣,然后再取消拆包");
|
}
|
|
_logger.LogInformation($"新旧条码均未被分拣,可以取消拆包");
|
|
// 检查新条码是否被再次拆包
|
var childSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OriginalBarcode == newBarcode && !x.IsReverted)
|
.ToListAsync();
|
|
if (childSplitRecords.Any())
|
{
|
var childBarcodes = string.Join(", ", childSplitRecords.Select(x => x.NewBarcode));
|
_logger.LogWarning($"条码 {newBarcode} 已被再次拆包,生成的新条码: {childBarcodes}");
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
|
$"该条码已被再次拆包,生成的新条码:{childBarcodes},请先取消后续拆包");
|
}
|
|
var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == newBarcode);
|
|
if (newStockDetail == null)
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到新库存明细");
|
|
_logger.LogInformation($"取消拆包验证通过 - 条码: {newBarcode}");
|
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((splitRecord, newLockInfo, newStockDetail));
|
}
|
|
#endregion
|
|
#region 批量取消拆包链
|
|
/// <summary>
|
/// 批量取消拆包链 - 取消某个条码及其所有后续拆包
|
/// </summary>
|
public async Task<WebResponseContent> CancelSplitPackageChain(string orderNo, string palletCode, string startBarcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 查找所有相关的拆包记录(形成拆包链)
|
var splitChain = await GetSplitPackageChain(orderNo, startBarcode);
|
|
if (!splitChain.Any())
|
return WebResponseContent.Instance.Error("未找到拆包记录");
|
|
_logger.LogInformation($"找到拆包链,共 {splitChain.Count} 条记录");
|
|
// 2. 收集拆包链中涉及的所有条码(包括原条码和新条码)
|
var allBarcodesInChain = new List<string> { startBarcode };
|
allBarcodesInChain.AddRange(splitChain.Select(x => x.NewBarcode));
|
|
// 3. 检查拆包链中是否有已被分拣的条码
|
var pickedBarcodesInfo = await GetPickedBarcodesInfo(orderNo, allBarcodesInChain);
|
|
if (pickedBarcodesInfo.Any())
|
{
|
var pickedBarcodes = string.Join(", ", pickedBarcodesInfo.Select(x => $"{x.Barcode}(已拣选{x.PickedQty})"));
|
return WebResponseContent.Instance.Error(
|
$"以下条码已被分拣,请先取消分拣:{pickedBarcodes}");
|
}
|
|
// 4. 按拆包顺序倒序取消(从最新的开始取消)
|
var reversedChain = splitChain.OrderByDescending(x => x.SplitTime).ToList();
|
|
foreach (var splitRecord in reversedChain)
|
{
|
_logger.LogInformation($"取消拆包记录 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}");
|
await CancelSingleSplitPackage(splitRecord, palletCode);
|
}
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK($"成功取消拆包链,共{reversedChain.Count}次拆包操作");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"取消拆包链失败 - OrderNo: {orderNo}, StartBarcode: {startBarcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取消拆包链失败:{ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 获取条码的拆包和拣选状态
|
/// </summary>
|
public async Task<WebResponseContent> GetBarcodeSplitAndPickStatus(string orderNo, string barcode)
|
{
|
try
|
{
|
// 1. 获取拆包信息
|
var splitChain = await GetSplitPackageChain(orderNo, barcode);
|
var isOriginalBarcode = !splitChain.Any(x => x.NewBarcode == barcode);
|
|
// 2. 获取拣选信息
|
var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == barcode && x.OrderNo == orderNo && !x.IsCancelled)
|
.ToListAsync();
|
|
var totalPickedQty = pickingRecords.Sum(x => x.PickQuantity);
|
|
// 3. 获取锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == barcode && x.OrderNo == orderNo)
|
.FirstAsync();
|
|
// 4. 获取库存信息
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == barcode)
|
.FirstAsync();
|
|
var statusInfo = new BarcodeStatusInfoDto
|
{
|
Barcode = barcode,
|
OrderNo = orderNo,
|
IsOriginalBarcode = isOriginalBarcode,
|
SplitChainCount = splitChain.Count,
|
HasBeenPicked = pickingRecords.Any(),
|
TotalPickedQuantity = totalPickedQty,
|
PickRecordCount = pickingRecords.Count,
|
LockInfoStatus = lockInfo?.Status ?? 0,
|
LockInfoPickedQty = lockInfo?.PickedQty ?? 0,
|
LockInfoAssignQty = lockInfo?.AssignQuantity ?? 0,
|
StockQuantity = stockDetail?.StockQuantity ?? 0,
|
StockStatus = stockDetail?.Status ?? 0,
|
CanCancelSplit = !pickingRecords.Any(), // 未被分拣才能取消拆包
|
NeedCancelPickFirst = pickingRecords.Any() // 需要先取消分拣
|
};
|
|
// 5. 获取操作建议
|
statusInfo.OperationSuggestions = GetOperationSuggestions(statusInfo);
|
|
return WebResponseContent.Instance.OK("获取状态成功", statusInfo);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"获取条码状态失败 - OrderNo: {orderNo}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error("获取条码状态失败");
|
}
|
}
|
/// <summary>
|
/// 获取操作建议
|
/// </summary>
|
private List<string> GetOperationSuggestions(BarcodeStatusInfoDto statusInfo)
|
{
|
var suggestions = new List<string>();
|
|
if (statusInfo.HasBeenPicked)
|
{
|
suggestions.Add($"该条码已被分拣(数量:{statusInfo.TotalPickedQuantity}),如需取消拆包,请先取消分拣");
|
|
if (statusInfo.IsOriginalBarcode)
|
{
|
suggestions.Add("这是原条码,取消分拣后将恢复为可分拣状态");
|
}
|
else
|
{
|
suggestions.Add("这是拆包生成的新条码,取消分拣后才能取消拆包");
|
}
|
}
|
else
|
{
|
if (statusInfo.IsOriginalBarcode && statusInfo.SplitChainCount > 0)
|
{
|
suggestions.Add("这是原条码,可以取消拆包链");
|
}
|
else if (!statusInfo.IsOriginalBarcode)
|
{
|
suggestions.Add("这是拆包生成的新条码,可以单独取消拆包");
|
}
|
}
|
|
if (statusInfo.LockInfoStatus == (int)OutLockStockStatusEnum.拣选完成)
|
{
|
suggestions.Add("锁定状态:拣选完成");
|
}
|
else if (statusInfo.LockInfoStatus == (int)OutLockStockStatusEnum.出库中)
|
{
|
suggestions.Add("锁定状态:出库中");
|
}
|
|
return suggestions;
|
}
|
/// <summary>
|
/// 获取已被分拣的条码信息
|
/// </summary>
|
private async Task<List<PickedBarcodeInfo>> GetPickedBarcodesInfo(string orderNo, List<string> barcodes)
|
{
|
var pickedBarcodes = new List<PickedBarcodeInfo>();
|
|
foreach (var barcode in barcodes)
|
{
|
var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == barcode && x.OrderNo == orderNo && !x.IsCancelled)
|
.ToListAsync();
|
|
if (pickingRecords.Any())
|
{
|
var totalPickedQty = pickingRecords.Sum(x => x.PickQuantity);
|
pickedBarcodes.Add(new PickedBarcodeInfo
|
{
|
Barcode = barcode,
|
PickedQty = totalPickedQty,
|
PickRecordCount = pickingRecords.Count
|
});
|
}
|
}
|
|
return pickedBarcodes;
|
}
|
/// <summary>
|
/// 获取拆包链 - 查找某个条码的所有拆包记录(包括后续拆包)
|
/// </summary>
|
public async Task<List<Dt_SplitPackageRecord>> GetSplitPackageChain(string orderNo, string startBarcode)
|
{
|
var allSplitRecords = new List<Dt_SplitPackageRecord>();
|
var visitedBarcodes = new HashSet<string>(); // 防止循环引用
|
|
// 使用队列进行广度优先搜索
|
var queue = new Queue<string>();
|
queue.Enqueue(startBarcode);
|
visitedBarcodes.Add(startBarcode);
|
|
while (queue.Count > 0)
|
{
|
var currentBarcode = queue.Dequeue();
|
|
// 查找以当前条码为原条码的所有拆包记录
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OriginalBarcode == currentBarcode &&
|
x.OrderNo == orderNo &&
|
!x.IsReverted)
|
.ToListAsync();
|
|
foreach (var record in splitRecords)
|
{
|
// 避免重复处理
|
if (!visitedBarcodes.Contains(record.NewBarcode))
|
{
|
allSplitRecords.Add(record);
|
queue.Enqueue(record.NewBarcode);
|
visitedBarcodes.Add(record.NewBarcode);
|
}
|
}
|
}
|
|
return allSplitRecords;
|
}
|
|
/// <summary>
|
/// 取消单个拆包记录
|
/// </summary>
|
private async Task CancelSingleSplitPackage(Dt_SplitPackageRecord splitRecord, string palletCode)
|
{
|
_logger.LogInformation($"开始取消单个拆包记录 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}");
|
|
// 再次验证分拣状态(防止并发操作)
|
var newBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == splitRecord.NewBarcode && !x.IsCancelled)
|
.ToListAsync();
|
|
if (newBarcodePickingRecords.Any())
|
{
|
throw new InvalidOperationException($"新条码 {splitRecord.NewBarcode} 在验证后被分拣,无法取消拆包");
|
}
|
|
var originalBarcodePickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == splitRecord.OriginalBarcode && !x.IsCancelled)
|
.ToListAsync();
|
|
if (originalBarcodePickingRecords.Any())
|
{
|
throw new InvalidOperationException($"原条码 {splitRecord.OriginalBarcode} 在验证后被分拣,无法取消拆包");
|
}
|
|
// 查找相关数据
|
var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == splitRecord.NewBarcode &&
|
x.PalletCode == palletCode)
|
.FirstAsync();
|
|
var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.NewBarcode);
|
|
var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
|
|
// 执行取消逻辑
|
await ExecuteCancelSplitLogic(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
|
|
_logger.LogInformation($"取消单个拆包记录完成 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}");
|
}
|
#endregion
|
|
#region 拆包信息查询增强
|
|
/// <summary>
|
/// 获取拆包链信息
|
/// </summary>
|
public async Task<WebResponseContent> GetSplitPackageChainInfo(string orderNo, string barcode)
|
{
|
try
|
{
|
var splitChain = await GetSplitPackageChain(orderNo, barcode);
|
|
var chainInfo = new SplitPackageChainInfoDto
|
{
|
OriginalBarcode = barcode,
|
TotalSplitTimes = splitChain.Count,
|
SplitChain = splitChain.Select(x => new SplitChainItemDto
|
{
|
SplitTime = x.SplitTime,
|
OriginalBarcode = x.OriginalBarcode,
|
NewBarcode = x.NewBarcode,
|
SplitQuantity = x.SplitQty,
|
Operator = x.Operator,
|
IsReverted = x.IsReverted,
|
IsAutoSplit = x.IsAutoSplit
|
}).ToList()
|
};
|
|
return WebResponseContent.Instance.OK("获取成功", chainInfo);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"获取拆包链信息失败 - OrderNo: {orderNo}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error("获取拆包链信息失败");
|
}
|
}
|
|
|
/// <summary>
|
/// 查找根条码
|
/// </summary>
|
public async Task<string> FindRootBarcode(string orderNo, string startBarcode)
|
{
|
var currentBarcode = startBarcode;
|
var visited = new HashSet<string>();
|
|
while (!string.IsNullOrEmpty(currentBarcode) && !visited.Contains(currentBarcode))
|
{
|
visited.Add(currentBarcode);
|
|
// 查找当前条码是否是由其他条码拆包而来
|
var parentRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.NewBarcode == currentBarcode &&
|
x.OrderNo == orderNo &&
|
!x.IsReverted)
|
.FirstAsync();
|
|
if (parentRecord == null)
|
{
|
// 没有父级拆包记录,说明这是根条码
|
return currentBarcode;
|
}
|
|
currentBarcode = parentRecord.OriginalBarcode;
|
}
|
|
// 如果出现循环引用,返回起始条码
|
return startBarcode;
|
}
|
#endregion
|
|
#region 更新批次状态检查
|
|
/// <summary>
|
/// 检查并更新批次状态
|
/// </summary>
|
private async Task CheckAndUpdateBatchStatus(string batchNo)
|
{
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == batchNo);
|
|
if (batch != null)
|
{
|
// 重新计算批次完成数量
|
var batchLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.BatchNo == batchNo)
|
.ToListAsync();
|
|
var completedQuantity = batchLocks.Where(x => x.Status == (int)OutLockStockStatusEnum.拣选完成)
|
.Sum(x => x.PickedQty);
|
|
batch.CompletedQuantity = completedQuantity;
|
|
// 更新批次状态
|
if (batch.CompletedQuantity >= batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.已完成;
|
}
|
else if (batch.CompletedQuantity > 0)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.执行中;
|
}
|
else
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.分配中;
|
}
|
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
}
|
}
|
|
#endregion
|
|
#region 统一回库逻辑
|
private async Task<Dt_Task> GetCurrentTask(string orderNo, string palletCode)
|
{
|
// 先尝试通过订单号和托盘号查找任务
|
var task = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (task == null)
|
{
|
// 如果找不到,再通过托盘号查找
|
task = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
}
|
|
return task;
|
}
|
private async Task<PalletStatusAnalysis> AnalyzePalletStatus(string orderNo, string palletCode, int stockId)
|
{
|
var result = new PalletStatusAnalysis
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
StockId = stockId
|
};
|
|
// 分析未分拣的出库锁定记录
|
var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo &&
|
it.PalletCode == palletCode &&
|
it.Status == (int)OutLockStockStatusEnum.出库中)
|
.ToListAsync();
|
|
if (remainingLocks.Any())
|
{
|
result.HasRemainingLocks = true;
|
result.RemainingLocks = remainingLocks;
|
result.RemainingLocksReturnQty = remainingLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
_logger.LogInformation($"发现{remainingLocks.Count}条未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
|
}
|
|
// 分析托盘上的库存货物
|
var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.StockId == stockId &&
|
(it.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
it.Status == StockStatusEmun.入库完成.ObjToInt() ||
|
it.Status == StockStatusEmun.出库锁定.ObjToInt()))
|
.Where(it => it.StockQuantity > 0)
|
.ToListAsync();
|
|
if (palletStockGoods.Any())
|
{
|
result.HasPalletStockGoods = true;
|
result.PalletStockGoods = palletStockGoods;
|
result.PalletStockReturnQty = palletStockGoods.Sum(x => x.StockQuantity);
|
_logger.LogInformation($"发现{palletStockGoods.Count}个库存货物,总数量: {result.PalletStockReturnQty}");
|
|
// 记录详细状态分布
|
var statusGroups = palletStockGoods.GroupBy(x => x.Status);
|
foreach (var group in statusGroups)
|
{
|
_logger.LogInformation($"库存状态{group.Key}: {group.Count()}个货物,数量: {group.Sum(x => x.StockQuantity)}");
|
}
|
}
|
|
//分析拆包记录
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(it => it.OrderNo == orderNo &&
|
it.PalletCode == palletCode &&
|
!it.IsReverted && it.Status != (int)SplitPackageStatusEnum.已拣选 &&
|
it.Status != (int)SplitPackageStatusEnum.已回库)
|
.ToListAsync();
|
|
if (splitRecords.Any())
|
{
|
result.HasSplitRecords = true;
|
result.SplitRecords = splitRecords;
|
result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
|
|
_logger.LogInformation($"发现{splitRecords.Count}条未拣选拆包记录,总数量: {result.SplitReturnQty}");
|
}
|
|
// 4. 计算总回库数量和空托盘状态
|
result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
|
result.HasItemsToReturn = result.TotalReturnQty > 0;
|
result.IsEmptyPallet = !result.HasItemsToReturn;
|
|
// 5. 检查是否有进行中的任务
|
result.HasActiveTasks = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.OrderNo == orderNo && x.TaskType == TaskTypeEnum.InPick.ObjToInt() &&
|
x.PalletCode == palletCode &&
|
x.TaskStatus == (int)TaskStatusEnum.New)
|
.AnyAsync();
|
|
_logger.LogInformation($"托盘状态分析完成 - 订单: {orderNo}, 托盘: {palletCode}, " +
|
$"总回库数量: {result.TotalReturnQty}, 是否空托盘: {result.IsEmptyPallet}, " +
|
$"有进行中任务: {result.HasActiveTasks}");
|
|
return result;
|
}
|
private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
|
{
|
decimal totalQty = 0;
|
var processedBarcodes = new HashSet<string>();
|
|
foreach (var splitRecord in splitRecords)
|
{
|
if (splitRecord.Status != (int)SplitPackageStatusEnum.已撤销)
|
continue;
|
// 检查原条码
|
if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId &&
|
it.Status != StockStatusEmun.出库完成.ObjToInt())
|
.FirstAsync();
|
|
if (originalStock != null && originalStock.StockQuantity > 0)
|
{
|
totalQty += originalStock.StockQuantity;
|
processedBarcodes.Add(splitRecord.OriginalBarcode);
|
}
|
}
|
|
// 检查新条码
|
if (!processedBarcodes.Contains(splitRecord.NewBarcode))
|
{
|
var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockId && it.Status != StockStatusEmun.出库完成.ObjToInt())
|
.FirstAsync();
|
|
if (newStock != null && newStock.StockQuantity > 0)
|
{
|
totalQty += newStock.StockQuantity;
|
processedBarcodes.Add(splitRecord.NewBarcode);
|
}
|
}
|
}
|
|
return totalQty;
|
}
|
|
/// <summary>
|
/// 统一回库方法 - 处理托盘上所有剩余货物
|
/// </summary>
|
public async Task<WebResponseContent> ExecutePalletReturn(string orderNo, string palletCode, string returnReason = "分批回库")
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
|
return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
|
|
// 获取库存信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
|
// 分析托盘状态
|
var statusAnalysis = await AnalyzePalletStatusForReturn(orderNo, palletCode, stockInfo.Id);
|
|
if (!statusAnalysis.HasItemsToReturn)
|
return await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
|
|
_logger.LogInformation($"开始回库操作 - 订单: {orderNo}, 托盘: {palletCode}, 回库数量: {statusAnalysis.TotalReturnQty}");
|
|
// 执行回库数据操作
|
await ExecuteReturnDataOperations(statusAnalysis);
|
|
// 更新订单状态
|
await UpdateOrderStatusAfterReturn(orderNo);
|
|
_unitOfWorkManage.CommitTran();
|
|
// 创建回库任务(AGV)
|
await CreateReturnTask(orderNo, palletCode, stockInfo);
|
|
return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}", new
|
{
|
ReturnQuantity = statusAnalysis.TotalReturnQty,
|
ReturnBarcodes = statusAnalysis.AllBarcodes,
|
Reason = returnReason,
|
PalletCode = palletCode,
|
OrderNo = orderNo
|
});
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"回库操作失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 执行回库数据操作
|
/// 确保不会将生成的条码数量错误绑定到锁定数量
|
/// </summary>
|
private async Task ExecuteReturnDataOperations(PalletStatusAnalysis statusAnalysis)
|
{
|
_logger.LogInformation($"开始执行回库数据操作 - 订单: {statusAnalysis.OrderNo}, 托盘: {statusAnalysis.PalletCode}");
|
|
try
|
{
|
// 1. 处理已分配的未分拣锁定记录
|
if (statusAnalysis.HasRemainingLocks)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} 条已分配未分拣锁定记录");
|
await HandleAllocatedLocksReturn(statusAnalysis.RemainingLocks);
|
}
|
|
// 2. 处理未分配的锁定记录(如自动拆包产生的)
|
if (statusAnalysis.HasUnallocatedLocks)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.UnallocatedLocks.Count} 条未分配锁定记录");
|
await HandleUnallocatedLocksReturn(statusAnalysis.UnallocatedLocks);
|
}
|
|
// 3. 处理未分配的库存货物
|
if (statusAnalysis.HasPalletStockGoods)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} 个未分配库存货物");
|
await HandleUnallocatedStockReturn(statusAnalysis.PalletStockGoods);
|
}
|
|
_logger.LogInformation($"回库数据操作完成 - 总回库数量: {statusAnalysis.TotalReturnQty}");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"回库数据操作失败 - 订单: {statusAnalysis.OrderNo}, 托盘: {statusAnalysis.PalletCode}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
// <summary>
|
/// 处理未分配的锁定记录回库
|
/// 不需要减少订单明细的分配数量
|
/// </summary>
|
private async Task HandleUnallocatedLocksReturn(List<Dt_OutStockLockInfo> unallocatedLocks)
|
{
|
_logger.LogInformation($"开始处理未分配锁定记录回库 - 共 {unallocatedLocks.Count} 条记录");
|
|
foreach (var lockInfo in unallocatedLocks)
|
{
|
// 计算回库数量(未拣选的部分)
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
if (returnQty <= 0)
|
{
|
_logger.LogInformation($"跳过未分配锁定记录 - 锁定ID: {lockInfo.Id}, 已拣选完成或无需回库");
|
continue;
|
}
|
|
_logger.LogInformation($"处理未分配锁定记录回库 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
|
// 恢复库存状态
|
await RestoreStockForLockInfo(lockInfo, returnQty);
|
|
// 更新锁定记录状态为已回库
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新未分配锁定状态 - 锁定ID: {lockInfo.Id}, 状态: 出库中 -> 已回库");
|
|
// 重要:未分配锁定记录不需要减少订单明细的分配数量
|
_logger.LogInformation($"未分配锁定记录回库完成 - 锁定ID: {lockInfo.Id}, 回库数量: {returnQty}, 无需更新订单明细");
|
}
|
|
_logger.LogInformation($"未分配锁定记录回库处理完成 - 共处理 {unallocatedLocks.Count} 条记录");
|
}
|
private async Task HandleAllocatedLocksReturn(List<Dt_OutStockLockInfo> allocatedLocks)
|
{
|
_logger.LogInformation($"开始处理已分配锁定记录回库 - 共 {allocatedLocks.Count} 条记录");
|
|
// 按订单明细分组处理
|
var orderDetailGroups = allocatedLocks.GroupBy(x => x.OrderDetailId);
|
|
foreach (var group in orderDetailGroups)
|
{
|
var orderDetailId = group.Key;
|
var groupLocks = group.ToList();
|
|
_logger.LogInformation($"处理订单明细 {orderDetailId} 的 {groupLocks.Count} 条锁定记录");
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
{
|
_logger.LogWarning($"未找到订单明细 - OrderDetailId: {orderDetailId}");
|
continue;
|
}
|
|
decimal totalReturnQtyForDetail = 0;
|
|
foreach (var lockInfo in groupLocks)
|
{
|
// 计算回库数量(未拣选的部分)
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
if (returnQty <= 0)
|
{
|
_logger.LogInformation($"跳过锁定记录 - 锁定ID: {lockInfo.Id}, 已拣选完成或无需回库");
|
continue;
|
}
|
|
_logger.LogInformation($"处理已分配锁定记录回库 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
|
// 恢复库存状态
|
await RestoreStockForLockInfo(lockInfo, returnQty);
|
|
// 更新锁定记录状态为已回库
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新已分配锁定状态 - 锁定ID: {lockInfo.Id}, 状态: 出库中 -> 已回库");
|
|
totalReturnQtyForDetail += returnQty;
|
}
|
|
// 减少订单明细的分配数量
|
if (totalReturnQtyForDetail > 0)
|
{
|
await ReduceOrderDetailAllocation(orderDetail, totalReturnQtyForDetail);
|
}
|
}
|
|
_logger.LogInformation($"已分配锁定记录回库处理完成 - 共处理 {allocatedLocks.Count} 条记录");
|
}
|
/// <summary>
|
/// 恢复锁定记录对应的库存
|
/// </summary>
|
private async Task RestoreStockForLockInfo(Dt_OutStockLockInfo lockInfo, decimal returnQty)
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null)
|
{
|
// 记录恢复前的库存状态
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
// 恢复库存数量:出库数量减少,库存数量增加
|
stockDetail.OutboundQuantity -= returnQty;
|
stockDetail.StockQuantity += returnQty;
|
|
// 确保数量不会为负数
|
if (stockDetail.OutboundQuantity < 0)
|
{
|
_logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + returnQty}");
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
// 恢复库存状态为可用状态
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
_logger.LogInformation($"库存状态更新为入库完成 - 条码: {stockDetail.Barcode}");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复库存状态 - 条码: {stockDetail.Barcode}, " +
|
$"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
|
$"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
|
}
|
else
|
{
|
_logger.LogWarning($"未找到对应的库存信息 - 条码: {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
|
}
|
}
|
/// <summary>
|
/// 创建回库任务
|
/// </summary>
|
private async Task CreateReturnTask(string orderNo, string palletCode, Dt_StockInfo stockInfo)
|
{
|
// 获取当前任务信息
|
var currentTask = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (currentTask != null)
|
{
|
// 分配新货位
|
var newLocation = _locationInfoService.AssignLocation(stockInfo.LocationType);
|
|
var returnTask = new Dt_Task()
|
{
|
CurrentAddress = stations[currentTask.TargetAddress],
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
OrderNo = orderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[currentTask.TargetAddress],
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = TaskTypeEnum.InPick.ObjToInt(),
|
PalletType = stockInfo.PalletType,
|
WarehouseId = currentTask.WarehouseId
|
};
|
|
await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
|
// 发送ESS命令
|
await SendESSCommands(palletCode, currentTask.TargetAddress, returnTask);
|
|
_logger.LogInformation($"创建回库任务成功 - 订单: {orderNo}, 托盘: {palletCode}");
|
}
|
}
|
/// <summary>
|
/// 更新回库后的订单状态
|
/// </summary>
|
private async Task UpdateOrderStatusAfterReturn(string orderNo)
|
{
|
// 检查订单是否还有未完成的锁定记录
|
var activeLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中))
|
.ToListAsync();
|
|
if (!activeLocks.Any())
|
{
|
// 所有锁定记录都已完成或已回库,更新订单状态
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = (int)OutOrderStatusEnum.出库完成,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新订单状态为出库完成 - 订单: {orderNo}");
|
}
|
}
|
|
/// <summary>
|
/// 处理空托盘回库
|
/// </summary>
|
private async Task<WebResponseContent> HandleEmptyPalletReturn(string orderNo, string palletCode, Dt_StockInfo stockInfo)
|
{
|
_logger.LogInformation($"处理空托盘回库 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
try
|
{
|
// 清理零库存数据
|
await CleanupZeroStockData(stockInfo.Id);
|
|
// 创建空托盘库存记录
|
var emptyStockInfo = new Dt_StockInfo()
|
{
|
PalletType = PalletTypeEnum.Empty.ObjToInt(),
|
StockStatus = StockStatusEmun.组盘暂存.ObjToInt(),
|
PalletCode = palletCode,
|
LocationType = stockInfo.LocationType
|
};
|
emptyStockInfo.Details = new List<Dt_StockInfoDetail>();
|
_stockInfoService.AddMaterielGroup(emptyStockInfo);
|
|
// 创建空托盘回库任务
|
await CreateReturnTask(orderNo, palletCode, emptyStockInfo);
|
|
return WebResponseContent.Instance.OK("空托盘回库成功");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"空托盘回库失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($"空托盘回库失败: {ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 分析托盘状态用于回库
|
/// 确保不会错误识别需要回库的物品
|
/// </summary>
|
private async Task<PalletStatusAnalysis> AnalyzePalletStatusForReturn(string orderNo, string palletCode, int stockId)
|
{
|
var result = new PalletStatusAnalysis
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
StockId = stockId
|
};
|
|
// 1. 分析未分拣的锁定记录(状态为出库中)
|
var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.ToListAsync();
|
|
if (unfinishedLocks.Any())
|
{
|
// 重要:区分已分配和未分配的锁定记录
|
var allocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated != 1 && x.OrderDetailId > 0).ToList();
|
var unallocatedLocks = unfinishedLocks.Where(x => x.IsUnallocated == 1 || x.OrderDetailId == 0).ToList();
|
|
// 处理已分配的锁定记录
|
if (allocatedLocks.Any())
|
{
|
result.HasRemainingLocks = true;
|
result.RemainingLocks = allocatedLocks;
|
result.RemainingLocksReturnQty = allocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
foreach (var lockInfo in allocatedLocks)
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
result.AllBarcodes.Add(lockInfo.CurrentBarcode);
|
}
|
}
|
|
_logger.LogInformation($"发现{allocatedLocks.Count}条已分配未分拣锁定记录,总数量: {result.RemainingLocksReturnQty}");
|
}
|
|
// 处理未分配的锁定记录(如自动拆包产生的)
|
if (unallocatedLocks.Any())
|
{
|
result.HasUnallocatedLocks = true;
|
result.UnallocatedLocks = unallocatedLocks;
|
result.UnallocatedLocksReturnQty = unallocatedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
foreach (var lockInfo in unallocatedLocks)
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
result.AllBarcodes.Add(lockInfo.CurrentBarcode);
|
}
|
}
|
|
_logger.LogInformation($"发现{unallocatedLocks.Count}条未分配锁定记录,总数量: {result.UnallocatedLocksReturnQty}");
|
}
|
}
|
|
// 2. 分析托盘上的剩余库存货物(状态为出库锁定但未分配)
|
var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId &&
|
x.Status == (int)StockStatusEmun.出库锁定 &&
|
x.StockQuantity > 0)
|
.ToListAsync();
|
|
// 过滤掉已经被锁定记录占用的库存
|
var lockedBarcodes = unfinishedLocks.Select(x => x.CurrentBarcode).ToList();
|
var unlockedStockGoods = palletStockGoods.Where(x => !lockedBarcodes.Contains(x.Barcode)).ToList();
|
|
// 进一步过滤:检查这些库存是否有关联的锁定记录
|
var trulyUnallocatedGoods = new List<Dt_StockInfoDetail>();
|
foreach (var stock in unlockedStockGoods)
|
{
|
var hasLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == stock.Barcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.AnyAsync();
|
|
if (!hasLock)
|
{
|
trulyUnallocatedGoods.Add(stock);
|
}
|
}
|
|
if (trulyUnallocatedGoods.Any())
|
{
|
result.HasPalletStockGoods = true;
|
result.PalletStockGoods = trulyUnallocatedGoods;
|
result.PalletStockReturnQty = trulyUnallocatedGoods.Sum(x => x.StockQuantity);
|
|
foreach (var stock in trulyUnallocatedGoods)
|
{
|
result.AllBarcodes.Add(stock.Barcode);
|
}
|
|
_logger.LogInformation($"发现{trulyUnallocatedGoods.Count}个真正未分配库存货物,总数量: {result.PalletStockReturnQty}");
|
}
|
|
// 3. 计算总回库数量
|
result.TotalReturnQty = result.RemainingLocksReturnQty + result.UnallocatedLocksReturnQty + result.PalletStockReturnQty;
|
result.HasItemsToReturn = result.TotalReturnQty > 0;
|
result.IsEmptyPallet = !result.HasItemsToReturn;
|
|
_logger.LogInformation($"托盘状态分析完成 - 订单: {orderNo}, 托盘: {palletCode}, 总回库数量: {result.TotalReturnQty}");
|
|
return result;
|
}
|
/// <summary>
|
/// 处理未分拣的锁定记录回库
|
/// 确保不会错误绑定条码数量到锁定数量
|
/// </summary>
|
private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks)
|
{
|
_logger.LogInformation($"开始处理未分拣锁定记录回库 - 共 {remainingLocks.Count} 条记录");
|
|
// 按订单明细分组处理,确保订单明细数据的一致性
|
var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
|
|
foreach (var group in orderDetailGroups)
|
{
|
var orderDetailId = group.Key;
|
var groupLocks = group.ToList();
|
|
_logger.LogInformation($"处理订单明细 {orderDetailId} 的 {groupLocks.Count} 条锁定记录");
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
{
|
_logger.LogWarning($"未找到订单明细 - OrderDetailId: {orderDetailId}");
|
continue;
|
}
|
|
decimal totalReturnQtyForDetail = 0;
|
|
foreach (var lockInfo in groupLocks)
|
{
|
// 只处理状态为出库中的锁定记录
|
if (lockInfo.Status != (int)OutLockStockStatusEnum.出库中)
|
{
|
_logger.LogInformation($"跳过非出库中状态的锁定记录 - 锁定ID: {lockInfo.Id}, 状态: {lockInfo.Status}");
|
continue;
|
}
|
|
// 计算回库数量(未拣选的部分)
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
if (returnQty <= 0)
|
{
|
_logger.LogInformation($"跳过锁定记录 - 锁定ID: {lockInfo.Id}, 已拣选完成或无需回库");
|
continue;
|
}
|
|
_logger.LogInformation($"处理锁定记录回库 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
|
// 恢复库存状态
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null)
|
{
|
// 记录恢复前的库存状态
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
// 只恢复实际的库存数量,不创建新的条码或绑定
|
// 恢复库存数量:出库数量减少,库存数量增加
|
stockDetail.OutboundQuantity -= returnQty;
|
stockDetail.StockQuantity += returnQty;
|
|
// 确保数量不会为负数
|
if (stockDetail.OutboundQuantity < 0)
|
{
|
_logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + returnQty}");
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
// 恢复库存状态为可用状态
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
_logger.LogInformation($"库存状态更新为入库完成 - 条码: {stockDetail.Barcode}");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复库存状态 - 条码: {stockDetail.Barcode}, " +
|
$"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
|
$"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
|
}
|
else
|
{
|
_logger.LogWarning($"未找到对应的库存信息 - 条码: {lockInfo.CurrentBarcode}, StockId: {lockInfo.StockId}");
|
// 重要:如果找不到库存信息,跳过此锁定记录,避免数据不一致
|
continue;
|
}
|
|
// 更新锁定记录状态为已回库,但不修改分配数量
|
// 分配数量在订单明细层面统一处理
|
var originalStatus = lockInfo.Status;
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新锁定状态 - 锁定ID: {lockInfo.Id}, 状态: {originalStatus} -> {lockInfo.Status}");
|
|
totalReturnQtyForDetail += returnQty;
|
|
_logger.LogInformation($"锁定记录回库完成 - 锁定ID: {lockInfo.Id}, 回库数量: {returnQty}");
|
}
|
|
// 减少订单明细的分配数量
|
if (totalReturnQtyForDetail > 0)
|
{
|
await ReduceOrderDetailAllocation(orderDetail, totalReturnQtyForDetail);
|
}
|
}
|
|
_logger.LogInformation($"未分拣锁定记录回库处理完成 - 共处理 {remainingLocks.Count} 条记录");
|
}
|
/// <summary>
|
/// 处理未分配的库存货物回库
|
/// 确保不会创建新的锁定记录
|
/// </summary>
|
private async Task HandleUnallocatedStockReturn(List<Dt_StockInfoDetail> stockGoods)
|
{
|
_logger.LogInformation($"开始处理未分配库存回库 - 共 {stockGoods.Count} 个货物");
|
|
foreach (var stockDetail in stockGoods)
|
{
|
if (stockDetail.StockQuantity <= 0)
|
{
|
_logger.LogInformation($"跳过零库存货物 - 条码: {stockDetail.Barcode}");
|
continue;
|
}
|
|
_logger.LogInformation($"处理未分配库存回库 - 条码: {stockDetail.Barcode}, 数量: {stockDetail.StockQuantity}");
|
|
// 检查是否已经有对应的锁定记录
|
var existingLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == stockDetail.Barcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
if (existingLock != null)
|
{
|
_logger.LogWarning($"库存条码 {stockDetail.Barcode} 已有锁定记录,跳过直接回库处理");
|
continue;
|
}
|
|
// 记录恢复前的状态
|
var originalStatus = stockDetail.Status;
|
|
// 直接恢复库存状态为可用状态,不创建任何锁定记录
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"未分配库存回库完成 - 条码: {stockDetail.Barcode}, 状态: {originalStatus} -> {stockDetail.Status}");
|
}
|
|
_logger.LogInformation($"未分配库存回库处理完成 - 共处理 {stockGoods.Count} 个货物");
|
}
|
/// <summary>
|
/// 减少订单明细的分配数量
|
/// 确保分配数量的减少是准确的
|
/// </summary>
|
private async Task ReduceOrderDetailAllocation(Dt_OutboundOrderDetail orderDetail, decimal reduceQty)
|
{
|
if (orderDetail == null)
|
return;
|
|
decimal originalAllocated = orderDetail.AllocatedQuantity;
|
decimal originalLock = orderDetail.LockQuantity;
|
|
// 验证减少数量不会导致负数
|
if (orderDetail.AllocatedQuantity < reduceQty)
|
{
|
_logger.LogWarning($"分配数量不足,调整减少数量 - 原计划减少: {reduceQty}, 实际可用: {orderDetail.AllocatedQuantity}");
|
reduceQty = orderDetail.AllocatedQuantity;
|
}
|
|
// 减少分配数量和锁定数量
|
orderDetail.AllocatedQuantity -= reduceQty;
|
orderDetail.LockQuantity -= reduceQty;
|
|
// 确保数量不会为负数
|
if (orderDetail.AllocatedQuantity < 0)
|
{
|
_logger.LogWarning($"分配数量出现负数,重置为0。原值: {orderDetail.AllocatedQuantity + reduceQty}, 减少: {reduceQty}");
|
orderDetail.AllocatedQuantity = 0;
|
}
|
|
if (orderDetail.LockQuantity < 0)
|
{
|
_logger.LogWarning($"锁定数量出现负数,重置为0。原值: {orderDetail.LockQuantity + reduceQty}, 减少: {reduceQty}");
|
orderDetail.LockQuantity = 0;
|
}
|
|
// 更新批次分配状态
|
await UpdateBatchAllocateStatus(orderDetail);
|
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"减少订单明细分配 - OrderDetailId: {orderDetail.Id}, " +
|
$"分配数量: {originalAllocated} -> {orderDetail.AllocatedQuantity}, " +
|
$"锁定数量: {originalLock} -> {orderDetail.LockQuantity}, " +
|
$"减少数量: {reduceQty}");
|
|
// 验证数据一致性
|
await ValidateOrderDetailConsistency(orderDetail.Id);
|
}
|
/// <summary>
|
/// 验证订单明细数据一致性
|
/// </summary>
|
private async Task ValidateOrderDetailConsistency(long orderDetailId)
|
{
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
return;
|
|
// 计算所有相关锁定记录的总分配数量
|
var relatedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId &&
|
x.Status != (int)OutLockStockStatusEnum.已回库)
|
.ToListAsync();
|
|
decimal totalLockAssignQty = relatedLocks.Sum(x => x.AssignQuantity);
|
|
// 验证订单明细分配数量与锁定记录总分配数量的一致性
|
if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
|
{
|
_logger.LogWarning($"数据不一致警告 - OrderDetailId: {orderDetailId}, " +
|
$"订单明细分配数量: {orderDetail.AllocatedQuantity}, " +
|
$"锁定记录总分配数量: {totalLockAssignQty}");
|
}
|
else
|
{
|
_logger.LogInformation($"数据一致性验证通过 - OrderDetailId: {orderDetailId}");
|
}
|
}
|
/// <summary>
|
/// 分批回库 - 调用统一回库方法
|
/// </summary>
|
public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode)
|
{
|
return await ExecutePalletReturn(orderNo, palletCode, "分批回库");
|
}
|
|
/// <summary>
|
/// 剩余回库 - 调用统一回库方法
|
/// </summary>
|
public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
|
{
|
return await ExecutePalletReturn(orderNo, palletCode, reason);
|
}
|
|
/// <summary>
|
/// 取走空箱 - 先执行回库再清理 - 增强版本
|
/// </summary>
|
public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
_logger.LogInformation($"开始取走空箱 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
// 1. 验证空箱取走条件(必须全部完成拣选)
|
var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
|
if (!validationResult.IsValid)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
}
|
|
var completedLocks = validationResult.Data;
|
|
// 2. 清理已完成的锁定记录(标记为已取走)
|
await CleanupCompletedLocks(completedLocks);
|
|
// 3. 清理对应的库存记录状态
|
foreach (var lockInfo in completedLocks)
|
{
|
await CleanupStockInfo(lockInfo);
|
}
|
|
// 4. 更新相关订单状态
|
await UpdateOrderStatusAfterPalletRemoval(orderNo);
|
|
// 5. 记录操作历史
|
await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
|
|
_unitOfWorkManage.CommitTran();
|
|
_logger.LogInformation($"取走空箱成功 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
return WebResponseContent.Instance.OK("取走空箱成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"取走空箱失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取走空箱失败:{ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 收集需要回库的条码
|
/// </summary>
|
private async Task<List<string>> CollectReturnBarcodes(PalletStatusAnalysis status)
|
{
|
var returnBarcodes = new HashSet<string>();
|
|
try
|
{
|
_logger.LogInformation($"开始收集回库条码 - 订单: {status.OrderNo}, 托盘: {status.PalletCode}");
|
|
// 1. 收集未分拣锁定记录的条码
|
if (status.HasRemainingLocks)
|
{
|
foreach (var lockInfo in status.RemainingLocks)
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
returnBarcodes.Add(lockInfo.CurrentBarcode);
|
_logger.LogInformation($"添加锁定记录条码: {lockInfo.CurrentBarcode}");
|
}
|
}
|
}
|
|
// 2. 收集托盘上库存货物的条码
|
if (status.HasPalletStockGoods)
|
{
|
foreach (var stockDetail in status.PalletStockGoods)
|
{
|
if (!string.IsNullOrEmpty(stockDetail.Barcode) && stockDetail.StockQuantity > 0)
|
{
|
returnBarcodes.Add(stockDetail.Barcode);
|
_logger.LogInformation($"添加库存货物条码: {stockDetail.Barcode}, 数量: {stockDetail.StockQuantity}");
|
}
|
}
|
}
|
|
// 3. 收集拆包记录相关的条码
|
if (status.HasSplitRecords)
|
{
|
foreach (var splitRecord in status.SplitRecords)
|
{
|
// 添加原条码
|
if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode))
|
{
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == status.StockId);
|
|
if (originalStock != null && originalStock.StockQuantity > 0)
|
{
|
returnBarcodes.Add(splitRecord.OriginalBarcode);
|
_logger.LogInformation($"添加拆包原条码: {splitRecord.OriginalBarcode}, 数量: {originalStock.StockQuantity}");
|
}
|
}
|
|
// 添加新条码
|
if (!string.IsNullOrEmpty(splitRecord.NewBarcode))
|
{
|
var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.NewBarcode && x.StockId == status.StockId);
|
|
if (newStock != null && newStock.StockQuantity > 0)
|
{
|
returnBarcodes.Add(splitRecord.NewBarcode);
|
_logger.LogInformation($"添加拆包新条码: {splitRecord.NewBarcode}, 数量: {newStock.StockQuantity}");
|
}
|
}
|
}
|
}
|
|
_logger.LogInformation($"回库条码收集完成 - 共 {returnBarcodes.Count} 个条码: {string.Join(", ", returnBarcodes)}");
|
|
return returnBarcodes.ToList();
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"收集回库条码失败 - Error: {ex.Message}");
|
return returnBarcodes.ToList();
|
}
|
}
|
#endregion
|
|
#region 回库操作核心方法
|
|
/// <summary>
|
/// 执行回库操作 - 增强版本
|
/// </summary>
|
private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
|
Dt_Task task, PalletStatusAnalysis statusAnalysis)
|
{
|
_logger.LogInformation($"开始执行回库操作 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
// 处理未分拣的锁定记录
|
if (statusAnalysis.HasRemainingLocks)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} 条未分拣锁定记录");
|
await HandleRemainingLocksReturn(statusAnalysis.RemainingLocks);
|
}
|
|
//处理托盘上的库存货物
|
if (statusAnalysis.HasPalletStockGoods)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} 个库存货物");
|
await HandlePalletStockGoodsReturn(statusAnalysis.PalletStockGoods, stockInfo.Id);
|
}
|
|
//处理拆包记录
|
if (statusAnalysis.HasSplitRecords)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.SplitRecords.Count} 条拆包记录");
|
await HandleSplitRecordsReturn(statusAnalysis.SplitRecords, stockInfo.Id);
|
}
|
|
_logger.LogInformation($"回库操作完成 - 总回库数量: {statusAnalysis.TotalReturnQty}");
|
}
|
|
/// <summary>
|
/// 处理未分拣的锁定记录回库
|
/// </summary>
|
|
|
/// <summary>
|
/// 处理托盘上的库存货物回库
|
/// </summary>
|
private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods, int stockId)
|
{
|
foreach (var stockDetail in palletStockGoods)
|
{
|
// 只处理出库锁定状态的库存
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定 && stockDetail.StockQuantity > 0)
|
{
|
// 恢复库存状态为可用状态
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复库存货物 - 条码: {stockDetail.Barcode}, 数量: {stockDetail.StockQuantity}");
|
}
|
}
|
}
|
|
/// <summary>
|
/// 处理拆包记录回库
|
/// </summary>
|
private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, int stockId)
|
{
|
foreach (var splitRecord in splitRecords)
|
{
|
// 处理新条码
|
var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == splitRecord.NewBarcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
if (newLockInfo != null)
|
{
|
await HandleSingleLockReturn(newLockInfo);
|
}
|
|
// 处理原条码
|
var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == splitRecord.OriginalBarcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
if (originalLockInfo != null)
|
{
|
await HandleSingleLockReturn(originalLockInfo);
|
}
|
|
// 更新拆包记录状态为已回库
|
splitRecord.Status = (int)SplitPackageStatusEnum.已回库;
|
await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
|
}
|
}
|
|
private async Task HandleSingleLockReturn(Dt_OutStockLockInfo lockInfo)
|
{
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
if (returnQty <= 0)
|
{
|
_logger.LogInformation($"跳过锁定记录 - 锁定ID: {lockInfo.Id}, 已拣选完成或无需回库");
|
return;
|
}
|
|
_logger.LogInformation($"处理拆包相关锁定记录 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
|
// 恢复库存
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null)
|
{
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
stockDetail.StockQuantity += returnQty;
|
stockDetail.OutboundQuantity -= returnQty;
|
|
if (stockDetail.OutboundQuantity < 0)
|
{
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复拆包相关库存 - 条码: {stockDetail.Barcode}, " +
|
$"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}");
|
}
|
|
// 更新锁定状态
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 减少订单明细的分配数量
|
if (lockInfo.OrderDetailId > 0)
|
{
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == lockInfo.OrderDetailId);
|
|
if (orderDetail != null)
|
{
|
await ReduceOrderDetailAllocation(orderDetail, returnQty);
|
}
|
}
|
|
_logger.LogInformation($"拆包相关锁定记录回库完成 - 锁定ID: {lockInfo.Id}, 回库数量: {returnQty}");
|
}
|
/// <summary>
|
/// 释放所有锁定以便重新分配
|
/// </summary>
|
private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis statusAnalysis)
|
{
|
// 更新订单明细的已分配数量
|
if (statusAnalysis.HasRemainingLocks)
|
{
|
var orderDetailGroups = statusAnalysis.RemainingLocks.GroupBy(x => x.OrderDetailId);
|
|
foreach (var group in orderDetailGroups)
|
{
|
var orderDetailId = group.Key;
|
var returnedQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail != null)
|
{
|
orderDetail.AllocatedQuantity -= returnedQty;
|
orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
|
|
await UpdateBatchAllocateStatus(orderDetail);
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, 减少分配数量: {returnedQty}");
|
}
|
}
|
}
|
}
|
|
#endregion
|
|
#region 辅助方法
|
|
/// <summary>
|
/// 处理没有回库物品的情况
|
/// </summary>
|
private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode, Dt_Task originalTask, int stockId)
|
{
|
_logger.LogInformation($"托盘 {palletCode} 没有需要回库的物品");
|
|
// 检查是否是空托盘
|
var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockId);
|
if (statusAnalysis.IsEmptyPallet)
|
{
|
try
|
{
|
var locationtype = 0;
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
{
|
var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>().FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
|
locationtype = firstLocation?.LocationType ?? 1;
|
}
|
else
|
{
|
locationtype = stockInfo.LocationType;
|
_stockInfoService.DeleteData(stockInfo);
|
}
|
|
var targetAddress = originalTask.TargetAddress;
|
|
await CleanupZeroStockData(stockId);
|
|
|
var emptystockInfo = new Dt_StockInfo() { PalletType = PalletTypeEnum.Empty.ObjToInt(), StockStatus = StockStatusEmun.组盘暂存.ObjToInt(), PalletCode = palletCode, LocationType = locationtype };
|
emptystockInfo.Details = new List<Dt_StockInfoDetail>();
|
_stockInfoService.AddMaterielGroup(emptystockInfo);
|
//空托盘如何处理 还有一个出库任务要处理。
|
originalTask.PalletType = PalletTypeEnum.Empty.ObjToInt();
|
|
await CreateReturnTaskAndHandleESS(orderNo, palletCode, originalTask, TaskTypeEnum.InEmpty, PalletTypeEnum.Empty.ObjToInt());
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($" HandleNoReturnItems 失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($" 回库空托盘失败!");
|
}
|
return WebResponseContent.Instance.OK("空托盘回库任务创建成功");
|
}
|
else
|
{
|
return WebResponseContent.Instance.Error("托盘状态异常:有物品但无法计算回库数量");
|
}
|
}
|
private async Task CleanupZeroStockData(int stockId)
|
{
|
try
|
{
|
// 1. 删除库存数量为0的明细记录
|
var deleteDetailCount = await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId && x.StockQuantity == 0)
|
.ExecuteCommandAsync();
|
|
await _stockInfoService.Db.Deleteable<Dt_StockInfo>()
|
.Where(x => x.Id == stockId).ExecuteCommandAsync();
|
|
_logger.LogInformation($"清理零库存明细记录 - StockId: {stockId}, 删除记录数: {deleteDetailCount}");
|
|
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning($"清理零库存数据失败 - StockId: {stockId}, Error: {ex.Message}");
|
// 注意:清理失败不应该影响主流程
|
}
|
}
|
/// <summary>
|
/// 更新订单状态(回库后)
|
/// </summary>
|
private async Task UpdateOrderStatusForReturn(string orderNo)
|
{
|
// 检查订单是否所有托盘都已完成或已回库
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo)
|
.ToListAsync();
|
|
var activeLocks = allLocks.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
|
|
// 如果没有活跃的锁定记录,更新订单状态
|
if (!activeLocks.Any())
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = (int)OutOrderStatusEnum.出库完成,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新订单状态为出库完成 - 订单: {orderNo}");
|
}
|
}
|
|
#endregion
|
|
|
|
|
#region 验证方法
|
private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest(
|
string orderNo, string palletCode, string barcode)
|
{
|
_logger.LogInformation($"开始验证分拣请求 - 订单: {orderNo}, 托盘: {palletCode}, 条码: {barcode}");
|
|
// 查找锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.CurrentBarcode == barcode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到有效的锁定信息");
|
|
if (lockInfo.IsUnallocated == 1 || lockInfo.OrderDetailId == 0)
|
{
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该条码是未分配锁定记录,不能直接分拣");
|
}
|
|
_logger.LogInformation($"找到锁定信息 - 分配数量: {lockInfo.AssignQuantity}, 已拣选: {lockInfo.PickedQty}");
|
|
// 检查是否已经分拣完成
|
if (lockInfo.PickedQty >= lockInfo.AssignQuantity)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该条码已分拣完成");
|
|
// 获取关联数据
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == lockInfo.OrderDetailId);
|
|
if (orderDetail == null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到订单明细");
|
|
_logger.LogInformation($"找到订单明细 - 已分配数量: {orderDetail.AllocatedQuantity}, 锁定数量: {orderDetail.LockQuantity}");
|
|
// 检查订单明细的已分配数量是否足够
|
decimal remainingToPick = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
// 这里应该检查锁定信息的分配数量,而不是订单明细的分配数量
|
// 因为拆包后,锁定信息的分配数量可能小于订单明细的分配数量
|
if (lockInfo.AssignQuantity < remainingToPick)
|
{
|
_logger.LogWarning($"锁定信息分配数量不足 - 需要拣选: {remainingToPick}, 锁定信息分配数量: {lockInfo.AssignQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"锁定信息分配数量不足,需要拣选:{remainingToPick},锁定信息分配数量:{lockInfo.AssignQuantity}");
|
}
|
|
// 检查锁定数量是否足够
|
if (orderDetail.LockQuantity < remainingToPick)
|
{
|
_logger.LogWarning($"订单明细锁定数量不足 - 需要拣选: {remainingToPick}, 可用锁定数量: {orderDetail.LockQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"订单明细锁定数量不足,需要拣选:{remainingToPick},可用锁定数量:{orderDetail.LockQuantity}");
|
}
|
|
// 检查订单明细分配数量是否为负数
|
if (orderDetail.AllocatedQuantity < 0)
|
{
|
_logger.LogError($"订单明细分配数量为负数 - 当前值: {orderDetail.AllocatedQuantity}");
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"订单明细分配数量异常(负数),请联系管理员处理");
|
}
|
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail == null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到对应的库存信息");
|
|
// 验证库存数量
|
if (stockDetail.StockQuantity < remainingToPick)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"库存数量不足,需要拣选:{remainingToPick},实际库存:{stockDetail.StockQuantity}");
|
|
// 验证库存状态
|
if (stockDetail.Status != (int)StockStatusEmun.出库锁定)
|
{
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"库存状态异常,当前状态:{stockDetail.Status},期望状态:出库锁定");
|
}
|
|
// 使用 OutboundBatchNo 查找批次
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == lockInfo.OutboundBatchNo);
|
|
_logger.LogInformation($"分拣验证通过 - 条码: {barcode}, 剩余需拣选: {remainingToPick}, 可用库存: {stockDetail.StockQuantity}");
|
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Success((lockInfo, orderDetail, stockDetail, batch));
|
}
|
|
|
/// <summary>
|
/// 检查并执行自动拆包(如果需要)
|
/// </summary>
|
private async Task<List<SplitResult>> CheckAndAutoSplitIfNeeded(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, string palletCode)
|
{
|
if (lockInfo.IsUnallocated == 1 || lockInfo.OrderDetailId == 0)
|
{
|
_logger.LogInformation($"跳过未分配锁定记录的自动拆包检查 - 锁定ID: {lockInfo.Id}");
|
return null;
|
}
|
// 检查是否需要自动拆包的条件:
|
// 1. 库存数量大于分配数量
|
// 2. 锁定信息状态为出库中
|
// 3. 未拣选数量等于分配数量(表示还未开始拣选)
|
bool needAutoSplit = stockDetail.StockQuantity > lockInfo.AssignQuantity &&
|
lockInfo.Status == (int)OutLockStockStatusEnum.出库中 &&
|
lockInfo.PickedQty == 0;
|
|
if (!needAutoSplit)
|
return null;
|
|
// 计算拆包数量 = 库存数量 - 分配数量
|
decimal splitQuantity = stockDetail.StockQuantity - lockInfo.AssignQuantity;
|
|
// 执行自动拆包
|
var splitResult = await ExecuteAutoSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
|
|
return splitResult;
|
}
|
/// <summary>
|
/// 执行自动拆包逻辑
|
/// 确保自动拆包不会影响回库逻辑
|
/// </summary>
|
private async Task<List<SplitResult>> ExecuteAutoSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal splitQuantity, string palletCode)
|
{
|
_logger.LogInformation($"开始执行自动拆包逻辑 - 原条码: {stockDetail.Barcode}, 拆包数量: {splitQuantity}");
|
|
// 验证拆包数量合理性
|
if (splitQuantity <= 0)
|
{
|
throw new InvalidOperationException($"拆包数量必须大于0,当前值: {splitQuantity}");
|
}
|
|
if (stockDetail.StockQuantity < lockInfo.AssignQuantity + splitQuantity)
|
{
|
throw new InvalidOperationException($"库存数量不足以进行自动拆包");
|
}
|
|
// 生成新条码
|
string newBarcode = await GenerateNewBarcode();
|
|
// 记录拆包前的分配数量
|
decimal originalAssignQty = lockInfo.AssignQuantity;
|
decimal remainQty = originalAssignQty; // 原锁定信息分配数量保持不变
|
|
// 创建新库存明细(多余部分)
|
var newStockDetail = new Dt_StockInfoDetail
|
{
|
StockId = stockDetail.StockId,
|
MaterielCode = stockDetail.MaterielCode,
|
OrderNo = stockDetail.OrderNo,
|
BatchNo = stockDetail.BatchNo,
|
StockQuantity = splitQuantity,
|
OutboundQuantity = 0,
|
Barcode = newBarcode,
|
Status = (int)StockStatusEmun.出库锁定,
|
SupplyCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit,
|
BarcodeQty = stockDetail.BarcodeQty,
|
BarcodeUnit = stockDetail.BarcodeUnit,
|
BusinessType = stockDetail.BusinessType,
|
InboundOrderRowNo = stockDetail.InboundOrderRowNo,
|
};
|
await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"创建新库存明细 - 条码: {newBarcode}, 库存数量: {splitQuantity}");
|
|
// 创建新锁定信息(多余部分)- 标记为未分配
|
var newLockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = lockInfo.OrderNo,
|
OrderDetailId = 0, // 重要:不绑定到具体订单明细
|
OutboundBatchNo = lockInfo.OutboundBatchNo,
|
MaterielCode = lockInfo.MaterielCode,
|
MaterielName = lockInfo.MaterielName,
|
StockId = lockInfo.StockId,
|
OrderQuantity = splitQuantity,
|
AssignQuantity = splitQuantity,
|
PickedQty = 0,
|
LocationCode = lockInfo.LocationCode,
|
PalletCode = lockInfo.PalletCode,
|
TaskNum = lockInfo.TaskNum,
|
Status = (int)OutLockStockStatusEnum.出库中,
|
Unit = lockInfo.Unit,
|
SupplyCode = lockInfo.SupplyCode,
|
OrderType = lockInfo.OrderType,
|
CurrentBarcode = newBarcode,
|
IsSplitted = 1,
|
ParentLockId = lockInfo.Id,
|
Operator = App.User.UserName,
|
FactoryArea = lockInfo.FactoryArea,
|
lineNo = lockInfo.lineNo,
|
WarehouseCode = lockInfo.WarehouseCode,
|
BarcodeQty = lockInfo.BarcodeQty,
|
BarcodeUnit = lockInfo.BarcodeUnit,
|
IsUnallocated = 1 // 标记为未分配
|
};
|
|
await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"创建新锁定信息 - 条码: {newBarcode}, 分配数量: {splitQuantity}, 标记为未分配");
|
|
// 自动拆包不改变订单明细的分配数量
|
_logger.LogInformation($"自动拆包 - 订单明细分配数量保持不变");
|
|
// 记录拆包历史
|
await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, true, stockDetail.StockQuantity);
|
|
// 创建拆包结果列表
|
var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
|
|
_logger.LogInformation($"自动拆包逻辑执行完成");
|
|
return splitResults;
|
}
|
|
/// <summary>
|
/// 验证自动拆包后数据一致性
|
/// </summary>
|
private async Task ValidateDataConsistencyAfterAutoSplit(long orderDetailId, decimal originalAllocatedQty, decimal originalLockQty, decimal splitQuantity)
|
{
|
// 重新获取订单明细数据
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
return;
|
|
// 计算所有锁定信息的总分配数量
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId)
|
.ToListAsync();
|
|
decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
|
|
_logger.LogInformation($"自动拆包后数据一致性验证 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
|
// 验证自动拆包后的数据一致性
|
decimal expectedAllocatedQty = originalAllocatedQty + splitQuantity;
|
decimal expectedLockQty = originalLockQty + splitQuantity;
|
|
if (Math.Abs(orderDetail.AllocatedQuantity - expectedAllocatedQty) > 0.01m)
|
{
|
_logger.LogWarning($"自动拆包后分配数量异常 - 期望: {expectedAllocatedQty}, 实际: {orderDetail.AllocatedQuantity}");
|
}
|
|
if (Math.Abs(orderDetail.LockQuantity - expectedLockQty) > 0.01m)
|
{
|
_logger.LogWarning($"自动拆包后锁定数量异常 - 期望: {expectedLockQty}, 实际: {orderDetail.LockQuantity}");
|
}
|
|
if (Math.Abs(orderDetail.AllocatedQuantity - totalLockAssignQty) > 0.01m)
|
{
|
_logger.LogWarning($"自动拆包后数据不一致 - 订单明细分配数量: {orderDetail.AllocatedQuantity}, 锁定信息总分配数量: {totalLockAssignQty}");
|
}
|
}
|
#endregion
|
|
#region 核心逻辑方法
|
|
private async Task<PickingResult> ExecutePickingLogic(
|
Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
|
Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
|
{
|
_logger.LogInformation($"开始执行分拣逻辑 - 条码: {stockDetail.Barcode}, 分配数量: {lockInfo.AssignQuantity}, 实际拣选: {actualPickedQty}");
|
|
// 再次验证订单明细的分配数量(防止并发操作)
|
if (orderDetail.AllocatedQuantity < actualPickedQty)
|
{
|
throw new InvalidOperationException($"订单明细分配数量不足,需要拣选 {actualPickedQty},可用分配数量 {orderDetail.AllocatedQuantity}");
|
}
|
|
if (orderDetail.LockQuantity < actualPickedQty)
|
{
|
throw new InvalidOperationException($"订单明细锁定数量不足,需要拣选 {actualPickedQty},可用锁定数量 {orderDetail.LockQuantity}");
|
}
|
|
// 更新锁定信息
|
lockInfo.PickedQty += actualPickedQty;
|
_logger.LogInformation($"更新锁定信息 - 已拣选数量从 {lockInfo.PickedQty - actualPickedQty} 增加到 {lockInfo.PickedQty}");
|
|
// 准确判断拣选完成状态
|
if (Math.Abs(lockInfo.PickedQty - lockInfo.AssignQuantity) < 0.001m)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
|
_logger.LogInformation($"锁定信息状态更新为拣选完成");
|
}
|
else if (lockInfo.PickedQty > 0)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
_logger.LogInformation($"锁定信息状态保持为出库中(部分拣选)");
|
}
|
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 更新库存信息
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
stockDetail.StockQuantity -= actualPickedQty;
|
stockDetail.OutboundQuantity += actualPickedQty;
|
|
_logger.LogInformation($"更新库存信息 - 库存数量从 {originalStockQty} 减少到 {stockDetail.StockQuantity}");
|
_logger.LogInformation($"更新库存信息 - 出库数量从 {originalOutboundQty} 增加到 {stockDetail.OutboundQuantity}");
|
|
// 准确判断库存状态
|
if (stockDetail.StockQuantity <= 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库完成;
|
_logger.LogInformation($"库存状态更新为出库完成");
|
}
|
else
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库锁定;
|
_logger.LogInformation($"库存状态保持为出库锁定");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"分拣逻辑执行完成 - 条码: {stockDetail.Barcode}");
|
|
return new PickingResult
|
{
|
FinalLockInfo = lockInfo,
|
ActualPickedQty = actualPickedQty
|
};
|
}
|
|
private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
|
{
|
_logger.LogInformation($"开始恢复拣选数据 - 拣选记录ID: {pickingRecord.Id}, 条码: {pickingRecord.Barcode}, 拣选数量: {pickingRecord.PickQuantity}");
|
|
// 1. 恢复锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
|
|
if (lockInfo == null)
|
{
|
throw new InvalidOperationException($"未找到对应的锁定信息,ID: {pickingRecord.OutStockLockId}");
|
}
|
|
decimal originalPickedQty = lockInfo.PickedQty;
|
decimal originalAssignQty = lockInfo.AssignQuantity; // 记录原始分配数量
|
|
// 只恢复已拣选数量,不修改分配数量
|
lockInfo.PickedQty -= pickingRecord.PickQuantity;
|
|
// 确保已拣选数量不会为负数
|
if (lockInfo.PickedQty < 0)
|
{
|
_logger.LogWarning($"已拣选数量出现负数,重置为0。原值: {lockInfo.PickedQty + pickingRecord.PickQuantity}, 恢复数量: {pickingRecord.PickQuantity}");
|
lockInfo.PickedQty = 0;
|
}
|
|
_logger.LogInformation($"恢复锁定信息 - 已拣选数量从 {originalPickedQty} 减少到 {lockInfo.PickedQty}");
|
_logger.LogInformation($"锁定信息分配数量保持不变: {originalAssignQty}");
|
|
// 恢复锁定状态
|
if (lockInfo.PickedQty <= 0)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
_logger.LogInformation($"锁定信息状态恢复为出库中");
|
}
|
else if (lockInfo.PickedQty < lockInfo.AssignQuantity)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中; // 部分拣选状态
|
_logger.LogInformation($"锁定信息状态恢复为出库中(部分拣选)");
|
}
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 2. 恢复库存信息
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == pickingRecord.Barcode);
|
|
if (stockDetail == null)
|
{
|
throw new InvalidOperationException($"未找到对应的库存信息,条码: {pickingRecord.Barcode}");
|
}
|
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
stockDetail.StockQuantity += pickingRecord.PickQuantity;
|
stockDetail.OutboundQuantity -= pickingRecord.PickQuantity;
|
|
// 确保出库数量不会为负数
|
if (stockDetail.OutboundQuantity < 0)
|
{
|
_logger.LogWarning($"出库数量出现负数,重置为0。原值: {stockDetail.OutboundQuantity + pickingRecord.PickQuantity}, 恢复数量: {pickingRecord.PickQuantity}");
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
_logger.LogInformation($"恢复库存信息 - 库存数量从 {originalStockQty} 增加到 {stockDetail.StockQuantity}");
|
_logger.LogInformation($"恢复库存信息 - 出库数量从 {originalOutboundQty} 减少到 {stockDetail.OutboundQuantity}");
|
|
// 恢复库存状态
|
if (stockDetail.StockQuantity > 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库锁定;
|
_logger.LogInformation($"库存状态恢复为出库锁定");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复拣选数据完成 - 条码: {pickingRecord.Barcode}");
|
|
return new RevertPickingResult
|
{
|
LockInfo = lockInfo,
|
StockDetail = stockDetail
|
};
|
}
|
|
#endregion
|
|
#region 数据更新方法
|
|
private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
|
{
|
_logger.LogInformation($"开始更新批次和订单数据 - 拣选数量: {pickedQty}");
|
var latestOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == orderDetail.Id);
|
|
if (latestOrderDetail == null)
|
throw new InvalidOperationException("未找到订单明细");
|
|
orderDetail = latestOrderDetail;
|
// 验证分配数量不会变成负数
|
if (orderDetail.AllocatedQuantity < pickedQty)
|
{
|
decimal actualPickedQty = orderDetail.AllocatedQuantity;
|
_logger.LogWarning($"分配数量不足,调整拣选数量 - 原需要: {pickedQty}, 实际可用: {actualPickedQty}");
|
pickedQty = actualPickedQty;
|
}
|
|
if (orderDetail.LockQuantity < pickedQty)
|
{
|
decimal actualPickedQty = orderDetail.LockQuantity;
|
_logger.LogWarning($"锁定数量不足,调整拣选数量 - 原需要: {pickedQty}, 实际可用: {actualPickedQty}");
|
pickedQty = actualPickedQty;
|
}
|
|
// 1. 更新批次完成数量
|
decimal originalBatchCompletedQty = batch.CompletedQuantity;
|
batch.CompletedQuantity += pickedQty;
|
|
_logger.LogInformation($"更新批次完成数量 - 从 {originalBatchCompletedQty} 增加到 {batch.CompletedQuantity}");
|
|
if (batch.CompletedQuantity >= batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.已完成;
|
_logger.LogInformation($"批次状态更新为已完成");
|
}
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
|
// 更新订单明细
|
decimal originalOverOutQty = orderDetail.OverOutQuantity;
|
decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
|
decimal originalLockQty = orderDetail.LockQuantity;
|
|
orderDetail.OverOutQuantity += pickedQty;
|
orderDetail.AllocatedQuantity -= pickedQty;
|
// LockQuantity 同步减少
|
orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
|
if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
|
if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
|
_logger.LogInformation($"更新订单明细 - 已出库数量从 {originalOverOutQty} 增加到 {orderDetail.OverOutQuantity}");
|
_logger.LogInformation($"更新订单明细 - 已分配数量从 {originalAllocatedQty} 减少到 {orderDetail.AllocatedQuantity}");
|
_logger.LogInformation($"更新订单明细 - 锁定数量从 {originalLockQty} 减少到 {orderDetail.LockQuantity}");
|
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
// 检查订单状态
|
await CheckAndUpdateOrderStatus(orderNo);
|
|
_logger.LogInformation($"批次和订单数据更新完成");
|
}
|
|
private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord, RevertPickingResult revertResult)
|
{
|
_logger.LogInformation($"开始恢复批次和订单数据");
|
|
// 恢复批次完成数量
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == revertResult.LockInfo.OutboundBatchNo);
|
|
if (batch != null)
|
{
|
decimal originalCompletedQty = batch.CompletedQuantity;
|
batch.CompletedQuantity -= pickingRecord.PickQuantity;
|
if (batch.CompletedQuantity < 0)
|
{
|
batch.CompletedQuantity = 0;
|
_logger.LogWarning($"批次完成数量出现负数,重置为0");
|
}
|
_logger.LogInformation($"恢复批次完成数量 - 从 {originalCompletedQty} 减少到 {batch.CompletedQuantity}");
|
|
// 重新计算批次状态
|
if (batch.CompletedQuantity <= 0)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.分配中;
|
_logger.LogInformation($"批次状态恢复为分配中");
|
}
|
else if (batch.CompletedQuantity < batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.执行中;
|
_logger.LogInformation($"批次状态恢复为执行中");
|
}
|
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
}
|
|
// 恢复订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
|
|
if (orderDetail != null)
|
{
|
decimal originalOverOutQty = orderDetail.OverOutQuantity;
|
decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
|
decimal originalLockQty = orderDetail.LockQuantity;
|
|
// 只恢复相关数量,分配数量保持不变
|
orderDetail.OverOutQuantity -= pickingRecord.PickQuantity;
|
orderDetail.AllocatedQuantity += pickingRecord.PickQuantity;
|
orderDetail.LockQuantity += pickingRecord.PickQuantity;
|
if (orderDetail.OverOutQuantity < 0) orderDetail.OverOutQuantity = 0;
|
if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
|
if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
|
|
_logger.LogInformation($"恢复订单明细 - 已出库数量从 {originalOverOutQty} 减少到 {orderDetail.OverOutQuantity}");
|
_logger.LogInformation($"订单明细分配数量保持不变: {originalAllocatedQty}");
|
_logger.LogInformation($"订单明细锁定数量保持不变: {originalLockQty}");
|
|
await UpdateBatchAllocateStatus(orderDetail);
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
}
|
|
// 3. 重新检查订单状态
|
await CheckAndUpdateOrderStatus(pickingRecord.OrderNo);
|
|
_logger.LogInformation($"恢复批次和订单数据完成");
|
}
|
|
private async Task UpdateBatchAllocateStatus(Dt_OutboundOrderDetail orderDetail)
|
{
|
if (orderDetail.AllocatedQuantity >= orderDetail.NeedOutQuantity)
|
{
|
orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
|
}
|
else if (orderDetail.AllocatedQuantity > 0)
|
{
|
orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.AssignOverPartial.ObjToInt();
|
}
|
else
|
{
|
orderDetail.BatchAllocateStatus = OrderDetailStatusEnum.New.ObjToInt();
|
}
|
}
|
private async Task ReleaseLockAndStock(Dt_OutStockLockInfo lockInfo)
|
{
|
// 恢复库存状态 - 回库后库存变为可用状态
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null)
|
{
|
// 回库后库存状态恢复为入库完成(可用状态)
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
|
// 更新锁定记录状态为已回库
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
}
|
|
/// <summary>
|
/// 更新批次状态(回库)
|
/// </summary>
|
private async Task UpdateBatchStatusForReturn(string outboundBatchNo, List<Dt_OutStockLockInfo> returnedLocks)
|
{
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == outboundBatchNo);
|
|
if (batch != null)
|
{
|
// 计算回库数量(未拣选的部分)
|
var returnedQty = returnedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
batch.CompletedQuantity -= returnedQty;
|
|
// 更新批次状态
|
if (batch.CompletedQuantity <= 0)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.已回库;
|
}
|
else if (batch.CompletedQuantity < batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.执行中;
|
}
|
else
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.已完成;
|
}
|
|
batch.Operator = App.User.UserName;
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
}
|
}
|
|
/// <summary>
|
/// 更新订单明细(回库后)
|
/// </summary>
|
private async Task UpdateOrderDetailAfterReturn(List<Dt_OutStockLockInfo> returnedLocks)
|
{
|
var orderDetailGroups = returnedLocks.GroupBy(x => x.OrderDetailId);
|
|
foreach (var group in orderDetailGroups)
|
{
|
var orderDetailId = group.Key;
|
var returnedQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail != null)
|
{
|
orderDetail.AllocatedQuantity -= returnedQty;
|
// LockQuantity 同步减少,保持与已分配数量一致
|
orderDetail.LockQuantity = orderDetail.AllocatedQuantity;
|
|
await UpdateBatchAllocateStatus(orderDetail);
|
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
}
|
}
|
}
|
#endregion
|
|
private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum, int palletType)
|
{
|
var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
|
.FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
|
|
// 分配新货位
|
var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
|
|
Dt_Task returnTask = new()
|
{
|
CurrentAddress = stations[originalTask.TargetAddress],
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
// OrderNo = originalTask.OrderNo,
|
OrderNo = orderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[originalTask.TargetAddress],
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = taskTypeEnum.ObjToInt(),
|
PalletType = palletType,
|
WarehouseId = originalTask.WarehouseId
|
|
};
|
// 保存回库任务
|
await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
var targetAddress = originalTask.TargetAddress;
|
|
// 删除原始出库任务
|
_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
|
// await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
|
|
|
// 给 ESS 发送流动信号和创建任务
|
await SendESSCommands(palletCode, targetAddress, returnTask);
|
}
|
/// <summary>
|
/// 给ESS下任务
|
/// </summary>
|
/// <param name="palletCode"></param>
|
/// <param name="targetAddress"></param>
|
/// <param name="returnTask"></param>
|
/// <returns></returns>
|
/// <exception cref="Exception"></exception>
|
private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask)
|
{
|
try
|
{
|
// 1. 发送流动信号
|
var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
|
{
|
slotCode = movestations[targetAddress],
|
containerCode = palletCode
|
});
|
|
//if (moveResult)
|
//{
|
// 2. 创建回库任务
|
var essTask = new TaskModel()
|
{
|
taskType = "putaway",
|
taskGroupCode = "",
|
groupPriority = 0,
|
tasks = new List<TasksType>{ new() {
|
taskCode = returnTask.TaskNum.ToString(),
|
taskPriority = 0,
|
taskDescribe = new TaskDescribeType
|
{
|
containerCode = palletCode,
|
containerType = "CT_KUBOT_STANDARD",
|
fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
|
toStationCode = "",
|
toLocationCode = returnTask.TargetAddress,
|
deadline = 0,
|
storageTag = ""
|
}
|
} }
|
};
|
|
var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
|
_logger.LogInformation($"ReturnRemaining 创建任务成功: {resultTask}");
|
//}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}");
|
throw new Exception($"ESS系统通信失败: {ex.Message}");
|
}
|
}
|
|
|
#region 辅助方法
|
|
private async Task<string> GenerateNewBarcode()
|
{
|
var seq = await _dailySequenceService.GetNextSequenceAsync();
|
return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString().PadLeft(5, '0');
|
}
|
|
private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQty, string newBarcode, bool isAutoSplit, decimal? originalStockQuantity = null)
|
{
|
var splitHistory = new Dt_SplitPackageRecord
|
{
|
OrderNo = lockInfo.OrderNo,
|
OutStockLockInfoId = lockInfo.Id,
|
StockId = stockDetail.StockId,
|
Operator = App.User.UserName,
|
OriginalBarcode = stockDetail.Barcode,
|
NewBarcode = newBarcode,
|
SplitQty = splitQty,
|
SplitTime = DateTime.Now,
|
Status = (int)SplitPackageStatusEnum.已拆包,
|
IsAutoSplit = isAutoSplit,
|
// SplitType = isAutoSplit ? "自动拆包" : "手动拆包"
|
OriginalStockQuantity = originalStockQuantity ?? stockDetail.StockQuantity,
|
//RemainingStockQuantity = stockDetail.StockQuantity - splitQty
|
TaskNum = lockInfo.TaskNum
|
};
|
|
await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
|
}
|
|
private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode)
|
{
|
var pickingRecord = new Dt_PickingRecord
|
{
|
OrderNo = orderNo,
|
// BatchNo = result.FinalLockInfo.BatchNo,
|
OrderDetailId = result.FinalLockInfo.OrderDetailId,
|
PalletCode = palletCode,
|
Barcode = result.FinalLockInfo.CurrentBarcode,
|
MaterielCode = result.FinalLockInfo.MaterielCode,
|
PickQuantity = result.ActualPickedQty,
|
PickTime = DateTime.Now,
|
Operator = App.User.UserName,
|
OutStockLockId = result.FinalLockInfo.Id,
|
|
BarcodeUnit = result.FinalLockInfo.BarcodeUnit,
|
BarcodeQty = result.FinalLockInfo.BarcodeQty,
|
BatchNo = result.FinalLockInfo.BatchNo,
|
lineNo = result.FinalLockInfo.lineNo,
|
SupplyCode = result.FinalLockInfo.SupplyCode,
|
WarehouseCode = result.FinalLockInfo.WarehouseCode,
|
// IsCancelled = false
|
};
|
|
await Db.Insertable(pickingRecord).ExecuteCommandAsync();
|
}
|
|
private async Task CheckAndUpdateOrderStatus(string orderNo)
|
{
|
var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((detail, order) => detail.OrderId == order.Id)
|
.Where((detail, order) => order.OrderNo == orderNo)
|
.Select((detail, order) => detail)
|
.ToListAsync();
|
|
bool allCompleted = orderDetails.All(x => x.OverOutQuantity >= x.NeedOutQuantity);
|
|
var orderStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
|
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => x.OrderStatus == orderStatus)
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
private string GetPalletStatusText(PalletStatusEnum status)
|
{
|
return status switch
|
{
|
PalletStatusEnum.未开始 => "未开始",
|
PalletStatusEnum.拣选中 => "拣选中",
|
PalletStatusEnum.已完成 => "已完成",
|
PalletStatusEnum.无任务 => "无任务",
|
_ => "未知"
|
};
|
}
|
#endregion
|
|
|
#region DTO类
|
/// <summary>
|
/// 条码状态信息DTO
|
/// </summary>
|
public class BarcodeStatusInfoDto
|
{
|
public string Barcode { get; set; }
|
public string OrderNo { get; set; }
|
public bool IsOriginalBarcode { get; set; }
|
public int SplitChainCount { get; set; }
|
public bool HasBeenPicked { get; set; }
|
public decimal TotalPickedQuantity { get; set; }
|
public int PickRecordCount { get; set; }
|
public int LockInfoStatus { get; set; }
|
public decimal LockInfoPickedQty { get; set; }
|
public decimal LockInfoAssignQty { get; set; }
|
public decimal StockQuantity { get; set; }
|
public int StockStatus { get; set; }
|
public bool CanCancelSplit { get; set; }
|
public bool NeedCancelPickFirst { get; set; }
|
public List<string> OperationSuggestions { get; set; } = new List<string>();
|
}
|
public class PickedBarcodeInfo
|
{
|
public string Barcode { get; set; }
|
public decimal PickedQty { get; set; }
|
public int PickRecordCount { get; set; }
|
}
|
/// <summary>
|
/// 自动拆包结果
|
/// </summary>
|
public class AutoSplitResult
|
{
|
public string NewBarcode { get; set; }
|
public decimal SplitQuantity { get; set; }
|
}
|
|
public class PickingResult
|
{
|
public Dt_OutStockLockInfo FinalLockInfo { get; set; }
|
public decimal ActualPickedQty { get; set; }
|
}
|
|
public class RevertPickingResult
|
{
|
public Dt_OutStockLockInfo LockInfo { get; set; }
|
public Dt_StockInfoDetail StockDetail { get; set; }
|
}
|
|
public class SplitResultDto
|
{
|
public string NewBarcode { get; set; }
|
}
|
|
public class ValidationResult<T>
|
{
|
public bool IsValid { get; set; }
|
public string ErrorMessage { get; set; }
|
public T Data { get; set; }
|
|
public static ValidationResult<T> Success(T data) => new ValidationResult<T> { IsValid = true, Data = data };
|
public static ValidationResult<T> Error(string message) => new ValidationResult<T> { IsValid = false, ErrorMessage = message };
|
}
|
|
#endregion
|
}
|
}
|