服务端的TigerApi 框架,基于.NET6 2024 版本
Rodney Chen
4 天以前 36746596927952a6b860129a62eec9059bf083a7
Tiger.Business.WMS/Transaction/Yada/Out_BIZ_WMS_PREP.cs
@@ -9,10 +9,12 @@
using System.Threading.Tasks;
using Tiger.Model;
using Tiger.IBusiness;
using Tiger.Business.WMS.Common;
using Org.BouncyCastle.Ocsp;
using Tiger.Model.Sharetronic.Shelf;
using Tiger.Business.WMS.Sharetronic.Shelf;
using Tiger.Model.Entitys.MES.U9C;
using Tiger.Model.Entitys.MES.Position;
using MailKit.Search;
using Tiger.Business.WMS.Extensions;
using static Tiger.Model.BIZ_WMS_PREP_BTH;
namespace Tiger.Business.WMS.Transaction
{
@@ -24,12 +26,20 @@
        public IOut_BIZ_WMS_PREP Init(string id, string userCode, string apiHost, string orgCode)
        {
            base.Init(id, userCode, apiHost, orgCode);
            CurPREP = new(MainDB, userCode, orgCode);
            Logger.Console.Info($"Start {this.GetType().Name} Transaction[ID: {TransID}]");
            return this;
        }
        #region Propertys & Variables
        public Preparation CurPREP { get; set; }
        private List<BIZ_WMS_PREP_SN> CurSn = new();
        private BIZ_WMS_PREP_DTL CurPrepDtl = new();
        public List<SuggestItem> Suggests { get; set; } = new();
        public List<WMS_ITEM_POOL> CurPoolList => Suggests.Where(q => !q.poolItem.IsNullOrEmpty()).Select(q => q.poolItem).ToList();
        public List<WMS_LOCATION> LocationHis { get; set; } = new();
        private bool isExceed = false;
        private bool isNeedCut = false;
        #endregion
        #region Functions
@@ -41,33 +51,331 @@
            var action = new ApiAction<ScanOutput>(new ScanOutput());
            try
            {
                if (input.SN.IsNullOrEmpty())
                if (input.Command == "ScanOrder")
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("条码不能为空");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.SnEmptyFailure");
                    return SetOutPutMqttMsg(action, input.Locale);
                    action = await ScanOrder(input);
                }
                //判断扫描的是否货架
                var whUnit = await MainDB.Queryable<V_WH_UNIT>().Where(t => (t.SHELF_CODE.ToUpper() == input.SN || t.LOCATION_CODE.ToUpper() == input.SN) && t.AUTH_ORG == OrgCode).IncludesAllFirstLayer().FirstAsync();
                //if (string.IsNullOrEmpty(CurScanShelf?.ShelfCode))
                if (!whUnit.IsNullOrEmpty() || string.IsNullOrEmpty(CurScanShelf?.ShelfCode))
                else if (input.Command == "SelectItem")
                {
                    action = await ScanShelf(input.SN, whUnit);
                    action = await SelectItem(input);
                }
                else//扫描物料并复核
                else if (input.Command == "ConfirmExceed")
                {
                    action = await ScanItem(input);
                    action = await ConfirmExceed(input);
                }
                else
                {
                    if (CurPREP.IsNullOrEmpty())
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("备料前请先选择需要备料的单据");
                        action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.NotScanOrder");
                        action.Data.Command = "ScanOrder";
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    if (CurPrepDtl.IsNullOrEmpty())
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("备料前请先选择需要备料的物料行");
                        action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.NotSelectItem");
                        action.Data.Command = "SelectItem";
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    if (input.SN.IsNullOrEmpty())
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("条码不能为空");
                        action.LocaleMsg = Biz.L("WMS.Default.ScanItem.SnEmptyFailure");
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    //扫描物料并复核
                    {
                        action = await ScanItem(input);
                    }
                }
            }
            catch (Exception ex)
            {
                //取消当前操作
                ResetScan();
                ResetTrans();
                //action.CatchExceptionWithLog(ex, $"扫描[{input.SN}]异常");
                action.CatchExceptionWithLog(ex, Biz.L("WMS.Default.Scan.ScanException", input.SN));
            }
            return SetOutPutMqttMsg(action, input.Locale);
        }
        /// <summary>
        /// 扫描下架单据号码
        /// </summary>
        public async Task<ApiAction<ScanOutput>> ScanOrder(BaseInput input)
        {
            var action = new ApiAction<ScanOutput>(new ScanOutput());
            try
            {
                var inputOrder = input?.Data?.JsonToObject<BIZ_WMS_PREP>();
                if ((inputOrder?.BIZ_TYPE).IsNullOrEmpty() || (inputOrder?.SOURCE_ORDER ?? "").IsNullOrEmpty())
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"请选择正确的备料任务类型和单号");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanOrder.InputOrderIsNull");
                    return action;
                }
                if (!CurPREP.Order.IsNullOrEmpty() && inputOrder.BIZ_TYPE != CurPREP.Order.BIZ_TYPE)
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"当前备料任务[{0}]的业务类型为{1},如需切换备料业务,请退出后重新选择");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanOrder.BizTypeError", CurPREP.Order.ORDER_NO, CurPREP.Order.BIZ_TYPE.GetEnumDesc<BIZ_WMS_PREP.BIZ_TYPEs>());
                    return action;
                }
                if (CurPREP.Order.IsNullOrEmpty() || !CurPREP.Order.SourceOrders.Any(q => q.SOURCE_ORDER == inputOrder.SOURCE_ORDER))
                {
                    action = CurPREP.AddOrder(inputOrder.BIZ_TYPE.GetEnum<BIZ_WMS_PREP.BIZ_TYPEs>(), inputOrder.SOURCE_CODE);
                }
                else
                {
                    //action.LocaleMsg = Biz.L($"当前备料任务[{0}]已包含单据[{1}],无需重复操作");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanOrder.Repeat", CurPREP.Order.ORDER_NO, inputOrder.SOURCE_ORDER);
                }
                action.Data.Command = "SelectItem";
                action.Data.Data = CurPREP.Order;
            }
            catch (Exception ex)
            {
                //取消当前操作
                ResetTrans();
                //action.CatchExceptionWithLog(ex, $"备料任务扫描单据异常(Data: {0})");
                action.CatchExceptionWithLog(ex, Biz.L("WMS.Out_BIZ_WMS_PREP.ScanOrderException", input?.Data));
            }
            return action;
        }
        /// <summary>
        ///  获取当前备料的备料任务信息
        /// </summary>
        /// <returns></returns>
        public async Task<ApiAction<BIZ_WMS_PREP>> GetPrepInfo()
        {
            var action = new ApiAction<BIZ_WMS_PREP>();
            CurPREP.Order = MainDB.Queryable<BIZ_WMS_PREP>().Where(q => q.ID == CurPREP.Order.ID).IncludesAllFirstLayer().First();
            action.Data = CurPREP.Order;
            return action;
        }
        /// <summary>
        /// 选择备料任务中要下架物料行
        /// </summary>
        public async Task<ApiAction<ScanOutput>> SelectItem(BaseInput input)
        {
            var action = new ApiAction<ScanOutput>(new ScanOutput());
            try
            {
                var option = CurPREP.Option = new BaseInput<SuggestOption>(input).Data;
                var prepDtl = MainDB.Queryable<BIZ_WMS_PREP_DTL>().Where(q => q.ID == option.LineID).First();
                if (prepDtl.IsNullOrEmpty())
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("备料前请先选择需要备料的物料行");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.NotSelectItem");
                    return action;
                }
                if (prepDtl.QTY_PREP > CurPREP.BizType.GetActReqQty(prepDtl))
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("当前选择物料行[{0}]已备料数量[{1}],以满足实际需求数量[{2}],无需继续备料");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.SelectItem.PrepFinish", prepDtl.ITEM_CODE, prepDtl.QTY_PREP, CurPREP.BizType.GetActReqQty(prepDtl));
                    return action;
                }
                //删除物料池中上一次分配的物料
                MainDB.Deleteable<WMS_ITEM_POOL>().Where(q => q.TRANS_NO == prepDtl.ORDER_NO && q.TRANS_LINE == prepDtl.ORDER_LINE).ExecuteCommand();
                if (!CurPrepDtl.IsNullOrEmpty())
                {
                    MainDB.Deleteable<WMS_ITEM_POOL>().Where(q => q.TRANS_NO == CurPrepDtl.ORDER_NO && q.TRANS_LINE == CurPrepDtl.ORDER_LINE).ExecuteCommand();
                }
                CurPrepDtl = prepDtl;
                //如果上一次推荐有数据,则先灭掉亮的灯
                if (Suggests.Any())
                {
                    //await CloseLight(LocationHis);
                }
                //如果是首套发料,则只推荐当前物料行的一盘物料
                if (option.DlvyMode == WMS_ITEM_POOL.DLVY_MODEs.First)
                {
                    //推荐物料
                    Result<List<SuggestItem>> result = Suggest(CurPrepDtl.ORDER_NO, CurPrepDtl.ITEM_CODE, option, input.AuthOption, 1);
                    action.LocaleMsg = result.LocaleMsg;
                    if (result.IsException)
                    {
                        action.IsSuccessed = false;
                        return action;
                    }
                    Suggests.Clear();
                    Suggests = result.Data;
                    if (Suggests.Count > 0)
                    {
                        var inv = Suggests.First();
                        var actQty = CurPrepDtl.QTY_PREP > CurPREP.BizType.GetActReqQty(CurPrepDtl) ? 0 : (CurPREP.BizType.GetActReqQty(CurPrepDtl) - CurPrepDtl.QTY_PREP);
                        if (inv != null && actQty > 0)
                        {
                            inv.poolItem = inv.Item.GetPoolItem(OrgCode, nameof(CurPREP.Order), CurPrepDtl.ORDER_NO, CurPrepDtl.ORDER_LINE, actQty, true);
                        }
                        else
                        {
                            inv.poolItem = null;
                        }
                    }
                    Suggests.RemoveAll(q => q.poolItem.IsNullOrEmpty());
                }
                //如果是正常发料,则按实际需求数量推荐当前物料行的所有物料
                if (option.DlvyMode == WMS_ITEM_POOL.DLVY_MODEs.Supply)
                {
                    //推荐物料
                    var actQty = CurPrepDtl.QTY_PREP > CurPREP.BizType.GetActReqQty(CurPrepDtl) ? 0 : (CurPREP.BizType.GetActReqQty(CurPrepDtl) - CurPrepDtl.QTY_PREP);
                    Result<List<SuggestItem>> result = Suggest(CurPrepDtl.ORDER_NO, CurPrepDtl.ITEM_CODE, option, input.AuthOption, actQty);
                    action.LocaleMsg = result.LocaleMsg;
                    if (result.IsException)
                    {
                        action.IsSuccessed = false;
                        return action;
                    }
                    Suggests.Clear();
                    var remain = actQty;
                    Suggests.ForEach((inv) =>
                    {
                        if (remain > 0)
                        {
                            inv.poolItem = inv.Item.GetPoolItem(input.AuthOption.OrgCode, nameof(CurPREP.Order), CurPrepDtl.ORDER_NO, CurPrepDtl.ORDER_LINE, remain, false);
                            remain -= inv.poolItem.ALLOC_QTY;
                        }
                        else
                        {
                            inv.poolItem = null;
                        }
                    });
                    Suggests.RemoveAll(q => q.poolItem.IsNullOrEmpty());
                }
                //保存物料池到数据库
                MainDB.Insertable(CurPoolList).ExecuteCommand();
                CurPrepDtl.Suggests = Suggests;
                //action = await LightAll(new() { AuthOption = input.AuthOption, Color = LedColor.Blue });
                action.Data.Command = "Normal";
                action.Data.Data = CurPrepDtl;
                //action.LocaleMsg = Biz.L($"已选择要备料物料行[{0},可以开始备料下架");
                action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.SelectItem.Success", CurPrepDtl.ITEM_CODE);
            }
            catch (Exception ex)
            {
                //取消当前操作
                ResetTrans();
                //action.CatchExceptionWithLog(ex, $"备料任务选择要物料行异常(Data: {0})");
                action.CatchExceptionWithLog(ex, Biz.L("WMS.Out_BIZ_WMS_PREP.SelectItemException", input?.Data));
            }
            return action;
        }
        /// <summary>
        ///  获取当前备料的物料行信息
        /// </summary>
        /// <returns></returns>
        public async Task<ApiAction<BIZ_WMS_PREP_DTL>> GetCurPrepItem()
        {
            var action = new ApiAction<BIZ_WMS_PREP_DTL>();
            var prepDtl = MainDB.Queryable<BIZ_WMS_PREP_DTL>().Where(q => q.ID == CurPrepDtl.ID).First();
            prepDtl.Suggests = Suggests;
            CurPrepDtl = prepDtl;
            CurPREP.Order.Details.RemoveAll(q => q.ID == CurPrepDtl.ID);
            CurPREP.Order.Details.Add(CurPrepDtl);
            action.Data = CurPrepDtl;
            return action;
        }
        /// <summary>
        /// 领料单推荐所有物料亮灯
        /// </summary>
        /// <param name="light"></param>
        /// <returns></returns>
        public async Task<ApiAction<ScanOutput>> LightAll(LightEntityInput light)
        {
            var action = new ApiAction<ScanOutput>();
            try
            {
                var list = Suggests.WhereIF(!light.ItemCode.IsNullOrEmpty(), q => q.Item.ITEM_CODE == light.ItemCode);
                if (list.Any())
                {
                    list = list.Where(q => q.Shelf.SHELF_TYPE == WMS_SHELF.SHELF_TYPEs.Smart.GetValue() || q.Shelf.SHELF_TYPE == WMS_SHELF.SHELF_TYPEs.QRCode.GetValue());
                    if (list.Any())
                    {
                        list = list.Where(q => q.poolItem.STATUS < WMS_ITEM_POOL.STATUSs.WaitSend.GetValue());
                        if (list.Any())
                        {
                            var locs = list.Select(x => x.Location.AddShelf(x.Shelf)).ToList();
                            LocationHis.AddRange(locs);
                            //亮灯前先灭一遍
                            foreach (var shelf in list.Select(q => q.Shelf))
                            {
                                await Share.Shelf.DownAll(shelf);
                            }
                            await Share.Shelf.LightMulti(TransID, light.Color, locs);
                            action.LocaleMsg = Biz.L("亮灯成功,亮灯颜色[{0}]", light.Color.GetDesc());
                        }
                        else
                        {
                            action.LocaleMsg = Biz.L($"无需亮灯,推荐的物料已全部下架");
                        }
                    }
                    else
                    {
                        action.LocaleMsg = Biz.L($"无需亮灯,推荐的物料不在智能货架上");
                    }
                }
                else
                {
                    action.IsSuccessed = false;
                    action.LocaleMsg = Biz.L($"亮灯失败,未找到推荐下架的物料");
                }
            }
            catch (Exception ex)
            {
                action.CatchExceptionWithLog(ex, $"亮灯异常");
            }
            return action;
        }
        /// <summary>
        /// 灭灯
        /// </summary>
        /// <returns></returns>
        public async Task<ApiAction> CloseLight(List<WMS_LOCATION> locations = null)
        {
            var action = new ApiAction();
            try
            {
                if (locations != null)
                {
                    foreach (var shelf in locations.Select(q => q.Shelf))
                    {
                        await Share.Shelf.DownAll(shelf);
                    }
                }
                //灭灯
                await Share.Shelf.DownMulti(TransID, locations.IsNullOrEmpty(Suggests.Where(q => q.Shelf.SHELF_TYPE == WMS_SHELF.SHELF_TYPEs.Smart.GetValue() || q.Shelf.SHELF_TYPE == WMS_SHELF.SHELF_TYPEs.QRCode.GetValue())
                    .Select(x => x.Location).ToList()));
            }
            catch (Exception ex)
            {
                action.CatchExceptionWithLog(ex, $"灭灯异常");
            }
            return action;
        }
        /// <summary>
@@ -78,195 +386,260 @@
            var action = new ApiAction<ScanOutput>(new ScanOutput() { Command = Command });
            try
            {
                if (CurInvItem.IsNullOrEmpty())
                {
                    //解析条码
                    Result<IInventory> result = WMS_ITEM_Biz.WmsItem.Get(input.SN, input.AuthOption, true);
                   if (!result.IsSuccessed)
                   {
                       action.IsSuccessed = false;
                       action.LocaleMsg = result.LocaleMsg;
                       return SetOutPutMqttMsg(action, input.Locale);
                   }
                   var inv = result.Data as Inventory;
                    //验证条码是否正确
                    if (!inv.isNormalStatus || inv.Status != WMS_ITEM.STATUSs.WaitIn)
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("状态[{0}]异常,请重新扫描");
                        action.LocaleMsg = Biz.L("WMS.Default.ScanItem.StatusException", string.Join(',', inv.StatusList.Select(q => q.GetDesc())));
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    //物料验证
                    if (inv.ItemInfo.IsNullOrEmpty() || inv.ItemInfo.IS_ACTIVE == "N")
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("物料编码[{0}]不存在或者该物料未启用");
                        action.LocaleMsg = Biz.L("WMS.Default.ScanItem.ItemCodeNotExistsOrNotActive", inv.ItemInfo.ITEM_CODE.IsNullOrEmpty(inv.Barcode.ItemCode));
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    CurInvItem = inv;
                }
                var receipt = await Biz.Db.Queryable<BIZ_U9_RECEIPT>().Where(q => q.ORDER_NO == CurInvItem.Items.First().TRANS_NO).IncludesAllFirstLayer().FirstAsync();
                if (receipt.IsNullOrEmpty())
                //解析条码
                Result<IInventory> result = GetInventory(input.SN, input.AuthOption, true);
                if (!result.IsSuccessed)
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("收货单中找不到条码所属的单据[{0}]信息,请重新扫描收货单的条码或选择正确的功能上架");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.ReceiptNotExists", CurInvItem.Items.First().TRANS_NO);
                    action.LocaleMsg = result.LocaleMsg;
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                var receiptDtl = receipt.Details.First(q => q.LINE_NO == CurInvItem.Items.First().TRANS_LINE);
                if (receiptDtl.STATUS > BIZ_U9_RECEIPT.STATUSs.Storing.GetValue())
                var inv = result.Data as Inventory;
                //验证条码是否正确
                if (!inv.isNormalStatus || inv.Status != WMS_ITEM.STATUSs.InStore)
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("收货单中行[{0}]状态[{1}]异常,请扫描正确的条码");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.ReceiptDtlStatusException", receiptDtl.LINE_NO, receiptDtl.STATUS.GetEnumDesc<BIZ_U9_RECEIPT.STATUSs>());
                    //action.LocaleMsg = Biz.L("状态[{0}]异常,请重新扫描");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.StatusException", string.Join(',', inv.StatusList.Select(q => q.GetDesc())));
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                var receiptSn = receipt.SnList.Where(q => CurInvItem.Items.Any(i => i.SN == q.SN)).ToList();
                if (receiptSn.Any(q => q.STATUS != WMS_ITEM.STATUSs.WaitIn.GetValue()))
                //验证条码是否被锁定
                if (inv.Items.Any(q => q.IS_LOCKED == "Y"))
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("收货单中条码[{0}]状态[{1}]异常,请扫描正确的条码");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.ReceiptSnStatusException", CurInvItem.SN, receiptSn.First(q => q.STATUS != WMS_ITEM.STATUSs.WaitIn.GetValue()).STATUS.GetEnumDesc<WMS_ITEM.STATUSs>());
                    //action.LocaleMsg = Biz.L("条码[{0}]已被锁定,请重新扫描");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.ItemIsLock", inv.CurPkg.SN);
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                var nLocation = new WMS_LOCATION();
                // 判断是否智能货架
                if (CurScanShelf.Shelf.IsLightShelf)
                //验证条码是否被其他用户和单据锁定
                if (inv.Items.Any(q => q.IS_LOCKED == "Y"))
                {
                    ShelfApiResult shelfApiResult = await Share.Shelf.PutOn(TransID, CurScanShelf.Shelf, CurInvItem.Items[0]);
                    if (!shelfApiResult.IsSuccess)
                    {
                        action.IsSuccessed = false;
                        action.LocaleMsg = Biz.L(shelfApiResult.GetData<string>());
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    var reaultShelf = shelfApiResult.GetData<ShelfChangeModel>();
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("条码[{0}]已被锁定,请重新扫描");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.ItemIsLock", inv.CurPkg.SN);
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                //储位验证
                if (inv.Location.IsNullOrEmpty())
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"条码[{0}]库存信息异常: 没有储位信息,请先上架后再扫描(储位Id: {1})");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.LocationIsNull", inv.CurPkg.SN, inv.CurPkg.LOCATION_ID);
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                //物料验证
                if (inv.ItemInfo.IsNullOrEmpty() || inv.ItemInfo.IS_ACTIVE == "N")
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"物料编码[{0}]不存在或者该物料未启用");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.ItemCodeNotExistsOrNotActive", inv.ItemInfo.ITEM_CODE.IsNullOrEmpty(inv.Barcode.ItemCode));
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                //判断是否在备料中的物料
                if (inv.ItemInfo.ITEM_CODE != CurPrepDtl.ITEM_CODE)
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"条码[{0}]不是当前选中的物料行[{1}],请放回原储位或者重新选择下架物料行");
                    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.NoNeedItemCode", inv.CurPkg.SN, CurPrepDtl.ITEM_CODE);
                    return action;
                }
                //判断是否在备料中的物料
                //if (!CurPREP.Order.Details.Any(q => q.ITEM_CODE == inv.ItemInfo.ITEM_CODE))
                //{
                //    action.IsSuccessed = false;
                //    //action.LocaleMsg = Biz.L($"当前备料任务[{0}]不包含条码[{1}]的物料编码[{2}],请放回原储位");
                //    action.LocaleMsg = Biz.L("WMS.Out_BIZ_WMS_PREP.ScanItem.NoNeedItemCode", inv.ItemInfo.ITEM_CODE.IsNullOrEmpty(inv.Barcode.ItemCode));
                //    return action;
                //}
                //ProcessingOrderDetail = input.SN;
                //if (WMSContext.TransactionDic.Where(q => !string.IsNullOrWhiteSpace(q.Value.ProcessingSn)).Any(q => q.Value.ProcessingSn == ProcessingSn && q.Value.TransID != this.TransID))
                //{
                //    action.IsSuccessed = false;
                //    action.LocaleMsg = Biz.L("当前条码正在执行中,无法扫描");
                //    return action;
                //}
                CurInvItem = inv;
                CurSn = CurInvItem.Items.Select(q => new BIZ_WMS_PREP_SN() {
                    ORDER_NO = CurPREP.Order.ORDER_NO,
                    ORDER_LINE = CurPrepDtl.ORDER_LINE,
                    SN = q.SN,
                    ITEM_CODE = q.ITEM_CODE,
                    UNIT = q.UNIT,
                    QTY = q.QTY,
                    QTY_DLVY = q.QTY,
                    STATUS = q.STATUS,
                    LOTNO = q.LOTNO,
                    WH_ID = CurInvItem.Warehouse.ID,
                    WH_CODE = CurInvItem.Warehouse.WH_CODE,
                    REGION_ID = CurInvItem.Region.ID,
                    REGION_CODE = CurInvItem.Region.REGION_CODE,
                    SHELF_ID = CurInvItem.Shelf.ID,
                    SHELF_CODE = CurInvItem.Shelf.SHELF_CODE,
                    LOCATION_ID = CurInvItem.Location.ID,
                    LOCATION_CODE = CurInvItem.Location.LOCATION_CODE,
                    PREP_USER = UserCode,
                    DOWN_TIME = DateTime.Now,
                    SMT_NO = CurPrepDtl.SMT_NO,
                    STATION_NO = CurPrepDtl.STATION_NO,
                    FEEDER_NO = CurPrepDtl.FEEDER_NO,
                    FEEDER_TYPE = CurPrepDtl.FEEDER_TYPE,
                    IS_FIRST = CurPREP.Option.DlvyMode == WMS_ITEM_POOL.DLVY_MODEs.First ? "Y" : "N",
                    NEED_CUTTING = "N",
                    TRACE_ID = TransID,
                }).ToList();
                    nLocation = reaultShelf.GetLocation();
                    if (nLocation == null)
                //判断是否超发
                isExceed = false;
                //计算剩余需求数量和条码中的发出数量
                var actQty = CurPrepDtl.QTY_PREP > CurPREP.BizType.GetActReqQty(CurPrepDtl) ? 0 : (CurPREP.BizType.GetActReqQty(CurPrepDtl) - CurPrepDtl.QTY_PREP);
                if (actQty < CurInvItem.CurPkg.QTY)
                {
                    isExceed = true;
                    var remain = actQty;
                    foreach (var sn in CurSn.OrderByDescending(q => q.QTY))
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L($"货架[{0}]中不存在id为[{1}]的储位,请先维护货架信息");
                        action.LocaleMsg = Biz.L("WMS.Default.ScanItem.LocationNotExistsInShelf", CurScanShelf.Shelf.SHELF_CODE, reaultShelf.ledAddr);
                        return SetOutPutMqttMsg(action, input.Locale);
                        sn.QTY_DLVY = remain >= sn.QTY ? sn.QTY : remain;
                        sn.NEED_CUTTING = remain >= sn.QTY ? "N" : "Y";
                        remain -= sn.QTY_DLVY;
                    }
                    var locationData = MainDB.Queryable<WMS_ITEM>().Where(q => q.LOCATION_ID == nLocation.ID).First();
                    if (!locationData.IsNullOrEmpty())
                }
                //判断物料是否允许超发,如果不允许则必须截料,否则由用户在PDA选择是否截料
                isNeedCut = CurInvItem.ItemInfo.DLVY_TYPE == BAS_ITEM.DLVY_TYPEs.ByDemand.GetValue();
                //如果超发则返回前端处理,否则直接发出
                if (isExceed)
                {
                    //action.LocaleMsg = Biz.L($"条码[{0}]扫描成功,备料物料[{1}]需求[{2}]还需备料[{3}],条码需要截料,发出数量[{4}]");
                    //action.LocaleMsg = Biz.L($"条码[{0}]扫描成功,备料物料[{1}]需求[{2}]还需备料[{3}],请选择要超发还是截料?");
                    action.LocaleMsg = Biz.L($"WMS.Out_BIZ_WMS_PREP.ScanItem.Success{(isNeedCut ? "" : "Confirm")}", CurInvItem.SN, CurInvItem.ItemInfo.ITEM_CODE, CurPREP.BizType.GetActReqQty(CurPrepDtl), actQty);
                    action.Data.Command = "ConfirmExceed";
                    action.Data.Data = new
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("储位[{0}]已存有物料[{1}],请检查系统库存信息");
                        action.LocaleMsg = Biz.L($"WMS.Default.ScanShelf.ItemAlreadyExistsInLocation", nLocation.LOCATION_CODE, locationData.SN);
                        return SetOutPutMqttMsg(action, input.Locale);
                    }
                    CurScanShelf.LocationCode = nLocation.LOCATION_CODE;
                        isExceed,
                        isNeedCut,
                        CurSn,
                    };
                }
                else
                {
                    input.Data = CurSn.ToJson();
                    action = await ConfirmExceed(input);
                }
            }
            catch (Exception ex)
            {
                action.CatchExceptionWithLog(ex, $"扫描物料[{input.SN}]复核异常");
            }
            return SetOutPutMqttMsg(action, input.Locale);
        }
        /// <summary>
        /// 前端确认超发或者返回截料后的信息
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<ApiAction<ScanOutput>> ConfirmExceed(BaseInput input)
        {
            var action = new ApiAction<ScanOutput>(new ScanOutput() { Command = "Normal" });
            try
            {
                var sns = (input.Data ?? "").JsonToObject<List<BIZ_WMS_PREP_SN>>() ?? new List<BIZ_WMS_PREP_SN>();
                if (!sns.Any())
                {
                    var actQty = CurPrepDtl.QTY_PREP > CurPREP.BizType.GetActReqQty(CurPrepDtl) ? 0 : (CurPREP.BizType.GetActReqQty(CurPrepDtl) - CurPrepDtl.QTY_PREP);
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L($"条码[{0}]扫描成功,备料物料[{1}]需求[{2}]还需备料[{3}],条码需要截料,发出数量[{4}]");
                    //action.LocaleMsg = Biz.L($"条码[{0}]扫描成功,备料物料[{1}]需求[{2}]还需备料[{3}],请选择要超发还是截料?");
                    action.LocaleMsg = Biz.L($"WMS.Out_BIZ_WMS_PREP.ScanItem.Success{(isNeedCut ? "" : "Confirm")}", CurInvItem.SN, CurInvItem.ItemInfo.ITEM_CODE, CurPREP.BizType.GetActReqQty(CurPrepDtl), actQty);
                    action.Data.Command = "ConfirmExceed";
                    action.Data.Data = new
                    {
                        isExceed,
                        isNeedCut,
                        CurSn,
                    };
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                else
                {
                    if (CurScanShelf.LocationCode.IsNullOrEmpty() || CurScanShelf.WarehouseCode.IsNullOrEmpty())
                    foreach (var item in sns)
                    {
                        action.IsSuccessed = false;
                        //action.LocaleMsg = Biz.L("请输入或扫描有效的货架/储位码");
                        action.LocaleMsg = Biz.L("WMS.Default.ScanShelf.ShelfCanNotEmpty");
                        return SetOutPutMqttMsg(action, input.Locale);
                        var sn = CurSn.First(q => q.ID == item.ID);
                        sn.NEED_CUTTING = item.NEED_CUTTING;//前端返回Y认为是截料发料,N认为是整盘发料
                        sn.DeliverySN = item.DeliverySN;
                        sn.ReserveSN = item.ReserveSN;
                    }
                    nLocation = await MainDB.Queryable<WMS_LOCATION>().Where(t => t.LOCATION_CODE == CurScanShelf.LocationCode && t.AUTH_ORG == OrgCode).FirstAsync();
                    Command = "Normal";
                }
                //判断储位是否单放
                if (nLocation.IS_SINGLE == "Y" && CurInvItem.Items.Count > 1)
                {
                    action.IsSuccessed = false;
                    //action.LocaleMsg = Biz.L("储位[{0}]只能存放一个物料");
                    action.LocaleMsg = Biz.L("WMS.Default.ScanItem.LocationSingleFailure", nLocation.LOCATION_CODE);
                    ResetScan();
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                //执行上架数据处理
                //出库下架
                foreach (var item in CurInvItem.Items)
                {
                    item.IS_LOCKED = "Y";
                    item.SOURCE_CODE = item.TRANS_CODE;
                    item.SOURCE_ORDER = item.TRANS_NO;
                    item.SOURCE_LINE = item.TRANS_LINE;
                    item.TRANS_CODE = nameof(BIZ_U9_RECEIPT);
                    item.TRANS_NO = receipt.ORDER_NO;
                    item.TRANS_LINE = receiptDtl.LINE_NO;
                    item.PROD_DATE = item.PROD_DATE < new DateTime(2000, 1, 1) ? DateTime.Now : item.PROD_DATE;
                    item.FIRST_IN_DATE = item.FIRST_IN_DATE < new DateTime(2000, 1, 1) ? DateTime.Now : item.FIRST_IN_DATE;
                    item.TRANS_CODE = nameof(BIZ_WMS_PREP);
                    item.TRANS_NO = CurPREP.Order.ORDER_NO;
                    item.TRANS_LINE = CurPrepDtl.ORDER_LINE;
                }
                Result putonResult = PutOn(input.AuthOption, nLocation.LOCATION_CODE);
                if (!putonResult.IsSuccessed)
                foreach (var item in CurInvItem.Packages)
                {
                    item.SOURCE_CODE = item.TRANS_CODE;
                    item.SOURCE_ORDER = item.TRANS_NO;
                    item.SOURCE_LINE = item.TRANS_LINE;
                    item.TRANS_CODE = nameof(BIZ_WMS_PREP);
                    item.TRANS_NO = CurPREP.Order.ORDER_NO;
                    item.TRANS_LINE = CurPrepDtl.ORDER_LINE;
                }
                Result downResult = TakeDown(CurSn, input.AuthOption, WMS_ITEM.STATUSs.OffShelf);
                if (!downResult.IsSuccessed)
                {
                    action.IsSuccessed = false;
                    action.LocaleMsg = putonResult.LocaleMsg;
                    action.LocaleMsg = downResult.LocaleMsg;
                    ResetScan();
                    return SetOutPutMqttMsg(action, input.Locale);
                }
                //更新单据信息
                foreach(var sn in receiptSn)
                else//处理备料任务数据
                {
                    sn.STATUS = WMS_ITEM.STATUSs.InStore.GetValue();
                    sn.IS_IN = "Y";
                }
                //如果当前行上架完成标记为待审核
                receiptDtl.QTY_IN = receipt.SnList.Where(q => q.LINE_NO == receiptDtl.LINE_NO && q.STATUS  == WMS_ITEM.STATUSs.InStore.GetValue()).Sum(q => q.QTY);
                if (receiptDtl.QTY_IN == receiptDtl.QTY)
                {
                    receiptDtl.STATUS = BIZ_U9_RECEIPT.STATUSs.Review.GetValue();
                    //当前单据明细已经全部上架完成,可以调用U9行审接口
                    var iInput = new SubmitLineInput
                    foreach (var sn in CurSn)
                    {
                        userId = UserCode,
                        IsLogin = true,
                        param = new()
                        sn.STATUS = WMS_ITEM.STATUSs.OffShelf.GetValue();
                        sn.DOWN_TIME = DateTime.Now;
                        if (sn.NEED_CUTTING == "Y")
                        {
                            RcvLineID = receiptDtl.ID,
                            RcvDocNo = receiptDtl.ORDER_NO,
                            DocLineNo = receiptDtl.LINE_NO,
                            OrgCode = receiptDtl.AUTH_ORG,
                            sn.NEED_CUTTING = "N";
                            sn.SN = sn.DeliverySN;
                            sn.QTY = sn.QTY_DLVY;
                        }
                    };
                    var result = await DI.Resolve<IWMS_U9C>().ReceivementApproveLine(iInput);
                    if (!result.IsSuccessed)
                    }
                    CurPrepDtl.QTY_PREP += CurSn.Sum(q => q.QTY_DLVY);
                    CurPREP.Order.STATUS = BIZ_WMS_PREP.STATUSs.Picking.GetValue();
                    //创建变量克隆对象用于传入DBSubmitAction中保存当前需要暂存的数据值
                    var _CurSn = CurSn.Clone();
                    var _CurPrepDtl = CurPrepDtl.Clone();
                    var _CurPREPOrder = CurPREP.Order.Clone();
                    AddCommitAction("UpdatePrep", () =>
                    {
                        action.IsSuccessed = false;
                        action.LocaleMsg = result.LocaleMsg;
                        return SetOutPutMqttMsg(action, input.Locale);
                        //使用统一的事务DB对象
                        var db = GetCommitDB();
                        //数据保存逻辑
                        db.Insertable(_CurSn, UserCode).ExecuteCommand();
                        _CurPrepDtl.QTY_PREP = db.Queryable<BIZ_WMS_PREP_SN>().Where(q => q.ORDER_NO == _CurPrepDtl.ORDER_NO && q.ORDER_LINE == _CurPrepDtl.ORDER_LINE).Sum(q => q.QTY_DLVY);
                        db.Updateable(_CurPrepDtl, UserCode).UpdateColumns(q => new { q.QTY_PREP, q.UPDATE_TIME, q.UPDATE_USER }).ExecuteCommand();
                        db.Updateable(_CurPREPOrder, UserCode).UpdateColumns(q => new { q.STATUS, q.UPDATE_TIME, q.UPDATE_USER }).ExecuteCommand();
                    });
                    //灭灯
                    if (CurInvItem.Shelf.IsLightShelf)
                    {
                        await Share.Shelf.DownSingle(TransID, CurInvItem.Location);
                    }
                }
                else
                {
                    receiptDtl.STATUS = BIZ_U9_RECEIPT.STATUSs.Storing.GetValue();
                }
                var dtlStatus = receipt.Details.Select(q => q.STATUS).Distinct();
                if (dtlStatus.Count() == 1 && dtlStatus.First() == BIZ_U9_RECEIPT.STATUSs.Review.GetValue())
                {
                    receipt.STATUS = BIZ_U9_RECEIPT.STATUSs.Review.GetValue();
                }
                else
                {
                    receipt.STATUS = BIZ_U9_RECEIPT.STATUSs.Storing.GetValue();
                }
                //创建变量克隆对象用于传入DBSubmitAction中保存当前需要暂存的数据值
                var _receipt = receipt.Clone();
                AddCommitAction("Receipt", () =>
                {
                    //使用统一的事务DB对象
                    var db = GetCommitDB();
                    //数据保存逻辑
                    db.Updateable(receipt, UserCode).UpdateColumns(q => new { q.STATUS, q.UPDATE_TIME, q.UPDATE_USER }).ExecuteCommand();
                    db.Updateable(receiptDtl, UserCode).UpdateColumns(q => new { q.STATUS, q.QTY_IN, q.UPDATE_TIME, q.UPDATE_USER }).ExecuteCommand();
                    db.Updateable(receiptSn, UserCode).UpdateColumns(q => new { q.STATUS, q.IS_IN, q.UPDATE_TIME, q.UPDATE_USER }).ExecuteCommand();
                });
                //完成所有处理后使用事务保存数据
                action = DoIfFinish(action, input.Locale);
@@ -274,7 +647,7 @@
            catch (Exception ex)
            {
                //取消当前操作
                ResetScan();
                ResetTrans();
                //action.CatchExceptionWithLog(ex, $"扫描条码[{input.SN}]复核异常");
                action.CatchExceptionWithLog(ex, Biz.L("WMS.Default.ScanItem.ScanException", input.SN));
            }
@@ -365,7 +738,7 @@
            catch (Exception ex)
            {
                //取消当前操作
                ResetScan();
                ResetTrans();
                //action.CatchExceptionWithLog(ex, $"扫描货架/储位[{0}]异常");
                action.CatchExceptionWithLog(ex, Biz.L("WMS.Default.ScanShelf.ScanException", Code));
            }
@@ -388,51 +761,72 @@
            doAfterSave?.Invoke();
            // 返回数据
            action.Data.Data = new DefaultInStoreOutput
            action.Data.Data = new DefaultScanItemOutput()
            {
                Barcode = CurInvItem.SN,
                MaterialCode = CurInvItem.ItemInfo.ITEM_CODE,
                SN = CurInvItem.SN,
                ItemCode = CurInvItem.ItemInfo.ITEM_CODE,
                MaterialName = CurInvItem.ItemInfo.ITEM_NAME,
                CurrentQty = CurInvItem.Items[0].QTY,
                Unit = CurInvItem.Items[0].UNIT,
                Qty = CurInvItem.CurPkg.QTY,
                Unit = CurInvItem.CurPkg.UNIT,
                regionCode = CurInvItem.Region.REGION_CODE,
                locationCode = CurInvItem.Location?.LOCATION_CODE,
                DateCode = CurInvItem.Items[0].PROD_DATE,
                WarehouseCode = CurInvItem.Warehouse.WH_CODE,
                RegionCode = CurInvItem.Region.REGION_CODE,
                ShelfCode = CurInvItem.Shelf.SHELF_CODE,
                LocationCode = CurInvItem.Location.LOCATION_CODE,
                ScanAfCut = CurInvItem.Warehouse.SCAN_AF_CUT
            };
            //action.LocaleMsg = Biz.L($"扫描条码[{0}]上架到储位[{1}]成功");
            action.LocaleMsg = Biz.L("WMS.Default.ScanItem.PutOnSucceeded", CurInvItem.SN, CurInvItem.Location.LOCATION_CODE);
            //action.LocaleMsg = Biz.L($"扫描条码[{0}]从储位[{1}]下架成功");
            action.LocaleMsg = Biz.L("WMS.Default.ScanItem.TakeDownSucceeded", CurInvItem.SN, CurInvItem.Location?.LOCATION_CODE);
            //重置工序
            ResetScanInfo();
            ResetScan();
            return SetOutPutMqttMsg(action, locale);
        }
        /// <summary>
        /// 创建发料批次以完成本次发料,调用发料单据的ERP接口
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<ApiAction> GenerateDlvyBatch(BaseInput input)
        {
            var range = input.Data.ToInt32().GetEnum<BATCH_RANGEs>();
            var action = await CurPREP.Order.BIZ_TYPE.GetEnum<BIZ_WMS_PREP.BIZ_TYPEs>() .GenerateDlvyBatch(this, range);
            return action;
        }
        #endregion
        /// <summary>
        /// 重置当前操作,有需要则重写此方法
        /// 重置事务数据,有需要则重写此方法
        /// </summary>
        public override void ResetScan()
        public override void ResetTrans()
        {
            base.ResetScan();
            ResetScanInfo();
            ResetScan();
            CurPREP = new(MainDB, UserCode, OrgCode);
            CurPrepDtl = new();
            CurScanShelf = null;
            base.ResetTrans();
        }
        /// <summary>
        /// 重置扫码信息
        /// 重置本次扫码信息
        /// </summary>
        public void ResetScanInfo()
        public override void ResetScan()
        {
            Command = null;
            Command = "Normal";
            CurInvItem = null;
            CurSn = new();
            base.ResetScan();
        }
        public override bool Close(bool needSaveHistoryLog = false)
        {
            //CloseLight(LocationHis).Wait();
            if (!(CurPREP?.Order?.ORDER_NO ?? "").IsNullOrEmpty())
            {
                MainDB.Deleteable<WMS_ITEM_POOL>().Where(x => x.TRANS_NO == CurPREP.Order.ORDER_NO).ExecuteCommand();
            }
            this.IsFinished = true;
            return IsFinished ? base.Close(needSaveHistoryLog) : IsFinished;
        }