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_ITaskInfoService;
|
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 ITask_HtyService _task_HtyService;
|
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, ITask_HtyService task_HtyService) : 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;
|
_task_HtyService = task_HtyService;
|
}
|
|
// <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)
|
{
|
_unitOfWorkManage.RollbackTran();
|
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)
|
{
|
_logger.LogInformation($"开始验证空托盘取走 - 订单: {orderNo}, 托盘: {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 stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
|
if (stockInfo == null)
|
{
|
_logger.LogWarning($"未找到托盘的库存信息,可能已被清理");
|
// 如果找不到库存信息,只检查锁定记录
|
}
|
else
|
{
|
// 检查托盘上是否还有库存货物
|
var remainingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockInfo.Id &&
|
x.StockQuantity > 0) // 只检查数量大于0的库存
|
.ToListAsync();
|
|
if (remainingStock.Any())
|
{
|
var remainingQty = remainingStock.Sum(x => x.StockQuantity);
|
var barcodes = string.Join(", ", remainingStock.Select(x => x.Barcode).Take(5)); // 只显示前5个
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
|
$"托盘上还有库存货物,数量{remainingQty},条码: {barcodes},不能取走空箱");
|
}
|
}
|
|
// 检查是否有未完成的锁定记录(状态为出库中或回库中)
|
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 completedLocks = lockInfos.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.拣选完成 ||
|
x.Status == (int)OutLockStockStatusEnum.已取走).ToList();
|
|
if (!completedLocks.Any())
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
|
|
_logger.LogInformation($"空托盘验证通过 - 找到 {completedLocks.Count} 条已完成记录");
|
|
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)
|
{
|
try
|
{
|
// 查找锁定的库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null)
|
{
|
// 记录清理前的状态
|
decimal originalQty = stockDetail.StockQuantity;
|
int originalStatus = stockDetail.Status;
|
|
// 如果库存数量大于0,需要先清零
|
if (stockDetail.StockQuantity > 0)
|
{
|
_logger.LogWarning($"取走空托盘时发现条码 {lockInfo.CurrentBarcode} 还有库存 {stockDetail.StockQuantity},自动清零");
|
stockDetail.StockQuantity = 0;
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
// 标记为已清理状态
|
stockDetail.Status = (int)StockStatusEmun.已清理;
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"清理库存明细 - 条码: {stockDetail.Barcode}, 原状态: {originalStatus} -> 已清理, 原数量: {originalQty} -> 0");
|
}
|
else
|
{
|
_logger.LogWarning($"未找到条码对应的库存明细: {lockInfo.CurrentBarcode}");
|
}
|
|
// 同时清理该托盘上的所有库存(避免遗漏)
|
var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == lockInfo.StockId && x.StockQuantity > 0)
|
.ToListAsync();
|
|
if (allStockDetails.Any())
|
{
|
foreach (var stock in allStockDetails)
|
{
|
stock.StockQuantity = 0;
|
stock.OutboundQuantity = 0;
|
stock.Status = (int)StockStatusEmun.已清理;
|
|
await _stockInfoDetailService.Db.Updateable(stock).ExecuteCommandAsync();
|
_logger.LogWarning($"清理遗漏库存 - 条码: {stock.Barcode}, 数量: {stock.StockQuantity} -> 0");
|
}
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"清理库存信息失败 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, Error: {ex.Message}");
|
// 不抛出异常,继续处理其他记录
|
}
|
}
|
|
|
/// <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}");
|
|
try
|
{
|
// 验证库存数量是否足够
|
decimal availableStock = stockDetail.StockQuantity;
|
if (availableStock < splitQuantity)
|
{
|
throw new InvalidOperationException($"库存不足,当前库存: {availableStock}, 需要拆包: {splitQuantity}");
|
}
|
|
// 验证拆包数量不能等于或大于原锁定信息分配数量
|
if (splitQuantity >= lockInfo.AssignQuantity)
|
{
|
throw new InvalidOperationException($"拆包数量不能等于或大于原锁定信息分配数量,拆包数量: {splitQuantity}, 原分配数量: {lockInfo.AssignQuantity}");
|
}
|
|
// 计算剩余数量
|
decimal remainQty = lockInfo.AssignQuantity - splitQuantity;
|
|
// 验证剩余数量是否合理
|
if (remainQty <= 0)
|
{
|
throw new InvalidOperationException($"拆包后剩余数量必须大于0,当前剩余: {remainQty}");
|
}
|
|
_logger.LogInformation($"拆包计算 - 原分配数量: {lockInfo.AssignQuantity}, 拆包数量: {splitQuantity}, 剩余数量: {remainQty}");
|
_logger.LogInformation($"原库存信息 - 库存数量: {stockDetail.StockQuantity}, 出库数量: {stockDetail.OutboundQuantity}");
|
|
// 生成新条码
|
string newBarcode = await GenerateNewBarcode();
|
_logger.LogInformation($"生成新条码: {newBarcode}");
|
|
// 记录拆包前的关键数据
|
decimal originalLockAssignQty = lockInfo.AssignQuantity;
|
decimal originalLockOrderQty = lockInfo.OrderQuantity;
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
|
// 创建新库存明细
|
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}");
|
|
// 修正:更新原库存明细 - 确保数据一致性
|
// 原库存减少拆包数量,但出库数量保持不变(因为是手动拆包,不涉及实际出库)
|
stockDetail.StockQuantity -= splitQuantity;
|
|
// 确保不会为负数
|
if (stockDetail.StockQuantity < 0)
|
{
|
_logger.LogWarning($"原库存数量出现负数,重置为0");
|
stockDetail.StockQuantity = 0;
|
}
|
|
// 出库数量保持不变
|
// stockDetail.OutboundQuantity = stockDetail.OutboundQuantity; // 保持不变
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"更新原库存明细 - 条码: {stockDetail.Barcode}, " +
|
$"库存数量: {originalStockQty} -> {stockDetail.StockQuantity}, " +
|
$"出库数量: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
|
|
// 创建新锁定信息
|
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();
|
_logger.LogInformation($"创建新锁定信息成功 - 条码: {newBarcode}, 分配数量: {splitQuantity}");
|
|
// 更新原锁定信息
|
lockInfo.AssignQuantity = remainQty;
|
lockInfo.OrderQuantity = remainQty;
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"更新原锁定信息 - 分配数量: {originalLockAssignQty} -> {lockInfo.AssignQuantity}, " +
|
$"订单数量: {originalLockOrderQty} -> {lockInfo.OrderQuantity}");
|
|
// 重要:手动拆包不改变订单明细的总分配数量
|
// 验证订单明细的总分配数量是否保持不变
|
await ValidateOrderDetailAllocationAfterSplit(lockInfo.OrderDetailId, originalLockAssignQty);
|
|
// 记录拆包历史
|
await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode, false, originalStockQty);
|
|
// 创建拆包结果列表
|
var splitResults = CreateSplitResults(lockInfo, splitQuantity, remainQty, newBarcode, stockDetail.Barcode);
|
|
// 验证拆包后数据一致性
|
await ValidateDataConsistencyAfterSplit(lockInfo.OrderDetailId, originalLockAssignQty, originalLockOrderQty);
|
|
_logger.LogInformation($"手动拆包逻辑执行完成 - 原条码: {stockDetail.Barcode}, 新条码: {newBarcode}, 拆包数量: {splitQuantity}");
|
|
return splitResults;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"手动拆包逻辑执行失败 - 原条码: {stockDetail.Barcode}, 拆包数量: {splitQuantity}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
/// <summary>
|
/// 验证拆包后订单明细的分配数量是否保持不变
|
/// </summary>
|
private async Task ValidateOrderDetailAllocationAfterSplit(long orderDetailId, decimal originalTotalAssignQty)
|
{
|
try
|
{
|
// 获取订单明细的所有锁定信息的总分配数量
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderDetailId == orderDetailId)
|
.ToListAsync();
|
|
decimal totalLockAssignQty = allLocks.Sum(x => x.AssignQuantity);
|
|
_logger.LogInformation($"拆包后分配数量验证 - 订单明细ID: {orderDetailId}");
|
_logger.LogInformation($"原始总分配数量: {originalTotalAssignQty}, 当前总分配数量: {totalLockAssignQty}");
|
|
// 手动拆包后总分配数量应该保持不变
|
if (Math.Abs(originalTotalAssignQty - totalLockAssignQty) > 0.01m)
|
{
|
_logger.LogWarning($"拆包后总分配数量发生变化 - 期望: {originalTotalAssignQty}, 实际: {totalLockAssignQty}");
|
|
// 如果变化很小,可能是精度问题,记录但不抛出异常
|
if (Math.Abs(originalTotalAssignQty - totalLockAssignQty) > 1.0m)
|
{
|
throw new InvalidOperationException($"拆包后总分配数量异常变化,期望: {originalTotalAssignQty}, 实际: {totalLockAssignQty}");
|
}
|
}
|
else
|
{
|
_logger.LogInformation($"拆包后分配数量验证通过 - 总分配数量保持不变");
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"验证拆包后分配数量失败 - OrderDetailId: {orderDetailId}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
/// <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}");
|
|
try
|
{
|
// 根据拆包类型调用不同的处理方法
|
if (splitRecord.IsAutoSplit)
|
{
|
await HandleAutoSplitCancel(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
|
}
|
else
|
{
|
await HandleManualSplitCancel(splitRecord, originalLockInfo, newLockInfo, newStockDetail);
|
}
|
|
// 验证取消拆包后数据一致性
|
await ValidateDataConsistencyAfterCancelSplit(originalLockInfo.OrderDetailId,
|
originalLockInfo.AssignQuantity, originalLockInfo.OrderQuantity,
|
splitRecord.IsAutoSplit, splitRecord.SplitQty);
|
|
// 检查并更新批次和订单状态
|
await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
|
await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
|
|
_logger.LogInformation($"取消拆包逻辑执行完成");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"取消拆包逻辑执行失败: {ex.Message}");
|
throw;
|
}
|
}
|
|
/// <summary>
|
/// 处理自动拆包取消
|
/// </summary>
|
private async Task HandleAutoSplitCancel(Dt_SplitPackageRecord splitRecord,
|
Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
|
Dt_StockInfoDetail newStockDetail)
|
{
|
_logger.LogInformation($"处理自动拆包取消 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}");
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == originalLockInfo.OrderDetailId);
|
|
if (orderDetail == null)
|
throw new InvalidOperationException("未找到订单明细");
|
|
// 1. 恢复订单明细的分配数量(自动拆包会增加分配数量)
|
decimal originalAllocatedQty = orderDetail.AllocatedQuantity;
|
decimal originalLockQty = orderDetail.LockQuantity;
|
|
orderDetail.AllocatedQuantity -= splitRecord.SplitQty;
|
orderDetail.LockQuantity -= splitRecord.SplitQty;
|
|
// 边界检查
|
if (orderDetail.AllocatedQuantity < 0) orderDetail.AllocatedQuantity = 0;
|
if (orderDetail.LockQuantity < 0) orderDetail.LockQuantity = 0;
|
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"自动拆包取消恢复订单明细 - 分配数量: {originalAllocatedQty} -> {orderDetail.AllocatedQuantity}");
|
|
// 2. 恢复原库存(将拆包的数量加回原库存)
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
|
|
if (originalStock != null)
|
{
|
decimal originalStockQty = originalStock.StockQuantity;
|
originalStock.StockQuantity += splitRecord.SplitQty;
|
|
// 如果原库存状态是出库完成,恢复为出库锁定
|
if (originalStock.Status == (int)StockStatusEmun.出库完成)
|
{
|
originalStock.Status = (int)StockStatusEmun.出库锁定;
|
}
|
|
await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
|
_logger.LogInformation($"自动拆包取消恢复原库存 - 条码: {originalStock.Barcode}, 数量: {originalStockQty} -> {originalStock.StockQuantity}");
|
}
|
|
// 3. 删除新锁定信息和库存明细
|
await DeleteNewSplitRecords(newLockInfo, newStockDetail);
|
}
|
|
/// <summary>
|
/// 处理手动拆包取消
|
/// </summary>
|
private async Task HandleManualSplitCancel(Dt_SplitPackageRecord splitRecord,
|
Dt_OutStockLockInfo originalLockInfo, Dt_OutStockLockInfo newLockInfo,
|
Dt_StockInfoDetail newStockDetail)
|
{
|
_logger.LogInformation($"处理手动拆包取消 - 原条码: {splitRecord.OriginalBarcode}, 新条码: {splitRecord.NewBarcode}");
|
|
// 1. 恢复原锁定信息的分配数量
|
decimal originalAssignQty = originalLockInfo.AssignQuantity;
|
decimal originalOrderQty = originalLockInfo.OrderQuantity;
|
|
originalLockInfo.AssignQuantity += splitRecord.SplitQty;
|
originalLockInfo.OrderQuantity += splitRecord.SplitQty;
|
|
// 恢复状态
|
if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
|
{
|
originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
|
await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"手动拆包取消恢复原锁定信息 - 分配数量: {originalAssignQty} -> {originalLockInfo.AssignQuantity}");
|
|
// 2. 恢复原库存明细
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
|
|
if (originalStock != null)
|
{
|
decimal originalStockQty = originalStock.StockQuantity;
|
originalStock.StockQuantity += splitRecord.SplitQty;
|
|
// 恢复库存状态
|
if (originalStock.Status == (int)StockStatusEmun.出库完成)
|
{
|
originalStock.Status = (int)StockStatusEmun.出库锁定;
|
}
|
|
await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
|
_logger.LogInformation($"手动拆包取消恢复原库存 - 条码: {originalStock.Barcode}, 数量: {originalStockQty} -> {originalStock.StockQuantity}");
|
}
|
|
// 3. 删除新锁定信息和库存明细
|
await DeleteNewSplitRecords(newLockInfo, newStockDetail);
|
}
|
|
/// <summary>
|
/// 删除新拆包记录相关的数据
|
/// </summary>
|
private async Task DeleteNewSplitRecords(Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
|
{
|
// 删除新锁定信息
|
if (newLockInfo != null)
|
{
|
_logger.LogInformation($"删除新锁定信息 - 条码: {newLockInfo.CurrentBarcode}, 分配数量: {newLockInfo.AssignQuantity}");
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == newLockInfo.Id)
|
.ExecuteCommandAsync();
|
}
|
|
// 删除新库存明细
|
if (newStockDetail != null)
|
{
|
_logger.LogInformation($"删除新库存明细 - 条码: {newStockDetail.Barcode}, 库存数量: {newStockDetail.StockQuantity}");
|
await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == newStockDetail.Barcode)
|
.ExecuteCommandAsync();
|
}
|
}
|
/// <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
|
// {
|
// int stockId = 0;
|
// Dt_StockInfo stockInfo = null;
|
// PalletStatusAnalysis statusAnalysis = null;
|
// _unitOfWorkManage.BeginTran();
|
|
// if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
|
// return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
|
|
// // 获取库存信息
|
// stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
// .FirstAsync(x => x.PalletCode == palletCode);
|
// if (stockInfo == null)
|
// return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
|
// stockId = stockInfo.Id;
|
|
// var beforeValidationResult = await ValidateReturnData(orderNo, palletCode, stockId, true);
|
// if (!beforeValidationResult)
|
// {
|
// _logger.LogWarning("回库前数据验证发现问题,但继续执行回库操作");
|
// }
|
// _logger.LogInformation($"步骤4完成: 回库前数据验证{(beforeValidationResult ? "通过" : "发现问题")}");
|
|
|
// // 分析托盘状态
|
// statusAnalysis = await AnalyzePalletStatusForReturn(orderNo, palletCode, stockInfo.Id);
|
// _logger.LogInformation($"【步骤6】托盘状态分析完成");
|
// _logger.LogInformation($" - 是否有回库物品: {statusAnalysis.HasItemsToReturn}");
|
// _logger.LogInformation($" - 是否空托盘: {statusAnalysis.IsEmptyPallet}");
|
// _logger.LogInformation($" - 总回库数量: {statusAnalysis.TotalReturnQty}");
|
// _logger.LogInformation($" - 条码数量: {statusAnalysis.AllBarcodes.Count}");
|
|
// if (!statusAnalysis.HasItemsToReturn)
|
// {
|
// _logger.LogInformation($"【步骤7】无回库物品,处理空托盘");
|
// var result = await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
|
// if (result.Status)
|
// {
|
// _unitOfWorkManage.CommitTran();
|
// _logger.LogInformation($"【空托盘回库成功】订单: {orderNo}, 托盘: {palletCode}");
|
// }
|
// else
|
// {
|
// _unitOfWorkManage.RollbackTran();
|
// _logger.LogError($"【空托盘回库失败】原因: {result.Message}");
|
// }
|
// return result;
|
// }
|
|
// _logger.LogInformation($"开始回库操作 - 订单: {orderNo}, 托盘: {palletCode}, 回库数量: {statusAnalysis.TotalReturnQty}");
|
|
// try
|
// {
|
// // 执行回库数据操作
|
// await ExecuteReturnDataOperations(statusAnalysis);
|
// }
|
// catch (Exception ex)
|
// {
|
// _logger.LogError($"回库数据操作失败: {ex.Message}");
|
// try
|
// {
|
// await ExecuteSimpleReturnDataOperations(statusAnalysis);
|
// _logger.LogInformation($"简化回库数据操作成功");
|
// }
|
// catch (Exception simpleEx)
|
// {
|
// _logger.LogError($"简化回库数据操作也失败 - {simpleEx.Message}");
|
// throw new InvalidOperationException($"回库数据操作失败,主方法: {ex.Message}, 简化方法: {simpleEx.Message}", ex);
|
// }
|
// }
|
|
// // 更新订单状态
|
// await UpdateOrderStatusAfterReturn(orderNo);
|
|
// _unitOfWorkManage.CommitTran();
|
|
// _logger.LogInformation($"步骤10: 开始回库后数据验证");
|
// var afterValidationResult = await ValidateReturnData(orderNo, palletCode, stockId, false);
|
// if (!afterValidationResult)
|
// {
|
// _logger.LogWarning("回库后数据验证发现问题,建议检查数据一致性");
|
// }
|
// _logger.LogInformation($"步骤10完成: 回库后数据验证{(afterValidationResult ? "通过" : "发现问题")}");
|
|
// try
|
// {
|
// await CreateReturnTask(orderNo, palletCode, stockInfo);
|
// _logger.LogInformation($"步骤11成功: 回库任务创建成功");
|
// }
|
// catch (Exception taskEx)
|
// {
|
// // 任务创建失败不影响回库数据操作的成功,只记录错误
|
// _logger.LogError($"步骤11警告: 回库任务创建失败 - {taskEx.Message}");
|
// _logger.LogError($"回库数据操作已成功,但任务创建失败,可能需要手动处理");
|
// }
|
|
// _logger.LogInformation($"【回库成功】订单: {orderNo}, 托盘: {palletCode}, 回库数量: {statusAnalysis.TotalReturnQty}");
|
|
// 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}, 托盘: {palletCode}, 错误类型: {ex.GetType().Name}");
|
// _logger.LogError($"【错误信息】{ex.Message}");
|
// _logger.LogError($"【堆栈信息】{ex.StackTrace}");
|
|
// if (ex.InnerException != null)
|
// {
|
// _logger.LogError($"【内部错误】{ex.InnerException.Message}");
|
// }
|
// return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
|
// }
|
//}
|
|
/// <summary>
|
/// 统一回库方法
|
/// </summary>
|
public async Task<WebResponseContent> ExecutePalletReturn(string orderNo, string palletCode, string returnReason = "分批回库")
|
{
|
try
|
{
|
_logger.LogInformation($"【增强回库开始】订单: {orderNo}, 托盘: {palletCode}");
|
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 基础验证
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
|
return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
|
|
// 2. 获取库存信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
|
int stockId = stockInfo.Id;
|
|
// 3. 执行回库前数据验证
|
var validationResult = await ValidateDataBeforeReturn(orderNo, palletCode, stockId);
|
if (!validationResult.IsValid)
|
{
|
_logger.LogWarning($"回库前数据验证失败: {validationResult.ErrorMessage}");
|
// 可以根据实际情况决定是否继续
|
}
|
|
// 4. 分析托盘状态
|
var statusAnalysis = await AnalyzePalletStatusForReturn(orderNo, palletCode, stockInfo.Id);
|
|
if (!statusAnalysis.HasItemsToReturn)
|
{
|
try
|
{
|
_logger.LogInformation($"【无回库物品】处理空托盘");
|
var result = await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
|
_unitOfWorkManage.CommitTran();
|
return result;
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"空箱回库失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($"空箱回库失败:{ex.Message}");
|
}
|
}
|
|
_logger.LogInformation($"【开始回库】总回库数量: {statusAnalysis.TotalReturnQty}, 条码数: {statusAnalysis.AllBarcodes.Count}");
|
|
// 5. 执行回库操作
|
try
|
{
|
await ExecuteEnhancedReturnOperations(statusAnalysis);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"主回库方法失败: {ex.Message}");
|
// 尝试简化方法
|
await ExecuteSimpleReturnDataOperations(statusAnalysis);
|
}
|
|
// 6. 更新订单状态
|
await UpdateOrderStatusAfterReturn(orderNo);
|
|
_unitOfWorkManage.CommitTran();
|
|
// 7. 创建回库任务
|
try
|
{
|
await CreateReturnTask(orderNo, palletCode, stockInfo);
|
}
|
catch (Exception taskEx)
|
{
|
_logger.LogError($"回库任务创建失败: {taskEx.Message}");
|
// 任务创建失败不影响数据回库
|
}
|
_unitOfWorkManage.CommitTran();
|
// 8. 回库后验证
|
await ValidateDataAfterReturn(orderNo, palletCode, stockId);
|
|
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($"回库失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($"回库失败:{ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 增强的回库前数据验证
|
/// </summary>
|
private async Task<ValidationResult<bool>> ValidateDataBeforeReturn(string orderNo, string palletCode, int stockId)
|
{
|
var errors = new List<string>();
|
|
try
|
{
|
// 1. 验证库存数据
|
var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId)
|
.ToListAsync();
|
|
// 检查库存数量是否为负数
|
var negativeStocks = stockDetails.Where(x => x.StockQuantity < 0).ToList();
|
if (negativeStocks.Any())
|
{
|
errors.Add($"发现负数库存: {string.Join(", ", negativeStocks.Select(x => $"{x.Barcode}:{x.StockQuantity}"))}");
|
}
|
|
// 2. 验证锁定记录
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
// 检查已拣选数量是否大于分配数量
|
var invalidLocks = lockInfos.Where(x => x.PickedQty > x.AssignQuantity).ToList();
|
if (invalidLocks.Any())
|
{
|
errors.Add($"发现已拣选数量大于分配数量的锁定记录");
|
}
|
|
// 3. 验证拆包记录
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
// 检查循环拆包
|
var barcodeMap = new Dictionary<string, string>();
|
foreach (var record in splitRecords.Where(x => !x.IsReverted))
|
{
|
if (!barcodeMap.ContainsKey(record.OriginalBarcode))
|
barcodeMap[record.OriginalBarcode] = record.NewBarcode;
|
}
|
|
// 检查循环引用
|
foreach (var record in splitRecords)
|
{
|
var current = record.OriginalBarcode;
|
var visited = new HashSet<string>();
|
|
while (barcodeMap.ContainsKey(current))
|
{
|
if (visited.Contains(current))
|
{
|
errors.Add($"发现循环拆包引用: {record.OriginalBarcode}");
|
break;
|
}
|
visited.Add(current);
|
current = barcodeMap[current];
|
}
|
}
|
|
if (errors.Any())
|
{
|
return ValidationResult<bool>.Error($"回库前数据验证发现问题: {string.Join("; ", errors)}");
|
}
|
|
return ValidationResult<bool>.Success(true);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"数据验证失败: {ex.Message}");
|
return ValidationResult<bool>.Error($"数据验证异常: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 执行增强的回库操作
|
/// </summary>
|
private async Task ExecuteEnhancedReturnOperations(PalletStatusAnalysis statusAnalysis)
|
{
|
_logger.LogInformation($"执行增强回库操作 - 订单: {statusAnalysis.OrderNo}, 托盘: {statusAnalysis.PalletCode}");
|
|
// 处理已分配的锁定记录
|
if (statusAnalysis.HasRemainingLocks)
|
{
|
await HandleAllocatedLocksReturn(statusAnalysis.RemainingLocks);
|
}
|
|
// 处理未分配的锁定记录
|
if (statusAnalysis.HasUnallocatedLocks)
|
{
|
await HandleUnallocatedLocksReturn(statusAnalysis.UnallocatedLocks);
|
}
|
|
// 处理未分配的库存货物
|
if (statusAnalysis.HasPalletStockGoods)
|
{
|
await HandleUnallocatedStockReturn(statusAnalysis.PalletStockGoods);
|
}
|
|
// 处理拆包记录
|
if (statusAnalysis.HasSplitRecords)
|
{
|
await HandleSplitRecordsReturn(statusAnalysis.SplitRecords, statusAnalysis.StockId);
|
}
|
}
|
|
/// <summary>
|
/// 回库后数据验证
|
/// </summary>
|
private async Task ValidateDataAfterReturn(string orderNo, string palletCode, int stockId)
|
{
|
try
|
{
|
_logger.LogInformation($"开始回库后数据验证");
|
|
// 1. 验证库存状态
|
var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId)
|
.ToListAsync();
|
|
var stillOutboundLocked = stockDetails.Where(x =>
|
x.Status == (int)StockStatusEmun.出库锁定 && x.StockQuantity > 0).ToList();
|
|
if (stillOutboundLocked.Any())
|
{
|
_logger.LogWarning($"回库后仍有出库锁定状态的库存: {stillOutboundLocked.Count}个");
|
}
|
|
// 2. 验证锁定记录状态
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
var notReturnedLocks = lockInfos.Where(x =>
|
x.Status != (int)OutLockStockStatusEnum.已回库 &&
|
x.Status != (int)OutLockStockStatusEnum.已取走).ToList();
|
|
if (notReturnedLocks.Any())
|
{
|
_logger.LogWarning($"回库后仍有未回库状态的锁定记录: {notReturnedLocks.Count}条");
|
}
|
|
// 3. 数据一致性验证
|
decimal totalStock = stockDetails.Sum(x => x.StockQuantity);
|
_logger.LogInformation($"回库后库存总量: {totalStock}");
|
|
_logger.LogInformation($"回库后数据验证完成");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"回库后验证失败: {ex.Message}");
|
}
|
}
|
|
|
/// <summary>
|
/// 验证回库前后数据一致性
|
/// </summary>
|
private async Task<bool> ValidateReturnData(string orderNo, string palletCode, int stockId, bool isBefore = true)
|
{
|
string phase = isBefore ? "回库前" : "回库后";
|
try
|
{
|
|
_logger.LogInformation($"【{phase}数据验证】开始 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
// 1. 检查库存明细
|
var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId)
|
.ToListAsync();
|
|
decimal totalStockQty = stockDetails.Sum(x => x.StockQuantity);
|
_logger.LogInformation($"{phase}库存总量: {totalStockQty}");
|
|
// 2. 检查锁定记录
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
// 检查锁定记录状态分布
|
var statusGroups = lockInfos.GroupBy(x => x.Status)
|
.Select(g => new { Status = g.Key, Count = g.Count() })
|
.ToList();
|
|
foreach (var group in statusGroups)
|
{
|
_logger.LogInformation($"{phase}锁定状态 {GetLockStatusName(group.Status)}: {group.Count} 条");
|
}
|
|
// 3. 基本验证
|
bool isValid = true;
|
|
// 验证1: 如果锁定记录状态为"拣选完成",对应库存应该为0
|
var completedLocks = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
|
foreach (var lockInfo in completedLocks)
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
var stock = stockDetails.FirstOrDefault(x => x.Barcode == lockInfo.CurrentBarcode);
|
if (stock != null && stock.StockQuantity > 0)
|
{
|
_logger.LogWarning($"{phase}验证警告 - 锁定ID {lockInfo.Id} 拣选完成但库存不为0: {stock.StockQuantity}");
|
}
|
}
|
}
|
|
// 验证2: 库存状态一致性
|
foreach (var stock in stockDetails)
|
{
|
if (stock.Status == (int)StockStatusEmun.出库锁定 && stock.StockQuantity == 0)
|
{
|
_logger.LogWarning($"{phase}验证警告 - 条码 {stock.Barcode} 状态为出库锁定但库存为0");
|
}
|
}
|
|
_logger.LogInformation($"【{phase}数据验证】完成 - 状态: {(isValid ? "通过" : "有警告")}");
|
return isValid;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"{phase} 数据验证失败: {ex.Message}");
|
return false;
|
}
|
}
|
|
private string GetLockStatusName(int status)
|
{
|
return status switch
|
{
|
1 => "出库中",
|
2 => "拣选完成",
|
3 => "已回库",
|
4 => "已取走",
|
_ => $"未知({status})"
|
};
|
}
|
/// <summary>
|
/// 简化回库数据操作(当主方法失败时使用)
|
/// </summary>
|
private async Task ExecuteSimpleReturnDataOperations(PalletStatusAnalysis statusAnalysis)
|
{
|
_logger.LogInformation($"【简化回库】开始执行简化回库操作");
|
|
try
|
{
|
// 获取该托盘的所有条码
|
var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == statusAnalysis.StockId && x.StockQuantity > 0)
|
.ToListAsync();
|
|
_logger.LogInformation($"找到 {allStockDetails.Count} 个有库存的条码");
|
|
foreach (var stockDetail in allStockDetails)
|
{
|
// 恢复所有库存状态为入库完成
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"简化回库 - 条码: {stockDetail.Barcode}, 数量: {stockDetail.StockQuantity}");
|
}
|
}
|
|
// 更新所有锁定记录为已回库
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == statusAnalysis.OrderNo &&
|
x.PalletCode == statusAnalysis.PalletCode &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.拣选完成))
|
.ToListAsync();
|
|
foreach (var lockInfo in allLocks)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"简化回库 - 锁定记录: {lockInfo.Id}");
|
}
|
|
_logger.LogInformation($"【简化回库】完成 - 处理 {allStockDetails.Count} 个条码, {allLocks.Count} 条锁定记录");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"简化回库失败: {ex.Message}");
|
throw;
|
}
|
}
|
/// <summary>
|
/// 执行回库数据操作 - 简化版本
|
/// </summary>
|
private async Task ExecuteReturnDataOperations(PalletStatusAnalysis statusAnalysis)
|
{
|
_logger.LogInformation($"开始执行回库数据操作 - 订单: {statusAnalysis.OrderNo}, 托盘: {statusAnalysis.PalletCode}");
|
|
try
|
{
|
// 使用 HashSet 避免重复处理条码
|
var processedBarcodes = new HashSet<string>();
|
decimal totalReturnedQty = 0;
|
|
// 1. 处理已分配的未分拣锁定记录
|
if (statusAnalysis.HasRemainingLocks)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.RemainingLocks.Count} 条已分配未分拣锁定记录");
|
|
foreach (var lockInfo in statusAnalysis.RemainingLocks)
|
{
|
if (string.IsNullOrEmpty(lockInfo.CurrentBarcode) || processedBarcodes.Contains(lockInfo.CurrentBarcode))
|
{
|
_logger.LogInformation($"跳过重复或空条码的锁定记录 - ID: {lockInfo.Id}");
|
continue;
|
}
|
|
// 计算回库数量(未拣选的部分)
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
if (returnQty > 0)
|
{
|
_logger.LogInformation($"处理锁定记录回库 - ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
|
// 处理库存
|
await ProcessStockForReturn(lockInfo.CurrentBarcode, statusAnalysis.StockId, returnQty);
|
|
// 标记为已回库
|
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);
|
await ReduceOrderDetailAllocation(orderDetail, returnQty);
|
}
|
|
processedBarcodes.Add(lockInfo.CurrentBarcode);
|
totalReturnedQty += returnQty;
|
|
_logger.LogInformation($"锁定记录回库完成 - ID: {lockInfo.Id}, 回库数量: {returnQty}");
|
}
|
else
|
{
|
_logger.LogInformation($"锁定记录无需回库 - ID: {lockInfo.Id}, 已拣选完成");
|
}
|
}
|
}
|
|
// 2. 处理未分配的库存货物
|
if (statusAnalysis.HasPalletStockGoods)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.PalletStockGoods.Count} 个未分配库存货物");
|
|
foreach (var stockDetail in statusAnalysis.PalletStockGoods)
|
{
|
if (string.IsNullOrEmpty(stockDetail.Barcode) || processedBarcodes.Contains(stockDetail.Barcode))
|
{
|
_logger.LogInformation($"跳过重复或空条码的库存 - 库存ID: {stockDetail.Id}");
|
continue;
|
}
|
|
if (stockDetail.StockQuantity > 0)
|
{
|
decimal returnQty = stockDetail.StockQuantity;
|
_logger.LogInformation($"处理未分配库存回库 - 条码: {stockDetail.Barcode}, 回库数量: {returnQty}");
|
|
// 直接恢复库存状态
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
processedBarcodes.Add(stockDetail.Barcode);
|
totalReturnedQty += returnQty;
|
|
_logger.LogInformation($"未分配库存回库完成 - 条码: {stockDetail.Barcode}, 数量: {returnQty}");
|
}
|
}
|
}
|
|
// 3. 处理拆包记录相关的条码
|
if (statusAnalysis.HasSplitRecords)
|
{
|
_logger.LogInformation($"处理 {statusAnalysis.SplitRecords.Count} 条拆包记录");
|
|
// 收集拆包相关的所有条码
|
var splitBarcodes = new List<string>();
|
foreach (var splitRecord in statusAnalysis.SplitRecords)
|
{
|
if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode))
|
splitBarcodes.Add(splitRecord.OriginalBarcode);
|
if (!string.IsNullOrEmpty(splitRecord.NewBarcode))
|
splitBarcodes.Add(splitRecord.NewBarcode);
|
}
|
|
// 去重
|
splitBarcodes = splitBarcodes.Distinct().ToList();
|
|
foreach (var barcode in splitBarcodes)
|
{
|
if (processedBarcodes.Contains(barcode))
|
{
|
_logger.LogInformation($"拆包条码已处理: {barcode}");
|
continue;
|
}
|
|
// 查找库存
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == statusAnalysis.StockId);
|
|
if (stockDetail != null && stockDetail.StockQuantity > 0)
|
{
|
decimal returnQty = stockDetail.StockQuantity;
|
_logger.LogInformation($"处理拆包相关库存回库 - 条码: {barcode}, 回库数量: {returnQty}");
|
|
// 恢复库存状态
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
processedBarcodes.Add(barcode);
|
totalReturnedQty += returnQty;
|
|
_logger.LogInformation($"拆包库存回库完成 - 条码: {barcode}, 数量: {returnQty}");
|
}
|
}
|
}
|
}
|
|
_logger.LogInformation($"回库数据操作完成 - 总回库数量: {totalReturnedQty}, 处理条码数: {processedBarcodes.Count}");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"回库数据操作失败 - 订单: {statusAnalysis.OrderNo}, 托盘: {statusAnalysis.PalletCode}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
/// <summary>
|
/// 处理库存回库 - 简化逻辑
|
/// </summary>
|
private async Task ProcessStockForReturn(string barcode, int stockId, decimal returnQty)
|
{
|
try
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
|
|
if (stockDetail == null)
|
{
|
_logger.LogWarning($"未找到库存明细 - 条码: {barcode}");
|
return;
|
}
|
|
// 记录原始值
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
int originalStatus = stockDetail.Status;
|
|
// 简化处理:如果库存数量大于0,恢复为入库完成状态
|
if (stockDetail.StockQuantity > 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
}
|
|
// 调整出库数量(避免出现负数)
|
if (stockDetail.OutboundQuantity > 0)
|
{
|
stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
|
}
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"库存回库处理 - 条码: {barcode}, 状态: {originalStatus} -> {stockDetail.Status}, " +
|
$"库存: {originalStockQty} -> {stockDetail.StockQuantity}, " +
|
$"出库: {originalOutboundQty} -> {stockDetail.OutboundQuantity}");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"处理库存回库失败 - 条码: {barcode}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
/// <summary>
|
/// 为锁定记录减少订单明细的分配数量
|
/// </summary>
|
private async Task ReduceOrderDetailAllocationForLock(long orderDetailId, decimal reduceQty)
|
{
|
if (orderDetailId <= 0)
|
return;
|
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
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}");
|
}
|
|
/// <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}");
|
|
// 恢复库存状态
|
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;
|
stockDetail.StockQuantity += returnQty;
|
|
// 恢复库存状态
|
if (stockDetail.Status == (int)StockStatusEmun.出库完成)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
}
|
else if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
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();
|
|
_logger.LogInformation($"更新未分配锁定状态 - 锁定ID: {lockInfo.Id}, 状态: 出库中 -> 已回库");
|
}
|
|
_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
|
};
|
var targetAddress = currentTask.TargetAddress;
|
|
try
|
{
|
await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
|
_logger.LogInformation($"CreateReturnTaskAndHandleESS 分批删除历史任务: {orderNo} , {currentTask.TaskNum}");
|
// 删除原始出库任务
|
//_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
|
var result = _task_HtyService.DeleteAndMoveIntoHty(currentTask, OperateTypeEnum.人工删除);
|
await _taskRepository.Db.Deleteable(currentTask).ExecuteCommandAsync();
|
|
if (!result)
|
{
|
await _taskRepository.Db.Deleteable(currentTask).ExecuteCommandAsync();
|
}
|
_logger.LogInformation($"CreateReturnTaskAndHandleESS 分批删除历史任务: {orderNo} , {currentTask.TaskNum},影响行 {result}");
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogInformation($"创建回库任务失败 - 订单: {orderNo}, 托盘: {palletCode}");
|
throw new Exception($"创建回库任务失败 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
}
|
// 发送ESS命令
|
await SendESSCommands(palletCode, 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>
|
|
/// <summary>
|
/// 分析托盘状态用于回库 - 完整版本
|
/// 确保不会错误识别需要回库的物品,避免重复计算
|
/// </summary>
|
private async Task<PalletStatusAnalysis> AnalyzePalletStatusForReturn(string orderNo, string palletCode, int stockId)
|
{
|
var result = new PalletStatusAnalysis
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
StockId = stockId,
|
AllBarcodes = new List<string>(),
|
RemainingLocks = new List<Dt_OutStockLockInfo>(),
|
UnallocatedLocks = new List<Dt_OutStockLockInfo>(),
|
PalletStockGoods = new List<Dt_StockInfoDetail>(),
|
SplitRecords = new List<Dt_SplitPackageRecord>()
|
};
|
|
_logger.LogInformation($"开始分析托盘状态用于回库 - 订单: {orderNo}, 托盘: {palletCode}, StockId: {stockId}");
|
|
try
|
{
|
// 1. 分析所有锁定记录(状态为出库中)
|
var allUnfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.ToListAsync();
|
|
if (allUnfinishedLocks.Any())
|
{
|
_logger.LogInformation($"找到 {allUnfinishedLocks.Count} 条出库中状态的锁定记录");
|
|
// 区分已分配和未分配的锁定记录
|
var allocatedLocks = allUnfinishedLocks
|
.Where(x => x.IsUnallocated != 1 && x.OrderDetailId > 0)
|
.ToList();
|
|
var unallocatedLocks = allUnfinishedLocks
|
.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}");
|
}
|
}
|
else
|
{
|
_logger.LogInformation($"未找到出库中状态的锁定记录");
|
}
|
|
// 2. 分析托盘上的剩余库存货物(状态为出库锁定但未分配)
|
var allStockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId &&
|
x.Status == (int)StockStatusEmun.出库锁定 &&
|
x.StockQuantity > 0)
|
.ToListAsync();
|
|
if (allStockDetails.Any())
|
{
|
_logger.LogInformation($"找到 {allStockDetails.Count} 个出库锁定状态的库存货物");
|
|
// 过滤掉已经被锁定记录占用的库存
|
var lockedBarcodes = allUnfinishedLocks.Select(x => x.CurrentBarcode).Where(b => !string.IsNullOrEmpty(b)).ToList();
|
var unlockedStockGoods = allStockDetails
|
.Where(x => !lockedBarcodes.Contains(x.Barcode))
|
.ToList();
|
|
_logger.LogInformation($"过滤后剩余 {unlockedStockGoods.Count} 个未被锁定的库存货物");
|
|
// 进一步过滤:检查这些库存是否有关联的锁定记录(包括状态不是出库中的)
|
var trulyUnallocatedGoods = new List<Dt_StockInfoDetail>();
|
foreach (var stock in unlockedStockGoods)
|
{
|
var hasActiveLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == stock.Barcode &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.拣选完成))
|
.AnyAsync();
|
|
if (!hasActiveLock)
|
{
|
trulyUnallocatedGoods.Add(stock);
|
}
|
else
|
{
|
_logger.LogInformation($"条码 {stock.Barcode} 有活跃的锁定记录,跳过作为未分配库存");
|
}
|
}
|
|
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}");
|
|
// 记录每个货物的详细信息
|
foreach (var stock in trulyUnallocatedGoods)
|
{
|
_logger.LogInformation($"未分配库存 - 条码: {stock.Barcode}, 物料: {stock.MaterielCode}, 数量: {stock.StockQuantity}");
|
}
|
}
|
}
|
else
|
{
|
_logger.LogInformation($"未找到出库锁定状态的库存货物");
|
}
|
|
// 3. 分析拆包记录(状态不是已拣选和已回库的)
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
!x.IsReverted &&
|
x.Status != (int)SplitPackageStatusEnum.已拣选 &&
|
x.Status != (int)SplitPackageStatusEnum.已回库)
|
.ToListAsync();
|
|
if (splitRecords.Any())
|
{
|
result.HasSplitRecords = true;
|
result.SplitRecords = splitRecords;
|
|
// 计算拆包记录相关的回库数量(避免重复计算)
|
var splitBarcodes = new HashSet<string>();
|
decimal splitReturnQty = 0;
|
|
foreach (var splitRecord in splitRecords)
|
{
|
// 原条码
|
if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode) && !splitBarcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == stockId);
|
|
if (originalStock != null && originalStock.StockQuantity > 0)
|
{
|
splitReturnQty += originalStock.StockQuantity;
|
|
// 添加到所有条码列表(如果没有重复)
|
if (!result.AllBarcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
result.AllBarcodes.Add(splitRecord.OriginalBarcode);
|
}
|
|
splitBarcodes.Add(splitRecord.OriginalBarcode);
|
_logger.LogInformation($"拆包记录 - 原条码: {splitRecord.OriginalBarcode}, 数量: {originalStock.StockQuantity}");
|
}
|
}
|
|
// 新条码
|
if (!string.IsNullOrEmpty(splitRecord.NewBarcode) && !splitBarcodes.Contains(splitRecord.NewBarcode))
|
{
|
var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.NewBarcode && x.StockId == stockId);
|
|
if (newStock != null && newStock.StockQuantity > 0)
|
{
|
splitReturnQty += newStock.StockQuantity;
|
|
// 添加到所有条码列表(如果没有重复)
|
if (!result.AllBarcodes.Contains(splitRecord.NewBarcode))
|
{
|
result.AllBarcodes.Add(splitRecord.NewBarcode);
|
}
|
|
splitBarcodes.Add(splitRecord.NewBarcode);
|
_logger.LogInformation($"拆包记录 - 新条码: {splitRecord.NewBarcode}, 数量: {newStock.StockQuantity}");
|
}
|
}
|
|
// 记录拆包信息
|
_logger.LogInformation($"拆包记录 - ID: {splitRecord.Id}, 原条码: {splitRecord.OriginalBarcode}, " +
|
$"新条码: {splitRecord.NewBarcode}, 拆包数量: {splitRecord.SplitQty}, " +
|
$"是否自动: {splitRecord.IsAutoSplit}, 是否撤销: {splitRecord.IsReverted}");
|
}
|
|
result.SplitReturnQty = splitReturnQty;
|
_logger.LogInformation($"发现{splitRecords.Count}条拆包记录,关联 {splitBarcodes.Count} 个条码,总数量: {splitReturnQty}");
|
}
|
else
|
{
|
_logger.LogInformation($"未找到需要回库的拆包记录");
|
}
|
|
// 4. 计算总回库数量和空托盘状态
|
result.TotalReturnQty = result.RemainingLocksReturnQty +
|
result.UnallocatedLocksReturnQty +
|
result.PalletStockReturnQty +
|
result.SplitReturnQty;
|
|
result.HasItemsToReturn = result.TotalReturnQty > 0;
|
result.IsEmptyPallet = !result.HasItemsToReturn;
|
|
// 去重所有条码
|
result.AllBarcodes = result.AllBarcodes.Distinct().ToList();
|
|
_logger.LogInformation($"托盘状态分析完成 - 订单: {orderNo}, 托盘: {palletCode}");
|
_logger.LogInformation($"汇总信息:");
|
_logger.LogInformation($" - 已分配锁定记录: {result.RemainingLocks.Count} 条, 数量: {result.RemainingLocksReturnQty}");
|
_logger.LogInformation($" - 未分配锁定记录: {result.UnallocatedLocks.Count} 条, 数量: {result.UnallocatedLocksReturnQty}");
|
_logger.LogInformation($" - 未分配库存货物: {result.PalletStockGoods.Count} 个, 数量: {result.PalletStockReturnQty}");
|
_logger.LogInformation($" - 拆包记录: {result.SplitRecords.Count} 条, 数量: {result.SplitReturnQty}");
|
_logger.LogInformation($" - 总回库数量: {result.TotalReturnQty}");
|
_logger.LogInformation($" - 是否空托盘: {result.IsEmptyPallet}");
|
_logger.LogInformation($" - 涉及条码: {string.Join(", ", result.AllBarcodes)}");
|
|
// 5. 额外的数据验证
|
await ValidateAnalysisResults(result, stockId);
|
|
return result;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"托盘状态分析失败 - 订单: {orderNo}, 托盘: {palletCode}, Error: {ex.Message}");
|
throw;
|
}
|
}
|
|
/// <summary>
|
/// 验证分析结果,确保数据一致性
|
/// </summary>
|
private async Task ValidateAnalysisResults(PalletStatusAnalysis analysis, int stockId)
|
{
|
_logger.LogInformation($"开始验证分析结果 - 订单: {analysis.OrderNo}, 托盘: {analysis.PalletCode}");
|
|
try
|
{
|
// 1. 验证锁定记录和库存明细的匹配
|
foreach (var lockInfo in analysis.RemainingLocks.Concat(analysis.UnallocatedLocks))
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail == null)
|
{
|
_logger.LogWarning($"锁定记录 {lockInfo.Id} 的条码 {lockInfo.CurrentBarcode} 在库存明细中不存在");
|
}
|
else if (stockDetail.StockQuantity <= 0)
|
{
|
_logger.LogWarning($"锁定记录 {lockInfo.Id} 的条码 {lockInfo.CurrentBarcode} 库存数量为0或负数");
|
}
|
}
|
}
|
|
// 2. 验证总回库数量的合理性
|
// 获取托盘上的总库存数量
|
var totalStockOnPallet = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId)
|
.SumAsync(x => x.StockQuantity);
|
|
if (analysis.TotalReturnQty > totalStockOnPallet)
|
{
|
_logger.LogWarning($"总回库数量 {analysis.TotalReturnQty} 大于托盘总库存 {totalStockOnPallet},可能存在计算错误");
|
}
|
|
// 3. 验证条码的唯一性
|
var duplicateBarcodes = analysis.AllBarcodes
|
.GroupBy(x => x)
|
.Where(g => g.Count() > 1)
|
.Select(g => g.Key)
|
.ToList();
|
|
if (duplicateBarcodes.Any())
|
{
|
_logger.LogWarning($"发现重复的条码: {string.Join(", ", duplicateBarcodes)}");
|
}
|
|
_logger.LogInformation($"分析结果验证完成");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"分析结果验证失败 - Error: {ex.Message}");
|
// 不抛出异常,只记录错误
|
}
|
}
|
|
/// <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
|
{
|
_logger.LogInformation($"【取走空箱开始】订单: {orderNo}, 托盘: {palletCode}");
|
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 先尝试执行回库操作,确保所有物品都回库
|
_logger.LogInformation($"步骤1: 先执行回库操作");
|
var returnResult = await ExecutePalletReturn(orderNo, palletCode, "取走空箱前回库");
|
if (!returnResult.Status)
|
{
|
// 回库失败,可能是空托盘或者有其他问题
|
_logger.LogWarning($"回库操作失败: {returnResult.Message}");
|
|
// 继续验证空箱取走条件
|
}
|
|
// 2. 验证空箱取走条件(必须全部完成拣选)
|
_logger.LogInformation($"步骤2: 验证空箱取走条件");
|
var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
|
if (!validationResult.IsValid)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"空箱验证失败: {validationResult.ErrorMessage}");
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
}
|
|
var completedLocks = validationResult.Data;
|
_logger.LogInformation($"验证通过,找到 {completedLocks.Count} 条已完成记录");
|
|
// 3. 清理已完成的锁定记录(标记为已取走)
|
_logger.LogInformation($"步骤3: 清理锁定记录");
|
foreach (var lockInfo in completedLocks)
|
{
|
// 只处理状态为拣选完成的记录(已取走的跳过)
|
if (lockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
|
{
|
// 标记锁定记录为已取走
|
lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"锁定记录标记为已取走 - ID: {lockInfo.Id}");
|
}
|
}
|
|
// 4. 清理对应的库存记录状态
|
_logger.LogInformation($"步骤4: 清理库存记录");
|
foreach (var lockInfo in completedLocks)
|
{
|
await CleanupStockInfo(lockInfo);
|
}
|
|
// 5. 更新相关订单状态
|
_logger.LogInformation($"步骤5: 更新订单状态");
|
await UpdateOrderStatusAfterPalletRemoval(orderNo);
|
|
// 6. 记录操作历史
|
_logger.LogInformation($"步骤6: 记录操作历史");
|
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}");
|
}
|
}
|
}
|
}
|
|
/// <summary>
|
/// 收集需要回库的条码(避免重复)
|
/// </summary>
|
private async Task<HashSet<string>> CollectBarcodesForReturn(string orderNo, string palletCode, int stockId)
|
{
|
var barcodes = new HashSet<string>();
|
|
try
|
{
|
_logger.LogInformation($"开始收集回库条码 - 订单: {orderNo}, 托盘: {palletCode}, StockId: {stockId}");
|
|
// 1. 从锁定记录收集
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中
|
// || x.Status == (int)OutLockStockStatusEnum.出库锁定)
|
))
|
.ToListAsync();
|
|
foreach (var lockInfo in lockInfos)
|
{
|
if (!string.IsNullOrEmpty(lockInfo.CurrentBarcode))
|
{
|
barcodes.Add(lockInfo.CurrentBarcode);
|
_logger.LogInformation($"从锁定记录添加条码: {lockInfo.CurrentBarcode}");
|
}
|
}
|
|
// 2. 从库存明细收集(状态为出库锁定的)
|
var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId &&
|
x.Status == (int)StockStatusEmun.出库锁定 &&
|
x.StockQuantity > 0)
|
.ToListAsync();
|
|
foreach (var stockDetail in stockDetails)
|
{
|
if (!barcodes.Contains(stockDetail.Barcode) && !string.IsNullOrEmpty(stockDetail.Barcode))
|
{
|
barcodes.Add(stockDetail.Barcode);
|
_logger.LogInformation($"从库存明细添加条码: {stockDetail.Barcode}, 数量: {stockDetail.StockQuantity}");
|
}
|
}
|
|
// 3. 从拆包记录收集
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
!x.IsReverted &&
|
x.Status != (int)SplitPackageStatusEnum.已拣选)
|
.ToListAsync();
|
|
foreach (var splitRecord in splitRecords)
|
{
|
// 添加原条码
|
if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode) && !barcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
barcodes.Add(splitRecord.OriginalBarcode);
|
_logger.LogInformation($"从拆包记录添加原条码: {splitRecord.OriginalBarcode}");
|
}
|
|
// 添加新条码
|
if (!string.IsNullOrEmpty(splitRecord.NewBarcode) && !barcodes.Contains(splitRecord.NewBarcode))
|
{
|
barcodes.Add(splitRecord.NewBarcode);
|
_logger.LogInformation($"从拆包记录添加新条码: {splitRecord.NewBarcode}");
|
}
|
}
|
|
_logger.LogInformation($"条码收集完成 - 共 {barcodes.Count} 个条码: {string.Join(", ", barcodes)}");
|
|
return barcodes;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"收集回库条码失败 - Error: {ex.Message}");
|
return barcodes;
|
}
|
}
|
|
/// <summary>
|
/// 统一处理条码回库(避免重复处理)
|
/// </summary>
|
private async Task ProcessBarcodeReturn(string barcode, int stockId, decimal returnQty, HashSet<string> processedBarcodes)
|
{
|
if (returnQty <= 0)
|
return;
|
|
// 检查是否已处理过
|
if (processedBarcodes.Contains(barcode))
|
{
|
_logger.LogInformation($"跳过已处理的条码: {barcode}");
|
return;
|
}
|
|
// 获取库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
|
|
if (stockDetail == null)
|
{
|
_logger.LogWarning($"未找到条码对应的库存明细: {barcode}");
|
return;
|
}
|
|
// 记录原始值
|
decimal originalStockQty = stockDetail.StockQuantity;
|
decimal originalOutboundQty = stockDetail.OutboundQuantity;
|
int originalStatus = stockDetail.Status;
|
|
_logger.LogInformation($"处理条码回库 - {barcode}: 原始库存={originalStockQty}, 原始出库={originalOutboundQty}, 状态={originalStatus}");
|
|
// 验证数据一致性
|
if (originalOutboundQty < returnQty)
|
{
|
_logger.LogWarning($"出库数量小于回库数量,调整回库数量 - 条码: {barcode}, 出库数量: {originalOutboundQty}, 回库数量: {returnQty}");
|
returnQty = originalOutboundQty;
|
}
|
|
// 更新库存:出库数量减少,库存数量增加
|
stockDetail.OutboundQuantity -= returnQty;
|
stockDetail.StockQuantity += returnQty;
|
|
// 确保不会出现负数
|
if (stockDetail.OutboundQuantity < 0)
|
{
|
_logger.LogWarning($"出库数量出现负数,重置为0 - 条码: {barcode}");
|
stockDetail.OutboundQuantity = 0;
|
}
|
|
// 更新状态
|
if (stockDetail.OutboundQuantity <= 0 && stockDetail.StockQuantity > 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
_logger.LogInformation($"库存状态更新为入库完成 - 条码: {barcode}");
|
}
|
else if (stockDetail.StockQuantity > 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库锁定;
|
_logger.LogInformation($"库存状态保持为出库锁定 - 条码: {barcode}");
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 标记为已处理
|
processedBarcodes.Add(barcode);
|
|
_logger.LogInformation($"条码回库完成 - {barcode}: " +
|
$"库存 {originalStockQty} -> {stockDetail.StockQuantity}, " +
|
$"出库 {originalOutboundQty} -> {stockDetail.OutboundQuantity}, " +
|
$"状态 {originalStatus} -> {stockDetail.Status}");
|
}
|
|
/// <summary>
|
/// 处理拆包记录回库 - 避免重复
|
/// </summary>
|
private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, int stockId, HashSet<string> processedBarcodes)
|
{
|
if (!splitRecords.Any())
|
return;
|
|
_logger.LogInformation($"开始处理拆包记录回库 - 共 {splitRecords.Count} 条记录");
|
|
foreach (var splitRecord in splitRecords)
|
{
|
// 只处理未撤销的拆包记录
|
if (splitRecord.IsReverted)
|
{
|
_logger.LogInformation($"跳过已撤销的拆包记录 - ID: {splitRecord.Id}");
|
continue;
|
}
|
|
// 处理新条码
|
if (!string.IsNullOrEmpty(splitRecord.NewBarcode) && !processedBarcodes.Contains(splitRecord.NewBarcode))
|
{
|
var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.NewBarcode && x.StockId == stockId);
|
|
if (newStock != null && newStock.StockQuantity > 0)
|
{
|
// 拆包的新条码回库数量应该是其库存数量
|
await ProcessBarcodeReturn(splitRecord.NewBarcode, stockId, newStock.StockQuantity, processedBarcodes);
|
}
|
}
|
|
// 处理原条码
|
if (!string.IsNullOrEmpty(splitRecord.OriginalBarcode) && !processedBarcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == stockId);
|
|
if (originalStock != null && originalStock.StockQuantity > 0)
|
{
|
// 原条码的回库数量应该是拆包后剩余的数量
|
await ProcessBarcodeReturn(splitRecord.OriginalBarcode, stockId, originalStock.StockQuantity, processedBarcodes);
|
}
|
}
|
|
// 更新拆包记录状态为已回库
|
splitRecord.Status = (int)SplitPackageStatusEnum.已回库;
|
await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
|
|
_logger.LogInformation($"拆包记录状态更新为已回库 - 记录ID: {splitRecord.Id}");
|
}
|
|
_logger.LogInformation($"拆包记录回库处理完成");
|
}
|
|
/// <summary>
|
/// 简化版回库方法 - 绕过复杂验证
|
/// </summary>
|
public async Task<WebResponseContent> SimplePalletReturn(string orderNo, string palletCode, string returnReason = "简化回库")
|
{
|
try
|
{
|
_logger.LogInformation($"【简化回库开始】订单: {orderNo}, 托盘: {palletCode}");
|
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 获取库存信息(跳过复杂验证)
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
|
if (stockInfo == null)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
}
|
|
// 2. 直接查找需要回库的条码(简化逻辑)
|
var barcodesToReturn = await GetBarcodesForSimpleReturn(orderNo, palletCode, stockInfo.Id);
|
|
if (!barcodesToReturn.Any())
|
{
|
try
|
{
|
_logger.LogInformation($"【无回库物品】处理空托盘");
|
var result = await HandleEmptyPalletReturn(orderNo, palletCode, stockInfo);
|
_unitOfWorkManage.CommitTran();
|
return result;
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"空箱回库失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($"空箱回库失败:{ex.Message}");
|
}
|
}
|
|
// 3. 简化处理每个条码
|
foreach (var barcode in barcodesToReturn)
|
{
|
await ProcessSimpleBarcodeReturn(barcode, stockInfo.Id);
|
}
|
|
// 4. 更新订单状态(简化)
|
await UpdateOrderStatusAfterReturn(orderNo);
|
|
|
// 5. 创建回库任务
|
await CreateReturnTask(orderNo, palletCode, stockInfo);
|
|
_unitOfWorkManage.CommitTran();
|
return WebResponseContent.Instance.OK($"简化回库成功,处理 {barcodesToReturn.Count} 个条码");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"简化回库失败: {ex.Message}");
|
return WebResponseContent.Instance.Error($"回库失败: {ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 简化获取回库条码
|
/// </summary>
|
private async Task<List<string>> GetBarcodesForSimpleReturn(string orderNo, string palletCode, int stockId)
|
{
|
var barcodes = new List<string>();
|
|
try
|
{
|
// 1. 从锁定记录获取
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.Select(x => x.CurrentBarcode)
|
.ToListAsync();
|
|
barcodes.AddRange(lockInfos.Where(b => !string.IsNullOrEmpty(b)));
|
|
// 2. 从库存明细获取
|
var stockDetails = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.StockId == stockId && x.StockQuantity > 0)
|
.Select(x => x.Barcode)
|
.ToListAsync();
|
|
barcodes.AddRange(stockDetails.Where(b => !string.IsNullOrEmpty(b)));
|
|
// 去重
|
return barcodes.Distinct().ToList();
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"获取回库条码失败: {ex.Message}");
|
return barcodes;
|
}
|
}
|
|
/// <summary>
|
/// 简化处理条码回库
|
/// </summary>
|
private async Task ProcessSimpleBarcodeReturn(string barcode, int stockId)
|
{
|
try
|
{
|
// 1. 获取库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == stockId);
|
|
if (stockDetail == null)
|
{
|
_logger.LogWarning($"未找到条码对应的库存明细: {barcode}");
|
return;
|
}
|
|
// 2. 如果是出库锁定状态,恢复为入库完成
|
if (stockDetail.Status == (int)StockStatusEmun.出库锁定)
|
{
|
stockDetail.Status = (int)StockStatusEmun.入库完成;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"条码状态恢复 - {barcode}: 出库锁定 -> 入库完成");
|
}
|
|
// 3. 更新相关的锁定记录
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.CurrentBarcode == barcode &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.拣选完成))
|
.ToListAsync();
|
|
foreach (var lockInfo in lockInfos)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
_logger.LogInformation($"锁定记录状态更新 - ID: {lockInfo.Id}: 已回库");
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"处理条码回库失败 - 条码: {barcode}, Error: {ex.Message}");
|
}
|
}
|
#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}, 标记为未分配");
|
|
// 重要:自动拆包需要增加订单明细的分配数量和锁定数量
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == lockInfo.OrderDetailId);
|
|
if (orderDetail != null)
|
{
|
decimal originalAllocated = orderDetail.AllocatedQuantity;
|
decimal originalLock = orderDetail.LockQuantity;
|
|
orderDetail.AllocatedQuantity += splitQuantity;
|
orderDetail.LockQuantity += splitQuantity;
|
|
await UpdateBatchAllocateStatus(orderDetail);
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"自动拆包增加订单明细分配 - 分配数量: {originalAllocated} -> {orderDetail.AllocatedQuantity}, " +
|
$"锁定数量: {originalLock} -> {orderDetail.LockQuantity}");
|
}
|
|
// 记录拆包历史
|
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
|
|
};
|
// 保存回库任务
|
var insertcount = await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
if (insertcount <= 0)
|
{
|
throw new Exception("创建任务失败!");
|
}
|
var targetAddress = originalTask.TargetAddress;
|
|
_logger.LogInformation($"CreateReturnTaskAndHandleESS 分批删除历史任务: {orderNo} , {originalTask.TaskNum}");
|
// 删除原始出库任务
|
//_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
|
var result = _task_HtyService.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.人工删除);
|
await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
|
if (!result)
|
{
|
await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
}
|
_logger.LogInformation($"CreateReturnTaskAndHandleESS 分批删除历史任务: {orderNo} , {originalTask.TaskNum},影响行 {result}");
|
|
|
// 给 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
|
}
|
}
|