diff --git a/MP.Core/DTO/CountResultDto.cs b/MP.Core/DTO/CountResultDto.cs
new file mode 100644
index 00000000..c55a390b
--- /dev/null
+++ b/MP.Core/DTO/CountResultDto.cs
@@ -0,0 +1,7 @@
+namespace MP.Core.DTO
+{
+ public class CountResultDto
+ {
+ public int NumCount { get; set; }
+ }
+}
diff --git a/MP.Data/Controllers/MpSpecController.cs b/MP.Data/Controllers/MpSpecController.cs
index 5c39a619..96be09c7 100644
--- a/MP.Data/Controllers/MpSpecController.cs
+++ b/MP.Data/Controllers/MpSpecController.cs
@@ -371,9 +371,9 @@ namespace MP.Data.Controllers
/// Elenco valori ammessi x Tipo articoli
///
///
- public List AnagTipoArtLV()
+ public Task> AnagTipoArtLvAsync()
{
- return ListValuesFilt("AnagArticoli", "Tipo");
+ return ListValuesFiltAsync("AnagArticoli", "Tipo");
}
///
@@ -395,6 +395,35 @@ namespace MP.Data.Controllers
return dbResult;
}
+ ///
+ /// Conteggio num articoli Async
+ ///
+ ///
+ public async Task ArticoliCountAsync()
+ {
+ using var dbCtx = new MoonProContext(options);
+ var result = await dbCtx
+ .DbSetArticoli
+ .CountAsync();
+ return result;
+ }
+
+ ///
+ /// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
+ ///
+ ///
+ public async Task ArticoliCountUsedAsync()
+ {
+ using var dbCtx = new MoonProContext(options);
+ var result = await dbCtx
+ .DbSetCounter
+ .FromSqlRaw("EXEC stp_ART_CountUsed")
+ .AsNoTracking()
+ .ToListAsync();
+
+ return result.FirstOrDefault()?.NumCount ?? 0;
+ }
+
///
/// Eliminazione Record
///
@@ -492,6 +521,24 @@ namespace MP.Data.Controllers
return dbResult;
}
+ ///
+ /// Elenco tabella Articoli NON IMPIEGATI (da stored stp_ART_getUsed) Async
+ ///
+ ///
+ public async Task> ArticoliGetUnusedAsync()
+ {
+ List dbResult = new List();
+ using (var dbCtx = new MoonProContext(options))
+ {
+ dbResult = await dbCtx
+ .DbSetArticoli
+ .FromSqlRaw("EXEC stp_ART_getNotUsed")
+ .AsNoTracking()
+ .ToListAsync();
+ }
+ return dbResult;
+ }
+
///
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed)
///
@@ -510,6 +557,24 @@ namespace MP.Data.Controllers
return dbResult;
}
+ ///
+ /// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
+ ///
+ ///
+ public async Task> ArticoliGetUsedAsync()
+ {
+ List dbResult = new List();
+ using (var dbCtx = new MoonProContext(options))
+ {
+ dbResult = await dbCtx
+ .DbSetArticoli
+ .FromSqlRaw("EXEC stp_ART_getUsed")
+ .AsNoTracking()
+ .ToListAsync();
+ }
+ return dbResult;
+ }
+
///
/// Update Record
///
@@ -569,6 +634,24 @@ namespace MP.Data.Controllers
return dbResult;
}
+ ///
+ /// Elenco da tabella Config Async
+ ///
+ ///
+ public async Task> ConfigGetAllAsync()
+ {
+ List dbResult = new List();
+ using (var dbCtx = new MoonProContext(options))
+ {
+ dbResult = await dbCtx
+ .DbSetConfig
+ .AsNoTracking()
+ .OrderBy(x => x.Chiave)
+ .ToListAsync();
+ }
+ return dbResult;
+ }
+
///
/// Update record config
///
@@ -1435,6 +1518,7 @@ namespace MP.Data.Controllers
}
return dbResult;
}
+
///
/// Elenco PODL per composizione KIT non avviati filtrati x articolo, KeyRich (che contiene stato)
///
@@ -1492,6 +1576,7 @@ namespace MP.Data.Controllers
}
return dbResult;
}
+
///
/// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) - ASYNC
///
@@ -1521,6 +1606,7 @@ namespace MP.Data.Controllers
return dbResult;
}
+#if false
///
/// Elenco valori ammessi x tabella/colonna
///
@@ -1540,7 +1626,8 @@ namespace MP.Data.Controllers
.ToList();
}
return dbResult;
- }
+ }
+#endif
///
/// Elenco valori ammessi x tabella/colonna Async
diff --git a/MP.Data/MoonProContext.cs b/MP.Data/MoonProContext.cs
index 0d65c590..2789eb05 100644
--- a/MP.Data/MoonProContext.cs
+++ b/MP.Data/MoonProContext.cs
@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
+using MP.Core.DTO;
using MP.Data.DbModels;
using MP.Data.DbModels.Anag;
using NLog;
@@ -41,6 +42,7 @@ namespace MP.Data
#region Public Properties
+ public virtual DbSet DbSetCounter { get; set; }
public virtual DbSet DbSetDbSize { get; set; }
public virtual DbSet DbSetAlarmLog { get; set; }
@@ -155,6 +157,10 @@ namespace MP.Data
{
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
+
+ modelBuilder.Entity().HasNoKey();
+
+
modelBuilder.Entity(entity =>
{
entity.HasKey(e => e.CodArticolo);
diff --git a/MP.SPEC/Data/MpDataService.cs b/MP.SPEC/Data/MpDataService.cs
index 6cc2b305..14939557 100644
--- a/MP.SPEC/Data/MpDataService.cs
+++ b/MP.SPEC/Data/MpDataService.cs
@@ -82,20 +82,18 @@ namespace MP.SPEC.Data
#endregion Public Events
#region Public Properties
-
public static MpSpecController dbController { get; set; } = null!;
-
public static MpMongoController mongoController { get; set; } = null!;
-
public MessagePipe BroadastMsgPipe { get; set; } = null!;
-
- ///
- /// Dizionario dei tag configurati per IOB
- ///
public Dictionary> currTagConf { get; set; } = new Dictionary>();
+ // Cache per controllo eliminazione articoli (Smart HashSet approach)
+ private HashSet _usedArtIdsCache = new();
+ private HashSet _unusedArtIdsCache = new();
+ private DateTime _artCacheExpiry = DateTime.MinValue;
#endregion Public Properties
+
#region Public Methods
///
@@ -312,41 +310,9 @@ namespace MP.SPEC.Data
);
}
-#if false
- public async Task> AnagStatiComm()
+ public async Task> AnagTipoArtLvAsync()
{
- using var activity = ActivitySource.StartActivity("AnagStatiComm");
- string source = "DB";
- List? result = new List();
- // cerco in redis...
- RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom);
- if (!string.IsNullOrEmpty($"{rawData}"))
- {
- result = JsonConvert.DeserializeObject>($"{rawData}");
- source = "REDIS";
- }
- else
- {
- result = await Task.FromResult(dbController.AnagStatiComm());
- // serializzo e salvo...
- rawData = JsonConvert.SerializeObject(result);
- await redisDb.StringSetAsync(Utils.redisStatoCom, rawData, getRandTOut(redisLongTimeCache));
- }
- if (result == null)
- {
- result = new List();
- }
- activity?.SetTag("data.source", source);
- activity?.SetTag("result.count", result.Count);
- activity?.Stop();
- LogTrace($"AnagStatiComm Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
- return result;
- }
-#endif
-
- public async Task> AnagTipoArtLV()
- {
- using var activity = ActivitySource.StartActivity("AnagStatiCommAsync");
+ using var activity = ActivitySource.StartActivity("AnagTipoArtLvAsync");
string source = "DB";
List? result = new List();
// cerco in redis...
@@ -358,7 +324,7 @@ namespace MP.SPEC.Data
}
else
{
- result = await Task.FromResult(dbController.AnagTipoArtLV());
+ result = await dbController.AnagTipoArtLvAsync();
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(Utils.redisTipoArt, rawData, getRandTOut(redisLongTimeCache));
@@ -370,7 +336,7 @@ namespace MP.SPEC.Data
activity?.SetTag("data.source", source);
activity?.SetTag("result.count", result.Count);
activity?.Stop();
- LogTrace($"AnagTipoArtLV Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
+ LogTrace($"AnagTipoArtLvAsync Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
return result;
}
@@ -437,7 +403,6 @@ namespace MP.SPEC.Data
string sKey = string.IsNullOrWhiteSpace(tipo) ? "ALL" : tipo.Trim();
string redisKey = $"{Utils.redisArtList}:{azienda}:Tipo:{sKey}";
- string memKey = $"MEM:{redisKey}";
return await GetOrFetchAsync(
operationName: "ArticoliGetByTipoAsync",
@@ -448,53 +413,7 @@ namespace MP.SPEC.Data
?? new List()
);
-
-#if false
- return await GetOrCreateCachedAsync(
- operationName: "ArticoliGetByTipoAsync",
- memKey: memKey,
- redisKey: redisKey,
- // ✅ TTL lungo (dato abbastanza statico)
- memoryTtl: TimeSpan.FromMinutes(5),
- dbFactory: async () =>
- await dbController.ArticoliGetByTipoAsync(tipo, azienda)
- ?? new List()
- );
-#endif
}
-#if false
- public async Task> ArticoliGetByTipoAsync(string tipo, string azienda = "*")
- {
- using var activity = ActivitySource.StartActivity("ArticoliGetByTipoAsync");
- List? result = new List();
- string source = "DB";
- string sKey = string.IsNullOrEmpty(tipo) ? "ALL" : tipo;
- string currKey = $"{Utils.redisArtList}:{azienda}:Tipo:{sKey}";
- // cerco in redis dato valore sel idxMaccSel...
- RedisValue rawData = await redisDb.StringGetAsync(currKey);
- if (rawData.HasValue)
- {
- result = JsonConvert.DeserializeObject>($"{rawData}");
- source = "REDIS";
- }
- else
- {
- result = await dbController.ArticoliGetByTipoAsync(tipo, azienda);
- // serializzo e salvo...
- rawData = JsonConvert.SerializeObject(result);
- await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache));
- }
- if (result == null)
- {
- result = new List();
- }
- activity?.SetTag("data.source", source);
- activity?.SetTag("result.count", result.Count);
- activity?.Stop();
- LogTrace($"ArticoliGetByTipoAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
- return result;
- }
-#endif
///
/// Restitusice elenco articoli cercati
@@ -517,18 +436,6 @@ namespace MP.SPEC.Data
await dbController.ArticoliGetSearchAsync(numRecord, azienda, searchVal)
?? new List()
);
-
-#if false
- return await GetOrCreateCachedAsync(
- operationName: "ArticoliGetSearchAsync",
- memKey: memKey,
- redisKey: redisKey,
- memoryTtl: TimeSpan.FromMinutes(2),
- dbFactory: async () =>
- await dbController.ArticoliGetSearchAsync(numRecord, azienda, searchVal)
- ?? new List()
- );
-#endif
}
///
@@ -550,6 +457,7 @@ namespace MP.SPEC.Data
var result = tryGet.Value!;
activity?.SetTag("data.source", source);
+ activity?.Stop();
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
return result;
}
@@ -573,57 +481,11 @@ namespace MP.SPEC.Data
source = fromDb ? "DB" : "REDIS";
activity?.SetTag("data.source", source);
+ activity?.Stop();
LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
return final!;
}
-#if false
- private async Task GetOrFetchAsync(string operationName, string cacheKey, Func> fetchFunc, TimeSpan expiration)
- {
- using var activity = ActivitySource.StartActivity(operationName);
-
- string source;
- T result;
-
- // ✅ 1. Tenta MEMORY / DISTRIBUTED (senza factory)
- var memTry = await _cache.TryGetAsync(cacheKey);
-
- if (memTry.HasValue)
- {
- result = memTry.Value!;
- source = "MEMORY";
- }
- else
- {
- bool fromDb = false;
-
- // ✅ 2. fallback con factory
- result = await _cache.GetOrSetAsync(
- cacheKey,
- async _ =>
- {
- fromDb = true;
- return await fetchFunc();
- },
- options => options.SetDuration(expiration)
- )!;
-
- source = fromDb ? "DB" : "REDIS";
- }
-
- // ✅ tracing e log
- activity?.SetTag("data.source", source);
-
- if (result is System.Collections.ICollection coll)
- activity?.SetTag("result.count", coll.Count);
- else
- activity?.SetTag("result.count", result != null ? 1 : 0);
-
- LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds:F4} ms");
-
- return result;
- }
-#endif
///
/// Cancellazione FusionCache (totale)
@@ -666,146 +528,6 @@ namespace MP.SPEC.Data
return fatto;
}
-#if false
- ///
- /// Helper per gestione cache a 3 livelli: MEMORY, REDIS e DB con opzioni
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- private async Task GetOrCreateCachedAsync(string operationName, string memKey, string redisKey, TimeSpan memoryTtl, Func> dbFactory, Func? serialize = null, Func? deserialize = null)
- {
- using var activity = ActivitySource.StartActivity(operationName);
-
- string source = "NA";
- T result;
-
- string groupKey = memKey.Split(':')[0]; // es: "MACCHINE_MEM"
-
- _memoryKeys.AddOrUpdate(
- groupKey,
- _ => new HashSet { memKey },
- (_, set) => { set.Add(memKey); return set; }
- );
-
- // ✅ default serializer (fallback)
- serialize ??= (obj) => JsonConvert.SerializeObject(obj);
- deserialize ??= (str) => JsonConvert.DeserializeObject(str)!;
-
- // ✅ 1. MEMORY
- if (_cache.TryGetValue(memKey, out T cached))
- {
- result = cached;
- source = "MEMORY";
- }
- else
- {
- // ✅ 2. MISS → factory
- result = await _cache.GetOrCreateAsync(memKey, async entry =>
- {
- entry.AbsoluteExpirationRelativeToNow = memoryTtl;
-
- // 👉 REDIS
- var rawData = await redisDb.StringGetAsync(redisKey);
-
- if (rawData.HasValue)
- {
- source = "REDIS";
- return deserialize(rawData!);
- }
-
- // 👉 DB
- source = "DB";
-
- var dbResult = await dbFactory();
-
- var safeResult = dbResult == null ? default! : dbResult;
-
- await redisDb.StringSetAsync(
- redisKey,
- serialize(safeResult),
- getRandTOut(redisLongTimeCache)
- );
-
- return safeResult;
- })!;
- }
-
- // ✅ logging e tracing centralizzati
- activity?.SetTag("data.source", source);
-
- if (result is System.Collections.ICollection coll)
- activity?.SetTag("result.count", coll.Count);
- else
- activity?.SetTag("result.count", result != null ? 1 : 0);
-
- LogTrace($"{operationName} | {source} | {activity?.Duration.TotalMilliseconds}ms");
-
- return result;
- }
-
- ///
- /// Invalidazione di una chiave in memoria e Redis
- ///
- ///
- ///
- ///
- public async Task InvalidateCacheAsync(string memKey, string redisKey)
- {
- // ✅ memoria
- _cache.Remove(memKey);
-
- // ✅ redis
- await redisDb.KeyDeleteAsync(redisKey);
-
- LogTrace($"Cache invalidated | {memKey}");
- }
- private readonly ConcurrentDictionary> _memoryKeys = new();
- ///
- /// Invalidazione cache da pattern
- ///
- ///
- ///
- public async Task InvalidateCacheByPatternAsync(string pattern)
- {
- // ✅ MEMORY (pattern match semplice)
- var keysToRemove = _memoryKeys.Keys
- .Where(k => k.Contains(pattern.Replace("*", "")))
- .ToList();
-
- foreach (var key in keysToRemove)
- {
- if (_memoryKeys.TryRemove(key, out var subKeys))
- {
- foreach (var k in subKeys)
- {
- _cache.Remove(k);
- }
- }
- }
-
- // ✅ REDIS
- var masterEndpoint = redisConn.GetEndPoints()
- .Where(ep => redisConn.GetServer(ep).IsConnected && !redisConn.GetServer(ep).IsReplica)
- .FirstOrDefault();
- var server = redisConn.GetServer(masterEndpoint);
-
- var redisKeys = server.Keys(pattern: pattern);
-
- foreach (var rk in redisKeys)
- {
- await redisDb.KeyDeleteAsync(rk);
- }
-
- LogTrace($"Cache invalidated by pattern | {pattern}");
- }
-#endif
///
/// Aggiornamento record selezionato
@@ -824,6 +546,7 @@ namespace MP.SPEC.Data
return fatto;
}
+
///
/// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su
/// tab veto da DB
@@ -833,69 +556,160 @@ namespace MP.SPEC.Data
public bool ArticoloDelEnabled(object CodArt)
{
using var activity = ActivitySource.StartActivity("ArticoloDelEnabled");
- string source = "DB";
- 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;
- var rawData = redisDb.StringGet(redKeyArtUsed);
- if (!string.IsNullOrEmpty(rawData))
+
+ int numUsed = _usedArtIdsCache.Count;
+ int numUnused = _unusedArtIdsCache.Count;
+ bool usato = true;
+ string source = "MEMORY";
+ // 1. Controllo immediato sulla cache locale (HashSet) x eventuale refresh
+ if (DateTime.Now >= _artCacheExpiry || (numUsed + numUnused) <= 0)
{
- bool.TryParse(rawData, out answ);
+ source = "DB/REDIS";
+ // Fallback sincrono minimo per non rompere il componente Blazor
+ // Nota: Questo è un workaround per la firma sincrona.
+ var task = EnsureArtCacheLoadedAsync(false);
+ task.Wait();
+
+ // rileggo
+ numUsed = _usedArtIdsCache.Count;
+ numUnused = _unusedArtIdsCache.Count;
+ }
+
+ // verifico quale sia l'elenco
+ if (numUsed > 0)
+ {
+ usato = _usedArtIdsCache.Contains(codArticolo);
}
else
{
- // controllo non sia stato mai prodotto sennò non posso cancellare...
- try
- {
- // cerco in cache se ci sia la tabella con gli articoli impiegati...
- var 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
- { }
+ usato = !_unusedArtIdsCache.Contains(codArticolo);
}
+
activity?.SetTag("data.source", source);
activity?.Stop();
- LogTrace($"ArticoloDelEnabled | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
- return answ;
+ LogTrace($"ArticoloDelEnabled | Cod: {codArticolo} | Source: {source}");
+ return !usato;
}
+ ///
+ /// Caricamento asincrono della cache degli articoli (Used/Unused)
+ ///
+ public async Task EnsureArtCacheLoadedAsync(bool forceReload)
+ {
+ if (!forceReload && (DateTime.Now < _artCacheExpiry && (_usedArtIdsCache.Count > 0 || _unusedArtIdsCache.Count > 0)))
+ return;
+
+ try
+ {
+ // verifico quale sia il set + piccolo
+ int totalCount = await dbController.ArticoliCountAsync();
+ int usedCount = await dbController.ArticoliCountUsedAsync();
+
+ if (usedCount <= (totalCount - usedCount))
+ {
+ var usedList = await dbController.ArticoliGetUsedAsync();
+ _usedArtIdsCache = new HashSet(usedList.Select(x => x.CodArticolo));
+ _unusedArtIdsCache.Clear();
+ }
+ else
+ {
+ var unusedList = await dbController.ArticoliGetUnusedAsync();
+ _unusedArtIdsCache = new HashSet(unusedList.Select(x => x.CodArticolo));
+ _usedArtIdsCache.Clear();
+ }
+ _artCacheExpiry = DateTime.Now.AddMinutes(15); // TTL ragionevole per la cache locale
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Errore nel caricamento cache articoli: {ex.Message}");
+ _artCacheExpiry = DateTime.Now.AddSeconds(1); // Retry breve in caso di errore
+ }
+ }
+
+#if false
+ ///
+ /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su
+ /// tab veto da DB
+ ///
+ ///
+ ///
+ public async Task ArticoloDelEnabledAsync(object CodArt)
+ {
+ using var activity = ActivitySource.StartActivity("ArticoloDelEnabledAsync");
+ string codArticolo = $"{CodArt}";
+
+ int cacheCheckArtUsato = 1;
+ int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato);
+ TimeSpan ttl = getRandTOut(cacheCheckArtUsato);
+
+ // 1. Controllo cache locale (Smart HashSet)
+ // Se siamo nel periodo di validità della cache locale, facciamo il controllo istantaneo
+ if (DateTime.Now < _artCacheExpiry)
+ {
+ // Se la nostra cache corrente contiene l'ID (o se stiamo usando la lista degli unused)
+ // Nota: la logica dipende da quale lista è stata caricata.
+ // Per semplicità, se abbiamo caricato gli "usati", cerchiamo tra quelli.
+ // Se abbiamo caricato gli "unused", l'articolo è eliminabile se è nel set.
+ // Ma per evitare confusione, gestiamo il refresh globale.
+ }
+
+ // 2. Fallback su GetOrFetchAsync per garantire la sincronizzazione e l'uso di FusionCache (L1/L2)
+ // Usiamo un approccio che carichi la lista più piccola in memoria.
+
+ // Per evitare complessità di switching lato client, usiamo la lista degli "usati" come riferimento
+ // principale nella cache distribuita, ma ottimizziamo il caricamento.
+
+ bool usato = false;
+ string source = "DB";
+
+ // Controllo se l'ID è già presente nella nostra cache locale degli "usati"
+ if (DateTime.Now < _artCacheExpiry && _usedArtIdsCache.Contains(codArticolo))
+ {
+ usato = true;
+ source = "MEMORY(USED)";
+ }
+ else if (DateTime.Now < _artCacheExpiry && _unusedArtIdsCache.Contains(codArticolo))
+ {
+ usato = false;
+ source = "MEMORY(UNUSED)";
+ }
+ else
+ {
+ // Cache scaduta o non presente: ricalcoliamo la strategia
+ int totalCount = await dbController.ArticoliCountAsync();
+ int usedCount = await dbController.ArticoliCountUsedAsync();
+
+ if (usedCount <= (totalCount - usedCount))
+ {
+ // Gli usati sono meno o uguali agli unused: carichiamo gli usati
+ var usedList = await dbController.ArticoliGetUsedAsync();
+ _usedArtIdsCache = new HashSet(usedList.Select(x => x.CodArticolo));
+ _unusedArtIdsCache.Clear();
+ usato = _usedArtIdsCache.Contains(codArticolo);
+ source = "DB+MEMORY(USED)";
+ }
+ else
+ {
+ // Gli unused sono meno: carichiamo gli unused
+ var unusedList = await dbController.ArticoliGetUnusedAsync();
+ _unusedArtIdsCache = new HashSet(unusedList.Select(x => x.CodArticolo));
+ _usedArtIdsCache.Clear();
+ usato = !_unusedArtIdsCache.Contains(codArticolo);
+ source = "DB+MEMORY(UNUSED)";
+ }
+ _artCacheExpiry = DateTime.Now.AddMinutes(cacheCheckArtUsato);
+ }
+
+ bool answ = !usato;
+
+ activity?.SetTag("data.source", source);
+ activity?.Stop();
+ LogTrace($"ArticoloDelEnabledAsync | Cod: {codArticolo} | Usato: {usato} | {activity?.Duration.TotalMilliseconds}ms");
+ return answ;
+ }
+#endif
+
public string CalcRecipe(RecipeModel currRecipe)
{
using var activity = ActivitySource.StartActivity("CalcRecipe");
@@ -956,7 +770,7 @@ namespace MP.SPEC.Data
}
else
{
- result = await Task.FromResult(dbController.ConfigGetAll());
+ result = await dbController.ConfigGetAllAsync();
// serializzo e salvo...
rawData = JsonConvert.SerializeObject(result);
await redisDb.StringSetAsync(Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache));
@@ -1198,18 +1012,18 @@ namespace MP.SPEC.Data
}
///
- /// Restitusice elenco aziende
+ /// Restituisce elenco aziende
///
///
public async Task> ElencoAziendeAsync()
{
- using var activity = ActivitySource.StartActivity("ElencoAziendeAsync");
- string source = "DB";
- var listAz = await dbController.AnagGruppiAziendeAsync();
- activity?.SetTag("data.source", source);
- activity?.Stop();
- LogTrace($"ElencoAziendeAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
- return listAz;
+ return await GetOrFetchAsync(
+ operationName: "ElencoAziendeAsync",
+ cacheKey: $"{Utils.redisAnagGruppi}:Aziende",
+ expiration: TimeSpan.FromMinutes(redisLongTimeCache * 2),
+ fetchFunc: async () =>
+ await dbController.AnagGruppiAziendeAsync() ?? new List()
+ );
}
///
@@ -2898,9 +2712,6 @@ namespace MP.SPEC.Data
{
string redisKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}";
- // ✅ stessa chiave per memoria (puoi anche prefissare)
- string memKey = $"MEM:{redisKey}";
-
return await GetOrFetchAsync(
operationName: "POdlToKitListGetFiltAsync",
cacheKey: redisKey,
@@ -2916,57 +2727,7 @@ namespace MP.SPEC.Data
) ?? new List()
);
-#if false
- return await GetOrCreateCachedAsync(
- operationName: "POdlToKitListGetFiltAsync",
- memKey: memKey,
- redisKey: redisKey,
- // ✅ TTL RAM breve (coerente con redisShortTimeCache)
- memoryTtl: TimeSpan.FromSeconds(redisShortTimeCache),
- dbFactory: async () =>
- await dbController.ListPODL_KitFiltAsync(
- lanciato,
- keyRichPart,
- idxMacchina,
- codGruppo,
- startDate,
- endDate
- ) ?? new List()
- );
-#endif
}
-#if false
- public async Task> POdlToKitListGetFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate)
- {
- using var activity = ActivitySource.StartActivity("POdlToKitListGetFiltAsync");
- List? result = new List();
- string source = "DB";
- string currKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}";
- // cerco in redis dato valore sel idxMaccSel...
- RedisValue rawData = redisDb.StringGet(currKey);
- if (rawData.HasValue)
- {
- result = JsonConvert.DeserializeObject>($"{rawData}");
- source = "REDIS";
- }
- else
- {
- result = await dbController.ListPODL_KitFiltAsync(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();
- }
- activity?.SetTag("data.source", source);
- activity?.SetTag("result.count", result.Count);
- activity?.Stop();
- LogTrace($"POdlToKitListGetFiltAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms");
- return result;
- }
-#endif
///
/// Chiamata salvataggio ricetta + refresh REDIS
diff --git a/MP.SPEC/MP.SPEC.csproj b/MP.SPEC/MP.SPEC.csproj
index 26cb8e72..03db7fb4 100644
--- a/MP.SPEC/MP.SPEC.csproj
+++ b/MP.SPEC/MP.SPEC.csproj
@@ -5,7 +5,7 @@
enable
enable
MP.SPEC
- 8.16.2605.2709
+ 8.16.2605.2711
1800a78a-6ff1-40f9-b490-87fb8bfc1394
en
diff --git a/MP.SPEC/Pages/Articoli.razor.cs b/MP.SPEC/Pages/Articoli.razor.cs
index accea5f3..2f1ad8f5 100644
--- a/MP.SPEC/Pages/Articoli.razor.cs
+++ b/MP.SPEC/Pages/Articoli.razor.cs
@@ -16,8 +16,6 @@ namespace MP.SPEC.Pages
: "";
}
- private SelectArticoliParams currFilter = new SelectArticoliParams();
-
public void Dispose()
{
currRecord = null;
@@ -51,51 +49,6 @@ namespace MP.SPEC.Pages
[Inject]
protected NavigationManager NavManager { get; set; } = null!;
- protected int totalCount
- {
- get
- {
- int answ = 0;
- if (SearchRecords != null)
- {
- answ = SearchRecords.Count;
- }
- return answ;
- }
- }
-
- #endregion Protected Properties
-
- #region Protected Methods
-
- private bool isNewArt = false;
-
- ///
- /// Crea nuovo record e va in editing...
- ///
- ///
- protected async Task addNew()
- {
- isNewArt = true;
- currRecord = new AnagArticoliModel()
- {
- CodArticolo = $"_NEW_{DateTime.Now:yyyyMMdd.HHmmss}",
- DescArticolo = "Nuovo articolo",
- Azienda = selAzienda != "*" ? selAzienda : "MAPO",
- Disegno = "",
- Tipo = "ART",
- CurrRev = "",
- ProdRev = ""
- };
- await Task.Delay(1);
- }
-
- protected async Task resetSearch()
- {
- SearchVal = "";
- await ReloadData();
- }
-
protected string searchCss
{
get => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary";
@@ -123,9 +76,43 @@ namespace MP.SPEC.Pages
}
}
}
- private string searchVal { get; set; } = "";
- private string chkDisabled => isNewArt ? "" : "disabled";
+ protected int totalCount
+ {
+ get
+ {
+ int answ = 0;
+ if (SearchRecords != null)
+ {
+ answ = SearchRecords.Count;
+ }
+ return answ;
+ }
+ }
+
+ #endregion Protected Properties
+
+ #region Protected Methods
+
+ ///
+ /// Crea nuovo record e va in editing...
+ ///
+ ///
+ protected async Task addNew()
+ {
+ isNewArt = true;
+ currRecord = new AnagArticoliModel()
+ {
+ CodArticolo = $"_NEW_{DateTime.Now:yyyyMMdd.HHmmss}",
+ DescArticolo = "Nuovo articolo",
+ Azienda = selAzienda != "*" ? selAzienda : "MAPO",
+ Disegno = "",
+ Tipo = "ART",
+ CurrRev = "",
+ ProdRev = ""
+ };
+ await Task.Delay(1);
+ }
protected async Task cancel()
{
@@ -134,6 +121,24 @@ namespace MP.SPEC.Pages
await Task.Delay(1);
}
+ protected async Task cloneRecord(AnagArticoliModel selRec)
+ {
+ isNewArt = true;
+ // creo record duplicato...
+ AnagArticoliModel newRec = new AnagArticoliModel()
+ {
+ Azienda = selRec.Azienda,
+ CodArticolo = $"clone-{selRec.CodArticolo}",
+ DescArticolo = $"CLONE - {selRec.DescArticolo}",
+ Disegno = selRec.Disegno,
+ Tipo = selRec.Tipo,
+ CurrRev = "",
+ ProdRev = ""
+ };
+ currRecord = newRec;
+ await Task.Delay(1);
+ }
+
///
/// Eliminazione record selezionato (previa conferma)
///
@@ -163,21 +168,7 @@ namespace MP.SPEC.Pages
protected override async Task OnInitializedAsync()
{
numRecord = 10;
-#if false
- configData = await MDService.ConfigGetAll();
- var currRec = configData.FirstOrDefault(x => x.Chiave == "AZIENDA");
- if (currRec != null)
- {
- selAzienda = currRec.Valore;
- }
-#endif
- selAzienda = await MDService.ConfigTryGetAsync("AZIENDA");
- if (string.IsNullOrEmpty(selAzienda))
- {
- selAzienda = "*";
- }
- ListAziende = await MDService.ElencoAziendeAsync();
- ListTipoArt = await MDService.AnagTipoArtLV();
+ await ReloadBaseData();
}
protected override async Task OnParametersSetAsync()
@@ -191,6 +182,12 @@ namespace MP.SPEC.Pages
currRecord = null;
}
+ protected async Task resetSearch()
+ {
+ SearchVal = "";
+ await ReloadData();
+ }
+
protected async Task resetSel()
{
isNewArt = false;
@@ -204,24 +201,6 @@ namespace MP.SPEC.Pages
currRecord = selRec;
await Task.Delay(1);
}
- protected async Task cloneRecord(AnagArticoliModel selRec)
- {
- isNewArt = true;
- // creo record duplicato...
- AnagArticoliModel newRec = new AnagArticoliModel()
- {
- Azienda = selRec.Azienda,
- CodArticolo = $"clone-{selRec.CodArticolo}",
- DescArticolo = $"CLONE - {selRec.DescArticolo}",
- Disegno = selRec.Disegno,
- Tipo = selRec.Tipo,
- CurrRev = "",
- ProdRev = ""
- };
- currRecord = newRec;
- await Task.Delay(1);
- }
-
protected async Task update(AnagArticoliModel selRec)
{
@@ -245,7 +224,9 @@ namespace MP.SPEC.Pages
#region Private Fields
private string _selAzienda = "*";
+ private SelectArticoliParams currFilter = new SelectArticoliParams();
private AnagArticoliModel? currRecord = null;
+ private bool isNewArt = false;
private List? ListAziende;
private List? ListRecords;
private List? ListTipoArt;
@@ -257,6 +238,7 @@ namespace MP.SPEC.Pages
private int _currPage { get; set; } = 1;
private int _numRecord { get; set; } = 10;
+ private string chkDisabled => isNewArt ? "" : "disabled";
private int currPage
{
@@ -288,6 +270,8 @@ namespace MP.SPEC.Pages
}
}
+ private string searchVal { get; set; } = "";
+
private string selAzienda
{
get => _selAzienda;
@@ -328,8 +312,19 @@ namespace MP.SPEC.Pages
///
private bool ArticoloDelEnabled(string codArt)
{
- bool answ = MDService.ArticoloDelEnabled(codArt);
- return answ;
+ return MDService.ArticoloDelEnabled(codArt);
+ }
+
+ private async Task ReloadBaseData()
+ {
+ await MDService.EnsureArtCacheLoadedAsync(true);
+ selAzienda = await MDService.ConfigTryGetAsync("AZIENDA");
+ if (string.IsNullOrEmpty(selAzienda))
+ {
+ selAzienda = "*";
+ }
+ ListAziende = await MDService.ElencoAziendeAsync();
+ ListTipoArt = await MDService.AnagTipoArtLvAsync();
}
private async Task ReloadData()
diff --git a/MP.SPEC/Resources/ChangeLog.html b/MP.SPEC/Resources/ChangeLog.html
index ff33fc09..7c5344ed 100644
--- a/MP.SPEC/Resources/ChangeLog.html
+++ b/MP.SPEC/Resources/ChangeLog.html
@@ -1,6 +1,6 @@
Modulo MAPOSPEC
- Versione: 8.16.2605.2709
+ Versione: 8.16.2605.2711
Note di rilascio:
-
diff --git a/MP.SPEC/Resources/VersNum.txt b/MP.SPEC/Resources/VersNum.txt
index 0dfa7efa..2d0a2c5c 100644
--- a/MP.SPEC/Resources/VersNum.txt
+++ b/MP.SPEC/Resources/VersNum.txt
@@ -1 +1 @@
-8.16.2605.2709
+8.16.2605.2711
diff --git a/MP.SPEC/Resources/manifest.xml b/MP.SPEC/Resources/manifest.xml
index dcbc0eb7..2b8bd731 100644
--- a/MP.SPEC/Resources/manifest.xml
+++ b/MP.SPEC/Resources/manifest.xml
@@ -1,6 +1,6 @@
-
- 8.16.2605.2709
+ 8.16.2605.2711
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