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_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_OutboundService
{
///
///
///
public class OutboundPickingService : ServiceBase>, IOutboundPickingService
{
private readonly IUnitOfWorkManage _unitOfWorkManage;
public IRepository 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 ISplitPackageService _splitPackageService;
public OutboundPickingService(IRepository BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService) : base(BaseDal)
{
_unitOfWorkManage = unitOfWorkManage;
_stockInfoService = stockInfoService;
_stockService = stockService;
_outStockLockInfoService = outStockLockInfoService;
_stockInfoDetailService = stockInfoDetailService;
_locationInfoService = locationInfoService;
_outboundOrderDetailService = outboundOrderDetailService;
_splitPackageService = splitPackageService;
}
///
/// 扫码拣选确认 - 简化版本
/// 只处理实际拣选的库存扣减
///
public async Task ConfirmPicking(PickingConfirmRequest request)
{
try
{
_unitOfWorkManage.BeginTran();
// 1. 验证条码有效性
var stockDetail = await _stockInfoDetailService.Db.Queryable()
.Where(x => x.Barcode == request.Barcode && x.MaterielCode == request.MaterielCode)
.FirstAsync();
if (stockDetail == null)
return WebResponseContent.Instance.Error("无效的条码或物料编码");
// 2. 检查库存可用数量
decimal availableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
if (request.PickQuantity > availableQuantity)
return WebResponseContent.Instance.Error($"拣选数量超过可用库存,可用数量:{availableQuantity}");
// 3. 查找相关的出库锁定信息(支持拆包后的新条码)
var lockInfo = await FindLockInfoByBarcode(request.OrderDetailId, request.Barcode, request.MaterielCode);
if (lockInfo == null)
return WebResponseContent.Instance.Error("未找到相关的出库锁定信息");
// 4. 检查锁定数量
decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
if (request.PickQuantity > remainingLockQuantity)
return WebResponseContent.Instance.Error($"拣选数量超过锁定数量,剩余可拣选:{remainingLockQuantity}");
// 5. 更新锁定信息的已拣选数量
lockInfo.PickedQty += request.PickQuantity;
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
// 6. 更新库存出库数量 - 实际减少库存
stockDetail.OutboundQuantity += request.PickQuantity;
await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
// 7. 更新出库单明细
var orderDetail = await _outboundOrderDetailService.Db.Queryable()
.Where(x => x.Id == request.OrderDetailId)
.FirstAsync();
orderDetail.OverOutQuantity += request.PickQuantity;
orderDetail.LockQuantity -= request.PickQuantity;
// 检查是否完成出库
if (Math.Abs(orderDetail.OverOutQuantity - orderDetail.OrderQuantity) < 0.001m)
{
orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
orderDetail.LockQuantity = 0;
// 更新相关的锁定信息状态为已出库
var relatedLockInfos = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderDetailId == request.OrderDetailId &&
x.Status == (int)OutLockStockStatusEnum.出库中)
.ToListAsync();
foreach (var relatedLock in relatedLockInfos)
{
relatedLock.Status = (int)OutLockStockStatusEnum.已出库;
}
await _outStockLockInfoService.Db.Updateable(relatedLockInfos).ExecuteCommandAsync();
}
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
// 8. 记录拣选历史
var pickHistory = new Dt_PickingRecord
{
OrderDetailId = request.OrderDetailId,
Barcode = request.Barcode,
PickQuantity = request.PickQuantity,
PickTime = DateTime.Now,
LocationCode = request.LocationCode,
StockId = stockDetail.StockId
};
await Db.Insertable(pickHistory).ExecuteCommandAsync();
_unitOfWorkManage.CommitTran();
return WebResponseContent.Instance.OK("拣选确认成功");
}
catch (Exception ex)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error($"拣选确认失败: {ex.Message}");
}
}
///
/// 根据条码查找锁定信息
///
private async Task FindLockInfoByBarcode(int orderDetailId, string barcode, string materielCode)
{
return await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderDetailId == orderDetailId &&
x.MaterielCode == materielCode &&
x.CurrentBarcode == barcode &&
x.Status == (int)OutLockStockStatusEnum.出库中 &&
x.AssignQuantity > x.PickedQty)
.FirstAsync();
}
///
/// 获取拣选历史
///
public async Task> GetPickingHistory(int orderId)
{
// 通过出库单ID查询相关的拣选历史
// 注意:Dt_PickingRecord 中没有直接存储OrderId,需要通过出库单明细关联
var detailIds = await _outboundOrderDetailService.Db.Queryable()
.Where(d => d.OrderId == orderId)
.Select(d => d.Id)
.ToListAsync();
return await Db.Queryable()
.Where(p => detailIds.Contains(p.OrderDetailId))
.OrderByDescending(p => p.PickTime)
.ToListAsync();
}
///
/// 撤销拣选
///
public async Task CancelPicking(CancelPickingRequest request)
{
// 实现撤销拣选的逻辑,需要:
// 1. 恢复库存出库数量
// 2. 恢复锁定信息的已拣选数量
// 3. 恢复出库单明细的已出数量和锁定数量
// 4. 删除或标记拣选历史记录
// 注意:这里需要事务处理
try
{
_unitOfWorkManage.BeginTran();
var pickHistory = await Db.Queryable()
.Where(x => x.Id == request.PickingHistoryId)
.FirstAsync();
if (pickHistory == null)
return WebResponseContent.Instance.Error("未找到拣选记录");
// 恢复库存
var stockDetail = await _stockInfoService.Db.Queryable()
.Where(x => x.Barcode == pickHistory.Barcode && x.StockId == pickHistory.StockId)
.FirstAsync();
if (stockDetail != null)
{
stockDetail.OutboundQuantity -= pickHistory.PickQuantity;
await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
}
// 恢复锁定信息
var lockInfo = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.OrderDetailId == pickHistory.OrderDetailId && x.StockId == pickHistory.StockId)
.FirstAsync();
lockInfo.PickedQty -= pickHistory.PickQuantity;
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
// 恢复出库单明细
var orderDetail = await _outboundOrderDetailService.Db.Queryable()
.Where(x => x.Id == pickHistory.OrderDetailId)
.FirstAsync();
orderDetail.OverOutQuantity -= pickHistory.PickQuantity;
orderDetail.LockQuantity += pickHistory.PickQuantity;
if (orderDetail.OverOutQuantity < orderDetail.OrderQuantity)
{
orderDetail.OrderDetailStatus = orderDetail.LockQuantity > 0 ?
(int)OrderDetailStatusEnum.Outbound : (int)OrderDetailStatusEnum.AssignOverPartial;
}
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
// 删除拣选历史记录
await Db.Deleteable().Where(x => x.Id == request.PickingHistoryId).ExecuteCommandAsync();
_unitOfWorkManage.CommitTran();
return WebResponseContent.Instance.OK("撤销成功");
}
catch (Exception ex)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error($"撤销失败: {ex.Message}");
}
}
///
/// 获取托盘的出库状态信息
///
public async Task GetPalletOutboundStatus(string palletCode)
{
// 获取托盘的锁定信息
var lockInfos = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.PalletCode == palletCode)
.ToListAsync();
// 获取托盘库存信息
var stockInfo = await _stockInfoService.Db.Queryable()
.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);
}
///
/// 直接出库 - 整个托盘出库,清空库存
///
public async Task DirectOutbound(DirectOutboundRequest request)
{
try
{
_unitOfWorkManage.BeginTran();
// 1. 获取托盘库存信息
var stockInfo = await _stockInfoService.Db.Queryable()
.Includes(x => x.Details)
.Where(x => x.PalletCode == request.PalletCode)
.FirstAsync();
if (stockInfo == null)
return WebResponseContent.Instance.Error("未找到托盘库存信息");
// 2. 获取相关的出库锁定信息
var lockInfos = await _outStockLockInfoService.Db.Queryable()
.Where(x => x.PalletCode == request.PalletCode &&
x.Status == (int)OutLockStockStatusEnum.出库中)
.ToListAsync();
// 3. 整个托盘出库 - 设置出库数量等于库存数量
foreach (var detail in stockInfo.Details)
{
decimal outboundQuantity = detail.StockQuantity - detail.OutboundQuantity;
detail.OutboundQuantity = detail.StockQuantity; // 全部出库
await _stockInfoDetailService.Db.Updateable(detail).ExecuteCommandAsync();
}
// 4. 更新出库锁定信息
foreach (var lockInfo in lockInfos)
{
decimal unpicked = lockInfo.AssignQuantity - lockInfo.PickedQty;
lockInfo.PickedQty += unpicked; // 标记为全部拣选
lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
// 更新出库单明细
var orderDetail = await _outboundOrderDetailService.Db.Queryable()
.Where(x => x.Id == lockInfo.OrderDetailId)
.FirstAsync();
orderDetail.OverOutQuantity += unpicked;
orderDetail.LockQuantity -= unpicked;
orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
orderDetail.LockQuantity = 0;
await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
}
// 5. 更新拆包记录状态
var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
var splitRecords = await _splitPackageService.Db.Queryable()
.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();
}
// 6. 清空货位
var location = await _locationInfoService.Db.Queryable()
.Where(x => x.LocationCode == stockInfo.LocationCode)
.FirstAsync();
if (location != null)
{
location.LocationStatus = (int)LocationStatusEnum.Free;
await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
}
_unitOfWorkManage.CommitTran();
return WebResponseContent.Instance.OK("直接出库成功");
}
catch (Exception ex)
{
_unitOfWorkManage.RollbackTran();
return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}");
}
}
}
}