From c7971052ec1636e58fb3f3ee95c15148f2cd5232 Mon Sep 17 00:00:00 2001
From: 647556386 <647556386@qq.com>
Date: 星期三, 03 十二月 2025 15:22:46 +0800
Subject: [PATCH] 虚拟出入库回传MES

---
 项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs | 1089 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 1,007 insertions(+), 82 deletions(-)

diff --git "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs" "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs"
index 81def2c..4f86ab1 100644
--- "a/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs"
+++ "b/\351\241\271\347\233\256\344\273\243\347\240\201/WMS\346\227\240\344\273\223\345\202\250\347\211\210/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs"
@@ -20,13 +20,20 @@
 using WIDESEA_Core.BaseServices;
 using WIDESEA_Core.Enums;
 using WIDESEA_Core.Helper;
+using WIDESEA_Core.Utilities;
+using WIDESEA_DTO.Allocate;
 using WIDESEA_DTO.Basic;
 using WIDESEA_DTO.Inbound;
 using WIDESEA_DTO.Outbound;
+using WIDESEA_IAllocateService;
 using WIDESEA_IBasicService;
+using WIDESEA_ICheckService;
+using WIDESEA_IInboundService;
 using WIDESEA_IOutboundService;
 using WIDESEA_IStockService;
 using WIDESEA_Model.Models;
+using WIDESEA_Model.Models.Basic;
+using WIDESEA_Model.Models.Check;
 
 namespace WIDESEA_OutboundService
 {
@@ -50,7 +57,11 @@
         private readonly IESSApiService _eSSApiService;
         private readonly IInvokeMESService _invokeMESService;
         private readonly IDailySequenceService _dailySequenceService;
-
+        private readonly IAllocateService _allocateService;
+        private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
+        private readonly IInboundOrderDetailService _inboundOrderDetailService;
+        private readonly IRepository<Dt_WarehouseArea> _warehouseAreaRepository;
+        private readonly IReCheckOrderService _reCheckOrderService;
         private readonly ILogger<OutboundPickingService> _logger;
 
         private Dictionary<string, string> stations = new Dictionary<string, string>
@@ -70,7 +81,7 @@
         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)
+            IRepository<Dt_Task> taskRepository, IESSApiService eSSApiService, ILogger<OutboundPickingService> logger, IInvokeMESService invokeMESService, IDailySequenceService dailySequenceService, IAllocateService allocateService, IRepository<Dt_InboundOrder> inboundOrderRepository, IInboundOrderDetailService inboundOrderDetailService, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IReCheckOrderService reCheckOrderService) : base(BaseDal)
         {
             _unitOfWorkManage = unitOfWorkManage;
             _stockInfoService = stockInfoService;
@@ -86,6 +97,11 @@
             _logger = logger;
             _invokeMESService = invokeMESService;
             _dailySequenceService = dailySequenceService;
+            _allocateService = allocateService;
+            _inboundOrderRepository = inboundOrderRepository;
+            _inboundOrderDetailService = inboundOrderDetailService;
+            _warehouseAreaRepository = warehouseAreaRepository;
+            _reCheckOrderService = reCheckOrderService;
         }
 
 
@@ -225,14 +241,14 @@
                 }
                 _unitOfWorkManage.BeginTran();
 
-                // 1. 鍓嶇疆楠岃瘉
+                // 鍓嶇疆楠岃瘉
                 var validationResult = await ValidateCancelRequest(orderNo, palletCode, barcode);
                 if (!validationResult.IsValid)
                     return WebResponseContent.Instance.Error(validationResult.ErrorMessage);
 
                 var (pickingRecord, lockInfo, orderDetail) = validationResult.Data;
 
-                // 2. 鎵ц鍙栨秷閫昏緫
+                //鎵ц鍙栨秷閫昏緫
                 await ExecuteCancelLogic(lockInfo, pickingRecord, orderDetail, orderNo);
 
                 _unitOfWorkManage.CommitTran();
@@ -290,6 +306,8 @@
 
                 //鎵ц鍥炲簱鎿嶄綔
                 await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis);
+
+                await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis);
 
                 _unitOfWorkManage.CommitTran();
 
@@ -672,9 +690,7 @@
 
             if (newOverOutQuantity > currentOrderDetail.NeedOutQuantity)
             {
-
                 _logger.LogError($"闃茶秴鎷f鏌ュけ璐� - OrderDetailId: {orderDetailId}, 宸插嚭搴�: {newOverOutQuantity}, 闇�姹�: {currentOrderDetail.NeedOutQuantity}, 鏈鍒嗘嫞: {pickedQty}");
-
 
                 decimal adjustedQty = currentOrderDetail.NeedOutQuantity - currentOrderDetail.OverOutQuantity;
 
@@ -683,6 +699,7 @@
                     _logger.LogWarning($"鑷姩璋冩暣鍒嗘嫞鏁伴噺闃叉瓒呮嫞锛氫粠{pickedQty}璋冩暣涓簕adjustedQty}");
                     newOverOutQuantity = currentOrderDetail.NeedOutQuantity;
                     newPickedQty = currentOrderDetail.PickedQty + adjustedQty;
+                    pickedQty = adjustedQty; // 鏇存柊瀹為檯鎷i�夋暟閲�
                 }
                 else
                 {
@@ -690,15 +707,21 @@
                 }
             }
 
-            // 鏇存柊璁㈠崟鏄庣粏
+            // 鏇存柊璁㈠崟鏄庣粏鏁伴噺鍜岀姸鎬�
             await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                 .SetColumns(it => new Dt_OutboundOrderDetail
                 {
                     PickedQty = newPickedQty,
                     OverOutQuantity = newOverOutQuantity,
+                    OrderDetailStatus = newOverOutQuantity >= currentOrderDetail.NeedOutQuantity ?
+                        OrderDetailStatusEnum.Over.ObjToInt() :
+                        OrderDetailStatusEnum.Outbound.ObjToInt()
                 })
                 .Where(it => it.Id == orderDetailId)
                 .ExecuteCommandAsync();
+
+            // 鏇存柊閿佸畾鏁伴噺
+            await UpdateOrderDetailLockQuantity(orderDetailId);
 
             // 妫�鏌ュ苟鏇存柊璁㈠崟鐘舵��
             await CheckAndUpdateOrderStatus(orderNo);
@@ -729,7 +752,15 @@
                 PickQuantity = result.ActualPickedQty,
                 PickTime = DateTime.Now,
                 Operator = App.User.UserName,
-                OutStockLockId = result.FinalLockInfo.Id
+                OutStockLockId = result.FinalLockInfo.Id,
+                BarcodeUnit = result.FinalLockInfo.BarcodeUnit,
+                BarcodeQty = result.FinalLockInfo.BarcodeQty,
+                BatchNo = result.FinalLockInfo.BatchNo,
+                lineNo = result.FinalLockInfo.lineNo,
+                SupplyCode = result.FinalLockInfo.SupplyCode,
+                WarehouseCode = result.FinalLockInfo.WarehouseCode,
+
+
             };
 
             await Db.Insertable(pickingHistory).ExecuteCommandAsync();
@@ -742,7 +773,7 @@
         {
             try
             {
-                // 1. 楠岃瘉璁㈠崟鏄庣粏鏁版嵁
+                //  楠岃瘉璁㈠崟鏄庣粏鏁版嵁
                 var currentOrderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                     .FirstAsync(x => x.Id == context.OrderDetail.Id);
 
@@ -752,26 +783,26 @@
                 if (currentOrderDetail.PickedQty < context.PickingRecord.PickQuantity)
                     return ValidationResult<bool>.Error($"璁㈠崟鏄庣粏宸叉嫞閫夋暟閲�({currentOrderDetail.PickedQty})灏忎簬鍙栨秷鏁伴噺({context.PickingRecord.PickQuantity})");
 
-                // 2. 楠岃瘉閿佸畾淇℃伅鏁版嵁
+                //  楠岃瘉閿佸畾淇℃伅鏁版嵁
                 var currentLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                     .FirstAsync(x => x.Id == context.LockInfo.Id);
 
                 if (currentLockInfo.PickedQty < context.PickingRecord.PickQuantity)
                     return ValidationResult<bool>.Error($"閿佸畾淇℃伅宸叉嫞閫夋暟閲�({currentLockInfo.PickedQty})灏忎簬鍙栨秷鏁伴噺({context.PickingRecord.PickQuantity})");
 
-                // 3. 楠岃瘉搴撳瓨鏁版嵁
-                var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
-                    .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
+                ////// 楠岃瘉搴撳瓨鏁版嵁
+                ////var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
+                ////    .FirstAsync(x => x.Barcode == context.PickingRecord.Barcode && x.StockId == context.PickingRecord.StockId);
 
-                if (currentStockDetail == null)
-                    return ValidationResult<bool>.Error($"鏈壘鍒板搴旂殑搴撳瓨鏄庣粏璁板綍");
+                ////if (currentStockDetail == null)
+                ////    return ValidationResult<bool>.Error($"鏈壘鍒板搴旂殑搴撳瓨鏄庣粏璁板綍");
 
-                if (currentStockDetail.Status == StockStatusEmun.鍏ュ簱纭.ObjToInt() ||
-                    currentStockDetail.Status == StockStatusEmun.鍏ュ簱瀹屾垚.ObjToInt())
-                    return ValidationResult<bool>.Error($"鏉$爜{context.PickingRecord.Barcode}宸茬粡鍥炲簱锛屾棤娉曞彇娑堝垎鎷�");
+                ////if (currentStockDetail.Status == StockStatusEmun.鍏ュ簱纭.ObjToInt() ||
+                ////    currentStockDetail.Status == StockStatusEmun.鍏ュ簱瀹屾垚.ObjToInt())
+                ////    return ValidationResult<bool>.Error($"鏉$爜{context.PickingRecord.Barcode}宸茬粡鍥炲簱锛屾棤娉曞彇娑堝垎鎷�");
 
-                // 4. 楠岃瘉鐘舵�佹祦杞殑鍚堟硶鎬�
-                if (!await CanCancelPicking(currentLockInfo, currentStockDetail))
+                // 楠岃瘉鐘舵�佹祦杞殑鍚堟硶鎬�
+                if (!await CanCancelPicking(currentLockInfo, null))
                     return ValidationResult<bool>.Error($"褰撳墠鐘舵�佷笉鍏佽鍙栨秷鍒嗘嫞");
 
                 return ValidationResult<bool>.Success(true);
@@ -789,9 +820,9 @@
             if (lockInfo.Status != (int)OutLockStockStatusEnum.鎷i�夊畬鎴�)
                 return false;
 
-            // 搴撳瓨鐘舵�佹鏌�
-            if (stockDetail.Status == StockStatusEmun.鍑哄簱瀹屾垚.ObjToInt())
-                return false;
+            ////// 搴撳瓨鐘舵�佹鏌�
+            ////if (stockDetail.Status == StockStatusEmun.鍑哄簱瀹屾垚.ObjToInt())
+            ////    return false;
 
             // 濡傛灉鏄媶鍖呰褰曪紝杩橀渶瑕佹鏌ョ埗閿佸畾淇℃伅鐘舵��
             if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
@@ -916,7 +947,7 @@
         {
             decimal cancelQty = pickingRecord.PickQuantity;
 
-            // 1. 鏁版嵁涓�鑷存�ч獙璇�
+            // 鏁版嵁涓�鑷存�ч獙璇�
             var context = new CancelPickingContext
             {
                 LockInfo = lockInfo,
@@ -930,7 +961,7 @@
             if (!validationResult.IsValid)
                 throw new Exception(validationResult.ErrorMessage);
 
-            // 2. 澶勭悊涓嶅悓绫诲瀷鐨勫彇娑�
+            // 澶勭悊涓嶅悓绫诲瀷鐨勫彇娑�
             if (lockInfo.IsSplitted == 1 && lockInfo.ParentLockId.HasValue)
             {
                 await HandleSplitBarcodeCancel(lockInfo, pickingRecord, cancelQty);
@@ -1092,12 +1123,28 @@
             if (newPickedQty < 0)
                 throw new Exception($"鍙栨秷鍒嗘嫞灏嗗鑷村凡鎷i�夋暟閲�({newPickedQty})涓鸿礋鏁�");
 
+            // 纭畾鏂扮殑鐘舵��
+            int newStatus;
+            if (newOverOutQuantity >= currentOrderDetail.NeedOutQuantity)
+            {
+                newStatus = OrderDetailStatusEnum.Over.ObjToInt();
+            }
+            else if (newOverOutQuantity > 0)
+            {
+                newStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
+            }
+            else
+            {
+                newStatus = OrderDetailStatusEnum.New.ObjToInt();
+            }
+
             // 鏇存柊璁㈠崟鏄庣粏
             var updateResult = await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
                 .SetColumns(it => new Dt_OutboundOrderDetail
                 {
                     PickedQty = newPickedQty,
-                    OverOutQuantity = newOverOutQuantity
+                    OverOutQuantity = newOverOutQuantity,
+                    OrderDetailStatus = newStatus
                 })
                 .Where(it => it.Id == orderDetailId)
                 .ExecuteCommandAsync();
@@ -1105,9 +1152,13 @@
             if (updateResult <= 0)
                 throw new Exception("鏇存柊璁㈠崟鏄庣粏澶辫触");
 
+            // 鏇存柊閿佸畾鏁伴噺
+            await UpdateOrderDetailLockQuantity(orderDetailId);
+
             _logger.LogInformation($"鏇存柊璁㈠崟鏄庣粏 - OrderDetailId: {orderDetailId}, " +
                                   $"鎵e噺宸插嚭搴�: {cancelQty}, 鏂板凡鍑哄簱: {newOverOutQuantity}, " +
-                                  $"鎵e噺宸叉嫞閫�: {cancelQty}, 鏂板凡鎷i��: {newPickedQty}");
+                                  $"鎵e噺宸叉嫞閫�: {cancelQty}, 鏂板凡鎷i��: {newPickedQty}, " +
+                                  $"鏂扮姸鎬�: {newStatus}");
         }
         #endregion
 
@@ -1303,6 +1354,116 @@
             await UpdateStockInfoStatus(stockInfo);
         }
 
+        /// <summary>
+        /// 瀹屽叏閲婃斁閿佸畾锛屽厑璁搁噸鏂板垎閰嶅簱瀛�
+        /// </summary>
+        private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis)
+        {
+            _logger.LogInformation($"寮�濮嬮噴鏀鹃攣瀹氫互渚块噸鏂板垎閰� - 璁㈠崟: {orderNo}, 鎵樼洏: {palletCode}");
+
+            // 1. 澶勭悊鏈垎鎷g殑鍑哄簱閿佸畾璁板綍 - 瀹屽叏閲婃斁
+            if (analysis.HasRemainingLocks)
+            {
+                await ReleaseRemainingLocks(analysis.RemainingLocks);
+            }
+
+            // 2. 澶勭悊宸插洖搴撶殑閿佸畾璁板綍 - 鍒犻櫎鎴栨爣璁颁负鏃犳晥
+            await CleanupReturnedLocks(orderNo, palletCode);
+
+            // 3. 閲嶇疆璁㈠崟鏄庣粏鐨勯攣瀹氭暟閲�
+            await ResetOrderDetailLockQuantities(analysis);
+
+            _logger.LogInformation($"閿佸畾閲婃斁瀹屾垚 - 璁㈠崟: {orderNo}, 鎵樼洏: {palletCode}");
+        }
+
+        /// <summary>
+        /// 閲婃斁鏈垎鎷g殑閿佸畾璁板綍
+        /// </summary>
+        private async Task ReleaseRemainingLocks(List<Dt_OutStockLockInfo> remainingLocks)
+        {
+            var lockIds = remainingLocks.Select(x => x.Id).ToList();
+
+            // 灏嗛攣瀹氳褰曠姸鎬佹敼涓�"宸查噴鏀�"锛屾垨鑰呯洿鎺ュ垹闄�
+            //  鏍囪涓哄凡閲婃斁 
+            await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
+                .SetColumns(it => new Dt_OutStockLockInfo
+                {
+                    Status = (int)OutLockStockStatusEnum.宸查噴鏀�, // 闇�瑕佹柊澧炶繖涓姸鎬�
+                                                              // ReleaseTime = DateTime.Now,
+                    Operator = App.User.UserName
+                })
+                .Where(it => lockIds.Contains(it.Id))
+                .ExecuteCommandAsync();
+
+            //  鐩存帴鍒犻櫎锛堟洿褰诲簳锛�
+            // await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
+            //     .Where(it => lockIds.Contains(it.Id))
+            //     .ExecuteCommandAsync();
+
+            _logger.LogInformation($"閲婃斁{remainingLocks.Count}鏉℃湭鍒嗘嫞閿佸畾璁板綍");
+        }
+
+        /// <summary>
+        /// 娓呯悊宸插洖搴撶殑閿佸畾璁板綍
+        /// </summary>
+        private async Task CleanupReturnedLocks(string orderNo, string palletCode)
+        {
+            // 鏌ユ壘鎵�鏈夌姸鎬佷负鍥炲簱涓殑閿佸畾璁板綍骞堕噴鏀�
+            var returnedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
+                .Where(it => it.OrderNo == orderNo &&
+                           it.PalletCode == palletCode &&
+                           it.Status == (int)OutLockStockStatusEnum.鍥炲簱涓�)
+                .ToListAsync();
+
+            if (returnedLocks.Any())
+            {
+                var returnedLockIds = returnedLocks.Select(x => x.Id).ToList();
+
+                await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>()
+                    .SetColumns(it => new Dt_OutStockLockInfo
+                    {
+                        Status = (int)OutLockStockStatusEnum.宸查噴鏀�,
+                        //ReleaseTime = DateTime.Now,
+                        Operator = App.User.UserName
+                    })
+                    .Where(it => returnedLockIds.Contains(it.Id))
+                    .ExecuteCommandAsync();
+
+                _logger.LogInformation($"娓呯悊{returnedLocks.Count}鏉″洖搴撲腑閿佸畾璁板綍");
+            }
+        }
+
+        /// <summary>
+        /// 閲嶇疆璁㈠崟鏄庣粏鐨勯攣瀹氭暟閲�
+        /// </summary>
+        private async Task ResetOrderDetailLockQuantities(PalletStatusAnalysis analysis)
+        {
+            // 鏀堕泦鎵�鏈夊彈褰卞搷鐨勮鍗曟槑缁咺D
+            var affectedOrderDetailIds = new HashSet<int>();
+
+            if (analysis.HasRemainingLocks)
+            {
+                foreach (var lockInfo in analysis.RemainingLocks)
+                {
+                    affectedOrderDetailIds.Add(lockInfo.OrderDetailId);
+                }
+            }
+
+            // 閲嶇疆杩欎簺璁㈠崟鏄庣粏鐨勯攣瀹氭暟閲�
+            foreach (var orderDetailId in affectedOrderDetailIds)
+            {
+                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
+                    .SetColumns(it => new Dt_OutboundOrderDetail
+                    {
+                        LockQuantity = 0, // 閲嶇疆閿佸畾鏁伴噺
+                        OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt() // 閲嶇疆鐘舵�佷负鏂板缓
+                    })
+                    .Where(it => it.Id == orderDetailId)
+                    .ExecuteCommandAsync();
+            }
+
+            _logger.LogInformation($"閲嶇疆{affectedOrderDetailIds.Count}涓鍗曟槑缁嗙殑閿佸畾鏁伴噺");
+        }
         private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId)
         {
             var lockIds = remainingLocks.Select(x => x.Id).ToList();
@@ -1334,6 +1495,7 @@
 
                 if (stockDetail != null)
                 {
+                    stockDetail.StockQuantity += returnQty;
                     // 鎭㈠搴撳瓨鐘舵��
                     stockDetail.OutboundQuantity = Math.Max(0, stockDetail.OutboundQuantity - returnQty);
                     stockDetail.Status = StockStatusEmun.鍏ュ簱纭.ObjToInt();
@@ -1341,6 +1503,7 @@
                 }
                 else
                 {
+                    _logger.LogWarning($"鏈壘鍒板搴旂殑搴撳瓨鏄庣粏 - 鏉$爜: {lockInfo.CurrentBarcode}, 搴撳瓨ID: {lockInfo.StockId}");
                     // 鍒涘缓鏂扮殑搴撳瓨璁板綍
                     //var newStockDetail = new Dt_StockInfoDetail
                     //{
@@ -1360,7 +1523,34 @@
                     //};
                     //await _stockInfoDetailService.Db.Insertable(newStockDetail).ExecuteCommandAsync();
                 }
+                try
+                {
+                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
+                        .FirstAsync(x => x.Id == lockInfo.OrderDetailId);
+
+                    if (orderDetail != null)
+                    {
+                        decimal newLockQuantity = Math.Max(0, orderDetail.LockQuantity - returnQty);
+
+                        await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
+                            .SetColumns(it => new Dt_OutboundOrderDetail
+                            {
+                                LockQuantity = newLockQuantity
+                            })
+                            .Where(it => it.Id == lockInfo.OrderDetailId)
+                            .ExecuteCommandAsync();
+
+                        _logger.LogInformation($"鏇存柊璁㈠崟鏄庣粏閿佸畾鏁伴噺 - OrderDetailId: {lockInfo.OrderDetailId}, " +
+                                             $"鎵e噺閿佸畾: {returnQty}, 鏂伴攣瀹氭暟閲�: {newLockQuantity}");
+                    }
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError($"鏇存柊璁㈠崟鏄庣粏閿佸畾鏁伴噺澶辫触 - OrderDetailId: {lockInfo.OrderDetailId}, Error: {ex.Message}");
+                }
             }
+
+
         }
 
         private async Task UpdateOrderDetailsOnReturn(List<Dt_OutStockLockInfo> remainingLocks)
@@ -1486,7 +1676,7 @@
 
             // 鍒犻櫎鍘熷鍑哄簱浠诲姟
             _taskRepository.DeleteAndMoveIntoHty(originalTask, OperateTypeEnum.鑷姩瀹屾垚);
-            // await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
+            await _taskRepository.Db.Deleteable(originalTask).ExecuteCommandAsync();
 
 
 
@@ -1562,13 +1752,25 @@
                     .ToListAsync();
 
                 bool allCompleted = true;
+                bool hasPartial = false;
+                bool hasLocked = false;
+
                 foreach (var detail in orderDetails)
                 {
                     if (detail.OverOutQuantity < detail.NeedOutQuantity)
                     {
                         allCompleted = false;
-                        break;
                     }
+
+                    if (detail.OrderDetailStatus == OrderDetailStatusEnum.Outbound.ObjToInt())
+                    {
+                        hasPartial = true;
+                    }
+
+                    //if (detail.OrderDetailStatus == OrderDetailStatusEnum.Locked.ObjToInt())
+                    //{
+                    //    hasLocked = true;
+                    //}
                 }
 
                 var outboundOrder = await _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
@@ -1576,7 +1778,19 @@
 
                 if (outboundOrder == null) return;
 
-                int newStatus = allCompleted ? (int)OutOrderStatusEnum.鍑哄簱瀹屾垚 : (int)OutOrderStatusEnum.鍑哄簱涓�;
+                int newStatus;
+                if (allCompleted)
+                {
+                    newStatus = (int)OutOrderStatusEnum.鍑哄簱瀹屾垚;
+                }
+                else if (hasPartial)
+                {
+                    newStatus = (int)OutOrderStatusEnum.鍑哄簱涓�;
+                }
+                else
+                {
+                    newStatus = (int)OutOrderStatusEnum.鏈紑濮�;
+                }
 
                 if (outboundOrder.OrderStatus != newStatus)
                 {
@@ -1589,11 +1803,9 @@
                         .Where(x => x.OrderNo == orderNo)
                         .ExecuteCommandAsync();
 
-                    // 鍙湁姝e父鍒嗘嫞瀹屾垚鏃舵墠鍚慚ES鍙嶉
-                    //if (allCompleted && newStatus == (int)OutOrderStatusEnum.鍑哄簱瀹屾垚)
-                    //{
-                    //    await HandleOrderCompletion(outboundOrder, orderNo);
-                    //}
+                    _logger.LogInformation($"璁㈠崟鐘舵�佹洿鏂� - OrderNo: {orderNo}, 鏃х姸鎬�: {outboundOrder.OrderStatus}, 鏂扮姸鎬�: {newStatus}");
+
+                   
                 }
             }
             catch (Exception ex)
@@ -1602,8 +1814,70 @@
             }
         }
 
+        /// <summary>
+        /// 鏇存柊璁㈠崟鏄庣粏鐘舵�侊紙鍩轰簬宸插嚭搴撴暟閲忥級
+        /// </summary>
+        private async Task UpdateOrderDetailStatus(int orderDetailId)
+        {
+            var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
+                .FirstAsync(x => x.Id == orderDetailId);
 
+            if (orderDetail == null) return;
 
+            int newStatus = orderDetail.OrderDetailStatus;
+
+            if (orderDetail.OverOutQuantity >= orderDetail.NeedOutQuantity)
+            {
+                // 宸插嚭搴撴暟閲� >= 闇�姹傛暟閲忥紝鏍囪涓哄畬鎴�
+                newStatus = OrderDetailStatusEnum.Over.ObjToInt();
+            }
+            else if (orderDetail.OverOutQuantity > 0)
+            {
+                // 鏈夐儴鍒嗗嚭搴擄紝浣嗘湭瀹屾垚
+                newStatus = OrderDetailStatusEnum.Outbound.ObjToInt();
+            }
+            else if (orderDetail.LockQuantity > 0)
+            {
+                // 鏈夐攣瀹氭暟閲忥紝浣嗘湭鍑哄簱
+                //newStatus = OrderDetailStatusEnum.Locked.ObjToInt();
+            }
+            else
+            {
+                // 鏂板缓鐘舵��
+                newStatus = OrderDetailStatusEnum.New.ObjToInt();
+            }
+
+            // 鍙湁鐘舵�佸彂鐢熷彉鍖栨椂鎵嶆洿鏂�
+            if (orderDetail.OrderDetailStatus != newStatus)
+            {
+                await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
+                    .SetColumns(it => new Dt_OutboundOrderDetail
+                    {
+                        OrderDetailStatus = newStatus,
+
+                    })
+                    .Where(it => it.Id == orderDetailId)
+                    .ExecuteCommandAsync();
+            }
+        }
+        /// <summary>
+        /// 鏇存柊璁㈠崟鏄庣粏閿佸畾鏁伴噺
+        /// </summary>
+        private async Task UpdateOrderDetailLockQuantity(int orderDetailId)
+        {
+            var totalLockedQty = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
+                .Where(x => x.OrderDetailId == orderDetailId &&
+                           x.Status == (int)OutLockStockStatusEnum.鍑哄簱涓�)
+                .SumAsync(x => x.AssignQuantity - x.PickedQty);
+
+            await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
+                .SetColumns(it => new Dt_OutboundOrderDetail
+                {
+                    LockQuantity = totalLockedQty
+                })
+                .Where(it => it.Id == orderDetailId)
+                .ExecuteCommandAsync();
+        }
         private async Task UpdateOrderStatusForReturn(string orderNo)
         {
             try
@@ -1654,78 +1928,141 @@
         private async Task HandleOrderCompletion(Dt_OutboundOrder outboundOrder, string orderNo)
         {
             // 璋冩嫧鍑哄簱鍜岄噸妫�鍑哄簱涓嶉渶瑕佸弽棣圡ES
-            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt() ||
-                outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
+            if (outboundOrder.OrderType == OutOrderTypeEnum.Allocate.ObjToInt())
             {
-                return;
-            }
-
-            try
-            {
-                var feedmodel = new FeedbackOutboundRequestModel
+                var allocate = _allocateService.Repository.QueryData(x => x.UpperOrderNo == outboundOrder.UpperOrderNo).First();
+                var allocatefeedmodel = new AllocateDto
                 {
-                    reqCode = Guid.NewGuid().ToString(),
-                    reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
-                    business_type = outboundOrder.BusinessType,
-                    factoryArea = outboundOrder.FactoryArea,
-                    operationType = 1,
-                    Operator = App.User.UserName,
-                    orderNo = outboundOrder.UpperOrderNo,
-                    documentsNO = outboundOrder.OrderNo,
-                    status = outboundOrder.OrderStatus,
-                    details = new List<FeedbackOutboundDetailsModel>()
-                };
+                    ReqCode = Guid.NewGuid().ToString(),
+                    ReqTime = DateTime.Now.ToString(),
+                    BusinessType = "2",
 
+                    FactoryArea = outboundOrder.FactoryArea,
+                    OperationType = 1,
+                    Operator = App.User.UserName,
+                    OrderNo = outboundOrder.UpperOrderNo,
+                    // documentsNO = outboundOrder.OrderNo,
+                    // status = outboundOrder.OrderStatus,
+                    fromWarehouse = allocate?.FromWarehouse ?? "",
+                    toWarehouse = allocate?.ToWarehouse ?? "",
+                    Details = new List<AllocateDtoDetail>()
+
+                };
                 // 鍙幏鍙栧凡鎷i�夊畬鎴愮殑閿佸畾璁板綍
                 var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                     .Where(x => x.OrderNo == orderNo && x.Status == (int)OutLockStockStatusEnum.鎷i�夊畬鎴�)
                     .ToListAsync();
 
                 var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.Unit, item.WarehouseCode })
-                   .Select(group => new FeedbackOutboundDetailsModel
+                   .Select(group => new AllocateDtoDetail
                    {
-                       materialCode = group.Key.MaterielCode,
-                       lineNo = group.Key.lineNo,
-                       warehouseCode = group.Key.WarehouseCode,
-                       qty = group.Sum(x => x.PickedQty),
-                       currentDeliveryQty = group.Sum(x => x.PickedQty),
-                       unit = group.Key.Unit,
-                       barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
+                       MaterialCode = group.Key.MaterielCode,
+                       LineNo = group.Key.lineNo,
+                       WarehouseCode = group.Key.WarehouseCode,
+                       Qty = group.Sum(x => x.PickedQty),
+
+                       Unit = group.Key.Unit,
+                       Barcodes = group.Select(row => new BarcodeInfo
                        {
-                           barcode = row.CurrentBarcode,
-                           supplyCode = row.SupplyCode,
-                           batchNo = row.BatchNo,
-                           unit = row.Unit,
-                           qty = row.PickedQty
+                           Barcode = row.CurrentBarcode,
+                           SupplyCode = row.SupplyCode,
+                           BatchNo = row.BatchNo,
+                           Unit = row.Unit,
+                           Qty = row.PickedQty
                        }).ToList()
+
+
                    }).ToList();
+                allocatefeedmodel.Details = groupedData;
 
-                feedmodel.details = groupedData;
-
-                var result = await _invokeMESService.FeedbackOutbound(feedmodel);
+                var result = await _invokeMESService.FeedbackAllocate(allocatefeedmodel);
                 if (result != null && result.code == 200)
                 {
                     await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>()
-                        .SetColumns(x => x.ReturnToMESStatus == 1)
-                        .Where(x => x.OrderId == outboundOrder.Id)
-                        .ExecuteCommandAsync();
+                           .SetColumns(x => x.ReturnToMESStatus == 1)
+                           .Where(x => x.OrderId == outboundOrder.Id).ExecuteCommandAsync();
 
                     await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                           .SetColumns(x => new Dt_OutboundOrder
                           {
                               ReturnToMESStatus = 1,
                               Operator = App.User.UserName,
-                          })
-
-                        .Where(x => x.OrderNo == orderNo)
-                        .ExecuteCommandAsync();
+                          }).Where(x => x.OrderNo == orderNo).ExecuteCommandAsync();
                 }
-
-                _logger.LogError($"FeedbackOutbound鎴愬姛 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
             }
-            catch (Exception ex)
+            else if (outboundOrder.OrderType == OutOrderTypeEnum.ReCheck.ObjToInt())
             {
-                _logger.LogError($"FeedbackOutbound澶辫触 - OrderNo: {orderNo}, Error: {ex.Message}");
+
+            }
+            else
+            {
+                try
+                {
+                    var feedmodel = new FeedbackOutboundRequestModel
+                    {
+                        reqCode = Guid.NewGuid().ToString(),
+                        reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                        business_type = outboundOrder.BusinessType,
+                        factoryArea = outboundOrder.FactoryArea,
+                        operationType = 1,
+                        Operator = App.User.UserName,
+                        orderNo = outboundOrder.UpperOrderNo,
+                        documentsNO = outboundOrder.OrderNo,
+                        status = outboundOrder.OrderStatus,
+                        details = new List<FeedbackOutboundDetailsModel>()
+                    };
+
+                    // 鍙幏鍙栧凡鎷i�夊畬鎴愮殑閿佸畾璁板綍
+                    var lists = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
+                        .Where(x => x.OrderNo == orderNo && (x.Status == (int)OutLockStockStatusEnum.鎷i�夊畬鎴� || x.Status == (int)OutLockStockStatusEnum.宸插洖搴�))
+                        .ToListAsync();
+
+                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
+                       .Select(group => new FeedbackOutboundDetailsModel
+                       {
+                           materialCode = group.Key.MaterielCode,
+                           lineNo = group.Key.lineNo,
+                           warehouseCode = group.Key.WarehouseCode,
+                           qty = group.Sum(x => x.BarcodeQty),
+                           currentDeliveryQty = group.Sum(x => x.BarcodeQty),
+                           unit = group.Key.BarcodeUnit,
+                           barcodes = group.Select(row => new WIDESEA_DTO.Outbound.BarcodesModel
+                           {
+                               barcode = row.CurrentBarcode,
+                               supplyCode = row.SupplyCode,
+                               batchNo = row.BatchNo,
+                               unit = row.BarcodeUnit,
+                               qty = row.BarcodeQty
+                           }).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 => new Dt_OutboundOrder
+                              {
+                                  ReturnToMESStatus = 1,
+                                  Operator = App.User.UserName,
+                              })
+
+                            .Where(x => x.OrderNo == orderNo)
+                            .ExecuteCommandAsync();
+                    }
+
+                    _logger.LogError($"FeedbackOutbound鎴愬姛 - OrderNo: {orderNo}, {JsonSerializer.Serialize(result)}");
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError($"FeedbackOutbound澶辫触 - OrderNo: {orderNo}, Error: {ex.Message}");
+                }
             }
         }
 
@@ -1864,7 +2201,7 @@
                 _logger.LogInformation($"璁㈠崟 {orderNo} 宸叉爣璁颁负鍑哄簱瀹屾垚");
 
                 // 鍚慚ES鍙嶉璁㈠崟瀹屾垚锛堝鏋滈渶瑕侊級
-                await HandleOrderCompletion(outboundOrder, orderNo);
+                //await HandleOrderCompletion(outboundOrder, orderNo);
             }
         }
 
@@ -2101,6 +2438,8 @@
                 FactoryArea = originalLock.FactoryArea,
                 lineNo = originalLock.lineNo,
                 WarehouseCode = originalLock.WarehouseCode,
+                BarcodeQty = originalLock.BarcodeQty,
+                BarcodeUnit = originalLock.BarcodeUnit,
 
             };
 
@@ -2206,8 +2545,586 @@
             return WebResponseContent.Instance.OK("鎷i�夌‘璁ゆ垚鍔�", new { SplitResults = new List<SplitResult>() });
         }
 
+
+
+        #region 铏氭嫙鍑哄叆搴�
+        public WebResponseContent GetAvailablePurchaseOrders()
+        {
+            List<Dt_InboundOrder> InOders = _inboundOrderRepository.QueryData().Where(x => x.OrderStatus != InOrderStatusEnum.鍏ュ簱瀹屾垚.ObjToInt()).ToList();
+            List<string> InOderCodes = InOders.Select(x => x.UpperOrderNo).ToList();
+            return WebResponseContent.Instance.OK("鎴愬姛", data: InOderCodes);
+        }
+
+        public WebResponseContent GetAvailablePickingOrders()
+        {
+            List<Dt_OutboundOrder> outOders = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>().Where(x => x.OrderStatus != OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt()).ToList();
+
+            List<string> outOderCodes = outOders.Select(x => x.UpperOrderNo).ToList();
+            return WebResponseContent.Instance.OK("鎴愬姛", data: outOderCodes);
+
+        }
+        public WebResponseContent BarcodeValidate(NoStockOutModel noStockOut)
+        {
+            try
+            {
+                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.鍏ュ簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (inboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒伴噰璐崟锛歿noStockOut.inOder}");
+                }
+                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                if (matchedDetail == null)
+                {
+                    return WebResponseContent.Instance.Error($"鍦ㄩ噰璐崟 {noStockOut.inOder} 涓湭鎵惧埌鏉$爜涓� {noStockOut.barCode} 鐨勬槑缁嗐��");
+                }
+                matchedDetail.NoStockOutQty = 0;
+
+                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (outboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒板嚭搴撳崟锛歿noStockOut.inOder}");
+                }
+                var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                if (matchedCode == null)
+                {
+                    return WebResponseContent.Instance.Error($"鍦ㄥ嚭搴撳崟鐨勭墿鏂欑紪鐮佷腑鏈壘鍒颁笌閲囪喘鍗曚腑鐨剓matchedDetail.MaterielCode} 瀵瑰簲鐨勭墿鏂欍��");
+                }
+                matchedCode.NoStockOutQty = 0;
+
+                //鍓╀綑鍏ュ簱鏁伴噺鍗宠櫄鎷熷嚭鍏ュ簱鍓╀綑鍙嚭鏁伴噺
+                decimal outQuantity = matchedDetail.OrderQuantity - matchedDetail.ReceiptQuantity;
+                if (outQuantity == 0)
+                {
+                    return WebResponseContent.Instance.Error($"璇ラ噰璐崟涓殑鏉$爜瀵瑰簲鐨勫彲鍑烘暟閲忎负0");
+                }
+                if (matchedCode.OrderQuantity < outQuantity)
+                {
+                    return WebResponseContent.Instance.Error($"璇ラ噰璐崟涓殑鏉$爜瀵瑰簲鐨勫彲鍑烘暟閲忚秴鍑哄嚭搴撳崟鍑哄簱鏁伴噺{matchedDetail.OrderQuantity - matchedCode.OrderQuantity},涓嶆弧瓒虫暣鍖呭嚭搴�");
+                }
+                //鍗曟嵁鍑哄簱閿佸畾鏁伴噺
+                matchedDetail.NoStockOutQty += outQuantity;
+                matchedCode.NoStockOutQty += outQuantity;
+
+                if ((matchedCode.LockQuantity + matchedCode.NoStockOutQty) > matchedCode.OrderQuantity)
+                {
+                    return WebResponseContent.Instance.Error($"鍑哄簱鍗曟槑缁嗘暟閲忔孩鍑簕matchedCode.LockQuantity - matchedCode.OrderQuantity}");
+                }
+                matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.Inbounding.ObjToInt();
+                matchedCode.OrderDetailStatus = OrderDetailStatusEnum.AssignOver.ObjToInt();
+
+                _unitOfWorkManage.BeginTran();
+                _inboundOrderDetailService.UpdateData(matchedDetail);
+                _outboundOrderDetailService.UpdateData(matchedCode);
+                _unitOfWorkManage.CommitTran();
+                return WebResponseContent.Instance.OK();
+            }
+            catch (Exception ex)
+            {
+                _unitOfWorkManage.RollbackTran();
+                return WebResponseContent.Instance.Error(ex.Message);
+            }
+        }
+
+
+        public WebResponseContent DeleteBarcode(NoStockOutModel noStockOut)
+        {
+            try
+            {
+                Dt_InboundOrder inboundOrder = Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOut.inOder && x.OrderStatus != InOrderStatusEnum.鍏ュ簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (inboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒伴噰璐崟锛歿noStockOut.inOder}");
+                }
+                var matchedDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == noStockOut.barCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                if (matchedDetail == null)
+                {
+                    return WebResponseContent.Instance.Error($"鍦ㄩ噰璐崟 {noStockOut.inOder} 涓湭鎵惧埌鏉$爜涓� {noStockOut.barCode} 鐨勬槑缁嗐��");
+                }
+                matchedDetail.NoStockOutQty = 0;
+
+                if (matchedDetail.ReceiptQuantity == 0 && matchedDetail.OverInQuantity == 0)
+                {
+                    matchedDetail.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
+                }
+
+                Dt_OutboundOrder outboundOrder = Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOut.outOder && x.OrderStatus != OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (outboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒板嚭搴撳崟锛歿noStockOut.inOder}");
+                }
+                var matchedCode = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == matchedDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                if (matchedCode == null)
+                {
+                    return WebResponseContent.Instance.Error($"鍦ㄥ嚭搴撳崟鐨勭墿鏂欑紪鐮佷腑鏈壘鍒颁笌閲囪喘鍗曚腑鐨剓matchedDetail.MaterielCode} 瀵瑰簲鐨勭墿鏂欍��");
+                }
+                matchedCode.NoStockOutQty = 0;
+                if (matchedCode.LockQuantity == 0 && matchedCode.OverOutQuantity == 0)
+                {
+                    matchedCode.OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt();
+                }
+                _unitOfWorkManage.BeginTran();
+                _inboundOrderDetailService.UpdateData(matchedDetail);
+                _outboundOrderDetailService.UpdateData(matchedCode);
+                _unitOfWorkManage.CommitTran();
+                return WebResponseContent.Instance.OK();
+
+            }
+            catch (Exception ex)
+            {
+                _unitOfWorkManage.RollbackTran();
+                return WebResponseContent.Instance.Error(ex.Message);
+            }
+        }
+
+        public async Task<WebResponseContent> NoStockOutSubmit(NoStockOutSubmit noStockOutSubmit)
+        {
+            try
+            {
+                Dt_InboundOrder inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.InOderSubmit && x.OrderStatus != InOrderStatusEnum.鍏ュ簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (inboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒伴噰璐崟锛歿noStockOutSubmit.InOderSubmit}");
+                }
+                Dt_OutboundOrder outboundOrder = _inboundOrderRepository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.UpperOrderNo == noStockOutSubmit.OutOderSubmit && x.OrderStatus != OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt()).Includes(x => x.Details).First();
+                if (outboundOrder == null)
+                {
+                    return WebResponseContent.Instance.Error($"鏈壘鍒板嚭搴撳崟锛歿noStockOutSubmit.OutOderSubmit}");
+                }
+                List<Dt_InboundOrderDetail> inboundOrderDetails = new List<Dt_InboundOrderDetail>();
+                List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
+                foreach (var BarCode in noStockOutSubmit.BarCodeSubmit)
+                {
+                    var inboundOrderDetail = inboundOrder.Details.FirstOrDefault(detail => detail.Barcode == BarCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                    if (inboundOrderDetail == null)
+                    {
+                        return WebResponseContent.Instance.Error($"鍦ㄩ噰璐崟 {noStockOutSubmit.InOderSubmit} 涓湭鎵惧埌鏉$爜涓� {BarCode} 鐨勬槑缁嗐��");
+                    }
+                    var outboundOrderDetail = outboundOrder.Details.FirstOrDefault(detail => detail.MaterielCode == inboundOrderDetail.MaterielCode && detail.OrderDetailStatus != OrderDetailStatusEnum.Over.ObjToInt());
+
+                    if (outboundOrderDetail == null)
+                    {
+                        return WebResponseContent.Instance.Error($"鍦ㄥ嚭搴撳崟鐨勭墿鏂欑紪鐮佷腑鏈壘鍒颁笌閲囪喘鍗曚腑鐨剓inboundOrderDetail.MaterielCode} 瀵瑰簲鐨勭墿鏂欍��");
+                    }
+                    inboundOrderDetail.ReceiptQuantity += inboundOrderDetail.NoStockOutQty;
+                    inboundOrderDetail.OverInQuantity = inboundOrderDetail.ReceiptQuantity;
+                    inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
+                    inboundOrderDetails.Add(inboundOrderDetail);
+
+                    outboundOrderDetail.LockQuantity += outboundOrderDetail.NoStockOutQty;
+                    outboundOrderDetail.OverOutQuantity = outboundOrderDetail.LockQuantity;
+                    if (outboundOrderDetail.OrderQuantity == outboundOrderDetail.OverOutQuantity)
+                    {
+                        outboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
+                    }
+                    outboundOrderDetails.Add(outboundOrderDetail);
+
+                    var newLockInfo = new Dt_OutStockLockInfo
+                    {
+                        OrderNo = outboundOrder.UpperOrderNo,
+                        OrderDetailId = outboundOrderDetail.Id,
+                        OutboundBatchNo = outboundOrderDetail.BatchNo,
+                        MaterielCode = outboundOrderDetail.MaterielCode,
+                        MaterielName = outboundOrderDetail.MaterielName,
+                        StockId = 0,
+                        OrderQuantity = outboundOrderDetail.OrderQuantity,
+                        AssignQuantity = outboundOrderDetail.OverOutQuantity,
+                        PickedQty = 0,
+                        LocationCode = "绌�",
+                        PalletCode = "绌�",
+                        TaskNum = 0,
+                        Status = (int)OutLockStockStatusEnum.鎷i�夊畬鎴�,
+                        Unit = outboundOrderDetail.Unit,
+                        SupplyCode = outboundOrderDetail.SupplyCode?? "鏃�",
+                        OrderType = outboundOrder.OrderType,
+                        CurrentBarcode = inboundOrderDetail.Barcode,
+                        IsSplitted = 1,
+                        Operator = App.User.UserName,
+                        lineNo= outboundOrderDetail.lineNo,
+                        WarehouseCode = outboundOrderDetail.WarehouseCode ?? "鏃�",
+                        BarcodeQty=outboundOrderDetail.NoStockOutQty,
+                        BarcodeUnit =outboundOrderDetail.BarcodeUnit,
+                        BatchNo = outboundOrderDetail.BatchNo
+                    };
+                    _outStockLockInfoService.AddData(newLockInfo);
+                }
+                //鍒ゆ柇鍏ュ簱鍗曟嵁鏄庣粏鏄惁鍏ㄩ儴鏄畬鎴愮姸鎬�
+                int e = inboundOrder.Details.Count();
+                int w = inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
+                bool inoderOver = inboundOrder.Details.Count() == inboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
+                if (inoderOver)
+                {
+                    inboundOrder.OrderStatus = InOrderStatusEnum.鍏ュ簱瀹屾垚.ObjToInt();
+                }
+                else
+                {
+                    inboundOrder.OrderStatus = InOrderStatusEnum.鍏ュ簱涓�.ObjToInt();
+                }
+                //鍒ゆ柇鍑哄簱鍗曟嵁鏄庣粏鏄惁鍏ㄩ儴鏄畬鎴愮姸鎬�
+                bool outOderOver = outboundOrder.Details.Count() == outboundOrder.Details.Where(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt()).Count();
+                if (outOderOver)
+                {
+                    outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱瀹屾垚.ObjToInt();
+                }
+                else
+                {
+                    outboundOrder.OrderStatus = OutOrderStatusEnum.鍑哄簱涓�.ObjToInt();
+                }
+                //鏁版嵁澶勭悊
+                _unitOfWorkManage.BeginTran();
+                _inboundOrderDetailService.UpdateData(inboundOrderDetails);
+                _outboundOrderDetailService.UpdateData(outboundOrderDetails);
+                _inboundOrderRepository.UpdateData(inboundOrder);
+                _outboundOrderService.UpdateData(outboundOrder);
+                _unitOfWorkManage.CommitTran();
+
+                //鍏ュ簱鍥炰紶MES
+                var infeedmodel = new FeedbackInboundRequestModel
+                {
+                    reqCode = Guid.NewGuid().ToString(),
+                    reqTime = DateTime.Now.ToString(),
+                    business_type = inboundOrder.BusinessType,
+                    factoryArea = inboundOrder.FactoryArea,
+                    operationType = 1,
+                    Operator = App.User.UserName,
+                    orderNo = inboundOrder.UpperOrderNo,
+                    status = inboundOrder.OrderStatus,
+                    details = new List<FeedbackInboundDetailsModel>()
+
+                };
+
+                var groupedData = inboundOrderDetails.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
+                    .Select(group => new FeedbackInboundDetailsModel
+                    {
+                        materialCode = group.Key.MaterielCode,
+                        supplyCode = group.Key.SupplyCode,
+                        batchNo = group.Key.BatchNo,
+                        lineNo = group.Key.lineNo,
+                        warehouseCode = group.Key.WarehouseCode,
+                        qty = group.Sum(x => x.BarcodeQty),
+                        // warehouseCode= "1072",
+                        unit = group.Key.BarcodeUnit,
+                        barcodes = group.Select(row => new FeedbackBarcodesModel
+                        {
+                            barcode = row.Barcode,
+                            qty = row.BarcodeQty
+                        }).ToList()
+                    }).ToList();
+                infeedmodel.details = groupedData;
+
+                var result1 = await _invokeMESService.FeedbackInbound(infeedmodel);
+                if (result1 != null && result1.code == 200)
+                {
+                    _inboundOrderRepository.Db.Updateable<Dt_InboundOrder>().SetColumns(it => new Dt_InboundOrder { ReturnToMESStatus = 1 })
+                    .Where(it => it.Id == inboundOrder.Id).ExecuteCommand();
+                    _inboundOrderDetailService.Db.Updateable<Dt_InboundOrderDetail>().SetColumns(it => new Dt_InboundOrderDetail { ReturnToMESStatus = 1 })
+                    .Where(it => it.OrderId == inboundOrder.Id).ExecuteCommand();
+                }
+                //鍑哄簱鍥炰紶MES
+                var outfeedmodel = new FeedbackOutboundRequestModel
+                    {
+                        reqCode = Guid.NewGuid().ToString(),
+                        reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+                        business_type = outboundOrder.BusinessType,
+                        factoryArea = outboundOrder.FactoryArea,
+                        operationType = 1,
+                        Operator = App.User.UserName,
+                        orderNo = outboundOrder.UpperOrderNo,
+                        documentsNO = outboundOrder.OrderNo,
+                        status = outboundOrder.OrderStatus,
+                        details = new List<FeedbackOutboundDetailsModel>()
+                    };
+                foreach (var detail in outboundOrder.Details)
+                {
+                    // 鑾峰彇璇ユ槑缁嗗搴旂殑鏉$爜淇℃伅锛堜粠閿佸畾璁板綍锛�
+                    var detailLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
+                        .Where(x => x.OrderNo == outboundOrder.UpperOrderNo &&
+                                    x.OrderDetailId == detail.Id &&
+                                        (x.Status == (int)OutLockStockStatusEnum.鎷i�夊畬鎴� || x.Status == (int)OutLockStockStatusEnum.宸插洖搴�))
+                        .ToListAsync();
+
+                    var groupdata = detailLocks.GroupBy(item => new { item.MaterielCode, item.lineNo, item.BarcodeUnit, item.WarehouseCode })
+                            .Select(group => new FeedbackOutboundDetailsModel
+                            {
+                                materialCode = group.Key.MaterielCode,
+                                lineNo = group.Key.lineNo,  
+                                warehouseCode = group.Key.WarehouseCode,
+                                qty = group.Sum(x => x.BarcodeQty),
+                                currentDeliveryQty = group.Sum(x => x.BarcodeQty),
+                                unit = group.Key.BarcodeUnit,
+                                barcodes = group.Select(lockInfo => new WIDESEA_DTO.Outbound.BarcodesModel
+                                {
+                                    barcode = lockInfo.CurrentBarcode,
+                                    supplyCode = lockInfo.SupplyCode,
+                                    batchNo = lockInfo.BatchNo,
+                                    unit = lockInfo.BarcodeUnit,
+                                    qty = lockInfo.BarcodeQty
+                                }).ToList()
+                            }).ToList();
+                    outfeedmodel.details.AddRange(groupdata);
+                }
+                var result = await _invokeMESService.FeedbackOutbound(outfeedmodel);
+                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.Id == outboundOrder.Id)
+                        .ExecuteCommandAsync();
+                }
+                return WebResponseContent.Instance.OK();
+            }
+            catch (Exception ex)
+            {
+                _unitOfWorkManage.RollbackTran();
+                return WebResponseContent.Instance.Error(ex.Message);
+            }
+        }
+        #endregion
+
+        public WebResponseContent UnPalletQuantity(string orderNo)
+        {
+            // 鍒濆鍖栬繑鍥濪TO锛堥粯璁ゅ�奸兘涓�0锛岄伩鍏峮ull锛�
+            var resultDTO = new PalletSumQuantityDTO
+            {
+                StockSumQuantity = 0,
+                StockCount = 0,
+                UniqueUnit = ""
+            };
+            WebResponseContent content = new WebResponseContent();
+            try
+            {
+                if (string.IsNullOrWhiteSpace(orderNo))
+                {
+                    return content.Error("浼犲叆鐨勮鍗曞彿orderNo涓虹┖鎴栫┖鐧�");
+                }
+                var orderDetail = Db.Queryable<Dt_PickingRecord>().Where(s => s.OrderNo == orderNo).ToList();
+                if (orderDetail == null)
+                {
+                    return content.Error("鏈壘鍒板崟鎹�");
+                }
+                var unitGroups = orderDetail.GroupBy(d => d.BarcodeUnit).ToList();
+                if (unitGroups.Count == 1)
+                {
+                    resultDTO.UniqueUnit = unitGroups.First().Key;
+                }
+                else
+                {
+                    resultDTO.UniqueUnit = "";
+                }
+                var validDetails = _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>().Where(s => s.OrderNo == orderNo).ToList();
+                resultDTO.StockSumQuantity = orderDetail.Sum(d => d.PickQuantity);
+                resultDTO.StockCount = orderDetail.Count;
+                if (validDetails.Any())
+                {
+                    resultDTO.StockSumQuantity -= validDetails.Sum(d => d.StockQuantity);
+                    // 鏄庣粏璁板綍鏁帮細绗﹀悎鏉′欢鐨勬湁鏁堣褰曟潯鏁�
+                    resultDTO.StockCount -= validDetails.Count;
+                }
+                return content.OK("", resultDTO);
+            }
+            catch (Exception ex)
+            {
+                return content.Error("SumQuantity 缁熻搴撳瓨鏁伴噺澶辫触锛岃鍗曞彿锛歿OrderNo}");
+            }
+        }
+
+        public WebResponseContent BarcodeMaterielGroup(BarcodeMaterielGroupDTO materielGroupDTO)
+        {
+
+            WebResponseContent content = new WebResponseContent();
+            try
+            {
+                (bool, string, object?) result2 = ModelValidate.ValidateModelData(materielGroupDTO);
+                if (!result2.Item1) return content = WebResponseContent.Instance.Error(result2.Item2);
+
+                //  materielGroupDTO.WarehouseCode
+                var code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>().Where(x => x.Code == materielGroupDTO.WarehouseType).Select(x => x.Code).First();
+                if (string.IsNullOrEmpty(code))
+                {
+                    return content = WebResponseContent.Instance.Error($"浠撳簱涓病鏈夎{materielGroupDTO.WarehouseType}缂栧彿銆�");
+                }
+
+                if (materielGroupDTO.orderTypes == InOrderTypeEnum.ReCheck.ObjToInt())
+                {
+                    var dborder = _reCheckOrderService.Db.Queryable<Dt_ReCheckOrder>().First(x => x.OrderNo == materielGroupDTO.OrderNo);
+                    if (dborder != null && dborder.SignSeq == 0)
+                    {
+                        return content.Error("鍙湁鎷垮埌閲嶆缁撴灉鎵嶈兘鍏ュ簱锛�");
+                    }
+                    else
+                    {
+                        return content.Error("娌℃湁鎵惧埌閲嶆鍗曟嵁鏁版嵁銆�");
+                    }
+                }
+                //  Dt_InboundOrder inboundOrder = GetInboundOrder(materielGroupDTO.OrderNo);
+
+                var dbinboundOrderDetails = Db.Queryable<Dt_PickingRecord>().Where(x => x.OrderNo == materielGroupDTO.OrderNo && !x.IsCancelled && x.Barcode == materielGroupDTO.Barcodes).ToList();
+
+                if (dbinboundOrderDetails != null && !dbinboundOrderDetails.Any())
+                {
+                    return content = WebResponseContent.Instance.Error($"鍗曟嵁涓病鏈夎{materielGroupDTO.Barcodes}鏉$爜鏁版嵁銆�");
+                }
+
+                List<string?> materielCodes = dbinboundOrderDetails.GroupBy(x => x.Barcode).Select(x => x.Key).ToList();
+
+                Dt_StockInfo? stockInfo = _stockService.StockInfoService.GetStockByPalletCode(materielGroupDTO.PalletCode);
+
+
+
+                (bool, string, object?) result = CheckMaterielGroupParam(materielGroupDTO, materielCodes, stockInfo);
+                if (!result.Item1) return content = WebResponseContent.Instance.Error(result.Item2);
+
+                if (stockInfo == null)
+                {
+                    stockInfo = new Dt_StockInfo() { PalletType = (int)PalletTypeEnum.None, LocationType = materielGroupDTO.locationType.ObjToInt() };
+                    stockInfo.Details = new List<Dt_StockInfoDetail>();
+                }
+                var inboindId = 0; Dt_InboundOrder dt_InboundOrder = null;
+                var dbinbound = _inboundOrderRepository.QueryData(x => x.InboundOrderNo == dbinboundOrderDetails.First().OrderNo).First();
+                if (dbinbound == null)
+                {
+                    dt_InboundOrder = new Dt_InboundOrder
+                    {
+                        WarehouseId = 0,
+                        InboundOrderNo = dbinboundOrderDetails.First()?.OrderNo,
+                        UpperOrderNo = dbinboundOrderDetails.First()?.OrderNo,
+                        SupplierId = dbinboundOrderDetails.First()?.SupplyCode,
+                        OrderType = materielGroupDTO.orderTypes,
+
+                        BusinessType = materielGroupDTO.orderTypes.ToString(),
+
+                        FactoryArea = dbinboundOrderDetails.First()?.FactoryArea,
+                        Remark = "",
+                        Details = new List<Dt_InboundOrderDetail>()
+                    };
+                    inboindId = _inboundOrderRepository.AddData(dt_InboundOrder);
+                }
+                else
+                {
+                    dt_InboundOrder = new Dt_InboundOrder { Details = new List<Dt_InboundOrderDetail>() };
+                    inboindId = dbinbound.Id;
+                }
+
+                foreach (var item in dbinboundOrderDetails)
+                {
+                    stockInfo.Details.Add(new Dt_StockInfoDetail
+                    {
+                        StockId = stockInfo == null ? 0 : stockInfo.Id,
+                        Barcode = item.Barcode,
+                        MaterielCode = item.MaterielCode,
+                        BatchNo = item.BatchNo,
+                        Unit = item.BarcodeUnit,
+                        InboundOrderRowNo = item.lineNo,
+                        SupplyCode = item.SupplyCode,
+                        WarehouseCode = materielGroupDTO.WarehouseType,
+                        StockQuantity = item.PickQuantity,
+                        BarcodeQty = item.BarcodeQty,
+                        BarcodeUnit = item.BarcodeUnit,
+                        FactoryArea = item.FactoryArea,
+                        Status = 0,
+                        OrderNo = item.OrderNo,
+                        BusinessType = InOrderTypeEnum.InternalAllocat.ObjToInt().ToString()
+
+                    });
+                    item.WarehouseCode = item.WarehouseCode;
+
+                    dt_InboundOrder.Details.Add(new Dt_InboundOrderDetail
+                    {
+                        OrderId = inboindId,
+                        MaterielCode = item.MaterielCode,
+                        MaterielName = "",
+                        BatchNo = item.BatchNo,
+                        OrderQuantity = item.PickQuantity,
+                        ReceiptQuantity = 0,
+                        OverInQuantity = 0,
+                        Unit = item.BarcodeUnit,
+                        RowNo = 0,
+                        lineNo = item.lineNo,
+                        SupplyCode = item.SupplyCode,
+                        WarehouseCode = item.WarehouseCode,
+                        Barcode = item.Barcode,
+                        OutBoxbarcodes = "",
+                        BarcodeQty = (decimal)item.BarcodeQty,
+                        BarcodeUnit = item.BarcodeUnit
+                    });
+                }
+
+                _inboundOrderDetailService.Db.Insertable<Dt_InboundOrderDetail>(dt_InboundOrder.Details);
+                if (stockInfo.Id == 0)
+                {
+                    stockInfo.PalletCode = materielGroupDTO.PalletCode;
+                    stockInfo.StockStatus = StockStatusEmun.缁勭洏鏆傚瓨.ObjToInt();
+                }
+                stockInfo.PalletType = (int)PalletTypeEnum.None;
+
+                List<int> updateDetailIds = dbinboundOrderDetails.Select(x => x.Id).ToList();
+
+                try
+                {
+                    _unitOfWorkManage.BeginTran();
+
+                    _stockService.StockInfoService.AddMaterielGroup(stockInfo);
+                    _unitOfWorkManage.CommitTran();
+                    return WebResponseContent.Instance.OK();
+                }
+                catch (Exception ex)
+                {
+                    _unitOfWorkManage.RollbackTran();
+                    return WebResponseContent.Instance.Error(ex.Message);
+                }
+            }
+            catch (Exception ex)
+            {
+                content = WebResponseContent.Instance.Error(ex.Message);
+            }
+            finally
+            {
+
+            }
+            return content;
+
+        }
+
+
+        public (bool, string, object?) CheckMaterielGroupParam(BarcodeMaterielGroupDTO materielGroupDTO, List<string> barcodeCodes, Dt_StockInfo stockInfo)
+        {
+            (bool, string, object?) result = ModelValidate.ValidateModelData(materielGroupDTO);
+            if (!result.Item1) return result;
+
+            if (_taskRepository.QueryFirst(x => x.PalletCode == materielGroupDTO.PalletCode) != null)
+            {
+                return (false, "璇ユ墭鐩樺彿宸叉湁浠诲姟", materielGroupDTO);
+            }
+
+            if (stockInfo != null && !string.IsNullOrEmpty(stockInfo.LocationCode) && stockInfo.StockStatus != StockStatusEmun.缁勭洏鏆傚瓨.ObjToInt())
+            {
+                return (false, "宸蹭笂鏋剁殑鎵樼洏涓嶈兘鍐嶆缁勭洏", materielGroupDTO);
+            }
+
+            if (_stockService.StockInfoDetailService.ExistBarcodes(barcodeCodes))
+            {
+                return (false, $"{barcodeCodes[0]} 鏉$爜鍦ㄥ簱瀛樹腑宸插瓨鍦�", materielGroupDTO);
+            }
+
+
+
+            return (true, "鎴愬姛", materielGroupDTO);
+        }
+
+
         #endregion
     }
+
+
 
     #region 鏀寔绫诲畾涔�
 
@@ -2272,12 +3189,20 @@
         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 bool HasUnallocatedLocks { get; set; }
+        public List<Dt_OutStockLockInfo> UnallocatedLocks { get; set; } = new List<Dt_OutStockLockInfo>();
+        public decimal UnallocatedLocksReturnQty { 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>();
 
+        public List<string> AllBarcodes { get; set; } = new List<string>();
         // 绌烘墭鐩樼浉鍏冲睘鎬�
         public bool IsEmptyPallet { get; set; }
         public bool HasActiveTasks { get; set; }

--
Gitblit v1.9.3