| | |
| | | using SqlSugar; |
| | | using Newtonsoft.Json; |
| | | using System.Text; |
| | | using OfficeOpenXml; |
| | | using OfficeOpenXml.Style; |
| | | |
| | | namespace MES.Service.service.Warehouse; |
| | | |
| | |
| | | public PagedResult<ReturnableStockDto> GetReturnableStocks( |
| | | ReturnableStockSearchDto searchDto) |
| | | { |
| | | // 参数校验 |
| | | if (searchDto.PageIndex < 1) |
| | | { |
| | | throw new Exception("页码必须大于0"); |
| | | } |
| | | |
| | | if (!new[] { 10, 20, 50 }.Contains(searchDto.PageSize)) |
| | | { |
| | | throw new Exception("每页条数必须为10、20或50"); |
| | | } |
| | | // 优化点1: 使用原生SQL ROW_NUMBER()在数据库端完成去重和排序 |
| | | var rackingTaskSql = @" |
| | | SELECT ITEM_BARCODE AS ItemBarcode, PALLETCODE AS PalletCode, CODE AS Code, TASKTYPE AS TaskType |
| | | FROM ( |
| | | SELECT ITEM_BARCODE, PALLETCODE, CODE, TASKTYPE, |
| | | ROW_NUMBER() OVER (PARTITION BY ITEM_BARCODE ORDER BY ID DESC) AS RN |
| | | FROM XB_RACKING_TASK_SYXT_LOG |
| | | WHERE ITEM_BARCODE IS NOT NULL AND (CODE IS NULL OR CODE != '500') |
| | | ) WHERE RN = 1"; |
| | | |
| | | // 1. 查询XB_RACKING_TASK_SYXT_LOG中ItemBarcode和PalletCode的映射关系 |
| | | var allRackingTaskData = Db.Queryable<XbRackingTaskSyxtLog>() |
| | | .Where(x => !string.IsNullOrEmpty(x.ItemBarcode)) |
| | | .Select(x => new { x.ItemBarcode, x.PalletCode, x.Code, x.Id }) |
| | | .ToList(); |
| | | |
| | | // ReturnableStockDto增加一个库存状态的字段 |
| | | // 当allRackingTaskData中的Code为null时,库存状态为立库入库中 |
| | | // 当allRackingTaskData中的Code为200时,库存状态为已经在立库内 |
| | | // 对每个条码,取最新ID的记录,并过滤掉Code为500(表示失败)的记录 |
| | | var rackingTaskData = allRackingTaskData |
| | | .GroupBy(x => x.ItemBarcode) // 按条码分组 |
| | | .Select(g => g.OrderByDescending(x => x.Id).First()) // 取每个条码最新的一条记录 |
| | | .Where(x => x.Code != "500") // 过滤掉Code为500的失败记录 |
| | | .ToList(); |
| | | var rackingTaskData = Db.Ado.SqlQuery<RackingTaskDto>(rackingTaskSql); |
| | | |
| | | if (rackingTaskData == null || !rackingTaskData.Any()) |
| | | { |
| | |
| | | }; |
| | | } |
| | | |
| | | // 1.1 提取去重后的条码列表用于查询 |
| | | var distinctBarcodes = rackingTaskData.Select(x => x.ItemBarcode) |
| | | .Distinct().ToList(); |
| | | // 优化点2: 过滤null值并使用字典提高查找效率 |
| | | var validRackingData = rackingTaskData.Where(x => !string.IsNullOrEmpty(x.ItemBarcode)).ToList(); |
| | | |
| | | if (!validRackingData.Any()) |
| | | { |
| | | return new PagedResult<ReturnableStockDto> |
| | | { |
| | | TbBillList = new List<ReturnableStockDto>(), |
| | | Pagination = new PaginationInfo |
| | | { |
| | | CurrentPage = searchDto.PageIndex, |
| | | PageSize = searchDto.PageSize, |
| | | TotalRecords = 0, |
| | | TotalPages = 0 |
| | | } |
| | | }; |
| | | } |
| | | |
| | | var rackingTaskDict = validRackingData.ToDictionary(x => x.ItemBarcode); |
| | | var distinctBarcodes = validRackingData.Select(x => x.ItemBarcode).ToList(); |
| | | |
| | | // 2. 构建查询条件 |
| | | // 优化点3: 构建查询条件 |
| | | var query = Db.Queryable<MesInvItemStocks>() |
| | | .LeftJoin<MesItems>((stock, item) => stock.ItemId == item.Id) |
| | | .LeftJoin<MesDepots>((stock, item, depot) => |
| | |
| | | distinctBarcodes.Contains(stock.StackCode)) && |
| | | stock.Quantity > 0); |
| | | |
| | | // 3. 应用搜索条件 |
| | | var conditions = searchDto.Conditions; |
| | | if (conditions != null) |
| | | { |
| | | //当conditions.IqcStatus为1时查询的值就要是特采直接使用,已检,免检,1的值 |
| | | // 精确匹配条件 |
| | | if (!string.IsNullOrEmpty(conditions.IqcStatus)) |
| | | { |
| | | if (conditions.IqcStatus == "1") |
| | | { |
| | | // 当IqcStatus为"1"时,查询特采直接使用、已检、免检状态 |
| | | query = query.Where((stock, item, depot, org, unit) => |
| | | stock.IqcStatus == "特采直接使用" || |
| | | stock.IqcStatus == "已检" || |
| | |
| | | } |
| | | else |
| | | { |
| | | // 其他情况按原值匹配 |
| | | query = query.Where((stock, item, depot, org, unit) => |
| | | stock.IqcStatus == conditions.IqcStatus); |
| | | } |
| | |
| | | query = query.Where((stock, item, depot, org, unit) => |
| | | stock.Quantity == conditions.Quantity.Value); |
| | | } |
| | | |
| | | // 模糊匹配条件 |
| | | |
| | | if (!string.IsNullOrEmpty(conditions.DepotName)) |
| | | { |
| | |
| | | if (!string.IsNullOrEmpty(conditions.ItemBarcode)) |
| | | { |
| | | query = query.Where((stock, item, depot, org, unit) => |
| | | stock.ItemBarcode != null && |
| | | stock.ItemBarcode.Contains(conditions.ItemBarcode)); |
| | | (stock.ItemBarcode != null && |
| | | stock.ItemBarcode.Contains(conditions.ItemBarcode)) || |
| | | (stock.StackCode != null && |
| | | stock.StackCode.Contains(conditions.ItemBarcode))); |
| | | } |
| | | |
| | | // 日期范围条件 |
| | | if (!string.IsNullOrEmpty(conditions.IndepDateStart)) |
| | | { |
| | | if (DateTime.TryParse(conditions.IndepDateStart, |
| | |
| | | } |
| | | } |
| | | |
| | | // 4. 查询所有符合条件的数据(不分页) |
| | | // 优化点4: 查询数据 |
| | | var queryResult = query |
| | | .OrderByDescending((stock, item, depot, org, unit) => |
| | | stock.IndepDate) |
| | |
| | | }) |
| | | .ToList(); |
| | | |
| | | // 5. 在内存中转换为DTO,关联PalletCode并赋值ItemBarcode(优先使用箱条码StockStackCode) |
| | | // 优化点5: 使用字典查找替代Where().FirstOrDefault() |
| | | var tempDataList = queryResult.Select(x => |
| | | { |
| | | // 优先使用StockStackCode,否则使用ItemBarcode去查找PalletCode |
| | | var barcodeToMatch = !string.IsNullOrEmpty(x.StockStackCode) |
| | | ? x.StockStackCode |
| | | : x.ItemBarcode; |
| | | var rackingTask = rackingTaskData |
| | | .Where(r => r.ItemBarcode == barcodeToMatch) |
| | | .FirstOrDefault(); |
| | | |
| | | rackingTaskDict.TryGetValue(barcodeToMatch, out var rackingTask); |
| | | |
| | | // 根据Code值确定库存状态: null为立库入库中(0), 200为已在立库内(1) |
| | | string stockStatus = "进入立库的路上"; // 默认为立库入库中 |
| | | if (rackingTask?.Code != null) |
| | | string stockStatus; |
| | | if (rackingTask?.TaskType == "立库出库请求") |
| | | { |
| | | stockStatus = rackingTask.Code == "200" ? "已在立库中" : "进入立库的路上"; |
| | | // 出库请求:Code=200表示已出库,Code=null表示出库路上 |
| | | stockStatus = rackingTask.Code == "200" ? "已出立库" : "出立库路上"; |
| | | } |
| | | else |
| | | { |
| | | // 回库请求(默认):Code=200表示已在立库中,Code=null表示进入立库的路上 |
| | | stockStatus = rackingTask?.Code == "200" ? "已在立库中" : "进入立库的路上"; |
| | | } |
| | | |
| | | return new |
| | | { |
| | | IqcStatus = x.IqcStatus == "已检" ? "1" : "0", |
| | | ItemType = x.DepotName == "原材料仓" ? "0" : "1", |
| | | StackCode = rackingTask?.PalletCode, // 使用PalletCode作为母托盘编号 |
| | | StackCode = rackingTask?.PalletCode, |
| | | x.DepotCode, |
| | | x.DepotName, |
| | | x.DepotSectionsCode, |
| | |
| | | x.OrgCode, |
| | | x.OrgName, |
| | | ItemBarcode = barcodeToMatch, |
| | | StockStatus = stockStatus // 添加库存状态 |
| | | StockStatus = stockStatus |
| | | }; |
| | | }).ToList(); |
| | | |
| | | // 5.1 应用StackCode搜索条件(在内存中过滤PalletCode) |
| | | if (conditions != null && !string.IsNullOrEmpty(conditions.StackCode)) |
| | | { |
| | | tempDataList = tempDataList |
| | |
| | | .ToList(); |
| | | } |
| | | |
| | | // 5.2 应用StockStatus搜索条件(在内存中过滤) |
| | | if (conditions != null && !string.IsNullOrEmpty(conditions.StockStatus)) |
| | | { |
| | | tempDataList = tempDataList |
| | |
| | | .ToList(); |
| | | } |
| | | |
| | | // 5.3 分组并合计Quantity |
| | | var dataList = tempDataList |
| | | .GroupBy(x => new |
| | | { |
| | |
| | | x.OrgCode, |
| | | x.OrgName, |
| | | x.ItemBarcode, |
| | | x.StockStatus // 添加库存状态到分组键中 |
| | | x.StockStatus |
| | | }) |
| | | .Select(g => new ReturnableStockDto |
| | | { |
| | |
| | | OrgCode = g.Key.OrgCode, |
| | | OrgName = g.Key.OrgName, |
| | | ItemBarcode = g.Key.ItemBarcode, |
| | | StockStatus = g.Key.StockStatus // 添加库存状态到DTO中 |
| | | StockStatus = g.Key.StockStatus |
| | | }).ToList(); |
| | | |
| | | // 6. 应用ItemType筛选(在内存中过滤) |
| | | if (conditions?.ItemType != null) |
| | | { |
| | | dataList = dataList.Where(x => x.ItemType == conditions.ItemType) |
| | | .ToList(); |
| | | } |
| | | |
| | | // 7. 计算总记录数和分页参数(基于最终结果) |
| | | var totalRecords = dataList.Count; |
| | | var totalPages = |
| | | (int)Math.Ceiling((double)totalRecords / searchDto.PageSize); |
| | | var skip = (searchDto.PageIndex - 1) * searchDto.PageSize; |
| | | |
| | | // 8. 对最终结果进行分页 |
| | | var pagedDataList = |
| | | dataList.Skip(skip).Take(searchDto.PageSize).ToList(); |
| | | |
| | | // 9. 返回分页结果 |
| | | return new PagedResult<ReturnableStockDto> |
| | | { |
| | | TbBillList = pagedDataList, |
| | |
| | | /// <returns>可退货物料库存列表</returns> |
| | | public List<ReturnableStockDto> GetReturnableStocks() |
| | | { |
| | | // 1. 查询XB_RACKING_TASK_SYXT_LOG中所有的条码并去重 |
| | | var distinctBarcodes = Db.Queryable<XbRackingTaskSyxtLog>() |
| | | .Where(x => !string.IsNullOrEmpty(x.ItemBarcode)) |
| | | .Select(x => x.ItemBarcode) |
| | | .Distinct() |
| | | .ToList(); |
| | | // 优化点1: 使用原生SQL在数据库端完成去重和排序,避免全表加载到内存 |
| | | // Oracle 11g使用ROW_NUMBER()窗口函数获取每个条码的最新记录 |
| | | var rackingTaskSql = @" |
| | | SELECT ITEM_BARCODE AS ItemBarcode, PALLETCODE AS PalletCode, CODE AS Code |
| | | FROM ( |
| | | SELECT ITEM_BARCODE, PALLETCODE, CODE, |
| | | ROW_NUMBER() OVER (PARTITION BY ITEM_BARCODE ORDER BY ID DESC) AS RN |
| | | FROM XB_RACKING_TASK_SYXT_LOG |
| | | WHERE ITEM_BARCODE IS NOT NULL AND (CODE IS NULL OR CODE != '500') |
| | | ) WHERE RN = 1"; |
| | | |
| | | if (distinctBarcodes == null || !distinctBarcodes.Any()) |
| | | var rackingTaskData = Db.Ado.SqlQuery<RackingTaskDto>(rackingTaskSql); |
| | | |
| | | if (rackingTaskData == null || !rackingTaskData.Any()) |
| | | { |
| | | return new List<ReturnableStockDto>(); |
| | | } |
| | | |
| | | // 1.1 查询XB_RACKING_TASK_SYXT_LOG中ItemBarcode和PalletCode、Code的映射关系 |
| | | var allRackingTaskData = Db.Queryable<XbRackingTaskSyxtLog>() |
| | | .Where(x => !string.IsNullOrEmpty(x.ItemBarcode)) |
| | | .Select(x => new { x.ItemBarcode, x.PalletCode, x.Code, x.Id }) |
| | | .ToList(); |
| | | // 优化点2: 过滤null值并使用字典提高条码匹配效率,从O(n)降低到O(1) |
| | | var validRackingData = rackingTaskData.Where(x => !string.IsNullOrEmpty(x.ItemBarcode)).ToList(); |
| | | var rackingTaskDict = validRackingData.ToDictionary(x => x.ItemBarcode); |
| | | var distinctBarcodes = validRackingData.Select(x => x.ItemBarcode).ToList(); |
| | | |
| | | var rackingTaskData = allRackingTaskData |
| | | .GroupBy(x => x.ItemBarcode) |
| | | .Select(g => g.OrderByDescending(x => x.Id).First()) |
| | | .ToList(); |
| | | |
| | | // 2. 使用条码查询MES_INV_ITEM_STOCKS中的数据,关联MES_ITEMS、MES_DEPOTS、ORGANIZE、MES_UNIT表 |
| | | // 优化点3: 在数据库层面完成关联查询,一次性获取所有需要的数据 |
| | | var queryResult = Db.Queryable<MesInvItemStocks>() |
| | | .LeftJoin<MesItems>((stock, item) => stock.ItemId == item.Id) |
| | | .LeftJoin<MesDepots>((stock, item, depot) => |
| | |
| | | }) |
| | | .ToList(); |
| | | |
| | | // 3. 在内存中进行数据转换 |
| | | // 优化点4: 使用字典查找替代Where().FirstOrDefault(),提高匹配性能 |
| | | var result = queryResult.Select(x => |
| | | { |
| | | // 根据条码查找对应的立库任务信息 |
| | | var rackingTask = rackingTaskData |
| | | .Where(r => r.ItemBarcode == x.ItemBarcode) |
| | | .FirstOrDefault(); |
| | | rackingTaskDict.TryGetValue(x.ItemBarcode, out var rackingTask); |
| | | |
| | | // 根据Code值确定库存状态: null为立库入库中(0), 200为已在立库内(1) |
| | | string stockStatus = "0"; // 默认为立库入库中 |
| | | string stockStatus = "0"; |
| | | if (rackingTask?.Code != null) |
| | | { |
| | | stockStatus = |
| | | rackingTask.Code == "200" |
| | | ? "1" |
| | | : "2"; // 200对应已在立库中(1),其他非500值为进入立库的路上(2) |
| | | stockStatus = rackingTask.Code == "200" ? "1" : "2"; |
| | | } |
| | | |
| | | return new ReturnableStockDto |
| | |
| | | OrgCode = x.OrgCode, |
| | | OrgName = x.OrgName, |
| | | ItemBarcode = x.ItemBarcode, |
| | | StockStatus = stockStatus // 添加库存状态 |
| | | StockStatus = stockStatus |
| | | }; |
| | | }).ToList(); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | private class RackingTaskDto |
| | | { |
| | | public string ItemBarcode { get; set; } |
| | | public string PalletCode { get; set; } |
| | | public string Code { get; set; } |
| | | public string TaskType { get; set; } |
| | | } |
| | | |
| | | /// <summary> |
| | |
| | | .OrderByDescending(x => x.Id) |
| | | .Select(x => new { x.PalletCode, x.WidthType }) |
| | | .First(); |
| | | |
| | | // 检查是否存在未完成的任务(基于PalletCode,Code为null) |
| | | if (!string.IsNullOrEmpty(rackingTaskInfo?.PalletCode)) |
| | | { |
| | | var pendingTask = Db.Queryable<XbRackingTaskSyxtLog>() |
| | | .Where(x => x.PalletCode == rackingTaskInfo.PalletCode && x.Code == null) |
| | | .OrderByDescending(x => x.Id) |
| | | .First(); |
| | | |
| | | if (pendingTask != null) |
| | | { |
| | | throw new Exception($"托盘编号 {rackingTaskInfo.PalletCode} 已存在未完成的立库任务,请等待任务完成后再操作。"); |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | |
| | | throw; |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 导出可退货物料库存为Excel |
| | | /// </summary> |
| | | /// <param name="searchDto">搜索请求参数</param> |
| | | /// <returns>Excel文件字节数组</returns> |
| | | public byte[] ExportReturnableStocksToExcel( |
| | | ReturnableStockSearchDto searchDto) |
| | | { |
| | | // 设置 EPPlus 许可证上下文(非商业用途) |
| | | ExcelPackage.LicenseContext = LicenseContext.NonCommercial; |
| | | |
| | | // 获取所有数据(不分页) |
| | | var tempSearchDto = new ReturnableStockSearchDto |
| | | { |
| | | PageIndex = 1, |
| | | PageSize = int.MaxValue, // 获取所有数据 |
| | | Conditions = searchDto.Conditions |
| | | }; |
| | | |
| | | // 调用查询方法获取数据 |
| | | var result = GetReturnableStocks(tempSearchDto); |
| | | var dataList = result.TbBillList; |
| | | |
| | | // 创建 Excel 文件 |
| | | using var package = new ExcelPackage(); |
| | | var worksheet = package.Workbook.Worksheets.Add("可退货物料库存"); |
| | | |
| | | // 设置表头 |
| | | var headers = new[] |
| | | { |
| | | "良品状态", |
| | | "是否成品", |
| | | "母托盘编号", |
| | | "仓库编码", |
| | | "仓库名称", |
| | | "虚拟库位", |
| | | "物料编号", |
| | | "物料名称", |
| | | "物料规格", |
| | | "物料数量", |
| | | "物料单位", |
| | | "入库时间", |
| | | "组织编码", |
| | | "组织名称", |
| | | "物料条码", |
| | | "库存状态" |
| | | }; |
| | | |
| | | // 写入表头 |
| | | for (int i = 0; i < headers.Length; i++) |
| | | { |
| | | worksheet.Cells[1, i + 1].Value = headers[i]; |
| | | worksheet.Cells[1, i + 1].Style.Font.Bold = true; |
| | | worksheet.Cells[1, i + 1].Style.Fill.PatternType = |
| | | ExcelFillStyle.Solid; |
| | | worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor |
| | | .SetColor(System.Drawing.Color.LightGray); |
| | | worksheet.Cells[1, i + 1].Style.HorizontalAlignment = |
| | | ExcelHorizontalAlignment.Center; |
| | | } |
| | | |
| | | // 写入数据 |
| | | int row = 2; |
| | | foreach (var item in dataList) |
| | | { |
| | | worksheet.Cells[row, 1].Value = |
| | | item.IqcStatus == "1" ? "良品" : "不良品"; |
| | | worksheet.Cells[row, 2].Value = item.ItemType == "1" ? "成品" : "非成品"; |
| | | worksheet.Cells[row, 3].Value = item.StackCode; |
| | | worksheet.Cells[row, 4].Value = item.DepotCode; |
| | | worksheet.Cells[row, 5].Value = item.DepotName; |
| | | worksheet.Cells[row, 6].Value = item.DepotSectionsCode; |
| | | worksheet.Cells[row, 7].Value = item.ItemNo; |
| | | worksheet.Cells[row, 8].Value = item.ItemName; |
| | | worksheet.Cells[row, 9].Value = item.ItemModel; |
| | | worksheet.Cells[row, 10].Value = item.Quantity; |
| | | worksheet.Cells[row, 11].Value = item.ItemUnitName; |
| | | worksheet.Cells[row, 12].Value = |
| | | item.IndepDate?.ToString("yyyy-MM-dd HH:mm:ss"); |
| | | worksheet.Cells[row, 13].Value = item.OrgCode; |
| | | worksheet.Cells[row, 14].Value = item.OrgName; |
| | | worksheet.Cells[row, 15].Value = item.ItemBarcode; |
| | | worksheet.Cells[row, 16].Value = item.StockStatus; |
| | | row++; |
| | | } |
| | | |
| | | // 自动调整列宽 |
| | | worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns(); |
| | | |
| | | // 返回 Excel 文件字节数组 |
| | | return package.GetAsByteArray(); |
| | | } |
| | | } |