Merge branch 'release/UpdateProd_01'

This commit is contained in:
Samuele E. Locatelli
2026-06-17 18:36:40 +02:00
71 changed files with 1034 additions and 247 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP_TAB3</RootNamespace>
</PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/MP-TAB3.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
@@ -197,11 +197,12 @@ namespace MP.Data.Repository.Production
var DateFrom = new SqlParameter("@DtInizio", startDate);
var DateTo = new SqlParameter("@DtFine", endDate);
var pFlagAtt = new SqlParameter("@flgAttive", flagAttive);
var pFlagKit = new SqlParameter("@flgPodlKit", true);
var pFlagKChild = new SqlParameter("@flgPodChild", flagKitChild);
return await dbCtx
.DbSetPODLExp
.FromSqlRaw("EXEC stp_PODL_getByFiltSpecKit @Lanciato, @KeyRich, @CodGruppo, @IdxMacchina, @DtInizio, @DtFine, @flgAttive, @flgPodChild", Lanc, KeyRich, CodGrp, IdxMacc, DateFrom, DateTo, pFlagAtt, pFlagKChild)
.FromSqlRaw("EXEC stp_PODL_getByFiltSpecKit @Lanciato, @KeyRich, @CodGruppo, @IdxMacchina, @DtInizio, @DtFine, @flgAttive, @flgPodlKit, @flgPodChild", Lanc, KeyRich, CodGrp, IdxMacc, DateFrom, DateTo, pFlagAtt, pFlagKit, pFlagKChild)
.AsNoTracking()
.ToListAsync();
}
+2 -2
View File
@@ -368,8 +368,8 @@ namespace MP.Data.Services
var cacheOptions = new FusionCacheEntryOptions()
.SetDuration(expiration)
.SetFailSafe(true);
// cache in RAM per 1/3 del tempo x risparmiare risorse
cacheOptions.MemoryCacheDuration = expiration / 3;
// cache in RAM per 1/2 del tempo x risparmiare risorse
cacheOptions.MemoryCacheDuration = expiration / 2;
var final = await _cache.GetOrSetAsync<T>(
cacheKey,
+95 -60
View File
@@ -91,6 +91,19 @@ namespace MP.Data.Services.IOC
/// <inheritdoc />
public async Task<string> GetCurrOdlAsync(string idxMacchina)
{
string cKey = $"{MP.Data.Utils.redisOdlCurrByMac}:{idxMacchina}";
return await GetOrFetchAsync(
operationName: "GetCurrOdlAsync",
cacheKey: cKey,
fetchFunc: async () =>
{
return await GetCurrOdlByProdAsync(idxMacchina);
},
expiration: GetRandTOut(redisLongTimeCache),
tagList: [idxMacchina]
);
#if false
string result = "";
string currKey = $"{MP.Data.Utils.redisOdlCurrByMac}:{idxMacchina}";
// cerco in redis dato valOut sel macchina...
@@ -105,6 +118,7 @@ namespace MP.Data.Services.IOC
_redisDb.StringSet(currKey, result, GetRandTOut(redisShortTimeCache));
}
return result;
#endif
}
/// <inheritdoc />
@@ -117,39 +131,48 @@ namespace MP.Data.Services.IOC
/// <inheritdoc />
public async Task<bool> IobInsEnabAsync(string idxMacchina)
{
#if false
string cacheKey = $"IOC_IobInsEnab_{idxMacchina}";
return await GetOrFetchAsync(cacheKey, async () =>
{
var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
string? val = await _redisDb.HashGetAsync(rKey, "insEnabled");
if (val == null)
string cKey = $"IOC_IobInsEnab_{idxMacchina}";
return await GetOrFetchAsync(
operationName: "StatoProdMacchinaAsync",
cacheKey: cKey,
fetchFunc: async () =>
{
var data = await ResetDatiMacchinaAsync(idxMacchina);
data.TryGetValue("insEnabled", out val);
}
var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
var val = await _redisDb.HashGetAsync(rKey, "insEnabled");
return val != null && (val == "1" || val.ToLower() == "true");
}, TimeSpan.FromSeconds(5));
if (!val.HasValue)
{
var data = await ResetDatiMacchinaAsync(idxMacchina);
// 2. Uso del pattern matching per evitare allocazioni e passaggi intermedi
return data != null
&& data.TryGetValue("insEnabled", out string? sVal)
&& IsStringTrue(sVal);
}
// 3. Conversione efficiente da RedisValue a string (evita l'interpolazione $"{val}")
string? sRedisVal = val;
return IsStringTrue(sRedisVal);
#if false
var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
var val = await _redisDb.HashGetAsync(rKey, "insEnabled");
string sVal = "";
if (!val.HasValue)
{
var data = await ResetDatiMacchinaAsync(idxMacchina);
data.TryGetValue("insEnabled", out sVal);
}
else
{
sVal = $"{val}";
}
return !string.IsNullOrEmpty(sVal) && (sVal == "1" || sVal.ToLower() == "true");
#endif
bool answ = false;
// ORA recupero da memoria redis...
var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
RedisValue rawData = await _redisDb.HashGetAsync(rKey, (RedisValue)"insEnabled");
// se è vuoto... leggo da DB e popolo!
if (!rawData.HasValue)
{
await ResetDatiMacchinaAsync(idxMacchina);
// riprovo
rawData = await _redisDb.HashGetAsync(rKey, (RedisValue)"insEnabled");
}
// provo conversione
bool.TryParse($"{rawData}", out answ);
return answ;
},
expiration: GetRandTOut(60),
//expiration: GetRandTOut(30),
tagList: ["IOC_IobInsEnab", cKey, idxMacchina]
);
}
/// <inheritdoc />
@@ -201,7 +224,6 @@ namespace MP.Data.Services.IOC
// gestisce i casi DB/REDIS secondo necessità
await CheckMicroStatoAsync(idxMacchina, valore, dataOraEvento, contatore, datiMacc);
}
await ClearFusionCache(idxMacchina);
// forzo RESET dati macchina...
await ResetDatiMacchinaAsync(idxMacchina);
// registro in risposta che è andato tutto bene...
@@ -301,30 +323,14 @@ namespace MP.Data.Services.IOC
#endregion Protected Fields
#if false
/// <summary>
/// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec
/// </summary>
/// <param name="stdMinutes"></param>
/// <returns></returns>
protected TimeSpan GetRandTOut(double stdMinutes)
{
double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60;
return TimeSpan.FromMinutes(rndValue);
}
#endif
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
#if false
private readonly IFusionCache _cache;
#endif
private readonly string _className;
private readonly IIocRepository _repo;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
@@ -339,14 +345,38 @@ namespace MP.Data.Services.IOC
#endregion Private Fields
#region Private Methods
private static bool IsStringTrue(string? value)
{
if (string.IsNullOrEmpty(value)) return false;
// Evita ToLower() che alloca una nuova stringa in memoria ad ogni chiamata
return value == "1"
|| value.Equals("true", StringComparison.OrdinalIgnoreCase);
}
#if false
/// <summary>
/// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec
/// </summary>
/// <param name="stdMinutes"></param>
/// <returns></returns>
protected TimeSpan GetRandTOut(double stdMinutes)
{
double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60;
return TimeSpan.FromMinutes(rndValue);
}
#endif
#if false
private readonly IFusionCache _cache;
#endif
#if false
private int redisLongTimeCache = 5;
private int redisShortTimeCache = 2;
#endif
#region Private Methods
/// <summary>
/// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE
/// </summary>
@@ -534,14 +564,15 @@ namespace MP.Data.Services.IOC
// se è vuoto... leggo da DB e popolo!
if (!rawData.HasValue)
{
await ResetDatiMacchinaAsync(idxMacchina);
// riprovo
rawData = await _redisDb.HashGetAsync(rKey, (RedisValue)"sLogEnabled");
}
var data = await ResetDatiMacchinaAsync(idxMacchina);
// provo conversione
bool.TryParse($"{rawData}", out answ);
return answ;
// 2. Uso del pattern matching per evitare allocazioni e passaggi intermedi
return data != null
&& data.TryGetValue("sLogEnabled", out string? sVal)
&& IsStringTrue(sVal);
}
string? sRedisVal = rawData;
return IsStringTrue(sRedisVal);
}
/// <summary>
@@ -886,6 +917,8 @@ namespace MP.Data.Services.IOC
// Eseguiamo tutto in un unico viaggio verso Redis
bool success = await transaction.ExecuteAsync();
await ForceFlushFusionCacheAsync(idxMacc);
return result;
}
@@ -1040,13 +1073,13 @@ namespace MP.Data.Services.IOC
/// <returns></returns>
private async Task<StatoProdModel> StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false)
{
string cKey = $"IOC_StatoProd_{idxMacchina}";
var stdTTL = TimeSpan.FromSeconds(30);
string cKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}";
return await GetOrFetchAsync(
operationName: "StatoProdMacchinaAsync",
cacheKey: cKey,
fetchFunc: async () =>
{
#if false
StatoProdModel? result = new StatoProdModel();
// cerco in _redisConn...
string currKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}";
@@ -1067,8 +1100,10 @@ namespace MP.Data.Services.IOC
result = new StatoProdModel();
}
return result;
#endif
return await _repo.StatoProdMacchinaAsync(idxMacchina, dtReq);
},
expiration: stdTTL,
expiration: GetRandTOut(redisLongTimeCache),
tagList: ["IOC_StatoProd", cKey, idxMacchina]
);
}
@@ -63,6 +63,13 @@ namespace MP.Data.Services.Utils
/// <returns></returns>
Task ResetCache();
/// <summary>
/// Forza il reset della cache REDIS x il pattern richiesto
/// </summary>
/// <param name="pattern"></param>
/// <returns></returns>
Task ResetCache(string pattern);
/// <summary>
/// Inserisce o aggiorna in batch le statistiche di dettaglio nel database.
/// Opzionalmente elimina i record precedenti nel periodo specificato.
+9 -1
View File
@@ -22,7 +22,7 @@ namespace MP.Data.Services.Utils
IConnectionMultiplexer redis,
IFusionCache cache,
IStatsDetailRepository repo
) : base(config,cache, redis)
) : base(config, cache, redis)
{
_className = "StatsDetail";
_repo = repo;
@@ -134,6 +134,14 @@ namespace MP.Data.Services.Utils
await ClearCacheAsync($"{_redisBaseKey}:*");
}
/// <inheritdoc />
public async Task ResetCache(string pattern)
{
// tolgo eventuali ":" finali
pattern = pattern.EndsWith(":") ? pattern.Substring(0, pattern.Length - 1) : pattern;
await ClearCacheAsync($"{pattern}:*");
}
/// <inheritdoc />
public async Task<int> UpsertManyAsync(List<StatsDetailModel> listRecords, bool removeOld)
{
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.INVE</RootNamespace>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MAPOINVE </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-INVE/stable/LAST/MP.INVE.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-INVE/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
@@ -14,6 +14,7 @@ namespace MP.IOC.Components.Pages
protected override async Task OnInitializedAsync()
{
MpIoNS = Config.GetValue<string>("ServerConf:MpIoNS") ?? "MP";
await ReloadData();
}
@@ -105,6 +106,9 @@ namespace MP.IOC.Components.Pages
[Inject]
private IIocService IocService { get; set; } = null!;
[Inject]
private IConfiguration Config { get; set; } = null!;
#endregion Private Properties
#region Private Methods
@@ -114,8 +118,14 @@ namespace MP.IOC.Components.Pages
return currStatSel != null && currStatSel.Title == curKey ? "active" : "";
}
private string MpIoNS = "";
private async Task DoForceReload()
{
// svuoto cache dati IoNS...
var dtMaccKey = Utils.RedKeyDatiMacc("", MpIoNS);
await SDetService.ResetCache(dtMaccKey);
// resto delle cache
await SDetService.ResetCache();
await IocService.ClearFusionCache();
DoReset();
+38 -17
View File
@@ -75,41 +75,61 @@ namespace MP.IOC.Controllers
return Ok(answ);
}
/// <summary>
/// GET: IOB/
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Alive()
{
return Ok("OK"); // Restituisce Status 200
}
#if false
/// <summary>
/// GET: IOB/enabled/SIMUL_03
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("enabled/{id}")]
public async Task<IActionResult> Enabled(string id)
public async Task<string> Enabled(string id)
{
if (string.IsNullOrEmpty(id)) return BadRequest("Missing ID");
if (string.IsNullOrEmpty(id))
{
Response.StatusCode = StatusCodes.Status400BadRequest;
return "Missing ID";
}
try
{
// Il metodo ora restituisce direttamente il booleano logico
bool isEnabled = await IOCService.IobInsEnabAsync(id);
return isEnabled
? Ok("OK")
: UnprocessableEntity("NO");
// Eliminazione delle allocazioni di stringhe e oggetti inutili
if (!isEnabled)
{
Response.StatusCode = StatusCodes.Status422UnprocessableEntity;
return "NO";
}
// Status 200 di default, scrive direttamente sul body della response
return "OK";
}
catch (Exception ex)
{
Log.Error(ex, "Errore durante la verifica abilitazione per {Id}", id);
return StatusCode(500, "Errore interno del server");
Response.StatusCode = StatusCodes.Status500InternalServerError;
return "Errore interno del server";
}
//if (string.IsNullOrEmpty(id)) return BadRequest("Missing ID");
//try
//{
// // Il metodo ora restituisce direttamente il booleano logico
// bool isEnabled = await IOCService.IobInsEnabAsync(id);
// return isEnabled
// ? Ok("OK")
// : UnprocessableEntity("NO");
//}
//catch (Exception ex)
//{
// Log.Error(ex, "Errore durante la verifica abilitazione per {Id}", id);
// return StatusCode(500, "Errore interno del server");
//}
}
#endif
/// <summary>
/// Processa una chiamata POST per l'invio di un array Json di oggetti input (EVENTI)
@@ -1086,7 +1106,7 @@ namespace MP.IOC.Controllers
try
{
answ = await DService.saveCaricoPezzi(id, qty);
await IOCService.ClearFusionCache();
await IOCService.ClearFusionCache(id);
return Ok(answ);
}
catch (Exception exc)
@@ -1449,6 +1469,7 @@ namespace MP.IOC.Controllers
#endregion Public Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private IIocService IOCService;
+1 -1
View File
@@ -1224,7 +1224,7 @@ namespace MP.IOC.Data
cacheKey: $"{Utils.redisOdlList}:Current:{IdxMacchina}",
expiration: TimeSpan.FromSeconds(redisShortTimeCache),
fetchFunc: async () => (await IocDbController.OdlCurrByMaccAsync(IdxMacchina)) ?? new ODLExpModel(),
tagList: [Utils.redisOdlList]
tagList: [Utils.redisOdlList, IdxMacchina]
);
}
+114
View File
@@ -0,0 +1,114 @@
using MP.Data.Services.IOC;
using NLog;
namespace MP.IOC.Endpoints
{
public static class IobEndpoints
{
#region Public Methods
public static void MapIobHighPerformanceEndpoints(this IEndpointRouteBuilder app)
{
// 1. Root di test base: api/IOB e api/IOB/alive
app.MapGet("api/IOB", () => "OK");
app.MapGet("api/IOB/alive", () => "OK");
// 2. Il metodo Enabled ad alto traffico: api/IOB/enabled/{id}
app.MapGet("api/IOB/enabled/{id}", async (string id, IIocService iocService, HttpContext context) =>
{
if (string.IsNullOrEmpty(id))
return Results.BadRequest("Missing ID");
try
{
bool isEnabled = await iocService.IobInsEnabAsync(id);
return isEnabled
? Results.Ok("OK")
: Results.UnprocessableEntity("NO");
}
catch (Exception ex)
{
Log.Error(ex, "Errore durante la verifica abilitazione per {Id}", id);
return Results.StatusCode(500);
}
});
#if false
// 3. Metodo: SetCounter (api/IOB/setCounter/{id}?counter=10)
// Nota: 'counter' viene letto automaticamente dalla Query String grazie al Model Binding automatico
app.MapGet("api/IOB/setCounter/{id}", async (string id, string counter, IIocService iocService, HttpContext context) =>
{
if (string.IsNullOrEmpty(id))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return "Missing ID";
}
// Ottimizzazione: esegui il Replace solo se il carattere è effettivamente presente
if (id.Contains('|'))
{
id = id.Replace('|', '#');
}
// Se il log di Debug è disattivato in produzione, eviti l'allocazione della stringa interpolata
if (Log.IsDebugEnabled)
{
Log.Debug($"Salvataggio counter | id: {id} | pzCount: {counter}");
}
try
{
string answ = await iocService.SaveCounterAsync(id, counter);
return answ; // Ritorna direttamente la stringa (Status 200 di default, zero allocazioni di ObjectResult)
}
catch (Exception exc)
{
// Usiamo la stringa formattata standard di NLog per evitare Environment.NewLine manuale
Log.Error(exc, "Errore in SetCounter per macchina {MachineId}", id);
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return "NO";
}
});
// 4. Metodo: GetCurrODL (api/IOB/getCurrODL/{id})
app.MapGet("api/IOB/getCurrODL/{id}", async (string id, IIocService iocService, HttpContext context) =>
{
if (string.IsNullOrEmpty(id))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return "Missing ID";
}
if (id.Contains('|'))
{
id = id.Replace('|', '#');
}
try
{
var odl = await iocService.GetCurrOdlAsync(id);
// Ottimizzazione: Se 'odl' è già una stringa, ritornala direttamente.
// Se è un oggetto o un numero, il C# gestirà l'estrazione. Evitiamo $"{odl}" che alloca memoria inutilmente.
return odl?.ToString() ?? string.Empty;
}
catch (Exception exc)
{
Log.Error(exc, "Errore GetCurrODL | macchina {MachineId}", id);
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return "NO";
}
});
#endif
}
#endregion Public Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1511</Version>
</PropertyGroup>
<ItemGroup>
+6 -1
View File
@@ -6,6 +6,7 @@ using MP.Core.Conf;
using MP.Data;
using MP.IOC.Components;
using MP.IOC.Data;
using MP.IOC.Endpoints;
using MP.IOC.Services;
using NLog;
using NLog.Web;
@@ -170,9 +171,13 @@ app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAntiforgery();
// Mappatura delle API
// Mappatura MinimalApi da file ext x metodi high perf
app.MapIobHighPerformanceEndpoints();
// Mappatura delle API "standard"
app.MapControllers();
// Mappatura della Dashboard Blazor
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
+1 -1
View File
@@ -14,7 +14,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7050;http://localhost:5264",
"applicationUrl": "https://*:7050;http://*:5264",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MP-IOC </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1511</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1511
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1511</version>
<url>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+3 -2
View File
@@ -6,7 +6,8 @@
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning",
"Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware": "None",
"Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware": "None"
"Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware": "None",
"ZiggyCreatures.Caching.Fusion": "Warning"
}
},
"NLog": {
@@ -24,7 +25,7 @@
"logfile": {
"type": "File",
"fileName": "${basedir}/logs/${shortdate}.log",
"keepFileOpen": false,
"keepFileOpen": true,
"archiveEvery": "Day",
"archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log",
"archiveNumbering": "DateAndSequence",
+377
View File
@@ -0,0 +1,377 @@
# Piano Migrazione MpDataService → IIocService
> **Obiettivo**: Standardizzare l'accesso dati su `IIocService` (con FusionCache) e pensionare `MpDataService` come service layer principale di `MP.IOC`.
>
> **Strategia**: Ogni metodo `MpDataService` usato dal controller viene migrato in `IocService`. La cache FusionCache avvolge le chiamate Redis/DB con le stesse durate e tag esistenti. I controller usano solo `IIocService`.
---
## Architettura Target
```
IOBController
└── IIocService (scoped)
├── FusionCache (L1 Memory + L2 Redis + L3 DB via tags)
├── StackExchange.Redis (IDatabase) ← sorgente "live" per dati esterni
└── IIocRepository (scoped) ← DB access
```
**Regola chiave**: Redis diretto (scritto da IOB-WIN, altri programmi) resta accessibile via `IDatabase _redisDb` in `IocService`. FusionCache ha una durata equivalente per validare/invalidare coerentemente.
---
## Stato Attuale
### Minimal APIs — OK
I 3 endpoint in `IobEndpoints.cs` sono stati puliti e ora contengono solo:
- `GET api/IOB` / `api/IOB/alive` — health check
- `GET api/IOB/enabled/{id}` — usa `await iocService.IobInsEnabAsync(id)`
I metodi `setCounter` e `getCurrODL` sono stati rimossi (troppo semplici, nel controller bastano).
### Controller — Misto
| Usa DService | Usa IOCService |
|---|---|
| ~37 metodi | 5 metodi (`getCounterTCRec`, `getCurrODL`, `setCounter`, `input`, `getTask2Exe` in parte) |
### MpDataService
- Registrato come **singleton** in `Program.cs:106`
- Contiene ~80 metodi, molti con accesso Redis diretto senza FusionCache
- `IocService` è **scoped** e usa già FusionCache
### IocService
- Eredita da `MP.Data.Services.BaseServ` (contiene `GetOrFetchAsync<T>`)
- 12 metodi pubblici nell'interface
- Usa FusionCache per i metodi più critici (`IobInsEnabAsync`, `GetCurrOdlAsync`, `StatoProdMacchinaAsync`)
---
## Piano di Migrazione — Fasi
### Fase 0: Preparazione (`IocService` base)
**File**: `IIocService.cs`, `IocService.cs`
0.1. `IocService` deve ereditare da `MP.Data.Services.BaseServ` (attuale `IocService` non lo fa) — per accedere a `GetOrFetchAsync<T>` condiviso
0.2. Aggiunta di `IFusionCache _cache` nel constructor (già presente in `BaseServ`)
0.3. Verifica: `redisLongTimeCache` e `redisShortTimeCache` usati da `IocService` per le durate FusionCache
---
### Fase 1: Metodi KeepAlive + Task Management
**Priorità**: Alta — usati da quasi tutti gli altri metodi come precursori.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `ScriviKeepAliveAsync` | ✓ già duplicato in `IocService` | Unificare, aggiungere FusionCache per la check key |
| `AddOptPar4Machine` | → `AddOptPar4Machine` | Redis hash, cache per tag machine |
| `AddTask4Machine` | → `AddTask4Machine` | Redis hash + backup, cache per tag machine |
| `mOptParMacchina` | → `GetOptParMacchina` | Redis hash read |
| `mTaskMacchina` / `mTaskMacchinaAsync` | → `GetTaskMacchina` | Redis hash read |
| `RemTask2ExeMacchinaAsync` | → `RemoveTask2ExeAsync` | Redis hash write + delete |
| `mSavedTaskMacchina` | → `GetSavedTasksMacchina` | Redis hash read |
| `AddCheckTask4Machine` | → `AddCheckTask4Machine` | Redis hash read/write |
| `AddTask4MacListAsync` | → `AddTaskListAsync` | Redis hash batch write |
**Durata cache**: `redisShortTimeCache` (2 min + jitter) per task hash (dati volatile)
**Tag cache**: `Task:{idxMacchina}`, `OptPar:{idxMacchina}`, `SavedTask:{idxMacchina}`
---
### Fase 2: Metodi Process (INPUT, FLOG, ULOG)
**Priorità**: Altissima — il cuore del sistema, alto traffico.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `ProcessInputAsync` | → `ProcessInputAsync` | GIÀ in `IocService` ✓ |
| `ProcessFluxLogAsync` | → `ProcessFluxLogAsync` | DB insert + Redis write |
| `ProcessUserLogAsync` | → `ProcessUserLogAsync` | DB insert multi-flux |
| `CheckMicroStatoAsync` | → `CheckMicroStatoAsync` | DB upsert + transizione |
| `scriviRigaEventoAsync` | → `WriteEventRecordAsync` | DB insert |
| `saveSigLogAsync` | → `SaveSignalLogAsync` | DB insert |
| `GetSrvDtEvent` | → `GetServerDateTime` | Pure logic — no cache |
| `ParseEventTime` | → `ParseEventTime` | GIÀ in `IocService` ✓ |
| `preProcInput` | → `PreProcessInput` | Pure logic — no cache |
| `ValidateInputParams` / `ValidateinputParams` | → `ValidateInputParams` | Pure logic — no cache |
**Durata cache**: Nessuna (scrittura). Tag invalidazione per `MachineData:{idxMacchina}` sui dati che influenzano.
**Tag cache**: `Input:{idxMacchina}`, `FluxLog:{idxMacchina}`, `UserLog:{idxMacchina}`, `MicroStato:{idxMacchina}`
---
### Fase 3: Metodi ODL/PODL
**Priorità**: Alta — letture frequenti durante produzione.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `GetCurrOdlAsync` | → `GetCurrOdlAsync` | GIÀ in `IocService` ✓ |
| `GetLastOdlAsync` | → `GetLastOdlAsync` | Usare `GetOrFetchAsync<T>` |
| `OdlCurrByMaccAsync` | → `GetCurrentOdlAsync` | Usare `GetOrFetchAsync<T>` |
| `AutoStartOdlAsync` | → `AutoStartOdlAsync` | Complesso — Redis + DB + eventi |
| `FixDailyDossierAsync` | → `FixDailyDossierAsync` | Chiamata batch |
| `OdlAutoDayGenAsync` | → `GenerateDailyOdlAsync` | DB stored procedure |
| `OdlAutoDayGenFullAsync` | → `GenerateDailyOdlFullAsync` | DB stored procedure |
| `POdlGetByKey` | → `GetPodlByKeyAsync` | Usare `GetOrFetchAsync<T>` |
| `POdlGetByMaccArtAsync` | → `GetPodlByMachineAsync` | Usare `GetOrFetchAsync<T>` |
**Durata cache**: `redisLongTimeCache` (5 min) per ODL/PODL
**Tag cache**: `Odl:{idxMacchina}`, `Podl:{idxMacchina}`
---
### Fase 4: Metodi Contapezzi
**Priorità**: Alta — traffico continuo.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `pzCounter` | → `GetPzCounterAsync` | Redis StringGet (lettura live) |
| `PzCounterTcAsync` | → `PzCounterTcAsync` | GIÀ in `IocService` ✓ |
| `saveCaricoPezzi` | → `SavePezziCaricoAsync` | Redis StringSet + DB insert |
| `SaveCounterAsync` | → `SaveCounterAsync` | GIÀ in `IocService` ✓ |
**Durata cache**: `redisShortTimeCache` per counter (dati volatile scritti da IOB esterni)
**Tag cache**: `PzCounter:{idxMacchina}`
---
### Fase 5: Metodi Parametri Macchina
**Priorità**: Alta — metodo più chiamato (ogni richiesta legge i parametri).
| MpDataService | → IocService | NOTE |
|---|---|---|
| `MachineParamListAsync` | → `GetMachineParamsAsync` | **CRITICO** — Redis read, aggiungere FusionCache |
| `MachineParamListPendingWriteAsync` | → `GetPendingWriteParamsAsync` | Wrapper su GetMachineParamsAsync |
| `MachineParamListSetAsync` | → `SetMachineParamsAsync` | Redis StringSet |
| `MachineParamUpsertAsync` | → `UpsertMachineParamsAsync` | Redis read + write batch |
| `UpsertCurrObjItemsAsync` | → `UpsertMachineParamsAsync` | Duplicate — unificare con UpsertMachineParamsAsync |
**Durata cache**: `redisShortTimeCache` (2 min) per parametri macchina — dati scritti da IOB esterni ogni pochi secondi
**Tag cache**: `MachineParams:{idxMacchina}`
---
### Fase 6: Metodi Anagrafica e Lookup
**Priorità**: Media — dati relativamente statici.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `AnagStatiGetAllAsync` | → `GetAllStatiAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `ArticoliGetLastByMaccAsync` | → `GetLastArticlesByMachineAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `DossierLastByMachAsync` | → `GetLastDossiersByMachineAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `ListValuesFilt` | → `GetListValuesFilteredAsync` | Redis read (duplicare logica FusionCache) |
| `DecNumArtGetFiltAsync` | → `GetDecNumArticoliAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `MacchineGetFilt` | → `GetMachinesFilteredAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `Macchine2SlaveGetAllAsync` | → `GetMachineSlaveMapAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `MacchineRecipeArchive` | → `GetMachineRecipeArchivePathAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `ConfigGetAllAsync` | → `GetAllConfigAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `ConfFluxMach` | → `GetConfFluxByMachineAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
| `GetArtNumAsync` | → `GetArtNumByCodeAsync` | Interno — wrapper a DecNumArtGetFiltAsync |
| `MseGetAllAsync` | → `GetMachineStateExplAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
**Durata cache**: `redisLongTimeCache` (5 min + jitter)
**Tag cache**: `AnagStati`, `Articoli:{idxMacchina}`, `Dossier:{idxMacchina}`, `Config`, `MSE`, `Macchine`
---
### Fase 7: Metodi Configurazione IOB + Redis Diretto
**Priorità**: Bassa — configurazione manuale, basso traffico.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `SetIobMemMap` | → `SetIobMemMapAsync` | Redis StringSet |
| `SetIobConfYamlAsync` | → `SetIobConfYamlAsync` | Redis StringSet |
| `SaveMachineIobConf` | → `SaveMachineIobConfAsync` | Redis HashSet |
| `SaveMachine2Iob` | → `SaveMachine2IobAsync` | Redis StringSet |
| `SaveDataItemsAsync` | → `SaveDataItemsAsync` | Mongo o DB |
| `GetCurrObjItems` | → `GetMachineParamsAsync` | **Duplicate** — usare Fase 5 |
---
### Fase 8: Metodi Logging e Monitoring
**Priorità**: Bassa — writing-only, nessun cache.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `AlarmInsertAsync` | → `InsertAlarmLogAsync` | DB insert |
| `RemRebootLogAddAsync` | → `AddRebootLogAsync` | DB insert + Redis flush |
| `WriteEventRecordAsync` (scriviRigaEventoAsync) | → `WriteEventRecordAsync` | DB insert |
| `FluxLogSaveSnapshotAsync` | → `SaveFluxSnapshotAsync` | DB stored procedure |
| `DossierLastByMachResetAsync` | → `ResetDossierCacheAsync` | Redis delete |
| `FluxLogFirstByMachAsync` | → `GetFirstFluxLogsAsync` | GIÀ usa `GetOrFetchAsync<T>` in MpDataService |
---
### Fase 9: Metodi Helper e Internal
**Priorità**: Bassa — usati internamente.
| MpDataService | → IocService | NOTE |
|---|---|---|
| `mDatiMacchineAsync` | → `GetMachineDataAsync` | Redis HashGetAll + reset |
| `ResetDatiMacchinaAsync` | → `ResetMachineDataAsync` | Redis transaction batch |
| `StateMachInByKeyAsync` | → `GetStateMachineIngressiAsync` | Redis Hash |
| `resetMSMIAsync` | → `ResetMultiSMIAsync` | Redis Hash write |
| `resetSMIAsync` | → `ResetSMIAsync` | Redis Hash write |
| `ValoreSmiAsync` | → `GetSMIValueAsync` | Redis HashField get |
| `isMulti` | → `IsMultiMachine` | Pure logic |
| `ListMasterAsync` / `ListSlaveAsync` | → `GetMasterListAsync` / `GetSlaveListAsync` | Redis cache |
| `getRandTOut` | → `GetRandomTimeout` | Shared in BaseServ |
| `RedisCountKey`, `RedisDelKey`, `RedisFlushPatternAsync` | → `FlushKeysByPatternAsync` | Admin only |
| `RedisGetHashDictAsync`, `RedisSetHashDictAsync` | → `RedisGet/HashDictAsync` | Wrapper utilities |
---
## Fase 10: Pulizia Controller
### 10.1. Sostituire `DService` con `IOCService` in `IOBController`
Cambiare constructor e tutte le chiamate:
```csharp
// PRIMA
public IOBController(IConfiguration configuration, MpDataService DataService, IIocService IService)
{
DService = DataService;
IOCService = IService;
}
// DOPO
public IOBController(IIocService service)
{
_service = service;
}
```
Poi sostituire ogni `DService.Xxx(...)` con `_service.Xxx(...)`.
### 10.2. Rimuovere `MpDataService` dal controller
- Rimuovere la property `DService`
- Rimuovere il parametro dal constructor
- Unificare le chiamate miste (alcuni metodi fanno `DService.ScriviKeepAliveAsync` + `IOCService.Xxx`)
### 10.3. Spostare i private method in `IocService`
I metodi `processEvListJsonAsync`, `processFLogJsonAsync`, `processULogJsonAsync` contengono logica di business:
- `processEvListJsonAsync``ProcessEventListJsonAsync` in `IocService`
- `processFLogJsonAsync``ProcessFluxLogJsonAsync` in `IocService` (complesso — usa `MachineParamListAsync` + `UpsertCurrObjItemsAsync`)
- `processULogJsonAsync``ProcessUserLogJsonAsync` in `IocService`
### 10.4. Rimuovere `MpDataService` da `Program.cs`
```csharp
// RIMUOVERE:
// builder.Services.AddSingleton<MpDataService>();
```
---
## Riepilogo Metodi da Migrire
| Categoria | Metodi da aggiungere a IocService |
|---|---|
| KeepAlive + Task | ~9 metodi |
| Process (INPUT/FLOG/ULOG) | ~9 metodi |
| ODL/PODL | ~8 metodi |
| Contapezzi | ~3 metodi |
| Parametri Macchina | ~4 metodi |
| Anagrafica/Lookup | ~11 metodi |
| Config IOB | ~5 metodi |
| Logging | ~6 metodi |
| Helper/Internal | ~10 metodi |
| **TOTALE** | **~65 metodi** |
Di questi, ~15 sono già in `IocService` o `BaseServ`. Quindi **~50 nuovi metodi** da implementare.
---
## Criteri di Validazione
1. **Build**`./build_all_par.ps1 --agent` senza errori
2. **Functional parity** — ogni endpoint del controller restituisce lo stesso output di prima
3. **Cache behavior** — i metodi migrati registrano `data.source` come TRACE/DEBUG
4. **No `MpDataService` references** nel controller
5. **`Program.cs`** — `MpDataService` rimosso dalla DI registration
---
## Sequenza di Sviluppo Consigliata
```
Fase 1 → Fase 4: Blocchi "hot" (keepalive, task, counter)
Fase 5: Parametri macchina (blocco più critico per perf)
Fase 2: Process (INPUT/FLOG/ULOG - cuore dell'app)
Fase 3: ODL/PODL
Fase 6: Anagrafica (dati statici, meno rischioso)
Fase 7-9: Config + Logging + Helper
Fase 10: Cleanup finale
```
Ogni fase va build-ata e testata prima di procedere alla successiva.
---
## Note Tecniche
### Caching su Redis diretto
Per i dati scritti da programmi esterni (IOB-WIN, ecc.), il pattern è:
```
1. Lettura: _redisDb.StringGet/HashGet (dati "live")
2. Scrittura: programmi esterni scrivono in Redis direttamente
3. FusionCache: durata pari a redisShortTimeCache o redisLongTimeCache
- Il backplane Redis invalida L1 negli altri worker
- Se un programma esterno scrive, la prossima lettura dal controller fa cache-miss
```
Questo è coerente con l'architettura attuale: il controller legge Redis (sorgente di verità) e FusionCache avvolge per ridurre i round-trip.
### Tagging Strategy
```
MachineData:{idxMacchina} — dati macchina generali (ResetDatiMacchina)
MachineParams:{idxMacchina} — parametri macchina (MachineParamListAsync)
Task:{idxMacchina} — task da eseguire
PzCounter:{idxMacchina} — contapezzi
Odl:{idxMacchina} — ODL correnti
Podl:{idxMacchina} — PODL
Input:{idxMacchina} — log input
FluxLog:{idxMacchina} — log flux
UserLog:{idxMacchina} — log user
```
### Transizioni di Scope
`MpDataService` è singleton → `IIocService` è scoped. Questo va bene per le minimal APIs (che iniettano `IIocService` correttamente). Per il controller, dopo la rimozione di `MpDataService`, il controller diventerà scoped di default (già lo è per `[ApiController]`).
### IocService constructor
Attuale (da `MP.Data.Services.IOC.IocService`):
```csharp
public IocService(IConfiguration config, IConnectionMultiplexer redis,
IFusionCache cache, IIocRepository repo,
IServiceScopeFactory scopeFactory)
```
Da `MP.Data.Services.BaseServ`:
```csharp
public BaseServ(IConfiguration configuration, IFusionCache cache, IConnectionMultiplexer redConn)
```
La gerarchia sarà: `IocService` estende `BaseServ` e il constructor chiama `base(configuration, cache, redConn)`.
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>MP.Land</RootNamespace>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<Configurations>Debug;Release;Debug_LiManDebug</Configurations>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<RunAnalyzersDuringBuild>True</RunAnalyzersDuringBuild>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo Tablet MAPO - DotNet6</i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br />
Note di rilascio:
<ul>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+1 -1
View File
@@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.MON</RootNamespace>
<AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-MON/stable/LAST/MP.MON.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-MON/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>MP.Prog</RootNamespace>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo gestione Programmi MAPO</i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br />
Note di rilascio:
<ul>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/MP.Prog.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+2 -1
View File
@@ -5,7 +5,8 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.RIOC</RootNamespace>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<ItemGroup>
+35 -3
View File
@@ -10,14 +10,30 @@ using System.Diagnostics;
using System.Net;
using System.Reflection;
// Forza il ThreadPool a tenere pronti almeno 500 thread per i calcoli e 500 per l'I/O di rete
ThreadPool.SetMinThreads(500, 500);
var builder = WebApplication.CreateBuilder(args);
// recupero env corrente
// RECUPERO L'AMBIENTE REALE (Che ora IIS passa correttamente come 'Staging')
var env = builder.Environment;
// FORZA IL CARICAMENTO CORRETTO DEI JSON CON LA GERARCHIA DI AMBIENTE
builder.Configuration
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
// recupero env corrente
var logger = LogManager.Setup()
.LoadConfigurationFromAppSettings()
.GetCurrentClassLogger();
builder.Logging.ClearProviders();
builder.Host.UseNLog();
var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
logger.Info($"MP.RIOC | Program.cs: startup | v.{assemblyVersion}");
logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}");
@@ -57,23 +73,35 @@ builder.Services.AddDbContextFactory<MoonPro_UtilsContext>(options =>
builder.Services.AddIocDataLayer();
// 1. Configurazione dell'invoker personalizzato (Risolve i tuoi errori)
// 1. Configurazione dell'invoker personalizzato (Potenziato con il Pooling)
var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler
{
UseProxy = false,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None,
UseCookies = false,
// Correzione per il tracing: usa il propagatore corrente di sistema
ActivityHeadersPropagator = DistributedContextPropagator.Current,
ConnectTimeout = TimeSpan.FromSeconds(60),
// Gestione certificato (ignora errori per localhost/test)
// ==================================================================
// 🚀 LE TRE RIGHE PER ABBATTERE IL MURO DEI 2 SECONDI SOTTO STRESS
// ==================================================================
// Permette a YARP di aprire fino a 5000 canali paralleli contemporanei verso IIS
MaxConnectionsPerServer = 5000,
// Tiene caldi i socket ed evita di rifare l'handshake TCP/TLS a ogni richiesta
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
// ==================================================================
// Gestione certificato (Invariata)
SslOptions = new System.Net.Security.SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
}
});
// Registrazione nella Dependency Injection (Invariata)
builder.Services.AddSingleton(httpClientInvoker);
builder.Services.AddHttpForwarder();
@@ -161,6 +189,10 @@ app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(routePath, StringComparis
});
});
// test per ambiente di esecuzione InProcess...
app.MapGet("api/alive", () =>
$"OK - Girando in: {System.Diagnostics.Process.GetCurrentProcess().ProcessName}");
// 6. Definizione degli Endpoints locali
app.MapRazorPages();
+30 -29
View File
@@ -1,33 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish>https://iis01.egalware.com/MP/RIOC/</SiteUrlToLaunchAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>b9188473-f4ae-4f9f-be2d-70edaace0db9</ProjectGuid>
<SelfContained>false</SelfContained>
<MSDeployServiceURL>https://iis01.egalware.com:8172/MsDeploy.axd</MSDeployServiceURL>
<DeployIisAppPath>Default Web Site/MP/RIOC</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>true</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>true</EnableMSDeployBackup>
<EnableMsDeployAppOffline>true</EnableMsDeployAppOffline>
<UserName>jenkins</UserName>
<_SavePWD>true</_SavePWD>
<_TargetId>IISWebDeploy</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
<ItemGroup>
<MsDeploySkipRules Include="SkipLogFiles">
<ObjectName>filePath</ObjectName>
<AbsolutePath>logs\\.*\.log$</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish>https://iis01.egalware.com/MP/RIOC/</SiteUrlToLaunchAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>b9188473-f4ae-4f9f-be2d-70edaace0db9</ProjectGuid>
<SelfContained>false</SelfContained>
<MSDeployServiceURL>https://iis01.egalware.com:8172/MsDeploy.axd</MSDeployServiceURL>
<DeployIisAppPath>Default Web Site/MP/RIOC</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>true</SkipExtraFilesOnServer>
<MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
<EnableMSDeployBackup>true</EnableMSDeployBackup>
<EnableMsDeployAppOffline>true</EnableMsDeployAppOffline>
<UserName>jenkins</UserName>
<_SavePWD>true</_SavePWD>
<_TargetId>IISWebDeploy</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
<AllowUntrustedCertificate>True</AllowUntrustedCertificate>
</PropertyGroup>
<ItemGroup>
<MsDeploySkipRules Include="SkipLogFiles">
<ObjectName>filePath</ObjectName>
<AbsolutePath>logs\\.*\.log$</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
</Project>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MP-RIOC </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-RIOC/stable/LAST/MP.RIOC.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-RIOC/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>
+90 -27
View File
@@ -33,8 +33,23 @@ namespace MP.RIOC.Services
{
try
{
await ProcessDayLiveMetricsAsync();
await ProcessHourLiveMetricsAsync();
//await ProcessDayLiveMetricsAsync();
//await ProcessHourLiveMetricsAsync();
// 2. 🚀 COSTRINGIAMO IL FLUSH A GIRARE IN BACKGROUND
// Task.Run prende l'intera esecuzione e la sposta su un thread separato.
// In questo modo, anche se l'Upsert fa calcoli pesanti o blocca momentaneamente,
// i thread principali di YARP rimangono liberi al 100% di rispondere a wrk in 2ms.
await Task.Run(async () =>
{
// Cede immediatamente il controllo per evitare di monopolizzare il thread corrente
await Task.Yield();
// Esegue i tuoi due metodi (che manterranno i loro scope interni asincroni)
await ProcessDayLiveMetricsAsync();
await ProcessHourLiveMetricsAsync();
}, stoppingToken);
// poi attendo...
await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken);
}
catch (TaskCanceledException) { break; }
@@ -69,20 +84,27 @@ namespace MP.RIOC.Services
/// <summary>
/// Cancellazione ricorsiva chiavi ausiliarie (:status, :errors) e rimozione dall'indice
/// </summary>
private async Task DeleteAuxKeysAndIndexAsync(RedisKey sKey, RedisKey indexKey, IBatch batch)
private async Task DeleteAuxKeysAndIndexAsync(RedisKey sKey, RedisKey indexKey, IBatch batch, List<Task> pendingOps)
{
string sKeyStr = sKey.ToString();
string keyDir = sKeyStr.Substring(0, sKeyStr.LastIndexOf(':'));
// Cancella :status dal sorted set e dalla hash
string statusKey = keyDir + ":status";
await batch.SortedSetRemoveAsync(indexKey, statusKey);
await batch.KeyDeleteAsync(statusKey);
try
{
pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, statusKey));
pendingOps.Add(batch.KeyDeleteAsync(statusKey));
}
catch { }
// Cancella :errors dal sorted set e dalla hash
string errorKey = keyDir + ":errors";
await batch.SortedSetRemoveAsync(indexKey, errorKey);
await batch.KeyDeleteAsync(errorKey);
try
{
pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, errorKey));
pendingOps.Add(batch.KeyDeleteAsync(errorKey));
}
catch { }
// Cancella chiave ausiliaria :status anche da un eventuale indice days se presente
if (statusKey.Contains(":stats:hours:"))
@@ -90,7 +112,7 @@ namespace MP.RIOC.Services
string daysIndex = statusKey.Replace(":stats:hours:", ":stats:days:");
try
{
await batch.SortedSetRemoveAsync(daysIndex, statusKey);
pendingOps.Add(batch.SortedSetRemoveAsync(daysIndex, statusKey));
}
catch { }
}
@@ -101,12 +123,13 @@ namespace MP.RIOC.Services
string daysIndex = errorKey.Replace(":stats:hours:", ":stats:days:");
try
{
await batch.SortedSetRemoveAsync(daysIndex, errorKey);
pendingOps.Add(batch.SortedSetRemoveAsync(daysIndex, errorKey));
}
catch { }
}
}
#if false
/// <summary>
/// Recupera il TTL residuo di una chiave Redis (-1 = nessun TTL, -2 = chiave non esiste)
/// </summary>
@@ -117,7 +140,8 @@ namespace MP.RIOC.Services
return _db.KeyTimeToLive(key);
}
catch { return null; }
}
}
#endif
/// <summary>
/// Processing dati giornalieri (da Redis a DB)
@@ -150,6 +174,7 @@ namespace MP.RIOC.Services
};
var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
foreach (var pattern in patternsToScan)
{
// Nota: KeyScanAsync/KeysAsync e' disponibile su IServer
@@ -165,21 +190,34 @@ namespace MP.RIOC.Services
var sKey = (RedisKey)$"{statKey}";
if (!TryParseKeyMetadata(sKey, out var meta) || meta.IsHourType) continue;
// Verifica se la chiave è "orfana" (nessun TTL o TTL troppo lungo >30gg)
// Verifica se la chiave è "orfana"
bool isOrphanKey = meta.Timestamp < currentDayStart;
#if false
bool isOrphanKey = false;
var keyTtl = GetKeyTtl(sKey);
bool isOrphanKey = keyTtl?.TotalSeconds < 0 || keyTtl?.TotalHours > 30.25 * 24;
if (keyTtl == null)
{
isOrphanKey = meta.Timestamp < currentDayStart;
}
else
{
isOrphanKey = keyTtl.Value.TotalSeconds < 0 || keyTtl.Value.TotalDays < 1;
}
#endif
// Se era scaduta o orfana e abbiamo il permesso, segnamola per la cancellazione
if ((meta.Timestamp < currentDayStart || isOrphanKey) && deleteConfirmed)
// Se è orfana e abbiamo il permesso, segnamola per la cancellazione
// NOTA: non usiamo il confronto con currentDayStart perché taglierebbe fuori
// i dati delle ore/giorni precedenti che devono essere ancora processati dal DB
if (isOrphanKey && deleteConfirmed)
{
// 1. Segna la chiave Hash (Dati) per l'eliminazione
keysToDelete.Add(sKey);
// 2. Rimuovi il riferimento dal Sorted Set (Indice)
await batch.SortedSetRemoveAsync(indexKey, statKey);
pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, statKey));
// 3. Cancellazione ricorsiva delle chiavi ausiliarie (:status, :errors, :days)
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch);
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch, pendingOps);
}
// Recupero dati dalla Hash
@@ -226,6 +264,8 @@ namespace MP.RIOC.Services
}
}
batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
}
// --- FASE UPSERT DB ---
@@ -241,13 +281,16 @@ namespace MP.RIOC.Services
if (deleteConfirmed && keysToDelete.Any())
{
var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
int deletedCount = 0;
foreach (var key in keysToDelete)
{
await batch.KeyDeleteAsync(key);
pendingOps.Add(batch.KeyDeleteAsync(key));
deletedCount++;
}
batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
Log.Info($"[CLEANUP DAY] Deleted {deletedCount} expired metric keys from Redis");
}
}
@@ -284,6 +327,7 @@ namespace MP.RIOC.Services
};
var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
foreach (var pattern in patternsToScan)
{
// Nota: KeyScanAsync/KeysAsync e' disponibile su IServer
@@ -299,21 +343,36 @@ namespace MP.RIOC.Services
var sKey = (RedisKey)$"{statKey}";
if (!TryParseKeyMetadata(sKey, out var meta) || !meta.IsHourType) continue;
// Verifica se la chiave è "orfana" (nessun TTL o TTL troppo lungo >30gg)
// Verifica se la chiave è "orfana"
bool isOrphanKey = meta.Timestamp < currentHourStart;
#if false
bool isOrphanKey = false;
var keyTtl = GetKeyTtl(sKey);
bool isOrphanKey = keyTtl?.TotalSeconds < 0 || keyTtl?.TotalHours > 30.25 * 24;
if (keyTtl == null)
{
// Nessun TTL recuperato = chiave senza scadenza = orfana se > 7 gg
DateTime cutoffHour = DateTime.Now.AddDays(-7);
isOrphanKey = meta.Timestamp < cutoffHour;
}
else
{
isOrphanKey = keyTtl.Value.TotalSeconds < 0 || keyTtl.Value.TotalDays < 15;
}
#endif
// Se era scaduta o orfana e abbiamo il permesso, segnamola per la cancellazione
if ((meta.Timestamp < currentHourStart || isOrphanKey) && deleteConfirmed)
// Se è orfana e abbiamo il permesso, segnamola per la cancellazione
// NOTA: non usiamo il confronto con currentHourStart perché taglierebbe fuori
// i dati delle ore precedenti che devono essere ancora processati dal DB
if (isOrphanKey && deleteConfirmed)
{
// 1. Segna la chiave Hash (Dati) per l'eliminazione
keysToDelete.Add(sKey);
// 2. Rimuovi il riferimento dal Sorted Set (Indice)
await batch.SortedSetRemoveAsync(indexKey, statKey);
pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, statKey));
// 3. Cancellazione ricorsiva chiavi ausiliarie
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch);
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch, pendingOps);
}
// Recupero dati dalla Hash
@@ -326,7 +385,6 @@ namespace MP.RIOC.Services
dict.TryGetValue("totalMs", out var totalMsStr))
{
long count = long.Parse(countStr);
count = long.Parse(countStr);
double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture);
double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0;
@@ -360,6 +418,8 @@ namespace MP.RIOC.Services
}
}
batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
}
// --- FASE UPSERT DB ---
@@ -375,13 +435,16 @@ namespace MP.RIOC.Services
if (deleteConfirmed && keysToDelete.Count > 0)
{
var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
int deletedCount = 0;
foreach (var key in keysToDelete)
{
await batch.KeyDeleteAsync(key);
pendingOps.Add(batch.KeyDeleteAsync(key));
deletedCount++;
}
batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
Log.Info($"[CLEANUP HOUR] Deleted {deletedCount} expired metric keys from Redis");
}
}
@@ -396,7 +459,7 @@ namespace MP.RIOC.Services
var p = relativeKey.Split(':');
if (p.Length < 4) return false;
meta.IsHourType = p[1].Equals("hour", StringComparison.InvariantCultureIgnoreCase);
meta.IsHourType = p[1].Contains("hour", StringComparison.InvariantCultureIgnoreCase);
meta.Dest = p[2];
if (meta.IsHourType)
{
+11 -3
View File
@@ -98,9 +98,17 @@ namespace MP.RIOC.Services
// ESECUZIONE FORWARDING
var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, HttpTransformer.Default, context.RequestAborted);
// commento transformer custom
//var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, _transformer, context.RequestAborted);
// Se YARP fallisce per un problema di connessione interrotta (come l'errore 995)
// e NON è un problema di sintassi della richiesta o di autorizzazione
if (error != ForwarderError.None &&
error != ForwarderError.RequestBodyClient &&
error != ForwarderError.RequestCanceled &&
error != ForwarderError.NoAvailableDestinations)
{
// Fai un secondo e ultimo tentativo immediato prima di dare errore al client
Log.Warn($"Micro-reset di rete rilevato ({error}). Tento il secondo invio immediato...");
error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, HttpTransformer.Default, context.RequestAborted);
}
sw.Stop();
_stats.RecordDuration(sKey, sw.Elapsed);
+1
View File
@@ -2,6 +2,7 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Yarp": "Warning",
"Microsoft.AspNetCore": "Warning"
}
}
+1
View File
@@ -2,6 +2,7 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Yarp": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
+35
View File
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Yarp": "Warning",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Hosting.Diagnostics": "Warning"
}
},
"NLog": {
"rules": [
{
"logger": "Microsoft.*",
"maxLevel": "Fatal",
"final": true
},
{
"logger": "Yarp.*",
"maxLevel": "Fatal",
"final": true
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logconsole"
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logfile"
}
]
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Yarp": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.AspNetCore.Hosting.Diagnostics": "Information"
}
},
"NLog": {
"rules": [
{
"logger": "Microsoft.AspNetCore.Hosting.Diagnostics",
"maxLevel": "Info",
"finalMinLevel": "Info"
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logfile"
}
]
}
}
+35
View File
@@ -0,0 +1,35 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Yarp": "Warning",
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Hosting.Diagnostics": "Warning"
}
},
"NLog": {
"rules": [
{
"logger": "Microsoft.*",
"maxLevel": "Fatal",
"final": true
},
{
"logger": "Yarp.*",
"maxLevel": "Fatal",
"final": true
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logconsole"
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "logfile"
}
]
}
}
+21 -5
View File
@@ -2,10 +2,11 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Yarp": "Debug",
"Microsoft.AspNetCore": "Warning",
"Yarp": "Information",
"Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
@@ -24,7 +25,7 @@
"logfile": {
"type": "File",
"fileName": "${basedir}/logs/${shortdate}.log",
"keepFileOpen": false,
"keepFileOpen": true,
"archiveEvery": "Day",
"archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log",
"archiveNumbering": "DateAndSequence",
@@ -39,9 +40,24 @@
}
},
"rules": [
{
"logger": "Microsoft.EntityFrameworkCore.*",
"maxLevel": "Info",
"final": true
},
{
"logger": "Microsoft.AspNetCore.Hosting.*",
"minLevel": "Info",
"finalMinLevel": "Info"
},
{
"logger": "Microsoft.AspNetCore.*",
"maxLevel": "Info",
"finalMinLevel": "Info"
},
{
"logger": "*",
"minLevel": "Trace",
"minLevel": "Info",
"writeTo": "logconsole"
},
{
+9
View File
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop;
using MP.AppAuth.Services;
using MP.Data.DbModels;
using MP.SPEC.Data;
using MP.SPEC.Extensions;
@@ -52,6 +53,14 @@ namespace MP.SPEC.Components
protected async Task FlushCache()
{
// svuoto cache redis...
ConfigModel updRec = new ConfigModel()
{
Chiave = "AZIENDA",
Valore = "*"
};
await MDService.ConfigUpdateAsync(updRec);
await MDService.ForceFlushRedisCache();
await MDService.ForceFlushFusionCacheAsync();
await ForceReload();
@@ -169,6 +169,7 @@ namespace MP.SPEC.Components.ProdKit
protected async Task toggleClosed()
{
hasOdl = !hasOdl;
currPage = 1;
await EC_HasOdl.InvokeAsync(hasOdl);
}
@@ -220,12 +220,6 @@ namespace MP.SPEC.Components.ProdKit
set => ActFilter.CurrPage = value;
}
private bool hasOdl
{
get => ActFilter.HasOdl;
set => ActFilter.HasOdl = value;
}
private bool isLoading { get; set; } = false;
private SelectXdlParams lastFilter { get; set; } = new SelectXdlParams() { CurrPage = -1 };
+2 -2
View File
@@ -54,10 +54,10 @@
</table>
}
<div runat="server" id="divPODL">
<b>Elenco promesse per KIT (100%)</b>
<b>Elenco promesse KIT (100%)</b> da produrre
@if (ListRecPODL == null || ListRecPODL.Count == 0)
{
<div class="alert bg-success bg-opacity-25 fw-bold text-center">Nessuna PODL compatibile presente</div>
<div class="alert bg-success bg-opacity-25 fw-bold text-center">Nessuna PODL KIT presente</div>
}
else
{
+1 -1
View File
@@ -188,7 +188,7 @@ namespace MP.SPEC.Components.ProdKit
{
listPOdlCheck = new List<PODLExpModel>();
listPOdlAct = await MDService.POdlListGetFiltAsync(ActFilt.HasOdl, ActFilt.CodFase, ActFilt.IdxMacchina, ActFilt.CodReparto, ActFilt.DtStart, ActFilt.DtEnd);
listPOdl2Kit = await MDService.POdlToKitListGetFiltAsync(ActFilt.HasOdl, ActFilt.CodFase, ActFilt.IdxMacchina, ActFilt.CodReparto, ActFilt.DtStart, ActFilt.DtEnd, true, true);
listPOdl2Kit = await MDService.POdlToKitListGetFiltAsync(false, ActFilt.CodFase, ActFilt.IdxMacchina, ActFilt.CodReparto, ActFilt.DtStart, ActFilt.DtEnd, true, false);
listWSM = await MDService.WipKitFiltAsync(keyFilt);
listTSM = await MDService.TksScoreAsync(keyFilt, 1000, true);
listIKP = await MDService.IstKitFiltAsync("", "");
+1 -1
View File
@@ -87,7 +87,7 @@
</div>
</div>
}
@if (!isActive || hasOdl)
@if (true || !isActive || hasOdl)
{
<div class="small mt-2">
<label class="px-2" for="dtMin" title="Selezionare inizio periodo">Inizio Periodo</label>
+3 -2
View File
@@ -1926,7 +1926,7 @@ namespace MP.SPEC.Data
return await GetOrFetchAsync(
operationName: "TksScoreAsync",
cacheKey: currKey,
expiration: TimeSpan.FromMinutes(redisLongTimeCache),
expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _productionRepository.TksScoreAsync(KeyFilt, MaxResult) ?? new List<TksScoreModel>(),
tagList: [Utils.redisKitScore]
);
@@ -2016,7 +2016,7 @@ namespace MP.SPEC.Data
return await GetOrFetchAsync(
operationName: "WipKitFiltAsync",
cacheKey: currKey,
expiration: TimeSpan.FromMinutes(redisLongTimeCache),
expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _productionRepository.WipKitFiltAsync(KeyFilt) ?? new List<WipSetupKitModel>(),
tagList: [Utils.redisKitWip]
);
@@ -2035,6 +2035,7 @@ namespace MP.SPEC.Data
fatto = await _productionRepository.WipKitUpsertAsync(currRecord);
// svuoto cache KitWip
await FlushFusionCacheAsync(Utils.redisKitWip);
await FlushFusionCacheAsync(Utils.redisKitScore);
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"WipKitUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms");
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.SPEC</RootNamespace>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<UserSecretsId>1800a78a-6ff1-40f9-b490-87fb8bfc1394</UserSecretsId>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
+8
View File
@@ -1,4 +1,5 @@
@page "/force-reset"
@page "/ForceReset"
@using MP.AppAuth.Services
@using MP.SPEC.Data
@@ -47,6 +48,13 @@
nextVal = 10;
await InvokeAsync(StateHasChanged);
await Task.Delay(bDelay);
// svuoto cache redis...
ConfigModel updRec = new ConfigModel()
{
Chiave = "AZIENDA",
Valore = "*"
};
await MDService.ConfigUpdateAsync(updRec);
title = "UserDataReload...";
currVal = 50;
nextVal = 80;
-2
View File
@@ -68,8 +68,6 @@ else
</div>
}
</div>
@*<div class="card-footer">
</div>*@
</div>
}
+23 -28
View File
@@ -1,27 +1,13 @@
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities;
using MP.Data.DbModels;
using MP.Data.Services;
using MP.SPEC.Data;
namespace MP.SPEC.Pages
{
public partial class Giacenze
{
#region Protected Fields
protected List<ODLModel>? elencoOdl;
#endregion Protected Fields
#region Protected Properties
protected int IdxOdl { get; set; } = 0;
protected ODLExpModel? odlExp { get; set; } = null!;
#endregion Protected Properties
#region Protected Methods
protected override async Task OnInitializedAsync()
@@ -41,34 +27,43 @@ namespace MP.SPEC.Pages
odlExp = await MDService.OdlByKeyAsync(IdxOdl);
}
protected void saveBatch(string newBatch)
{
BatchSel = newBatch;
}
#endregion Protected Methods
#region Private Fields
private List<ODLModel>? elencoOdl;
private string giacenzeConf = "false";
#endregion Private Fields
#region Private Properties
private string BatchSel { get; set; } = "";
private int IdxOdl { get; set; } = 0;
private string mainTabCss
{
get => !string.IsNullOrEmpty(BatchSel) ? "col-10" : "col-12";
}
[Inject]
private MpDataService MDService { get; set; } = null!;
[Inject]
private NavigationManager NavManager { get; set; } = null!;
private ODLExpModel? odlExp { get; set; } = null!;
private string padCodXdl { get; set; } = "00000";
private string giacenzeConf = "false";
[Inject]
private ISessionStorageService sessionStorage { get; set; } = null!;
private string BatchSel { get; set; } = "";
private string mainTabCss
{
get => !string.IsNullOrEmpty(BatchSel) ? "col-10" : "col-12";
}
protected void saveBatch(string newBatch)
{
BatchSel = newBatch;
}
#endregion Private Properties
}
}
+1 -1
View File
@@ -33,7 +33,7 @@
<div class="input-group input-group-sm d-flex justify-content-between">
<div class="input-group-text" id="inputGroup-sizing-sm">
<div class="pe-3" title="PODL Kit Child">
Kit
Kit C
</div>
<div class="form-check form-check-sm form-switch py-1" title="Mostra KIT">
<input class="form-check-input" type="checkbox" @bind="@ShowKit">
-15
View File
@@ -192,21 +192,6 @@ builder.Services.TryAddScoped<MpDataService>();
builder.Services.TryAddSingleton<IOApiService>();
builder.Services.TryAddScoped<MsgServiceSpec>();
#if false
builder.Services.AddSingleton<AppAuthService>();
builder.Services.AddSingleton<ListSelectDataSrv>();
builder.Services.AddSingleton<SharedMemService>();
builder.Services.AddSingleton<TabDataService>();
builder.Services.AddSingleton<TabDataFeeder>();
#endif
#if false
// aggiunta helper local/session storage service
builder.Services.AddScoped<ISessionStorageService, SessionStorageService>();
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
#endif
builder.Services.AddHttpClient();
logger.Info("Aggiunti services");
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</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>
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>MP.Stats</RootNamespace>
<UserSecretsId>826e877c-ba70-4253-84cb-d0b1cafd4440</UserSecretsId>
<Version>8.16.2606.1117</Version>
<Version>8.16.2606.1312</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>Modulo statistiche MAPO</i>
<h4>Versione: 8.16.2606.1117</h4>
<h4>Versione: 8.16.2606.1312</h4>
<br />
Note di rilascio:
<ul>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.1117
8.16.2606.1312
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>8.16.2606.1117</version>
<version>8.16.2606.1312</version>
<url>https://nexus.steamware.net/repository/SWS/MP-STATS/stable/LAST/MP.Stats.zip</url>
<changelog>https://nexus.steamware.net/repository/SWS/MP-STATS/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory>