using Microsoft.Extensions.Logging;
|
using SqlSugar;
|
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_Core;
|
using WIDESEA_Core.BaseRepository;
|
using WIDESEA_Core.BaseServices;
|
using WIDESEA_DTO.Outbound;
|
using WIDESEA_IAllocateService;
|
using WIDESEA_IBasicService;
|
using WIDESEA_IOutboundService;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
using WIDESEA_Model.Models.Basic;
|
using WIDESEA_Model.Models.Outbound;
|
using static WIDESEA_OutboundService.OutboundBatchPickingService;
|
|
namespace WIDESEA_OutboundService
|
{
|
public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundBatchPickingService
|
{
|
|
|
private readonly IUnitOfWorkManage _unitOfWorkManage;
|
public IRepository<Dt_PickingRecord> Repository => BaseDal;
|
|
private readonly IStockInfoService _stockInfoService;
|
private readonly IStockService _stockService;
|
private readonly IOutStockLockInfoService _outStockLockInfoService;
|
private readonly IStockInfoDetailService _stockInfoDetailService;
|
private readonly ILocationInfoService _locationInfoService;
|
private readonly IOutboundOrderDetailService _outboundOrderDetailService;
|
private readonly IOutboundOrderService _outboundOrderService;
|
private readonly ISplitPackageService _splitPackageService;
|
private readonly IRepository<Dt_Task> _taskRepository;
|
private readonly IESSApiService _eSSApiService;
|
private readonly IInvokeMESService _invokeMESService;
|
private readonly IDailySequenceService _dailySequenceService;
|
private readonly IAllocateService _allocateService;
|
private readonly IRepository<Dt_OutboundBatch> _outboundBatchRepository;
|
private readonly ILogger<OutboundPickingService> _logger;
|
|
private Dictionary<string, string> stations = new Dictionary<string, string>
|
{
|
{"2-1","2-9" },
|
{"3-1","3-9" },
|
|
};
|
|
private Dictionary<string, string> movestations = new Dictionary<string, string>
|
{
|
{"2-1","2-5" },
|
{"3-1","3-5" },
|
|
};
|
|
public OutboundBatchPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService,
|
IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService,
|
IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService,
|
IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_OutboundBatch> outboundBatchRepository) : base(BaseDal)
|
{
|
_unitOfWorkManage = unitOfWorkManage;
|
_stockInfoService = stockInfoService;
|
_stockService = stockService;
|
_outStockLockInfoService = outStockLockInfoService;
|
_stockInfoDetailService = stockInfoDetailService;
|
_locationInfoService = locationInfoService;
|
_outboundOrderDetailService = outboundOrderDetailService;
|
_splitPackageService = splitPackageService;
|
_outboundOrderService = outboundOrderService;
|
_taskRepository = taskRepository;
|
_eSSApiService = eSSApiService;
|
_logger = logger;
|
_invokeMESService = invokeMESService;
|
_dailySequenceService = dailySequenceService;
|
_allocateService = allocateService;
|
_outboundBatchRepository = outboundBatchRepository;
|
}
|
|
// <summary>
|
/// 获取托盘的锁定信息
|
/// </summary>
|
public async Task<List<PalletLockInfoDto>> GetPalletLockInfos(string orderNo, string palletCode)
|
{
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.Select(x => new
|
{
|
x.Id,
|
x.OrderNo,
|
x.BatchNo,
|
x.MaterielCode,
|
x.CurrentBarcode,
|
x.AssignQuantity,
|
x.PickedQty,
|
x.Status,
|
x.LocationCode,
|
x.PalletCode
|
}).ToListAsync();
|
|
var lockInfoDtos = lockInfos.Select(x => new PalletLockInfoDto
|
{
|
Id = x.Id,
|
OrderNo = x.OrderNo,
|
BatchNo = x.BatchNo,
|
MaterielCode = x.MaterielCode,
|
CurrentBarcode = x.CurrentBarcode,
|
AssignQuantity = x.AssignQuantity,
|
PickedQty = x.PickedQty,
|
Status = x.Status,
|
LocationCode = x.LocationCode,
|
PalletCode = x.PalletCode,
|
CanSplit = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.AssignQuantity - x.PickedQty > 0),
|
CanPick = (x.Status == (int)OutLockStockStatusEnum.出库中 && x.PickedQty < x.AssignQuantity)
|
}).ToList();
|
|
return lockInfoDtos;
|
}
|
#region 查询方法
|
|
/// <summary>
|
/// 获取托盘的已拣选列表
|
/// </summary>
|
public async Task<List<PalletPickedInfoDto>> GetPalletPickedList(string orderNo, string palletCode)
|
{
|
var pickedList = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
!x.IsCancelled)
|
.Select(x => new PalletPickedInfoDto
|
{
|
Id = x.Id,
|
OrderNo = x.OrderNo,
|
OrderDetailId = x.OrderDetailId,
|
PalletCode = x.PalletCode,
|
Barcode = x.Barcode,
|
MaterielCode = x.MaterielCode,
|
PickedQty = x.PickQuantity,
|
PickTime = x.PickTime,
|
Operator = x.Operator,
|
LocationCode = x.LocationCode
|
})
|
.ToListAsync();
|
|
return pickedList;
|
}
|
|
/// <summary>
|
/// 获取托盘状态
|
/// </summary>
|
public async Task<PalletStatusDto> GetPalletStatus(string orderNo, string palletCode)
|
{
|
// 获取托盘的锁定信息
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
if (!lockInfos.Any())
|
{
|
return new PalletStatusDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Status = (int)PalletStatusEnum.无任务,
|
StatusText = "无任务",
|
TotalItems = 0,
|
CompletedItems = 0,
|
PendingItems = 0
|
};
|
}
|
|
var totalItems = lockInfos.Count;
|
var completedItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.拣选完成);
|
var pendingItems = lockInfos.Count(x => x.Status == (int)OutLockStockStatusEnum.出库中);
|
|
var status = PalletStatusEnum.拣选中;
|
if (pendingItems == 0 && completedItems > 0)
|
{
|
status = PalletStatusEnum.已完成;
|
}
|
else if (pendingItems > 0 && completedItems == 0)
|
{
|
status = PalletStatusEnum.未开始;
|
}
|
else if (pendingItems > 0 && completedItems > 0)
|
{
|
status = PalletStatusEnum.拣选中;
|
}
|
|
return new PalletStatusDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Status = (int)status,
|
StatusText = GetPalletStatusText(status),
|
TotalItems = totalItems,
|
CompletedItems = completedItems,
|
PendingItems = pendingItems
|
};
|
}
|
|
/// <summary>
|
/// 获取拆包信息
|
/// </summary>
|
public async Task<SplitPackageInfoDto> GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
|
{
|
// 查找锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.CurrentBarcode == barcode
|
//&& x.Status == (int)OutLockStockStatusEnum.出库中
|
)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
throw new Exception("未找到有效的锁定信息");
|
|
// 计算剩余可拆数量
|
var remainQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
return new SplitPackageInfoDto
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
Barcode = barcode,
|
MaterielCode = lockInfo.MaterielCode,
|
RemainQuantity = remainQuantity,
|
AssignQuantity = lockInfo.AssignQuantity,
|
PickedQty = lockInfo.PickedQty
|
};
|
}
|
|
#endregion
|
|
#region 取走空箱逻辑
|
|
|
|
/// <summary>
|
/// 取走空箱 - 清理已完成拣选的托盘数据
|
/// </summary>
|
public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 验证托盘是否可以取走(必须全部完成拣选)
|
var validationResult = await ValidateEmptyPalletRemoval(orderNo, palletCode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var completedLocks = validationResult.Data;
|
|
// 清理锁定记录(标记为已完成)
|
await CleanupCompletedLocks(completedLocks);
|
|
// 更新相关订单状态
|
await UpdateOrderStatusAfterPalletRemoval(orderNo);
|
|
// 记录操作历史
|
await RecordEmptyPalletRemoval(orderNo, palletCode, completedLocks);
|
|
_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}");
|
}
|
}
|
|
/// <summary>
|
/// 验证空箱取走条件
|
/// </summary>
|
private async Task<ValidationResult<List<Dt_OutStockLockInfo>>> ValidateEmptyPalletRemoval(string orderNo, string palletCode)
|
{
|
// 获取托盘的所有锁定记录
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
if (!lockInfos.Any())
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有锁定记录");
|
|
// 检查是否有未完成的锁定记录
|
var unfinishedLocks = lockInfos.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中).ToList();
|
|
if (unfinishedLocks.Any())
|
{
|
var unfinishedCount = unfinishedLocks.Count;
|
var unfinishedQty = unfinishedLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error(
|
$"托盘还有{unfinishedCount}条未完成记录,剩余数量{unfinishedQty},不能取走空箱");
|
}
|
|
// 获取已完成的锁定记录
|
var completedLocks = lockInfos.Where(x =>
|
x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
|
|
if (!completedLocks.Any())
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Error("该托盘没有已完成拣选的记录");
|
|
return ValidationResult<List<Dt_OutStockLockInfo>>.Success(completedLocks);
|
}
|
|
/// <summary>
|
/// 清理已完成的锁定记录
|
/// </summary>
|
private async Task CleanupCompletedLocks(List<Dt_OutStockLockInfo> completedLocks)
|
{
|
foreach (var lockInfo in completedLocks)
|
{
|
// 标记锁定记录为已取走
|
lockInfo.Status = (int)OutLockStockStatusEnum.已取走;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 清理对应的库存记录状态
|
await CleanupStockInfo(lockInfo);
|
}
|
}
|
|
/// <summary>
|
/// 清理库存信息
|
/// </summary>
|
private async Task CleanupStockInfo(Dt_OutStockLockInfo lockInfo)
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail != null && stockDetail.Status == (int)StockStatusEmun.出库完成)
|
{
|
stockDetail.Status = (int)StockStatusEmun.已清理;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
}
|
|
|
/// <summary>
|
/// 更新订单状态
|
/// </summary>
|
private async Task UpdateOrderStatusAfterPalletRemoval(string orderNo)
|
{
|
// 检查订单是否所有托盘都已完成
|
var allLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo)
|
.ToListAsync();
|
|
var unfinishedPallets = allLocks
|
.GroupBy(x => x.PalletCode)
|
.Where(g => g.Any(x => x.Status == (int)OutLockStockStatusEnum.出库中 ||
|
x.Status == (int)OutLockStockStatusEnum.回库中))
|
.ToList();
|
|
// 如果没有未完成的托盘,更新订单状态为出库完成
|
if (!unfinishedPallets.Any())
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = (int)OutOrderStatusEnum.出库完成,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
}
|
|
/// <summary>
|
/// 记录空箱取走历史
|
/// </summary>
|
private async Task RecordEmptyPalletRemoval(string orderNo, string palletCode, List<Dt_OutStockLockInfo> completedLocks)
|
{
|
var removalRecord = new Dt_EmptyPalletRemoval
|
{
|
OrderNo = orderNo,
|
PalletCode = palletCode,
|
RemovalTime = DateTime.Now,
|
Operator = App.User.UserName,
|
CompletedItemsCount = completedLocks.Count,
|
TotalPickedQuantity = completedLocks.Sum(x => x.PickedQty)
|
};
|
|
await Db.Insertable(removalRecord).ExecuteCommandAsync();
|
}
|
|
#endregion
|
|
|
#region 分批分拣
|
|
/// <summary>
|
/// 分批分拣确认
|
/// </summary>
|
public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 验证分拣请求
|
var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data;
|
|
// 使用锁定信息的分配数量作为实际分拣数量
|
var actualPickedQty = lockInfo.AssignQuantity;
|
|
// 2. 执行分拣逻辑
|
var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty);
|
|
// 3. 更新批次和订单数据
|
await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo);
|
|
// 4. 记录拣选历史
|
await RecordPickingHistory(pickingResult, orderNo, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("分拣成功", new
|
{
|
PickedQuantity = actualPickedQty,
|
Barcode = barcode,
|
MaterialCode = lockInfo.MaterielCode
|
});
|
}
|
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>
|
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 ExecuteSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK("手动拆包成功", new
|
{
|
NewBarcode = splitResult.NewBarcode,
|
OriginalBarcode = originalBarcode,
|
SplitQuantity = splitQuantity
|
});
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"手动拆包失败 - OrderNo: {orderNo}, Barcode: {originalBarcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"手动拆包失败:{ex.Message}");
|
}
|
}
|
|
#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)
|
{
|
// 1. 恢复原锁定信息
|
// 注意:这里需要累加,而不是简单的赋值,因为可能有多次拆包
|
originalLockInfo.AssignQuantity += splitRecord.SplitQty;
|
originalLockInfo.OrderQuantity += splitRecord.SplitQty;
|
|
// 如果原锁定信息的状态是拣选完成,需要重新设置为出库中
|
if (originalLockInfo.Status == (int)OutLockStockStatusEnum.拣选完成)
|
{
|
originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
|
await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
|
|
// 恢复原库存明细
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
|
|
originalStock.StockQuantity += splitRecord.SplitQty;
|
|
// 如果原库存状态是出库完成,需要重新设置为出库锁定
|
if (originalStock.Status == (int)StockStatusEmun.出库完成)
|
{
|
originalStock.Status = (int)StockStatusEmun.出库锁定;
|
}
|
|
await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
|
|
// 删除新锁定信息
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == newLockInfo.Id)
|
.ExecuteCommandAsync();
|
|
// 删除新库存明细
|
await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == newLockInfo.CurrentBarcode)
|
.ExecuteCommandAsync();
|
|
//标记拆包记录为已撤销
|
splitRecord.IsReverted = true;
|
splitRecord.RevertTime = DateTime.Now;
|
splitRecord.RevertOperator = App.User.UserName;
|
await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
|
|
// 检查并更新批次和订单状态
|
await CheckAndUpdateBatchStatus(originalLockInfo.BatchNo);
|
await CheckAndUpdateOrderStatus(originalLockInfo.OrderNo);
|
}
|
|
/// <summary>
|
/// 验证取消拆包请求
|
/// </summary>
|
private async Task<ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateCancelSplitRequest(
|
string orderNo, string palletCode, string 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("未找到拆包记录");
|
|
// 查找新锁定信息
|
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("未找到新锁定信息");
|
|
// 检查新条码是否已被分拣
|
var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == newBarcode && !x.IsCancelled)
|
.FirstAsync();
|
|
if (pickingRecord != null)
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("该条码已被分拣,无法取消拆包");
|
|
// 检查新条码是否被再次拆包
|
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));
|
return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error(
|
$"该条码已被再次拆包,生成的新条码:{childBarcodes},请先取消后续拆包");
|
}
|
|
var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == 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("未找到拆包记录");
|
|
// 2. 按拆包顺序倒序取消(从最新的开始取消)
|
var reversedChain = splitChain.OrderByDescending(x => x.SplitTime).ToList();
|
|
foreach (var splitRecord in reversedChain)
|
{
|
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<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)
|
{
|
// 查找相关数据
|
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);
|
}
|
#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
|
}).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 分批回库
|
|
/// <summary>
|
/// 分批回库 - 释放未拣选的库存
|
/// </summary>
|
public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 查找托盘上未完成的锁定记录(只处理出库中的记录)
|
var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Status == (int)OutLockStockStatusEnum.出库中)
|
.ToListAsync();
|
|
if (!unfinishedLocks.Any())
|
return WebResponseContent.Instance.Error("该托盘没有未完成的锁定记录");
|
|
// 按出库批次分组处理
|
var batchGroups = unfinishedLocks.GroupBy(x => x.OutboundBatchNo); // 使用 OutboundBatchNo
|
|
foreach (var batchGroup in batchGroups)
|
{
|
var outboundBatchNo = batchGroup.Key;
|
var batchLocks = batchGroup.ToList();
|
|
// 释放库存和锁定记录
|
foreach (var lockInfo in batchLocks)
|
{
|
await ReleaseLockAndStock(lockInfo);
|
}
|
|
// 更新批次状态
|
await UpdateBatchStatusForReturn(outboundBatchNo, batchLocks);
|
|
// 更新订单明细的已分配数量
|
await UpdateOrderDetailAfterReturn(batchLocks);
|
}
|
|
_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 验证方法
|
private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest(
|
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)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("未找到有效的锁定信息");
|
|
// 检查是否已经分拣完成
|
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);
|
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == barcode && x.StockId == lockInfo.StockId);
|
|
// 验证库存数量
|
if (stockDetail.StockQuantity < lockInfo.AssignQuantity)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error(
|
$"库存数量不足,需要:{lockInfo.AssignQuantity},实际:{stockDetail.StockQuantity}");
|
|
// 使用 OutboundBatchNo 查找批次
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == lockInfo.OutboundBatchNo); // 修正为 OutboundBatchNo
|
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Success((lockInfo, orderDetail, stockDetail, batch));
|
}
|
private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest(
|
string orderNo, string palletCode, string originalBarcode, decimal 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)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("未找到有效的锁定信息");
|
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId);
|
|
if (stockDetail.StockQuantity < splitQuantity)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于库存数量");
|
|
if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("拆包数量不能大于未拣选数量");
|
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail));
|
}
|
|
#endregion
|
|
#region 核心逻辑方法
|
|
private async Task<PickingResult> ExecutePickingLogic(
|
Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail,
|
Dt_StockInfoDetail stockDetail, decimal actualPickedQty)
|
{
|
// 更新锁定信息
|
lockInfo.PickedQty += actualPickedQty;
|
if (lockInfo.PickedQty >= lockInfo.AssignQuantity)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
|
}
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 更新库存
|
stockDetail.StockQuantity -= actualPickedQty;
|
stockDetail.OutboundQuantity += actualPickedQty;
|
|
if (stockDetail.StockQuantity <= 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库完成;
|
}
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
return new PickingResult
|
{
|
FinalLockInfo = lockInfo,
|
ActualPickedQty = actualPickedQty
|
};
|
}
|
|
private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord)
|
{
|
// 恢复锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == pickingRecord.OutStockLockId);
|
|
lockInfo.PickedQty -= pickingRecord.PickQuantity;
|
|
// 根据拣选数量判断状态
|
if (lockInfo.PickedQty <= 0)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
else if (lockInfo.PickedQty < lockInfo.AssignQuantity)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 恢复库存
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == pickingRecord.Barcode);
|
|
stockDetail.StockQuantity += pickingRecord.PickQuantity;
|
stockDetail.OutboundQuantity -= pickingRecord.PickQuantity;
|
|
// 恢复库存状态
|
if (stockDetail.StockQuantity > 0)
|
{
|
stockDetail.Status = (int)StockStatusEmun.出库锁定;
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
return new RevertPickingResult
|
{
|
LockInfo = lockInfo,
|
StockDetail = stockDetail
|
};
|
}
|
private async Task<SplitResultDto> ExecuteSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal splitQuantity, string palletCode)
|
{
|
// 生成新条码
|
string newBarcode = await GenerateNewBarcode();
|
|
// 创建新库存明细
|
var newStockDetail = new Dt_StockInfoDetail
|
{
|
StockId = stockDetail.StockId,
|
MaterielCode = stockDetail.MaterielCode,
|
OrderNo = stockDetail.OrderNo,
|
BatchNo = stockDetail.BatchNo, // 物料批次
|
StockQuantity = splitQuantity,
|
OutboundQuantity = 0,
|
Barcode = newBarcode,
|
Status = (int)StockStatusEmun.出库锁定,
|
SupplyCode = stockDetail.SupplyCode,
|
Unit = stockDetail.Unit,
|
BarcodeQty = stockDetail.BarcodeQty,
|
BarcodeUnit = stockDetail.BarcodeUnit,
|
BusinessType = stockDetail.BusinessType,
|
InboundOrderRowNo = stockDetail.InboundOrderRowNo,
|
};
|
await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
|
|
// 更新原库存明细
|
stockDetail.StockQuantity -= splitQuantity;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 创建新锁定信息 - 使用正确的 OutboundBatchNo
|
var newLockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = lockInfo.OrderNo,
|
OrderDetailId = lockInfo.OrderDetailId,
|
OutboundBatchNo = lockInfo.OutboundBatchNo, // 使用 OutboundBatchNo
|
MaterielCode = lockInfo.MaterielCode,
|
MaterielName = lockInfo.MaterielName,
|
StockId = lockInfo.StockId,
|
OrderQuantity = splitQuantity,
|
AssignQuantity = splitQuantity,
|
PickedQty = 0,
|
LocationCode = lockInfo.LocationCode,
|
PalletCode = lockInfo.PalletCode,
|
TaskNum = lockInfo.TaskNum,
|
Status = (int)OutLockStockStatusEnum.出库中,
|
Unit = lockInfo.Unit,
|
SupplyCode = lockInfo.SupplyCode,
|
OrderType = lockInfo.OrderType,
|
CurrentBarcode = newBarcode,
|
IsSplitted = 1,
|
ParentLockId = lockInfo.Id,
|
Operator = App.User.UserName,
|
FactoryArea = lockInfo.FactoryArea,
|
lineNo = lockInfo.lineNo,
|
WarehouseCode = lockInfo.WarehouseCode,
|
BarcodeQty = lockInfo.BarcodeQty,
|
BarcodeUnit = lockInfo.BarcodeUnit,
|
};
|
|
await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync();
|
|
// 更新原锁定信息
|
lockInfo.AssignQuantity -= splitQuantity;
|
lockInfo.OrderQuantity -= splitQuantity;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 记录拆包历史
|
await RecordSplitHistory(lockInfo, stockDetail, splitQuantity, newBarcode);
|
|
return new SplitResultDto { NewBarcode = newBarcode };
|
}
|
private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord, Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail)
|
{
|
// 恢复原库存
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId);
|
|
originalStock.StockQuantity += splitRecord.SplitQty;
|
await _stockInfoDetailService.Db.Updateable(originalStock).ExecuteCommandAsync();
|
|
// 恢复原锁定信息
|
var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId);
|
|
originalLockInfo.AssignQuantity += splitRecord.SplitQty;
|
originalLockInfo.OrderQuantity += splitRecord.SplitQty;
|
await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
|
|
// 删除新库存明细
|
await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == newLockInfo.CurrentBarcode)
|
.ExecuteCommandAsync();
|
|
// 删除新锁定信息
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == newLockInfo.Id)
|
.ExecuteCommandAsync();
|
|
// 标记拆包记录为已撤销
|
splitRecord.IsReverted = true;
|
splitRecord.RevertTime = DateTime.Now;
|
splitRecord.RevertOperator = App.User.UserName;
|
await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync();
|
}
|
|
#endregion
|
|
#region 数据更新方法
|
|
private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo)
|
{
|
// 更新批次完成数量
|
batch.CompletedQuantity += pickedQty;
|
if (batch.CompletedQuantity >= batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.已完成;
|
}
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
|
// 更新订单明细
|
orderDetail.OverOutQuantity += pickedQty;
|
orderDetail.AllocatedQuantity -= pickedQty;
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
// 检查订单状态
|
await CheckAndUpdateOrderStatus(orderNo);
|
}
|
|
private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord, RevertPickingResult revertResult)
|
{
|
// 恢复批次完成数量
|
var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>()
|
.FirstAsync(x => x.BatchNo == revertResult.LockInfo.OutboundBatchNo); // 使用 OutboundBatchNo
|
|
if (batch != null)
|
{
|
batch.CompletedQuantity -= pickingRecord.PickQuantity;
|
|
// 重新计算批次状态
|
if (batch.CompletedQuantity <= 0)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.分配中;
|
}
|
else if (batch.CompletedQuantity < batch.BatchQuantity)
|
{
|
batch.BatchStatus = (int)BatchStatusEnum.执行中;
|
}
|
|
await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync();
|
}
|
|
// 恢复订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
|
|
orderDetail.OverOutQuantity -= pickingRecord.PickQuantity;
|
orderDetail.AllocatedQuantity += pickingRecord.PickQuantity;
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
|
// 重新检查订单状态
|
await CheckAndUpdateOrderStatus(pickingRecord.OrderNo);
|
}
|
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;
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
}
|
}
|
}
|
#endregion
|
|
#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)
|
{
|
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.已拆包
|
};
|
|
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类
|
|
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
|
}
|
|
|
// 支持类
|
public class SplitResultDto
|
{
|
public string NewBarcode { get; set; }
|
}
|
}
|