heshaofeng
20 小时以前 673b5a596f611099eaacc310f6e7def0e022daca
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -1,14 +1,18 @@
using MailKit.Search;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OfficeOpenXml;
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
using SqlSugar;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using WIDESEA_BasicService;
using WIDESEA_Common.CommonEnum;
using WIDESEA_Common.LocationEnum;
@@ -29,6 +33,7 @@
using WIDESEA_Model.Models.Check;
using WIDESEA_Model.Models.Outbound;
namespace WIDESEA_TaskInfoService
{
    public partial class TaskService
@@ -38,22 +43,75 @@
        /// </summary>
        /// <param name="inTask"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> PalletOutboundTask(int num, int locationType)
        // æ–¹æ³•增加了 string? SupplierCode å¯é€‰å‚æ•°
        public async Task<WebResponseContent> PalletOutboundTask(int num, int locationType, string? supplierCode = null)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                var stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt() && x.StockStatus == StockStatusEmun.入库完成.ObjToInt()).WhereIF(locationType != 0, x => x.LocationType == locationType).Take(num).ToList();
                Dictionary<string, SqlSugar.OrderByType> orderByDict = new Dictionary<string, SqlSugar.OrderByType>()
        {
            { nameof(Dt_LocationInfo.Layer), SqlSugar.OrderByType.Asc },
            { nameof(Dt_LocationInfo.Row), SqlSugar.OrderByType.Asc },
            { nameof(Dt_LocationInfo.Column), SqlSugar.OrderByType.Asc },
            { nameof(Dt_LocationInfo.Depth), SqlSugar.OrderByType.Desc },
        };
                if (stockInfos.Count() == 0)
                var query = _stockRepository.Db.Queryable<Dt_StockInfo>()
                    .Where(x => x.PalletType == PalletTypeEnum.Empty.ObjToInt()
                             && x.StockStatus == StockStatusEmun.入库完成.ObjToInt())
                    .WhereIF(locationType != 0, x => x.LocationType == locationType)
                    .LeftJoin<Dt_LocationInfo>((s, l) => s.LocationCode == l.LocationCode);
                if (!await query.AnyAsync())
                {
                    return WebResponseContent.Instance.Error("未找到空托盘库存");
                }
                bool isFirstOrder = true;
                foreach (var item in orderByDict)
                {
                    string fieldName = item.Key.Equals("Column", StringComparison.OrdinalIgnoreCase)
                        ? $"l.[{item.Key}]"
                        : $"l.{item.Key}";
                    string sortSql = $"{fieldName} {(item.Value == SqlSugar.OrderByType.Asc ? "ASC" : "DESC")}";
                    if (isFirstOrder)
                    {
                        query = query.OrderBy(sortSql);
                        isFirstOrder = false;
                    }
                    else
                    {
                        query.OrderBy(sortSql);
                    }
                }
                var stockInfos = await query.Take(num).ToListAsync();
                _unitOfWorkManage.BeginTran();
                Dt_PlasticContainerLedger? todayLedger = null;
                var today = DateTime.Now.Date;
                var tomorrow = today.AddDays(1);
                if (!string.IsNullOrWhiteSpace(supplierCode))
                {
                    todayLedger = await _stockRepository.Db.Queryable<Dt_PlasticContainerLedger>()
                        .Where(x => x.SupplyCode == supplierCode)
                        .Where(x => x.CreateDate >= today && x.CreateDate < tomorrow)
                        .FirstAsync();
                }
                string allTaskNums = string.Empty;
                foreach (var stockInfo in stockInfos)
                {
                    Dt_LocationInfo locationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == stockInfo.LocationCode);
                    Dt_LocationInfo locationInfo = await _locationInfoService.Repository.QueryFirstAsync(x => x.LocationCode == stockInfo.LocationCode);
                    if (locationInfo == null)
                    {
                        _unitOfWorkManage.RollbackTran();
                        return WebResponseContent.Instance.Error("未找到空托盘库存对应的货位信息");
                    }
@@ -70,33 +128,78 @@
                        TaskType = TaskTypeEnum.OutEmpty.ObjToInt(),
                        WarehouseId = stockInfo.WarehouseId,
                        PalletType = stockInfo.PalletType
                    };
                    int beforeStatus = locationInfo.LocationStatus;
                    _unitOfWorkManage.BeginTran();
                    stockInfo.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                    locationInfo.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                    int taskId = BaseDal.AddData(task);
                    int taskId = await BaseDal.AddDataAsync(task);
                    task.TaskId = taskId;
                    allTaskNums += task.TaskNum + ",";
                    stockInfo.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                    _stockService.StockInfoService.UpdateData(stockInfo);
                    int beforeStatus = locationInfo.LocationStatus;
                    locationInfo.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                    _locationInfoService.UpdateData(locationInfo);
                    _recordService.LocationStatusChangeRecordSetvice.AddLocationStatusChangeRecord(locationInfo, beforeStatus, StockChangeType.Outbound.ObjToInt(), "", task.TaskNum);
                    _unitOfWorkManage.CommitTran();
                    _recordService.LocationStatusChangeRecordSetvice.AddLocationStatusChangeRecord(
                       locationInfo, beforeStatus, StockChangeType.Outbound.ObjToInt(), "", task.TaskNum);
                }
                if (!string.IsNullOrWhiteSpace(supplierCode))
                {
                    var ledgerList = new List<Dt_PlasticContainerLedger>();
                    foreach (var stock in stockInfos)
                    {
                        ledgerList.Add(new Dt_PlasticContainerLedger
                        {
                            SupplyCode = supplierCode,
                            PalletCode = stock.PalletCode,
                            CreateDate = DateTime.Now,
                            Creater = App.User?.ToString()
                        });
                    }
                    _plasticContainerLedger.AddData(ledgerList);
                }
                _unitOfWorkManage.CommitTran();
                return content.OK("空托出库成功!");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        /// <summary>
        /// å®žæ—¶æœç´¢ä¾›åº”商编码(支持输入 r è‡ªåŠ¨åŒ¹é…ï¼Œä¸‹æ‹‰æ¡†ç”¨ï¼‰
        /// </summary>
        /// <param name="keyword">输入的关键词(如 r)</param>
        /// <returns>匹配的供应商编码列表</returns>
        public async Task<WebResponseContent> SearchSupplierCode(string keyword)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(keyword))
                    return WebResponseContent.Instance.OK("没有匹配到该供应商编号");
                var list = await _stockRepository.Db.Queryable<Dt_SupplierInfo>()
                    .Where(x => x.SupplierShortName.StartsWith(keyword))
                    .OrderBy(x => x.SupplierShortName)
                    .Take(20)
                    .Select(x => x.SupplierShortName)
                    .ToListAsync();
                return WebResponseContent.Instance.OK(data:list);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        /// <summary>
        /// å‡ºåº“任务数据处理
@@ -1087,119 +1190,196 @@
        /// <returns></returns>
        public async Task<WebResponseContent> TakeOutbound(List<StockViewDTO> stockViews, string outStation)
        {
            WebResponseContent content = new WebResponseContent();
            var content = new WebResponseContent();
            var errorMessages = new List<string>();
            try
            {
                List<int> ids = stockViews.Select(x => x.StockId).ToList();
                //获取库存
                List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
                if (stockInfos.Count != stockViews.Count)
                if (stockViews == null || !stockViews.Any())
                {
                    StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).ToList().Contains(x.PalletCode));
                    return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
                    content.Error("未获取到库存数据");
                    content.Data = new { total = 0, success = 0, fail = 0, failMsg = errorMessages };
                    return content;
                }
                //获取货位
                List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
                List<Dt_LocationInfo> locationInfos =_locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
                if (stockInfos.Count != locationInfos.Count)
                var ids = stockViews.Select(x => x.StockId).ToList();
                var stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>()
                    .Where(x => ids.Contains(x.Id))
                    .Includes(x => x.Details)
                    .ToList();
                var locCodes = stockInfos.Select(x => x.LocationCode).ToList();
                var locationInfos = _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => locCodes.Contains(x.LocationCode))
                    .ToList();
                var finalSuccessStocks = new List<Dt_StockInfo>();
                var finalSuccessLocations = new List<Dt_LocationInfo>();
                foreach (var stock in stockInfos)
                {
                    string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).ToList().Contains(x));
                    return content.Error($"未找到{locStr}货位数据");
                }
                Dt_TakeStockOrder takeStockOrder = new Dt_TakeStockOrder()
                {
                    WarehouseId = stockInfos.FirstOrDefault().WarehouseId,
                    TakeStockStatus = TakeStockStatusEnum.盘点中.ObjToInt(),
                    OrderNo = CreateCodeByRule(nameof(RuleCodeEnum.PDCodeRule)),
                    AllPalletCode = string.Join(",", stockInfos.Select(item => item.PalletCode).Where(palletCode => !string.IsNullOrEmpty(palletCode))),
                    Remark = outStation
                };
                foreach (var item in stockInfos)
                {
                    if (item.Details.Count <= 0)
                    try
                    {
                        return content.Error($"未找到{item.PalletCode}库存明细数据");
                        if (stock.Details == null || !stock.Details.Any())
                        {
                            errorMessages.Add($"托盘【{stock.PalletCode}】:无库存明细");
                            continue;
                        }
                        var loc = locationInfos.FirstOrDefault(x => x.LocationCode == stock.LocationCode);
                        if (loc == null)
                        {
                            errorMessages.Add($"托盘【{stock.PalletCode}】:库位不存在");
                            continue;
                        }
                        if (loc.EnableStatus != EnableStatusEnum.Normal.ObjToInt() ||
                            loc.LocationStatus != LocationStatusEnum.InStock.ObjToInt() ||
                            stock.StockStatus != StockStatusEmun.入库完成.ObjToInt())
                        {
                            errorMessages.Add($"托盘【{stock.PalletCode}】:状态不允许出库");
                            continue;
                        }
                        finalSuccessStocks.Add(stock);
                        finalSuccessLocations.Add(loc);
                    }
                    Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
                    if (locationInfo == null && (locationInfo.EnableStatus == EnableStatusEnum.Disable.ObjToInt() || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt()) && locationInfo.LocationStatus != LocationStatusEnum.InStock.ObjToInt() && item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
                    catch (Exception ex)
                    {
                        content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
                    }
                        errorMessages.Add($"托盘【{stock.PalletCode}】异常:{ex.Message}");
                        continue;
                    }
                }
                List<Dt_Task> tasks = GetTasks(stockInfos, TaskTypeEnum.OutInventory,outStation);
                if (tasks == null || tasks.Count <= 0)
                if (finalSuccessStocks.Any())
                {
                    return content.Error($"生成任务失败");
                    var takeStockOrder = new Dt_TakeStockOrder
                    {
                        WarehouseId = finalSuccessStocks.First().WarehouseId,
                        TakeStockStatus = TakeStockStatusEnum.盘点中.ObjToInt(),
                        OrderNo = CreateCodeByRule(nameof(RuleCodeEnum.PDCodeRule)),
                        AllPalletCode = string.Join(",", finalSuccessStocks.Select(x => x.PalletCode)),
                        Remark = outStation
                    };
                    var tasks = GetTasks(finalSuccessStocks, TaskTypeEnum.OutInventory, outStation);
                    if (tasks == null || tasks.Count <= 0)
                    {
                        errorMessages.Add("任务生成失败");
                    }
                    else
                    {
                        finalSuccessStocks.ForEach(x => x.StockStatus = StockStatusEmun.盘点出库锁定.ObjToInt());
                        finalSuccessLocations.ForEach(x => x.LocationStatus = LocationStatusEnum.Lock.ObjToInt());
                        tasks.ForEach(x => x.OrderNo = takeStockOrder.OrderNo);
                        _unitOfWorkManage.BeginTran();
                        _stockRepository.UpdateData(finalSuccessStocks);
                        _takeStockOrder.AddData(takeStockOrder);
                        BaseDal.AddData(tasks);
                        _locationInfoService.UpdateData(finalSuccessLocations);
                        _unitOfWorkManage.CommitTran();
                    }
                }
                stockInfos.ForEach(x =>
                {
                    x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                });
                tasks.ForEach(x =>
                {
                    x.OrderNo = takeStockOrder.OrderNo;
                });
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                });
                _unitOfWorkManage.BeginTran();
                //更新库存状态
                _stockRepository.UpdateData(stockInfos);
                _takeStockOrder.AddData(takeStockOrder);
                //新建任务
                BaseDal.AddData(tasks);
                _locationInfoService.UpdateData(locationInfos);
                _unitOfWorkManage.CommitTran();
                content.OK();
                //TaskModel esstask = new TaskModel()
                //{
                //    taskType = "carry",
                //    taskGroupCode = "",
                //    groupPriority = 0,
                //    tasks = new List<TasksType>()
                //};
                //foreach (var task in tasks)
                //{
                //    esstask.
                //       tasks.Add(new TasksType
                //       {
                //           taskCode = task.TaskNum.ToString(),
                //           taskPriority = 0,
                //           taskDescribe = new TaskDescribeType
                //           {
                //               containerCode = task.PalletCode,
                //               containerType = "CT_KUBOT_STANDARD",
                //               fromLocationCode = task.SourceAddress ?? "",
                //               toStationCode = "",
                //               toLocationCode = task.TargetAddress,
                //               deadline = 0,
                //               storageTag = ""
                //           }
                //       }
                //   );
                //}
                //var result = await _eSSApiService.CreateTaskAsync(esstask);
                //_logger.LogInformation("创建任务PalletOutboundTask è¿”回:  " + result);
                //if (result)
                //{
                //    return WebResponseContent.Instance.OK();
                //}
                //else
                //{
                //    return WebResponseContent.Instance.Error("下发机器人任务失败!");
                //}
                //content.OK();
                content.OK($"处理完成:成功【{finalSuccessStocks.Count}】条,失败【{errorMessages.Count}】条");
                content.Data = new
                {
                    total = stockInfos.Count,
                    success = finalSuccessStocks.Count,
                    fail = errorMessages.Count,
                    failMsg = errorMessages
                };
                return content;
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                content.Error(ex.Message);
                content.Error($"系统异常:{ex.Message}");
                content.Data = new
                {
                    total = stockViews?.Count ?? 0,
                    success = 0,
                    fail = 1,
                    failMsg = new List<string> { ex.Message }
                };
                return content;
            }
            return content;
        }
        public async Task<WebResponseContent> BatchOutboundByExcel(IFormFile file, string outStation)
        {
            WebResponseContent content = new WebResponseContent();
            List<StockViewDTO> successStockViews = new List<StockViewDTO>();
            List<string> noStockMaterialCodes = new List<string>();
            try
            {
                if (file == null || file.Length == 0)
                    return content.Error("上传失败:请选择需要导入的Excel文件");
                var ext = Path.GetExtension(file.FileName).ToLower();
                if (ext != ".xls" && ext != ".xlsx")
                    return content.Error("上传失败:仅支持 .xls å’Œ .xlsx æ ¼å¼çš„Excel文件,请重新上传");
                try
                {
                    using var stream = file.OpenReadStream();
                    var excelList = ReadExcel(stream);
                    if (!excelList.Any())
                        return content.Error("导入失败:Excel文件中未读取到任何有效数据");
                    foreach (var item in excelList)
                    {
                        var matchedStocks = _stockRepository.Db.Queryable<Dt_StockInfo>()
                            .Includes(x => x.Details)
                            .Where(x =>
                                x.Details.Any(d =>
                                    d.WarehouseCode == item.WarehouseCode
                                    && d.MaterielCode == item.MaterialCode
                                )
                            )
                            .ToList();
                        if (matchedStocks.Any())
                        {
                            successStockViews.AddRange(matchedStocks.Select(s => new StockViewDTO
                            {
                                StockId = s.Id
                            }));
                        }
                        else
                        {
                            noStockMaterialCodes.Add(item.MaterialCode);
                        }
                    }
                    // æ‰§è¡Œå‡ºåº“
                    if (successStockViews.Any())
                    {
                        var result = await TakeOutbound(successStockViews, outStation);
                        return result;
                    }
                    // æ— ä»»ä½•可出库数据
                    var msg = "未查询到任何可出库库存";
                    if (noStockMaterialCodes.Any())
                    {
                        msg += $",无库存料号:{string.Join("、", noStockMaterialCodes)}";
                    }
                    return content.Error(msg);
                }
                catch (Exception ex)
                {
                    return content.Error($"Excel解析失败:{ex.Message}");
                }
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return content.Error($"批量出库失败:系统异常,{ex.Message}");
            }
        }
        /// <summary>
        /// å•据生成方法
@@ -1263,6 +1443,312 @@
                return code;
            }
        }
        /// <summary>
        /// é€‰å®šåº“存同区域移库
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> AreaOutbound(List<StockViewDTO> stockViews)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                List<int> ids = stockViews.Select(x => x.StockId).ToList();
                //获取库存
                List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
                if (stockInfos.Count != stockViews.Count)
                {
                    StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).ToList().Contains(x.PalletCode));
                    return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
                }
                //获取货位
                List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
                List<Dt_LocationInfo> locationInfos = _locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
                if (stockInfos.Count != locationInfos.Count)
                {
                    string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).ToList().Contains(x));
                    return content.Error($"未找到{locStr}货位数据");
                }
                foreach (var item in stockInfos)
                {
                    Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
                    if (locationInfo == null || (locationInfo.EnableStatus == EnableStatusEnum.Disable.ObjToInt() || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt()) || item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
                    {
                        return content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
                    }
                }
                List<Dt_Task> tasks = AreaGetTasks(stockInfos, TaskTypeEnum.AreaRelocation);
                if (tasks == null || tasks.Count <= 0)
                {
                    return content.Error($"生成任务失败");
                }
                stockInfos.ForEach(x =>
                {
                    x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                });
                tasks.ForEach(x =>
                {
                    x.OrderNo = "无单据移库";
                });
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                });
                _unitOfWorkManage.BeginTran();
                //更新库存状态
                _stockRepository.UpdateData(stockInfos);
                //新建任务
                BaseDal.AddData(tasks);
                _locationInfoService.UpdateData(locationInfos);
                _unitOfWorkManage.CommitTran();
                content.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return await Task.FromResult(WebResponseContent.Instance.Error(ex.Message));
            }
            return content;
        }
        public List<Dt_Task> AreaGetTasks(List<Dt_StockInfo> stockInfos, TaskTypeEnum taskType)
        {
            // ä½¿ç”¨ TransactionScope åŒ…裹整个操作,确保所有数据库操作在同一事务中
            using (var scope = new TransactionScope(TransactionScopeOption.Required,
                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
            {
                try
                {
                    List<Dt_Task> tasks = new List<Dt_Task>();
                    List<Dt_LocationInfo> locationInfos = _locationInfoService.Repository.QueryData(
                        x => stockInfos.Select(x => x.LocationCode).Contains(x.LocationCode));
                    for (int i = 0; i < stockInfos.Count; i++)
                    {
                        Dt_StockInfo stockInfo = stockInfos[i];
                        if (stockInfo != null)
                        {
                            Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == stockInfo.LocationCode);
                            var newLocation = _locationInfoService.AssignLocation(stockInfo.LocationType);
                            if (newLocation == null)
                            {
                                throw new Exception($"在{stockInfo.PalletCode}时没有空闲库位可进行同区域移库,请检查或减少移库料箱");
                            }
                            if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
                            {
                                Dt_Task task = new()
                                {
                                    CurrentAddress = stockInfo.LocationCode,
                                    Grade = 0,
                                    PalletCode = stockInfo.PalletCode,
                                    NextAddress = "",
                                    Roadway = locationInfo.RoadwayNo,
                                    SourceAddress = stockInfo.LocationCode,
                                    TargetAddress = newLocation.LocationCode,
                                    TaskStatus = TaskStatusEnum.New.ObjToInt(),
                                    TaskType = taskType.ObjToInt(),
                                    PalletType = stockInfo.PalletType,
                                    WarehouseId = stockInfo.WarehouseId,
                                };
                                tasks.Add(task);
                            }
                            newLocation.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                            _locationInfoService.UpdateData(newLocation);
                        }
                    }
                    // æäº¤äº‹åŠ¡
                    scope.Complete();
                    return tasks;
                }
                catch (Exception ex)
                {
                    // TransactionScope ä¼šè‡ªåŠ¨å›žæ»šäº‹åŠ¡ï¼Œæ— éœ€æ‰‹åŠ¨ Rollback
                    throw new Exception(ex.Message);
                }
            }
        }
        /// <summary>
        /// é€‰å®šåº“存跨区域移库
        /// </summary>
        /// <param name="stockViews"></param>
        /// <param name="targetLocationType">目标货位类型</param>
        /// <returns></returns>
        public async Task<WebResponseContent> CrossAreaOutbound(List<StockViewDTO> stockViews, int targetLocationType)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                if(targetLocationType == (int)LocationTypeEnum.Electronic)
                {
                    return content.Error("电子仓不允许跨区域移库");
                }
                List<int> ids = stockViews.Select(x => x.StockId).ToList();
                List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
                if (stockInfos.Count != stockViews.Count)
                {
                    StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).Contains(x.PalletCode));
                    return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
                }
                List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
                List<Dt_LocationInfo> locationInfos = _locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
                if (stockInfos.Count != locationInfos.Count)
                {
                    string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).Contains(x));
                    return content.Error($"未找到{locStr}货位数据");
                }
                foreach (var item in stockInfos)
                {
                    if (item.PalletType != PalletTypeEnum.Empty.ObjToInt())
                    {
                        return content.Error($"托盘【{item.PalletCode}】非空箱,仅空箱允许跨区域移库!");
                    }
                    Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
                    if (locationInfo == null || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt() || item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
                    {
                        return content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
                    }
                }
                List<Dt_Task> tasks = CrossAreaGetTasks(stockInfos, targetLocationType, TaskTypeEnum.CrossAreaRelocation);
                if (tasks == null || tasks.Count <= 0)
                {
                    return content.Error("生成跨区域移库任务失败");
                }
                stockInfos.ForEach(x =>
                {
                    x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                });
                tasks.ForEach(x =>
                {
                    x.OrderNo = "跨区域移库";
                });
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                });
                _unitOfWorkManage.BeginTran();
                _stockRepository.UpdateData(stockInfos);
                BaseDal.AddData(tasks);
                _locationInfoService.UpdateData(locationInfos);
                _unitOfWorkManage.CommitTran();
                content.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return await Task.FromResult(WebResponseContent.Instance.Error(ex.Message));
            }
            return content;
        }
        /// <summary>
        /// ç”Ÿæˆè·¨åŒºåŸŸç§»åº“任务
        /// </summary>
        /// <param name="stockInfos">库存列表</param>
        /// <param name="targetAreaCode">目标区域编码</param>
        /// <param name="taskType">任务类型</param>
        /// <returns></returns>
        public List<Dt_Task> CrossAreaGetTasks(List<Dt_StockInfo> stockInfos, int targetLocationType, TaskTypeEnum taskType)
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required,
                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
            {
                try
                {
                    List<Dt_Task> tasks = new List<Dt_Task>();
                    foreach (var stockInfo in stockInfos)
                    {
                        Dt_LocationInfo newLocation = _locationInfoService.AssignLocation(targetLocationType);
                        if (newLocation == null)
                        {
                            throw new Exception($"{stockInfo.PalletCode} æ²¡æœ‰ç©ºé—²è´§ä½å¯è¿›è¡Œè·¨åŒºåŸŸç§»åº“");
                        }
                        if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
                        {
                            Dt_Task task = new()
                            {
                                CurrentAddress = stockInfo.LocationCode,
                                Grade = 0,
                                PalletCode = stockInfo.PalletCode,
                                NextAddress = "",
                                Roadway = newLocation.RoadwayNo,
                                SourceAddress = stockInfo.LocationCode,
                                TargetAddress = newLocation.LocationCode,
                                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                                TaskType = taskType.ObjToInt(),
                                PalletType = stockInfo.PalletType,
                                WarehouseId = stockInfo.WarehouseId,
                            };
                            tasks.Add(task);
                        }
                        newLocation.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                        _locationInfoService.UpdateData(newLocation);
                    }
                    scope.Complete();
                    return tasks;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
        }
        public static List<ExcelInventoryOutboundDto> ReadExcel(Stream stream)
        {
            var list = new List<ExcelInventoryOutboundDto>();
            using (var package = new ExcelPackage(stream))
            {
                var sheet = package.Workbook.Worksheets.FirstOrDefault();
                if (sheet == null) return list;
                // æ­£ç¡®èŽ·å–è¡Œæ•°
                int rowCount = sheet.Dimension?.End.Row ?? 0;
                // ç¬¬2行开始读
                for (int row = 2; row <= rowCount; row++)
                {
                    string location = sheet.Cells[row, 8].Value?.ToString()?.Trim() ?? "";
                    string materialCode = sheet.Cells[row, 2].Value?.ToString()?.Trim() ?? "";
                    if (string.IsNullOrEmpty(location) || string.IsNullOrEmpty(materialCode))
                        continue;
                    list.Add(new ExcelInventoryOutboundDto
                    {
                        WarehouseCode = location,
                        MaterialCode = materialCode
                    });
                }
            }
            return list;
        }
    }
}