From 897ef574cae4c6550c6ca5f57f28817d241a0c14 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Tue, 14 Feb 2023 17:16:51 +0100 Subject: [PATCH] Aggiunta progetto MP-IOC - MP-IO versione dotNet Core - inserito metodo x gestione recupero ricetta - da verificare condivisione file conf ricetta (in SPEC e in IOC...) --- MP-IOC.sln | 31 + MP.IOC/Controllers/RecipeController.cs | 73 ++ MP.IOC/Data/MpDataService.cs | 1626 ++++++++++++++++++++++++ MP.IOC/MP.IOC.csproj | 19 + MP.IOC/Program.cs | 38 + MP.IOC/Properties/launchSettings.json | 31 + MP.IOC/Recipe/Fimat/RecipeConf.json | 80 ++ MP.IOC/Recipe/Fimat/TemplateOutput.tpl | 14 + MP.IOC/Recipe/Fimat/_RefRecipe.json | 60 + MP.IOC/Recipe/README.md | 134 ++ MP.IOC/Recipe/README.pdf | Bin 0 -> 71330 bytes MP.IOC/appsettings.Development.json | 8 + MP.IOC/appsettings.json | 16 + MP.SPEC/MP.SPEC.csproj | 2 +- MP.SPEC/Resources/ChangeLog.html | 2 +- MP.SPEC/Resources/VersNum.txt | 2 +- MP.SPEC/Resources/manifest.xml | 2 +- 17 files changed, 2134 insertions(+), 4 deletions(-) create mode 100644 MP-IOC.sln create mode 100644 MP.IOC/Controllers/RecipeController.cs create mode 100644 MP.IOC/Data/MpDataService.cs create mode 100644 MP.IOC/MP.IOC.csproj create mode 100644 MP.IOC/Program.cs create mode 100644 MP.IOC/Properties/launchSettings.json create mode 100644 MP.IOC/Recipe/Fimat/RecipeConf.json create mode 100644 MP.IOC/Recipe/Fimat/TemplateOutput.tpl create mode 100644 MP.IOC/Recipe/Fimat/_RefRecipe.json create mode 100644 MP.IOC/Recipe/README.md create mode 100644 MP.IOC/Recipe/README.pdf create mode 100644 MP.IOC/appsettings.Development.json create mode 100644 MP.IOC/appsettings.json diff --git a/MP-IOC.sln b/MP-IOC.sln new file mode 100644 index 00000000..a64b294c --- /dev/null +++ b/MP-IOC.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33205.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MP.Data", "MP.Data\MP.Data.csproj", "{A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MP.IOC", "MP.IOC\MP.IOC.csproj", "{B9F508BF-8503-4C25-B9BA-0FAC411C44C5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C7A1E7-6E5F-41BA-8ED0-C4A6C581C1B3}.Release|Any CPU.Build.0 = Release|Any CPU + {B9F508BF-8503-4C25-B9BA-0FAC411C44C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9F508BF-8503-4C25-B9BA-0FAC411C44C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9F508BF-8503-4C25-B9BA-0FAC411C44C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9F508BF-8503-4C25-B9BA-0FAC411C44C5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8030DF52-992F-46A3-A9F1-5FF64A9D5D9D} + EndGlobalSection +EndGlobal diff --git a/MP.IOC/Controllers/RecipeController.cs b/MP.IOC/Controllers/RecipeController.cs new file mode 100644 index 00000000..882930a7 --- /dev/null +++ b/MP.IOC/Controllers/RecipeController.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Mvc; +using MP.IOC.Data; +using Newtonsoft.Json; +using NLog; +using System.Xml; + +namespace MP.IOC.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class RecipeController : ControllerBase + { + #region Public Constructors + + public RecipeController(IConfiguration configuration, MpDataService DataService) + { + Log.Info("Starting MpDataService INIT"); + _configuration = configuration; + DService = DataService; + Log.Info("Avviata classe Recipe"); + } + + #endregion Public Constructors + + #region Public Methods + + [HttpGet("GetRecipe")] + public async Task GetRecipe(int idxPODL) + { + string answ = ""; + var reqRecipe = await DService.RecipeGetByPODL(idxPODL); + if (reqRecipe != null) + { + answ = DService.CalcRecipe(reqRecipe); + } + return answ; + } + + [HttpGet("GetRecipeXML")] + public async Task GetRecipeXML(int idxPODL) + { + // aggiungo root node? + string answ = ""; + // recupero versione json + string rawData = await GetRecipe(idxPODL); + if (!string.IsNullOrEmpty(rawData)) + { + XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(rawData); + answ += doc.InnerXml; + } + return answ; + } + + #endregion Public Methods + + #region Protected Properties + + /// + /// Dataservice x accesso DB + /// + protected MpDataService DService { get; set; } + + #endregion Protected Properties + + #region Private Fields + + private static IConfiguration _configuration = null!; + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/MP.IOC/Data/MpDataService.cs b/MP.IOC/Data/MpDataService.cs new file mode 100644 index 00000000..9581d271 --- /dev/null +++ b/MP.IOC/Data/MpDataService.cs @@ -0,0 +1,1626 @@ +using MP.Data; +using MP.Data.Conf; +using MP.Data.DatabaseModels; +using MP.Data.DTO; +using MP.Data.MgModels; +using Newtonsoft.Json; +using NLog; +using StackExchange.Redis; +using System.Diagnostics; + +namespace MP.IOC.Data +{ + public class MpDataService : IDisposable + { + #region Public Constructors + + public MpDataService(IConfiguration configuration, ILogger logger) + { + _logger = logger; + _logger.LogInformation("Starting MpDataService INIT"); + _configuration = configuration; + + // setup compoenti REDIS + redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); + redisConnAdmin = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("RedisAdmin")); + redisDb = redisConn.GetDatabase(); + BroadastMsgPipe = new MessagePipe(redisConn, Constants.BROADCAST_M_PIPE); + // leggo cache lungo periodo + int.TryParse(_configuration.GetValue("ServerConf:redisLongTimeCache"), out redisLongTimeCache); + + _logger.LogInformation("Redis INIT"); + + // conf DB + string connStr = _configuration.GetConnectionString("Mp.Data"); + if (string.IsNullOrEmpty(connStr)) + { + _logger.LogError("DbController: ConnString empty!"); + } + else + { + dbController = new MP.Data.Controllers.MpSpecController(configuration); + _logger.LogInformation("DbController OK"); + } + + // conf mongo... + connStr = _configuration.GetConnectionString("MongoConnect"); + if (string.IsNullOrEmpty(connStr)) + { + _logger.LogError("MongoController: ConnString empty!"); + } + else + { + mongoController = new MP.Data.Controllers.MpMongoController(configuration); + _logger.LogInformation("MongoController OK"); + } + } + + #endregion Public Constructors + + #region Public Properties + + public static MP.Data.Controllers.MpSpecController dbController { get; set; } = null!; + public static MP.Data.Controllers.MpMongoController mongoController { get; set; } = null!; + + public MessagePipe BroadastMsgPipe { get; set; } = null!; + + /// + /// Dizionario dei tag configurati per IOB + /// + public Dictionary> currTagConf { get; set; } = new Dictionary>(); + + #endregion Public Properties + + /// + /// Init ricetta + /// + /// + /// + /// + /// + public RecipeModel InitRecipe(string confPath, int idxPODL, Dictionary CalcArgs) + { + return mongoController.InitRecipe(confPath, idxPODL, CalcArgs); + } + + /// + /// Salva ricetta su MongoDB + /// + /// + /// + public async Task RecipeSetByPODL(RecipeModel currRecord) + { + bool answ = false; + answ = await mongoController.RecipeSetByPODL(currRecord); + return answ; + } + /// + /// Ricerca ricetta su MongoDB dato PODL + /// + /// + /// + public async Task RecipeGetByPODL(int idxPODL) + { + RecipeModel? result = null; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "MongoDB"; + result = await mongoController.RecipeGetByPODL(idxPODL); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"RecipeGetByPODL | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + public string CalcRecipe(RecipeModel currRecipe) + { + return mongoController.CalcRecipe(currRecipe); + } + + #region Public Methods + + /// + /// Recupera eventuali azioni richieste + /// + /// + public async Task ActionGetReq() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + DisplayAction? result = null; + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(redisActionReq); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ActionGetReq Read from REDIS: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new DisplayAction(); + } + return result; + } + + /// + /// Salva richiesta azione + /// + /// + /// + public bool ActionSetReq(DisplayAction? act2save) + { + bool fatto = false; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + // cerco in redis... + string rawData = JsonConvert.SerializeObject(act2save); + // invio broadcast + salvo in redis + BroadastMsgPipe.saveAndSendMessage(redisActionReq, rawData); + //await redisDb.StringSetAsync(redisActionReq, rawData); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ActionSetReq REDIS send to broadcast + Write cache: {ts.TotalMilliseconds}ms"); + return fatto; + } + + public async Task> AnagStatiComm() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(redisStatoCom); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagStatiComm Read from REDIS: {ts.TotalMilliseconds}ms"); + } + else + { + result = await Task.FromResult(dbController.AnagStatiComm()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(redisStatoCom, rawData, getRandTOut(redisLongTimeCache)); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagStatiComm Read from DB: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new List(); + } + return result; + } + + public async Task> AnagTipoArtLV() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string source = "DB"; + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(redisTipoArt); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + source = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.AnagTipoArtLV()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(redisTipoArt, rawData, getRandTOut(redisLongTimeCache)); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagTipoArtLV Read from {source}: {ts.TotalMilliseconds}ms"); + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Elenco Codice articolo con dati dossier gestiti + /// + /// + public async Task> ArticleWithDossier() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = redisArtByDossier; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ArticleWithDossier()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ArticleWithDossier | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Eliminazione record selezionato + /// + /// + /// + public async Task ArticoliDeleteRecord(AnagArticoli currRec) + { + bool fatto = await dbController.ArticoliDeleteRecord(currRec); + await resetCacheArticoli(); + return fatto; + } + + /// + /// Restitusice elenco articoli cercati + /// + /// + /// + /// + public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string sKey = string.IsNullOrEmpty(searchVal) ? "***" : searchVal; + string currKey = $"{redisArtList}:{azienda}:{sKey}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ArticoliGetSearch(numRecord, azienda, searchVal)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ArticoliGetSearch | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Aggiornamento record selezionato + /// + /// + /// + public async Task ArticoliUpdateRecord(AnagArticoli currRec) + { + bool fatto = await dbController.ArticoliUpdateRecord(currRec); + await resetCacheArticoli(); + return fatto; + } + + /// + /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su + /// tab veto da DB + /// + /// + /// + public bool ArticoloDelEnabled(object CodArt) + { + bool answ = false; + string codArticolo = $"{CodArt}"; + int cacheCheckArtUsato = 1; + int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato); + TimeSpan TTLCache = getRandTOut(cacheCheckArtUsato); + // cerco in cache redis... + string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}"; + string redKeyTabCheckArt = Utils.redKeyTabCheckArt; + string rawData = redisDb.StringGet(redKeyArtUsed); + if (!string.IsNullOrEmpty(rawData)) + { + bool.TryParse(rawData, out answ); + } + else + { + // controllo non sia stato mai prodotto sennò non posso cancellare... + try + { + // cerco in cache se ci sia la tabella con gli articoli impiegati... + string rawTable = redisDb.StringGet(redKeyTabCheckArt); + List? artList = new List(); + if (!string.IsNullOrEmpty(rawTable)) + { + artList = JsonConvert.DeserializeObject>(rawTable); + } + // rileggo... + if (artList == null || artList.Count == 0) + { + artList = new List(); + var tabArticoli = dbController.ArticoliGetUsed(); + var codList = tabArticoli.Select(x => x.CodArticolo); + foreach (string cod in codList) + { + artList.Add(cod); + } + // SE fosse vuoto aggiungo comunque il cado "ND"... + if (artList.Count == 0) + { + artList.Add("ND"); + } + // salvo + rawTable = JsonConvert.SerializeObject(artList); + redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache); + } + // cerco nella tabella: se ci fosse --> disabilitato delete + bool usato = false; + if (artList != null && artList.Count > 0) + { + usato = artList.Contains(codArticolo); + } + answ = !usato; + redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache); + } + catch + { } + } + return answ; + } + + public async Task> ConfigGetAll() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(redisConfKey); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ConfigGetAll Read from REDIS: {ts.TotalMilliseconds}ms"); + } + else + { + result = await Task.FromResult(dbController.ConfigGetAll()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(redisConfKey, rawData, getRandTOut(redisLongTimeCache)); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ConfigGetAll Read from DB: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Reset dati cache config + /// + /// + public async Task ConfigResetCache() + { + await redisDb.StringSetAsync(redisConfKey, ""); + } + + /// + /// Update chiave config + /// + /// + public async Task ConfigUpdate(ConfigModel updRec) + { + return await Task.FromResult(dbController.ConfigUpdate(updRec)); + } + + /// + /// Dispose del connettore ai dati + /// + public void Dispose() + { + // Clear database controller + dbController.Dispose(); + mongoController.Dispose(); + redisConn.Dispose(); + } + + /// + /// Eliminazione di un dossier + /// + /// record dossier da eliminare + /// + public async Task DossiersDeleteRecord(DossierModel selRecord) + { + bool result = false; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + result = await dbController.DossiersDeleteRecord(selRecord); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{redisDossByMac}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"DossiersDeleteRecord | IdxMacchina {selRecord.IdxMacchina} | DtRif {selRecord.DtRif} | IdxODL {selRecord.IdxODL} | {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco ultimi n record DOssiers (che contengono ad esempio "salvataggi" di FLuxLog) dato + /// macchina (ordinato x data registrazione) + /// + /// * = tutte, altrimenti solo x una data macchina + /// Data minima per estrazione records + /// Data Massima per estrazione records + /// + public async Task> DossiersGetLastFilt(string IdxMacchina, string CodArticolo, DateTime DtStart, DateTime DtEnd) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisDossByMac}:{IdxMacchina}:{CodArticolo}:{DtStart:yyyyMMddHHmm}:{DtEnd:yyyyMMddHHmm}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.DossiersGetLastFilt(IdxMacchina, CodArticolo, DtStart, DtEnd)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"DossiersGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Inserimento nuovo record dossier + /// + /// + /// + public async Task DossiersInsert(DossierModel currDoss) + { + // aggiorno record sul DB + bool answ = await dbController.DossiersInsert(currDoss); + + return answ; + } + + /// + /// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redis + /// + /// macchina + /// NUm massimo secondi per recuperare dati correnti + /// DataOra riferimento x cui prendere valori antecedenti + /// + public async Task DossiersTakeParamsSnapshotLast(string IdxMacchina, DateTime dtMin, DateTime dtMax) + { + bool answ = false; + await Task.Delay(1); + Log.Info($"Richiesta snapshot per macchina {IdxMacchina} | periodo {dtMin} --> {dtMax}"); + // chiamo stored x salvare parametri + dbController.DossiersTakeParamsSnapshotLast(IdxMacchina, dtMin, dtMax); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{redisDossByMac}:*"); + answ = await ExecFlushRedisPattern(pattern); + Log.Info($"Svuotata cache dossier | {pattern}"); + return answ; + } + + /// + /// Update valore dossier + /// + /// + /// + public async Task DossiersUpdateValore(DossierModel currDoss) + { + // aggiorno record sul DB + bool answ = await dbController.DossiersUpdateValore(currDoss); + + return answ; + } + + /// + /// Restitusice elenco aziende + /// + /// + public Task> ElencoAziende() + { + return Task.FromResult(dbController.AnagGruppiAziende()); + } + + /// + /// Restitusice elenco fasi + /// + /// + public Task> ElencoGruppiFase() + { + List result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisAnagGruppi}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject>($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + } + readType = "REDIS"; + } + else + { + result = dbController.AnagGruppiFase(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ElencoGruppiFase | Read from {readType}: {ts.TotalMilliseconds}ms"); + return Task.FromResult(result); + } + + public Task> ElencoLink() + { + return Task.FromResult(dbController.ElencoLink()); + } + + /// + /// Aggiunta record EventList + /// + /// + /// + public async Task EvListInsert(EventListModel newRec) + { + return await dbController.EvListInsert(newRec); + } + + /// + /// Imposta in redis la scadenza della pagina x il reload + /// + /// + /// + public DateTime ExpiryReloadParamGet() + { + DateTime dtRif = DateTime.Now; + string currKey = $"{redisParamPageExp}"; + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + dtRif = JsonConvert.DeserializeObject($"{rawData}"); + } + return dtRif; + } + + /// + /// Imposta in redis la scadenza della pagina x il reload + /// + /// + /// + public bool ExpiryReloadParamSet(DateTime expTime) + { + bool fatto = false; + string currKey = $"{redisParamPageExp}"; + string rawData = JsonConvert.SerializeObject(expTime); + fatto = redisDb.StringSet(currKey, rawData); + return fatto; + } + + public async Task FlushRedisCache() + { + await Task.Delay(1); + RedisValue pattern = new RedisValue($"{redisBaseAddrSpec}*"); + bool answ = await ExecFlushRedisPattern(pattern); + // rileggo vocabolario.,.. + ObjVocabolario = VocabolarioGetAll(); + return answ; + } + + /// + /// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione) + /// + /// Data massima x eventi + /// Data minima x eventi + /// * = tutte, altrimenti solo x una data macchina + /// *=tutti, altrimenti solo selezionato + /// numero massimo record da restituire + /// + public async Task> FluxLogGetLastFilt(DateTime DtMax, DateTime DtMin, string IdxMacchina, string CodFlux, int MaxRec, double redisCacheSec) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisFluxLogFilt}:{IdxMacchina}:{CodFlux}:{MaxRec}:{DtMax:yyyyMMddHHmm}:{DtMin:yyyyMMddHHmm}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.FluxLogGetLastFilt(DtMax, DtMin, IdxMacchina, CodFlux, MaxRec)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + if (string.IsNullOrEmpty(canCacheParametri)) + { + canCacheParametri = await tryGetConfig("SPEC_ParametriEnableRedisCache"); + } + if (canCacheParametri != "false") + { + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisCacheSec)); + } + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"FluxLogGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco setup dei tag conf correnti + /// + /// + public Task>> getAllTags() + { + return Task.FromResult(currTagConf); + } + + public List getFluxLog(string Valore) + { + List answ = new List(); + DossierFluxLogDTO? result = JsonConvert.DeserializeObject(Valore); + if (result != null) + { + if (result.ODL != null) + { + answ = result + .ODL + .OrderBy(x => x.CodFlux) + .ToList(); + // inizializzo SE necessario + foreach (var item in answ) + { + item.ValoreEdit = String.IsNullOrEmpty(item.ValoreEdit) ? item.Valore : item.ValoreEdit; + } + } + } + return answ; + } + + /// + /// restituisce il valore da REDIS associato al tag richeisto + /// + /// Chiave in cui cercare il valore + /// + public string getTagConf(string redKey) + { + string outVal = ""; + // cerco in REDIS la conf x l'IOB + var rawData = redisDb.StringGet(redKey); + if (!string.IsNullOrEmpty(rawData)) + { + outVal = $"{rawData}"; + } + return outVal; + } + + /// + /// + /// id odl da cercare + /// + public async Task> ListGiacenze(int IdxOdl) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisGiacenzaList}:{IdxOdl}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ListGiacenze(IdxOdl)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ListGiacenze | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// elenco TUTTI gli ODL + /// + /// + /// + public List ListOdlAll() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; +#if false + string currKey = $"{redisGiacenzaList}:{IdxOdl}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } +#endif + result = dbController.ListOdlAll(); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ListOdlAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco ODL filtrati x stato, articolo, KeyRich (che contiene stato) + /// + /// Stato ODL: true=in corso/completato + /// Cod articolo + /// KeyRich (parziale) da cercare (es cod stato x yacht) + /// Reparto selezionato + /// Macchina selezionata + /// Data inizio + /// Data fine + /// + public async Task> ListODLFilt(bool inCorso, string codArt, string keyRichPart, string Reparto, string IdxMacchina, DateTime startDate, DateTime endDate) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisOdlList}:{inCorso}:{codArt}:{keyRichPart}:{Reparto}:{IdxMacchina}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ListODLFilt(inCorso, codArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ListODLFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + + //return await Task.FromResult(dbController.ListODLFilt(inCorso, codArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate)); + } + + /// + /// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) + /// + /// Solo lanciati (1) o ancora disponibili (0) + /// KeyRich (parziale) da cercare (es cod stato x yacht) + /// Macchina + /// Gruppo + /// Data inizio + /// Data fine + /// + public async Task> ListPODLFilt(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisPOdlList}:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ListPODLFilt(lanciato, keyRichPart, idxMacchina, codGruppo, startDate, endDate)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ListPODLFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco di tutte le macchine gestite + /// + /// + /// + public async Task> MacchineGetFilt(string codGruppo) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string keyGrp = codGruppo != "*" ? codGruppo : "ALL"; + string currKey = $"{redisMacList}:{keyGrp}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.MacchineGetFilt(codGruppo)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Verifica se la macchina abbia un codice ricetta associato + /// + /// + /// + public async Task MacchineRecipe(string idxMacchina) + { + string? result = ""; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisMacRecipe}:{idxMacchina}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + readType = "REDIS"; + } + else + { + //recupero elenco macchine... + var machineList = await MacchineGetFilt("*"); + var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); + result = currMach != null ? currMach.RecipePath : null; + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineRecipe | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result ?? ""; + } + + /// + /// Elenco id Macchine che abbiano dati FLuxLog, nel periodo indicato + /// + /// + /// + /// + public async Task> MacchineWithFlux(DateTime dtStart, DateTime dtEnd) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisMacByFlux}:{dtStart:yyyyMMddHHmm}:{dtEnd:yyyyMMddHHmm}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await dbController.MacchineWithFlux(dtStart, dtEnd); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineWithFlux | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco ODL dato batch selezionato + /// + /// Batch richiesto + /// + public async Task> OdlByBatch(string BatchSel) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = redisOdlByBatch; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.OdlByBatch(BatchSel)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlByBatch | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// ODL da chiave + /// + /// + /// + public ODLExpModel OdlByKey(int IdxOdl) + { + ODLExpModel? result = new ODLExpModel(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; +#if false + string currKey = $"{redisGiacenzaList}:{IdxOdl}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } +#endif + result = dbController.OdlByKey(IdxOdl); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Effettua chiusura dell'ODL indicato, andand + /// + /// idx odl da chiudere + /// idx macchina + /// matricola operatore + /// indica se confermare i pezzi priam di chiudere ODL + public async Task ODLClose(int idxOdl, string idxMacchina, int matrOpr, bool confPezzi) + { + bool fatto = false; + await Task.Delay(1); + // recupero dati x conf modalità conferma + var configData = await ConfigGetAll(); + if (configData != null) + { + bool confRett = false; + var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); + if (currRec != null) + { + bool.TryParse(currRec.Valore, out confRett); + } + int modoConfProd = 0; + currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); + if (currRec != null) + { + int.TryParse(currRec.Valore, out modoConfProd); + } + // chiamo metodo conferma! + fatto = await dbController.ODLClose(idxOdl, idxMacchina, matrOpr, confPezzi, confRett, modoConfProd); + } + + return fatto; + } + + /// + /// Record ODL da chaive + /// + /// + public async Task OdlGetByKey(int IdxOdl) + { + await Task.Delay(1); + var dbResult = dbController.OdlGetByKey(IdxOdl); + return dbResult; + } + + /// + /// ODL correnti (tutti) + /// + /// + /// + public List OdlGetCurrent() + { + List? dbResult = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisOdlCurrByMac}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + try + { + dbResult = JsonConvert.DeserializeObject>($"{rawData}"); + } + catch + { } + readType = "REDIS"; + } + else + { + dbResult = dbController.OdlGetCurrent().Select(x => x.IdxMacchina).Distinct().ToList(); + rawData = JsonConvert.SerializeObject(dbResult); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(3)); + } + if (dbResult == null) + { + dbResult = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlGetCurrent | Read from {readType}: {ts.TotalMilliseconds}ms"); + + return dbResult; + } + + /// + /// Elenco di tutti i parametri filtrati x macchina + /// + /// * = tutte, altrimenti solo x una data macchina + /// + public async Task> ParametriGetFilt(string IdxMacchina) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisFluxByMac}:{IdxMacchina}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(dbController.ParametriGetFilt(IdxMacchina)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ParametriGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Recupero PODL da chiave + /// + /// + /// + public async Task PODL_getByKey(int idxPODL) + { + PODLModel result = new PODLModel(); + if (idxPODL != 0) + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisPOdlByPOdl}:{idxPODL}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + readType = "REDIS"; + } + } + else + { + result = await dbController.PODL_getByKey(idxPODL); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new PODLModel(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"PODL_getByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); + } + else + { + Log.Debug("Errore IdxPODL = 0"); + } + return result; + } + + /// + /// Recupero PODL da IdxODL + /// + /// + /// + public PODLModel PODL_getByOdl(int idxODL) + { + PODLModel result = new PODLModel(); + if (idxODL != 0) + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{redisPOdlByOdl}:{idxODL}"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + } + readType = "REDIS"; + } + else + { + result = dbController.PODL_getByOdl(idxODL); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new PODLModel(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"PODL_getByOdl | Read from {readType}: {ts.TotalMilliseconds}ms"); + } + else + { + Log.Debug("Errore IdxODL = 0"); + } + return result; + } + + /// + /// Eliminazione record selezionato + /// + /// + /// + public async Task PODLDeleteRecord(PODLExpModel currRec) + { + var dbResult = await dbController.PODLDeleteRecord(currRec); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{redisXdlData}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + await Task.Delay(1); + return dbResult; + } + + /// + /// Avvio fase setup per il record selezionato + /// + /// + /// + public async Task POdlDoSetup(PODLExpModel currRec) + { + var dbResult = await dbController.PODL_startSetup(currRec, 0, 1, 1, ""); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{redisXdlData}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + await Task.Delay(1); + return dbResult; + } + + /// + /// Aggiornamento record selezionato + /// + /// + /// + public async Task POdlUpdateRecord(PODLModel currRec) + { + var dbResult = await dbController.PODLUpdateRecord(currRec); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{redisXdlData}:*"); + bool answ = await ExecFlushRedisPattern(pattern); + await Task.Delay(1); + return dbResult; + } + + /// + /// Statistiche ODL calcolate (da stored stp_STAT_ODL) + /// + /// + public Task> StatOdl(int IdxOdl) + { + return dbController.OdlStart(IdxOdl); + } + + /// + /// Esegue traduzione dato vocabolario da Lingua + Lemma + /// + /// + /// + /// + public string Traduci(string lemma, string lingua) + { + string answ = $"[{lemma}]"; + // verifico se ho qualcosa nell'obj vocabolario... + if (ObjVocabolario == null || ObjVocabolario.Count == 0) + { + // inizializzo il vocabolario... + ObjVocabolario = VocabolarioGetAll(); + } + var record = ObjVocabolario.Where(x => x.Lingua == lingua && x.Lemma == lemma).FirstOrDefault(); + if (record != null) + { + answ = record.Traduzione; + } + return answ; + } + + /// + /// Restituisce valore della stringa (SE disponibile) + /// + /// + /// + public async Task tryGetConfig(string keyName) + { + string answ = ""; + // preselezione valori + var configData = await ConfigGetAll(); + var currRec = configData.FirstOrDefault(x => x.Chiave == keyName); + if (currRec != null) + { + answ = currRec.Valore; + } + + return answ; + } + + public async Task updateDossierValue(DossierModel currDoss, FluxLogDTO editFL) + { + bool answ = false; + // recupero intero set valori dossier deserializzando... + var fluxLogList = getFluxLog(currDoss.Valore); + await Task.Delay(1); + + // se tutto ok + if (fluxLogList != null) + { + // da provare...!!!! + + // elimino vecchio record + var currRec = fluxLogList.FirstOrDefault(x => x.CodFlux == editFL.CodFlux && x.dtEvento == editFL.dtEvento); + if (currRec != null) + { + fluxLogList.Remove(currRec); + // aggiungo nuovo + fluxLogList.Add(editFL); + } + + // serializzo nuovamente valore + DossierFluxLogDTO? result = new DossierFluxLogDTO(); + var ODLflux = result.ODL.ToList(); + foreach (var item in fluxLogList) + { + ODLflux.Add(item); + } + + DossierFluxLogDTO updatedResult = new DossierFluxLogDTO() { ODL = ODLflux }; + + string rawVal = JsonConvert.SerializeObject(updatedResult); + currDoss.Valore = rawVal; + // aggiorno record sul DB + await dbController.DossiersUpdateValore(currDoss); + } + + return answ; + } + + /// + /// Elenco completo tabella Vocabolario + /// + /// + public List VocabolarioGetAll() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + string source = "REDIS"; + // cerco in redis... + RedisValue rawData = redisDb.StringGet(redisVocabolario); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + } + else + { + result = dbController.VocabolarioGetAll(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(redisVocabolario, rawData, getRandTOut(redisLongTimeCache / 5)); + source = "DB"; + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"VocabolarioGetAll Read from {source}: {ts.TotalMilliseconds}ms"); + if (result == null) + { + result = new List(); + } + return result; + } + + #endregion Public Methods + + #region Protected Fields + + protected Random rand = new Random(); + + #endregion Protected Fields + + #region Protected Properties + + protected string canCacheParametri { get; set; } = ""; + + #endregion Protected Properties + + #region Protected Methods + + /// + /// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec + /// + /// + /// + protected TimeSpan getRandTOut(int stdMinutes) + { + double rndValue = (double)stdMinutes + (double)rand.Next(1, 60) / 60; + return TimeSpan.FromMinutes(rndValue); + } + + #endregion Protected Methods + + #region Private Fields + + private const string redisActionReq = redisBaseAddr + "IO:Action:Req"; + private const string redisAnagGruppi = redisBaseAddrSpec + "Cache:AnagGruppi"; + + private const string redisArtByDossier = redisBaseAddrSpec + "Cache:ArtByDossier"; + + private const string redisArtList = redisBaseAddrSpec + "Cache:ArtList"; + + private const string redisBaseAddr = "MP:"; + private const string redisBaseAddrSpec = redisBaseAddr + "SPEC:"; + + private const string redisConfKey = redisBaseAddrSpec + "Cache:Config"; + + private const string redisDossByMac = redisBaseAddrSpec + "Cache:DossByMac"; + + private const string redisFluxByMac = redisBaseAddrSpec + "Cache:FluxByMac"; + + private const string redisFluxLogFilt = redisBaseAddrSpec + "Cache:FluxLogFilt"; + + private const string redisGiacenzaList = redisBaseAddrSpec + "Cache:GiacenzaList"; + private const string redisMacByFlux = redisBaseAddrSpec + "Cache:MacByFlux"; + + private const string redisMacList = redisBaseAddrSpec + "Cache:MacList"; + private const string redisMacRecipe = redisBaseAddrSpec + "Cache:Recipe"; + + private const string redisOdlByBatch = redisXdlData + "OdlByBatch"; + private const string redisOdlCurrByMac = redisXdlData + "OdlByMac"; + private const string redisOdlList = redisXdlData + "OdlList"; + private const string redisParamPageExp = redisBaseAddrSpec + "Cache:ParamPage"; + private const string redisPOdlByOdl = redisXdlData + "POdlByOdl"; + private const string redisPOdlByPOdl = redisXdlData + "POdlByPOdl"; + private const string redisPOdlList = redisXdlData + "POdlList"; + private const string redisStatoCom = redisBaseAddrSpec + "Cache:StatoCom"; + private const string redisTipoArt = redisBaseAddrSpec + "Cache:TipoArt"; + private const string redisVocabolario = redisBaseAddrSpec + "Cache:Vocabolario"; + private const string redisXdlData = redisBaseAddrSpec + "Cache:XDL:"; + + private const string redisRecipeConf = redisBaseAddrSpec + "Cache:Recipe:Conf"; + + + private static IConfiguration _configuration = null!; + private static ILogger _logger = null!; + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + /// + /// Oggetto vocabolario x uso continuo traduzione + /// + private List ObjVocabolario = new List(); + + /// + /// Oggetto per connessione a REDIS + /// + private ConnectionMultiplexer redisConn = null!; + + /// + /// Oggetto per connessione a REDIS modalità admin (ex flux dati) + /// + private ConnectionMultiplexer redisConnAdmin = null!; + + /// + /// Oggetto DB redis da impiegare x chiamate R/W + /// + private IDatabase redisDb = null!; + + private int redisLongTimeCache = 5; + private int redisShortTimeCache = 4; + + #endregion Private Fields + + #region Private Methods + + /// + /// Esegue flush memoria redis dato pattern + /// + /// + /// + private async Task ExecFlushRedisPattern(RedisValue pattern) + { + bool answ = false; + var listEndpoints = redisConnAdmin.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + //var server = redisConnAdmin.GetServer(listEndpoints[0]); + var server = redisConnAdmin.GetServer(endPoint); + if (server != null) + { + var keyList = server.Keys(redisDb.Database, pattern); + foreach (var item in keyList) + { + await redisDb.KeyDeleteAsync(item); + } + // brutalmente rimuovo intero contenuto DB... DANGER + //await server.FlushDatabaseAsync(); + answ = true; + } + } + + return answ; + } + + private async Task resetCacheArticoli() + { + RedisValue pattern = new RedisValue($"{redisArtByDossier}:*"); + await ExecFlushRedisPattern(pattern); + pattern = new RedisValue($"{redisArtList}:*"); + await ExecFlushRedisPattern(pattern); + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/MP.IOC/MP.IOC.csproj b/MP.IOC/MP.IOC.csproj new file mode 100644 index 00000000..cfaeaa03 --- /dev/null +++ b/MP.IOC/MP.IOC.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/MP.IOC/Program.cs b/MP.IOC/Program.cs new file mode 100644 index 00000000..21ff5744 --- /dev/null +++ b/MP.IOC/Program.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Configuration; +using MP.IOC.Data; +using StackExchange.Redis; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +ConfigurationManager configuration = builder.Configuration; +// REDIS setup +string connStringRedis = configuration.GetConnectionString("Redis"); +string redisSrvAddr = connStringRedis.Substring(0, connStringRedis.IndexOf(":")); +// avvio oggetto shared x redis... +var redisMultiplexer = ConnectionMultiplexer.Connect(connStringRedis); +builder.Services.AddSingleton(redisMultiplexer); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/MP.IOC/Properties/launchSettings.json b/MP.IOC/Properties/launchSettings.json new file mode 100644 index 00000000..3785cec7 --- /dev/null +++ b/MP.IOC/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:57791", + "sslPort": 44362 + } + }, + "profiles": { + "MP.IOC": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7050;http://localhost:5264", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MP.IOC/Recipe/Fimat/RecipeConf.json b/MP.IOC/Recipe/Fimat/RecipeConf.json new file mode 100644 index 00000000..534d2a53 --- /dev/null +++ b/MP.IOC/Recipe/Fimat/RecipeConf.json @@ -0,0 +1,80 @@ +{ + "TemplateFile": "Fimat/TemplateOutput.tpl", + "NumRow": 2, + "HeadConf": { + "ListKeys": { + "CustDrumCode": "F:", + "Taglio-N": "F:1", + "Taglio-D": "F:4", + "ServiceType": "F:N", + "RecipeType": "F:C", + "ViscoName": "F:", + "ViscoValue": "F:0", + "LotID": "C:IdxPODL", + "OrderCode": "C:CodePODL", + "Article": "C:CodArticolo", + "Info1": "C:DescArticolo", + "Prio": "E:Priority", + "DrumType": "E:DrumType", + "Customer": "Tenditalia", + "Design": "DESIGN", + "Screen": "SCREEN", + "Variant": "VARIANT", + "RecName": "CODE000", + "Series": "E:Series", + "UM": "E:UM", + "DosType": "E:DosType", + "Note1": "", + "Note2": "", + "Sequence": "1", + "SequenceTot": "8", + "Quantity-kg": "1.00" + }, + "EnumVal": { + "Priority": { + "N": "Normal", + "H": "Hight" + }, + "DrumType": { + "1": "Small", + "2": "Medium", + "3": "Big" + }, + "Series": { + "1": "Series 1", + "2": "Series 2" + }, + "UM": { + "0": "Percentage", + "1": "g/kg", + "2": "parts for colour and g/kg for thickener", + "3": "gr and parts for thickener", + "4": "g/kg for colour and parts for thickener", + "5": "parts for colour and parts for thickener" + }, + "DosType": { + "P": "Production", + "S": "Sampling" + } + } + }, + "RowsConf": { + "ListKeys": { + "Weight-gr-prev": "F:0.00", + "CompNumber": "C:RowNum", + "ColourCode": "C001", + "Description": "COLOR1", + "TypComp": "E:ColType", + "PartsWeight": "1.00", + "PartsPerc": "0.10", + "Weight-gr": "30.00" + }, + "EnumVal": { + "ColType": { + "C": "Color", + "A": "Thickener", + "X": "Auxiliaries" + } + } + } +} \ No newline at end of file diff --git a/MP.IOC/Recipe/Fimat/TemplateOutput.tpl b/MP.IOC/Recipe/Fimat/TemplateOutput.tpl new file mode 100644 index 00000000..512e709d --- /dev/null +++ b/MP.IOC/Recipe/Fimat/TemplateOutput.tpl @@ -0,0 +1,14 @@ +{ + "A_Recipe": { + "DesRecipe": { + "DesData": { + ||PlaceholderHeader|| + } + }, + "ColRecipe": [ + ||SROW:{"ColData":{|| + ||PlaceholderRows|| + ||EROW:}}|| + ] + } +} \ No newline at end of file diff --git a/MP.IOC/Recipe/Fimat/_RefRecipe.json b/MP.IOC/Recipe/Fimat/_RefRecipe.json new file mode 100644 index 00000000..5e5c1292 --- /dev/null +++ b/MP.IOC/Recipe/Fimat/_RefRecipe.json @@ -0,0 +1,60 @@ +{ + "A_Recipe": { + "DesRecipe": { + "DesData": { + "Prio": "N", + "DrumType": "1", + "CustDrumCode": "123456789012", + "OrderCode": "ORDERCODE", + "Customer": "CUSTOMER", + "Design": "DESIGN", + "Screen": "SCREEN", + "Variant": "VARIANT", + "RecName": "RECNAME", + "Article": "ARTICLE", + "LotID": "LOTID", + "Info1": "INFO1", + "ViscoName": "", + "Taglio-N": "1", + "Taglio-D": "4", + "Sequence": "1", + "SequenceTot": "8", + "Series": "2", + "ViscoValue": "0", + "UM": "1", + "DosType": "P", + "ServiceType": "N", + "RecipeType": "C", + "Note1": "NOTE1", + "Note2": "NOTE2", + "Quantity-kg": "10.00" + } + }, + "ColRecipe": [ + { + "ColData": { + "CompNumber": "1", + "ColourCode": "C001", + "Description": "COLOR1", + "TypComp": "C", + "PartsWeight": "1.00", + "PartsPerc": "0.10", + "Weight-gr": "30.00", + "Weight-gr-prev": "0.00" + } + }, + { + "ColData": { + "CompNumber": "2", + "ColourCode": "THICK1", + "Description": "Thickner 1", + "TypComp": "A", + "PartsWeight": "997.00", + "PartsPerc": "99.70", + "Weight-gr": "9970.00", + "Weight-gr-prev": "0.00" + } + } + ] + } +} \ No newline at end of file diff --git a/MP.IOC/Recipe/README.md b/MP.IOC/Recipe/README.md new file mode 100644 index 00000000..5f86f69b --- /dev/null +++ b/MP.IOC/Recipe/README.md @@ -0,0 +1,134 @@ +# Ricette +- [Ricette](#ricette) +- [Gestione formati e tag x ricette](#gestione-formati-e-tag-x-ricette) + - [Definizione tag ricette](#definizione-tag-ricette) + - [Esempio tracciato Template](#esempio-tracciato-template) + - [Esempio tracciato configurazione complessivo](#esempio-tracciato-configurazione-complessivo) + - [Campi Calcolati](#campi-calcolati) + + +# Gestione formati e tag x ricette + +Nelle ricette ci possono essere campi liberi, campi da enum (da configurare nel json) e campi calcolati. + +E' utile riportare un esempio di tracciato finale desiderato insieme ad un file template tpl da cui attingere x la realizzazione insieme ai campi definiti x testata e corpo. + +In particolare sia per testata che corpo sono indicati casi di dati enumerativi (in modo che sia usato uno tra i valori ammessi) + + +## Definizione tag ricette + +I tag ammessi x le ricette sono di seguito riassunti e definiti: + +| Cod | Significato | Definizione | +|-----|-------------|--------------------------------------------| +| C | Calcolato | Campo calcolato (NON modificabile) | +| E | Enum | IdxODL numerico | +| F | Fixed | IdxODL numerico | +| S | Suggested | Campo calcolato e suggerito (modificabile) | + +IN particolare gli Enum sono poi da riportare nella struttura degli EnumVal che deve completare i valori di testata o di corpo. + +## Esempio tracciato Template + +Ecco un esempio di template + +```csharp +{ + "A_Recipe": { + "DesRecipe": { + "DesData": { + ||PlaceholderHeader|| + }, + "ColRecipe": [ + ||SROW:{"ColData":{|| + ||PlaceholderRows|| + ||EROW":}}|| + ] + } +} +``` + +il blocco ||PlaceholderHeader|| verrà sostituito per intero dai valori di testata. + +Il blocco delel righe è invece più complesso e composto da 3 parti: + * nel primo blocco, ||SROW:{"ColData":{||, si cerca start riga e si prende il valore compreso tra ||SROW:: e || come testata riga da ripetere + * nel secondo blocco si sostituiscono tutti i valori della riga i-esima + * nel terzo blocco ||EROW":}}|| si sistema la chiusura della riga (end row) + + + +## Esempio tracciato configurazione complessivo + +```json +{ + "TemplateFile": "TemplateOutput.tpl", + "NumRow": 2, + "HeadConf": { + "ListKeys": { + "CustDrumCode": "F:", + "Taglio-N": "F:1", + "LotID": "C:IdxPODL", + "OrderCode": "C:CodePODL", + "Prio": "E:Priority", + "DrumType": "E:DrumType", + "Customer": "Tenditalia", + "Design": "DESIGN", + "Quantity-kg": "1.00" + }, + "EnumVal": { + "Priority": { + "N": "Normal", + "H": "Hight" + }, + "DrumType": { + "1": "Small", + "2": "Medium", + "3": "Big" + } + } + }, + "RowsConf": { + "ListKeys": { + "Weight-gr-prev": "F:0.00", + "CompNumber": "C:RowNum", + "ColourCode": "C001", + "Description": "COLOR1", + "TypComp": "E:ColType", + "PartsWeight": "1.00", + "PartsPerc": "0.10", + "Weight-gr": "30.00" + }, + "EnumVal": { + "ColType": { + "C": "Color", + "A": "Thickener", + "X": "Auxiliaries" + } + } + } +} +``` + +Come si può notare, il tracciato di configurazione comprende i seguenti blocchi: +| Blocco | descrizione | +|-------------------|--------------------------------------| +| HeadConf | Configurazione campi testata | +| HeadConf:ListKeys | Elenco chiavi/valori x testata | +| HeadConf:EnumVal | Elenco enumerativi ammessi x testata | +| RowsConf | Configurazione campi riga | +| RowsConf:ListKeys | Elenco chiavi/valori x righe | +| RowsConf:EnumVal | Elenco enumerativi ammessi x righe | + +## Campi Calcolati + +I tag noti x decodifica riguardano i campi calcolati; hard coded, e riconosciuti, sono i seguenti: + +| ID | Note | Format | Esempio | +|--------------|-----------------------------------------|------------------|-----------------| +| IdxPODL | IdxODL numerico | - | 123 | +| CodePODL | Codice alfanumerico partendo da IdxPODL | PODL{0:00000000} | PODL00000123 | +| CodArticolo | Campo CodArticolo | - | Art000123 | +| DescArticolo | Campo DescArticolo | - | Articol 123 blu | +| RowNum | Numero riga | - | 1 | +| RowTot | Totale righe | - | 10 | diff --git a/MP.IOC/Recipe/README.pdf b/MP.IOC/Recipe/README.pdf new file mode 100644 index 0000000000000000000000000000000000000000..02bb87f2864fa5531173b7dc44fcb107bf20f866 GIT binary patch literal 71330 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 Im>l4L0Z`v*<^TWy literal 0 HcmV?d00001 diff --git a/MP.IOC/appsettings.Development.json b/MP.IOC/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/MP.IOC/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json new file mode 100644 index 00000000..8d3d38fb --- /dev/null +++ b/MP.IOC/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "CodApp": "MP.IOC", + "ConnectionStrings": { + "Mp.Data": "Server=SQL2016DEV;Database=MoonPro; User ID=sa;Password=keyhammer16; integrated security=False; MultipleActiveResultSets=True; App=MP.SPEC;", + "Redis": "localhost:6379,DefaultDatabase=1,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", + "RedisAdmin": "localhost:6379,DefaultDatabase=1,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true", + "MongoConnect": "mongodb://W2019-MONGODB:27017" + } +} diff --git a/MP.SPEC/MP.SPEC.csproj b/MP.SPEC/MP.SPEC.csproj index dd054b69..b05ecb5e 100644 --- a/MP.SPEC/MP.SPEC.csproj +++ b/MP.SPEC/MP.SPEC.csproj @@ -5,7 +5,7 @@ enable enable MP.SPEC - 6.16.2302.1313 + 6.16.2302.1415 diff --git a/MP.SPEC/Resources/ChangeLog.html b/MP.SPEC/Resources/ChangeLog.html index f5c92414..4e5272ac 100644 --- a/MP.SPEC/Resources/ChangeLog.html +++ b/MP.SPEC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MAPOSPEC -

Versione: 6.16.2302.1313

+

Versione: 6.16.2302.1415


Note di rilascio:
  • diff --git a/MP.SPEC/Resources/VersNum.txt b/MP.SPEC/Resources/VersNum.txt index 5b8cb817..5b6fd3b7 100644 --- a/MP.SPEC/Resources/VersNum.txt +++ b/MP.SPEC/Resources/VersNum.txt @@ -1 +1 @@ -6.16.2302.1313 +6.16.2302.1415 diff --git a/MP.SPEC/Resources/manifest.xml b/MP.SPEC/Resources/manifest.xml index 027ebd80..fd6463e2 100644 --- a/MP.SPEC/Resources/manifest.xml +++ b/MP.SPEC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 6.16.2302.1313 + 6.16.2302.1415 https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/MP.SPEC.zip https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/ChangeLog.html false