1
647556386
2026-03-19 ddea73b709a57e2719e722cabf2d2c1518e36685
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -330,7 +330,7 @@
                    foreach (var detail in materielCalc.Details)
                    {
                        if (remainingToLock <= 0) break;
                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity;
                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity-detail.LockQuantity;
                        if (maxLockableQty <= 0) continue;
                        decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty);
                        detail.LockQuantity += currentLockQty;
@@ -1970,6 +1970,7 @@
                OperateType = "出库完成",
                InsertTime = DateTime.Now,
                StockId = stockDetail.StockId,
                Barcode = stockDetail.Barcode,
                MaterielCode = stockDetail.MaterielCode,
                MaterielName = stockDetail.MaterielName,
                OrderNo = stockDetail.OrderNo,
@@ -1987,6 +1988,7 @@
                Creater = stockDetail.Creater,
                CreateDate = stockDetail.CreateDate,
                WarehouseCode = stockDetail.WarehouseCode,
                ValidDate = stockDetail.ValidDate,
                Remark = $"出库完成删除,条码:{request.Barcode},原数量:{stockDetail.StockQuantity},出库数量:{actualOutboundQuantity},操作者:{request.Operator}"
            };
            _stockDetailHistoryRepository.AddData(historyRecord);
@@ -2444,6 +2446,303 @@
        #endregion
        #region æ’¤é”€æ‹£é€‰
        /// <summary>
        /// æ’¤é”€æ‹£é€‰æ¡ç ï¼ˆåå‘回滚出库拣选操作)
        /// </summary>
        /// <param name="request">撤销拣选请求(至少包含条码、订单号、托盘号)</param>
        /// <returns>撤销响应</returns>
        public WebResponseContent ReversePicking(ReversePickingRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
            ReversePickingResponseDTO response = new ReversePickingResponseDTO();
            try
            {
                if (string.IsNullOrWhiteSpace(request.Barcode) || string.IsNullOrWhiteSpace(request.OrderNo) || string.IsNullOrWhiteSpace(request.PalletCode))
                {
                    response.Success = false;
                    response.Message = "条码、订单号、托盘号不能为空";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                Dt_StockInfo stockInfo = _stockInfoRepository.QueryFirst(x => x.PalletCode == request.PalletCode);
                if (stockInfo == null)
                {
                    response.Success = false;
                    response.Message = $"托盘号 {request.PalletCode} å¯¹åº”的库存不存在";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
                if (outboundOrder == null)
                {
                    response.Success = false;
                    response.Message = $"出库单 {request.OrderNo} ä¸å­˜åœ¨";
                    return WebResponseContent.Instance.Error(response.Message);
                }
                Dt_StockInfoDetail_Hty historyDetail = _stockDetailHistoryRepository.QueryFirst(x =>
                    x.Barcode == request.Barcode &&
                    (x.OperateType == "出库完成" || x.OperateType == "拆包-原始记录"));
                if(historyDetail != null)
                {
                    double minutesDiff = (DateTime.Now - historyDetail.InsertTime).TotalMinutes;
                    if (minutesDiff >= 30)
                    {
                        return WebResponseContent.Instance.Error($"条码{request.Barcode}已无法撤销");
                    }
                }
                Dt_OutStockLockInfo lockInfo = _outboundLockInfoRepository.QueryFirst(x =>
                    x.OrderNo == request.OrderNo &&
                    x.StockId == stockInfo.Id &&
                    x.MaterielCode == historyDetail.MaterielCode &&
                    x.PalletCode == stockInfo.PalletCode);
                _unitOfWorkManage.BeginTran();
                try
                {
                    if(lockInfo == null)
                    {
                        return WebResponseContent.Instance.Error("该托盘已全部拣选完,不允许撤销");
                    }
                    bool isUnpack = historyDetail.OperateType == "拆包-原始记录";
                    if (isUnpack)
                    {
                        return WebResponseContent.Instance.Error("该条码已拆包,不允许撤销");
                    }
                    else
                    {
                        ReverseFullOutboundOperation(historyDetail, stockInfo, request);
                    }
                    RollbackOutboundOrderDetails(historyDetail.MaterielCode, request.OrderNo, historyDetail.StockQuantity, stockInfo.Id);
                    if (lockInfo != null)
                    {
                        lockInfo.SortedQuantity = Math.Max(0, (decimal)(lockInfo.SortedQuantity - historyDetail.StockQuantity));
                        if (lockInfo.SortedQuantity == 0)
                        {
                            _outboundLockInfoRepository.UpdateData(lockInfo);
                        }
                        else
                        {
                            _outboundLockInfoRepository.UpdateData(lockInfo);
                        }
                    }
                    _stockDetailHistoryRepository.DeleteData(historyDetail);
                    DeleteStockChangeRecord(request.Barcode, request.OrderNo);
                    if (!CheckOutboundOrderCompleted(request.OrderNo))
                    {
                        UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.出库中.ObjToInt());
                    }
                    _unitOfWorkManage.CommitTran();
                    response.Success = true;
                    response.Message = $"条码 {request.Barcode} æ’¤é”€æ‹£é€‰æˆåŠŸ";
                    response.Barcode = request.Barcode;
                    response.RestoredQuantity = historyDetail.StockQuantity;
                    content = WebResponseContent.Instance.OK(data: response);
                }
                catch (Exception ex)
                {
                    _unitOfWorkManage.RollbackTran();
                    response.Success = false;
                    response.Message = $"撤销拣选失败:{ex.Message}";
                    content = WebResponseContent.Instance.Error(response.Message);
                }
            }
            catch (Exception ex)
            {
                response.Success = false;
                response.Message = $"处理撤销拣选失败:{ex.Message}";
                content = WebResponseContent.Instance.Error(response.Message);
            }
            return content;
        }
        /// <summary>
        /// æ’¤é”€æ‹†åŒ…出库操作(反向恢复拆包前状态)
        /// </summary>
        private void ReverseUnpackOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request, Dt_OutboundOrder outboundOrder)
        {
            Dt_StockInfoDetail currentDetail = _stockDetailRepository.QueryFirst(x =>
                x.Barcode == request.Barcode &&
                x.StockId == stockInfo.Id &&
                x.MaterielCode == historyDetail.MaterielCode);
            if (currentDetail != null)
            {
                currentDetail.StockQuantity = historyDetail.StockQuantity;
                currentDetail.Remark = $"撤销拆包拣选,恢复原始数量:{historyDetail.StockQuantity},操作者:{request.Operator}";
                _stockDetailRepository.UpdateData(currentDetail);
            }
            else
            {
                Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail
                {
                    StockId = historyDetail.StockId,
                    MaterielCode = historyDetail.MaterielCode,
                    MaterielName = historyDetail.MaterielName,
                    Barcode =historyDetail.Barcode,
                    OrderNo = historyDetail.OrderNo,
                    BatchNo = historyDetail.BatchNo,
                    ProductionDate = historyDetail.ProductionDate,
                    EffectiveDate = historyDetail.EffectiveDate,
                    SerialNumber = historyDetail.SerialNumber,
                    StockQuantity = historyDetail.StockQuantity,
                    OutboundQuantity = historyDetail.OutboundQuantity,
                    Status = historyDetail.Status,
                    Unit = historyDetail.Unit,
                    InboundOrderRowNo = historyDetail.InboundOrderRowNo,
                    SupplyCode = historyDetail.SupplyCode,
                    Creater = request.Operator,
                    CreateDate = DateTime.Now,
                    FactoryArea = historyDetail.FactoryArea,
                    WarehouseCode = historyDetail.WarehouseCode,
                    Remark = $"撤销拆包拣选,重新创建库存明细,条码:{request.Barcode},操作者:{request.Operator}"
                };
                _stockDetailRepository.AddData(restoreDetail);
            }
            List<Dt_MaterialCodeInfo> materialCodeInfos = _basicService.MaterielCodeInfoService.Repository.QueryData(x =>
                x.OldBarcode == request.Barcode &&
                x.OrderNo == request.OrderNo);
            if (materialCodeInfos.Any())
            {
                _basicService.MaterielCodeInfoService.Repository.DeleteData(materialCodeInfos);
            }
            if (outboundOrder.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
            {
                Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x =>
                    x.Barcode == request.Barcode &&
                    x.OrderNo == request.OrderNo);
                if (allocateMaterialInfo != null)
                {
                    _allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo);
                }
            }
        }
        /// <summary>
        /// æ’¤é”€å®Œæ•´å‡ºåº“操作(恢复被删除的库存明细)
        /// </summary>
        private void ReverseFullOutboundOperation(Dt_StockInfoDetail_Hty historyDetail, Dt_StockInfo stockInfo, ReversePickingRequestDTO request)
        {
            Dt_StockInfoDetail restoreDetail = new Dt_StockInfoDetail
            {
                StockId = historyDetail.StockId,
                MaterielCode = historyDetail.MaterielCode,
                MaterielName = historyDetail.MaterielName,
                Barcode = historyDetail.Barcode,
                OrderNo = historyDetail.OrderNo,
                BatchNo = historyDetail.BatchNo,
                ProductionDate = historyDetail.ProductionDate,
                EffectiveDate = historyDetail.EffectiveDate,
                SerialNumber = historyDetail.SerialNumber,
                StockQuantity = historyDetail.StockQuantity,
                OutboundQuantity = historyDetail.OutboundQuantity - historyDetail.StockQuantity,
                Status = historyDetail.Status,
                Unit = historyDetail.Unit,
                InboundOrderRowNo = historyDetail.InboundOrderRowNo,
                SupplyCode = historyDetail.SupplyCode,
                Creater = request.Operator,
                CreateDate = DateTime.Now,
                FactoryArea = historyDetail.FactoryArea,
                WarehouseCode = historyDetail.WarehouseCode,
                Remark = $"撤销完整出库拣选,恢复库存明细,条码:{request.Barcode},操作者:{request.Operator}"
            };
            _stockDetailRepository.AddData(restoreDetail);
            if (request.OrderType == InOrderTypeEnum.InternalAllocat.ObjToInt())
            {
                Dt_AllocateMaterialInfo allocateMaterialInfo = _allocateMaterialInfoRepository.QueryFirst(x =>
                    x.Barcode == request.Barcode &&
                    x.OrderNo == request.OrderNo);
                if (allocateMaterialInfo != null)
                {
                    _allocateMaterialInfoRepository.DeleteData(allocateMaterialInfo);
                }
            }
        }
        /// <summary>
        /// å›žæ»šå‡ºåº“单明细(扣减已出库数量)
        /// </summary>
        private void RollbackOutboundOrderDetails(string materielCode, string orderNo, decimal rollbackQuantity, int stockId)
        {
            Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
            List<Dt_OutboundOrderDetail> details = _detailRepository.QueryData(x =>
                x.OrderId == outboundOrder.Id &&
                x.MaterielCode == materielCode &&
                x.OverOutQuantity > 0);
            if (!details.Any()) return;
            decimal remainingRollbackQty = rollbackQuantity;
            foreach (var detail in details)
            {
                if (remainingRollbackQty <= 0) break;
                decimal rollbackQty = Math.Min(remainingRollbackQty, detail.OverOutQuantity);
                detail.OverOutQuantity -= rollbackQty;
                detail.CurrentDeliveryQty -= rollbackQty;
                if (detail.OrderDetailStatus == (int)OrderDetailStatusEnum.Over && detail.OverOutQuantity < detail.OrderQuantity - detail.MoveQty)
                {
                    detail.OrderDetailStatus = (int)OrderDetailStatusEnum.New;
                }
                if (!string.IsNullOrEmpty(detail.ReturnJsonData))
                {
                    List<Barcodes> barcodesList = JsonConvert.DeserializeObject<List<Barcodes>>(detail.ReturnJsonData) ?? new List<Barcodes>();
                    var targetBarcode = barcodesList.FirstOrDefault(x => x.Barcode == materielCode);
                    if (targetBarcode != null)
                    {
                        barcodesList.Remove(targetBarcode);
                        detail.ReturnJsonData = JsonConvert.SerializeObject(barcodesList, new JsonSerializerSettings
                        {
                            ContractResolver = new CamelCasePropertyNamesContractResolver()
                        });
                    }
                }
                _detailRepository.UpdateData(detail);
                remainingRollbackQty -= rollbackQty;
            }
        }
        /// <summary>
        /// åˆ é™¤åº“存变动记录(拣选时生成的)
        /// </summary>
        private void DeleteStockChangeRecord(string barcode, string orderNo)
        {
            Dt_StockQuantityChangeRecord changeRecord = _stockChangeRepository.QueryFirst(x =>
                x.OriginalSerilNumber == barcode &&
                x.OrderNo == orderNo &&
                x.ChangeType == (int)StockChangeTypeEnum.Outbound);
            if (changeRecord != null)
            {
                _stockChangeRepository.DeleteData(changeRecord);
            }
        }
        #endregion
    }
}