Fix metodi accesso dati SPEC con assistant
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace MP.Core.DTO
|
||||
{
|
||||
public class CountResultDto
|
||||
{
|
||||
public int NumCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -371,9 +371,9 @@ namespace MP.Data.Controllers
|
||||
/// Elenco valori ammessi x Tipo articoli
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<ListValuesModel> AnagTipoArtLV()
|
||||
public Task<List<ListValuesModel>> AnagTipoArtLvAsync()
|
||||
{
|
||||
return ListValuesFilt("AnagArticoli", "Tipo");
|
||||
return ListValuesFiltAsync("AnagArticoli", "Tipo");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -395,6 +395,35 @@ namespace MP.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conteggio num articoli Async
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<int> ArticoliCountAsync()
|
||||
{
|
||||
using var dbCtx = new MoonProContext(options);
|
||||
var result = await dbCtx
|
||||
.DbSetArticoli
|
||||
.CountAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<int> ArticoliCountUsedAsync()
|
||||
{
|
||||
using var dbCtx = new MoonProContext(options);
|
||||
var result = await dbCtx
|
||||
.DbSetCounter
|
||||
.FromSqlRaw("EXEC stp_ART_CountUsed")
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
return result.FirstOrDefault()?.NumCount ?? 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eliminazione Record
|
||||
/// </summary>
|
||||
@@ -492,6 +521,24 @@ namespace MP.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco tabella Articoli NON IMPIEGATI (da stored stp_ART_getUsed) Async
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<AnagArticoliModel>> ArticoliGetUnusedAsync()
|
||||
{
|
||||
List<AnagArticoliModel> dbResult = new List<AnagArticoliModel>();
|
||||
using (var dbCtx = new MoonProContext(options))
|
||||
{
|
||||
dbResult = await dbCtx
|
||||
.DbSetArticoli
|
||||
.FromSqlRaw("EXEC stp_ART_getNotUsed")
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed)
|
||||
/// </summary>
|
||||
@@ -510,6 +557,24 @@ namespace MP.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco tabella Articoli IMPIEGATI (da stored stp_ART_getUsed) Async
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<AnagArticoliModel>> ArticoliGetUsedAsync()
|
||||
{
|
||||
List<AnagArticoliModel> dbResult = new List<AnagArticoliModel>();
|
||||
using (var dbCtx = new MoonProContext(options))
|
||||
{
|
||||
dbResult = await dbCtx
|
||||
.DbSetArticoli
|
||||
.FromSqlRaw("EXEC stp_ART_getUsed")
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Record
|
||||
/// </summary>
|
||||
@@ -569,6 +634,24 @@ namespace MP.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco da tabella Config Async
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<ConfigModel>> ConfigGetAllAsync()
|
||||
{
|
||||
List<ConfigModel> dbResult = new List<ConfigModel>();
|
||||
using (var dbCtx = new MoonProContext(options))
|
||||
{
|
||||
dbResult = await dbCtx
|
||||
.DbSetConfig
|
||||
.AsNoTracking()
|
||||
.OrderBy(x => x.Chiave)
|
||||
.ToListAsync();
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update record config
|
||||
/// </summary>
|
||||
@@ -1435,6 +1518,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco PODL per composizione KIT non avviati filtrati x articolo, KeyRich (che contiene stato)
|
||||
/// </summary>
|
||||
@@ -1492,6 +1576,7 @@ namespace MP.Data.Controllers
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) - ASYNC
|
||||
/// </summary>
|
||||
@@ -1521,6 +1606,7 @@ namespace MP.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Elenco valori ammessi x tabella/colonna
|
||||
/// </summary>
|
||||
@@ -1540,7 +1626,8 @@ namespace MP.Data.Controllers
|
||||
.ToList();
|
||||
}
|
||||
return dbResult;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Elenco valori ammessi x tabella/colonna Async
|
||||
|
||||
@@ -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<CountResultDto> DbSetCounter { get; set; }
|
||||
|
||||
public virtual DbSet<DbSizeModel> DbSetDbSize { get; set; }
|
||||
public virtual DbSet<AlarmLogModel> DbSetAlarmLog { get; set; }
|
||||
@@ -155,6 +157,10 @@ namespace MP.Data
|
||||
{
|
||||
modelBuilder.HasAnnotation("Relational:Collation", "SQL_Latin1_General_CP1_CI_AS");
|
||||
|
||||
|
||||
modelBuilder.Entity<CountResultDto>().HasNoKey();
|
||||
|
||||
|
||||
modelBuilder.Entity<StatsAnagArticoli>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.CodArticolo);
|
||||
|
||||
+165
-404
@@ -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!;
|
||||
|
||||
/// <summary>
|
||||
/// Dizionario dei tag configurati per IOB
|
||||
/// </summary>
|
||||
public Dictionary<string, List<TagData>> currTagConf { get; set; } = new Dictionary<string, List<TagData>>();
|
||||
|
||||
// Cache per controllo eliminazione articoli (Smart HashSet approach)
|
||||
private HashSet<string> _usedArtIdsCache = new();
|
||||
private HashSet<string> _unusedArtIdsCache = new();
|
||||
private DateTime _artCacheExpiry = DateTime.MinValue;
|
||||
#endregion Public Properties
|
||||
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
@@ -312,41 +310,9 @@ namespace MP.SPEC.Data
|
||||
);
|
||||
}
|
||||
|
||||
#if false
|
||||
public async Task<List<ListValuesModel>> AnagStatiComm()
|
||||
public async Task<List<ListValuesModel>> AnagTipoArtLvAsync()
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("AnagStatiComm");
|
||||
string source = "DB";
|
||||
List<ListValuesModel>? result = new List<ListValuesModel>();
|
||||
// cerco in redis...
|
||||
RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom);
|
||||
if (!string.IsNullOrEmpty($"{rawData}"))
|
||||
{
|
||||
result = JsonConvert.DeserializeObject<List<ListValuesModel>>($"{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<ListValuesModel>();
|
||||
}
|
||||
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<List<ListValuesModel>> AnagTipoArtLV()
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("AnagStatiCommAsync");
|
||||
using var activity = ActivitySource.StartActivity("AnagTipoArtLvAsync");
|
||||
string source = "DB";
|
||||
List<ListValuesModel>? result = new List<ListValuesModel>();
|
||||
// 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<AnagArticoliModel>()
|
||||
);
|
||||
|
||||
|
||||
#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<AnagArticoliModel>()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#if false
|
||||
public async Task<List<AnagArticoliModel>> ArticoliGetByTipoAsync(string tipo, string azienda = "*")
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("ArticoliGetByTipoAsync");
|
||||
List<AnagArticoliModel>? result = new List<AnagArticoliModel>();
|
||||
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<List<AnagArticoliModel>>($"{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<AnagArticoliModel>();
|
||||
}
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Restitusice elenco articoli cercati
|
||||
@@ -517,18 +436,6 @@ namespace MP.SPEC.Data
|
||||
await dbController.ArticoliGetSearchAsync(numRecord, azienda, searchVal)
|
||||
?? new List<AnagArticoliModel>()
|
||||
);
|
||||
|
||||
#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<AnagArticoliModel>()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<T> GetOrFetchAsync<T>(string operationName, string cacheKey, Func<Task<T>> fetchFunc, TimeSpan expiration)
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity(operationName);
|
||||
|
||||
string source;
|
||||
T result;
|
||||
|
||||
// ✅ 1. Tenta MEMORY / DISTRIBUTED (senza factory)
|
||||
var memTry = await _cache.TryGetAsync<T>(cacheKey);
|
||||
|
||||
if (memTry.HasValue)
|
||||
{
|
||||
result = memTry.Value!;
|
||||
source = "MEMORY";
|
||||
}
|
||||
else
|
||||
{
|
||||
bool fromDb = false;
|
||||
|
||||
// ✅ 2. fallback con factory
|
||||
result = await _cache.GetOrSetAsync<T>(
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Cancellazione FusionCache (totale)
|
||||
@@ -666,146 +528,6 @@ namespace MP.SPEC.Data
|
||||
return fatto;
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Helper per gestione cache a 3 livelli: MEMORY, REDIS e DB con opzioni
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="operationName"></param>
|
||||
/// <param name="memKey"></param>
|
||||
/// <param name="redisKey"></param>
|
||||
/// <param name="memoryTtl"></param>
|
||||
/// <param name="dbFactory"></param>
|
||||
/// <param name="serialize"></param>
|
||||
/// <param name="deserialize"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<T> GetOrCreateCachedAsync<T>(string operationName, string memKey, string redisKey, TimeSpan memoryTtl, Func<Task<T>> dbFactory, Func<T, string>? serialize = null, Func<string, T>? 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<string> { memKey },
|
||||
(_, set) => { set.Add(memKey); return set; }
|
||||
);
|
||||
|
||||
// ✅ default serializer (fallback)
|
||||
serialize ??= (obj) => JsonConvert.SerializeObject(obj);
|
||||
deserialize ??= (str) => JsonConvert.DeserializeObject<T>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidazione di una chiave in memoria e Redis
|
||||
/// </summary>
|
||||
/// <param name="memKey"></param>
|
||||
/// <param name="redisKey"></param>
|
||||
/// <returns></returns>
|
||||
public async Task InvalidateCacheAsync(string memKey, string redisKey)
|
||||
{
|
||||
// ✅ memoria
|
||||
_cache.Remove(memKey);
|
||||
|
||||
// ✅ redis
|
||||
await redisDb.KeyDeleteAsync(redisKey);
|
||||
|
||||
LogTrace($"Cache invalidated | {memKey}");
|
||||
}
|
||||
private readonly ConcurrentDictionary<string, HashSet<string>> _memoryKeys = new();
|
||||
/// <summary>
|
||||
/// Invalidazione cache da pattern
|
||||
/// </summary>
|
||||
/// <param name="pattern"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Aggiornamento record selezionato
|
||||
@@ -824,6 +546,7 @@ namespace MP.SPEC.Data
|
||||
return fatto;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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<string>("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<string>? artList = new List<string>();
|
||||
if (!string.IsNullOrEmpty(rawTable))
|
||||
{
|
||||
artList = JsonConvert.DeserializeObject<List<string>>($"{rawTable}");
|
||||
}
|
||||
// rileggo...
|
||||
if (artList == null || artList.Count == 0)
|
||||
{
|
||||
artList = new List<string>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Caricamento asincrono della cache degli articoli (Used/Unused)
|
||||
/// </summary>
|
||||
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<string>(usedList.Select(x => x.CodArticolo));
|
||||
_unusedArtIdsCache.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
var unusedList = await dbController.ArticoliGetUnusedAsync();
|
||||
_unusedArtIdsCache = new HashSet<string>(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
|
||||
/// <summary>
|
||||
/// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su
|
||||
/// tab veto da DB
|
||||
/// </summary>
|
||||
/// <param name="CodArt"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> ArticoloDelEnabledAsync(object CodArt)
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("ArticoloDelEnabledAsync");
|
||||
string codArticolo = $"{CodArt}";
|
||||
|
||||
int cacheCheckArtUsato = 1;
|
||||
int.TryParse(_configuration.GetValue<string>("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<string>(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<string>(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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restitusice elenco aziende
|
||||
/// Restituisce elenco aziende
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<List<AnagGruppiModel>> 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<AnagGruppiModel>()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<PODLExpModel>()
|
||||
);
|
||||
|
||||
#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<PODLExpModel>()
|
||||
);
|
||||
#endif
|
||||
}
|
||||
#if false
|
||||
public async Task<List<PODLExpModel>> POdlToKitListGetFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
using var activity = ActivitySource.StartActivity("POdlToKitListGetFiltAsync");
|
||||
List<PODLExpModel>? result = new List<PODLExpModel>();
|
||||
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<List<PODLExpModel>>($"{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<PODLExpModel>();
|
||||
}
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Chiamata salvataggio ricetta + refresh REDIS
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>MP.SPEC</RootNamespace>
|
||||
<Version>8.16.2605.2709</Version>
|
||||
<Version>8.16.2605.2711</Version>
|
||||
<UserSecretsId>1800a78a-6ff1-40f9-b490-87fb8bfc1394</UserSecretsId>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Crea nuovo record e va in editing...
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Crea nuovo record e va in editing...
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eliminazione record selezionato (previa conferma)
|
||||
/// </summary>
|
||||
@@ -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<AnagGruppiModel>? ListAziende;
|
||||
private List<AnagArticoliModel>? ListRecords;
|
||||
private List<ListValuesModel>? 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
|
||||
/// <returns></returns>
|
||||
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()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>Modulo MAPOSPEC </i>
|
||||
<h4>Versione: 8.16.2605.2709</h4>
|
||||
<h4>Versione: 8.16.2605.2711</h4>
|
||||
<br /> Note di rilascio:
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
8.16.2605.2709
|
||||
8.16.2605.2711
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>8.16.2605.2709</version>
|
||||
<version>8.16.2605.2711</version>
|
||||
<url>https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/MP.SPEC.zip</url>
|
||||
<changelog>https://nexus.steamware.net/repository/SWS/MP-SPEC/stable/LAST/ChangeLog.html</changelog>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
Reference in New Issue
Block a user