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.Threading.Tasks;
|
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.Helper;
|
using WIDESEA_DTO.Basic;
|
using WIDESEA_DTO.Inbound;
|
using WIDESEA_DTO.Outbound;
|
using WIDESEA_IBasicService;
|
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 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) : 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;
|
}
|
|
|
#region 查询出库详情列表
|
public async Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo)
|
{
|
var locks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(t => t.OrderNo == orderNo)
|
.ToListAsync();
|
|
return locks.Select(t => new OutStockLockListResp
|
{
|
Id = t.Id,
|
// TaskNum = t.TaskNum,
|
PalletCode = t.PalletCode,
|
CurrentBarcode = t.CurrentBarcode,
|
AssignQuantity = t.AssignQuantity,
|
PickedQty = t.PickedQty,
|
Status = t.Status,
|
// IsSplitted = t.IsSplitted
|
}).ToList();
|
}
|
#endregion
|
public async Task<WebResponseContent> ValidateBarcode(string barcode)
|
{
|
try
|
{
|
if (string.IsNullOrEmpty(barcode))
|
{
|
return WebResponseContent.Instance.Error("条码不能为空");
|
}
|
|
// 根据条码查询库存明细
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Includes(x => x.StockInfo)
|
.Where(x => x.Barcode == barcode)
|
.FirstAsync();
|
|
if (stockDetail == null)
|
{
|
return WebResponseContent.Instance.Error("条码不存在");
|
}
|
|
|
|
var result = new
|
{
|
Barcode = barcode,
|
MaterielCode = stockDetail.MaterielCode,
|
|
BatchNo = stockDetail.BatchNo,
|
AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
|
LocationCode = stockDetail.StockInfo?.LocationCode,
|
PalletCode = stockDetail.StockInfo?.PalletCode
|
};
|
|
return WebResponseContent.Instance.OK(null, result);
|
}
|
catch (Exception ex)
|
{
|
return WebResponseContent.Instance.Error($"条码验证失败: {ex.Message}");
|
}
|
}
|
|
// 检查并更新订单状态
|
private async Task CheckAndUpdateOrderStatus(string orderNo)
|
{
|
|
var orderDetails = _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id) // 关联条件:父表 Id = 子表 OrderId
|
.Where((o, item) => item.OrderNo == orderNo) // 过滤父表 OrderNo
|
.Select((o, item) => o) // 只返回子表数据
|
.ToList();
|
|
//var orderDetails = await _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
// .Where(x => x.OrderId == orderNo.ObjToInt())
|
// .ToListAsync();
|
|
bool allCompleted = true;
|
foreach (var detail in orderDetails)
|
{
|
if (detail.OverOutQuantity < detail.NeedOutQuantity)
|
{
|
allCompleted = false;
|
break;
|
}
|
}
|
|
if (allCompleted)
|
{
|
try
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => x.OrderStatus == 2) // 已完成
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
var outboundOrder = _stockInfoService.Db.Queryable<Dt_OutboundOrder>().First(x => x.OrderNo == orderNo);
|
|
|
if (outboundOrder != null && outboundOrder.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
|
{
|
|
if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt().ObjToInt())//调拨出库
|
{
|
|
}
|
else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt()) //重检出库
|
{
|
|
}
|
else
|
{
|
var feedmodel = new FeedbackOutboundRequestModel
|
{
|
reqCode = Guid.NewGuid().ToString(),
|
reqTime = DateTime.Now.ToString(),
|
business_type = outboundOrder.BusinessType,
|
factoryArea = outboundOrder.FactoryArea,
|
operationType = 1,
|
Operator = outboundOrder.Operator,
|
orderNo = outboundOrder.UpperOrderNo,
|
status = outboundOrder.OrderStatus,
|
details = new List<FeedbackOutboundDetailsModel>()
|
|
};
|
var lists = _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>().Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.拣选完成).ToList();
|
|
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,
|
currentDeliveryQty = group.Sum(x => x.OrderQuantity),
|
// warehouseCode= "1072",
|
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 => x.ReturnToMESStatus == 1) // 已完成
|
.Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync();
|
}
|
}
|
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError(" OutboundPickingService FeedbackOutbound : " + ex.Message);
|
}
|
|
}
|
}
|
|
public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 1. 验证输入参数
|
if (string.IsNullOrEmpty(orderNo) || string.IsNullOrEmpty(palletCode) || string.IsNullOrEmpty(barcode))
|
{
|
throw new Exception("订单号、托盘码和条码不能为空");
|
}
|
|
// 2. 查找出库锁定信息
|
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.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
|
throw new Exception($"条码{barcode}不属于托盘{palletCode}或不存在待分拣记录");
|
}
|
}
|
|
if (lockInfo.PalletCode != palletCode)
|
throw new Exception($"条码{barcode}不属于托盘{palletCode}");
|
|
// 检查拣选历史,防止重复分拣
|
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)
|
{
|
throw new Exception($"条码{barcode}已经分拣过,不能重复分拣");
|
}
|
|
// 获取订单明细并检查数量限制
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == lockInfo.OrderDetailId);
|
|
if (orderDetail == null)
|
throw new Exception($"未找到订单明细,ID: {lockInfo.OrderDetailId}");
|
|
// 关键修复:检查累计拣选数量是否会超过订单数量
|
decimal actualQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
decimal remainingOrderQty = orderDetail.NeedOutQuantity - orderDetail.OverOutQuantity;
|
|
if (actualQty > remainingOrderQty)
|
{
|
// 如果分配数量大于剩余订单数量,调整实际拣选数量
|
actualQty = remainingOrderQty;
|
|
if (actualQty <= 0)
|
{
|
throw new Exception($"订单{orderNo}的需求数量已满足,无法继续分拣");
|
}
|
|
_logger.LogWarning($"调整分拣数量:原分配{lockInfo.AssignQuantity - lockInfo.PickedQty},调整为{actualQty},订单需求{orderDetail.NeedOutQuantity},已出库{orderDetail.OverOutQuantity}");
|
}
|
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == barcode && x.StockId == lockInfo.StockId)
|
.FirstAsync();
|
|
if (stockDetail == null)
|
return WebResponseContent.Instance.Error("无效的条码或物料编码");
|
|
decimal stockQuantity = stockDetail.StockQuantity;
|
|
List<SplitResult> splitResults = new List<SplitResult>();
|
Dt_OutStockLockInfo finalLockInfo = lockInfo;
|
var finalBarcode = barcode;
|
var finalStockId = stockDetail.Id;
|
decimal actualPickedQty = actualQty;
|
|
if (actualQty < stockQuantity)
|
{
|
// 情况1: 分配数量小于库存数量,需要自动拆包
|
decimal remainingStockQty = stockQuantity - actualQty;
|
|
// 更新原条码库存为剩余数量
|
stockDetail.StockQuantity = remainingStockQty;
|
stockDetail.OutboundQuantity = remainingStockQty;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 生成新条码用于记录拣选数量
|
var seq = await _dailySequenceService.GetNextSequenceAsync();
|
string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0');
|
|
// 为新条码创建出库锁定信息
|
var newLockInfo = new Dt_OutStockLockInfo
|
{
|
OrderNo = lockInfo.OrderNo,
|
OrderDetailId = lockInfo.OrderDetailId,
|
BatchNo = lockInfo.BatchNo,
|
MaterielCode = lockInfo.MaterielCode,
|
MaterielName = lockInfo.MaterielName,
|
StockId = lockInfo.StockId,
|
OrderQuantity = actualQty,
|
OriginalQuantity = actualQty,
|
AssignQuantity = actualQty,
|
PickedQty = actualQty,
|
LocationCode = lockInfo.LocationCode,
|
PalletCode = lockInfo.PalletCode,
|
TaskNum = lockInfo.TaskNum,
|
Status = (int)OutLockStockStatusEnum.拣选完成,
|
Unit = lockInfo.Unit,
|
SupplyCode = lockInfo.SupplyCode,
|
OrderType = lockInfo.OrderType,
|
CurrentBarcode = newBarcode,
|
OriginalLockQuantity = actualQty,
|
IsSplitted = 1,
|
ParentLockId = lockInfo.Id
|
};
|
|
var newLockId = await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteReturnIdentityAsync();
|
newLockInfo.Id = newLockId;
|
|
// 记录拆包历史
|
var splitHistory = new Dt_SplitPackageRecord
|
{
|
FactoryArea = lockInfo.FactoryArea,
|
TaskNum = lockInfo.TaskNum,
|
OutStockLockInfoId = lockInfo.Id,
|
StockId = stockDetail.StockId,
|
Operator = App.User.UserName,
|
IsReverted = false,
|
OriginalBarcode = barcode,
|
NewBarcode = newBarcode,
|
SplitQty = actualQty,
|
RemainQuantity = remainingStockQty,
|
MaterielCode = lockInfo.MaterielCode,
|
SplitTime = DateTime.Now,
|
OrderNo = lockInfo.OrderNo,
|
PalletCode = lockInfo.PalletCode,
|
Status = (int)SplitPackageStatusEnum.已拣选
|
};
|
await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync();
|
|
// 更新原锁定信息为剩余库存数量
|
lockInfo.AssignQuantity = remainingStockQty;
|
lockInfo.PickedQty = 0;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
splitResults.Add(new SplitResult
|
{
|
materialCode = lockInfo.MaterielCode,
|
supplierCode = lockInfo.SupplyCode,
|
quantityTotal = actualQty.ToString("F2"),
|
batchNumber = newBarcode,
|
batch = lockInfo.BatchNo,
|
factory = lockInfo.FactoryArea,
|
date = DateTime.Now.ToString("yyyy-MM-dd"),
|
});
|
|
splitResults.Add(new SplitResult
|
{
|
materialCode = lockInfo.MaterielCode,
|
supplierCode = lockInfo.SupplyCode,
|
quantityTotal = remainingStockQty.ToString("F2"),
|
batchNumber = barcode,
|
batch = lockInfo.BatchNo,
|
factory = lockInfo.FactoryArea,
|
date = DateTime.Now.ToString("yyyy-MM-dd"),
|
});
|
|
finalLockInfo = newLockInfo;
|
finalBarcode = newBarcode;
|
}
|
else if (actualQty == stockQuantity)
|
{
|
// 情况2: 分配数量等于库存数量,整包出库
|
stockDetail.StockQuantity = 0;
|
stockDetail.OutboundQuantity = 0;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
lockInfo.PickedQty += actualQty;
|
lockInfo.Status = (int)OutLockStockStatusEnum.拣选完成;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
}
|
else
|
{
|
// 情况3: 分配数量大于库存数量,库存整包出库
|
decimal stockOutQty = stockQuantity;
|
stockDetail.StockQuantity = 0;
|
stockDetail.OutboundQuantity = 0;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
decimal remainingAssignQty = actualQty - stockQuantity;
|
lockInfo.PickedQty += stockOutQty;
|
lockInfo.AssignQuantity = remainingAssignQty;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
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();
|
}
|
|
actualPickedQty = stockOutQty;
|
}
|
|
// 关键修复:再次检查订单数量限制
|
decimal newOverOutQuantity = orderDetail.OverOutQuantity + actualPickedQty;
|
decimal newPickedQty = orderDetail.PickedQty + actualPickedQty;
|
|
if (newOverOutQuantity > orderDetail.NeedOutQuantity)
|
{
|
throw new Exception($"分拣后将导致已出库数量({newOverOutQuantity})超过订单需求数量({orderDetail.NeedOutQuantity})");
|
}
|
|
// 更新订单明细
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
PickedQty = newPickedQty,
|
OverOutQuantity = newOverOutQuantity
|
})
|
.Where(it => it.Id == lockInfo.OrderDetailId)
|
.ExecuteCommandAsync();
|
|
await CheckAndUpdateOrderStatus(orderNo);
|
|
// 查询任务表
|
var task = _taskRepository.QueryData(x => x.OrderNo == orderNo && x.PalletCode == palletCode).FirstOrDefault();
|
|
if (finalLockInfo.Id <= 0)
|
{
|
throw new Exception($"锁定信息ID无效: {finalLockInfo.Id},无法记录拣选历史");
|
}
|
|
// 记录拣选历史
|
var pickingHistory = new Dt_PickingRecord
|
{
|
FactoryArea = finalLockInfo.FactoryArea,
|
TaskNo = task?.TaskNum ?? 0,
|
LocationCode = task?.SourceAddress ?? "",
|
StockId = finalStockId,
|
OrderNo = orderNo,
|
OrderDetailId = finalLockInfo.OrderDetailId,
|
PalletCode = palletCode,
|
Barcode = finalBarcode,
|
MaterielCode = finalLockInfo.MaterielCode,
|
PickQuantity = actualPickedQty,
|
PickTime = DateTime.Now,
|
Operator = App.User.UserName,
|
OutStockLockId = finalLockInfo.Id
|
};
|
await Db.Insertable(pickingHistory).ExecuteCommandAsync();
|
|
_unitOfWorkManage.CommitTran();
|
|
if (splitResults.Any())
|
{
|
return WebResponseContent.Instance.OK("拣选确认成功,已自动拆包", new { SplitResults = splitResults });
|
}
|
|
return WebResponseContent.Instance.OK("拣选确认成功", new { SplitResults = new List<SplitResult>() });
|
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"ConfirmPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
|
}
|
}
|
|
public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
// 查找拣选记录
|
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 WebResponseContent.Instance.Error("未找到对应的拣选记录");
|
|
// 查找出库锁定信息
|
var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.Id == pickingRecord.OutStockLockId)
|
.FirstAsync();
|
|
if (lockInfo == null)
|
return WebResponseContent.Instance.Error("未找到对应的出库锁定信息");
|
|
// 检查是否可以取消
|
if (lockInfo.Status != (int)OutLockStockStatusEnum.拣选完成)
|
return WebResponseContent.Instance.Error("当前状态不允许取消分拣");
|
|
decimal cancelQty = pickingRecord.PickQuantity;
|
|
// 获取订单明细
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.FirstAsync(x => x.Id == pickingRecord.OrderDetailId);
|
|
if (orderDetail == null)
|
throw new Exception($"未找到订单明细,ID: {pickingRecord.OrderDetailId}");
|
|
// 关键修复:检查取消后数量不会为负数
|
decimal newOverOutQuantity = orderDetail.OverOutQuantity - cancelQty;
|
decimal newPickedQty = orderDetail.PickedQty - cancelQty;
|
|
if (newOverOutQuantity < 0 || newPickedQty < 0)
|
{
|
throw new Exception($"取消分拣将导致已出库数量或已拣选数量为负数");
|
}
|
|
// 处理取消逻辑
|
if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
|
{
|
await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
|
}
|
else
|
{
|
await HandleNormalBarcodeCancel(lockInfo, pickingRecord, cancelQty);
|
}
|
|
// 更新订单明细
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => new Dt_OutboundOrderDetail
|
{
|
PickedQty = newPickedQty,
|
OverOutQuantity = newOverOutQuantity
|
})
|
.Where(it => it.Id == pickingRecord.OrderDetailId)
|
.ExecuteCommandAsync();
|
|
// 删除拣选记录
|
await Db.Deleteable<Dt_PickingRecord>()
|
.Where(x => x.Id == pickingRecord.Id)
|
.ExecuteCommandAsync();
|
|
// 重新检查订单状态
|
await CheckAndUpdateOrderStatus(orderNo);
|
|
_unitOfWorkManage.CommitTran();
|
return WebResponseContent.Instance.OK($"取消分拣成功,恢复数量:{cancelQty}");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"CancelPicking失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"取消分拣失败:{ex.Message}");
|
}
|
}
|
|
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("未找到父锁定信息,无法取消拆包分拣");
|
}
|
|
// 恢复父锁定信息的分配数量
|
parentLockInfo.AssignQuantity += cancelQty;
|
await _outStockLockInfoService.Db.Updateable(parentLockInfo).ExecuteCommandAsync();
|
|
// 恢复库存
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(x => x.Barcode == parentLockInfo.CurrentBarcode && x.StockId == parentLockInfo.StockId)
|
.FirstAsync();
|
|
if (stockDetail != null)
|
{
|
stockDetail.StockQuantity += cancelQty;
|
stockDetail.OutboundQuantity += cancelQty;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
|
// 更新拆包记录状态
|
await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
|
.SetColumns(x => new Dt_SplitPackageRecord
|
{
|
Status = (int)SplitPackageStatusEnum.已撤销,
|
IsReverted = true
|
})
|
.Where(x => x.NewBarcode == lockInfo.CurrentBarcode && !x.IsReverted)
|
.ExecuteCommandAsync();
|
|
// 删除拆包产生的锁定信息
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(x => x.Id == lockInfo.Id)
|
.ExecuteCommandAsync();
|
}
|
|
private async Task HandleNormalBarcodeCancel(Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, decimal cancelQty)
|
{
|
// 恢复锁定信息
|
lockInfo.PickedQty -= cancelQty;
|
if (lockInfo.PickedQty < 0) lockInfo.PickedQty = 0;
|
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 恢复库存
|
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 += cancelQty;
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
}
|
}
|
|
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);
|
}
|
|
// 执行回库操作
|
await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, returnAnalysis);
|
|
//创建回库任务并处理ESS
|
await CreateReturnTaskAndHandleESS(orderNo, palletCode, task, returnAnalysis);
|
|
_unitOfWorkManage.CommitTran();
|
|
// 更新订单状态
|
await UpdateOrderStatusForReturn(orderNo);
|
|
return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{returnAnalysis.TotalReturnQty}");
|
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
_logger.LogError($"ReturnRemaining失败 - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}");
|
return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
|
}
|
}
|
|
#region 订单状态
|
|
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 => x.OrderStatus == newStatus)
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
|
_logger.LogInformation($"回库操作更新订单状态 - OrderNo: {orderNo}, 新状态: {newStatus}");
|
}
|
}
|
catch (Exception ex)
|
{
|
_logger.LogError($"UpdateOrderStatusForReturn失败 - OrderNo: {orderNo}, Error: {ex.Message}");
|
|
}
|
}
|
|
#endregion
|
|
#region Private Methods
|
|
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<ReturnAnalysisResult> AnalyzeReturnItems(string orderNo, string palletCode, int stockId)
|
{
|
var result = new ReturnAnalysisResult();
|
|
// 获取未分拣的出库锁定记录
|
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);
|
}
|
|
// 检查托盘上是否有其他库存货物
|
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);
|
}
|
|
// 检查拆包记录
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
|
.ToListAsync();
|
|
if (splitRecords.Any())
|
{
|
result.HasSplitRecords = true;
|
result.SplitRecords = splitRecords;
|
result.SplitReturnQty = await CalculateSplitReturnQuantity(splitRecords, stockId);
|
}
|
|
result.TotalReturnQty = result.RemainingLocksReturnQty + result.PalletStockReturnQty + result.SplitReturnQty;
|
result.HasItemsToReturn = result.TotalReturnQty > 0;
|
|
return result;
|
}
|
|
private async Task<decimal> CalculateSplitReturnQuantity(List<Dt_SplitPackageRecord> splitRecords, int stockId)
|
{
|
decimal totalQty = 0;
|
var processedBarcodes = new HashSet<string>();
|
|
foreach (var splitRecord in splitRecords)
|
{
|
// 检查原条码
|
if (!processedBarcodes.Contains(splitRecord.OriginalBarcode))
|
{
|
var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockId)
|
.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)
|
.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)
|
{
|
// 检查是否所有货物都已拣选完成
|
var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
|
.AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
|
|
if (allPicked)
|
{
|
return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
|
}
|
else
|
{
|
return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
|
}
|
}
|
|
private async Task ExecuteReturnOperations(string orderNo, string palletCode, Dt_StockInfo stockInfo,
|
Dt_Task task, ReturnAnalysisResult analysis)
|
{
|
// 情况1:处理未分拣的出库锁定记录
|
if (analysis.HasRemainingLocks)
|
{
|
await HandleRemainingLocksReturn(analysis.RemainingLocks, stockInfo.Id);
|
|
// 关键:更新订单明细的已拣选数量
|
await UpdateOrderDetailsOnReturn(analysis.RemainingLocks);
|
}
|
|
// 情况2:处理托盘上其他库存货物
|
if (analysis.HasPalletStockGoods)
|
{
|
await HandlePalletStockGoodsReturn(analysis.PalletStockGoods);
|
}
|
|
// 情况3:处理拆包记录
|
if (analysis.HasSplitRecords)
|
{
|
await HandleSplitRecordsReturn(analysis.SplitRecords, orderNo, palletCode);
|
}
|
|
// 更新库存主表状态
|
await UpdateStockInfoStatus(stockInfo);
|
}
|
|
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;
|
|
// 查找对应的库存明细
|
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)
|
{
|
foreach (var stockGood in palletStockGoods)
|
{
|
// 恢复库存状态
|
stockGood.OutboundQuantity = 0;
|
stockGood.Status = StockStatusEmun.入库完成.ObjToInt();
|
await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
|
}
|
}
|
|
private async Task HandleSplitRecordsReturn(List<Dt_SplitPackageRecord> splitRecords, string orderNo, string palletCode)
|
{
|
// 更新拆包记录状态
|
await _splitPackageService.Db.Updateable<Dt_SplitPackageRecord>()
|
.SetColumns(x => new Dt_SplitPackageRecord
|
{
|
Status = (int)SplitPackageStatusEnum.已回库
|
})
|
.Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && !x.IsReverted)
|
.ExecuteCommandAsync();
|
}
|
|
private async Task UpdateStockInfoStatus(Dt_StockInfo stockInfo)
|
{
|
// 更新库存主表状态
|
stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
|
await _stockInfoService.Db.Updateable(stockInfo).ExecuteCommandAsync();
|
}
|
|
private async Task CreateReturnTaskAndHandleESS(string orderNo, string palletCode, Dt_Task originalTask, ReturnAnalysisResult analysis)
|
{
|
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,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[originalTask.TargetAddress],
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = TaskTypeEnum.InPick.ObjToInt(),
|
PalletType = originalTask.PalletType,
|
WarehouseId = originalTask.WarehouseId,
|
|
};
|
|
// 保存回库任务
|
await _taskRepository.Db.Insertable(returnTask).ExecuteCommandAsync();
|
|
// 删除原始出库任务
|
_taskRepository.Db.Deleteable(originalTask);
|
|
// 给 ESS 发送流动信号和创建任务
|
await SendESSCommands(palletCode, originalTask, returnTask);
|
}
|
|
private async Task<int> GenerateTaskNumber()
|
{
|
return await _dailySequenceService.GetNextSequenceAsync();
|
}
|
|
private async Task SendESSCommands(string palletCode, Dt_Task originalTask, Dt_Task returnTask)
|
{
|
try
|
{
|
// 1. 发送流动信号
|
var moveResult = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
|
{
|
slotCode = movestations[originalTask.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(originalTask.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
|
|
|
|
/// <summary>
|
/// 回库操作
|
/// </summary>
|
|
//public async Task<WebResponseContent> ReturnRemaining(string orderNo, string palletCode, string reason)
|
//{
|
// try
|
// {
|
// _unitOfWorkManage.BeginTran();
|
|
// // 获取所有未分拣的出库锁定记录,包括拆包产生的记录
|
// var remainingLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
// .Where(it => it.OrderNo == orderNo && it.Status == (int)OutLockStockStatusEnum.出库中)
|
// .ToListAsync();
|
|
// var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
|
|
// var tasks = new List<Dt_Task>();
|
|
// // 查询任务表
|
// var task = remainingLocks.Any()
|
// ? _taskRepository.QueryData(x => x.TaskNum == remainingLocks.First().TaskNum).FirstOrDefault()
|
// : _taskRepository.QueryData(x => x.PalletCode == palletCode).FirstOrDefault();
|
|
// if (task == null)
|
// {
|
// return WebResponseContent.Instance.Error("未找到对应的任务信息");
|
// }
|
|
// // 检查托盘上是否有其他非出库货物(库存货物)
|
// var palletStockGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
// .Where(it => it.StockId == stockinfo.Id &&
|
// (it.Status == StockStatusEmun.入库确认.ObjToInt() ||
|
// it.Status == StockStatusEmun.入库完成.ObjToInt() ||
|
// it.Status == StockStatusEmun.出库锁定.ObjToInt()))
|
// .Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity) // 未完全出库的
|
// .ToListAsync();
|
|
// // 检查拆包记录,找出需要回库的条码
|
// var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
// .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
|
// .ToListAsync();
|
|
// // 计算需要回库的拆包条码
|
// var splitBarcodesToReturn = new List<string>();
|
// foreach (var splitRecord in splitRecords)
|
// {
|
// // 检查原条码是否还有库存需要回库
|
// var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
// .Where(it => it.Barcode == splitRecord.OriginalBarcode && it.StockId == stockinfo.Id)
|
// .FirstAsync();
|
|
// if (originalStock != null && originalStock.StockQuantity > 0)
|
// {
|
// splitBarcodesToReturn.Add(splitRecord.OriginalBarcode);
|
// }
|
|
// // 检查新条码是否还有库存需要回库
|
// var newStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
// .Where(it => it.Barcode == splitRecord.NewBarcode && it.StockId == stockinfo.Id)
|
// .FirstAsync();
|
|
// if (newStock != null && newStock.StockQuantity > 0)
|
// {
|
// splitBarcodesToReturn.Add(splitRecord.NewBarcode);
|
// }
|
// }
|
|
// // 如果没有需要回库的货物(既无未分拣出库货物,也无其他库存货物,也无拆包剩余货物)
|
// if (!remainingLocks.Any() && !palletStockGoods.Any() && !splitBarcodesToReturn.Any())
|
// {
|
// // 检查是否所有货物都已拣选完成
|
// var allPicked = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
// .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode)
|
// .AnyAsync(it => it.Status == (int)OutLockStockStatusEnum.拣选完成);
|
|
// if (allPicked)
|
// {
|
// return WebResponseContent.Instance.OK("所有货物已拣选完成,托盘为空");
|
// }
|
// else
|
// {
|
// return WebResponseContent.Instance.Error("没有需要回库的剩余货物");
|
// }
|
// }
|
|
// var firstlocation = _locationInfoService.Db.Queryable<Dt_LocationInfo>().First(x => x.LocationCode == task.SourceAddress);
|
// decimal totalReturnQty = 0;
|
|
// // 情况1:处理未分拣的出库锁定记录
|
// if (remainingLocks.Any(x => x.PalletCode == palletCode))
|
// {
|
// var palletLocks = remainingLocks.Where(x => x.PalletCode == palletCode).ToList();
|
// totalReturnQty = palletLocks.Sum(x => x.AssignQuantity - x.PickedQty);
|
|
// if (totalReturnQty > 0)
|
// {
|
// // 分配新货位
|
// var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
|
|
// // 更新出库锁定记录状态
|
// var lockIds = palletLocks.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 palletLocks)
|
// {
|
// decimal returnQty = lockInfo.AssignQuantity - lockInfo.PickedQty;
|
|
// // 检查库存记录是否存在
|
// var existingStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
// .Where(it => it.Barcode == lockInfo.CurrentBarcode && it.StockId == lockInfo.StockId)
|
// .FirstAsync();
|
|
// if (existingStock != null)
|
// {
|
// // 库存记录存在,恢复锁定数量
|
// existingStock.OutboundQuantity = 0;
|
|
// await _stockInfoDetailService.Db.Updateable(existingStock).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();
|
// }
|
// }
|
|
// // 创建回库任务
|
// CreateReturnTask(tasks, task, palletCode, newLocation);
|
// }
|
// }
|
|
// // 情况2:处理拆包剩余的库存货物
|
// if (splitBarcodesToReturn.Any())
|
// {
|
// decimal splitReturnQty = 0;
|
|
// foreach (var barcode in splitBarcodesToReturn)
|
// {
|
// var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
// .Where(it => it.Barcode == barcode && it.StockId == stockinfo.Id)
|
// .FirstAsync();
|
|
// if (stockDetail != null && stockDetail.StockQuantity > 0)
|
// {
|
// splitReturnQty += stockDetail.StockQuantity;
|
|
// // 恢复库存状态为入库完成
|
// stockDetail.OutboundQuantity = 0;
|
|
// await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
// }
|
// }
|
|
// totalReturnQty += splitReturnQty;
|
|
// // 如果没有创建任务,创建回库任务
|
// if (!tasks.Any())
|
// {
|
// var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
|
// CreateReturnTask(tasks, task, palletCode, newLocation);
|
// }
|
// }
|
|
// // 情况3:出库货物已分拣完,但托盘上还有其他库存货物需要回库
|
// if (palletStockGoods.Any() && !remainingLocks.Any(x => x.PalletCode == palletCode))
|
// {
|
// decimal otherReturnQty = palletStockGoods.Sum(x => x.StockQuantity - x.OutboundQuantity);
|
// totalReturnQty += otherReturnQty;
|
// // 更新这些库存货物的状态
|
// foreach (var stockGood in palletStockGoods)
|
// {
|
// stockGood.OutboundQuantity = 0;
|
|
// await _stockInfoDetailService.Db.Updateable(stockGood).ExecuteCommandAsync();
|
// }
|
// // 如果没有创建任务,创建回库任务
|
// if (!tasks.Any())
|
// {
|
// var newLocation = _locationInfoService.AssignLocation(firstlocation.LocationType);
|
// CreateReturnTask(tasks, task, palletCode, newLocation);
|
// }
|
|
|
// }
|
|
// var allSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
// .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && !it.IsReverted)
|
// .ToListAsync();
|
|
// foreach (var record in allSplitRecords)
|
// {
|
// record.Status = (int)SplitPackageStatusEnum.已回库;
|
// await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
|
// }
|
// // 保存任务 给ESS下发任务
|
// if (tasks.Any())
|
// {
|
// try
|
// {
|
// await _taskRepository.Db.Insertable(tasks).ExecuteCommandAsync();
|
// var targetAddress = task.TargetAddress;
|
// _taskRepository.DeleteData(task);
|
|
// // 给 ESS 流动信号和创建任务
|
// try
|
// {
|
// var result = await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
|
// {
|
// slotCode = movestations[targetAddress],
|
// containerCode = palletCode
|
// });
|
|
// if (result)
|
// {
|
// TaskModel esstask = new TaskModel()
|
// {
|
// taskType = "putaway",
|
// taskGroupCode = "",
|
// groupPriority = 0,
|
// tasks = new List<TasksType>
|
// {
|
// new()
|
// {
|
// taskCode = tasks.First().TaskNum.ToString(),
|
// taskPriority = 0,
|
// taskDescribe = new TaskDescribeType {
|
// containerCode = palletCode,
|
// containerType = "CT_KUBOT_STANDARD",
|
// fromLocationCode = stations.GetValueOrDefault(targetAddress) ?? "",
|
// toStationCode = "",
|
// toLocationCode = tasks.First().TargetAddress,
|
// deadline = 0, storageTag = ""
|
// }
|
// }
|
// }
|
// };
|
|
// var resulttask = await _eSSApiService.CreateTaskAsync(esstask);
|
// _logger.LogInformation("ReturnRemaining 创建任务返回: " + resulttask);
|
// }
|
// }
|
// catch (Exception ex)
|
// {
|
// _logger.LogInformation("ReturnRemaining 创建任务返回 catch err: " + ex.Message);
|
// }
|
|
// _unitOfWorkManage.CommitTran();
|
// return WebResponseContent.Instance.OK($"回库操作成功,共回库数量:{totalReturnQty}");
|
// }
|
// catch (Exception ex)
|
// {
|
// _unitOfWorkManage.RollbackTran();
|
// return WebResponseContent.Instance.Error($"创建回库任务失败: {ex.Message}");
|
// }
|
// }
|
|
// _unitOfWorkManage.RollbackTran();
|
// return WebResponseContent.Instance.Error("未创建任何回库任务");
|
// }
|
// catch (Exception ex)
|
// {
|
// _unitOfWorkManage.RollbackTran();
|
// return WebResponseContent.Instance.Error($"回库操作失败: {ex.Message}");
|
// }
|
//}
|
|
|
/// <summary>
|
/// 创建回库任务
|
/// </summary>
|
private void CreateReturnTask(List<Dt_Task> tasks, Dt_Task originalTask, string palletCode, Dt_LocationInfo newLocation)
|
{
|
Dt_Task newTask = new()
|
{
|
CurrentAddress = stations[originalTask.TargetAddress],
|
Grade = 0,
|
PalletCode = palletCode,
|
NextAddress = "",
|
OrderNo = originalTask.OrderNo,
|
Roadway = newLocation.RoadwayNo,
|
SourceAddress = stations[originalTask.TargetAddress],
|
TargetAddress = newLocation.LocationCode,
|
TaskStatus = TaskStatusEnum.New.ObjToInt(),
|
TaskType = TaskTypeEnum.InPick.ObjToInt(),
|
PalletType = originalTask.PalletType,
|
WarehouseId = originalTask.WarehouseId,
|
|
};
|
tasks.Add(newTask);
|
}
|
|
/// <summary>
|
/// 检查托盘是否需要回库的辅助方法
|
/// </summary>
|
public async Task<bool> CheckPalletNeedReturn(string orderNo, string palletCode)
|
{
|
// 1. 检查是否有未分拣的出库记录
|
var hasUnpickedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == 1)
|
.AnyAsync();
|
|
if (hasUnpickedLocks)
|
return true;
|
|
// 2. 检查出库是否已完成但托盘还有库存货物
|
var outboundFinished = !await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.PalletCode == palletCode && it.Status == 1)
|
.AnyAsync();
|
|
var stockinfo = _stockInfoService.Db.Queryable<Dt_StockInfo>().First(x => x.PalletCode == palletCode);
|
|
|
var hasRemainingGoods = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.StockId == stockinfo.Id && it.Status == StockStatusEmun.入库确认.ObjToInt())
|
.Where(it => it.OutboundQuantity == 0 || it.OutboundQuantity < it.StockQuantity)
|
.AnyAsync();
|
|
return outboundFinished && hasRemainingGoods;
|
}
|
|
|
/// <summary>
|
/// 获取拆包链(从当前条码追溯到原始条码)
|
/// </summary>
|
// 在 GetSplitChain 方法中添加更严格的验证
|
private async Task<List<SplitChainItem>> GetSplitChain(string currentBarcode)
|
{
|
var chain = new List<SplitChainItem>();
|
var visited = new HashSet<string>();
|
|
string current = currentBarcode;
|
int maxDepth = 10; // 防止无限循环
|
|
while (!string.IsNullOrEmpty(current) && maxDepth > 0)
|
{
|
maxDepth--;
|
|
if (visited.Contains(current))
|
{
|
_logger.LogWarning($"检测到循环引用在拆包链中: {current}");
|
break;
|
}
|
|
visited.Add(current);
|
|
var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(it => it.NewBarcode == current && !it.IsReverted)
|
.FirstAsync();
|
|
if (splitRecord == null)
|
break;
|
|
// 验证拆包记录的完整性
|
if (string.IsNullOrEmpty(splitRecord.OriginalBarcode))
|
{
|
_logger.LogError($"拆包记录 {splitRecord.Id} 缺少原始条码");
|
break;
|
}
|
|
var item = new SplitChainItem
|
{
|
SplitRecord = splitRecord,
|
OriginalBarcode = splitRecord.OriginalBarcode,
|
NewBarcode = splitRecord.NewBarcode,
|
SplitQuantity = splitRecord.SplitQty
|
};
|
|
chain.Add(item);
|
|
current = splitRecord.OriginalBarcode;
|
}
|
|
if (maxDepth <= 0)
|
{
|
_logger.LogWarning($"拆包链追溯达到最大深度: {currentBarcode}");
|
}
|
|
chain.Reverse();
|
return chain;
|
}
|
/// <summary>
|
/// 处理拆包链的取消分拣
|
/// </summary>
|
private async Task HandleSplitChainCancel(string orderNo, string palletCode, string barcode,
|
decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord, List<SplitChainItem> splitChain)
|
{
|
if (!splitChain.Any())
|
return;
|
|
// 找到原始条码(链的第一个)
|
var originalSplitItem = splitChain.First();
|
var originalBarcode = originalSplitItem.OriginalBarcode;
|
|
// 查找原始条码的锁定信息和库存
|
var originalLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(it => it.CurrentBarcode == originalBarcode && it.Status == (int)OutLockStockStatusEnum.出库中)
|
.FirstAsync();
|
|
var originalStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == originalBarcode && it.StockId == originalLockInfo.StockId)
|
.FirstAsync();
|
|
if (originalLockInfo == null || originalStockDetail == null)
|
throw new Exception("未找到原始条码的锁定信息或库存信息");
|
|
// 恢复原始条码库存(将取消的数量加回去)
|
originalStockDetail.StockQuantity += cancelQty;
|
originalStockDetail.OutboundQuantity += cancelQty;
|
await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync();
|
|
// 恢复原始条码锁定信息
|
originalLockInfo.AssignQuantity += cancelQty;
|
await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync();
|
|
// 删除拆包链中所有新条码的锁定信息和库存记录
|
var allNewBarcodes = splitChain.Select(x => x.NewBarcode).ToList();
|
|
// 删除锁定信息
|
await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
|
.Where(it => allNewBarcodes.Contains(it.CurrentBarcode))
|
.ExecuteCommandAsync();
|
|
// 删除库存记录(只删除拆包产生的新条码库存,保留原始条码)
|
await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>()
|
.Where(it => allNewBarcodes.Contains(it.Barcode) && it.Barcode != originalBarcode)
|
.ExecuteCommandAsync();
|
|
// 更新拆包链中所有拆包记录状态为已拆包
|
foreach (var chainItem in splitChain)
|
{
|
chainItem.SplitRecord.Status = (int)SplitPackageStatusEnum.已拆包;
|
await _splitPackageService.Db.Updateable(chainItem.SplitRecord).ExecuteCommandAsync();
|
}
|
|
// 恢复订单明细拣选数量(使用原始锁定信息的订单明细ID)
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
|
.Where(it => it.Id == originalLockInfo.OrderDetailId)
|
.ExecuteCommandAsync();
|
|
// 恢复订单状态
|
await CheckAndRevertOrderStatus(orderNo);
|
|
// 删除拣选记录
|
await Db.Deleteable<Dt_PickingRecord>()
|
.Where(it => it.Id == pickingRecord.Id)
|
.ExecuteCommandAsync();
|
|
//// 记录取消操作历史
|
//await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
|
// lockInfo.MaterielCode, "取消拆包链分拣");
|
}
|
|
/// <summary>
|
/// 处理普通条码的取消分拣
|
/// </summary>
|
private async Task HandleNormalBarcodeCancel(string orderNo, string palletCode, string barcode,
|
decimal cancelQty, Dt_OutStockLockInfo lockInfo, Dt_PickingRecord pickingRecord)
|
{
|
// 1. 查找库存信息
|
var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
|
.Where(it => it.Barcode == barcode && it.StockId == lockInfo.StockId)
|
.FirstAsync();
|
|
if (stockDetail == null)
|
throw new Exception("未找到对应的库存信息");
|
|
// 2. 恢复库存数量
|
if (stockDetail.StockQuantity == 0)
|
{
|
// 整包出库的情况
|
stockDetail.StockQuantity = cancelQty;
|
stockDetail.OutboundQuantity = cancelQty;
|
}
|
else
|
{
|
// 部分出库的情况
|
stockDetail.StockQuantity += cancelQty;
|
stockDetail.OutboundQuantity += cancelQty;
|
}
|
await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync();
|
|
// 3. 恢复锁定信息状态
|
lockInfo.AssignQuantity += cancelQty;
|
lockInfo.PickedQty -= cancelQty;
|
|
if (lockInfo.PickedQty == 0)
|
{
|
lockInfo.Status = (int)OutLockStockStatusEnum.出库中;
|
}
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
// 4. 处理相关的拆包记录状态恢复
|
var relatedSplitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(it => it.OriginalBarcode == barcode &&
|
it.Status == (int)SplitPackageStatusEnum.已拣选)
|
.ToListAsync();
|
|
foreach (var record in relatedSplitRecords)
|
{
|
record.Status = (int)SplitPackageStatusEnum.已拆包;
|
await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
|
}
|
|
// 5. 恢复订单明细的拣选数量
|
await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
|
.SetColumns(it => it.PickedQty == it.PickedQty - cancelQty)
|
.Where(it => it.Id == lockInfo.OrderDetailId)
|
.ExecuteCommandAsync();
|
|
// 6. 恢复订单状态
|
await CheckAndRevertOrderStatus(orderNo);
|
|
// 7. 删除拣选记录
|
await Db.Deleteable<Dt_PickingRecord>().Where(it => it.Id == pickingRecord.Id).ExecuteCommandAsync();
|
|
//// 8. 记录取消操作历史
|
//await RecordCancelHistory(orderNo, palletCode, barcode, cancelQty, pickingRecord.Id,
|
// lockInfo.MaterielCode, "取消分拣");
|
}
|
|
/// <summary>
|
/// 检查并恢复订单状态
|
/// </summary>
|
private async Task CheckAndRevertOrderStatus(string orderNo)
|
{
|
var order = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
|
.Where(x => x.OrderNo == orderNo)
|
.FirstAsync();
|
|
if (order != null && order.OrderStatus == OutOrderStatusEnum.出库完成.ObjToInt())
|
{
|
await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
|
.SetColumns(x => x.OrderStatus == OutOrderStatusEnum.出库中.ObjToInt())
|
.Where(x => x.OrderNo == orderNo)
|
.ExecuteCommandAsync();
|
}
|
}
|
|
|
|
|
// 获取未拣选列表
|
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;
|
}
|
/// <summary>
|
/// 获取拣选历史
|
/// </summary>
|
public async Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId)
|
{
|
// 通过出库单ID查询相关的拣选历史
|
// 注意:Dt_PickingRecord 中没有直接存储OrderId,需要通过出库单明细关联
|
var detailIds = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.Where(d => d.OrderId == orderId)
|
.Select(d => d.Id)
|
.ToListAsync();
|
|
return await Db.Queryable<Dt_PickingRecord>()
|
.Where(p => detailIds.Contains(p.OrderDetailId))
|
.OrderByDescending(p => p.PickTime)
|
.ToListAsync();
|
}
|
|
|
/// <summary>
|
/// 获取托盘的出库状态信息
|
/// </summary>
|
public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
|
{
|
// 获取托盘的锁定信息
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.PalletCode == palletCode)
|
.ToListAsync();
|
|
// 获取托盘库存信息
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Includes(x => x.Details)
|
.Where(x => x.PalletCode == palletCode)
|
.FirstAsync();
|
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error("未找到托盘信息");
|
|
// 计算各种数量
|
var totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
|
var totalOutboundQuantity = stockInfo.Details.Sum(x => x.OutboundQuantity);
|
var totalLockedQuantity = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.出库中)
|
.Sum(x => x.AssignQuantity - x.PickedQty);
|
var totalPickedQuantity = lockInfos.Sum(x => x.PickedQty);
|
|
var result = new
|
{
|
PalletCode = palletCode,
|
LocationCode = stockInfo.LocationCode,
|
StockStatus = stockInfo.StockStatus,
|
TotalStockQuantity = totalStockQuantity,
|
TotalOutboundQuantity = totalOutboundQuantity,
|
TotalLockedQuantity = totalLockedQuantity,
|
TotalPickedQuantity = totalPickedQuantity,
|
AvailableQuantity = totalStockQuantity - totalOutboundQuantity,
|
LockInfos = lockInfos.Select(x => new
|
{
|
x.Id,
|
x.MaterielCode,
|
x.OrderDetailId,
|
x.AssignQuantity,
|
x.PickedQty,
|
x.Status,
|
x.CurrentBarcode,
|
x.IsSplitted
|
}).ToList(),
|
StockDetails = stockInfo.Details.Select(x => new
|
{
|
x.Barcode,
|
x.MaterielCode,
|
StockQuantity = x.StockQuantity,
|
OutboundQuantity = x.OutboundQuantity,
|
AvailableQuantity = x.StockQuantity - x.OutboundQuantity
|
}).ToList()
|
};
|
|
return WebResponseContent.Instance.OK(null, result);
|
}
|
|
/// <summary>
|
/// 直接出库 - 整个托盘出库,清空库存
|
/// </summary>
|
public async Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request)
|
{
|
try
|
{
|
_unitOfWorkManage.BeginTran();
|
|
var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
|
.Includes(x => x.Details)
|
.Where(x => x.PalletCode == request.PalletCode).FirstAsync();
|
|
if (stockInfo == null)
|
return WebResponseContent.Instance.Error("未找到托盘库存信息");
|
|
|
var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
|
.Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode)
|
.ToListAsync();
|
|
|
foreach (var lockInfo in lockInfos)
|
{
|
if (lockInfo.Status == (int)OutLockStockStatusEnum.出库中)
|
{
|
lockInfo.PickedQty = lockInfo.AssignQuantity;
|
}
|
lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
|
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
|
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
|
.Where(x => x.Id == lockInfo.OrderDetailId)
|
.FirstAsync();
|
if (orderDetail != null)
|
{
|
orderDetail.OverOutQuantity += lockInfo.PickedQty;
|
orderDetail.LockQuantity -= lockInfo.PickedQty;
|
orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
|
orderDetail.LockQuantity = 0;
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
}
|
}
|
var groupDetails = lockInfos.GroupBy(x => x.OrderDetailId).Select(x => new
|
{
|
OrderDetailId = x.Key,
|
TotalQuantity = x.Sum(o => o.PickedQty)
|
}).ToList();
|
foreach (var item in groupDetails)
|
{
|
var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>().Where(x => x.Id == item.OrderDetailId).FirstAsync();
|
if (orderDetail != null)
|
{
|
orderDetail.OverOutQuantity = item.TotalQuantity;
|
orderDetail.LockQuantity = 0;
|
orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
|
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
|
}
|
}
|
|
await CheckAndUpdateOrderStatus(request.OrderNo);
|
|
var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
|
var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
|
.Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
|
x.Status == (int)SplitPackageStatusEnum.已拆包)
|
.ToListAsync();
|
|
foreach (var record in splitRecords)
|
{
|
record.Status = (int)SplitPackageStatusEnum.已拣选;
|
await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
|
}
|
|
|
var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
|
.Where(x => x.LocationCode == stockInfo.LocationCode)
|
.FirstAsync();
|
if (location != null)
|
{
|
location.LocationStatus = (int)LocationStatusEnum.Free;
|
await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
|
}
|
|
foreach (var detail in stockInfo.Details)
|
{
|
await _stockInfoDetailService.Db.Deleteable(detail).ExecuteCommandAsync();
|
}
|
await _stockInfoService.Db.Deleteable(stockInfo).ExecuteCommandAsync();
|
|
|
|
_unitOfWorkManage.CommitTran();
|
return WebResponseContent.Instance.OK("直接出库成功");
|
}
|
catch (Exception ex)
|
{
|
_unitOfWorkManage.RollbackTran();
|
return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}");
|
}
|
}
|
}
|
|
|
#region 返回
|
/// <summary>
|
/// 拆包链项
|
/// </summary>
|
public class SplitChainItem
|
{
|
public Dt_SplitPackageRecord SplitRecord { get; set; }
|
public string OriginalBarcode { get; set; }
|
public string NewBarcode { get; set; }
|
public decimal SplitQuantity { get; set; }
|
}
|
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>();
|
}
|
|
#endregion
|
|
}
|