using System.Linq;
using MongoDB.Driver;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NKC_SDK;
using SteamWare;
using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Net;
using System.Net.NetworkInformation;

namespace AppData
{
    /// <summary>
    /// Classe con metodi di supporto per COMUNICAZIONE
    /// </summary>
    public class ComLib
    {
        #region Private Fields

        /// <summary>
        /// Database corrente MongoDB
        /// </summary>
        private IMongoDatabase database;

        #endregion Private Fields

        #region Protected Fields

        /// <summary>
        /// Chiave primo bunk su redis
        /// </summary>
        protected static string redAllNextBunkKey = $"{redProdReq}:NextBunk";

        /// <summary>
        /// Chiave primo bunk su redis
        /// </summary>
        protected static string redFirstBunkKey = $"{redProdReq}:FirstBunk";

        /// <summary>
        /// TTL standard x dati da scambiare con PROD (30 gg)
        /// </summary>
        protected static int ttlProdData = 3600 * 24 * 30;

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// Classe impiego sstatico ComLib...
        /// </summary>
        public static ComLib man = new ComLib();

        public static string redBatchDescend = "NKC:SERV:BATCHDESCEND";
        public static string redMachUnloadCount = "NKC:SERV:MACH_UNLOAD:COUNT";
        public static string redMachUnloadForce = "NKC:SERV:MACH_UNLOAD:FORCERELOAD";
        public static string redMLCurrBunk = "NKC:SERV:TAKT:CurrBunk";
        public static string redMsgCount = "NKC:SERV:BREQ:MCount";
        public static string redMsgList = "NKC:SERV:BREQ:MList";
        public static string redNestAnsw = "NKC:NEST:BANSW";
        public static string redOrders = "NKC:SERV:ORDERS";
        public static string redOutPath = "NKC:SERV:BREQ";
        public static string redProdAnsw = "NKC:PROD:BUNKS";
        public static string redProdMachClock = "NKC:PROD:MACH:CLOCK";
        public static string redProdMachList = "NKC:PROD:MACH:LIST";
        public static string redProdMachStateData = "NKC:PROD:MACH:STATE";
        public static string redProdReq = "NKC:SERV:BUNKS";
        public static string redProdStatDec = "NKC:PROD:MACH:STATDEC";

        /// <summary>
        /// chaive redis x cache conteggio coda PJQ
        /// </summary>
        public static string redQueueCount = "NKC:SERV:PJQ";

        /// <summary>
        /// chaive redis x cache conteggio coda PJQ composta da tante code
        /// </summary>
        public static string redQueueCountSet = "NKC:SERV:PJQCOUNT";

        public static string redSecScreenReq = "NKC:SECSCREEN:REQ";

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// Init classe ComLib
        /// </summary>
        public ComLib()
        {
            database = memLayer.ML.getMongoDatabase("NKC");
        }

        #endregion Public Constructors

        #region Private Properties

        /// <summary>
        /// Cache redis del PRIMO bunk da lavorare
        /// </summary>
        private static ProdBunk redisFirstBunk
        {
            get
            {
                ProdBunk answ = null;
                string rawData = memLayer.ML.getRSV(redFirstBunkKey);
                if (!string.IsNullOrEmpty(rawData))
                {
                    // provo a deserializzare
                    try
                    {
                        answ = JsonConvert.DeserializeObject<ProdBunk>(rawData);
                    }
                    catch
                    { }
                }
                return answ;
            }
            set
            {
                if (value != null)
                {
                    string redVal = JsonConvert.SerializeObject(value);
                    // default lascio x 5 minuti...
                    memLayer.ML.setRSV(redFirstBunkKey, redVal, ttlProdData);
                }
                else
                // se null elimino da redis
                {
                    memLayer.ML.setRSV(redFirstBunkKey, "");
                    // elimino TUTTI i next...
                    memLayer.ML.redFlushKey(redAllNextBunkKey);
                    // imposto force reload UNLOAD
                    setReloadMU();
                }
            }
        }

        #endregion Private Properties

        #region Public Properties

        /// <summary>
        ///  verifica se sia avviabile un nuovo task nesting controllando cache Redis o tabelle batch/offlineOrders
        /// </summary>
        public static bool canStartNew
        {
            get
            {
                DataLayer DLMan = new DataLayer();
                bool answ = false;
                bool valido = false;
                // cerco in redis
                if (memLayer.ML.redKeyPresent(canStartNewKey))
                {
                    string rawData = memLayer.ML.getRSV(canStartNewKey);
                    valido = bool.TryParse(rawData, out answ);
                }
                if (!valido)
                {
                    // in primis controllo SE ci siano task running, nel qual caso è false e basta...
                    int numEst = DLMan.taBL.getByStatus((int)BatchStatus.EstimationRequested, "", true, 0).Count;
                    int numNest = DLMan.taBL.getByStatus((int)BatchStatus.NestRequested, "", true, 0).Count;
                    // ora controllo anche offline orders...
                    int numOffOrd = DLMan.taOffOL.getRunning().Count;
                    // ora la somma di tutti DEVE essere zero...
                    answ = ((numEst + numNest + numOffOrd) == 0);
                    // salvo in redis
                    memLayer.ML.setRSV(canStartNewKey, answ.ToString(), 2);
                }
                return answ;
            }
        }

        public static string canStartNewKey
        {
            get
            {
                return $"{redOutPath}:CanStartNew";
            }
        }

        /// <summary>
        /// Valore della richiesta corrente di elaborazione batch
        /// </summary>
        public static string currBatchReq
        {
            get
            {
                return memLayer.ML.getRSV(currBatchReqKey);
            }
            set
            {
                // scrivo su REDIS
                memLayer.ML.setRSV(currBatchReqKey, value);
            }
        }

        public static string currBatchReqKey
        {
            get
            {
                return $"{redOutPath}:CURR";
            }
        }

        public static DS_App.StatusDecodeDataTable TabStatusDec
        {
            get
            {
                DS_App.StatusDecodeDataTable tabStaDec = new DS_App.StatusDecodeDataTable();
                // cerco in Redis...
                string rawData = memLayer.ML.getRSV(redProdStatDec);
                // provo a deserializzare e cercare...
                if (!string.IsNullOrEmpty(rawData))
                {
                    tabStaDec = JsonConvert.DeserializeObject<DS_App.StatusDecodeDataTable>(rawData);
                }
                else
                { }
                return tabStaDec;
            }
            set
            {
                string rawData = JsonConvert.SerializeObject(value);
                memLayer.ML.setRSV(redProdStatDec, rawData, 600);
            }
        }

        /// <summary>
        /// ID del BUNK correntemente in processing per ML (MachineLoad)
        /// </summary>
        public static string taktMLCurrBunk
        {
            get
            {
                return memLayer.ML.getRSV(redMLCurrBunk);
            }
            set
            {
                memLayer.ML.setRSV(redMLCurrBunk, value);
            }
        }

        #endregion Public Properties

        #region Private Methods

        /// <summary>
        /// verifica che la macchina dichiarata esiste o la crea...
        /// </summary>
        /// <param name="machine"></param>
        private static void checkPlace(string machine)
        {
            DS_App.PlacesDataTable tabPlace = new DS_App.PlacesDataTable();
            DataLayer DLMan = new DataLayer();
            // cerco in Redis...
            string rawData = memLayer.ML.getRSV(redProdMachList);
            // provo a deserializzare e cercare...
            if (!string.IsNullOrEmpty(rawData))
            {
                tabPlace = JsonConvert.DeserializeObject<DS_App.PlacesDataTable>(rawData);
            }
            // cerco item
            var foundItem = tabPlace.Where(x => x.PlaceCod == machine).ToList();
            if (foundItem.Count == 0)
            {
                // forzo (ri)lettura...
                tabPlace = DLMan.taPlac.GetData();
                rawData = JsonConvert.SerializeObject(tabPlace);
                memLayer.ML.setRSV(redProdMachList, rawData);
            }
            // cerco di nuovoitem
            foundItem = tabPlace.Where(x => x.PlaceCod == machine).ToList();
            if (foundItem.Count == 0)
            {
                // se non ci fosse --> creo
                DLMan.taPlac.insert(machine);
                // inserisco empty x 1 sec così rileggerà...
                memLayer.ML.setRSV(redProdMachList, "", 1);
            }
        }

        /// <summary>
        /// Transcodifica dati da format DB a formato PROD
        /// </summary>
        /// <param name="sheetList">Elenco fogli da DB</param>
        /// <returns></returns>
        private static SheetWorkList convertSheetWorkList(List<DS_App.SheetListRow> sheetList)
        {
            SheetWorkList answ;
            List<ProdSheetExt> elSheet = new List<ProdSheetExt>();
            string nestBasePath = memLayer.ML.CRS("nestBasePath").ToLower();
            string servBasePath = memLayer.ML.CRS("servBasePath").ToLower();
            string prodBasePath = memLayer.ML.CRS("prodBasePath").ToLower();
            DateTime dataStart = DateTime.Now;
            ProdSheetExt currPanel;
            // ciclo elenco fogli x escludere lavorati...
            foreach (var item in sheetList)
            {
                // converto i workData con check null sul campo data
                WorkData wdPrint = new WorkData()
                {
                    DtStart = item.IsPrntStartNull() ? null : (DateTime?)item.PrntStart,
                    DtEnd = item.IsPrntEndNull() ? null : (DateTime?)item.PrntEnd,
                    ProgramPath = item.PrintFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                };
                WorkData wdMachining = new WorkData()
                {
                    DtStart = item.IsWrkStartNull() ? null : (DateTime?)item.WrkStart,
                    DtEnd = item.IsWrkEndNull() ? null : (DateTime?)item.WrkEnd,
                    ProgramPath = item.CncFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                };
                WorkData wdUnload = new WorkData()
                {
                    DtStart = item.IsUnlStartNull() ? null : (DateTime?)item.UnlStart,
                    DtEnd = item.IsUnlEndNull() ? null : (DateTime?)item.UnlEnd
                };
                MaterialData material = new MaterialData()
                {
                    MaterialId = item.MatID,
                    MaterialDescription = item.MatDesc,
                    MaterialPN = item.MatExtCode.ToString()
                };
                PStatus currPnlStatus = PStatus.Programmed;
                Enum.TryParse(item.ShStatus.ToString(), out currPnlStatus);
                currPanel = new ProdSheetExt()
                {
                    BunkId = item.StackID,
                    Printing = wdPrint,
                    Machining = wdMachining,
                    Material = material,
                    SheetId = item.SheetID,
                    SheetIndex = item.SheetIndex,
                    Status = currPnlStatus,
                    Unloading = wdUnload
                };
                elSheet.Add(currPanel);
                if (!item.IsPrntStartNull())
                {
                    dataStart = item.PrntStart < dataStart ? item.PrntStart : dataStart;
                }
            }
            // compongo il bunk...
            answ = new SheetWorkList()
            {
                SheetList = elSheet
            };
            return answ;
        }

        /// <summary>
        /// Recupera e transcodifica DA DB i dati di UN SINGOLO bunk
        /// </summary>
        /// <param name="currBunk"></param>
        /// <returns></returns>
        private static ProdBunk getBunkFromDb(DS_App.StackListRow currBunk)
        {
            DataLayer DLMan = new DataLayer();
            ProdBunk answ;
            // calcolo attributi oggetti
            CStatus currSt = CStatus.Programmed;
            if (currBunk.Position == 5)
            {
                currSt = CStatus.Running;
            }
            else
            {
                currSt = CStatus.Done;
            }
            List<ProdSheet> elSheet = new List<ProdSheet>();
            // recupero gli sheets di questo stack...
            DS_App.SheetListDataTable tabSheets = DLMan.taSHL.getByStack(currBunk.StackID);
            DateTime dataStart = DateTime.Now;
            ProdSheet currPanel;
            string nestBasePath = memLayer.ML.CRS("nestBasePath").ToLower();
            string servBasePath = memLayer.ML.CRS("servBasePath").ToLower();
            string prodBasePath = memLayer.ML.CRS("prodBasePath").ToLower();
            foreach (var item in tabSheets)
            {
                // converto i workData con check null sul campo data
                WorkData wdPrint = new WorkData()
                {
                    DtStart = item.IsPrntStartNull() ? null : (DateTime?)item.PrntStart,
                    DtEnd = item.IsPrntEndNull() ? null : (DateTime?)item.PrntEnd,
                    ProgramPath = item.PrintFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                };
                WorkData wdMachining = new WorkData()
                {
                    DtStart = item.IsWrkStartNull() ? null : (DateTime?)item.WrkStart,
                    DtEnd = item.IsWrkEndNull() ? null : (DateTime?)item.WrkEnd,
                    ProgramPath = item.CncFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                };
                WorkData wdUnload = new WorkData()
                {
                    DtStart = item.IsUnlStartNull() ? null : (DateTime?)item.UnlStart,
                    DtEnd = item.IsUnlEndNull() ? null : (DateTime?)item.UnlEnd
                };
                MaterialData material = new MaterialData()
                {
                    MaterialId = item.MatID,
                    MaterialDescription = item.MatDesc,
                    MaterialPN = item.MatExtCode.ToString()
                };
                PStatus currPnlStatus = PStatus.Programmed;
                Enum.TryParse(item.ShStatus.ToString(), out currPnlStatus);
                currPanel = new ProdSheet()
                {
                    Printing = wdPrint,
                    Machining = wdMachining,
                    Unloading = wdUnload,
                    Material = material,
                    SheetId = item.SheetID,
                    Status = currPnlStatus
                };
                elSheet.Add(currPanel);
                if (!item.IsPrntStartNull())
                {
                    dataStart = item.PrntStart < dataStart ? item.PrntStart : dataStart;
                }
            }
            // compongo il bunk...
            answ = new ProdBunk()
            {
                BunkId = currBunk.StackID,
                Status = currSt,
                DataMatrix = currBunk.StackDtmx,
                SheetList = elSheet,
                DtStart = dataStart
            };
            return answ;
        }

        /// <summary>
        /// Recupero da Redis del SUCCESSIVO bunk da lavorare
        /// </summary>
        /// <param name="currBunkId"></param>
        /// <returns></returns>
        private static ProdBunk getRedisNextBunk(int currBunkId)
        {
            ProdBunk answ = null;
            string rawData = memLayer.ML.getRSV(redNextBunkKey(currBunkId));
            if (rawData != "")
            {
                // provo a deserializzare
                try
                {
                    answ = JsonConvert.DeserializeObject<ProdBunk>(rawData);
                }
                catch
                { }
            }
            return answ;
        }

        private static List<MachineStatData> saveStatus(string machine, List<MachineStatRecord> lastRecord)
        {
            List<MachineStatData> currData = new List<MachineStatData>();
            // ora processo lo status attuale x salvare MAPPA ULTIMO status in Redis...
            string redVal = memLayer.ML.getRSV(redProdMachStateData);
            if (!string.IsNullOrEmpty(redVal))
            {
                currData = JsonConvert.DeserializeObject<List<MachineStatData>>(redVal);

                // certo info x macchina corrente
                var machRecord = currData.Where(x => x.Machine == machine).FirstOrDefault();
                // recupero ultimo stato

                if (machRecord != null)
                {
                    machRecord.Records = lastRecord;
                }
                // aggiungo
                else
                {
                    currData.Add(new MachineStatData() { Machine = machine, Records = lastRecord });
                }
            }

            // serializzo
            string rawData = JsonConvert.SerializeObject(currData);

            // salvo!
            memLayer.ML.setRSV(redProdMachStateData, rawData);
            return currData;
        }

        /// <summary>
        /// Salvo in Redis il SUCCESSIVO bunk da lavorare
        /// </summary>
        /// <param name="currBunkId"></param>
        /// <param name="value"></param>
        private static void setRedisNextBunk(int currBunkId, ProdBunk value)
        {
            if (value != null)
            {
                string redVal = JsonConvert.SerializeObject(value);
                // default lascio x 5 minuti...
                memLayer.ML.setRSV(redNextBunkKey(currBunkId), redVal, ttlProdData);
            }
            else
            // se null elimino da redis
            {
                memLayer.ML.setRSV(redNextBunkKey(currBunkId), "");
            }
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// lettura currBunk table da cache
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static DS_App.StackListDataTable getCurrBunkTab(string machine)
        {
            DS_App.StackListDataTable answ = null;
            string rawData = memLayer.ML.getRSV(redCurrBunkTabKey(machine));
            if (!string.IsNullOrEmpty(rawData))
            {
                try
                {
                    // deserializzo...
                    answ = JsonConvert.DeserializeObject<DS_App.StackListDataTable>(rawData);
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Recupero da redis tab fogli correnti
        /// </summary>
        protected static DS_App.SheetListDataTable getCurrSheetTab(string machine)
        {
            DS_App.SheetListDataTable answ = null;
            string rawData = memLayer.ML.getRSV(redCurrSheetTabKey(machine));
            if (!string.IsNullOrEmpty(rawData))
            {
                try
                {
                    // deserializzo...
                    answ = JsonConvert.DeserializeObject<DS_App.SheetListDataTable>(rawData);
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Chiave BATCH corrente su redis
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static string redCurrBatchId(string machine)
        {
            string answ = "";
            if (string.IsNullOrEmpty(machine))
            {
                answ = $"{redProdReq}:CurrBatchID";
            }
            else
            {
                answ = $"{redProdReq}:CurrBatchID:{machine}";
            }
            return answ;
        }

        /// <summary>
        /// Chiave Bunk corrente su redis
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static string redCurrBunkId(string machine)
        {
            string answ = "";
            if (string.IsNullOrEmpty(machine))
            {
                answ = $"{redProdReq}:CurrBunkID";
            }
            else
            {
                answ = $"{redProdReq}:CurrBunkID:{machine}";
            }
            return answ;
        }

        /// <summary>
        /// Chiave bunk corrente su redis
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static string redCurrBunkTabKey(string machine)
        {
            string answ = "";
            if (string.IsNullOrEmpty(machine))
            {
                answ = $"{redProdReq}:CurrBunks";
            }
            else
            {
                answ = $"{redProdReq}:CurrBunks:{machine}";
            }
            return answ;
        }

        /// <summary>
        /// Chiave Sheets corrente su redis
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static string redCurrSheetId(string machine)
        {
            string answ = "";
            if (string.IsNullOrEmpty(machine))
            {
                answ = $"{redProdReq}:CurrSheetID";
            }
            else
            {
                answ = $"{redProdReq}:CurrSheetID:{machine}";
            }
            return answ;
        }

        /// <summary>
        /// Chiave sheets correnti su redis
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        protected static string redCurrSheetTabKey(string machine)
        {
            string answ = "";
            if (string.IsNullOrEmpty(machine))
            {
                answ = $"{redProdReq}:CurrSheets";
            }
            else
            {
                answ = $"{redProdReq}:CurrSheets:{machine}";
            }
            return answ;
        }

        /// <summary>
        /// Chiave primo bunk su redis
        /// </summary>
        protected static string redNextBunkKey(int BunkID)
        {
            return $"{redProdReq}:NextBunk:{BunkID}";
        }

        /// <summary>
        /// salvataggio inc ache currBunk table
        /// </summary>
        /// <param name="machine"></param>
        /// <param name="tabBunks"></param>
        protected static void setCurrBunkTab(string machine, DS_App.StackListDataTable tabBunks)
        {
            string rawData = JsonConvert.SerializeObject(tabBunks);
            // TTL 15 sec
            memLayer.ML.setRSV(redCurrBunkTabKey(machine), rawData, 15);
        }

        /// <summary>
        /// Salvataggio in redis tab fogli correnti
        /// </summary>
        /// <param name="machine"></param>
        /// <param name="tabella"></param>
        protected static void setCurrSheetTab(string machine, DS_App.SheetListDataTable tabella)
        {
            string rawData = JsonConvert.SerializeObject(tabella);
            // TTL 5 sec
            memLayer.ML.setRSV(redCurrSheetTabKey(machine), rawData, 2);
        }

        /// <summary>
        /// Recupera prossimo batch da DB (NEXT... da db x status...)
        /// </summary>
        protected int getNextBatch(string PlaceCod)
        {
            DataLayer DLMan = new DataLayer();
            int answ = 0;
            try
            {
                var currData = DLMan.taBL.getNextAvailable(PlaceCod);
                if (currData.Count > 0)
                {
                    answ = currData[0].BatchID;
                }
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Recupera SHEET da batch e sstati permessi...
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="minVal"></param>
        /// <param name="maxVal"></param>
        /// <returns></returns>
        protected int getSheetIdByBatch(int BatchID, int minVal, int maxVal)
        {
            int answ = 0;
            try
            {
                DataLayer DLMan = new DataLayer();
                var tabSheet = DLMan.taSHL.getByMLStatus(BatchID, minVal, maxVal);
                if (tabSheet.Count > 0)
                {
                    answ = tabSheet[0].SheetID;
                }
            }
            catch
            { }
            return answ;
        }

        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Aggiorna revisione indice sheet x BUNK (cambio foglio)
        /// </summary>
        /// <param name="BunkID"></param>
        /// <returns></returns>
        public static long advaceSheetRevByBunk(int BunkID)
        {
            long answ = 0;
            if (BunkID > 0)
            {
                // recupero da REDIS!
                string redKeyExp = memLayer.ML.redHash($"DataExp");
                string redKeyRev = $"{ComLib.machineUnloadBunkArea(BunkID)}:Rev";
                // incremento...
                answ = memLayer.ML.setRCntI(redKeyRev);
                // se > 999 --> resetto
                if (answ > 999)
                {
                    memLayer.ML.resetRCnt(redKeyRev);
                }
            }
            return answ;
        }

        /// <summary>
        /// Elenco dei Batch descendant di un batch ancestor
        /// </summary>
        public static DS_App.BatchListDataTable BatchDescendant(int BatchId)
        {
            DataLayer DLMan = new DataLayer();
            string redKey = $"{redBatchDescend}:{BatchId}";
            string rawData = "";
            DS_App.BatchListDataTable answ = new DS_App.BatchListDataTable();
            // cerco in redis
            if (memLayer.ML.redKeyPresent(redKey))
            {
                rawData = memLayer.ML.getRSV(redKey);
                if (!string.IsNullOrEmpty(rawData))
                {
                    try
                    {
                        answ = JsonConvert.DeserializeObject<DS_App.BatchListDataTable>(rawData);
                    }
                    catch
                    { }
                }
            }
            // se vuoto rileggo
            if (answ.Count == 0)
            {
                answ = DLMan.taBL.getDescendByKey(BatchId);
                // salvo in cache
                rawData = JsonConvert.SerializeObject(answ);
                memLayer.ML.setRSV(redKey, rawData, 5 * 60);
            }
            return answ;
        }

        /// <summary>
        /// Elenco dei Batch descendant di un batch a sua volta descendant
        /// </summary>
        public static DS_App.BatchListDataTable BatchOtherDescendant(int BatchDescId)
        {
            DataLayer DLMan = new DataLayer();
            string redKey = $"{redBatchDescend}:{BatchDescId}";
            string rawData = "";
            DS_App.BatchListDataTable answ = new DS_App.BatchListDataTable();
            // cerco in redis
            if (memLayer.ML.redKeyPresent(redKey))
            {
                rawData = memLayer.ML.getRSV(redKey);
                if (!string.IsNullOrEmpty(rawData))
                {
                    try
                    {
                        answ = JsonConvert.DeserializeObject<DS_App.BatchListDataTable>(rawData);
                    }
                    catch
                    { }
                }
            }
            // se vuoto rileggo
            if (answ.Count == 0)
            {
                answ = DLMan.taBL.getSplitByKey(BatchDescId);
                // salvo in cache
                rawData = JsonConvert.SerializeObject(answ);
                memLayer.ML.setRSV(redKey, rawData, 5 * 60);
            }
            return answ;
        }

        public static string BatchStatusDescr(object value)
        {
            string answ = "";
            try
            {
                BatchStatus bStatus = (BatchStatus)Enum.Parse(typeof(BatchStatus), value.ToString());
                switch (bStatus)
                {
                    case BatchStatus.Imported:
                        answ = traduci("Imported");
                        break;

                    case BatchStatus.EstimationRequested:
                        answ = traduci("EstimationRequested");
                        break;

                    case BatchStatus.EstimationDone:
                        answ = traduci("EstimationCompleted");
                        break;

                    case BatchStatus.NestRequested:
                        answ = traduci("NestingRequested");
                        break;

                    case BatchStatus.NestDone:
                        answ = traduci("NestingCompleted");
                        break;

                    case BatchStatus.Approved:
                        answ = traduci("NestingApproved");
                        break;

                    case BatchStatus.Discarded:
                        answ = traduci("NestingDiscarded");
                        break;

                    case BatchStatus.Errors:
                        answ = traduci("NestingPrelimErrors");
                        break;

                    case BatchStatus.PartEval:
                        answ = traduci("NestPartEvaluating");
                        break;

                    case BatchStatus.PartOk:
                        answ = traduci("NestPartValidated");
                        break;

                    case BatchStatus.PartKo:
                        answ = traduci("NestPartNotValidated");
                        break;

                    default:
                        answ = traduci("NestStateUnk");
                        break;
                }
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Stato batch (bozza/stima/nesting...)
        /// </summary>
        /// <param name="BatchId"></param>
        /// <returns></returns>
        public static BatchStatus BStatus(int BatchId)
        {
            DataLayer DLMan = new DataLayer();
            BatchStatus answ = BatchStatus.Imported;
            DS_App.BatchListDataTable tabBatch = DLMan.taBL.getByKey(BatchId);
            if (tabBatch != null && tabBatch.Count > 0)
            {
                try
                {
                    // mostro split se di tipo 1...
                    answ = (BatchStatus)tabBatch[0].STATUS;
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Tipo batch tra ancestor/descendant
        /// </summary>
        /// <param name="BatchId"></param>
        /// <returns></returns>
        public static BatchType BType(int BatchId)
        {
            DataLayer DLMan = new DataLayer();
            BatchType answ = BatchType.Original;
            DS_App.BatchListDataTable tabBatch = DLMan.taBL.getByKey(BatchId);
            if (tabBatch != null && tabBatch.Count > 0)
            {
                try
                {
                    // mostro split se di tipo 1...
                    answ = (BatchType)tabBatch[0].BatchType;
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Verifica lo stato di una richiesta di esecuzione  BATCH x stima/nesting
        /// </summary>
        /// <param name="OffOrderID">OfflineOrder di cui si chiede processing</param>
        /// <param name="note">note opzionali</param>
        /// <returns></returns>
        public static bool checkBatchReq(int OffOrderID)
        {
            bool answ = false;
            // restituisco ok
            return answ;
        }

        /// <summary>
        /// Verifica lo stato di una richiesta di esecuzione
        /// </summary>
        /// <param name="OffOrderID">OfflineOrder di cui si chiede processing</param>
        /// <param name="note">note opzionali</param>
        /// <returns></returns>
        public static bool checkOfflineOrderReq(int OffOrderID)
        {
            DataLayer DLMan = new DataLayer();
            bool answ = false;
            string typeSearch = "OffOrdCalculation";
            string keyEnv = "";
            // recupero lista dei vari task APERTI...
            KeyValuePair<string, string>[] comAperte = getEnvList();
            // processo x cercare ordine...
            foreach (var item in comAperte)
            {
                // cerco dove sia quello richiesto
                if (item.Value.Contains(typeSearch))
                {
                    // controllo SE sia quello richiesto
                    if (item.Value == $"{OffOrderID}|{typeSearch}")
                    {
                        keyEnv = item.Key;
                        break;
                    }
                }
            }
            // controllo e se c'è risposta --> update DB
            string redKey = $"{redNestAnsw}:FULL:{keyEnv}";
            string rawAnsw = memLayer.ML.getRSV(redKey);
            if (rawAnsw != "")
            {
                // cerco risposta come stack --> disegno...
                var offOrder = deserializeOfflineOrder(rawAnsw);
                // se ho in risposta drawing
                if (!string.IsNullOrEmpty(offOrder.DrawingPath))
                {
                    DLMan.taOffOL.updateDrawing(OffOrderID, offOrder.DrawingPath, offOrder.CncPath);
                }
            }
            // restituisco ok
            return answ;
        }

        /// <summary>
        /// Verifica esistenza del PDF SE presente negli optParams
        /// - OK SE NO c'è pdf
        /// - OK SE presente parametro --> cerca il file --> lo trova
        /// </summary>
        /// <param name="reqPart">Part da analizzare</param>
        /// <param name="pdfPath">PAth del file PDF analizzato</param>
        /// <returns></returns>
        public static bool checkPdfExistAccessible(Part reqPart, out string pdfPath)
        {
            pdfPath = getPdfFilePath(reqPart.OptParameters);
            bool answ = string.IsNullOrEmpty(pdfPath);
            if (!answ)
            {
                try
                {
                    string parentDir = Path.GetDirectoryName(pdfPath);
                    bool shareOk = true;
                    bool dirOk = true;
                    if (IsUnc(parentDir))
                    {
                        string pathRoot = Path.GetPathRoot(parentDir);
                        Uri newUri = new Uri(pathRoot);
                        // tento PING
                        PingReply pingResult = NKC.testPingDevice(newUri.Host);
                        shareOk = (pingResult.Status == IPStatus.Success);
                    }
                    if (shareOk)
                    {
                        dirOk = Directory.Exists(parentDir);
                        if (dirOk)
                        {
                            answ = File.Exists(pdfPath);
                        }
                    }
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// verifica che lo status e la decodifica ci siano...
        /// </summary>
        /// <param name="machine"></param>
        public static DS_App.StatusDecodeRow checkStationDecode(string station, string code)
        {
            DataLayer DLMan = new DataLayer();
            var currTab = TabStatusDec;

            // cerco item
            DS_App.StatusDecodeRow foundItem = currTab.Where(x => x.Station == station && x.Code == code).FirstOrDefault();
            if (foundItem == null)
            {
                // forzo (ri)lettura...
                currTab = DLMan.taStatDec.GetData();
                TabStatusDec = currTab;
            }
            // cerco di nuovoitem
            foundItem = currTab.Where(x => x.Station == station && x.Code == code).FirstOrDefault();
            if (foundItem == null)
            {
                // se non ci fosse --> creo
                DLMan.taStatDec.insert(station, code);
                // inserisco empty x 1 sec così rileggerà...
                memLayer.ML.setRSV(redProdStatDec, "", 1);
                // rileggo
                currTab = TabStatusDec;
                foundItem = currTab.Where(x => x.Station == station && x.Code == code).FirstOrDefault();
            }

            return foundItem;
        }

        /// <summary>
        /// Deserializza un ordine offline
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        public static nestReplyOffOrd deserializeOfflineOrder(string rawData)
        {
            nestReplyOffOrd answ = null;
            try
            {
                answ = JsonConvert.DeserializeObject<nestReplyOffOrd>(rawData);
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Helper x deserializzare l'oggetto
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        public static Takt deserializeTakt(string rawData)
        {
            Takt answ = JsonConvert.DeserializeObject<Takt>(rawData);
            return answ;
        }

        /// <summary>
        /// Cache redis del BunkID in lavorazione sulla macchina
        /// <param name="machine"></param>
        /// </summary>
        public static int getCurrBatchId(string macchina)
        {
            int answ = -1;
            string rawData = memLayer.ML.getRSV(redCurrBatchId(macchina));
            int.TryParse(rawData, out answ);
            return answ;
        }

        /// <summary>
        /// BUNK corrente (x ora copia di first, poi sarà quello SELEZIONATO tra quelli SELEZIONABILI)
        /// </summary>
        /// <returns></returns>
        public static DS_App.StackListRow getCurrBunk(string machine = "WRK001")
        {
            DS_App.StackListRow answ = null;
            DS_App.StackListDataTable tabBunk = null;
            DataLayer DLMan = new DataLayer();
            // cerco in cache....
            if (getCurrBunkTab(machine) != null)
            {
                tabBunk = getCurrBunkTab(machine);
            }
            else
            {
                tabBunk = DLMan.taSTL.getLoaded(machine);
                // salvo in redis
                setCurrBunkTab(machine, tabBunk);
            }
            if (tabBunk != null && tabBunk.Count > 0)
            {
                answ = tabBunk[0];
            }
            return answ;
        }

        /// <summary>
        /// Cache redis del BunkID in lavorazione sulla macchina
        /// <param name="machine"></param>
        /// </summary>
        public static int getCurrBunkId(string macchina)
        {
            int answ = -1;
            string rawData = memLayer.ML.getRSV(redCurrBunkId(macchina));
            int.TryParse(rawData, out answ);
            return answ;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sheetID"></param>
        /// <returns></returns>
        public static string getCurrentCss(int sheetID)
        {
            DataLayer DLMan = new DataLayer();
            // area REDIS!
            string redKeyBase = $"{machineUnloadArea(sheetID)}";
            // TTL standard
            int dataCacheTTL = memLayer.ML.cdvi("cssCacheTTL");
            dataCacheTTL = dataCacheTTL <= 0 ? 60 : dataCacheTTL;
            // files
            string filename = HttpContext.Current.Server.MapPath("~/Content/SheetColor.css");
            string answ = File.ReadAllText(filename);
            // solo se sheet > 0...
            if (sheetID > 0)
            {
                try
                {
                    // elenco items da foglio!!!
                    var tabItems = DLMan.taIL.getBySheet(sheetID);
                    List<string> itemsAll = new List<string>();
                    List<string> itemsDepo = new List<string>();
                    List<string> itemsCart = new List<string>();
                    List<string> itemsBin = new List<string>();
                    List<string> itemsSecOp = new List<string>();
                    List<string> itemsScrap = new List<string>();
                    List<string> itemsSelect = new List<string>();

                    //se ho items...
                    if (tabItems.Count > 0)
                    {
                        // ciclo!
                        foreach (var item in tabItems)
                        {
                            // aggiungoncomunque a lista generale...
                            itemsAll.Add(item.ItemDtmx);
                            // controllo se sia SCRAP
                            if (item.StatusID >= 990)
                            {
                                itemsScrap.Add(item.ItemDtmx);
                            }
                            // controllo SE sia stato depositato... check status 3/4
                            else if (item.StatusID == 3 || item.StatusID == 4)
                            {
                                itemsDepo.Add(item.ItemDtmx);
                            }
                            else if (item.ProcessesReq.Contains("PaintFlag"))
                            {
                                itemsBin.Add(item.ItemDtmx);
                            }
                            else
                            {
                                itemsCart.Add(item.ItemDtmx);
                            }
                            // controllo ANCHE postprocessing
                            if (item.PostProcList != "")
                            {
                                itemsSecOp.Add(item.ItemDtmx);
                            }
                            // se selezionato status 2...
                            if (item.StatusID == 2)
                            {
                                itemsSelect.Add(item.ItemDtmx);
                            }
                        }
                    }

                    // FIX BIN
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsBin", itemsBin, dataCacheTTL);

                    // FIX CART
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsCart", itemsCart, dataCacheTTL);

                    // FIX Scaricati
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsDepo", itemsDepo, dataCacheTTL);

                    // FIX SEC-OP
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsSecOp", itemsSecOp, dataCacheTTL);

                    // FIX SCRAP
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsScrap", itemsScrap, dataCacheTTL);

                    // FIXED SEL da array oggetti selezionati...
                    answ = updateCssByItemList(answ, redKeyBase, "ItemsSel", itemsSelect, dataCacheTTL);
                    //answ = updateCssByPickedItems(answ, redKeyBase, "ItemsSel", dataCacheTTL);

                    // INFINE serializzo e salvo tutti gli items trovati...
                    string serVal = JsonConvert.SerializeObject(itemsAll);
                    memLayer.ML.setRSV($"{redKeyBase}:ItemsAll", serVal, dataCacheTTL);

                    // salvo redis css!
                    memLayer.ML.setRSV($"{redKeyBase}:Css", answ, dataCacheTTL);
                }
                catch
                { }
            }

            return answ;
        }

        /// <summary>
        /// PackList corrente (running in KIT)
        /// </summary>
        /// <returns></returns>
        public static DS_App.PackListRow getCurrPackList()
        {
            DataLayer DLMan = new DataLayer();
            DS_App.PackListRow answ = null;
            var tabPLD = DLMan.taPL.getRunning();
            if (tabPLD.Count > 0)
            {
                answ = tabPLD[0];
            }
            return answ;
        }

        /// <summary>
        /// Recupera lo sheet corrente da un BUNK come quello in status 5...
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="machine"></param>
        /// <returns></returns>
        public static DS_App.SheetListRow getCurrSheet(int BatchID, string machine)
        {
            DS_App.SheetListRow answ = null;
            if (BatchID > 0)
            {
                try
                {
                    DS_App.SheetListDataTable tabSheets = null;
                    // cerco in cache....
                    if (getCurrSheetTab(machine) != null)
                    {
                        tabSheets = getCurrSheetTab(machine);
                    }
                    else
                    {
                        // recupero sheet corrente da Bunk...
                        DataLayer DLMan = new DataLayer();
                        tabSheets = DLMan.taSHL.getByMLStatus(BatchID, 5, 5);
                        // salvo in redis
                        setCurrSheetTab(machine, tabSheets);
                    }
                    // leggo prima riga --> foglio corrente
                    if (tabSheets != null && tabSheets.Count > 0)
                    {
                        answ = tabSheets[0];
                    }
                }
                catch
                { }
            }
            return answ;
        }

        /// <summary>
        /// Cache redis del SheetID in lavorazione sulla macchina
        /// <param name="machine"></param>
        /// </summary>
        public static int getCurrSheetId(string macchina)
        {
            int answ = -1;
            string rawData = memLayer.ML.getRSV(redCurrSheetId(macchina));
            int.TryParse(rawData, out answ);
            return answ;
        }

        /// <summary>
        /// Restituisce elenco KVP delle buste ancora "pending" (quando processate le elimina da elenco...)
        /// </summary>
        /// <returns></returns>
        public static KeyValuePair<string, string>[] getEnvList()
        {
            KeyValuePair<string, string>[] answ = memLayer.ML.redGetHash(redMsgList);
            return answ;
        }

        /// <summary>
        /// IP del device
        /// </summary>
        /// <returns></returns>
        public static string GetIPAddress()
        {
            HttpContext context = HttpContext.Current;
            string ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

            if (!string.IsNullOrEmpty(ipAddress))
            {
                string[] addresses = ipAddress.Split(',');
                if (addresses.Length != 0)
                {
                    return addresses[0];
                }
            }

            return context.Request.ServerVariables["REMOTE_ADDR"];
        }

        /// <summary>
        /// Restituisce il prossimo codice di envelope per comunicare con sistemi esterni
        /// </summary>
        /// <param name="BatchID">Batch contenuto nell'envelope</param>
        /// <param name="note">note opzionali (motivo envelope)</param>
        /// <returns></returns>
        public static string getNextEnv(int BatchID, string note)
        {
            // incremento counter
            long nextIndex = memLayer.ML.setRCntI(redMsgCount);
            // salvo contenuto della busta
            string answ = $"Z{nextIndex:000000000000}";
            Dictionary<string, string> lista = new Dictionary<string, string>();
            lista.Add(answ, $"{BatchID}|{note}");
            memLayer.ML.redSaveHashDict(redMsgList, lista);
            // ritorno
            return answ;
        }

        /// <summary>
        /// Recupera file pdf
        /// </summary>
        /// <param name="optParameters"></param>
        /// <returns></returns>
        public static string getPdfFilePath(Dictionary<string, string> optParameters)
        {
            string answ = "";
            if (optParameters.ContainsKey("PdfLink"))
            {
                answ = optParameters["PdfLink"];
            }
            return answ;
        }

        /// <summary>
        /// Recupera le operazioni di PostProcessing, esempio:
        /// - T-Nut
        /// - Round
        /// - Chop
        /// </summary>
        /// <param name="optParameters"></param>
        /// <returns></returns>
        public static string getPostProcList(Dictionary<string, string> optParameters)
        {
            string answ = "";
            foreach (var currOpt in optParameters)
            {
                switch (currOpt.Key)
                {
                    case "ChopAtTab":
                    case "RoundEdge":
                    case "TNutFlag":
                        // se contiene YES aggiungo...
                        if (currOpt.Value.ToLower() == "yes")
                        {
                            answ += $"{currOpt.Key}#";
                        }
                        break;
                }
            }
            // se finisce per "#" trimmo...
            if (answ.EndsWith("#"))
            {
                answ = answ.Substring(0, answ.Length - 1);
            }
            return answ;
        }

        /// <summary>
        /// Recupera le operazioni RICHIESTE a valle
        /// - Paint
        /// - Assembly
        /// - SecOp
        /// </summary>
        /// <param name="optParameters"></param>
        /// <returns></returns>
        public static string getProcessesReq(Dictionary<string, string> optParameters)
        {
            string answ = "";
            foreach (var currOpt in optParameters)
            {
                switch (currOpt.Key)
                {
                    case "AssemblyCell":
                    case "PaintFlag":
                        // se contiene YES aggiungo...
                        if (currOpt.Value.ToLower() == "yes")
                        {
                            answ += $"{currOpt.Key}#";
                        }
                        break;
                }
            }
            // se finisce per "#" trimmo...
            if (answ.EndsWith("#"))
            {
                answ = answ.Substring(0, answ.Length - 1);
            }
            return answ;
        }

        public static int getSecScreenCode()
        {
            int answ = 0;
            Random rnd = new Random();
            answ = rnd.Next(1000000);
            return answ;
        }

        /// <summary>
        /// Restitusice il path del PDF richeisto su una data SecScreen come salvato su REDIS
        /// </summary>
        /// <param name="secScreenCode">cod schermo, formato SSC000000</param>
        /// <returns></returns>
        public static string getSecScreenRequest(string secScreenCode)
        {
            string answ = "";
            // recupero ultima call
            string redKey = $"{redSecScreenReq}:{secScreenCode}";
            answ = memLayer.ML.getRSV(redKey);
            return answ;
        }

        /// <summary>
        /// Recupera da REDIS info indice revisione x BUNK (cambio foglio)
        /// </summary>
        /// <param name="BunkID"></param>
        /// <returns></returns>
        public static int getSheetRevByBunk(int BunkID)
        {
            int answ = 0;
            if (BunkID > 0)
            {
                // recupero da REDIS!
                string redKeyExp = memLayer.ML.redHash($"DataExp");
                string redKeyRev = $"{ComLib.machineUnloadBunkArea(BunkID)}:Rev";
                // controllo expiry globale... se manca SVUOTO area
                string rawData = memLayer.ML.getRSV(redKeyExp);
                if (string.IsNullOrEmpty(rawData))
                {
                    // svuoto e scrivo...
                    memLayer.ML.redFlushKey(memLayer.ML.redHash($"MachineUnload"));
                    memLayer.ML.redFlushKey(memLayer.ML.redHash($"TabSheets"));
                    memLayer.ML.setRSV(redKeyExp, $"Reload Data {DateTime.Now}", 3600);
                }
                answ = memLayer.ML.getRCnt(redKeyRev);
            }
            return answ;
        }

        public static bool IsUnc(string path)
        {
            string root = Path.GetPathRoot(path);

            // Check if root starts with "\\", clearly an UNC
            if (root.StartsWith(@"\\"))
                return true;

            // Check if the drive is a network drive
            DriveInfo drive = new DriveInfo(root);
            if (drive.DriveType == DriveType.Network)
                return true;

            return false;
        }

        /// <summary>
        /// restitusice ultima chiamata REST registrata su REDIS
        /// </summary>
        /// <returns></returns>
        public static string lastRestAnsw()
        {
            string answ = "";
            // recupero ultima call
            string redKey = $"{redNestAnsw}:LAST_CALL";
            answ = memLayer.ML.getRSV(redKey);
            return answ;
        }

        /// <summary>
        /// Calcola area REDIS per MACCHINA
        /// </summary>
        /// <param name="SheetID"></param>
        /// <returns></returns>
        public static string machineArea(string machineCod)
        {
            string answ = memLayer.ML.redHash($"currMachineData:{machineCod}");
            return answ;
        }

        /// <summary>
        /// Calcola area REDIS per FOGLIO in fase di scarico
        /// </summary>
        /// <param name="SheetID"></param>
        /// <returns></returns>
        public static string machineUnloadArea(int SheetID)
        {
            // se 0 --> tutto l'albero, sennò solo area corrente...
            string answ = "";
            if (SheetID > 0)
            {
                answ = memLayer.ML.redHash($"MachineUnload:{SheetID}");
            }
            else
            {
                answ = memLayer.ML.redHash($"MachineUnload");
            }
            return answ;
        }

        /// <summary>
        /// Calcola area REDIS per BUNK in fase di scarico
        /// </summary>
        /// <param name="BunkID"></param>
        /// <returns></returns>
        public static string machineUnloadBunkArea(int BunkID)
        {
            // se 0 --> tutto l'albero, sennò solo area corrente...
            string answ = "";
            if (BunkID > 0)
            {
                answ = memLayer.ML.redHash($"MachineUnload:Bunk:{BunkID}");
            }
            else
            {
                answ = memLayer.ML.redHash($"MachineUnload:Bunk");
            }
            return answ;
        }

#if false
        /// <summary>
        /// Elenco degli orders dato Batch
        /// </summary>
        public static DS_App.OrderListDataTable OrdersByBatch(int BatchId)
        {
            DataLayer DLMan = new DataLayer();
            string redKey = $"{redOrders}:BYBATCH:{BatchId}";
            string rawData = "";
            DS_App.OrderListDataTable answ = new DS_App.OrderListDataTable();
            // cerco in redis
            if (memLayer.ML.redKeyPresent(redKey))
            {
                rawData = memLayer.ML.getRSV(redKey);
                if (!string.IsNullOrEmpty(rawData))
                {
                    try
                    {
                        answ = JsonConvert.DeserializeObject<DS_App.OrderListDataTable>(rawData);
                    }
                    catch
                    { }
                }
                // se vuoto rileggo
                if (answ.Count == 0)
                {
                    answ = DLMan.taOL.getByBatch(BatchId);
                    // salvo in cache
                    rawData = JsonConvert.SerializeObject(answ);
                    memLayer.ML.setRSV(redKey, rawData, 5 * 60);
                }
            }
            return answ;
        }
#endif

        /// <summary>
        /// Elenco degli orders dato Batch (Ancestor/Principale)
        /// </summary>
        /// <param name="BatchId"></param>
        /// <returns></returns>
        public static DS_App.OrderListTreeDataTable OrdersExtByBatch(int BatchId)
        {
            DS_App.OrderListTreeDataTable answ = new DS_App.OrderListTreeDataTable();
            if (BatchId > 0)
            {
                DataLayer DLMan = new DataLayer();
                string redKey = $"{redOrders}:BYBATCH:{BatchId}";
                string rawData = "";

                // cerco in redis
                if (memLayer.ML.redKeyPresent(redKey))
                {
                    rawData = memLayer.ML.getRSV(redKey);
                    if (!string.IsNullOrEmpty(rawData))
                    {
                        try
                        {
                            answ = JsonConvert.DeserializeObject<DS_App.OrderListTreeDataTable>(rawData);
                        }
                        catch
                        { }
                    }
                }
                // se vuoto rileggo
                if (answ.Count == 0)
                {
                    // recupero ordini dato il batch MAIN
                    answ = DLMan.taOLT.getByBatchTree(BatchId);

                    // salvo in cache
                    rawData = JsonConvert.SerializeObject(answ);
                    memLayer.ML.setRSV(redKey, rawData, 1);
                }
            }
            return answ;
        }

        public static string PositionStatusDescr(object value)
        {
            string answ = "";
            try
            {
                BatchPosition pStatus = (BatchPosition)Enum.Parse(typeof(BatchPosition), value.ToString());
                switch (pStatus)
                {
                    case BatchPosition.NotStarted:
                        answ = traduci("NotStarted");
                        break;

                    case BatchPosition.StackStarted:
                        answ = traduci("Stacking");
                        break;

                    case BatchPosition.StackDone:
                        answ = traduci("StackDone");
                        break;

                    case BatchPosition.Current:
                        answ = traduci("BatchCurrent");
                        break;

                    case BatchPosition.Completed:
                        answ = traduci("BatchCompleted");
                        break;

                    default:
                        break;
                }
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Esegue richieste lettura da PROD
        /// </summary>
        public static void procProdReadReq()
        {
            // !!!FIXME!!!
        }

        /// <summary>
        /// Esegue richieste scrittura da PROD
        /// </summary>
        public static void procProdWriteReq()
        {
            // !!!FIXME!!!
        }

        /// <summary>
        /// Recupera e transcodifica DA DB i dati di UN SINGOLO bunk
        /// </summary>
        /// <param name="currBunk"></param>
        /// <returns></returns>
        public static ProdBunk prodGetBunk(int StackID)
        {
            DataLayer DLMan = new DataLayer();
            ProdBunk answ = null;
            List<ProdSheet> elSheet = new List<ProdSheet>();
            var tabBunks = DLMan.taSTL.getByKey(StackID);
            if (tabBunks.Count == 1)
            {
                DS_App.StackListRow currBunk = tabBunks[0];
                // calcolo attributi oggetti
                CStatus currSt = CStatus.Programmed;
                if (currBunk.Position == 5)
                {
                    currSt = CStatus.Running;
                }
                else
                {
                    currSt = CStatus.Done;
                }
                // recupero gli sheets di questo stack...
                DS_App.SheetListDataTable tabSheets = DLMan.taSHL.getByStack(StackID);
                DateTime dataStart = DateTime.Now;
                ProdSheet currPanel;
                string nestBasePath = memLayer.ML.CRS("nestBasePath").ToLower();
                string servBasePath = memLayer.ML.CRS("servBasePath").ToLower();
                string prodBasePath = memLayer.ML.CRS("prodBasePath").ToLower();
                foreach (var item in tabSheets)
                {
                    // converto i workData
                    WorkData wdPrint = new WorkData()
                    {
                        DtStart = item.IsPrntStartNull() ? null : (DateTime?)item.PrntStart,
                        DtEnd = item.IsPrntEndNull() ? null : (DateTime?)item.PrntEnd,
                        ProgramPath = item.PrintFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                    };
                    WorkData wdMachining = new WorkData()
                    {
                        DtStart = item.IsWrkStartNull() ? null : (DateTime?)item.WrkStart,
                        DtEnd = item.IsWrkEndNull() ? null : (DateTime?)item.WrkEnd,
                        ProgramPath = item.CncFilePath.ToLower().Replace(nestBasePath, prodBasePath).Replace(servBasePath, prodBasePath)
                    };
                    WorkData wdUnload = new WorkData()
                    {
                        DtStart = item.IsUnlStartNull() ? null : (DateTime?)item.UnlStart,
                        DtEnd = item.IsUnlEndNull() ? null : (DateTime?)item.UnlEnd
                    };
                    MaterialData material = new MaterialData()
                    {
                        MaterialId = item.MatID,
                        MaterialDescription = item.MatDesc,
                        MaterialPN = item.MatExtCode.ToString()
                    };
                    PStatus currPnlStatus = PStatus.Programmed;
                    Enum.TryParse(item.ShStatus.ToString(), out currPnlStatus);
                    currPanel = new ProdSheet()
                    {
                        Printing = wdPrint,
                        Machining = wdMachining,
                        Unloading = wdUnload,
                        Material = material,
                        SheetId = item.StackID,
                        Status = currPnlStatus
                    };
                    elSheet.Add(currPanel);
                    if (!item.IsPrntStartNull())
                    {
                        dataStart = item.PrntStart < dataStart ? item.PrntStart : dataStart;
                    }
                }
                // compongo il bunk...
                answ = new ProdBunk()
                {
                    BunkId = currBunk.StackID,
                    Status = currSt,
                    DataMatrix = currBunk.StackDtmx,
                    SheetList = elSheet,
                    DtStart = dataStart
                };
            }
            return answ;
        }

        /// <summary>
        /// Restituisce il BUNK che è il primo della lista x la MACCHINA indicata:
        /// - posizione = 3 (ho letto da webApp il BUNK e preso in carico)
        /// - NumSheet > NumSheetUnload
        /// - Ordinato per StackIndex (crescente) x avere il più VECCHIO
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        public static ProdBunk prodGetFirstBunk(string machine)
        {
            DataLayer DLMan = new DataLayer();
            // cerco prima su REDIS...
            ProdBunk answ = redisFirstBunk;
            if (answ == null)
            {
                // vado sul DB e leggo ...
                DS_App.StackListDataTable tabBunks = DLMan.taSTL.getLoaded(machine);
                // controllo di averne almeno 1...
                if (tabBunks.Count > 0)
                {
                    DS_App.StackListRow currBunk = tabBunks[0];
                    answ = getBunkFromDb(currBunk);
                    // se ho qualcosa salvo su REDIS
                    redisFirstBunk = answ;
                }
            }
            return answ;
        }

        /// <summary>
        /// Restituisce il PROSSIMO bunk x la MACCHINA indicata secondo criterio:
        /// - posizione = 5 (ho letto da webApp il BUNK e preso in carico)
        /// - NumSheet > NumSheetUnload
        /// - Ordinato per StackIndex (crescente) x avere il più VECCHIO
        /// - SUCCESSIVO al BunkID(=StackID) ricevuto
        /// </summary>
        /// <param name="machine"></param>
        /// <returns></returns>
        public static ProdBunk prodGetNextBunk(int BunkID, string machine)
        {
            DataLayer DLMan = new DataLayer();
            ProdBunk answ = getRedisNextBunk(BunkID);
            if (answ == null)
            {
                // vado sul DB e leggo ...
                DS_App.StackListDataTable tabBunks = DLMan.taSTL.getLoaded(machine);
                // controllo di averne almeno 1...
                if (tabBunks.Count > 0)
                {
                    DS_App.StackListRow currBunk = null;
                    bool trovato = false;
                    // ciclo
                    foreach (var item in tabBunks)
                    {
                        if (trovato)
                        {
                            currBunk = item;
                            break;
                        }
                        // controllo se sia quello richiesto
                        if (item.StackID == BunkID)
                        {
                            trovato = true;
                        }
                    }
                    // se c'è un bunk trovato --> carico
                    if (currBunk != null)
                    {
                        answ = getBunkFromDb(currBunk);
                        // salvo su redis
                        setRedisNextBunk(BunkID, answ);
                    }
                }
            }
            return answ;
        }

        /// <summary>
        /// Elenco di sheet in lavorazione su una data macchina:
        /// - da macchina recupera il BATCH
        /// - dal batch prende i BUNK CARICATI sulla macchina (posizione 5)
        /// - filtra i fogli già lavorati
        /// </summary>
        /// <param name="codPost"></param>
        /// <returns></returns>
        public static SheetWorkList prodGetSheetWorkList(string codPost)
        {
            DataLayer DLMan = new DataLayer();
            // init out
            SheetWorkList answ = null;
            // recupero batch corrente
            DS_App.BatchListDataTable currBatch = DLMan.taBL.getCurrentByMachine(codPost);
            try
            {
                if (currBatch != null)
                {
                    // come batch DOVREI averne solo 1 in corso...
                    DS_App.BatchListRow batchRow = currBatch[0];
                    // recupero i vari SHEETS del batch...
                    DS_App.SheetListDataTable sheetTable = DLMan.taSHL.getCurrentByBatch(batchRow.BatchID);
                    List<DS_App.SheetListRow> sheetList = new List<DS_App.SheetListRow>();
                    // controllo che SIA NULL la data di unload --> NON ancora scaricato
                    foreach (var item in sheetTable)
                    {
                        // metto in elenco SOLO se NON nulla data fine unload
                        if (item.IsUnlEndNull())
                        {
                            sheetList.Add(item);
                        }
                    }
                    answ = convertSheetWorkList(sheetList);
                }
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Restituisce stato macchina (da Redis)
        /// </summary>
        /// <returns></returns>
        public static List<MachineStatData> prodMachStateDataGet()
        {
            List<MachineStatData> answ = new List<MachineStatData>();

            string redVal = memLayer.ML.getRSV(redProdMachStateData);
            if (!string.IsNullOrEmpty(redVal))
            {
                answ = JsonConvert.DeserializeObject<List<MachineStatData>>(redVal);
            }
            return answ;
        }

        /// <summary>
        /// Salva gli stati macchina ricevuti sul DB e poi salva ultimo su redis
        /// Do x scontato tutti singola macchina
        /// </summary>
        /// <returns></returns>
        public static bool prodMachStateDataInsert(MachineStatData updateRecords)
        {
            /* ------------------------------------------------------------------
             * Info preliminari
             *   Public Const STATION_LINE As String = "LINE"
             *   Public Const STATION_PRINTER As String = "PRINTER"
             *   Public Const STATION_NC_MACHINE As String = "NC_MACHINE"
             *   Public Const STATION_UNLOADER As String = "UNLOADER"
             //*   Public Const STATION_NC_MACHINE As String = "NC MACHINE"
             *
             *
             *
             *   Public Const ST_KEEP_ALIVE_START As String = "00"
             *   Public Const ST_KEEP_ALIVE As String = "01"
             *
             *
             *   Public Const ST_PRINTER_INIT As String = "02"
             *   Public Const ST_PRINTER_WARM_START As String = "03"
             *   Public Const ST_PRINTER_READY As String = "04"
             *   Public Const ST_PRINTER_WAIT_SHEET_LOADED As String = "05"
             *   Public Const ST_PRINTER_PRINTING As String = "06"
             *   Public Const ST_PRINTER_WAIT_SHEET_UNLOADED As String = "07"
             *   Public Const ST_PRINTER_WAIT_PROGRAM As String = "08"
             *
             * ------------------------------------------------------------------ */

            bool answ = false;
            DataLayer DLMan = new DataLayer();

            // recupero clockDrift
            string clockDriftKey = $"{redProdMachClock}:{updateRecords.Machine}";
            string rawDrift = memLayer.ML.getRSV(clockDriftKey);
            double driftSec = 0;
            if (!string.IsNullOrEmpty(rawDrift))
            {
                double.TryParse(rawDrift, out driftSec);
            }

            // salvo in mongo x debug
            ComLib.man.saveProdStat(updateRecords);

            // verifica preliminare della PLACE
            checkPlace(updateRecords.Machine);
            // salvo sul DB il set di dati...
            foreach (var item in updateRecords.Records)
            {
                // i keep alive NON li scrivo ma processo
                switch (item.Code)
                {
                    case "00":
                        // è PRIMO keepalive --> uso x sync dell'orologio
                        DateTime adesso = DateTime.Now;
                        DateTime machClock = item.DtRecord;
                        driftSec = adesso.Subtract(machClock).TotalSeconds;
                        // salvo
                        memLayer.ML.setRSV(clockDriftKey, $"{driftSec}");
                        break;

                    case "01":
                        // x ora lo ignoro ma so che ora la macchina è ALIVE...
                        break;

                    default:
                        DLMan.taStatLog.insert(item.DtRecord.AddMilliseconds(driftSec), updateRecords.Machine, item.Station, item.Code, item.Payload, 0);
                        break;
                }
            }

            // chiamo procedura x fix durate
            DLMan.taStatLog.updateDuration(updateRecords.Machine);

            // recupero ultimo record ricevuto
            try
            {
                var lastRecord = updateRecords.Records.OrderByDescending(x => x.DtRecord).Take(1).ToList();
                saveStatus(updateRecords.Machine, lastRecord);
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Reset stati macchina registrati
        /// </summary>
        /// <returns></returns>
        public static bool prodMachStateDataReset()
        {
            bool answ = memLayer.ML.setRSV(redProdMachStateData, "", 1);
            return answ;
        }

        /// <summary>
        /// Leggo il Takt inviato
        /// </summary>
        /// <param name="origin">Origine del dato: SERV / PROD / NEST</param>
        /// <param name="currData"></param>
        /// <returns></returns>
        public static Takt readTakt(string origin, string TaktId)
        {
            Takt answ = null;
            try
            {
                string redKey = $"NKC:{origin.ToUpper()}:TAKT:{TaktId}";
                string redVal = memLayer.ML.getRSV(redKey);
                answ = deserializeTakt(redVal);
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Reset della cache dei BatchDescendant x ID indicato
        /// </summary>
        /// <param name="BatchId"></param>
        public static void ResetBatchDescendant(int BatchId)
        {
            string redKey = $"{redBatchDescend}:{BatchId}";
            memLayer.ML.setRSV(redKey, "", 10);
        }

        /// <summary>
        /// resetta la richeista corrente in modo che non ce ne siano altre in coda
        /// </summary>
        /// <returns></returns>
        public static bool resetBatchReq()
        {
            bool answ = false;
            try
            {
                currBatchReq = "";
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// resetto curr bunk
        /// </summary>
        /// <param name="machine"></param>
        public static void resetCurrBunk(string machine = "WRK001")
        {
            // svuoto"
            memLayer.ML.redFlushKey(redCurrBunkTabKey(machine));
        }

        /// <summary>
        /// resetto curr sheets
        /// </summary>
        /// <param name="machine"></param>
        public static void resetCurrSheet(string machine = "WRK001")
        {
            // svuoto"
            memLayer.ML.redFlushKey(redCurrSheetTabKey(machine));
        }

        /// <summary>
        /// Registra che un dato ITEM è stato prelevato in fase di scarico da ID foglio + datamatrix
        /// </summary>
        /// <param name="sheetID">ID foglio</param>
        /// <param name="deviceId">Cod del device di scarico</param>
        /// <returns></returns>
        public static bool resetItemPickup(int sheetID, string deviceId)
        {
            // area REDIS!
            string redKeyBase = $"{machineUnloadArea(sheetID)}";
            bool answ = false;
            string rawData = memLayer.ML.getRSV($"{redKeyBase}:ItemsSel");
            if (!string.IsNullOrEmpty(rawData))
            {
                try
                {
                    Dictionary<string, string> dictData = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
                    // cerco chiave device...
                    if (dictData.ContainsKey(deviceId))
                    {
                        // sostituisco
                        dictData.Remove(deviceId);
                    }
                    // salvo!
                    rawData = JsonConvert.SerializeObject(dictData);
                    memLayer.ML.setRSV($"{redKeyBase}:ItemsSel", rawData);
                    answ = true;
                }
                catch
                { }
            }
            // reset memoria redis del CSS x obbligare refresh...
            memLayer.ML.setRSV($"{redKeyBase}:Css", "");
            return answ;
        }

        /// <summary>
        /// Reset della cache dei BatchDescendant x ID indicato
        /// </summary>
        /// <param name="BatchId"></param>
        public static void ResetOrderByBatch(int BatchId)
        {
            string redKey = $"{redOrders}:BYBATCH:{BatchId}";
            memLayer.ML.setRSV(redKey, "", 10);
        }

        /// <summary>
        /// Resetto i dati PRIMA di salvare i nuovi dati dal nesting
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="BunkList"></param>
        public static void resetPrevDataFromNesting(int BatchID)
        {
            //elimino dati child MA NON il batch...
            DataLayer DLMan = new DataLayer();
            DLMan.taBL.deleteTree(BatchID, 0);
        }

        /// <summary>
        /// Resetto in REDIS i dati di bunk (corrente e successivi)
        /// </summary>
        public static void resetRedisBunkData(string machine)
        {
            resetCurrBunk();
            resetCurrSheet();
            setCurrBatchId(machine, 0);
            setCurrBunkId(machine, 0);
            setCurrSheetId(machine, 0);
            redisFirstBunk = null;
        }

        /// <summary>
        /// Annulla (Svuota) richiesta di esecuzione di CAM
        /// </summary>
        /// <returns></returns>
        public static bool resetRequestToNest()
        {
            bool answ = false;

            return answ;
        }

        /// <summary>
        /// Registra che un dato ITEM è stato prelevato in fase di scarico da ID foglio + datamatrix
        /// </summary>
        /// <param name="sheetID">ID foglio</param>
        /// <param name="deviceId">Cod del device di scarico</param>
        /// <param name="itemDtmx">Cod datamatrix ITEM</param>
        /// <returns></returns>
        public static bool saveItemPickup(int sheetID, string deviceId, string itemDtmx)
        {
            // area REDIS!
            string redKeyBase = $"{machineUnloadArea(sheetID)}";
            bool answ = false;
            string rawData = memLayer.ML.getRSV($"{redKeyBase}:ItemsSel");
            Dictionary<string, string> dictData = new Dictionary<string, string>();
            try
            {
                if (!string.IsNullOrEmpty(rawData))
                {
                    dictData = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
                }
                // cerco chiave device...
                if (dictData.ContainsKey(deviceId))
                {
                    // sostituisco
                    dictData[deviceId] = itemDtmx;
                }
                else
                {
                    dictData.Add(deviceId, itemDtmx);
                }
                // salvo!
                rawData = JsonConvert.SerializeObject(dictData);
                memLayer.ML.setRSV($"{redKeyBase}:ItemsSel", rawData);
                answ = true;
                // reset memoria redis del CSS x obbligare refresh...
                memLayer.ML.setRSV($"{redKeyBase}:Css", "");
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salvo il Takt inviato
        /// </summary>
        /// <param name="origin">Origine del dato: SERV / PROD / NEST</param>
        /// <param name="currData"></param>
        /// <returns></returns>
        public static bool saveTakt(string origin, Takt currData)
        {
            bool answ = false;
            try
            {
                // calcolo valori redis
                string redKey = $"NKC:{origin.ToUpper()}:TAKT:{currData.TaktId}";
                string redVal = serializeTakt(currData);
                // scrivo per ora solo su REDIS
                memLayer.ML.setRSV(redKey, redVal);
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Invia una richiesta di esecuzione di Nesting x un Batch
        /// </summary>
        /// <param name="BatchID">Batch di cui si chiede processing</param>
        /// <param name="note">note opzionali</param>
        /// <param name="pType">Tipo processo: 1 = stima, 2 = nesting, 3 = stima EXTENDED</param>
        /// <param name="isValidation">Indica se sia validaizone (part o disegno) --> in tal caso MaxTime = 1</param>
        /// <returns></returns>
        public static bool sendBatchReq(int BatchID, string note, int pType, bool isValidation)
        {
            DataLayer DLMan = new DataLayer();
            bool answ = false;
            // per prima cosa mi serve una "nuova busta" per inviare i messaggi
            string nextEnv = getNextEnv(BatchID, note);
            // preparo il contenuto della busta ed invio i messaggi...
            try
            {
                // in base allo stato del BATCH corrente determino il tempo e le opzioni da inviare...
                var batch = DLMan.taBL.getByKey(BatchID);
                int mTime = 1;
                // se non è validazione verifico che tempo impostare...
                if (!isValidation)
                {
                    if (batch[0].STATUS < (int)BatchStatus.EstimationDone)
                    {
                        mTime = memLayer.ML.cdvi("estimMaxTime");
                    }
                    else
                    {
                        mTime = memLayer.ML.cdvi("nestMaxTime");
                    }
                }

                // init oggetti x fare cicli...
                Order currOrder = null;
                Kit currentKit = null;
                Part currPart = null;
                List<Order> listOrder = new List<Order>();
                List<Kit> listKit = new List<Kit>();
                List<Part> listPart = new List<Part>();

                // leggo tab ORDINI da DB
                var tblOrd = DLMan.taOL.getByBatch(BatchID);
                // leggo tab KIT da DB
                var tblKit = DLMan.taKL.getByBatch(BatchID);
                // leggo tab ITEMS da DB
                var tblItm = DLMan.taIL.getByBatch(BatchID);

                // ciclo per comporre oggetto con cicli annidiati ext --> int
                foreach (var rigaOrd in tblOrd)
                {
                    listKit = new List<Kit>();
                    // compongo kit
                    var righeKit = (DS_App.KitListRow[])tblKit.Select($"OrdID = {rigaOrd.OrdID}");
                    foreach (var rigaKit in righeKit)
                    {
                        listPart = new List<Part>();
                        // elenco ITEMS
                        var righeItems = (DS_App.ItemListRow[])tblItm.Select($"KitID = {rigaKit.KitID}");
                        foreach (var rigaItem in righeItems)
                        {
                            currPart = new Part()
                            {
                                PartId = rigaItem.ItemID,
                                PartExtCode = rigaItem.ItemExtCode,
                                PartDtmx = rigaItem.ItemDtmx,
                                PartQty = rigaItem.ItemQty,
                                MatId = rigaItem.MatID,
                                CadFilePath = rigaItem.CadFilePath
                            };
                            listPart.Add(currPart);
                        }
                        // compongo KIT
                        currentKit = new Kit()
                        {
                            KitId = rigaKit.KitID,
                            KitExtCode = rigaKit.KitExtCode,
                            PartList = listPart
                        };
                        listKit.Add(currentKit);
                    }
                    // compongo ordine
                    currOrder = new Order()
                    {
                        OrderId = rigaOrd.OrdID,
                        OrderCod = rigaOrd.FamilyCode,
                        OrderExtCode = rigaOrd.OrderExtCode,
                        DestPlant = rigaOrd.DestPlant,
                        KitList = listKit
                    };
                    listOrder.Add(currOrder);
                }
                // oggetto complessivo
                batchRequest newBatchreq = new batchRequest()
                {
                    BatchId = BatchID,
                    EnvNum = nextEnv,
                    MaxTime = mTime,
                    ProcType = pType,
                    MachineType = mType.Multiax,
                    OrderType = oType.BatchRequest,
                    OrderList = listOrder
                };

                // serializzo
                string redVal = JsonConvert.SerializeObject(newBatchreq);
                // salvo
                string redKey = $"{redOutPath}:{nextEnv}";
                // scrivo su REDIS
                memLayer.ML.setRSV(redKey, redVal);

                // invio notifica che c'è una busta da processare
                memLayer.ML.setRSV(currBatchReqKey, nextEnv);
                answ = true;
            }
            catch
            { }
            // restituisco ok
            return answ;
        }

        /// <summary>
        /// Manda il primo batch di VALIDAZIONE al NESTING...
        /// </summary>
        /// <returns></returns>
        public static bool sendFirstValidationBatch()
        {
            DataLayer DLMan = new DataLayer();
            bool answ = false;
            // verifico se NON CI SIANO GIA' validazioni in corso...
            var tabBatchRunning = DLMan.taBL.getByStatus(1, "", true, 0);
            if (tabBatchRunning.Count == 0)
            {
                // ora verifico, se ci sono batch per FILE (dxf) VALIDATION in stato 8 (da validare) --> metto in coda!
                DS_App.BatchListDataTable tabBatch = DLMan.taBL.getByStatus(8, "", true, 2);
                // se non ne ho cerco nei PartValidation
                if (tabBatch.Count == 0)
                {
                    tabBatch = DLMan.taBL.getByStatus(8, "", true, 1);
                }
                if (tabBatch.Count > 0)
                {
                    // invia a redis una richiesta...
                    sendMaterials();
                    // recupero PRIMO batchID da validare
                    int nextBatchId = 0;
                    try
                    {
                        nextBatchId = tabBatch[0].BatchID;
                    }
                    catch
                    { }
                    if (nextBatchId > 0)
                    {
                        // richiedo!
                        sendBatchReq(nextBatchId, "Estimation", 1, true);
                        // registro su DB nesting iniziato... QUANDO MI RISPONDE dovrò verificare che era un batch x VALIDAZIONE
                        DLMan.taBL.updateStatus(nextBatchId, (int)BatchStatus.EstimationRequested, "", 0);
                        answ = true;
                    }
                }
            }
            // resituisco
            return answ;
        }

        /// <summary>
        /// Salva su redis l'oggetto dei materiali serializzato
        /// </summary>
        /// <returns></returns>
        public static bool sendMaterials()
        {
            bool answ = false;
            // leggo tab MATERIALS da DB
            DataLayer DLMan = new DataLayer();
            var table = DLMan.taMat.GetData();
            // serializzo
            string redVal = JsonConvert.SerializeObject(table);
            // salvo
            string redKey = $"NKC:SERV:CONF:MATERIALS";
            // scrivo per ora solo su REDIS
            memLayer.ML.setRSV(redKey, redVal);
            return answ;
        }

        /// <summary>
        /// Invia una richiesta di esecuzione di CAM x un ordine offline
        /// </summary>
        /// <param name="OffOrderID">OfflineOrder di cui si chiede processing</param>
        /// <param name="note">note opzionali</param>
        /// <returns></returns>
        public static bool sendOfflineOrderReq(int OffOrderID, string note)
        {
            DataLayer DLMan = new DataLayer();
            bool answ = false;
            // per prima cosa mi serve una "nuova busta" per inviare i messaggi
            string nextEnv = getNextEnv(OffOrderID, note);
            // preparo il contenuto della busta ed invio i messaggi...
            try
            {
                int mTime = 5;
                // FIX pType a ESTIM
                int pType = 1;
                // ora ITEMS
                var tblItm = DLMan.taIL.getByOfflineOrder(OffOrderID);

                // init oggetti x fare cicli...
                Order currOrder = null;
                Kit currentKit = null;
                Part currPart = null;
                List<Order> listOrder = new List<Order>();
                List<Kit> listKit = new List<Kit>();
                List<Part> listPart = new List<Part>();

                // elenco ITEMS
                foreach (var rigaItem in tblItm)
                {
                    currPart = new Part()
                    {
                        PartId = rigaItem.ItemID,
                        PartExtCode = rigaItem.ItemExtCode,
                        PartDtmx = rigaItem.ItemDtmx,
                        PartQty = rigaItem.ItemQty,
                        MatId = rigaItem.MatID,
                        CadFilePath = rigaItem.CadFilePath
                    };
                    listPart.Add(currPart);
                }
                // compongo KIT
                currentKit = new Kit()
                {
                    KitId = OffOrderID,
                    KitExtCode = $"OOK{OffOrderID:000000000}",
                    PartList = listPart
                };
                listKit.Add(currentKit);

                // compongo ordine
                currOrder = new Order()
                {
                    OrderId = OffOrderID,
                    OrderCod = $"OFOR{OffOrderID:000000000}",
                    OrderExtCode = $"OFOR{OffOrderID:000000000}",
                    DestPlant = "00",
                    KitList = listKit
                };
                listOrder.Add(currOrder);

                // ora versione gerarchica
                batchRequest newBatchreq = new batchRequest()
                {
                    BatchId = -OffOrderID,
                    EnvNum = nextEnv,
                    MaxTime = mTime,
                    ProcType = pType,
                    MachineType = mType.Offline,
                    OrderType = oType.OfflineOrder,
                    OrderList = listOrder
                };
                // serializzo
                string redVal = JsonConvert.SerializeObject(newBatchreq);
                // salvo
                string redKey = $"{redOutPath}:{nextEnv}";
                // scrivo su REDIS
                memLayer.ML.setRSV(redKey, redVal);

                // invio notifica che c'è una busta da processare
                memLayer.ML.setRSV(currBatchReqKey, nextEnv);
                answ = true;
            }
            catch (Exception exc)
            {
                logger.lg.scriviLog($"Eccezione in sendOfflineOrderReq:{Environment.NewLine}{exc}");
            }
            // restituisco ok
            return answ;
        }

        /// <summary>
        /// Helper x serializzare l'oggetto
        /// </summary>
        /// <param name="currData"></param>
        /// <returns></returns>
        public static string serializeTakt(Takt currData)
        {
            string answ = JsonConvert.SerializeObject(currData);
            return answ;
        }

        /// <summary>
        /// Salvataggio in redis del BunkID in lavorazione sulla macchina
        /// </summary>
        public static void setCurrBatchId(string macchina, int BatchID)
        {
            memLayer.ML.setRSV(redCurrBatchId(macchina), BatchID.ToString());
        }

        /// <summary>
        /// Salvataggio in redis del BunkID in lavorazione sulla macchina
        /// </summary>
        public static void setCurrBunkId(string macchina, int BunkID)
        {
            memLayer.ML.setRSV(redCurrBunkId(macchina), BunkID.ToString());
        }

        /// <summary>
        /// Salvataggio in redis del SheetID in lavorazione sulla macchina
        /// </summary>
        public static void setCurrSheetId(string macchina, int SheetID)
        {
            memLayer.ML.setRSV(redCurrSheetId(macchina), SheetID.ToString());
        }

        /// <summary>
        /// Salva per 5 sec la richiesta di reload delle pagine MachineUnload
        /// </summary>
        public static void setReloadMU()
        {
            memLayer.ML.setRSV(redMachUnloadForce, "true", 5);
        }

        /// <summary>
        /// Salva su REDIS richiesta visualizzaizone SecondScreen x
        /// </summary>
        /// <param name="secScreenCode">cod schermo, formato SSC000000</param>
        /// <param name="filePath">Path file richiesto (pdf)</param>
        /// <param name="ttl_sec">durata in redis della richiesta</param>
        /// <returns></returns>
        public static bool setSecScreenRequest(string secScreenCode, string filePath, int ttl_sec)
        {
            bool answ = false;
            // recupero ultima call
            string redKey = $"{redSecScreenReq}:{secScreenCode}";
            answ = memLayer.ML.setRSV(redKey, filePath, ttl_sec);
            return answ;
        }

        /// <summary>
        /// Wrapper traduzione termini
        /// </summary>
        /// <param name="lemma"></param>
        /// <returns></returns>
        public static string traduci(string lemma)
        {
            return user_std.UtSn.Traduci(lemma);
        }

        /// <summary>
        /// Aggiorna la POSITION del BATCH a aprtire dallo stato DI TUTTI i bunk
        /// 0 = not started, 1 = STACKS started, 2 = STACKS done, 3 = Current, 4 = Completed
        /// </summary>
        /// <param name="BatchID"></param>
        /// <returns></returns>
        public static void updateBatchPosition(int BatchID)
        {
            DataLayer DLMan = new DataLayer();
            // chiamo stored x checkPosition
            DLMan.taBL.checkPosition(BatchID);
        }

        /// <summary>
        /// Aggiorna la POSITION del BATCH a partire dallo stato dei bunk/stack selezionato
        /// 0 = not started, 1 = STACKS started, 2 = STACKS done, 3 = Current, 4 = Completed
        /// </summary>
        /// <param name="BunkID"></param>
        /// <returns></returns>
        public static void updateBatchPositionByBunk(int BunkID)
        {
            DataLayer DLMan = new DataLayer();
            // recupero il BatchID
            DS_App.StackListDataTable tabBunk = DLMan.taSTL.getByKey(BunkID);
            if (tabBunk.Count > 0)
            {
                try
                {
                    int BatchID = tabBunk[0].BatchID;
                    // chiamo procedura di check sull'intero bunk
                    updateBatchPosition(BatchID);
                }
                catch
                { }
            }
        }

        /// <summary>
        /// Salvo dati su PartList ricevuti da Nesting (COMPETATO)
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="BinList"></param>
        public static void updateBinsFromNesting(int BatchID, List<NestBin> BinList)
        {
            DataLayer DLMan = new DataLayer();
            // inizio a processare... bunks!
            if (BinList != null)
            {
                foreach (var item in BinList)
                {
                    // creo il BIN!
                    object newBin = DLMan.taBN.insertAndReturn(item.BinIndex);
                    int BinID = 0;
                    bool valOk = int.TryParse(newBin.ToString(), out BinID);
                    // se ho un BIN in risposta...
                    if (valOk)
                    {
                        // processo sheets
                        if (item.PartList != null)
                        {
                            foreach (var part in item.PartList)
                            {
                                // creo lo sheet...
                                var newB2I = DLMan.taBNLS.insertAndReturn(BinID, part.PartId);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Salvo dati Bunks/Sheets/Parts ricevuti da Nesting (COMPETATO)
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="BunkList"></param>
        public static void updateBunksFromNesting(int BatchID, List<NestBunk> BunkList)
        {
            DataLayer DLMan = new DataLayer();
            // inizio a processare... bunks!
            if (BunkList != null)
            {
                foreach (var bunk in BunkList)
                {
                    // creo il BUNK!
                    var newBunk = DLMan.taSTL.insertAndReturn(bunk.BunkIndex, BatchID);
                    // se ho un bunk in risposta...
                    if (newBunk.Count == 1)
                    {
                        var currBunk = newBunk[0];
                        // processo sheets
                        if (bunk.SheetList != null)
                        {
                            foreach (var sheet in bunk.SheetList)
                            {
                                // creo lo sheet...
                                var newSheet = DLMan.taSHL.insertAndReturn(sheet.SheetIndex, sheet.MatId, (decimal)sheet.EstimatedWorktime, currBunk.StackID, sheet.PrintProgram, sheet.MachiningProgram, sheet.Drawing);
                                // se ho 1 sheet in risposta
                                if (newSheet.Count == 1)
                                {
                                    var currSheet = newSheet[0];
                                    // processo items!
                                    if (sheet.PartList != null)
                                    {
                                        foreach (var part in sheet.PartList)
                                        {
                                            // aggiungo part a sheet (in tab nesting)
                                            DLMan.taNest.insertAndReturn(currSheet.SheetID, part.PartId);
                                        }
                                    }
                                    // ora salvo ANCHE i remnants dello sheet... SE ci sono
                                    if (sheet.Remnant != null)
                                    {
                                        DLMan.taRem.insertAndReturn(BatchID, sheet.MatId, currSheet.SheetID, sheet.Remnant.L_mm, sheet.Remnant.W_mm, false);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Salvo dati su PartList ricevuti da Nesting (COMPETATO)
        /// </summary>
        /// <param name="BatchID"></param>
        /// <param name="CartList"></param>
        public static void updateCartsFromNesting(int BatchID, List<NestCart> CartList)
        {
            DataLayer DLMan = new DataLayer();
            // inizio a processare... bunks!
            if (CartList != null)
            {
                foreach (var item in CartList)
                {
                    // creo il CART!
                    var newCart = DLMan.taCR.insertAndReturn(item.CartIndex);
                    // se ho un CART in risposta...
                    if (newCart.Count == 1)
                    {
                        var currCart = newCart[0];
                        // processo sheets
                        if (item.KitList != null)
                        {
                            foreach (var kit in item.KitList)
                            {
                                // creo lo sheet...
                                var updKit = DLMan.taKL.updateCart(kit.KitId, currCart.CartID);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Processa elenco items e salva in redis valori x css, elenchi, ...
        /// </summary>
        /// <param name="currCss">CSS corrente</param>
        /// <param name="redKeyBase">HASH abse x area REDIS</param>
        /// <param name="varName">nome variabile x elenco items</param>
        /// <param name="itemList">Elenco items specifici</param>
        /// <param name="dataCacheTTL">TTL della cache x salvataggio valori</param>
        /// <returns></returns>
        public static string updateCssByItemList(string currCss, string redKeyBase, string varName, List<string> itemList, int dataCacheTTL)
        {
            string replaceVal = "";
            if (itemList.Count > 0)
            {
                foreach (var item in itemList)
                {
                    replaceVal += $"#{item},";
                }
                if (replaceVal.Length > 1)
                {
                    replaceVal = replaceVal.Remove(replaceVal.Length - 1);
                }
                // FIX CSS!
                currCss = currCss.Replace($"#{varName}", replaceVal);
                //serializzo e salvo
                string serVal = JsonConvert.SerializeObject(itemList);
                memLayer.ML.setRSV($"{redKeyBase}:{varName}", serVal, dataCacheTTL);
            }
            return currCss;
        }

        /// <summary>
        /// Processa elenco items e salva in redis valori x css, elenchi, ...
        /// </summary>
        /// <param name="currCss">CSS corrente</param>
        /// <param name="redKeyBase">HASH abse x area REDIS</param>
        /// <param name="varName">nome variabile x elenco items</param>
        /// <param name="dataCacheTTL">TTL della cache x salvataggio valori</param>
        /// <returns></returns>
        public static string updateCssByPickedItems(string currCss, string redKeyBase, string varName, int dataCacheTTL)
        {
            string replaceVal = "";
            string rawData = memLayer.ML.getRSV($"{redKeyBase}:ItemsSel");
            if (!string.IsNullOrEmpty(rawData))
            {
                try
                {
                    Dictionary<string, string> dictData = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
                    // ciclo x cercare x ogni chiave (sessione utente)
                    foreach (var item in dictData)
                    {
                        replaceVal += $"#{item.Value},";
                    }
                    if (replaceVal.Length > 1)
                    {
                        replaceVal = replaceVal.Remove(replaceVal.Length - 1);
                    }
                    // FIX CSS!
                    currCss = currCss.Replace("#ItemsSel", replaceVal);
                }
                catch
                { }
            }
            return currCss;
        }

        /// <summary>
        /// Salvo dati di OrderId ricevuti da Nesting (stima)
        /// </summary>
        /// <param name="PartList"></param>
        public static void updateExtEstimFromNesting(int BatchId, List<OrderPrev> OrdExtList)
        {
            DataLayer DLMan = new DataLayer();
            // svuoto x batch...
            DLMan.taOLT.deleteByBatch(BatchId);
            // salvo elenco materiali x ogni item...
            foreach (var currItem in OrdExtList)
            {
                // tempo in sec --> divido 60
                DLMan.taOLT.insert(BatchId, BatchId, currItem.OrderId, currItem.OrderExtCode, currItem.NumBin, currItem.NumCart, currItem.NumPart, (currItem.TotTime / 60));
            }
        }

        /// <summary>
        /// Salvo dati su PartList ricevuti da Nesting (stima)
        /// </summary>
        /// <param name="PartList"></param>
        public static void updatePartsFromNesting(List<Part> PartList)
        {
            DataLayer DLMan = new DataLayer();
            string PostProcList = "";
            string ProcessesReq = "";
            string pdfFilePath = "";
            // salvo elenco materiali x ogni item...
            foreach (Part currItem in PartList)
            {
                // calcolo parametri...
                PostProcList = getPostProcList(currItem.OptParameters);
                ProcessesReq = getProcessesReq(currItem.OptParameters);
                pdfFilePath = getPdfFilePath(currItem.OptParameters);
                DLMan.taIL.updateFromNesting(currItem.PartId, currItem.MatId, PostProcList, ProcessesReq, pdfFilePath);
            }
        }

        /// <summary>
        /// Fornisce un dictionary di valori ATTUALI per una specifica macchina
        /// </summary>
        /// <param name="machineCod"></param>
        /// <returns></returns>
        public Dictionary<string, string> getCurrMachineData(string machineCod)
        {
            Dictionary<string, string> answ = new Dictionary<string, string>();
            // cerco in REDIS
            string rawData = memLayer.ML.getRSV(machineArea(machineCod));
            if (!string.IsNullOrEmpty(rawData))
            {
                answ = JsonConvert.DeserializeObject<Dictionary<string, string>>(rawData);
            }
            else
            {
                // recupero batch e sheet...
                int batchId = getNextBatch(machineCod);
                // se non trovo creo un oggetto NUOVO e vuoto x ogni oggetto... e salvo...
                answ.Add("BatchID", batchId.ToString());
                answ.Add("SheetID_load", getSheetIdByBatch(batchId, 0, 1).ToString());
                answ.Add("SheetID_print", getSheetIdByBatch(batchId, 1, 2).ToString());
                answ.Add("SheetID_work", getSheetIdByBatch(batchId, 2, 3).ToString());
                answ.Add("SheetID_unload", getSheetIdByBatch(batchId, 3, 5).ToString());
                // serializzo e salvo!
                saveMachineData(machineCod, answ);
            }
            // restituisco!
            return answ;
        }

        /// <summary>
        /// Recupero risposta stima salvata
        /// </summary>
        /// <param name="BatchID"></param>
        /// <returns></returns>
        public nestReplyBatchInitial getEstAnsw(int BatchID)
        {
            nestReplyBatchInitial answ = null;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchInitial>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", BatchID);
                var collRawData = database.GetCollection<nestReplyBatchInitial>("EstimationArchive");
                // recupero
                answ = collRawData.Find(filter).Project<nestReplyBatchInitial>("{_id: 0}").FirstOrDefault<nestReplyBatchInitial>();
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Recupero risposta stima ESTESA salvata
        /// </summary>
        /// <param name="BatchID"></param>
        /// <returns></returns>
        public nestReplyBatchExtEst getExtEstAnsw(int BatchID)
        {
            nestReplyBatchExtEst answ = null;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchExtEst>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", BatchID);
                var collRawData = database.GetCollection<nestReplyBatchExtEst>("ExtendedEstimationArchive");
                // recupero
                answ = collRawData.Find(filter).Project<nestReplyBatchExtEst>("{_id: 0}").FirstOrDefault<nestReplyBatchExtEst>();
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Recupero risposta nesting salvata
        /// </summary>
        /// <param name="BatchID"></param>
        /// <returns></returns>
        public nestReplyBatchFinal getNestAnsw(int BatchID)
        {
            nestReplyBatchFinal answ = null;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchFinal>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", BatchID);
                FindOptions opz = new FindOptions { ShowRecordId = false };
                var collRawData = database.GetCollection<nestReplyBatchFinal>("NestingArchive");
                // recupero
                answ = collRawData.Find(filter).Project<nestReplyBatchFinal>("{_id: 0}").FirstOrDefault<nestReplyBatchFinal>();
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Fornisce il prossimo TAKT da elaborare oppure null se non ce ne fossero altri da elaborare per la data CORRENTE
        /// </summary>
        /// <returns></returns>
        public Takt prodGetNextTakt()
        {
            return null;
        }

        /// <summary>
        /// Svuota elenco pezzi nella postazione di unload
        /// </summary>
        /// <param name="SheetID"></param>
        /// <returns></returns>
        public bool resetSheetUnload(int SheetID = 0)
        {
            bool answ = false;
            try
            {
                memLayer.ML.redFlushKey(machineUnloadArea(SheetID));
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta x STIMA
        /// </summary>
        /// <param name="nestAnsw">Stringa della risposta JSON ricevuta dal nesting</param>
        /// <returns></returns>
        public bool saveEstAnsw(nestReplyBatchInitial nestAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchInitial>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", nestAnsw.BatchID);
                var collRawData = database.GetCollection<nestReplyBatchInitial>("EstimationArchive");
                // elimino old
                collRawData.DeleteMany(filter);
                // aggiungo
                collRawData.InsertOne(nestAnsw);

                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta x STIMA ESTESA
        /// </summary>
        /// <param name="nestAnsw">Stringa della risposta JSON ricevuta dal nesting</param>
        /// <returns></returns>
        public bool saveExtEstAnsw(nestReplyBatchExtEst nestAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchExtEst>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", nestAnsw.BatchID);
                var collRawData = database.GetCollection<nestReplyBatchExtEst>("ExtendedEstimationArchive");
                // elimino old
                collRawData.DeleteMany(filter);
                // aggiungo
                collRawData.InsertOne(nestAnsw);

                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salvataggio array dati di una macchina
        /// </summary>
        /// <param name="machineCod"></param>
        /// <param name="paramsArray"></param>
        /// <returns></returns>
        public bool saveMachineData(string machineCod, Dictionary<string, string> paramsArray)
        {
            bool answ = false;
            try
            {
                // serializzo e salvo!
                string rawData = JsonConvert.SerializeObject(paramsArray);
                memLayer.ML.setRSV(machineArea(machineCod), rawData);
                answ = true;
            }
            catch
            { }
            // restituisco!
            return answ;
        }

        /// <summary>
        /// Fornisce un dictionary di valori ATTUALI per una specifica macchina
        /// </summary>
        /// <param name="machineCod"></param>
        /// <param name="Key"></param>
        /// <param name="Val"></param>
        /// <returns></returns>
        public bool saveMachineParam(string machineCod, string Key, string Val)
        {
            bool answ = false;
            // recupero i dati
            try
            {
                var currData = getCurrMachineData(machineCod);
                // aggiorno il record
                if (currData.ContainsKey(Key))
                {
                    currData[Key] = Val;
                }
                // oppure aggiungo...
                else
                {
                    currData.Add(Key, Val);
                }
                // salvo...
                saveMachineData(machineCod, currData);
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta x NESTING
        /// </summary>
        /// <param name="nestAnsw">Stringa della risposta JSON ricevuta dal nesting</param>
        /// <returns></returns>
        public bool saveNestAnsw(nestReplyBatchFinal nestAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyBatchFinal>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", nestAnsw.BatchID);
                var collRawData = database.GetCollection<nestReplyBatchFinal>("NestingArchive");
                // elimino old
                collRawData.DeleteMany(filter);
                // aggiungo
                collRawData.InsertOne(nestAnsw);
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta x OfflineOrder
        /// </summary>
        /// <param name="offOrdAnsw">Stringa della risposta JSON ricevuta dal nesting</param>
        /// <returns></returns>
        public bool saveOffOrdAnsw(nestReplyOffOrd offOrdAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyOffOrd>.Filter;
                var filter = filtBuilder.Eq<int>("BatchID", offOrdAnsw.BatchID);
                var collRawData = database.GetCollection<nestReplyOffOrd>("OfflineArchive");
                // elimino old
                collRawData.DeleteMany(filter);
                // aggiungo
                collRawData.InsertOne(offOrdAnsw);

                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta dal PROD
        /// </summary>
        /// <param name="prodAnsw">Stringa della risposta JSON ricevuta dal PROD</param>
        /// <returns></returns>
        public bool saveProdAnsw(SheetWorkList prodAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<SheetWorkList>.Filter;
                // filtro vuoto x svuotare sempre
                var filter = filtBuilder.Empty;
                var collRawData = database.GetCollection<SheetWorkList>("ProdArchive");
                // elimino old (TUTTI!!!)
                collRawData.DeleteMany(filter);
                // aggiungo
                collRawData.InsertOne(prodAnsw);
                // fatto!
                answ = true;
            }
            catch
            { }
            return answ;
        }

        /// <summary>
        /// Salva una risposta ricevuta x OfflineOrder
        /// </summary>
        /// <param name="offOrdAnsw">Stringa della risposta JSON ricevuta dal nesting</param>
        /// <returns></returns>
        public bool saveProdStat(MachineStatData offOrdAnsw)
        {
            bool answ = false;
            try
            {
                // definisco filtro
                var filtBuilder = Builders<nestReplyOffOrd>.Filter;
#if false
                //var filter = filtBuilder.Eq<int>("BatchID", offOrdAnsw.BatchID);
#endif
                var collRawData = database.GetCollection<MachineStatData>("ProdStat");
                // elimino old
#if false
                //collRawData.DeleteMany(filter);
#endif
                // aggiungo
                collRawData.InsertOne(offOrdAnsw);

                answ = true;
            }
            catch
            { }
            return answ;
        }

        #endregion Public Methods

        #region Public Classes

        public class Parte
        {
            #region Public Properties

            public string CadFilePath { get; set; }
            public string DataMatrix { get; set; }
            public string Description { get; set; }
            public string ExtCode { get; set; }
            public int MatId { get; set; }
            public int PartId { get; set; }
            public string PostProc { get; set; }
            public string ProcessReq { get; set; }
            public int Qty { get; set; }

            #endregion Public Properties
        }

        /// <summary>
        /// Oggetto globale TAKT
        /// </summary>
        public class Takt
        {
            #region Public Properties

            /// <summary>
            /// Numero di Stack da lavorare
            /// </summary>
            public int NumStack
            {
                get
                {
                    int answ = 0;
                    try
                    {
                        answ = StackList.Count;
                    }
                    catch
                    { }
                    return answ;
                }
            }

            /// <summary>
            /// Elenco degli Stack da lavorare
            /// </summary>
            public List<ProdBunk> StackList { get; set; }

            /// <summary>
            /// Stato del TAKT
            /// </summary>
            public CStatus Status { get; set; }

            /// <summary>
            /// Codice univoco oggetto TAKT (data.num)
            /// </summary>
            public string TaktId { get; set; }

            #endregion Public Properties
        }

        #endregion Public Classes
    }
}