using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Org.BouncyCastle.Asn1.Ocsp; using SqlSugar.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Common.StockEnum; using WIDESEA_Core; using WIDESEA_Core.BaseRepository; using WIDESEA_Core.BaseServices; using WIDESEA_DTO.Basic; using WIDESEA_DTO.Outbound; using WIDESEA_IBasicService; using WIDESEA_IOutboundService; using WIDESEA_IStockService; using WIDESEA_Model.Models; namespace WIDESEA_OutboundService { internal class SplitPackageService : ServiceBase>, ISplitPackageService { private readonly IUnitOfWorkManage _unitOfWorkManage; public IRepository Repository => BaseDal; private readonly IStockInfoService _stockInfoService; private readonly IStockInfoDetailService _stockInfoDetailService; private readonly IOutStockLockInfoService _outStockLockInfoService; private readonly IDailySequenceService _dailySequenceService; private readonly IInvokeMESService _invokeMESService; private readonly ILogger _logger; public SplitPackageService(IRepository BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, IDailySequenceService dailySequenceService, IInvokeMESService invokeMESService, ILogger logger) : base(BaseDal) { _unitOfWorkManage = unitOfWorkManage; _stockInfoService = stockInfoService; _outStockLockInfoService = outStockLockInfoService; _stockInfoDetailService = stockInfoDetailService; _dailySequenceService = dailySequenceService; _invokeMESService = invokeMESService; _logger = logger; } /// /// 拆包拆箱操作 /// //public async Task SplitPackage(SplitPackageDto request) //{ // try // { // _unitOfWorkManage.BeginTran(); // // 1. 验证出库锁定信息 // var lockInfo = await _outStockLockInfoService.Db.Queryable() // .Where(x => x.OrderNo == request.OrderNo && // x.PalletCode == request.PalletCode && // x.CurrentBarcode == request.OriginalBarcode && // x.Status == 1) // .FirstAsync(); // if (lockInfo == null) // return WebResponseContent.Instance.Error("未找到有效的出库锁定信息"); // // 2. 检查剩余锁定数量 // decimal remainingLockQuantity = lockInfo.OriginalQuantity - lockInfo.PickedQty; // if (request.SplitQuantity > remainingLockQuantity) // return WebResponseContent.Instance.Error($"拆包数量不能大于剩余锁定数量,剩余:{remainingLockQuantity}"); // var baseStockDetail = await _stockInfoDetailService.Db.Queryable() // .Where(x => x.Barcode == request.OriginalBarcode && x.StockId == lockInfo.StockId) // .FirstAsync(); // if (baseStockDetail == null) // throw new Exception($"未找到条码{request.OriginalBarcode}对应的库存记录"); // // 4. 计算拆分后的数量 // decimal remainingQty = baseStockDetail.StockQuantity - request.SplitQuantity; // // 更新基础条码的库存数量为剩余数量 // baseStockDetail.StockQuantity = remainingQty; // baseStockDetail.OutboundQuantity = remainingQty; // await _stockInfoDetailService.Db.Updateable(baseStockDetail).ExecuteCommandAsync(); // var seq = await _dailySequenceService.GetNextSequenceAsync(); // // 3. 生成新条码 // string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0'); // // 为拆包产生的新条码创建库存记录 // var newStockDetail = new Dt_StockInfoDetail // { // SupplyCode = baseStockDetail.SupplyCode, // WarehouseCode = baseStockDetail.WarehouseCode, // BarcodeQty = baseStockDetail.BarcodeQty, // BarcodeUnit = baseStockDetail.BarcodeUnit, // BusinessType = baseStockDetail.BusinessType, // Unit = baseStockDetail.Unit, // StockId = lockInfo.StockId, // MaterielCode = baseStockDetail.MaterielCode, // OrderNo = baseStockDetail.OrderNo, // BatchNo = baseStockDetail.BatchNo, // StockQuantity = request.SplitQuantity, // 新条码获得拆分数量 // OutboundQuantity = request.SplitQuantity, // Barcode = newBarcode, // InboundOrderRowNo = baseStockDetail.InboundOrderRowNo, // }; // await _outStockLockInfoService.Db.Insertable(newStockDetail).ExecuteCommandAsync(); // // 4. 创建新的出库锁定信息(新条码) // var newLockInfo = new Dt_OutStockLockInfo // { // OrderNo = lockInfo.OrderNo, // OrderDetailId = lockInfo.OrderDetailId, // BatchNo = lockInfo.BatchNo, // MaterielCode = lockInfo.MaterielCode, // MaterielName = lockInfo.MaterielName, // StockId = lockInfo.StockId, // OrderQuantity = request.SplitQuantity, // OriginalQuantity = request.SplitQuantity, // AssignQuantity = request.SplitQuantity, // 新条码分配数量 // PickedQty = 0, // 新条码未拣选 // LocationCode = lockInfo.LocationCode, // PalletCode = lockInfo.PalletCode, // TaskNum = lockInfo.TaskNum, // Status = (int)OutLockStockStatusEnum.出库中, // Unit = lockInfo.Unit, // SupplyCode = lockInfo.SupplyCode, // OrderType = lockInfo.OrderType, // CurrentBarcode = newBarcode, // 新条码 // OriginalLockQuantity = request.SplitQuantity, // IsSplitted = 1, // ParentLockId = lockInfo.Id // 记录父级锁定ID // }; // await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync(); // lockInfo.AssignQuantity = remainingQty; // lockInfo.IsSplitted = 1; // 标记为已拆包 // await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // var previousSplitRecord = await Db.Queryable() // .Where(x => x.OriginalBarcode == request.OriginalBarcode && !x.IsReverted) // .OrderByDescending(x => x.SplitTime) // .FirstAsync(); // // 6. 记录拆包历史(用于追踪) // var splitHistory = new Dt_SplitPackageRecord // { // FactoryArea = lockInfo.FactoryArea, // TaskNum = lockInfo.TaskNum, // OutStockLockInfoId = lockInfo.Id, // StockId = baseStockDetail.StockId, // Operator = App.User.UserName, // IsReverted = false, // OriginalBarcode = request.OriginalBarcode, // NewBarcode = newBarcode, // SplitQty = request.SplitQuantity, // RemainQuantity = remainingQty, // 记录拆分后的剩余数量 // MaterielCode = lockInfo.MaterielCode, // SplitTime = DateTime.Now, // OrderNo = request.OrderNo, // PalletCode = request.PalletCode, // Status = (int)SplitPackageStatusEnum.已拆包, // PreviousSplitRecordId = previousSplitRecord?.Id??0 // 记录前一次拆包ID,建立拆包链 // }; // await Db.Insertable(splitHistory).ExecuteCommandAsync(); // _unitOfWorkManage.CommitTran(); // try // { // MaterielToMesDTO dto = new MaterielToMesDTO // { // batchNo = baseStockDetail.BatchNo, // factoryArea = baseStockDetail.FactoryArea, // materialCode = baseStockDetail.MaterielCode, // newmaterialCode = newBarcode, // oldmaterialCode = request.OriginalBarcode, // operationType = 1, // qty = remainingQty, // supplyCode = baseStockDetail.SupplyCode, // unit = baseStockDetail.BarcodeUnit, // warehouseCode = baseStockDetail.WarehouseCode, // reqCode = Guid.NewGuid().ToString(), // reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") // }; // _invokeMESService.NewMaterielToMes(dto); // } // catch(Exception ex) // { // _logger.LogError("SplitPackage 回传MES: " + ex.Message); // } // return WebResponseContent.Instance.OK("拆包成功", new // { // NewBarcode = newBarcode, // NewLockInfoId = newLockInfo.Id // }); // } // catch (Exception ex) // { // _unitOfWorkManage.RollbackTran(); // return WebResponseContent.Instance.Error($"拆包失败: {ex.Message}"); // } //} // 获取条码的拆包历史链 public async Task SplitPackage(SplitPackageDto request) { try { _unitOfWorkManage.BeginTran(); // 1. 验证出库锁定信息 var lockInfo = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == request.OrderNo && x.PalletCode == request.PalletCode && x.CurrentBarcode == request.OriginalBarcode && x.Status == 1) .FirstAsync(); if (lockInfo == null) return WebResponseContent.Instance.Error("未找到有效的出库锁定信息"); // 2. 检查剩余锁定数量 decimal remainingLockQuantity = lockInfo.OriginalQuantity - lockInfo.PickedQty; if (request.SplitQuantity > remainingLockQuantity) return WebResponseContent.Instance.Error($"拆包数量不能大于剩余锁定数量,剩余:{remainingLockQuantity}"); // 3. 查找当前条码的库存信息 var baseStockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == request.OriginalBarcode && x.StockId == lockInfo.StockId) .FirstAsync(); if (baseStockDetail == null) throw new Exception($"未找到条码{request.OriginalBarcode}对应的库存记录"); // 4. 记录拆包前的库存数量和锁定信息 decimal stockBeforeSplit = baseStockDetail.StockQuantity; decimal assignBeforeSplit = lockInfo.AssignQuantity; // 5. 计算拆分后的剩余数量 decimal remainingQty = baseStockDetail.StockQuantity - request.SplitQuantity; // 更新基础条码的库存数量为剩余数量 baseStockDetail.StockQuantity = remainingQty; baseStockDetail.OutboundQuantity = remainingQty; await _stockInfoDetailService.Db.Updateable(baseStockDetail).ExecuteCommandAsync(); // 6. 生成新条码 var seq = await _dailySequenceService.GetNextSequenceAsync(); string newBarcode = "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0'); // 7. 为新条码创建库存记录(拆分出的数量) var newStockDetail = new Dt_StockInfoDetail { SupplyCode = baseStockDetail.SupplyCode, WarehouseCode = baseStockDetail.WarehouseCode, BarcodeQty = baseStockDetail.BarcodeQty, BarcodeUnit = baseStockDetail.BarcodeUnit, BusinessType = baseStockDetail.BusinessType, Unit = baseStockDetail.Unit, StockId = lockInfo.StockId, MaterielCode = baseStockDetail.MaterielCode, OrderNo = baseStockDetail.OrderNo, BatchNo = baseStockDetail.BatchNo, StockQuantity = request.SplitQuantity, OutboundQuantity = request.SplitQuantity, Barcode = newBarcode, InboundOrderRowNo = baseStockDetail.InboundOrderRowNo, }; await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync(); // 8. 创建新的出库锁定信息(新条码) var newLockInfo = new Dt_OutStockLockInfo { OrderNo = lockInfo.OrderNo, OrderDetailId = lockInfo.OrderDetailId, BatchNo = lockInfo.BatchNo, MaterielCode = lockInfo.MaterielCode, MaterielName = lockInfo.MaterielName, StockId = lockInfo.StockId, OrderQuantity = request.SplitQuantity, OriginalQuantity = request.SplitQuantity, AssignQuantity = request.SplitQuantity, PickedQty = 0, LocationCode = lockInfo.LocationCode, PalletCode = lockInfo.PalletCode, TaskNum = lockInfo.TaskNum, Status = (int)OutLockStockStatusEnum.出库中, Unit = lockInfo.Unit, SupplyCode = lockInfo.SupplyCode, OrderType = lockInfo.OrderType, CurrentBarcode = newBarcode, OriginalLockQuantity = request.SplitQuantity, IsSplitted = 1, ParentLockId = lockInfo.Id }; await _outStockLockInfoService.Db.Insertable(newLockInfo).ExecuteCommandAsync(); // 9. 更新原锁定信息的分配数量(减少拆包数量) lockInfo.AssignQuantity = remainingQty; lockInfo.IsSplitted = 1; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // 10. 记录拆包历史 var splitHistory = new Dt_SplitPackageRecord { FactoryArea = lockInfo.FactoryArea, TaskNum = lockInfo.TaskNum, OutStockLockInfoId = lockInfo.Id, StockId = baseStockDetail.StockId, Operator = App.User.UserName, IsReverted = false, OriginalBarcode = request.OriginalBarcode, NewBarcode = newBarcode, SplitQty = request.SplitQuantity, RemainQuantity = remainingQty, StockBeforeSplit = stockBeforeSplit, // 记录拆包前的库存数量 AssignBeforeSplit = assignBeforeSplit, // 记录拆包前的分配数量 MaterielCode = lockInfo.MaterielCode, SplitTime = DateTime.Now, OrderNo = request.OrderNo, PalletCode = request.PalletCode, Status = (int)SplitPackageStatusEnum.已拆包 }; await Db.Insertable(splitHistory).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("拆包成功", new SplitPackageChainDto { NewBarcode = newBarcode, NewLockInfoId = newLockInfo.Id, RemainQuantity = remainingQty }); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error($"拆包失败: {ex.Message}"); } } public async Task RevertSplitPackage(string originalBarcode) { try { _unitOfWorkManage.BeginTran(); // 1. 查找该条码作为原始条码的所有未撤销拆包记录,按时间倒序 var splitRecords = await Db.Queryable() .Where(x => x.OriginalBarcode == originalBarcode && !x.IsReverted) .OrderByDescending(x => x.SplitTime) .ToListAsync(); if (splitRecords == null || !splitRecords.Any()) return WebResponseContent.Instance.Error("未找到可撤销的拆包记录"); // 2. 检查所有新条码是否已拣选 var newBarcodes = splitRecords.Select(x => x.NewBarcode).ToList(); var newLockInfos = await _outStockLockInfoService.Db.Queryable() .Where(x => newBarcodes.Contains(x.CurrentBarcode)) .ToListAsync(); var pickedBarcodes = newLockInfos.Where(x => x.Status == 2).Select(x => x.CurrentBarcode).ToList(); if (pickedBarcodes.Any()) return WebResponseContent.Instance.Error($"以下条码已拣选,无法撤销拆包:{string.Join(",", pickedBarcodes)}"); // 3. 获取原条码的锁定信息和库存信息 var originalLockInfo = await _outStockLockInfoService.Db.Queryable() .Where(x => x.CurrentBarcode == originalBarcode) .FirstAsync(); if (originalLockInfo == null) return WebResponseContent.Instance.Error("未找到原条码锁定信息"); var originalStockDetail = await _stockInfoDetailService.Db.Queryable() .Where(x => x.Barcode == originalBarcode && x.StockId == originalLockInfo.StockId) .FirstAsync(); if (originalStockDetail == null) return WebResponseContent.Instance.Error("未找到原条码库存信息"); // 4. 查找第一次拆包记录,获取拆包前的状态 var firstSplitRecord = splitRecords.OrderBy(x => x.SplitTime).FirstOrDefault(); if (firstSplitRecord == null) return WebResponseContent.Instance.Error("未找到有效的拆包记录"); // 5. 恢复原条码到拆包前的状态 originalStockDetail.StockQuantity = firstSplitRecord.StockBeforeSplit; originalStockDetail.OutboundQuantity = firstSplitRecord.StockBeforeSplit; await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync(); // 6. 恢复原条码锁定信息到拆包前的状态 originalLockInfo.AssignQuantity = firstSplitRecord.AssignBeforeSplit; originalLockInfo.OriginalQuantity = firstSplitRecord.AssignBeforeSplit; originalLockInfo.OrderQuantity = firstSplitRecord.AssignBeforeSplit; originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中; originalLockInfo.IsSplitted = 0; // 恢复为未拆包状态 await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync(); // 7. 删除所有新条码的锁定信息 await _outStockLockInfoService.Db.Deleteable() .Where(x => newBarcodes.Contains(x.CurrentBarcode)) .ExecuteCommandAsync(); // 8. 删除所有新条码的库存记录 await _stockInfoDetailService.Db.Deleteable() .Where(x => newBarcodes.Contains(x.Barcode)) .ExecuteCommandAsync(); // 9. 标记所有拆包记录为已撤销 foreach (var record in splitRecords) { record.IsReverted = true; record.RevertTime = DateTime.Now; record.Status = (int)SplitPackageStatusEnum.已撤销; } await Db.Updateable(splitRecords).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK($"撤销拆包成功,共撤销{splitRecords.Count}次拆包,{originalBarcode}恢复为拆包前数量:{firstSplitRecord.StockBeforeSplit}"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); return WebResponseContent.Instance.Error($"撤销拆包失败:{ex.Message}"); } } //public async Task RevertSplitPackage(string originalBarcode) //{ // try // { // _unitOfWorkManage.BeginTran(); // // 1. 查找最近的未撤销拆包记录 // var splitRecord = await Db.Queryable() // .Where(x => x.OriginalBarcode == originalBarcode && !x.IsReverted) // .OrderByDescending(x => x.SplitTime) // .FirstAsync(); // if (splitRecord == null) // return WebResponseContent.Instance.Error("未找到可撤销的拆包记录"); // // 2. 检查新条码是否已拣选 // var newLockInfo = await _outStockLockInfoService.Db.Queryable() // .Where(x => x.CurrentBarcode == splitRecord.NewBarcode) // .FirstAsync(); // if (newLockInfo?.Status ==6) // 假设状态2表示已拣选 // return WebResponseContent.Instance.Error("新条码已拣选,无法撤销拆包"); // // 3. 获取原条码的锁定信息 // var originalLockInfo = await _outStockLockInfoService.Db.Queryable() // .Where(x => x.CurrentBarcode == originalBarcode) // .FirstAsync(); // if (originalLockInfo == null) // return WebResponseContent.Instance.Error("未找到原条码锁定信息"); // originalLockInfo.AssignQuantity += splitRecord.RemainQuantity; // // originalLockInfo.Status = (int)OutLockStockStatusEnum.出库中; // originalLockInfo.IsSplitted = 0; // 标记为未拆包 // await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync(); // // 5. 还原原条码库存记录 // var originalStockDetail = await _stockInfoDetailService.Db.Queryable() // .Where(x => x.Barcode == originalBarcode && x.StockId == splitRecord.StockId) // .FirstAsync(); // if (originalStockDetail != null) // { // // 将拆出的数量加回到原条码库存 // originalStockDetail.StockQuantity += splitRecord.RemainQuantity; // originalStockDetail.OutboundQuantity += splitRecord.RemainQuantity; // await _stockInfoDetailService.Db.Updateable(originalStockDetail).ExecuteCommandAsync(); // } // // 6. 删除新条码的锁定信息 // if (newLockInfo != null) // { // await _outStockLockInfoService.Db.Deleteable() // .Where(x => x.CurrentBarcode == splitRecord.NewBarcode) // .ExecuteCommandAsync(); // } // // 7. 删除新条码的库存记录 // var newStockDetail = await _stockInfoDetailService.Db.Queryable() // .Where(x => x.Barcode == splitRecord.NewBarcode) // .FirstAsync(); // if (newStockDetail != null) // { // await _stockInfoDetailService.Db.Deleteable() // .Where(x => x.Barcode == splitRecord.NewBarcode) // .ExecuteCommandAsync(); // } // // 8. 更新拆包记录为已撤销 // splitRecord.IsReverted = true; // await Db.Updateable(splitRecord).ExecuteCommandAsync(); // _unitOfWorkManage.CommitTran(); // return WebResponseContent.Instance.OK($"撤销拆包成功,还原数量:{splitRecord.SplitQty}"); // } // catch (Exception ex) // { // _unitOfWorkManage.RollbackTran(); // return WebResponseContent.Instance.Error($"撤销拆包失败:{ex.Message}"); // } //} // 获取可撤销的拆包记录列表 public Dt_SplitPackageRecord GetRevertableSplitRecords(string originalBarcode) { var revertableRecords = Db.Queryable() .Where(x => x.OriginalBarcode == originalBarcode && !x.IsReverted) .OrderBy(x => x.SplitTime) .First(); return revertableRecords ; } // 获取拆包信息 public async Task GetSplitPackageInfo(string orderNo, string palletCode, string barcode) { var outStockInfo = await _outStockLockInfoService.Db.Queryable() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && x.CurrentBarcode == barcode && x.Status == 1) .FirstAsync(); if (outStockInfo == null) return WebResponseContent.Instance.Error("未找到对应的出库信息"); //var stockDetail = await _stockInfoDetailService.Db.Queryable() // .Where(x => x.Barcode == barcode) // .FirstAsync(); return WebResponseContent.Instance.OK("", new { MaterielCode = outStockInfo.MaterielCode, RemainQuantity = outStockInfo.RemainQuantity, Unit = outStockInfo.Unit }); } /// /// 获取可拆包的出库锁定信息 /// public async Task GetSplitableLockInfos(int orderDetailId) { var lockInfos = await _outStockLockInfoService.Db.Queryable() .Includes(x => x.StockInfo) .Where(x => x.OrderDetailId == orderDetailId && x.Status == (int)OutLockStockStatusEnum.出库中 && x.AssignQuantity > x.PickedQty) // 还有未拣选数量 .Select(x => new { x.Id, x.PalletCode, x.LocationCode, x.MaterielCode, LockQuantity = x.AssignQuantity - x.PickedQty, x.CurrentBarcode, x.IsSplitted, StockDetails = x.StockInfo.Details.Where(d => d.MaterielCode == x.MaterielCode) .Select(d => new { d.Barcode, AvailableQuantity = d.StockQuantity - d.OutboundQuantity }) .ToList() }) .ToListAsync(); return WebResponseContent.Instance.OK(null, lockInfos); } } }