using Dm.filter;
|
using MailKit.Search;
|
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.Extensions.Logging;
|
using SqlSugar;
|
using System;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using System.Text.Json;
|
using System.Threading.Tasks;
|
using WIDESEA_Common.CommonEnum;
|
using WIDESEA_Common.LocationEnum;
|
using WIDESEA_Common.OrderEnum;
|
using WIDESEA_Common.StockEnum;
|
using WIDESEA_Common.TaskEnum;
|
using WIDESEA_Core;
|
using WIDESEA_Core.BaseRepository;
|
using WIDESEA_Core.BaseServices;
|
using WIDESEA_Core.Enums;
|
using WIDESEA_Core.Helper;
|
using WIDESEA_DTO.Allocate;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.Inbound;
|
using WIDESEA_DTO.Outbound;
|
using WIDESEA_IAllocateService;
|
using WIDESEA_IBasicService;
|
using WIDESEA_IInboundService;
|
using WIDESEA_IOutboundService;
|
using WIDESEA_IStockService;
|
using WIDESEA_Model.Models;
|
|
namespace WIDESEA_OutboundService
|
{
|
/// <summary>
|
///
|
/// </summary>
|
public class OutboundPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundPickingService
|
{
|
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_InboundOrder> _inboundOrderRepository;
|
private readonly IInboundOrderDetailService _inboundOrderDetailService;
|
|
|
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 OutboundPickingService(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_InboundOrder> inboundOrderRepository,IInboundOrderDetailService inboundOrderDetailService) : 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;
|
_inboundOrderRepository = inboundOrderRepository;
|
_inboundOrderDetailService = inboundOrderDetailService;
|
}
|
|
|
#region 查询方法
|
// 获取未拣选列表
|
public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
|
{
|
var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Status == 1)
|
.ToListAsync();
|
return list.Where(x => x.RemainQuantity > 0).ToList();
|
}
|
|
// 获取已拣选列表
|
public async Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode)
|
{
|
var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode == palletCode &&
|
x.Status == 6)
|
.ToListAsync();
|
return list;
|
}
|
// 获取拣选汇总
|
public async Task<object> GetPickingSummary(ConfirmPickingDto dto)
|
{
|
var picked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
|
.WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
|
.Where(x => x.Status == 6)
|
.GroupBy(x => new { x.PalletCode, x.MaterielCode })
|
.Select(x => new SummaryPickingDto
|
{
|
PalletCode = x.PalletCode,
|
MaterielCode = x.MaterielCode,
|
pickedCount = SqlFunc.AggregateCount(x.Id)
|
}).FirstAsync();
|
if (picked == null)
|
{
|
picked = new SummaryPickingDto { pickedCount = 0 };
|
}
|
|
var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.OrderNo == dto.OrderNo)
|
.WhereIF(!string.IsNullOrEmpty(dto.PalletCode), x => x.PalletCode == dto.PalletCode)
|
.Where(x => x.Status == 1)
|
.GroupBy(x => new { x.PalletCode, x.MaterielCode })
|
.Select(x => new SummaryPickingDto
|
{
|
PalletCode = x.PalletCode,
|
MaterielCode = x.MaterielCode,
|
UnpickedCount = SqlFunc.AggregateCount(x.Id),
|
UnpickedQuantity = SqlFunc.AggregateSum(x.AssignQuantity) - SqlFunc.AggregateSum(x.PickedQty),
|
|
}).FirstAsync();
|
if (summary == null)
|
{
|
summary = new SummaryPickingDto { pickedCount = 0 };
|
}
|
summary.pickedCount = picked.pickedCount;
|
|
return summary;
|
}
|
|
#endregion
|
|
#region 核心业务流程
|
/// <summary>
|
/// 拣选
|
/// </summary>
|
/// <param name="orderNo"></param>
|
/// <param name="palletCode"></param>
|
/// <param name="barcode"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (lockInfo, orderDetail, stockDetail) = validationResult.Data;
|
|
// 计算实际拣选数量
|
var quantityResult = await CalculateActualPickingQuantity(lockInfo, orderDetail, stockDetail);
|
if (!quantityResult.IsValid)
|
return WebResponseContent.Instance.Error(quantityResult.ErrorMessage);
|
|
var (actualQty, adjustedReason) = quantityResult.Data;
|
|
var overPickingValidation = await ValidateOverPicking(orderDetail.Id, actualQty);
|
if (!overPickingValidation.IsValid)
|
{
|
return WebResponseContent.Instance.Error(overPickingValidation.ErrorMessage);
|
}
|
|
// 执行分拣逻辑(只处理库存和锁定,不处理订单)
|
var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, orderNo, palletCode, barcode, actualQty);
|
|
// 统一更新订单数据(所有分支都在这里更新)
|
await UpdateOrderRelatedData(orderDetail.Id, pickingResult.ActualPickedQty, orderNo);
|
|
// 记录操作历史
|
await RecordPickingHistory(pickingResult, orderNo, palletCode);
|
|
_unitOfWorkManage.CommitTran();
|
|
return CreatePickingResponse(pickingResult, adjustedReason);
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"ConfirmPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 取消拣选
|
/// </summary>
|
/// <param name="orderNo"></param>
|
/// <param name="palletCode"></param>
|
/// <param name="barcode"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
if (await IsPalletReturned(palletCode))
|
{
|
return WebResponseContent.Instance.Error($"托盘{palletCode}已经回库,不能取消分拣");
|
}
|
_unitOfWorkManage.BeginTran();
|
|
// 前置验证
|
var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode);
|
if (!validationResult.IsValid)
|
return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
|
|
var (pickingRecord, lockInfo, orderDetail) = validationResult.Data;
|
|
//执行取消逻辑
|
await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo);
|
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK($"取消分拣成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 回库
|
/// </summary>
|
/// <param name="orderNo"></param>
|
/// <param name="palletCode"></param>
|
/// <param name="reason"></param>
|
/// <returns></returns>
|
public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
|
return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
|
|
// 获取库存和任务信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>().FirstAsync(x => x.PalletCode == palletCode);
|
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
|
var task = await GetCurrentTask(orderNo, palletCode);
|
if (task == null)
|
return WebResponseContent.Instance.Error("未找到对应的任务信息");
|
|
//分析需要回库的货物
|
//var returnAnalysis = await AnalyzeReturnItems(orderNo, palletCode, stockInfo.Id);
|
//if (!returnAnalysis.HasItemsToReturn)
|
// return await HandleNoReturnItems(orderNo, palletCode, task);
|
|
var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
|
if (!statusAnalysis.HasItemsToReturn)
|
return await HandleNoReturnItems(orderNo, palletCode, task, stockInfo.Id);
|
|
// 4. 检查是否有进行中的任务
|
if (statusAnalysis.HasActiveTasks)
|
{
|
return WebResponseContent.Instance.Error($"托盘 {palletCode} 有进行中的任务,不能执行回库操作");
|
}
|
|
//执行回库操作
|
await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis);
|
|
await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis);
|
|
_unitOfWorkManage.CommitTran();
|
|
|
// 创建回库任务
|
await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, TaskTypeEnum.InPick, task.PalletType);
|
|
// 更新订单状态(不触发MES回传)
|
await UpdateOrderStatusForReturn(orderNo);
|
|
return WebResponseContent.Instance.OK($"回库操作成功");
|
//return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{statusAnalysis.TotalReturnQty}");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 空托盘取走接口(带订单号)
|
/// 验证托盘是否真的为空,清理数据,更新订单状态,创建取托盘任务
|
/// </summary>
|
public async Task<WebResponseContent> RemoveEmptyPallet(string orderNo, string palletCode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode))
|
return WebResponseContent.Instance.Error("订单号和托盘码不能为空");
|
|
// 检查订单是否存在
|
var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.Where(x => x.OrderNo == orderNo)
|
.FirstAsync();
|
|
if (order == null)
|
return WebResponseContent.Instance.Error($"未找到订单 {orderNo}");
|
|
//检查托盘是否存在且属于该订单
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error($"未找到托盘 {palletCode} 对应的库存信息");
|
|
var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
|
|
if (!statusAnalysis.CanRemove)
|
{
|
if (!statusAnalysis.IsEmptyPallet)
|
{
|
return WebResponseContent.Instance.Error($"托盘 {palletCode} 上还有货物,不能取走");
|
}
|
if (statusAnalysis.HasActiveTasks)
|
{
|
return WebResponseContent.Instance.Error($"托盘 {palletCode} 还有进行中的任务,不能取走");
|
}
|
}
|
// 清理零库存数据
|
await CleanupZeroStockData(stockInfo.Id);
|
|
// 删除或取消相关任务
|
await HandleTaskCleanup(orderNo, palletCode);
|
|
// 更新订单相关数据
|
await UpdateOrderData(orderNo, palletCode);
|
|
|
_unitOfWorkManage.CommitTran();
|
|
_logger.LogInformation($"空托盘取走操作成功 - 订单: {orderNo}, 托盘: {palletCode}, 操作人: {App.User.UserName}");
|
|
return WebResponseContent.Instance.OK("空托盘取走操作成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"RemoveEmptyPallet失败 - 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)>> ValidatePickingRequest(string orderNo, string palletCode, string barcode)
|
{
|
// 1. 基础参数验证
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("订单号、托盘码和条码不能为空");
|
|
// 2. 查找有效的锁定信息
|
var lockInfo = await FindValidLockInfo(orderNo, palletCode, barcode);
|
if (lockInfo == null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"未找到有效的锁定信息");
|
|
// 3. 检查订单状态
|
var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.Where(x => x.OrderNo == orderNo)
|
.FirstAsync();
|
|
if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单{orderNo}已完成,不能继续分拣");
|
|
// 4. 获取订单明细
|
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)>.Error($"未找到订单明细");
|
|
// 5. 检查订单明细数量
|
if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"订单明细需求数量已满足");
|
|
// 6. 获取库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId &&
|
x.Status != StockStatusEmun.入库确认.ObjToInt())
|
.FirstAsync();
|
|
if (stockDetail == null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"无效的条码或物料编码");
|
|
// 7. 检查库存状态和数量
|
if (stockDetail.StockQuantity <= 0)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}库存不足");
|
|
if (stockDetail.Status != StockStatusEmun.出库锁定.ObjToInt())
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}状态不正确,无法分拣");
|
|
// 8. 检查是否重复分拣
|
var existingPicking = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.Barcode == barcode && x.OrderNo == orderNo && x.PalletCode == palletCode && x.OutStockLockId == lockInfo.Id)
|
.FirstAsync();
|
|
if (existingPicking != null)
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error($"条码{barcode}已经分拣过");
|
|
return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail));
|
}
|
|
private async Task<Dt_OutStockLockInfo> FindValidLockInfo(string orderNo, string palletCode, string barcode)
|
{
|
// 优先查找精确匹配的记录
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo &&
|
it.Status == (int)OutLockStockStatusEnum.出库中 &&
|
it.PalletCode == palletCode &&
|
it.CurrentBarcode == barcode &&
|
it.AssignQuantity > it.PickedQty).FirstAsync();
|
|
if (lockInfo == null)
|
{
|
// 查找同一订单下的记录
|
lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo && it.CurrentBarcode == barcode && it.Status == (int)OutLockStockStatusEnum.出库中 && it.AssignQuantity > it.PickedQty).FirstAsync();
|
|
if (lockInfo == null)
|
{
|
// 检查是否已经完成分拣
|
var completedLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.CurrentBarcode == barcode &&
|
(it.Status == (int)OutLockStockStatusEnum.拣选完成 ||
|
it.PickedQty >= it.AssignQuantity)).FirstAsync();
|
|
if (completedLockInfo != null)
|
throw new Exception($"条码{barcode}已经完成分拣,不能重复分拣");
|
else
|
return null;
|
}
|
}
|
|
return lockInfo;
|
}
|
|
private async Task<ValidationResult<(decimal, string)>> CalculateActualPickingQuantity(
|
Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail)
|
{
|
decimal plannedQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
|
decimal stockQuantity = stockDetail.StockQuantity;
|
|
if (plannedQty <= 0)
|
{
|
return ValidationResult<(decimal, string)>.Error($"计划拣选数量必须大于0,当前: {plannedQty}");
|
}
|
|
if (remainingOrderQty <= 0)
|
{
|
return ValidationResult<(decimal, string)>.Error($"订单剩余需求数量必须大于0,当前: {remainingOrderQty}");
|
}
|
|
if (stockQuantity <= 0)
|
{
|
return ValidationResult<(decimal, string)>.Error($"库存数量必须大于0,当前: {stockQuantity}");
|
}
|
// 三重检查:取最小值
|
decimal actualQty = plannedQty;
|
string adjustedReason = null;
|
|
if (plannedQty > remainingOrderQty)
|
{
|
actualQty = remainingOrderQty;
|
adjustedReason = $"订单数量限制:从{plannedQty}调整为{actualQty}";
|
}
|
if (actualQty > stockQuantity)
|
{
|
actualQty = stockQuantity;
|
adjustedReason = adjustedReason != null
|
? $"{adjustedReason},库存数量限制:进一步调整为{actualQty}"
|
: $"库存数量限制:从{plannedQty}调整为{actualQty}";
|
}
|
if (actualQty <= 0)
|
{
|
return ValidationResult<(decimal, string)>.Error($"无法分拣:计算后的实际数量为{actualQty}");
|
}
|
decimal projectedOverOut = orderDetail.OverOutQuantity + actualQty;
|
if (projectedOverOut > orderDetail.NeedOutQuantity)
|
{
|
// 如果会超拣,调整为刚好满足需求的数量
|
actualQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
|
adjustedReason = adjustedReason != null
|
? $"{adjustedReason},防超拣限制:最终调整为{actualQty}"
|
: $"防超拣限制:从{plannedQty}调整为{actualQty}";
|
}
|
|
if (adjustedReason != null)
|
{
|
_logger.LogWarning($"分拣数量调整:{adjustedReason},订单{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity},库存{stockQuantity}");
|
}
|
|
return ValidationResult<(decimal, string)>.Success((actualQty, adjustedReason));
|
}
|
|
/// <summary>
|
/// 专门验证是否会发生超拣
|
/// </summary>
|
private async Task<ValidationResult<bool>> ValidateOverPicking(int orderDetailId, decimal pickingQty)
|
{
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail == null)
|
return ValidationResult<bool>.Error("未找到订单明细");
|
|
decimal projectedOverOut = orderDetail.OverOutQuantity + pickingQty;
|
|
if (projectedOverOut > orderDetail.NeedOutQuantity)
|
{
|
return ValidationResult<bool>.Error(
|
$"分拣后将导致超拣:当前已出库{orderDetail.OverOutQuantity},本次分拣{pickingQty},合计{projectedOverOut},超过需求{orderDetail.NeedOutQuantity}");
|
}
|
|
return ValidationResult<bool>.Success(true);
|
}
|
|
|
private async Task<PickingResult> ExecutePickingLogic(
|
Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail,
|
string orderNo, string palletCode, string barcode, decimal actualQty)
|
{
|
decimal stockQuantity = stockDetail.StockQuantity;
|
var result = new PickingResult
|
{
|
FinalLockInfo = lockInfo,
|
FinalBarcode = barcode,
|
FinalStockId = stockDetail.Id,
|
ActualPickedQty = actualQty
|
};
|
|
if (actualQty < stockQuantity)
|
{
|
await HandleSplitPacking(lockInfo, stockDetail, actualQty, stockQuantity, result);
|
// 拆包场景返回实际拣选数量
|
result.ActualPickedQty = actualQty;
|
}
|
else if (actualQty == stockQuantity)
|
{
|
await HandleFullPicking(lockInfo, stockDetail, actualQty, result);
|
// 整包拣选返回实际拣选数量
|
result.ActualPickedQty = actualQty;
|
}
|
else
|
{
|
await HandlePartialPicking(lockInfo, stockDetail, actualQty, stockQuantity, result);
|
// 部分拣选返回调整后的数量
|
result.ActualPickedQty = result.ActualPickedQty; // 已经在方法内调整
|
}
|
|
return result;
|
}
|
private async Task HandleSplitPacking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal actualQty, decimal stockQuantity, PickingResult result)
|
{
|
decimal remainingStockQty = stockQuantity - actualQty;
|
|
// 更新原条码库存
|
stockDetail.StockQuantity = remainingStockQty;
|
stockDetail.OutboundQuantity = remainingStockQty;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 生成新条码
|
string newBarcode = await GenerateNewBarcode();
|
|
// 创建新锁定信息
|
var newLockInfo = await CreateSplitLockInfo(lockInfo, actualQty, newBarcode);
|
|
// 记录拆包历史
|
await RecordSplitHistory(lockInfo, stockDetail, actualQty, remainingStockQty, newBarcode);
|
|
// 更新原锁定信息
|
lockInfo.AssignQuantity = remainingStockQty;
|
lockInfo.PickedQty = 0;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 设置结果
|
result.FinalLockInfo = newLockInfo;
|
result.FinalBarcode = newBarcode;
|
result.SplitResults.AddRange(CreateSplitResults(lockInfo, actualQty, remainingStockQty, newBarcode, stockDetail.Barcode));
|
|
|
|
_logger.LogInformation($"拆包分拣完成 - OrderDetailId: {lockInfo.OrderDetailId}, 分拣数量: {actualQty}");
|
}
|
private async Task HandleFullPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal actualQty, PickingResult result)
|
{
|
// 1. 更新库存
|
stockDetail.StockQuantity = 0;
|
stockDetail.OutboundQuantity = 0;
|
stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 2. 更新锁定信息
|
lockInfo.PickedQty += actualQty;
|
lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
}
|
|
private async Task HandlePartialPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal actualQty, decimal stockQuantity, PickingResult result)
|
{
|
decimal stockOutQty = stockQuantity;
|
decimal remainingAssignQty = actualQty - stockQuantity;
|
|
// 1. 更新库存
|
stockDetail.StockQuantity = 0;
|
stockDetail.OutboundQuantity = 0;
|
stockDetail.Status = StockStatusEmun.出库完成.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 2. 更新锁定信息
|
lockInfo.PickedQty += stockOutQty;
|
lockInfo.AssignQuantity = remainingAssignQty;
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 3. 更新拆包记录状态
|
await UpdateSplitRecordsStatus(stockDetail.Barcode);
|
|
result.ActualPickedQty = stockOutQty;
|
}
|
|
private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo)
|
{
|
// 获取最新的订单明细数据
|
var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity + pickedQty;
|
decimal newPickedQty = currentOrderDetail.PickedQty + pickedQty;
|
|
if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
|
{
|
|
_logger.LogError($"防超拣检查失败 - OrderDetailId: {orderDetailId}, 已出库: {newOverOutQuantity}, 需求: {currentOrderDetail.NeedOutQuantity}, 本次分拣: {pickedQty}");
|
|
|
decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
|
|
if (adjustedQty > 0)
|
{
|
_logger.LogWarning($"自动调整分拣数量防止超拣:从{pickedQty}调整为{adjustedQty}");
|
newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
|
newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
|
}
|
else
|
{
|
throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({currentOrderDetail.NeedOutQuantity}),且无法自动调整");
|
}
|
}
|
|
// 更新订单明细
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
PickedQty = newPickedQty,
|
OverOutQuantity = newOverOutQuantity,
|
})
|
.Where(it => it.Id == orderDetailId)
|
.ExecuteCommandAsync();
|
|
// 检查并更新订单状态
|
await CheckAndUpdateOrderStatus(orderNo);
|
}
|
|
private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode)
|
{
|
var task = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (result.FinalLockInfo.Id <= 0)
|
{
|
throw new Exception($"锁定信息ID无效: {result.FinalLockInfo.Id},无法记录拣选历史");
|
}
|
|
var pickingHistory = new Dt_PickingRecord
|
{
|
FactoryArea = result.FinalLockInfo.FactoryArea,
|
TaskNo = task?.TaskNum ?? 0,
|
LocationCode = task?.SourceAddress ?? "",
|
StockId = result.FinalStockId,
|
OrderNo = orderNo,
|
OrderDetailId = result.FinalLockInfo.OrderDetailId,
|
PalletCode = palletCode,
|
Barcode = result.FinalBarcode,
|
MaterielCode = result.FinalLockInfo.MaterielCode,
|
PickQuantity = result.ActualPickedQty,
|
PickTime = DateTime.Now,
|
Operator = App.User.UserName,
|
OutStockLockId = result.FinalLockInfo.Id
|
};
|
|
await Db.Insertable(pickingHistory).ExecuteCommandAsync();
|
}
|
|
#endregion
|
|
#region 取消分拣私有方法
|
private async Task<ValidationResult<bool>> ValidateDataConsistencyBeforeCancel(CancelPickingContext context)
|
{
|
try
|
{
|
// 验证订单明细数据
|
var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == context.OrderDetail.Id);
|
|
if (currentOrderDetail.OverOutQuantity < context.PickingRecord.PickQuantity)
|
return ValidationResult<bool>.Error($"订单明细已出库数量({currentOrderDetail.OverOutQuantity})小于取消数量({context.PickingRecord.PickQuantity})");
|
|
if (currentOrderDetail.PickedQty < context.PickingRecord.PickQuantity)
|
return ValidationResult<bool>.Error($"订单明细已拣选数量({currentOrderDetail.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
|
|
// 验证锁定信息数据
|
var currentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == context.LockInfo.Id);
|
|
if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity)
|
return ValidationResult<bool>.Error($"锁定信息已拣选数量({currentLockInfo.PickedQty})小于取消数量({context.PickingRecord.PickQuantity})");
|
|
////// 验证库存数据
|
////var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
//// .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
|
|
////if (currentStockDetail == null)
|
//// return ValidationResult<bool>.Error($"未找到对应的库存明细记录");
|
|
////if (currentStockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
//// currentStockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
|
//// return ValidationResult<bool>.Error($"条码{context.PickingRecord.Barcode}已经回库,无法取消分拣");
|
|
// 验证状态流转的合法性
|
if (!await CanCancelPicking(currentLockInfo, null))
|
return ValidationResult<bool>.Error($"当前状态不允许取消分拣");
|
|
return ValidationResult<bool>.Success(true);
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"取消分拣数据一致性验证失败: {ex.Message}");
|
return ValidationResult<bool>.Error($"数据验证失败: {ex.Message}");
|
}
|
}
|
|
private async Task<bool> CanCancelPicking(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail)
|
{
|
// 锁定信息状态检查
|
if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
|
return false;
|
|
////// 库存状态检查
|
////if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt())
|
//// return false;
|
|
// 如果是拆包记录,还需要检查父锁定信息状态
|
if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
|
{
|
var parentLock = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.FirstAsync(x => x.Id == lockInfo.ParentLockId.Value);
|
|
if (parentLock == null || parentLock.Status == (int)OutLockStockStatusEnum.回库中)
|
return false;
|
}
|
|
return true;
|
}
|
private async Task<ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>> ValidateCancelRequest(string orderNo, string palletCode, string barcode)
|
{
|
// 基础参数验证
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单号、托盘码和条码不能为空");
|
|
// 查找拣选记录
|
var pickingRecord = await Db.Queryable<Dt_PickingRecord>()
|
.Where(it => it.OrderNo == orderNo &&
|
it.PalletCode == palletCode &&
|
it.Barcode == barcode)
|
.OrderByDescending(it => it.PickTime)
|
.FirstAsync();
|
|
if (pickingRecord == null)
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的拣选记录");
|
|
if (pickingRecord.PickQuantity <= 0)
|
{
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"拣选记录数量无效: {pickingRecord.PickQuantity}");
|
}
|
// 查找锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.Id == pickingRecord.OutStockLockId)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("未找到对应的出库锁定信息");
|
|
if (lockInfo.PickedQty < pickingRecord.PickQuantity)
|
{
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error(
|
$"取消数量({pickingRecord.PickQuantity})超过锁定信息的已拣选数量({lockInfo.PickedQty})");
|
}
|
// 检查状态是否允许取消
|
if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("当前状态不允许取消分拣");
|
|
var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().FirstAsync(x => x.OrderNo == orderNo);
|
|
if (order?.OrderStatus == (int)OutOrderStatusEnum.出库完成)
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error("订单已出库完成,不允许取消分拣");
|
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
|
|
if (orderDetail == null)
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
|
|
// 检查订单明细的已拣选数量是否足够取消
|
if (orderDetail.PickedQty < pickingRecord.PickQuantity)
|
{
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已拣选数量({orderDetail.PickedQty})");
|
}
|
|
// 检查订单明细的已出库数量是否足够取消
|
if (orderDetail.OverOutQuantity < pickingRecord.PickQuantity)
|
{
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"取消数量({pickingRecord.PickQuantity})超过订单明细的已出库数量({orderDetail.OverOutQuantity})");
|
}
|
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>().FirstAsync(it => it.Barcode == barcode && it.StockId == pickingRecord.StockId);
|
|
if (stockDetail != null)
|
{
|
// 检查库存状态 - 如果状态是入库确认或入库完成,说明已经回库
|
if (stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
stockDetail.Status == StockStatusEmun.入库完成.ObjToInt())
|
{
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Error($"条码{barcode}已经回库,不能取消分拣");
|
}
|
}
|
|
return ValidationResult<(Dt_PickingRecord, Dt_OutStockLockInfo, Dt_OutboundOrderDetail)>.Success((pickingRecord, lockInfo, orderDetail));
|
}
|
/// <summary>
|
/// 检查条码是否已经回库
|
/// </summary>
|
private async Task<bool> IsBarcodeReturned(string barcode, int stockId)
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == barcode && it.StockId == stockId)
|
.FirstAsync();
|
|
if (stockDetail == null)
|
return false;
|
|
// 如果状态是入库确认或入库完成,说明已经回库
|
return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
|
}
|
|
/// <summary>
|
/// 检查锁定信息对应的条码是否已经回库
|
/// </summary>
|
private async Task<bool> IsLockInfoReturned(Dt_OutStockLockInfo lockInfo)
|
{
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
|
.FirstAsync();
|
|
if (stockDetail == null)
|
return false;
|
|
return stockDetail.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
stockDetail.Status == StockStatusEmun.入库完成.ObjToInt();
|
}
|
private async Task ExecuteCancelLogic(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord,
|
Dt_OutboundOrderDetail orderDetail, string orderNo)
|
{
|
decimal cancelQty = pickingRecord.PickQuantity;
|
|
// 数据一致性验证
|
var context = new CancelPickingContext
|
{
|
LockInfo = lockInfo,
|
PickingRecord = pickingRecord,
|
OrderDetail = orderDetail,
|
OrderNo = orderNo,
|
CancelQuantity = cancelQty
|
};
|
|
var validationResult = await ValidateDataConsistencyBeforeCancel(context);
|
if (!validationResult.IsValid)
|
throw new Exception(validationResult.ErrorMessage);
|
|
// 处理不同类型的取消
|
if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
|
{
|
await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
|
}
|
else
|
{
|
await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
|
}
|
|
// 更新订单明细
|
await UpdateOrderDetailOnCancel(pickingRecord.OrderDetailId, cancelQty);
|
|
// 删除拣选记录
|
var deleteResult = await Db.Deleteable<Dt_PickingRecord>()
|
.Where(x => x.Id == pickingRecord.Id)
|
.ExecuteCommandAsync();
|
|
if (deleteResult <= 0)
|
throw new Exception("删除拣选记录失败");
|
|
_logger.LogInformation($"删除拣选记录 - 记录ID: {pickingRecord.Id}, 条码: {pickingRecord.Barcode}");
|
|
// 重新检查订单状态
|
await UpdateOrderStatusForReturn(orderNo);
|
|
|
}
|
|
private async Task HandleSplitBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
|
{
|
// 查找父锁定信息
|
var parentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == lockInfo.ParentLockId.Value)
|
.FirstAsync();
|
|
if (parentLockInfo == null)
|
throw new Exception("未找到父锁定信息,无法取消拆包分拣");
|
|
// 检查父条码和拆包条码的状态
|
if (await IsLockInfoReturned(parentLockInfo))
|
throw new Exception($"父条码{parentLockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
|
|
if (await IsLockInfoReturned(lockInfo))
|
throw new Exception($"拆包条码{lockInfo.CurrentBarcode}已经回库,无法取消拆包分拣");
|
|
// 恢复父锁定信息的分配数量
|
parentLockInfo.AssignQuantity += cancelQty;
|
parentLockInfo.Status = (int)OutLockStockStatusEnum.出库中; // 恢复为出库中状态
|
await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
|
|
// 恢复父条码库存
|
var parentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
|
.FirstAsync();
|
|
if (parentStockDetail != null)
|
{
|
parentStockDetail.StockQuantity += cancelQty;
|
parentStockDetail.OutboundQuantity = parentStockDetail.StockQuantity;
|
parentStockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(parentStockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复父条码库存 - 条码: {parentStockDetail.Barcode}, 恢复数量: {cancelQty}, 新库存: {parentStockDetail.StockQuantity}");
|
}
|
|
// 处理拆包产生的新条码库存
|
var splitStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == lockInfo.CurrentBarcode && x.StockId == lockInfo.StockId)
|
.FirstAsync();
|
|
if (splitStockDetail != null)
|
{
|
// 删除拆包产生的新条码库存记录
|
await _stockInfoDetailService.Db.Deleteable(splitStockDetail).ExecuteCommandAsync();
|
_logger.LogInformation($"删除拆包新条码库存 - 条码: {splitStockDetail.Barcode}");
|
}
|
|
// 更新拆包记录状态
|
var updateCount = await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
|
.SetColumns(x => new Dt_SplitPackageRecord
|
{
|
Status = (int)SplitPackageStatusEnum.已撤销,
|
IsReverted = true,
|
Operator = App.User.UserName,
|
RevertTime = DateTime.Now
|
})
|
.Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"更新拆包记录状态 - 更新记录数: {updateCount}");
|
|
// 删除拆包产生的锁定信息
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == lockInfo.Id)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"删除拆包锁定信息 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}");
|
}
|
private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
|
{
|
if (await IsLockInfoReturned(lockInfo))
|
throw new Exception($"条码{lockInfo.CurrentBarcode}已经回库,无法取消分拣");
|
|
// 恢复锁定信息
|
lockInfo.PickedQty -= cancelQty;
|
if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
|
|
// 只有当拣选数量完全取消时才恢复状态
|
if (lockInfo.PickedQty == 0)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
|
lockInfo.Operator = App.User.UserName;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复锁定信息 - 锁定ID: {lockInfo.Id}, 扣减拣选数量: {cancelQty}, 新已拣选数量: {lockInfo.PickedQty}");
|
|
// 恢复库存
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == pickingRecord.Barcode && x.StockId == pickingRecord.StockId)
|
.FirstAsync();
|
|
if (stockDetail != null)
|
{
|
stockDetail.StockQuantity += cancelQty;
|
stockDetail.OutboundQuantity = stockDetail.StockQuantity;
|
|
// 恢复库存状态
|
if (stockDetail.Status == StockStatusEmun.出库完成.ObjToInt())
|
{
|
stockDetail.Status = StockStatusEmun.出库锁定.ObjToInt();
|
}
|
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
_logger.LogInformation($"恢复库存 - 条码: {stockDetail.Barcode}, 恢复数量: {cancelQty}, " +
|
$"新库存: {stockDetail.StockQuantity}, 新状态: {stockDetail.Status}");
|
}
|
else
|
{
|
_logger.LogWarning($"未找到库存记录 - 条码: {pickingRecord.Barcode}, 库存ID: {pickingRecord.StockId}");
|
}
|
}
|
private async Task UpdateOrderDetailOnCancel(int orderDetailId, decimal cancelQty)
|
{
|
// 获取最新的订单明细数据(带锁)
|
var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.With(SqlWith.RowLock)
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
decimal newOverOutQuantity = currentOrderDetail.OverOutQuantity - cancelQty;
|
decimal newPickedQty = currentOrderDetail.PickedQty - cancelQty;
|
|
// 严格检查取消后数量不会为负数
|
if (newOverOutQuantity < 0)
|
throw new Exception($"取消分拣将导致已出库数量({newOverOutQuantity})为负数");
|
|
if (newPickedQty < 0)
|
throw new Exception($"取消分拣将导致已拣选数量({newPickedQty})为负数");
|
|
// 更新订单明细
|
var updateResult = await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
PickedQty = newPickedQty,
|
OverOutQuantity = newOverOutQuantity
|
})
|
.Where(it => it.Id == orderDetailId)
|
.ExecuteCommandAsync();
|
|
if (updateResult <= 0)
|
throw new Exception("更新订单明细失败");
|
|
_logger.LogInformation($"更新订单明细 - OrderDetailId: {orderDetailId}, " +
|
$"扣减已出库: {cancelQty}, 新已出库: {newOverOutQuantity}, " +
|
$"扣减已拣选: {cancelQty}, 新已拣选: {newPickedQty}");
|
}
|
#endregion
|
|
#region 回库操作私有方法
|
|
private async Task<Dt_StockInfo> GetStockInfo(string palletCode)
|
{
|
return await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.FirstAsync(x => x.PalletCode == palletCode);
|
}
|
/// <summary>
|
/// 检查整个托盘是否已经回库
|
/// </summary>
|
private async Task<bool> IsPalletReturned(string palletCode)
|
{
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
return false;
|
|
// 如果托盘状态是入库确认或入库完成,说明已经回库
|
return stockInfo.StockStatus == StockStatusEmun.入库确认.ObjToInt() ||
|
stockInfo.StockStatus == StockStatusEmun.入库完成.ObjToInt();
|
}
|
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<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;
|
}
|
|
private async Task<WebResponseContent> HandleNoReturnItems(string orderNo, string palletCode, Dt_Task originalTask, int stockInfoId)
|
{
|
// 检查是否所有货物都已拣选完成
|
//var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
// .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
|
// .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
|
|
//if (allPicked)
|
//{
|
// // 删除原始出库任务 组空盘 空盘回库
|
// //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
// return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
|
//}
|
//else
|
//{
|
// // 删除原始出库任务
|
// //await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
// return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
|
//}
|
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(stockInfoId);
|
|
|
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("空托盘回库任务创建成功");
|
|
}
|
|
private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
|
Dt_Task task, PalletStatusAnalysis analysis)
|
{
|
// 情况1:处理未分拣的出库锁定记录
|
if (analysis.HasRemainingLocks)
|
{
|
await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
|
|
// await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
|
}
|
|
// 处理托盘上其他库存货物
|
if (analysis.HasPalletStockGoods)
|
{
|
var validStockGoods = analysis.PalletStockGoods
|
.Where(x => x.Status != StockStatusEmun.出库完成.ObjToInt())
|
.ToList();
|
|
if (validStockGoods.Any())
|
{
|
await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
|
}
|
else
|
{
|
_logger.LogInformation("没有有效的库存货物需要回库");
|
}
|
}
|
|
// 处理拆包记录
|
if (analysis.HasSplitRecords)
|
{
|
var validSplitRecords = analysis.SplitRecords
|
.Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选)
|
.ToList();
|
|
if (validSplitRecords.Any())
|
{
|
await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
|
}
|
else
|
{
|
_logger.LogInformation("没有有效的拆包记录需要处理");
|
}
|
}
|
|
// 更新库存主表状态
|
await UpdateStockInfoStatus(stockInfo);
|
}
|
|
/// <summary>
|
/// 完全释放锁定,允许重新分配库存
|
/// </summary>
|
private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis)
|
{
|
_logger.LogInformation($"开始释放锁定以便重新分配 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
// 1. 处理未分拣的出库锁定记录 - 完全释放
|
if (analysis.HasRemainingLocks)
|
{
|
await ReleaseRemainingLocks(analysis.RemainingLocks);
|
}
|
|
// 2. 处理已回库的锁定记录 - 删除或标记为无效
|
await CleanupReturnedLocks(orderNo, palletCode);
|
|
// 3. 重置订单明细的锁定数量
|
await ResetOrderDetailLockQuantities(analysis);
|
|
_logger.LogInformation($"锁定释放完成 - 订单: {orderNo}, 托盘: {palletCode}");
|
}
|
|
/// <summary>
|
/// 释放未分拣的锁定记录
|
/// </summary>
|
private async Task ReleaseRemainingLocks(List<Dt_OutStockLockInfo> remainingLocks)
|
{
|
var lockIds = remainingLocks.Select(x => x.Id).ToList();
|
|
// 将锁定记录状态改为"已释放",或者直接删除
|
// 标记为已释放
|
await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
|
.SetColumns(it => new Dt_OutStockLockInfo
|
{
|
Status = (int)OutLockStockStatusEnum.已释放, // 需要新增这个状态
|
// ReleaseTime = DateTime.Now,
|
Operator = App.User.UserName
|
})
|
.Where(it => lockIds.Contains(it.Id))
|
.ExecuteCommandAsync();
|
|
// 直接删除(更彻底)
|
// await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
// .Where(it => lockIds.Contains(it.Id))
|
// .ExecuteCommandAsync();
|
|
_logger.LogInformation($"释放{remainingLocks.Count}条未分拣锁定记录");
|
}
|
|
/// <summary>
|
/// 清理已回库的锁定记录
|
/// </summary>
|
private async Task CleanupReturnedLocks(string orderNo, string palletCode)
|
{
|
// 查找所有状态为回库中的锁定记录并释放
|
var returnedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo &&
|
it.PalletCode == palletCode &&
|
it.Status == (int)OutLockStockStatusEnum.回库中)
|
.ToListAsync();
|
|
if (returnedLocks.Any())
|
{
|
var returnedLockIds = returnedLocks.Select(x => x.Id).ToList();
|
|
await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
|
.SetColumns(it => new Dt_OutStockLockInfo
|
{
|
Status = (int)OutLockStockStatusEnum.已释放,
|
//ReleaseTime = DateTime.Now,
|
Operator = App.User.UserName
|
})
|
.Where(it => returnedLockIds.Contains(it.Id))
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"清理{returnedLocks.Count}条回库中锁定记录");
|
}
|
}
|
|
/// <summary>
|
/// 重置订单明细的锁定数量
|
/// </summary>
|
private async Task ResetOrderDetailLockQuantities(PalletStatusAnalysis analysis)
|
{
|
// 收集所有受影响的订单明细ID
|
var affectedOrderDetailIds = new HashSet<int>();
|
|
if (analysis.HasRemainingLocks)
|
{
|
foreach (var lockInfo in analysis.RemainingLocks)
|
{
|
affectedOrderDetailIds.Add(lockInfo.OrderDetailId);
|
}
|
}
|
|
// 重置这些订单明细的锁定数量
|
foreach (var orderDetailId in affectedOrderDetailIds)
|
{
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
LockQuantity = 0, // 重置锁定数量
|
OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt() // 重置状态为新建
|
})
|
.Where(it => it.Id == orderDetailId)
|
.ExecuteCommandAsync();
|
}
|
|
_logger.LogInformation($"重置{affectedOrderDetailIds.Count}个订单明细的锁定数量");
|
}
|
private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
|
{
|
var lockIds = remainingLocks.Select(x => x.Id).ToList();
|
|
// 更新出库锁定记录状态为回库中
|
await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
|
.SetColumns(it => new Dt_OutStockLockInfo
|
{
|
Status = (int)OutLockStockStatusEnum.回库中,
|
})
|
.Where(it => lockIds.Contains(it.Id))
|
.ExecuteCommandAsync();
|
|
// 处理库存记录
|
foreach (var lockInfo in remainingLocks)
|
{
|
decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
if (returnQty <= 0)
|
{
|
_logger.LogWarning($"锁定记录{lockInfo.Id}无需回库,分配数量: {lockInfo.AssignQuantity}, 已拣选: {lockInfo.PickedQty}");
|
continue;
|
}
|
|
_logger.LogInformation($"处理锁定记录回库 - 锁定ID: {lockInfo.Id}, 条码: {lockInfo.CurrentBarcode}, 回库数量: {returnQty}");
|
// 查找对应的库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
|
.FirstAsync();
|
|
if (stockDetail != null)
|
{
|
// 恢复库存状态
|
stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
|
stockDetail.Status = StockStatusEmun.入库确认.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
else
|
{
|
// 创建新的库存记录
|
//var newStockDetail = new Dt_StockInfoDetail
|
//{
|
// StockId = lockInfo.StockId,
|
// MaterielCode = lockInfo.MaterielCode,
|
// MaterielName = lockInfo.MaterielName,
|
// OrderNo = lockInfo.OrderNo,
|
// BatchNo = lockInfo.BatchNo,
|
// StockQuantity = returnQty,
|
// OutboundQuantity = 0,
|
// Barcode = lockInfo.CurrentBarcode,
|
// InboundOrderRowNo = "",
|
// Status = StockStatusEmun.入库确认.ObjToInt(),
|
// SupplyCode = lockInfo.SupplyCode,
|
// WarehouseCode = lockInfo.WarehouseCode,
|
// Unit = lockInfo.Unit,
|
//};
|
//await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
|
}
|
}
|
}
|
|
private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
|
{
|
// 按订单明细分组
|
var orderDetailGroups = remainingLocks.GroupBy(x => x.OrderDetailId);
|
|
foreach (var group in orderDetailGroups)
|
{
|
var orderDetailId = group.Key;
|
var totalReturnQty = group.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
// 获取当前订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == orderDetailId);
|
|
if (orderDetail != null)
|
{
|
// 调整已拣选数量和已出库数量
|
decimal newPickedQty = Math.Max(0, orderDetail.PickedQty - totalReturnQty);
|
decimal newOverOutQuantity = Math.Max(0, orderDetail.OverOutQuantity - totalReturnQty);
|
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
PickedQty = newPickedQty,
|
OverOutQuantity = newOverOutQuantity,
|
})
|
.Where(it => it.Id == orderDetailId)
|
.ExecuteCommandAsync();
|
}
|
}
|
}
|
|
private async Task HandlePalletStockGoodsReturn(List<Dt_StockInfoDetail> palletStockGoods)
|
{
|
_logger.LogInformation($"回库操作:发现{palletStockGoods.Count}个库存明细需要回库,等待AGV搬运");
|
foreach (var stockGood in palletStockGoods)
|
{
|
_logger.LogInformation($"待回库货物 - 条码: {stockGood.Barcode}, 数量: {stockGood.StockQuantity}, 当前状态: {stockGood.Status}");
|
|
if (stockGood.Status != StockStatusEmun.出库完成.ObjToInt())
|
{
|
stockGood.OutboundQuantity = 0;
|
stockGood.Status = StockStatusEmun.入库确认.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
|
|
_logger.LogInformation($"库存货物回库完成 - 条码: {stockGood.Barcode}, 新状态: {stockGood.Status}");
|
}
|
else
|
{
|
_logger.LogWarning($"跳过已出库完成的货物 - 条码: {stockGood.Barcode}");
|
}
|
}
|
}
|
|
private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
|
{
|
var validRecords = splitRecords.Where(x => x.Status != (int)SplitPackageStatusEnum.已拣选).ToList();
|
|
if (!validRecords.Any())
|
{
|
_logger.LogInformation("没有需要回库的拆包记录");
|
return;
|
}
|
|
_logger.LogInformation($"更新{validRecords.Count}条拆包记录状态为已回库");
|
|
// 更新拆包记录状态
|
await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
|
.SetColumns(x => new Dt_SplitPackageRecord
|
{
|
Status = (int)SplitPackageStatusEnum.已回库,
|
Operator = App.User.UserName
|
})
|
.Where(x => validRecords.Select(r => r.Id).Contains(x.Id))
|
.ExecuteCommandAsync();
|
}
|
|
private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
|
{
|
_logger.LogInformation($"回库操作:托盘{stockInfo.PalletCode}等待AGV回库搬运");
|
// 更新库存主表状态
|
stockInfo.StockStatus = StockStatusEmun.入库确认.ObjToInt();
|
await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
|
}
|
/// <summary>
|
/// 创建回库任务
|
/// </summary>
|
/// <param name="orderNo"></param>
|
/// <param name="palletCode"></param>
|
/// <param name="originalTask"></param>
|
/// <param name="analysis"></param>
|
/// <returns></returns>
|
private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, TaskTypeEnum taskTypeEnum, int palletType)
|
{
|
var firstLocation = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
|
.FirstAsync(x => x.LocationCode == originalTask.SourceAddress);
|
|
// 分配新货位
|
var newLocation = _locationInfoService.AssignLocation(firstLocation.LocationType);
|
|
Dt_Task returnTask = new()
|
{
|
CurrentAddress = stations[originalTask.TargetAddress],
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
// OrderNo = originalTask.OrderNo,
|
OrderNo = orderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[originalTask.TargetAddress],
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = taskTypeEnum.ObjToInt(),
|
PalletType = palletType,
|
WarehouseId = originalTask.WarehouseId
|
|
};
|
// 保存回库任务
|
await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
var targetAddress = originalTask.TargetAddress;
|
|
// 删除原始出库任务
|
_taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.自动完成);
|
// await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
|
|
|
|
// 给 ESS 发送流动信号和创建任务
|
await SendESSCommands(palletCode, targetAddress, returnTask);
|
}
|
/// <summary>
|
/// 给ESS下任务
|
/// </summary>
|
/// <param name="palletCode"></param>
|
/// <param name="targetAddress"></param>
|
/// <param name="returnTask"></param>
|
/// <returns></returns>
|
/// <exception cref="Exception"></exception>
|
private async Task SendESSCommands(string palletCode, string targetAddress, Dt_Task returnTask)
|
{
|
try
|
{
|
// 1. 发送流动信号
|
var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
|
{
|
slotCode = movestations[targetAddress],
|
containerCode = palletCode
|
});
|
|
//if (moveResult)
|
//{
|
// 2. 创建回库任务
|
var essTask = new TaskModel()
|
{
|
taskType = "putaway",
|
taskGroupCode = "",
|
groupPriority = 0,
|
tasks = new List<TasksType>{ new() {
|
taskCode = returnTask.TaskNum.ToString(),
|
taskPriority = 0,
|
taskDescribe = new TaskDescribeType
|
{
|
containerCode = palletCode,
|
containerType = "CT_KUBOT_STANDARD",
|
fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
|
toStationCode = "",
|
toLocationCode = returnTask.TargetAddress,
|
deadline = 0,
|
storageTag = ""
|
}
|
} }
|
};
|
|
var resultTask = await _eSSApiService.CreateTaskAsync(essTask);
|
_logger.LogInformation($"ReturnRemaining 创建任务成功: {resultTask}");
|
//}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"ReturnRemaining ESS命令发送失败: {ex.Message}");
|
throw new Exception($"ESS系统通信失败: {ex.Message}");
|
}
|
}
|
|
#endregion
|
|
#region 订单状态管理
|
|
private async Task CheckAndUpdateOrderStatus(string orderNo)
|
{
|
try
|
{
|
var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
|
.Where((o, item) => item.OrderNo == orderNo)
|
.Select((o, item) => o)
|
.ToListAsync();
|
|
bool allCompleted = true;
|
foreach (var detail in orderDetails)
|
{
|
if (detail.OverOutQuantity < detail.NeedOutQuantity)
|
{
|
allCompleted = false;
|
break;
|
}
|
}
|
|
var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.FirstAsync(x => x.OrderNo == orderNo);
|
|
if (outboundOrder == null) return;
|
|
int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
|
|
if (outboundOrder.OrderStatus != newStatus)
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = newStatus,
|
Operator = App.User.UserName,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
// 只有正常分拣完成时才向MES反馈
|
//if (allCompleted && newStatus == (int)OutOrderStatusEnum.出库完成)
|
//{
|
// await HandleOrderCompletion(outboundOrder, orderNo);
|
//}
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"CheckAndUpdateOrderStatus失败 - OrderNo: {orderNo}, Error: {ex.Message}");
|
}
|
}
|
|
|
|
private async Task UpdateOrderStatusForReturn(string orderNo)
|
{
|
try
|
{
|
var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
|
.Where((o, item) => item.OrderNo == orderNo)
|
.Select((o, item) => o)
|
.ToListAsync();
|
|
bool allCompleted = true;
|
foreach (var detail in orderDetails)
|
{
|
if (detail.OverOutQuantity < detail.NeedOutQuantity)
|
{
|
allCompleted = false;
|
break;
|
}
|
}
|
|
var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.FirstAsync(x => x.OrderNo == orderNo);
|
|
if (outboundOrder == null) return;
|
|
int newStatus = allCompleted ? (int)OutOrderStatusEnum.出库完成 : (int)OutOrderStatusEnum.出库中;
|
|
if (outboundOrder.OrderStatus != newStatus)
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
OrderStatus = newStatus,
|
Operator = App.User.UserName,
|
})
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, 新状态: {newStatus}");
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}");
|
}
|
}
|
|
private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
|
{
|
// 调拨出库和重检出库不需要反馈MES
|
if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt())
|
{
|
var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First();
|
var allocatefeedmodel = new AllocateDto
|
{
|
ReqCode = Guid.NewGuid().ToString(),
|
ReqTime = DateTime.Now.ToString(),
|
BusinessType = "3",
|
|
FactoryArea = outboundOrder.FactoryArea,
|
OperationType = 1,
|
Operator = App.User.UserName,
|
OrderNo = outboundOrder.UpperOrderNo,
|
// documentsNO = outboundOrder.OrderNo,
|
// status = outboundOrder.OrderStatus,
|
fromWarehouse = allocate?.FromWarehouse ?? "",
|
toWarehouse = allocate?.ToWarehouse ?? "",
|
Details = new List<AllocateDtoDetail>()
|
|
};
|
// 只获取已拣选完成的锁定记录
|
var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
|
.ToListAsync();
|
|
var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
|
.Select(group => new AllocateDtoDetail
|
{
|
MaterialCode = group.Key.MaterielCode,
|
LineNo = group.Key.lineNo,
|
WarehouseCode = group.Key.WarehouseCode,
|
Qty = group.Sum(x => x.PickedQty),
|
|
Unit = group.Key.Unit,
|
Barcodes = group.Select(row => new BarcodeInfo
|
{
|
Barcode = row.CurrentBarcode,
|
SupplyCode = row.SupplyCode,
|
BatchNo = row.BatchNo,
|
Unit = row.Unit,
|
Qty = row.PickedQty
|
}).ToList()
|
|
|
}).ToList();
|
allocatefeedmodel.Details = groupedData;
|
|
var result = await _invokeMESService.FeedbackAllocate(allocatefeedmodel);
|
if (result != null && result.code == 200)
|
{
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(x => x.ReturnToMESStatus == 1)
|
.Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync();
|
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
ReturnToMESStatus = 1,
|
Operator = App.User.UserName,
|
}).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync();
|
}
|
}
|
else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
|
{
|
|
}
|
else
|
{
|
try
|
{
|
var feedmodel = new FeedbackOutboundRequestModel
|
{
|
reqCode = Guid.NewGuid().ToString(),
|
reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
business_type = outboundOrder.BusinessType,
|
factoryArea = outboundOrder.FactoryArea,
|
operationType = 1,
|
Operator = App.User.UserName,
|
orderNo = outboundOrder.UpperOrderNo,
|
documentsNO = outboundOrder.OrderNo,
|
status = outboundOrder.OrderStatus,
|
details = new List<FeedbackOutboundDetailsModel>()
|
};
|
|
// 只获取已拣选完成的锁定记录
|
var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成)
|
.ToListAsync();
|
|
var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
|
.Select(group => new FeedbackOutboundDetailsModel
|
{
|
materialCode = group.Key.MaterielCode,
|
lineNo = group.Key.lineNo,
|
warehouseCode = group.Key.WarehouseCode,
|
qty = group.Sum(x => x.PickedQty),
|
currentDeliveryQty = group.Sum(x => x.PickedQty),
|
unit = group.Key.Unit,
|
barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
|
{
|
barcode = row.CurrentBarcode,
|
supplyCode = row.SupplyCode,
|
batchNo = row.BatchNo,
|
unit = row.Unit,
|
qty = row.PickedQty
|
}).ToList()
|
}).ToList();
|
|
feedmodel.details = groupedData;
|
|
var result = await _invokeMESService.FeedbackOutbound(feedmodel);
|
if (result != null && result.code == 200)
|
{
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(x => x.ReturnToMESStatus == 1)
|
.Where(x => x.OrderId == outboundOrder.Id)
|
.ExecuteCommandAsync();
|
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => new Dt_OutboundOrder
|
{
|
ReturnToMESStatus = 1,
|
Operator = App.User.UserName,
|
})
|
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
|
_logger.LogError($"FeedbackOutbound成功 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"FeedbackOutbound失败 - OrderNo: {orderNo}, Error: {ex.Message}");
|
}
|
}
|
}
|
|
#endregion
|
|
#region 空托盘
|
|
/// <summary>
|
/// 清理零库存数据
|
/// </summary>
|
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 && (x.Status == StockStatusEmun.出库完成.ObjToInt() || x.Status ==
|
StockStatusEmun.入库完成.ObjToInt()))
|
.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 HandleTaskCleanup(string orderNo, string palletCode)
|
{
|
try
|
{
|
// 1. 查找所有与该订单和托盘相关的任务
|
var tasks = await _taskRepository.Db.Queryable<Dt_Task>().Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode).ToListAsync();
|
|
if (tasks.Any())
|
{
|
foreach (var task in tasks)
|
{
|
task.TaskStatus = (int)TaskStatusEnum.Finish;
|
}
|
// await _taskRepository.Db.Updateable(tasks).ExecuteCommandAsync();
|
|
_taskRepository.DeleteAndMoveIntoHty(tasks, OperateTypeEnum.自动完成);
|
_logger.LogInformation($"完成{tasks.Count}个托盘任务 - 订单: {orderNo}, 托盘: {palletCode}");
|
}
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning($"处理任务清理失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
throw new Exception($"任务清理失败: {ex.Message}");
|
}
|
}
|
/// <summary>
|
/// 更新订单相关数据
|
/// </summary>
|
private async Task UpdateOrderData(string orderNo, string palletCode)
|
{
|
try
|
{
|
// 检查订单是否还有其他托盘在处理中
|
var otherActivePallets = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode != palletCode &&
|
(x.Status == (int)OutLockStockStatusEnum.出库中 || x.Status == (int)OutLockStockStatusEnum.回库中))
|
.AnyAsync();
|
|
var otherActiveTasks = await _taskRepository.Db.Queryable<Dt_Task>()
|
.Where(x => x.OrderNo == orderNo &&
|
x.PalletCode != palletCode
|
// && x.TaskStatus.In((int)TaskStatusEnum.待执行, (int)TaskStatusEnum.执行中)
|
)
|
.AnyAsync();
|
|
// 如果没有其他托盘在处理,检查订单是否应该完成
|
if (!otherActivePallets && !otherActiveTasks)
|
{
|
await CheckAndUpdateOrderCompletion(orderNo);
|
}
|
else
|
{
|
_logger.LogInformation($"订单 {orderNo} 还有其他托盘在处理,不更新订单状态");
|
}
|
|
// 3. 更新拣选记录状态(可选)
|
await UpdatePickingRecordsStatus(orderNo, palletCode);
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning($"更新订单数据失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
throw new Exception($"更新订单数据失败: {ex.Message}");
|
}
|
}
|
|
/// <summary>
|
/// 检查并更新订单完成状态
|
/// </summary>
|
private async Task CheckAndUpdateOrderCompletion(string orderNo)
|
{
|
var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id)
|
.Where((o, item) => item.OrderNo == orderNo)
|
.Select((o, item) => o)
|
.ToListAsync();
|
|
bool allCompleted = true;
|
foreach (var detail in orderDetails)
|
{
|
if (detail.OverOutQuantity < detail.NeedOutQuantity)
|
{
|
allCompleted = false;
|
break;
|
}
|
}
|
|
var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.FirstAsync(x => x.OrderNo == orderNo);
|
|
if (outboundOrder != null && allCompleted && outboundOrder.OrderStatus != (int)OutOrderStatusEnum.出库完成)
|
{
|
outboundOrder.OrderStatus = (int)OutOrderStatusEnum.出库完成;
|
outboundOrder.Operator = App.User.UserName;
|
await _outboundOrderService.Db.Updateable(outboundOrder).ExecuteCommandAsync();
|
|
_logger.LogInformation($"订单 {orderNo} 已标记为出库完成");
|
|
// 向MES反馈订单完成(如果需要)
|
await HandleOrderCompletion(outboundOrder, orderNo);
|
}
|
}
|
|
/// <summary>
|
/// 更新拣选记录状态
|
/// </summary>
|
private async Task UpdatePickingRecordsStatus(string orderNo, string palletCode)
|
{
|
try
|
{
|
// 可以将相关的拣选记录标记为已完成
|
var pickingRecords = await Db.Queryable<Dt_PickingRecord>()
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
|
.ToListAsync();
|
|
// 这里可以根据需要更新拣选记录的状态字段
|
// 例如:pickingRecord.Status = (int)PickingStatusEnum.已完成;
|
|
_logger.LogInformation($"找到{pickingRecords.Count}条拣选记录 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning($"更新拣选记录状态失败: {ex.Message}");
|
}
|
}
|
#endregion
|
|
|
|
#region 辅助方法
|
/// <summary>
|
/// 统一分析托盘状态 - 返回托盘的完整状态信息
|
/// </summary>
|
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;
|
}
|
|
/// <summary>
|
/// 检查托盘是否为空
|
/// </summary>
|
private async Task<bool> IsPalletEmpty(string orderNo, string palletCode)
|
{
|
try
|
{
|
// 获取库存信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
return false;
|
|
// 使用统一的状态分析
|
var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
|
return statusAnalysis.IsEmptyPallet;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogWarning($"检查托盘是否为空失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return false;
|
}
|
}
|
/// <summary>
|
/// 检查并处理空托盘
|
/// </summary>
|
private async Task<bool> CheckAndHandleEmptyPallet(string orderNo, string palletCode)
|
{
|
try
|
{
|
// 1. 获取库存信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
{
|
_logger.LogWarning($"未找到托盘 {palletCode} 的库存信息");
|
return false;
|
}
|
|
// 2. 使用统一的状态分析
|
var statusAnalysis = await AnalyzePalletStatus(orderNo, palletCode, stockInfo.Id);
|
|
// 3. 检查是否为空托盘且没有进行中的任务
|
if (!statusAnalysis.IsEmptyPallet || statusAnalysis.HasActiveTasks)
|
{
|
return false;
|
}
|
|
_logger.LogInformation($"检测到空托盘,开始自动处理 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
//// 清理零库存数据
|
//await CleanupZeroStockData(stockInfo.Id);
|
|
//// 更新库存主表状态为空托盘
|
//await UpdateStockInfoAsEmpty(stockInfo);
|
|
//// 处理出库锁定记录
|
//await HandleOutStockLockRecords(orderNo, palletCode);
|
|
//// 处理任务状态
|
//await HandleTaskStatusForEmptyPallet(orderNo, palletCode);
|
|
//// 更新订单数据
|
//await UpdateOrderDataForEmptyPallet(orderNo, palletCode);
|
|
////记录操作历史
|
//await RecordAutoEmptyPalletOperation(orderNo, palletCode);
|
|
_logger.LogInformation($"空托盘自动处理完成 - 订单: {orderNo}, 托盘: {palletCode}");
|
|
return true;
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"自动处理空托盘失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return false;
|
}
|
}
|
|
private async Task<string> GenerateNewBarcode()
|
{
|
var seq = await _dailySequenceService.GetNextSequenceAsync();
|
return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
|
}
|
|
private async Task<Dt_OutStockLockInfo> CreateSplitLockInfo(Dt_OutStockLockInfo originalLock, decimal quantity, string newBarcode)
|
{
|
var newLockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = originalLock.OrderNo,
|
OrderDetailId = originalLock.OrderDetailId,
|
BatchNo = originalLock.BatchNo,
|
MaterielCode = originalLock.MaterielCode,
|
MaterielName = originalLock.MaterielName,
|
StockId = originalLock.StockId,
|
OrderQuantity = quantity,
|
OriginalQuantity = quantity,
|
AssignQuantity = quantity,
|
PickedQty = quantity,
|
LocationCode = originalLock.LocationCode,
|
PalletCode = originalLock.PalletCode,
|
TaskNum = originalLock.TaskNum,
|
Status = (int)OutLockStockStatusEnum.拣选完成,
|
Unit = originalLock.Unit,
|
SupplyCode = originalLock.SupplyCode,
|
OrderType = originalLock.OrderType,
|
CurrentBarcode = newBarcode,
|
OriginalLockQuantity = quantity,
|
IsSplitted = 1,
|
ParentLockId = originalLock.Id,
|
Operator = App.User.UserName,
|
FactoryArea = originalLock.FactoryArea,
|
lineNo = originalLock.lineNo,
|
WarehouseCode = originalLock.WarehouseCode,
|
BarcodeQty=originalLock.BarcodeQty,
|
BarcodeUnit=originalLock.BarcodeUnit,
|
|
};
|
|
var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
|
newLockInfo.Id = newLockId;
|
return newLockInfo;
|
}
|
|
private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,
|
decimal splitQty, decimal remainQty, string newBarcode)
|
{
|
var splitHistory = new Dt_SplitPackageRecord
|
{
|
FactoryArea = lockInfo.FactoryArea,
|
TaskNum = lockInfo.TaskNum,
|
OutStockLockInfoId = lockInfo.Id,
|
StockId = stockDetail.StockId,
|
Operator = App.User.UserName,
|
IsReverted = false,
|
OriginalBarcode = stockDetail.Barcode,
|
NewBarcode = newBarcode,
|
SplitQty = splitQty,
|
RemainQuantity = remainQty,
|
MaterielCode = lockInfo.MaterielCode,
|
SplitTime = DateTime.Now,
|
OrderNo = lockInfo.OrderNo,
|
PalletCode = lockInfo.PalletCode,
|
Status = (int)SplitPackageStatusEnum.已拣选
|
};
|
|
await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
|
}
|
|
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"),
|
}
|
};
|
}
|
|
private async Task UpdateSplitRecordsStatus(string barcode)
|
{
|
var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(it => it.OriginalBarcode == barcode || it.NewBarcode == barcode)
|
.Where(it => !it.IsReverted)
|
.ToListAsync();
|
|
foreach (var record in relatedSplitRecords)
|
{
|
record.Status = (int)SplitPackageStatusEnum.已拣选;
|
await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
|
}
|
}
|
|
private async Task<int> GenerateTaskNumber()
|
{
|
return await _dailySequenceService.GetNextSequenceAsync();
|
}
|
|
private WebResponseContent CreatePickingResponse(PickingResult result, string adjustedReason)
|
{
|
//if (result.SplitResults.Any())
|
//{
|
// var responseData = new { SplitResults = result.SplitResults, AdjustedReason = "" };
|
// if (!string.IsNullOrEmpty(adjustedReason))
|
// {
|
// responseData = new { SplitResults = result.SplitResults, AdjustedReason = adjustedReason };
|
// }
|
// return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", responseData);
|
//}
|
|
//if (!string.IsNullOrEmpty(adjustedReason))
|
//{
|
// return WebResponseContent.Instance.OK($"拣选确认成功({adjustedReason})");
|
//}
|
|
//return WebResponseContent.Instance.OK("拣选确认成功");
|
|
if (result.SplitResults.Any())
|
{
|
return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = result.SplitResults });
|
}
|
return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
|
}
|
|
|
|
#region 虚拟出入库
|
public WebResponseContent GetAvailablePurchaseOrders()
|
{
|
List<Dt_InboundOrder> InOders = _inboundOrderRepository.QueryData().Where(x => x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).ToList();
|
List<string> InOderCodes = InOders.Select(x => x.UpperOrderNo).ToList();
|
return WebResponseContent.Instance.OK("成功",data: InOderCodes);
|
}
|
|
public WebResponseContent GetAvailablePickingOrders()
|
{
|
List<Dt_OutboundOrder> outOders = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).ToList();
|
|
List<string> outOderCodes = outOders.Select(x => x.UpperOrderNo).ToList();
|
return WebResponseContent.Instance.OK("成功", data: outOderCodes);
|
|
}
|
public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut)
|
{
|
try
|
{
|
Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
|
if(inboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
|
}
|
var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if (matchedDetail == null)
|
{
|
return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} 中未找到条码为 {noStockOut.barCode} 的明细。");
|
}
|
matchedDetail.NoStockOutQty = 0;
|
|
Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
|
if (outboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
|
}
|
var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if (matchedCode == null)
|
{
|
return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} 对应的物料。");
|
}
|
matchedCode.NoStockOutQty = 0;
|
|
//剩余入库数量即虚拟出入库剩余可出数量
|
decimal outQuantity = matchedDetail.OrderQuantity - matchedDetail.ReceiptQuantity;
|
if(outQuantity == 0)
|
{
|
return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量为0");
|
}
|
if (matchedCode.OrderQuantity < outQuantity)
|
{
|
return WebResponseContent.Instance.Error($"该采购单中的条码对应的可出数量超出出库单出库数量{matchedDetail.OrderQuantity - matchedCode.OrderQuantity},不满足整包出库");
|
}
|
//单据出库锁定数量
|
matchedDetail.NoStockOutQty += outQuantity;
|
matchedCode.NoStockOutQty += outQuantity;
|
|
if ((matchedCode.LockQuantity + matchedCode.NoStockOutQty) > matchedCode.OrderQuantity)
|
{
|
return WebResponseContent.Instance.Error($"出库单明细数量溢出{matchedCode.LockQuantity - matchedCode.OrderQuantity}");
|
}
|
matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.Inbounding.ObjToInt();
|
matchedCode.OrderDetailStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
|
|
_unitOfWorkManage.BeginTran();
|
_inboundOrderDetailService.UpdateData(matchedDetail);
|
_outboundOrderDetailService.UpdateData(matchedCode);
|
_unitOfWorkManage.CommitTran();
|
return WebResponseContent.Instance.OK();
|
}
|
catch(Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
|
public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut)
|
{
|
try
|
{
|
Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
|
if (inboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到采购单:{noStockOut.inOder}");
|
}
|
var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if (matchedDetail == null)
|
{
|
return WebResponseContent.Instance.Error($"在采购单 {noStockOut.inOder} 中未找到条码为 {noStockOut.barCode} 的明细。");
|
}
|
matchedDetail.NoStockOutQty = 0;
|
|
Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
|
if (outboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到出库单:{noStockOut.inOder}");
|
}
|
var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if (matchedCode == null)
|
{
|
return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{matchedDetail.MaterielCode} 对应的物料。");
|
}
|
matchedCode.NoStockOutQty = 0;
|
_unitOfWorkManage.BeginTran();
|
_inboundOrderDetailService.UpdateData(matchedDetail);
|
_outboundOrderDetailService.UpdateData(matchedCode);
|
_unitOfWorkManage.CommitTran();
|
return WebResponseContent.Instance.OK();
|
|
}
|
catch(Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
|
public WebResponseContent NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
|
{
|
try
|
{
|
Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.InOderSubmit && x.OrderStatus != InOrderStatusEnum.入库完成.ObjToInt()).Includes(x => x.Details).First();
|
if (inboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到采购单:{noStockOutSubmit.InOderSubmit}");
|
}
|
Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.OutOderSubmit && x.OrderStatus != OutOrderStatusEnum.出库完成.ObjToInt()).Includes(x => x.Details).First();
|
if (outboundOrder == null)
|
{
|
return WebResponseContent.Instance.Error($"未找到出库单:{noStockOutSubmit.OutOderSubmit}");
|
}
|
List<Dt_InboundOrderDetail> inboundOrderDetails = new List<Dt_InboundOrderDetail>();
|
List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
|
foreach (var BarCode in noStockOutSubmit.BarCodeSubmit)
|
{
|
var inboundOrderDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == BarCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if(inboundOrderDetail == null)
|
{
|
return WebResponseContent.Instance.Error($"在采购单 {noStockOutSubmit.InOderSubmit} 中未找到条码为 {BarCode} 的明细。");
|
}
|
var outboundOrderDetail = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == inboundOrderDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
|
|
if (outboundOrderDetail == null)
|
{
|
return WebResponseContent.Instance.Error($"在出库单的物料编码中未找到与采购单中的{inboundOrderDetail.MaterielCode} 对应的物料。");
|
}
|
inboundOrderDetail.ReceiptQuantity += inboundOrderDetail.NoStockOutQty;
|
inboundOrderDetail.OverInQuantity = inboundOrderDetail.ReceiptQuantity;
|
inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
|
inboundOrderDetails.Add(inboundOrderDetail);
|
|
outboundOrderDetail.LockQuantity += outboundOrderDetail.NoStockOutQty;
|
outboundOrderDetail.OverOutQuantity = outboundOrderDetail.LockQuantity;
|
if(outboundOrderDetail.OrderQuantity == outboundOrderDetail.OverOutQuantity)
|
{
|
outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
|
}
|
outboundOrderDetails.Add(outboundOrderDetail);
|
|
}
|
//判断入库单据明细是否全部是完成状态
|
bool inoderOver = inboundOrder.Details.Count() == inboundOrder.Details.Select(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
|
if (inoderOver)
|
{
|
inboundOrder.OrderStatus = InOrderStatusEnum.入库完成.ObjToInt();
|
}
|
//判断出库单据明细是否全部是完成状态
|
bool outOderOver = outboundOrder.Details.Count() == outboundOrder.Details.Select(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
|
if (outOderOver)
|
{
|
outboundOrder.OrderStatus = OutOrderStatusEnum.出库完成.ObjToInt();
|
}
|
//数据处理
|
_unitOfWorkManage.BeginTran();
|
_inboundOrderDetailService.UpdateData(inboundOrderDetails);
|
_outboundOrderDetailService.UpdateData(outboundOrderDetails);
|
_inboundOrderRepository.UpdateData(inboundOrder);
|
_outboundOrderService.UpdateData(outboundOrder);
|
_unitOfWorkManage.CommitTran();
|
|
return WebResponseContent.Instance.OK();
|
}
|
catch(Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error(ex.Message);
|
}
|
}
|
#endregion
|
|
|
#endregion
|
}
|
|
|
|
#region 支持类定义
|
|
public class ValidationResult<T>
|
{
|
public bool IsValid { get; set; }
|
public T Data { get; set; }
|
public string ErrorMessage { get; set; }
|
|
public static ValidationResult<T> Success(T data)
|
{
|
return new ValidationResult<T>
|
{
|
IsValid = true,
|
Data = data
|
};
|
}
|
|
public static ValidationResult<T> Error(string message)
|
{
|
return new ValidationResult<T>
|
{
|
IsValid = false,
|
ErrorMessage = message
|
};
|
}
|
}
|
|
public class PickingResult
|
{
|
public Dt_OutStockLockInfo FinalLockInfo { get; set; }
|
public string FinalBarcode { get; set; }
|
public int FinalStockId { get; set; }
|
public decimal ActualPickedQty { get; set; }
|
public List<SplitResult> SplitResults { get; set; } = new List<SplitResult>();
|
}
|
|
public class ReturnAnalysisResult
|
{
|
public bool HasItemsToReturn { get; set; }
|
public bool HasRemainingLocks { get; set; }
|
public bool HasPalletStockGoods { get; set; }
|
public bool HasSplitRecords { get; set; }
|
public decimal RemainingLocksReturnQty { get; set; }
|
public decimal PalletStockReturnQty { get; set; }
|
public decimal SplitReturnQty { get; set; }
|
public decimal TotalReturnQty { get; set; }
|
public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
|
public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
|
public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
|
}
|
public class PalletStatusAnalysis
|
{
|
public string OrderNo { get; set; }
|
public string PalletCode { get; set; }
|
public int StockId { get; set; }
|
|
// 回库相关属性
|
public bool HasItemsToReturn { get; set; }
|
public bool HasRemainingLocks { get; set; }
|
public bool HasPalletStockGoods { get; set; }
|
public bool HasSplitRecords { get; set; }
|
public decimal RemainingLocksReturnQty { get; set; }
|
public decimal PalletStockReturnQty { get; set; }
|
public decimal SplitReturnQty { get; set; }
|
public decimal TotalReturnQty { get; set; }
|
public List<Dt_OutStockLockInfo> RemainingLocks { get; set; } = new List<Dt_OutStockLockInfo>();
|
public List<Dt_StockInfoDetail> PalletStockGoods { get; set; } = new List<Dt_StockInfoDetail>();
|
public List<Dt_SplitPackageRecord> SplitRecords { get; set; } = new List<Dt_SplitPackageRecord>();
|
|
// 空托盘相关属性
|
public bool IsEmptyPallet { get; set; }
|
public bool HasActiveTasks { get; set; }
|
|
// 便利方法
|
public bool CanReturn => HasItemsToReturn && !HasActiveTasks;
|
public bool CanRemove => IsEmptyPallet && !HasActiveTasks;
|
}
|
public class PickingContext
|
{
|
public string OrderNo { get; set; }
|
public string PalletCode { get; set; }
|
public string Barcode { get; set; }
|
public string Operator { get; set; }
|
public Dt_OutStockLockInfo LockInfo { get; set; }
|
public Dt_OutboundOrderDetail OrderDetail { get; set; }
|
public Dt_StockInfoDetail StockDetail { get; set; }
|
public decimal ActualQuantity { get; set; }
|
public string AdjustedReason { get; set; }
|
}
|
public class CancelPickingContext
|
{
|
public string OrderNo { get; set; }
|
public string PalletCode { get; set; }
|
public string Barcode { get; set; }
|
public string Operator { get; set; }
|
|
public decimal CancelQuantity { get; set; }
|
public Dt_PickingRecord PickingRecord { get; set; }
|
public Dt_OutStockLockInfo LockInfo { get; set; }
|
public Dt_OutboundOrderDetail OrderDetail { get; set; }
|
}
|
#endregion
|
}
|