Compare commits

..

56 Commits

Author SHA1 Message Date
Samuele Locatelli f738c0a690 Fix spec: rimanda su pagina 1 al cambio filtro su PODL KIT, fix vari RIOC 2026-06-12 18:34:57 +02:00
Samuele Locatelli 5e7016629a Update POOL di RIOC x testing specifico sotto carico 2026-06-12 17:58:10 +02:00
Samuele Locatelli 12994c5c7a RIOC: update verbosità x test staging 2026-06-12 16:58:23 +02:00
Samuele Locatelli 1ff5b10a8f Aggiunto reset forzato x SPEC 2026-06-12 16:58:15 +02:00
Samuele Locatelli 3c42f0ab83 Fix scadenza PODL in compoisizione KIT 2026-06-12 15:23:22 +02:00
Samuele Locatelli 295dab2fd9 Rimozione differente cache FusionCache dati 2026-06-12 15:14:16 +02:00
Samuele Locatelli c8632f00d5 Fix visualizzazione filtro data ricerche PODL 2026-06-12 15:13:26 +02:00
Samuele Locatelli 1231731c8f SPEC: fix filtro sel PODL disponibili in creazione KIT 2026-06-12 15:10:06 +02:00
Samuele Locatelli 650d88a276 SPEC: fix visualizzazione KIT Child 2026-06-12 11:02:48 +02:00
Samuele Locatelli c03a8a7827 Update scadenza cache IOC x salvataggio contapezzi 2026-06-12 10:37:24 +02:00
Samuele Locatelli 6c400332ff Modifica logica scadenza produzione ed ODL 2026-06-12 09:58:24 +02:00
Samuele Locatelli 56392e0dcf Aggiunta metodi con MinimalApi 2026-06-12 09:47:42 +02:00
Samuele Locatelli f192c35454 update conf RIOC da testare 2026-06-12 07:27:18 +02:00
Samuele Locatelli 6105e76917 Review pulizia key redis 2026-06-12 07:14:59 +02:00
Samuele Locatelli 3c0eb3fb46 Fix controllo scadenza dati DAY 2026-06-11 20:58:02 +02:00
Samuele Locatelli e9c4049824 Cambio modalità di calcolo chiavi orphaned x gestire statistiche in modo + corretto 2026-06-11 20:55:06 +02:00
Samuele Locatelli 1361aab3b9 Ancora ottimizzazioni minori IOC x metodi + frequenti 2026-06-11 20:27:06 +02:00
Samuele Locatelli db44a8ca87 Spostato controller alive in map controller, sotto 1ms... (HOPE) 2026-06-11 20:10:47 +02:00
Samuele Locatelli 1d1a71e95f Provo modifica durata info statoProd in redis... 2026-06-11 19:54:05 +02:00
Samuele Locatelli 850c549b1b Fix flush dati RIOC 2026-06-11 19:41:45 +02:00
Samuele Locatelli 9f6e012b51 Fix RIOC x scadenza chiavi hour 2026-06-11 18:53:35 +02:00
Samuele Locatelli 1e7572a098 Bozza dopia scadenza giorno/ora per le statistiche 2026-06-11 18:28:43 +02:00
Samuele Locatelli 2594166efc Fix spec x gestione giacenze (si apre) + cleanup program.cs 2026-06-11 18:17:24 +02:00
Samuele Locatelli d17e394416 Merge tag 'CodeAssisted_03' into develop
Update componenti CodeAssisted vari su + giorni
2026-06-11 17:47:29 +02:00
Samuele Locatelli 2169da5ec3 Merge branch 'Release/CodeAssisted_03' 2026-06-11 17:47:10 +02:00
Samuele Locatelli 5175af0769 Fix flux orario x RIOC 2026-06-11 17:46:05 +02:00
Samuele Locatelli 6a7a66dfc5 refresh compile 2026-06-11 17:33:51 +02:00
Samuele Locatelli 063018f9ad Update gestione flush service 2026-06-11 17:28:11 +02:00
Samuele Locatelli 3989fa5659 Fix ricerca PODL x KIT e Attivi 2026-06-11 17:27:26 +02:00
Samuele Locatelli 61b590d4fa inizio gestione filtro x PODL 2026-06-11 12:25:50 +02:00
Samuele Locatelli 5e21535103 fix compilazione IOC post modifiche non ok in attesa nuova versione 2026-06-11 12:15:46 +02:00
Samuele Locatelli 89529d9018 Update flush dati vecchi 2026-06-11 12:02:36 +02:00
Samuele Locatelli 06baf0167c Update add articoli (upsert) 2026-06-11 12:02:27 +02:00
Samuele Locatelli d447b5501f Altri expire TTL 2026-06-11 11:21:24 +02:00
Samuele Locatelli bd583a59fe Messo TTL x dati statistiche a 30gg max 2026-06-11 11:12:55 +02:00
Samuele Locatelli 054e5fe72e Refresh post compilazione 2026-06-10 21:02:55 +02:00
Samuele Locatelli 081b52cfa8 Test modifica cache IOC 2026-06-10 21:02:00 +02:00
Samuele Locatelli e28d620814 modifiche minori prima di ottimizzare IOC di nuovo 2026-06-10 19:50:19 +02:00
Samuele Locatelli 7a2eab03f4 update cache timeout vari 2026-06-09 19:26:39 +02:00
Samuele Locatelli b4b7d21592 Pulizia metodi commentati e non usati da MpIocController 2026-06-09 15:45:59 +02:00
Samuele Locatelli 26ca34f99a pulizia + commento aree MpIocController (legacy) 2026-06-09 15:17:43 +02:00
Samuele Locatelli db7372b10a Fix interfaccia + commenti MpDataService x IOC 2026-06-09 15:17:04 +02:00
Samuele Locatelli bbaca1adc5 Refresh tab 2026-06-08 17:13:47 +02:00
Samuele Locatelli 657e203767 Fix ricerca repa-opr 2026-06-08 17:09:12 +02:00
Samuele Locatelli 1eaedba6cd Fix correzioni viste con Gian 2026-06-08 16:59:53 +02:00
Samuele Locatelli e6df6a5e18 Fix gestione creazione KIT con stored corretta 2026-06-08 12:01:05 +02:00
Samuele Locatelli 3499d8b32d refresh 2026-06-04 18:23:21 +02:00
Samuele Locatelli 10e39b1c32 Update IOC con nuovi metodi cache FusionCache (int est...) 2026-06-04 18:23:13 +02:00
Samuele Locatelli a6900f01a1 Analisi progetto IOC e documentazione pre modifiche struttura DAL 2026-06-04 14:21:00 +02:00
Samuele Locatelli d4615d9ef2 Inizio refactor IOC (repository, poi sarà la volta di FusionCache... 2026-06-04 13:52:58 +02:00
Samuele Locatelli fa467778e5 Refresh compilazioni 2026-06-04 13:45:54 +02:00
Samuele Locatelli 7c9406682c SPEC:
- pulizia vecchio controller/repository globale
- pulizia MpDataService
- aggiunta file readme progetto
- update refactor progetto e config
2026-06-04 13:45:46 +02:00
Samuele Locatelli 4d2ae932d0 Merge tag 'CodeAssisted_02' into develop
Update gestione ricerca operatori, rilascio versione MASTER preliminare
2026-06-04 09:53:08 +02:00
Samuele Locatelli ed4d5f4743 Merge branch 'Release/CodeAssisted_02' 2026-06-04 09:52:41 +02:00
Samuele Locatelli bbdf6b4012 SPEC:
- aggiunta ricerca x operatori
2026-06-04 09:52:12 +02:00
Samuele Locatelli 3a3336fcdf Merge branch 'feature/CodeAssisted_01' into develop 2026-06-04 08:14:03 +02:00
110 changed files with 3401 additions and 4796 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1117</Version>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP_TAB3</RootNamespace> <RootNamespace>MP_TAB3</RootNamespace>
</PropertyGroup> </PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MAPOSPEC </i> <i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1117</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.408 8.16.2606.1117
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.408</version> <version>8.16.2606.1117</version>
<url>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/MP-TAB3.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-TAB3/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+7 -535
View File
@@ -15,8 +15,6 @@ namespace MP.Data.Controllers
{ {
public class MpIocController public class MpIocController
{ {
protected readonly IDbContextFactory<MoonProContext> _ctxFactory;
protected readonly IDbContextFactory<MoonPro_FluxContext> _ctxFactoryFL;
#region Public Constructors #region Public Constructors
public MpIocController( public MpIocController(
@@ -24,21 +22,8 @@ namespace MP.Data.Controllers
IDbContextFactory<MoonProContext> ctxFactory, IDbContextFactory<MoonProContext> ctxFactory,
IDbContextFactory<MoonPro_FluxContext> ctxFactoryFL) IDbContextFactory<MoonPro_FluxContext> ctxFactoryFL)
{ {
#if false
_configuration = configuration;
#endif
_ctxFactory = ctxFactory; _ctxFactory = ctxFactory;
_ctxFactoryFL = ctxFactoryFL; _ctxFactoryFL = ctxFactoryFL;
#if false
string connStr = configuration.GetConnectionString("MP.Data");
options = new DbContextOptionsBuilder<MoonProContext>()
.UseSqlServer(connStr)
.Options;
string connStrFlux = configuration.GetConnectionString("MP.Flux");
optionsFlux = new DbContextOptionsBuilder<MoonPro_FluxContext>()
.UseSqlServer(connStrFlux)
.Options;
#endif
Log.Info("Avviata classe MpIocController"); Log.Info("Avviata classe MpIocController");
} }
@@ -500,9 +485,6 @@ namespace MP.Data.Controllers
// Update() allega l'entità e segna tutti i campi come Modified // Update() allega l'entità e segna tutti i campi come Modified
dbCtx.DbSetMicroStatoMacc.Update(actRec); dbCtx.DbSetMicroStatoMacc.Update(actRec);
#if false
dbCtx.Entry(actRec).State = EntityState.Modified;
#endif
} }
// ora record EVList // ora record EVList
@@ -540,28 +522,6 @@ namespace MP.Data.Controllers
.ToListAsync(); .ToListAsync();
} }
/// <summary>
/// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione)
/// </summary>
/// <param name="DtMax">Data massima x eventi</param>
/// <param name="DtMin">Data minima x eventi</param>
/// <param name="IdxMacchina">* = tutte, altrimenti solo x una data macchina</param>
/// <param name="CodFlux">*=tutti, altrimenti solo selezionato</param>
/// <param name="MaxRec">numero massimo record da restituire</param>
/// <returns></returns>
public async Task<List<FluxLogModel>> FluxLogGetLastFiltAsync(DateTime DtMax, DateTime DtMin, string IdxMacchina, string CodFlux, int MaxRec)
{
using var dbCtx = await _ctxFactoryFL.CreateDbContextAsync();
return await dbCtx
.DbSetFluxLog
.AsNoTracking()
.Where(x => (x.dtEvento >= DtMin && x.dtEvento <= DtMax) && (IdxMacchina == "*" || x.IdxMacchina == IdxMacchina) && (CodFlux == "*" || x.CodFlux == CodFlux))
.OrderByDescending(x => x.dtEvento)
.Take(MaxRec)
.ToListAsync();
}
/// <summary> /// <summary>
/// Aggiunta record FluxLog /// Aggiunta record FluxLog
/// </summary> /// </summary>
@@ -575,7 +535,6 @@ namespace MP.Data.Controllers
.DbSetFluxLog .DbSetFluxLog
.Add(newRec); .Add(newRec);
return await dbCtx.SaveChangesAsync() > 0; return await dbCtx.SaveChangesAsync() > 0;
} }
/// <summary> /// <summary>
@@ -597,266 +556,6 @@ namespace MP.Data.Controllers
return result > 0; return result > 0;
} }
/// <summary>
/// Upsert record keepalive
/// </summary>
/// <param name="IdxMacc"></param>
/// <param name="OraServer"></param>
/// <param name="OraMacc"></param>
/// <returns></returns>
public async Task<bool> KeepAliveUpsertAsync(string IdxMacc, DateTime OraServer, DateTime OraMacc)
{
bool fatto = false;
using var dbCtx = _ctxFactory.CreateDbContext();
var currRec = await dbCtx
.DbSetKeepAlive
.Where(x => x.IdxMacchina == IdxMacc)
.FirstOrDefaultAsync();
if (currRec != null)
{
currRec.DataOraServer = OraServer;
currRec.DataOraMacchina = OraMacc;
dbCtx.Entry(currRec).State = EntityState.Modified;
}
else
{
KeepAliveModel newRec = new KeepAliveModel()
{
IdxMacchina = IdxMacc,
DataOraMacchina = OraMacc,
DataOraServer = OraServer,
DataOraStart = DateTime.Now
};
dbCtx
.DbSetKeepAlive
.Add(newRec);
}
fatto = await dbCtx.SaveChangesAsync() > 0;
return fatto;
}
public async Task<List<LinkMenuModel>> ListLinkFiltAsync(string tipoLink)
{
List<LinkMenuModel> dbResult = new List<LinkMenuModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
dbResult = await dbCtx
.DbSetLinkMenu
.Where(x => x.TipoLink == tipoLink)
.AsNoTracking()
.OrderBy(x => x.Ordine)
.ToListAsync();
return dbResult;
}
/// <summary>
/// Elenco valori ammessi x tabella/colonna con filtro parametrico
/// </summary>
/// <param name="tabName">Filtro tabella (se "" tutto)</param>
/// <param name="fieldName">Filtro colonna (se "" tutto)</param>
/// <returns></returns>
public async Task<List<ListValuesModel>> ListValuesFiltAsync(string tabName, string fieldName)
{
List<ListValuesModel> dbResult = new List<ListValuesModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
var query = dbCtx
.DbSetListValues
.AsNoTracking()
.AsQueryable();
if (!string.IsNullOrEmpty(tabName))
query = query.Where(x => x.TableName == tabName);
if (!string.IsNullOrEmpty(fieldName))
query = query.Where(x => x.FieldName == fieldName);
dbResult = await query.ToListAsync();
return dbResult;
}
/// <summary>
/// Intera tabella relazione master/slave in machine (gestione setup master - slave)
/// </summary>
/// <returns></returns>
public async Task<List<Macchine2SlaveModel>> Macchine2SlaveAsync()
{
List<Macchine2SlaveModel> dbResult = new List<Macchine2SlaveModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
dbResult = await dbCtx
.DbSetM2S
.AsNoTracking()
.OrderBy(x => x.IdxMacchina)
.ToListAsync();
return dbResult;
}
/// <summary>
/// Elenco Record Macchine
/// </summary>
/// <returns></returns>
public async Task<List<MacchineModel>> MacchineGetAllAsync()
{
List<MacchineModel> dbResult = new List<MacchineModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
dbResult = await dbCtx
.DbSetMacchine
.ToListAsync();
return dbResult;
}
public async Task<MacchineModel?> MacchineGetByIdxAsync(string IdxMacchina)
{
MacchineModel dbResult = null;
using var dbCtx = _ctxFactory.CreateDbContext();
dbResult = await dbCtx
.DbSetMacchine
.FirstOrDefaultAsync(x => x.IdxMacchina == IdxMacchina);
return dbResult;
}
/// <summary>
/// Elenco da tabella Macchine
/// </summary>
/// <param name="codGruppo"></param>
/// <returns></returns>
public async Task<List<MacchineModel>> MacchineGetFiltAsync(string codGruppo)
{
List<MacchineModel> dbResult = new List<MacchineModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
if (codGruppo == "*")
{
dbResult = await dbCtx
.DbSetMacchine
.AsNoTracking()
.OrderBy(x => x.IdxMacchina)
.ToListAsync();
}
else
{
dbResult = await dbCtx
.DbSetGrp2Macc
.Where(g => g.CodGruppo == codGruppo)
.Join(dbCtx.DbSetMacchine,
g => g.IdxMacchina,
m => m.IdxMacchina,
(g, m) => m
)
.AsNoTracking()
.OrderBy(x => x.IdxMacchina)
.ToListAsync();
}
return dbResult;
}
/// <summary>
/// Upsert Record Macchine ASYNC
/// </summary>
/// <returns></returns>
public async Task<bool> MacchineUpsertAsync(MacchineModel entity)
{
using var dbCtx = _ctxFactory.CreateDbContext();
// Recuperiamo l'entità tracciata dal context
var trackedEntity = await dbCtx
.DbSetMacchine
.FirstOrDefaultAsync(x => x.IdxMacchina == entity.IdxMacchina);
if (trackedEntity != null)
{
// Aggiorna i valori dell'entità tracciata con quelli della nuova
dbCtx.Entry(trackedEntity).CurrentValues.SetValues(entity);
}
else
{
dbCtx.DbSetMacchine.Update(entity);
}
bool fatto = await dbCtx.SaveChangesAsync() > 0;
return fatto;
}
/// <summary>
/// Elenco da tabella Macchine
/// </summary>
/// <param name="IdxMacc"></param>
/// <returns></returns>
public async Task<List<MicroStatoMacchinaModel>> MicroStatoMacchinaGetByIdxMaccAsync(string IdxMacc)
{
List<MicroStatoMacchinaModel> dbResult = new List<MicroStatoMacchinaModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
dbResult = await dbCtx
.DbSetMicroStatoMacc
.Where(x => x.IdxMacchina == IdxMacc)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary>
/// Aggiornamento record Microstato macchina
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
public async Task<bool> MicroStatoMacchinaUpsertAsync(MicroStatoMacchinaModel newRec)
{
bool fatto = false;
using var dbCtx = _ctxFactory.CreateDbContext();
var actRec = await dbCtx
.DbSetMicroStatoMacc
.Where(x => x.IdxMacchina == newRec.IdxMacchina)
.AsNoTracking()
.FirstOrDefaultAsync();
if (actRec == null)
{
dbCtx
.DbSetMicroStatoMacc
.Add(newRec);
}
else
{
actRec.IdxMicroStato = newRec.IdxMicroStato;
actRec.InizioStato = newRec.InizioStato;
actRec.Value = newRec.Value;
dbCtx.Entry(actRec).State = EntityState.Modified;
}
fatto = await dbCtx.SaveChangesAsync() > 0;
return fatto;
}
/// <summary>
/// Elenco da tabella MappaStatoExplModel
/// </summary>
/// <returns></returns>
public async Task<List<MappaStatoExplModel>> MseGetAllAsync(int maxAge = 2000)
{
List<MappaStatoExplModel> dbResult = new List<MappaStatoExplModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
var maxAgeSec = new SqlParameter("@maxAgeSec", maxAge);
dbResult = await dbCtx
.DbSetMSE
.FromSqlRaw("EXEC stp_MSE_getData @maxAgeSec", maxAgeSec)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary> /// <summary>
/// Generazione automatica ODL /// Generazione automatica ODL
/// </summary> /// </summary>
@@ -929,28 +628,6 @@ namespace MP.Data.Controllers
return answ; return answ;
} }
/// <summary>
/// Fix ODL per macchine SLAVE
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="numDayPrev"></param>
/// <param name="doInsert"></param>
/// <returns></returns>
public async Task<bool> OdlFixMachineSlave(string idxMacchina, int numDayPrev, int doInsert)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var idxMaccParam = new SqlParameter("@IdxMacchina", idxMacchina ?? "");
var numDayPrevParam = new SqlParameter("@NumDayPrev", numDayPrev);
var doInsertParam = new SqlParameter("@DoInsert", doInsert);
var result = await dbCtx
.Database
.ExecuteSqlRawAsync("EXEC stp_ODL_fixMachineSlave @IdxMacchina, @NumDayPrev, @DoInsert", idxMaccParam, numDayPrevParam, doInsertParam);
return result != -1;
}
/// <summary> /// <summary>
/// Fix ODL per macchine SLAVE Async /// Fix ODL per macchine SLAVE Async
/// </summary> /// </summary>
@@ -993,30 +670,6 @@ namespace MP.Data.Controllers
return answ; return answ;
} }
/// <summary>
/// Elenco ODL data macchina e periodo
/// </summary>
/// <param name="idxMacchina"></param>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
public async Task<List<ODLExpModel>> OdlListByMaccPeriodoAsync(string idxMacchina, DateTime dtStart, DateTime dtEnd)
{
List<ODLExpModel> dbResult = new List<ODLExpModel>();
using var dbCtx = _ctxFactory.CreateDbContext();
var IdxMacchina = new SqlParameter("@IdxMacchina", idxMacchina);
var DataFrom = new SqlParameter("@dataFrom", dtStart);
var DataTo = new SqlParameter("@dataTo", dtEnd);
dbResult = await dbCtx
.DbSetODLExp
.FromSqlRaw("EXEC stp_ODL_getByMacchinaPeriodo @IdxMacchina, @dataFrom, @dataTo", IdxMacchina, DataFrom, DataTo)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary> /// <summary>
/// Conteggio PzProd Macchina Async /// Conteggio PzProd Macchina Async
/// </summary> /// </summary>
@@ -1131,27 +784,6 @@ namespace MP.Data.Controllers
return result != 0; return result != 0;
} }
/// <summary>
/// Update record Registro Dichiarazioni
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
public async Task<bool> RegDichiarUpdateAsync(RegistroDichiarazioniModel newRec)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var Original_IdxDich = new SqlParameter("@Original_IdxDich", newRec.IdxDich);
var DtRec = new SqlParameter("@DtRec", newRec.DtRec);
var TagCode = new SqlParameter("@TagCode", newRec.TagCode);
var ValString = new SqlParameter("@ValString", newRec.ValString);
var MatrOpr = new SqlParameter("@MatrOpr", newRec.MatrOpr);
var result = await dbCtx
.Database
.ExecuteSqlRawAsync("exec dbo.stp_DD_updateQuery @Original_IdxDich, @DtRec, @TagCode, @ValString, @MatrOpr", Original_IdxDich, DtRec, TagCode, ValString, MatrOpr);
return result != 0;
}
/// <summary> /// <summary>
/// Aggiunta record RegistroScarti /// Aggiunta record RegistroScarti
/// </summary> /// </summary>
@@ -1222,160 +854,6 @@ namespace MP.Data.Controllers
return fatto; return fatto;
} }
/// <summary>
/// Aggiunta record RemoteRebootLog
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
public async Task<bool> RemRebootLogAddAsync(RemoteRebootLogModel newRec)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var dbResult = dbCtx
.DbSetRemRebLog
.Add(newRec);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Recupera tutti i record di RemoteRebootLog
/// </summary>
/// <returns></returns>
public async Task<List<RemoteRebootLogModel>> RemRebootLogGetAllAsync()
{
using var dbCtx = _ctxFactory.CreateDbContext();
var dbResult = await dbCtx
.DbSetRemRebLog
.AsNoTracking()
.OrderByDescending(x => x.IdxReboot)
.ToListAsync();
return dbResult;
}
/// <summary>
/// Recupera ultimo record x ogni IdxMacchina x avere ultimo attivo
/// </summary>
/// <returns></returns>
public async Task<List<RemoteRebootLogModel>> RemRebootLogGetLastAsync()
{
using var dbCtx = _ctxFactory.CreateDbContext();
var dbResult = await dbCtx
.DbSetRemRebLog
.FromSqlRaw("EXEC stp_RRL_getLast")
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary>
/// Recupera ultimo record x ogni IdxMacchina x avere ultimo attivo
/// </summary>
/// <returns></returns>
public async Task<bool> RemRebootLogKeepLastAsync(int num2keep)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var pNum2Keep = new SqlParameter("@num2keep", num2keep);
// La SP gestisce già la logica di soglia (1.5x), ma la specifico x sicurezza
var pThresh = new SqlParameter("@threshMult", 1.5);
var dbResult = await dbCtx.Database.ExecuteSqlRawAsync(
"EXEC dbo.stp_RRL_KeepLatest @num2keep, @threshMult", pNum2Keep, pThresh);
return dbResult != 0;
}
/// <summary>
/// Aggiunta record SignalLog Async
/// </summary>
/// <param name="newRec"></param>
/// <returns></returns>
public async Task<bool> SignalLogInsertAsync(SignalLogModel newRec)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var currRec = dbCtx
.DbSetSignalLog
.Add(newRec);
return await dbCtx.SaveChangesAsync() > 0;
}
/// <summary>
/// Tabella state machine eventi 2 stati data macchina e tipo evento
/// </summary>
/// <param name="idxTipo"></param>
/// <returns></returns>
public async Task<List<TransizioneStatiModel>> SMES_getHwTransitionsAsync(string idxMacchina, int idxTipo)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var IdxMacchina = new SqlParameter("@IdxMacchina", idxMacchina);
var IdxTipo = new SqlParameter("@IdxTipo", idxTipo);
var dbResult = await dbCtx
.DbSetSMES
.FromSqlRaw("exec dbo.stp_TS_getByIdxMacchIdxTipoEv @IdxMacchina, @IdxTipo", IdxMacchina, IdxTipo)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary>
/// Tabella state machine eventi 2 stati data macchina e tipo evento
/// </summary>
/// <param name="idxTipo"></param>
/// <returns></returns>
public async Task<List<TransizioneStatiModel>> SMES_getUserForcedAsync(string idxMacchina, int idxTipo)
{
await using var dbCtx = _ctxFactory.CreateDbContext();
var IdxMacchina = new SqlParameter("@IdxMacchina", idxMacchina);
var IdxTipo = new SqlParameter("@IdxTipo", idxTipo);
var dbResult = await dbCtx
.DbSetSMES
.FromSqlRaw("exec dbo.stp_TS_getUserForcedTrans @IdxMacchina, @IdxTipo", IdxMacchina, IdxTipo)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary>
/// Intera tabella state machine ingressi 2 eventi
/// </summary>
/// <returns></returns>
public List<TransizioneIngressiModel> StateMachineIngressi(int idxFam)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var IdxFamIn = new SqlParameter("@IdxFamigliaIngresso", idxFam);
return dbCtx
.DbSetSMI
.FromSqlRaw("exec dbo.stp_TRI_getByIdxFamIng @IdxFamigliaIngresso", IdxFamIn)
.AsNoTracking()
.AsEnumerable()
.ToList();
}
/// <summary>
/// Intera tabella state machine ingressi 2 eventi
/// </summary>
/// <returns></returns>
public async Task<List<TransizioneIngressiModel>> StateMachineIngressiAsync(int idxFam)
{
using var dbCtx = _ctxFactory.CreateDbContext();
var IdxFamIn = new SqlParameter("@IdxFamigliaIngresso", idxFam);
var dbResult = await dbCtx
.DbSetSMI
.FromSqlRaw("exec dbo.stp_TRI_getByIdxFamIng @IdxFamigliaIngresso", IdxFamIn)
.AsNoTracking()
.ToListAsync();
return dbResult;
}
/// <summary> /// <summary>
/// Stato prod macchina (completo) Async /// Stato prod macchina (completo) Async
/// </summary> /// </summary>
@@ -1457,24 +935,18 @@ namespace MP.Data.Controllers
#endregion Public Methods #endregion Public Methods
#region Protected Fields
protected readonly IDbContextFactory<MoonProContext> _ctxFactory;
protected readonly IDbContextFactory<MoonPro_FluxContext> _ctxFactoryFL;
#endregion Protected Fields
#region Private Fields #region Private Fields
#if false
private static IConfiguration _configuration;
#endif
private static NLog.Logger Log = LogManager.GetCurrentClassLogger(); private static NLog.Logger Log = LogManager.GetCurrentClassLogger();
#if false
private DbContextOptions<MoonProContext> options;
#endif
private DbContextOptions<MoonPro_FluxContext> optionsFlux; private DbContextOptions<MoonPro_FluxContext> optionsFlux;
#endregion Private Fields #endregion Private Fields
#if false
public void Dispose()
{
_configuration = null;
}
#endif
} }
} }
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -35,9 +35,11 @@ namespace MP.Data
// Repository Singleton // Repository Singleton
services.TryAddSingleton<IMtcSetupRepository, MtcSetupRepository>(); services.TryAddSingleton<IMtcSetupRepository, MtcSetupRepository>();
services.TryAddSingleton<IProductionRepository, ProductionRepository>(); services.TryAddSingleton<IProductionRepository, ProductionRepository>();
services.TryAddSingleton<IIocRepository, IocRepository>();
services.TryAddSingleton<IAnagRepository, AnagRepository>();
services.TryAddSingleton<ISystemRepository, SystemRepository>();
// Repository Scoped // Repository Scoped (non usate da MpDataService)
services.TryAddScoped<IIocRepository, IocRepository>();
services.TryAddScoped<IStatsAggrRepository, StatsAggrRepository>(); services.TryAddScoped<IStatsAggrRepository, StatsAggrRepository>();
services.TryAddScoped<IStatsDetailRepository, StatsDetailRepository>(); services.TryAddScoped<IStatsDetailRepository, StatsDetailRepository>();
+10 -37
View File
@@ -27,9 +27,7 @@ namespace MP.Data.Repository.Anag
{ {
AnagCountersModel answ = new AnagCountersModel(); AnagCountersModel answ = new AnagCountersModel();
await using var dbCtx = await CreateContextAsync(); await using var dbCtx = await CreateContextAsync();
bool outTable = true;
if (outTable)
{
var pCntType = new SqlParameter("@CntType", cntType); var pCntType = new SqlParameter("@CntType", cntType);
var pLastNum = new SqlParameter var pLastNum = new SqlParameter
{ {
@@ -38,44 +36,16 @@ namespace MP.Data.Repository.Anag
Direction = ParameterDirection.Output Direction = ParameterDirection.Output
}; };
var dbResult = await dbCtx var dbResult = (await dbCtx
.DbSetAnagCount .DbSetAnagCount
.FromSqlRaw("EXEC dbo.stp_getNextNumb @CntType, @LastNum OUTPUT", pCntType, pLastNum) .FromSqlRaw("EXEC dbo.stp_getNextNumb @CntType, @LastNum OUTPUT", pCntType, pLastNum)
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(); .ToListAsync()).FirstOrDefault();
if (dbResult != null) if (dbResult != null)
{ {
answ = dbResult; answ = dbResult;
} }
}
else
{
// se si volessero impiegare parametri OUTPUT (qui ne mancherebbe 1 nella stored x CntCode...)
var pCntType = new SqlParameter("@CntType", cntType);
var pLastNum = new SqlParameter
{
ParameterName = "@LastNum",
SqlDbType = SqlDbType.Int,
Direction = ParameterDirection.Output
};
var pCntCode = new SqlParameter
{
ParameterName = "@CntCode",
SqlDbType = SqlDbType.NVarChar,
Direction = ParameterDirection.Output
};
var dbResult = await dbCtx
.Database
.ExecuteSqlRawAsync("EXEC dbo.stp_getNextNumb @CntType, @LastNum OUTPUT, @CntCode OUTPUT", pCntType, pLastNum, pCntCode);
if (dbResult != 0)
{
answ.CntType = cntType;
answ.CntCode = $"{pCntCode.Value}";
int lNum = 0;
int.TryParse($"{pLastNum.Value}", out lNum);
answ.LastNum = lNum;
}
}
return answ; return answ;
} }
@@ -352,7 +322,7 @@ namespace MP.Data.Repository.Anag
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<bool> ArticoliUpdateRecord(AnagArticoliModel editRec) public async Task<bool> ArticoliUpsertAsync(AnagArticoliModel editRec)
{ {
await using var dbCtx = await CreateContextAsync(); await using var dbCtx = await CreateContextAsync();
var currRec = await dbCtx.DbSetArticoli.FirstOrDefaultAsync(x => x.CodArticolo == editRec.CodArticolo); var currRec = await dbCtx.DbSetArticoli.FirstOrDefaultAsync(x => x.CodArticolo == editRec.CodArticolo);
@@ -363,9 +333,12 @@ namespace MP.Data.Repository.Anag
currRec.Tipo = editRec.Tipo; currRec.Tipo = editRec.Tipo;
currRec.Azienda = editRec.Azienda; currRec.Azienda = editRec.Azienda;
dbCtx.Entry(currRec).State = EntityState.Modified; dbCtx.Entry(currRec).State = EntityState.Modified;
return await dbCtx.SaveChangesAsync() > 0;
} }
return false; else
{
dbCtx.DbSetArticoli.Add(editRec);
}
return await dbCtx.SaveChangesAsync() > 0;
} }
/// <inheritdoc /> /// <inheritdoc />
+2 -2
View File
@@ -140,11 +140,11 @@ namespace MP.Data.Repository.Anag
Task<List<AnagArticoliModel>> ArticoliInKitAsync(); Task<List<AnagArticoliModel>> ArticoliInKitAsync();
/// <summary> /// <summary>
/// Update Record Articolo /// Upsert (add/update) Record Articolo
/// </summary> /// </summary>
/// <param name="editRec">Record da aggiornare</param> /// <param name="editRec">Record da aggiornare</param>
/// <returns>True se aggiornato</returns> /// <returns>True se aggiornato</returns>
Task<bool> ArticoliUpdateRecord(AnagArticoliModel editRec); Task<bool> ArticoliUpsertAsync(AnagArticoliModel editRec);
/// <summary> /// <summary>
/// Elenco Gruppi tipo REPARTOin formato DTO con conteggi del numero record trovati filtrati per operatore /// Elenco Gruppi tipo REPARTOin formato DTO con conteggi del numero record trovati filtrati per operatore
@@ -31,7 +31,7 @@ namespace MP.Data.Repository.Production
Task<List<PODLExpModel>> ListPODL_ByKitParentAsync(int IdxPodlParent); Task<List<PODLExpModel>> ListPODL_ByKitParentAsync(int IdxPodlParent);
Task<List<PODLExpModel>> ListPODL_KitFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate); Task<List<PODLExpModel>> ListPODL_KitFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate, bool flagAttive, bool flagKitChild);
Task<PODLModel> PODL_getByKeyAsync(int idxPODL); Task<PODLModel> PODL_getByKeyAsync(int idxPODL);
@@ -187,7 +187,7 @@ namespace MP.Data.Repository.Production
} }
/// <inheritdoc /> /// <inheritdoc />
public async Task<List<PODLExpModel>> ListPODL_KitFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) public async Task<List<PODLExpModel>> ListPODL_KitFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate, bool flagAttive, bool flagKitChild)
{ {
await using var dbCtx = await GetMoonProContextAsync(); await using var dbCtx = await GetMoonProContextAsync();
var Lanc = new SqlParameter("@Lanciato", lanciato); var Lanc = new SqlParameter("@Lanciato", lanciato);
@@ -196,10 +196,13 @@ namespace MP.Data.Repository.Production
var IdxMacc = new SqlParameter("@IdxMacchina", idxMacchina); var IdxMacc = new SqlParameter("@IdxMacchina", idxMacchina);
var DateFrom = new SqlParameter("@DtInizio", startDate); var DateFrom = new SqlParameter("@DtInizio", startDate);
var DateTo = new SqlParameter("@DtFine", endDate); 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 return await dbCtx
.DbSetPODLExp .DbSetPODLExp
.FromSqlRaw("EXEC stp_PODL_getByFiltSpecKit @Lanciato, @KeyRich, @CodGruppo, @IdxMacchina, @DtInizio, @DtFine", Lanc, KeyRich, CodGrp, IdxMacc, DateFrom, DateTo) .FromSqlRaw("EXEC stp_PODL_getByFiltSpecKit @Lanciato, @KeyRich, @CodGruppo, @IdxMacchina, @DtInizio, @DtFine, @flgAttive, @flgPodlKit, @flgPodChild", Lanc, KeyRich, CodGrp, IdxMacc, DateFrom, DateTo, pFlagAtt, pFlagKit, pFlagKChild)
.AsNoTracking() .AsNoTracking()
.ToListAsync(); .ToListAsync();
} }
+76 -3
View File
@@ -10,7 +10,6 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion;
using static Org.BouncyCastle.Asn1.Cmp.Challenge;
namespace MP.Data.Services namespace MP.Data.Services
{ {
@@ -54,6 +53,35 @@ namespace MP.Data.Services
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary>
/// Cancellazione FusionCache (totale) - wrapper public
/// </summary>
/// <returns></returns>
public Task<bool> ForceFlushFusionCacheAsync()
{
return FlushFusionCacheAsync();
}
/// <summary>
/// Cancellazione Fusion Cache x tag (wrapper)
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
public Task<bool> ForceFlushFusionCacheAsync(string tag)
{
return FlushFusionCacheAsync(tag);
}
/// <summary>
/// Cancellazione Fusion Cache x list tags (wrapper)
/// </summary>
/// <param name="listTags"></param>
/// <returns></returns>
public Task<bool> ForceFlushFusionCacheAsync(List<string> listTags)
{
return FlushFusionCacheAsync(listTags);
}
/// <summary> /// <summary>
/// Recupero info IOB x TAB (da info registrate IOB-WIN--&gt; MP-IO) /// Recupero info IOB x TAB (da info registrate IOB-WIN--&gt; MP-IO)
/// </summary> /// </summary>
@@ -340,8 +368,8 @@ namespace MP.Data.Services
var cacheOptions = new FusionCacheEntryOptions() var cacheOptions = new FusionCacheEntryOptions()
.SetDuration(expiration) .SetDuration(expiration)
.SetFailSafe(true); .SetFailSafe(true);
// cache in RAM per 1/3 del tempo x risparmiare risorse // cache in RAM per 1/2 del tempo x risparmiare risorse
cacheOptions.MemoryCacheDuration = expiration / 3; cacheOptions.MemoryCacheDuration = expiration / 2;
var final = await _cache.GetOrSetAsync<T>( var final = await _cache.GetOrSetAsync<T>(
cacheKey, cacheKey,
@@ -525,5 +553,50 @@ namespace MP.Data.Services
private double slowLogThresh = 0; private double slowLogThresh = 0;
#endregion Private Fields #endregion Private Fields
#region Private Methods
/// <summary>
/// Cancellazione FusionCache (totale)
/// </summary>
/// <returns></returns>
private async Task<bool> FlushFusionCacheAsync()
{
await _cache.ClearAsync(allowFailSafe: false);
return true;
}
/// <summary>
/// Cancellazione FusionCache dato singolo tag
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
private async Task<bool> FlushFusionCacheAsync(string tag)
{
if (string.IsNullOrWhiteSpace(tag)) return false;
await _cache.RemoveByTagAsync(tag);
return true;
}
/// <summary>
/// Cancellazione FusionCache dato elenco tags
/// </summary>
/// <param name="listTags"></param>
/// <returns></returns>
private async Task<bool> FlushFusionCacheAsync(List<string> listTags)
{
if (listTags == null || listTags.Count == 0) return false;
// Generiamo i Task di rimozione ed eseguiamoli in parallelo su Redis/L1
var tasks = listTags
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.Select(tag => _cache.RemoveByTagAsync(tag).AsTask());
await Task.WhenAll(tasks);
return true;
}
#endregion Private Methods
} }
} }
+14
View File
@@ -30,6 +30,20 @@ namespace MP.Data.Services.IOC
/// <returns></returns> /// <returns></returns>
Task<bool> ClearFusionCache(); Task<bool> ClearFusionCache();
/// <summary>
/// Esegue flush della cache fusion dato tag
/// </summary>
/// <param name="tag"></param>
/// <returns></returns>
Task<bool> ClearFusionCache(string tag);
/// <summary>
/// Esegue flush della cache fusion data list tags
/// </summary>
/// <param name="listTags"></param>
/// <returns></returns>
Task<bool> ClearFusionCache(List<string> listTags);
/// <summary> /// <summary>
/// Aggiunta record MicroStato + EventList /// Aggiunta record MicroStato + EventList
/// </summary> /// </summary>
+135 -64
View File
@@ -58,6 +58,29 @@ namespace MP.Data.Services.IOC
return fatto; return fatto;
} }
/// <inheritdoc />
public async Task<bool> ClearFusionCache(string tag)
{
if (string.IsNullOrWhiteSpace(tag)) return false;
await _cache.RemoveByTagAsync(tag);
return true;
}
/// <inheritdoc />
public async Task<bool> ClearFusionCache(List<string> listTags)
{
if (listTags == null || listTags.Count == 0) return false;
// Generiamo i Task di rimozione ed eseguiamoli in parallelo su Redis/L1
var tasks = listTags
.Where(tag => !string.IsNullOrWhiteSpace(tag))
.Select(tag => _cache.RemoveByTagAsync(tag).AsTask());
await Task.WhenAll(tasks);
return true;
}
/// <inheritdoc /> /// <inheritdoc />
public async Task<bool> EvListMicroStatoInsertAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv) public async Task<bool> EvListMicroStatoInsertAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv)
{ {
@@ -68,6 +91,19 @@ namespace MP.Data.Services.IOC
/// <inheritdoc /> /// <inheritdoc />
public async Task<string> GetCurrOdlAsync(string idxMacchina) 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 result = "";
string currKey = $"{MP.Data.Utils.redisOdlCurrByMac}:{idxMacchina}"; string currKey = $"{MP.Data.Utils.redisOdlCurrByMac}:{idxMacchina}";
// cerco in redis dato valOut sel macchina... // cerco in redis dato valOut sel macchina...
@@ -79,11 +115,10 @@ namespace MP.Data.Services.IOC
else else
{ {
result = await GetCurrOdlByProdAsync(idxMacchina); result = await GetCurrOdlByProdAsync(idxMacchina);
// serializzo e salvo... _redisDb.StringSet(currKey, result, GetRandTOut(redisShortTimeCache));
rawData = JsonConvert.SerializeObject(result);
_redisDb.StringSet(currKey, rawData, GetRandTOut(redisLongTimeCache));
} }
return result; return result;
#endif
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -96,21 +131,48 @@ namespace MP.Data.Services.IOC
/// <inheritdoc /> /// <inheritdoc />
public async Task<bool> IobInsEnabAsync(string idxMacchina) public async Task<bool> IobInsEnabAsync(string idxMacchina)
{ {
string cacheKey = $"IOC_IobInsEnab_{idxMacchina}"; string cKey = $"IOC_IobInsEnab_{idxMacchina}";
return await GetOrFetchAsync(cacheKey, async () => return await GetOrFetchAsync(
operationName: "StatoProdMacchinaAsync",
cacheKey: cKey,
fetchFunc: async () =>
{ {
var key = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
var val = await _redisDb.HashGetAsync(rKey, "insEnabled");
string? val = await _redisDb.HashGetAsync(key, "insEnabled"); if (!val.HasValue)
if (val == null)
{ {
var data = await ResetDatiMacchinaAsync(idxMacchina); var data = await ResetDatiMacchinaAsync(idxMacchina);
data.TryGetValue("insEnabled", out val); // 2. Uso del pattern matching per evitare allocazioni e passaggi intermedi
return data != null
&& data.TryGetValue("insEnabled", out string? sVal)
&& IsStringTrue(sVal);
} }
return val != null && (val == "1" || val.ToLower() == "true"); // 3. Conversione efficiente da RedisValue a string (evita l'interpolazione $"{val}")
}, TimeSpan.FromSeconds(5)); 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
},
expiration: GetRandTOut(30),
tagList: ["IOC_IobInsEnab", cKey, idxMacchina]
);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -181,8 +243,7 @@ namespace MP.Data.Services.IOC
public async Task<int> PzCounterTcAsync(string idxMacchina) public async Task<int> PzCounterTcAsync(string idxMacchina)
{ {
int answ = -1; int answ = -1;
DateTime dataRif = DateTime.Now; var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now);
var datiProd = await StatoProdMacchinaAsync(idxMacchina, dataRif);
if (datiProd != null) if (datiProd != null)
{ {
answ = datiProd.PzTotODL; answ = datiProd.PzTotODL;
@@ -230,6 +291,7 @@ namespace MP.Data.Services.IOC
answ = currCount.ToString(); answ = currCount.ToString();
// salvo per meno tempo... // salvo per meno tempo...
await _redisDb.StringSetAsync(currKey, answ); await _redisDb.StringSetAsync(currKey, answ);
await ClearFusionCache(idxMacchina);
} }
} }
} }
@@ -261,7 +323,38 @@ namespace MP.Data.Services.IOC
#endregion Protected Fields #endregion Protected Fields
#region Protected Methods #region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly string _className;
private readonly IIocRepository _repo;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
/// Provider CultureInfo x parse valori(es dataora)
/// </summary>
private CultureInfo ciProvider = CultureInfo.InvariantCulture;
/// <summary>
/// Formato dataora standard x parsing
/// </summary>
private string dtFormat = "yyyyMMddHHmmssfff";
#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 #if false
/// <summary> /// <summary>
@@ -275,42 +368,15 @@ namespace MP.Data.Services.IOC
return TimeSpan.FromMinutes(rndValue); return TimeSpan.FromMinutes(rndValue);
} }
#endif #endif
#endregion Protected Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
#if false #if false
private readonly IFusionCache _cache; private readonly IFusionCache _cache;
#endif #endif
private readonly string _className;
private readonly IIocRepository _repo;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
/// Provider CultureInfo x parse valori(es dataora)
/// </summary>
private CultureInfo ciProvider = CultureInfo.InvariantCulture;
/// <summary>
/// Formato dataora standard x parsing
/// </summary>
private string dtFormat = "yyyyMMddHHmmssfff";
#if false #if false
private int redisLongTimeCache = 5; private int redisLongTimeCache = 5;
private int redisShortTimeCache = 2; private int redisShortTimeCache = 2;
#endif #endif
#endregion Private Fields
#region Private Methods
/// <summary> /// <summary>
/// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE /// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE
/// </summary> /// </summary>
@@ -467,7 +533,7 @@ namespace MP.Data.Services.IOC
} }
/// <summary> /// <summary>
/// Implementa gestione recupero cache da memoria o da obj esterno + cache memoria /// Implementa gestione recupero cache da memoria o da obj esterno + cache memoria (versione semplificata)
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="cacheKey"></param> /// <param name="cacheKey"></param>
@@ -493,26 +559,20 @@ namespace MP.Data.Services.IOC
{ {
bool answ = false; bool answ = false;
// ORA recupero da memoria redis... // ORA recupero da memoria redis...
try var rKey = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
{ RedisValue rawData = await _redisDb.HashGetAsync(rKey, (RedisValue)"sLogEnabled");
var currHash = MP.Data.Utils.RedKeyDatiMacc(idxMacchina, MpIoNS);
RedisValue rawData = await _redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled");
// se è vuoto... leggo da DB e popolo! // se è vuoto... leggo da DB e popolo!
if (!rawData.HasValue) if (!rawData.HasValue)
{ {
await ResetDatiMacchinaAsync(idxMacchina); var data = await ResetDatiMacchinaAsync(idxMacchina);
// riprovo
rawData = await _redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled");
}
// provo conversione // 2. Uso del pattern matching per evitare allocazioni e passaggi intermedi
bool.TryParse($"{rawData}", out answ); return data != null
&& data.TryGetValue("sLogEnabled", out string? sVal)
&& IsStringTrue(sVal);
} }
catch (Exception exc) string? sRedisVal = rawData;
{ return IsStringTrue(sRedisVal);
Log.Error($"Errore IobSLogEnabAsync | idxMacchina {idxMacchina}:{Environment.NewLine}{exc}");
}
return answ;
} }
/// <summary> /// <summary>
@@ -832,7 +892,7 @@ namespace MP.Data.Services.IOC
result.Add("palletChange", $"{dbResult.PalletChange}"); result.Add("palletChange", $"{dbResult.PalletChange}");
// durata cache in secondi dal valOut insEnabled... // durata cache in secondi dal valOut insEnabled...
//double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache); //double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache);
numSecCache = dbResult.InsEnabled ? redisShortTimeCache : redisLongTimeCache; numSecCache = dbResult.InsEnabled ? redisShortTimeCache * 2 : redisLongTimeCache;
} }
// dati master/slave // dati master/slave
@@ -853,10 +913,12 @@ namespace MP.Data.Services.IOC
// 3. Inseriamo i nuovi valori // 3. Inseriamo i nuovi valori
_ = transaction.HashSetAsync(redKey, entries); _ = transaction.HashSetAsync(redKey, entries);
// 4. Impostiamo l'expiration in un unico colpo // 4. Impostiamo l'expiration in un unico colpo
_ = transaction.KeyExpireAsync(redKey, TimeSpan.FromSeconds(numSecCache)); _ = transaction.KeyExpireAsync(redKey, GetRandTOut(numSecCache));
// Eseguiamo tutto in un unico viaggio verso Redis // Eseguiamo tutto in un unico viaggio verso Redis
bool success = await transaction.ExecuteAsync(); bool success = await transaction.ExecuteAsync();
await ForceFlushFusionCacheAsync(idxMacc);
return result; return result;
} }
@@ -1011,9 +1073,13 @@ namespace MP.Data.Services.IOC
/// <returns></returns> /// <returns></returns>
private async Task<StatoProdModel> StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false) private async Task<StatoProdModel> StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false)
{ {
string cacheKey = $"IOC_StatoProd_{idxMacchina}"; string cKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}";
return await GetOrFetchAsync(cacheKey, async () => return await GetOrFetchAsync(
operationName: "StatoProdMacchinaAsync",
cacheKey: cKey,
fetchFunc: async () =>
{ {
#if false
StatoProdModel? result = new StatoProdModel(); StatoProdModel? result = new StatoProdModel();
// cerco in _redisConn... // cerco in _redisConn...
string currKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}"; string currKey = $"{MP.Data.Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}";
@@ -1027,14 +1093,19 @@ namespace MP.Data.Services.IOC
result = await _repo.StatoProdMacchinaAsync(idxMacchina, dtReq); result = await _repo.StatoProdMacchinaAsync(idxMacchina, dtReq);
// serializzo e salvo... // serializzo e salvo...
rawData = JsonConvert.SerializeObject(result); rawData = JsonConvert.SerializeObject(result);
await _redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); await _redisDb.StringSetAsync(currKey, rawData, stdTTL);
} }
if (result == null) if (result == null)
{ {
result = new StatoProdModel(); result = new StatoProdModel();
} }
return result; return result;
}, TimeSpan.FromSeconds(3)); #endif
return await _repo.StatoProdMacchinaAsync(idxMacchina, dtReq);
},
expiration: GetRandTOut(redisLongTimeCache),
tagList: ["IOC_StatoProd", cKey, idxMacchina]
);
} }
/// <summary> /// <summary>
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.INVE</RootNamespace> <RootNamespace>MP.INVE</RootNamespace>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1117</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MAPOINVE </i> <i>Modulo MAPOINVE </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1117</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.408 8.16.2606.1117
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.408</version> <version>8.16.2606.1117</version>
<url>https://nexus.steamware.net/repository/SWS/MP-INVE/stable/LAST/MP.INVE.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-INVE/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+1 -4
View File
@@ -13,10 +13,9 @@ namespace MP.IOC.Controllers
{ {
#region Public Constructors #region Public Constructors
public BenchController(IConfiguration configuration, MpDataService DataService) public BenchController(MpDataService DataService)
{ {
Log.Info("Starting BenchController"); Log.Info("Starting BenchController");
_configuration = configuration;
DService = DataService; DService = DataService;
Log.Info("Avviata BenchController"); Log.Info("Avviata BenchController");
} }
@@ -378,8 +377,6 @@ namespace MP.IOC.Controllers
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields #endregion Private Fields
+41 -21
View File
@@ -20,7 +20,6 @@ namespace MP.IOC.Controllers
public IOBController(IConfiguration configuration, MpDataService DataService, IIocService IService) public IOBController(IConfiguration configuration, MpDataService DataService, IIocService IService)
{ {
_configuration = configuration;
DService = DataService; DService = DataService;
IOCService = IService; IOCService = IService;
} }
@@ -76,41 +75,61 @@ namespace MP.IOC.Controllers
return Ok(answ); return Ok(answ);
} }
/// <summary> #if false
/// GET: IOB/
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Alive()
{
return Ok("OK"); // Restituisce Status 200
}
/// <summary> /// <summary>
/// GET: IOB/enabled/SIMUL_03 /// GET: IOB/enabled/SIMUL_03
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("enabled/{id}")] [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 try
{ {
// Il metodo ora restituisce direttamente il booleano logico // Il metodo ora restituisce direttamente il booleano logico
bool isEnabled = await IOCService.IobInsEnabAsync(id); bool isEnabled = await IOCService.IobInsEnabAsync(id);
return isEnabled // Eliminazione delle allocazioni di stringhe e oggetti inutili
? Ok("OK") if (!isEnabled)
: UnprocessableEntity("NO"); {
Response.StatusCode = StatusCodes.Status422UnprocessableEntity;
return "NO";
}
// Status 200 di default, scrive direttamente sul body della response
return "OK";
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "Errore durante la verifica abilitazione per {Id}", id); 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> /// <summary>
/// Processa una chiamata POST per l'invio di un array Json di oggetti input (EVENTI) /// Processa una chiamata POST per l'invio di un array Json di oggetti input (EVENTI)
@@ -464,6 +483,7 @@ namespace MP.IOC.Controllers
return Ok(actValues); return Ok(actValues);
} }
#if false
/// <summary> /// <summary>
/// Recupera ODL corrente x macchina: /// Recupera ODL corrente x macchina:
/// GET: IOB/getCurrODL/SIMUL_03 /// GET: IOB/getCurrODL/SIMUL_03
@@ -480,16 +500,15 @@ namespace MP.IOC.Controllers
try try
{ {
var odl = await IOCService.GetCurrOdlAsync(id); var odl = await IOCService.GetCurrOdlAsync(id);
//var odl = await DService.GetCurrOdlAsync(id);
return Ok($"{odl}"); return Ok($"{odl}");
} }
catch (Exception exc) catch (Exception exc)
{ {
Log.Error(exc, "Errore GetCurrODL | macchina {MachineId}", id); Log.Error(exc, "Errore GetCurrODL | macchina {MachineId}", id);
return StatusCode(StatusCodes.Status500InternalServerError, "NO"); return StatusCode(StatusCodes.Status500InternalServerError, "NO");
//return StatusCode(StatusCodes.Status500InternalServerError, "Errore interno | GetCurrODL");
} }
} }
#endif
/// <summary> /// <summary>
/// Restituisce la quantità pezzi dell'odl correntemente in lavorazione sulla macchina... /// Restituisce la quantità pezzi dell'odl correntemente in lavorazione sulla macchina...
@@ -1089,6 +1108,7 @@ namespace MP.IOC.Controllers
try try
{ {
answ = await DService.saveCaricoPezzi(id, qty); answ = await DService.saveCaricoPezzi(id, qty);
await IOCService.ClearFusionCache(id);
return Ok(answ); return Ok(answ);
} }
catch (Exception exc) catch (Exception exc)
@@ -1207,6 +1227,7 @@ namespace MP.IOC.Controllers
return Ok(answ); return Ok(answ);
} }
#if false
/// <summary> /// <summary>
/// SALVA Counter x macchina restituendo il valore appena inviato o, se mancasse chiave /// SALVA Counter x macchina restituendo il valore appena inviato o, se mancasse chiave
/// redis, valore da DB /// /// redis, valore da DB ///
@@ -1238,6 +1259,7 @@ namespace MP.IOC.Controllers
} }
return Ok(answ); return Ok(answ);
} }
#endif
/// <summary> /// <summary>
/// Salva associazione tra macchina, device IOB chiamante e sue info /// Salva associazione tra macchina, device IOB chiamante e sue info
@@ -1451,8 +1473,6 @@ namespace MP.IOC.Controllers
#endregion Public Methods #endregion Public Methods
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private IIocService IOCService; private IIocService IOCService;
@@ -10,10 +10,9 @@ namespace MP.IOC.Controllers
{ {
#region Public Constructors #region Public Constructors
public RecipeArchiveController(IConfiguration configuration, MpDataService DataService) public RecipeArchiveController(MpDataService DataService)
{ {
Log.Info("Starting RecipeArchiveController"); Log.Info("Starting RecipeArchiveController");
_configuration = configuration;
DService = DataService; DService = DataService;
Log.Info("Avviata classe RecipeArchiveController"); Log.Info("Avviata classe RecipeArchiveController");
} }
@@ -121,8 +120,6 @@ namespace MP.IOC.Controllers
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields #endregion Private Fields
+1 -4
View File
@@ -12,10 +12,9 @@ namespace MP.IOC.Controllers
{ {
#region Public Constructors #region Public Constructors
public RecipeController(IConfiguration configuration, MpDataService DataService) public RecipeController(MpDataService DataService)
{ {
Log.Info("Starting RecipeController"); Log.Info("Starting RecipeController");
_configuration = configuration;
DService = DataService; DService = DataService;
Log.Info("Avviata RecipeController"); Log.Info("Avviata RecipeController");
} }
@@ -64,8 +63,6 @@ namespace MP.IOC.Controllers
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields #endregion Private Fields
+620
View File
@@ -0,0 +1,620 @@
using MP.Core.DTO;
using MP.Core.Objects;
using MP.Data.DbModels;
using MP.Data.DbModels.Anag;
using MP.Data.MgModels;
using StackExchange.Redis;
using static MP.Core.Objects.Enums;
namespace MP.IOC.Data
{
public interface IMpDataService
{
#region Public Methods
/// <summary>
/// Verifica se sia da reinviare un taskName alla macchina dall'elenco di quelli salvati (in
/// modalit\u00e0 upsert) se non scaduti
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="taskKey">tipo task</param>
/// <param name="taskVal">valore task</param>
/// <returns>true se il task \u00e8 stato reinviato</returns>
bool AddCheckTask4Machine(string idxMacchina, taskType taskKey, string taskVal);
/// <summary>
/// Aggiunge un parametro opzionale all'elenco dei saved task (in modalit\u00e0 upsert)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="taskKey">chiave del parametro</param>
/// <param name="taskVal">valore del parametro</param>
/// <returns>true se inserito</returns>
bool AddOptPar4Machine(string idxMacchina, string taskKey, string taskVal);
/// <summary>
/// Aggiunge un task all'elenco di quelli salvati (in modalit\u00e0 upsert)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="taskKey">tipo task</param>
/// <param name="taskVal">valore task</param>
/// <returns>true se inserito</returns>
bool AddTask4Machine(string idxMacchina, taskType taskKey, string taskVal);
/// <summary>
/// Aggiunge un set di task per macchina all'elenco di quelli salvati (in modalit\u00e0 upsert)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="taskDict"> Dizionario di task tipo-valore da salvare</param>
/// <returns>true se completato</returns>
Task<bool> AddTask4MacListAsync(string idxMacchina, Dictionary<taskType, string> taskDict);
/// <summary>
/// Inserimento record allarme su DB
/// </summary>
/// <param name="dtRif">Data evento</param>
/// <param name="idxMacchina">Nome macchina</param>
/// <param name="memAddress">Indirizzo memoria PLC</param>
/// <param name="memIndex">Indice memoria</param>
/// <param name="statusVal">Stato valOut</param>
/// <param name="valDecoded">Valore decodificato</param>
/// <returns>true se inserito</returns>
Task<bool> AlarmInsertAsync(DateTime dtRif, string idxMacchina, string memAddress, int memIndex, int statusVal, string valDecoded);
/// <summary>
/// Restituisce l'anagrafica STATI per intero con cache Fusion
/// </summary>
/// <returns>Lista di modelli AnagStatiModel</returns>
Task<List<AnagStatiModel>> AnagStatiGetAllAsync();
/// <summary>
/// Restituisce i modelli di ultimo articolo per data macchina, con cache Fusion
/// </summary>
/// <param name="idxMacc">idx macchina</param>
/// <returns>Lista di modelli AnagArticoliModel</returns>
Task<List<AnagArticoliModel>> ArticoliGetLastByMaccAsync(string idxMacc);
/// <summary>
/// Effettua lo split dell'ODL corrente per la macchina, con eventuale conferma produzione e
/// gestione slave
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="doConfirm">effettuare la conferma quantitativa</param>
/// <param name="qtyFromLast">imposta la qty del prossimo ODL da quello che si chiude</param>
/// <param name="matrOpr">matricola operatore</param>
/// <param name="roundStep">Step di arrotondamento quantit\u00e0</param>
/// <param name="keyRichiesta">Chiave esterna da associare all'ODL</param>
/// <returns>"OK" se successo, "KO" altrimenti</returns>
Task<string> AutoStartOdlAsync(string idxMacchina, bool doConfirm, bool qtyFromLast, int matrOpr, int roundStep = 100, string keyRichiesta = "");
/// <summary>
/// Calcola la ricetta su MongoDB dato modello ricetta corrente
/// </summary>
/// <param name="currRecipe">Modello ricetta da calcolare</param>
/// <returns>Risultato del calcolo ricetta</returns>
string CalcRecipe(RecipeModel currRecipe);
/// <summary>
/// Controlla se dal segnale di "microstato" deriva un evento da generare - modalit\u00e0 OFFLINE
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="valore">valore valOut ingresso</param>
/// <param name="dtEve">data-ora evento (server)</param>
/// <param name="contatore">sequenza dati inviati</param>
/// <param name="datiMaccCache">dati macchina in cache (opzionale, se null fa lookup)</param>
/// <returns>Risultato del processing</returns>
Task<inputComandoMapo> CheckMicroStatoAsync(string idxMacchina, string valore, DateTime dtEve, string contatore, Dictionary<string, string>? datiMaccCache = null);
/// <summary>
/// Restituisce l'elenco completo delle configurazioni da DB, con cache Fusion
/// </summary>
/// <returns>Lista di modelli ConfigModel</returns>
Task<List<ConfigModel>> ConfigGetAllAsync();
/// <summary>
/// Restituisce l'elenco delle decodifiche articoli filtrata per codice, con cache Fusion
/// </summary>
/// <param name="codArt">codice articolo (opzionale, se vuoto restituisce tutto)</param>
/// <returns>Lista di modelli DecNumArticoliModel</returns>
Task<List<DecNumArticoliModel>> DecNumArtGetFiltAsync(string codArt = "");
/// <summary>
/// Restituisce le date dei dossier per una macchina, con cache Fusion
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Lista di modelli DossierModel</returns>
Task<List<DossierModel>> DossierLastByMachAsync(string idxMacchina);
/// <summary>
/// Task completo per sistemazione dossier quotidiani mancanti
/// </summary>
/// <param name="idxMacc">idx macchina</param>
/// <returns>"OK" se completato</returns>
Task<string> FixDailyDossierAsync(string idxMacc);
/// <summary>
/// Restituisce il codice valOut dell'ODL corrente (con cache redis interna)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>codice valOut dell'ODL corrente</returns>
Task<string> GetCurrOdlAsync(string idxMacchina);
/// <summary>
/// Restituisce il modello dell'ultimo ODL per macchina, con cache Fusion
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Modello ODLExpModel</returns>
Task<ODLExpModel> GetLastOdlAsync(string idxMacchina);
/// <summary>
/// Effettua il calcolo della data-ora di riferimento per il server, correggendo il delta
/// tra orologio macchina e server
/// </summary>
/// <param name="dtEve">data-oras dell'evento (macchina)</param>
/// <param name="dtCurr">data-ora corrente (server)</param>
/// <returns>Data-ora evento corretta</returns>
DateTime GetSrvDtEvent(string dtEve, string dtCurr);
/// <summary>
/// Restituisce se la macchina sia abilitata all'inserimento dati da IOB
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>true se abilitato all'input</returns>
Task<bool> IobInsEnabAsync(string idxMacchina);
/// <summary>
/// Restituisce se la macchina sia abilitata come master di un impianto
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>true se \u00e8 master</returns>
Task<bool> IobIsMasterAsync(string idxMacchina);
/// <summary>
/// Restituisce se la macchina sia abilitata all'inserimento nel Signal Log
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>true se abilitato</returns>
Task<bool> IobSLogEnabAsync(string idxMacchina);
/// <summary>
/// Restituisce i valori ammessi per una tabella/colonna (con cache redis interna)
/// </summary>
/// <param name="tabName">nome tabella</param>
/// <param name="fieldName">nome campo</param>
/// <returns>Lista di ListValuesModel</returns>
Task<List<ListValuesModel>> ListValuesFilt(string tabName, string fieldName);
/// <summary>
/// Restituisce l'elenco completo delle relazioni macchine master-slave, con cache Fusion
/// </summary>
/// <returns>Lista di Macchine2SlaveModel</returns>
Task<List<Macchine2SlaveModel>> Macchine2SlaveGetAllAsync();
/// <summary>
/// Restituisce le macchine filtrate per gruppo, con cache Fusion
/// </summary>
/// <param name="codGruppo">codice gruppo (o "*" per tutto)</param>
/// <returns>Lista di MacchineModel</returns>
Task<List<MacchineModel>> MacchineGetFilt(string codGruppo);
/// <summary>
/// Restituisce il path delle ricette di una macchina, con cache Fusion
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Path delle ricette</returns>
Task<string> MacchineRecipeArchive(string idxMacchina);
/// <summary>
/// Restituisce la lista parametri correnti (ObjItemDTO) della macchina da Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Lista di ObjItemDTO</returns>
Task<List<ObjItemDTO>> MachineParamListAsync(string idxMacchina);
/// <summary>
/// Restituisce i parametri correnti della macchina che necessitano di write (writable + reqValue valorizzato)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Lista di ObjItemDTO</returns>
Task<List<ObjItemDTO>> MachineParamListPendingWriteAsync(string idxMacchina);
/// <summary>
/// Imposta i parametri correnti (ObjItemDTO) nella cache Redis della macchina
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="currData">dati parametri da impostare</param>
/// <returns>true se salvato</returns>
Task<bool> MachineParamListSetAsync(string idxMacchina, List<ObjItemDTO> currData);
/// <summary>
/// Effettua l'UPSERT degli elementi dei parametri macchina in Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="innovations">nuovi elementi da aggiungere/aggiornare</param>
/// <returns>true se completato</returns>
Task<bool> MachineParamUpsertAsync(string idxMacchina, List<ObjItemDTO> innovations);
/// <summary>
/// Restituisce i campi DatiMacchine + StatoMacchine come dizionario (da Redis o DB)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Dizionario KVP dei campi macchina</returns>
Task<Dictionary<string, string>> mDatiMacchineAsync(string idxMacchina);
/// <summary>
/// Restituisce i parametri ottimizzati per la macchine (Redis hash sync)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Dizionario KVP parametri</returns>
Dictionary<string, string> mOptParMacchina(string idxMacchina);
/// <summary>
/// Restituisce i task salvati della macchina (Redis hash sync)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Dizionario KVP dei task salvati</returns>
Dictionary<string, string> mSavedTaskMacchina(string idxMacchina);
/// <summary>
/// Restituisce l'elenco dalla tabella MappaStatoExpl, con o senza cache
/// </summary>
/// <param name="forceDb">se true forza lettura database senza cache</param>
/// <returns>Lista di MappaStatoExplModel</returns>
Task<List<MappaStatoExplModel>> MseGetAllAsync(bool forceDb = false);
/// <summary>
/// Restituisce il dizionario KVP della tabella Multi State Machine Ingressi (da Redis)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Array di KeyValuePair</returns>
Task<KeyValuePair<string, string>[]> mTabMSMIAsync(string idxMacchina);
/// <summary>
/// Restituisce i task in esecuzione per la macchina (Redis hash sync)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Dizionario KVP dei task</returns>
Dictionary<string, string> mTaskMacchina(string idxMacchina);
/// <summary>
/// Restituisce i task in esecuzione per la macchina (Redis hash async)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Dizionario KVP dei task</returns>
Task<Dictionary<string, string>> mTaskMacchinaAsync(string idxMacchina);
/// <summary>
/// Generazione automatica ODL giornalieri per la macchina
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="dataInizio">data di inizio generazione</param>
/// <param name="dataFine">data di fine generazione</param>
/// <param name="codArticolo">codice articolo</param>
/// <returns>true se generato con successo</returns>
Task<bool> OdlAutoDayGenAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo);
/// <summary>
/// Generazione automatica ODL giornalieri completa con tutti i parametri PO/TC
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="dataInizio">data inizio</param>
/// <param name="dataFine">data fine</param>
/// <param name="codArticolo">codice articolo</param>
/// <param name="pzPODL">pezzi per PODL</param>
/// <param name="pzPallet">pezzi per pallet</param>
/// <param name="keyRichiesta">chiave richiesta</param>
/// <param name="tcAssegnato">TC assegnato</param>
/// <param name="codGruppo">codice gruppo</param>
/// <param name="flgCreaPODL">flag crea PODL</param>
/// <param name="flgCheckTC">flag verifica TC</param>
/// <returns>true se generato con successo</returns>
Task<bool> OdlAutoDayGenFullAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo, int? pzPODL, int? pzPallet, string? keyRichiesta, int? tcAssegnato, string? codGruppo, bool flgCreaPODL, bool flgCheckTC);
/// <summary>
/// Restituisce l'ODL corrente per macchina, con cache Fusion
/// </summary>
/// <param name="IdxMacchina">idx macchina</param>
/// <returns>Modello ODLExpModel</returns>
Task<ODLExpModel> OdlCurrByMaccAsync(string IdxMacchina);
/// <summary>
/// Restituisce il modello PODL dato il suo indice, con cache Fusion
/// </summary>
/// <param name="idxPODL">indice PODL</param>
/// <returns>Modello PODLModel</returns>
Task<PODLModel> POdlGetByKey(int idxPODL);
/// <summary>
/// Restituisce i PODL per macchina/articolo, con cache Fusion
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="codArticolo">codice articolo</param>
/// <param name="codGruppo">codice gruppo</param>
/// <param name="onlyFree">solo PODL liberi</param>
/// <returns>Lista di PODLExpModel</returns>
Task<List<PODLExpModel>> POdlGetByMaccArtAsync(string idxMacchina, string codArticolo, string codGruppo, bool onlyFree);
/// <summary>
/// Processa la registrazione di un flusso (FL) da IOB
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="flux">codice flusso (es. Ingresso, Uscita)</param>
/// <param name="valore">valore valOut del flusso</param>
/// <param name="dtEve">data-ora dell'evento (macchina)</param>
/// <param name="dtCurr">data-ora corrente (server)</param>
/// <param name="contatore">contatore invio</param>
/// <param name="disabKA">se true disabilita la scrittura del keepalive</param>
/// <returns>"OK" se completato</returns>
Task<string> ProcessFluxLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, bool disabKA);
/// <summary>
/// Processa un input completo da IOB (verifica parametri, log segnali, processing microstato)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="valore">valore valOut</param>
/// <param name="dtEve">data-ora evento (macchina)</param>
/// <param name="dtCurr">data-ora corrente (server)</param>
/// <param name="contatore">contatore</param>
/// <returns>"OK" se completato, error message in caso contrario</returns>
Task<string> ProcessInputAsync(string idxMacchina, string valore, string dtEve, string dtCurr, string contatore);
/// <summary>
/// Processa un UserLog registrato da IOB (DI=dichiarazioni, RC=controlli, RS=scarti)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="flux">tipo flusso: DI (Dichiarazione), RC (Controllo), RS (Scarto)</param>
/// <param name="valore">valore/testo</param>
/// <param name="dtEve">data-ora evento</param>
/// <param name="dtCurr">data-ora corrente</param>
/// <param name="contatore">contatore invio dati</param>
/// <param name="matrOpr">matricola operatore</param>
/// <param name="label">causale scarto o tagCode</param>
/// <param name="valNum">esitoOk (0/1) o quantit\u00e0 scarto</param>
/// <returns>"OK" se completato</returns>
Task<string> ProcessUserLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, int matrOpr, string label, int valNum);
/// <summary>
/// Restituisce il contapezzi salvato in Redis per la macchina
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Contatore pezzi (-1 se non trovato)</returns>
Task<int> pzCounter(string idxMacchina);
/// <summary>
/// Restituisce il contapezzi come conteggio da TCRilevati (dal DB)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Conteggio pezzi (-1 se non trovato)</returns>
Task<int> PzCounterTcAsync(string idxMacchina);
/// <summary>
/// Ricerca la ricetta su MongoDB dato l'indice del PODL
/// </summary>
/// <param name="idxPODL">indice PODL di riferimento</param>
/// <returns>Modello ricetta o null</returns>
Task<RecipeModel?> RecipeGetByPODL(int idxPODL);
/// <summary>
/// Effettua il conteggio delle chiavi Redis che corrispondono a un pattern
/// </summary>
/// <param name="keyPattern">pattern di ricerca</param>
/// <returns>Numero di chiavi trovate</returns>
int RedisCountKey(string keyPattern);
/// <summary>
/// Elimina una chiave dalla memoria Redis
/// </summary>
/// <param name="keyVal">chiave da eliminare</param>
/// <returns>true se eliminata</returns>
bool RedisDelKey(string keyVal);
/// <summary>
/// Elimina una chiave dalla memoria Redis (async)
/// </summary>
/// <param name="keyVal">chiave da eliminare</param>
/// <returns>true se eliminata</returns>
Task<bool> RedisDelKeyAsync(RedisKey keyVal);
/// <summary>
/// Esegue il flush di tutte le chiavi Redis che corrispondono a un pattern
/// </summary>
/// <param name="pattern">pattern di ricerca</param>
/// <returns>true se completato</returns>
Task<bool> RedisFlushPatternAsync(RedisValue pattern);
/// <summary>
/// Leggo un hash Redis come array di KeyValuePair (sync)
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <returns>Array di KeyValuePair</returns>
KeyValuePair<string, string>[] RedisGetHash(RedisKey redKey);
/// <summary>
/// Leggo un hash Redis come array di KeyValuePair (async)
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <returns>Array di KeyValuePair</returns>
Task<KeyValuePair<string, string>[]> RedisGetHashAsync(RedisKey redKey);
/// <summary>
/// Leggo un hash Redis come dizionario (sync)
/// </summary>
/// <param name="hashKey">chiave Redis</param>
/// <returns>Dizionario KVP</returns>
Dictionary<string, string> RedisGetHashDict(RedisKey hashKey);
/// <summary>
/// Leggo un hash Redis come dizionario (async)
/// </summary>
/// <param name="hashKey">chiave Redis</param>
/// <returns>Dizionario KVP</returns>
Task<Dictionary<string, string>> RedisGetHashDictAsync(RedisKey hashKey);
/// <summary>
/// Leggo un singolo campo di un hash Redis (async)
/// </summary>
/// <param name="key">chiave Redis</param>
/// <param name="hashField">nome campo hash</param>
/// <returns>Valore del campo</returns>
Task<RedisValue> RedisGetHashFieldAsync(RedisKey key, string hashField);
/// <summary>
/// Verifica se una chiave esista in Redis (sync)
/// </summary>
/// <param name="key">chiave Redis</param>
/// <returns>true se la chiave esiste</returns>
bool RedisKeyPresent(RedisKey key);
/// <summary>
/// Verifica se una chiave esista in Redis (async)
/// </summary>
/// <param name="key">chiave Redis</param>
/// <returns>true se la chiave esiste</returns>
Task<bool> RedisKeyPresentAsync(RedisKey key);
/// <summary>
/// Imposta un hash Redis con expiration opzionale (sync)
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <param name="valori">array di coppie chiave-valore da inserire</param>
/// <param name="expireSeconds">TTL in secondi (-1 = nessuna expiration)</param>
void RedisSetHash(RedisKey redKey, KeyValuePair<string, string>[] valori, double expireSeconds = -1.0);
/// <summary>
/// Imposta un hash Redis con expiration opzionale (async)
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <param name="valori">array di coppie chiave-valore da inserire</param>
/// <param name="expireSeconds">TTL in secondi (-1 = nessuna expiration)</param>
Task RedisSetHashAsync(RedisKey redKey, KeyValuePair<string, string>[] valori, double expireSeconds = -1.0);
/// <summary>
/// Imposta un hash Redis con expiration opzionale (sync) da dizionario
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <param name="valori">dizionario di coppie chiave-valore da inserire</param>
/// <param name="expireSeconds">TTL in secondi (-1 = nessuna expiration)</param>
void RedisSetHashDict(RedisKey redKey, Dictionary<string, string> valori, double expireSeconds = -1.0);
/// <summary>
/// Imposta un hash Redis con expiration opzionale (async) da dizionario
/// </summary>
/// <param name="redKey">chiave Redis</param>
/// <param name="valori">dizionario di coppie chiave-valore da inserire</param>
/// <param name="expireSeconds">TTL in secondi (-1 = nessuna expiration)</param>
Task RedisSetHashDictAsync(RedisKey redKey, Dictionary<string, string> valori, double expireSeconds = -1.0);
/// <summary>
/// Inserimento record RemoteRebootLog con eventuale pulizia dei record vecchi
/// </summary>
/// <param name="newRec">record da inserire</param>
/// <returns>true se completato</returns>
Task<bool> RemRebootLogAddAsync(RemoteRebootLogModel newRec);
/// <summary>
/// Elimina un task da elenco Redis per l'impianto indicato
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="tName">nome task da eliminare</param>
/// <returns>Dizionario KVP rimanente</returns>
Task<Dictionary<string, string>> RemTask2ExeMacchinaAsync(string idxMacchina, taskType tName);
/// <summary>
/// Resetta la tabella Multi State Machine Ingressi per macchina rileggendola dal DB
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <returns>Array di KVP della State Machine Ingressi</returns>
Task<KeyValuePair<string, string>[]> resetMSMIAsync(string idxMacchina);
/// <summary>
/// Registra la movimentazione di un carico pezzi su Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="qty">quantit\u00e0 da registrare</param>
/// <returns>"OK" se completato</returns>
Task<string> saveCaricoPezzi(string idxMacchina, string qty);
/// <summary>
/// Salva un elenco di dati macchina in DB
/// </summary>
/// <param name="id">identificativo della serie dati</param>
/// <param name="dataList">elenco di dati macchina da salvare</param>
/// <returns>true se salvato</returns>
Task<bool> SaveDataItemsAsync(string id, List<MachDataItem> dataList);
/// <summary>
/// Salva i dati macchina-IOB (serializzazione JSON) su Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="serData">dati JSON serializzati</param>
/// <returns>true se salvato</returns>
Task<bool> SaveMachine2Iob(string idxMacchina, string serData);
/// <summary>
/// Salva la configurazione della macchina come dizionario su Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="currDict">dizionario di configurazione</param>
/// <returns>true se salvato</returns>
Task<bool> SaveMachineIobConf(string idxMacchina, Dictionary<string, string> currDict);
/// <summary>
/// Salva il segnale di "microstato" come record SignalLog su DB (async)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="valore">valOut ingresso</param>
/// <param name="dtEve">data-ora evento (server)</param>
/// <param name="contatore">contatore sequenza dati inviati</param>
/// <returns>true se salvato</returns>
Task<bool> saveSigLogAsync(string idxMacchina, string valore, DateTime dtEve, int contatore);
/// <summary>
/// Scrive un evento di keepalive se non presente in Redis con TTL
/// </summary>
/// <param name="IdxMacchina">idx macchina</param>
/// <param name="oraMacchina">ora macchina</param>
Task ScriviKeepAliveAsync(string IdxMacchina, DateTime oraMacchina);
/// <summary>
/// Salva la configurazione YAML completa dell'IOB su Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="iobConfFull">configurazione YAML completa</param>
/// <returns>true se salvata</returns>
Task<bool> SetIobConfYamlAsync(string idxMacchina, string iobConfFull);
/// <summary>
/// Salva la mappatura memoria PLC su Redis
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="currMap">mappatura PLC da salvare</param>
/// <returns>true se salvata</returns>
Task<bool> SetIobMemMap(string idxMacchina, PlcMemMapDto currMap);
/// <summary>
/// Restituisce la tabella key-value della State Machine Ingressi per famiglia,
/// rileggendola dal DB e salvandola in Redis
/// </summary>
/// <param name="idxFamIn">idx famiglia ingressi</param>
/// <returns>Array di KVP (currentMicroStato_nVal -> IdxTipoEv_nStato)</returns>
Task<KeyValuePair<string, string>[]> StateMachInByKeyAsync(int idxFamIn);
/// <summary>
/// Effettua l'UPSERT degli oggetti corrente della macchina su Redis (legacy compatibilit\u00e0)
/// </summary>
/// <param name="idxMacchina">idx macchina</param>
/// <param name="innovations">dati da aggiornare/aggiungere</param>
/// <returns>true se completato</returns>
Task<bool> UpsertCurrObjItemsAsync(string idxMacchina, List<ObjItemDTO> innovations);
/// <summary>
/// Restituisce il valore SPECIFICATO per la state machine ingressi in formato hash Redis
/// </summary>
/// <param name="idxFamIn">idx famiglia ingressi</param>
/// <param name="idxMicroStato">idx microstato</param>
/// <param name="valoreIn">valore ingresso</param>
/// <returns>valore (IdxFamIn_nValore) dalla tab SMI Redis</returns>
Task<string> ValoreSmiAsync(int idxFamIn, int idxMicroStato, int valoreIn);
#endregion Public Methods
}
}
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
using MP.Data.Services.IOC;
using NLog;
namespace MP.IOC.Endpoints
{
public static class IobEndpoints
{
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))
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return "Missing ID";
}
try
{
bool isEnabled = await iocService.IobInsEnabAsync(id);
if (!isEnabled)
{
context.Response.StatusCode = StatusCodes.Status422UnprocessableEntity;
return "NO";
}
return "OK";
}
catch (Exception ex)
{
Log.Error(ex, "Errore durante la verifica abilitazione per {Id}", id);
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return "Errore interno del server";
}
});
// 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";
}
});
}
private static Logger Log = LogManager.GetCurrentClassLogger();
}
}
+6 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1212</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -31,10 +31,15 @@
<PackageReference Include="EgwCoreLib.Razor" /> <PackageReference Include="EgwCoreLib.Razor" />
<PackageReference Include="EgwCoreLib.Utils" /> <PackageReference Include="EgwCoreLib.Utils" />
<PackageReference Include="Microsoft.AspNetCore.Components" /> <PackageReference Include="Microsoft.AspNetCore.Components" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="NLog" /> <PackageReference Include="NLog" />
<PackageReference Include="NLog.Web.AspNetCore" /> <PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="Snappier" /> <PackageReference Include="Snappier" />
<PackageReference Include="Swashbuckle.AspNetCore" /> <PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="ZiggyCreatures.FusionCache" />
<PackageReference Include="ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis" />
<PackageReference Include="ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+21 -2
View File
@@ -1,16 +1,19 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using MP.Core.Conf; using MP.Core.Conf;
using MP.Data; using MP.Data;
using MP.IOC.Components; using MP.IOC.Components;
using MP.IOC.Data; using MP.IOC.Data;
using MP.IOC.Endpoints;
using MP.IOC.Services; using MP.IOC.Services;
using NLog; using NLog;
using NLog.Web; using NLog.Web;
using StackExchange.Redis; using StackExchange.Redis;
using System.Reflection; using System.Reflection;
using ZiggyCreatures.Caching.Fusion; using ZiggyCreatures.Caching.Fusion;
using ZiggyCreatures.Caching.Fusion.Backplane.StackExchangeRedis;
using ZiggyCreatures.Caching.Fusion.Serialization; using ZiggyCreatures.Caching.Fusion.Serialization;
using ZiggyCreatures.Caching.Fusion.Serialization.NewtonsoftJson; using ZiggyCreatures.Caching.Fusion.Serialization.NewtonsoftJson;
@@ -92,6 +95,12 @@ builder.Services.AddSwaggerGen(c =>
var redisMux = ConnectionMultiplexer.Connect(confRedis); var redisMux = ConnectionMultiplexer.Connect(confRedis);
builder.Services.AddSingleton<IConnectionMultiplexer>(redisMux); builder.Services.AddSingleton<IConnectionMultiplexer>(redisMux);
// Distributed cache (necessario per FusionCache L2 Redis)
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = confRedis;
});
// oggetto principale accesso dati // oggetto principale accesso dati
//builder.Services.AddScoped<MpDataService>(); //builder.Services.AddScoped<MpDataService>();
builder.Services.AddSingleton<MpDataService>(); builder.Services.AddSingleton<MpDataService>();
@@ -99,8 +108,14 @@ builder.Services.AddSingleton<MpDataService>();
// 1. Registra il serializzatore NewtonsoftJson per FusionCache // 1. Registra il serializzatore NewtonsoftJson per FusionCache
builder.Services.AddSingleton<IFusionCacheSerializer>(new FusionCacheNewtonsoftJsonSerializer()); builder.Services.AddSingleton<IFusionCacheSerializer>(new FusionCacheNewtonsoftJsonSerializer());
// 2. Configura FusionCache solo per la memoria (L1) // 2. Configura FusionCache (L1 Memory + L2 Redis Distributed + L3 DB via factory)
builder.Services.AddFusionCache() builder.Services.AddFusionCache()
.WithDistributedCache(sp => sp.GetRequiredService<IDistributedCache>())
.WithSerializer(new FusionCacheNewtonsoftJsonSerializer())
.WithBackplane(new RedisBackplane(new RedisBackplaneOptions
{
ConnectionMultiplexerFactory = () => Task.FromResult<IConnectionMultiplexer>(redisMux)
}))
.WithDefaultEntryOptions(options => .WithDefaultEntryOptions(options =>
{ {
// Durata di default dei dati in memoria // Durata di default dei dati in memoria
@@ -156,9 +171,13 @@ app.UseDefaultFiles();
app.UseStaticFiles(); app.UseStaticFiles();
app.UseAntiforgery(); app.UseAntiforgery();
// Mappatura delle API // Mappatura MinimalApi da file ext x metodi high perf
app.MapIobHighPerformanceEndpoints();
// Mappatura delle API "standard"
app.MapControllers(); app.MapControllers();
// Mappatura della Dashboard Blazor // Mappatura della Dashboard Blazor
app.MapRazorComponents<App>() app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(); .AddInteractiveServerRenderMode();
+130
View File
@@ -0,0 +1,130 @@
# MP.IOC
MAPO IOC: WebApi rest per la gestione delle chiamate dagli applicativi remoti sul campo (IOB-PI su Raspberry Pi e IOB-WIN su PC Windows) che si occupano di comunicare con le varie macchine di processo i dati da trasmettere al MES.
È la nuova implementazione del modulo IO (il precedente era fatto in .NET Framework 4.7.2) su cui si sta dirigendo lo sviluppo della soluzione MES MAPO.
Integra raccolta di informazioni riguardo ai compiti svolti e questi sono ottimizzati per l'impiego di cache e ottimizzazioni varie su ogni strato del progetto.
**Build:** ✅ 0 errori, 12 warnings
## Sezioni Principali
- Api IOB (`api/IOB`) — endpoint principale per comunicazioni da campo (52 metodi)
- Benchmark Redis (`api/Bench`) — tool di test per debugging Redis
- Api Ricette (`api/Recipe`) — recupero ricette per PODL
- Dashboard Blazor — interfaccia web interattiva
## Architettura
### Livello Controller (MP.IOC/Controllers/)
| Controller | Routing | Methods | Descrizione |
|------------|---------|---------|-------------|
| `IOBController` | `api/IOB` | 52 | Endpoint principale per IOB-PI/IOB-WIN |
| `BenchController` | `api/Bench` | 10 | Test/benchmark Redis |
| `RecipeController` | `api/Recipe` | 2 | API ricette (JSON) |
| `RecipeArchiveController` | `api/RecipeArchive` | 2 | API archive ricette (file) |
### Livello Dati
| Componente | Tipologia | Descrizione |
|------------|-----------|-------------|
| `MpDataService` | Singleton | Service di accesso dati principale (~3516 righe) |
| `MpIocController` | Singleton | 82 metodi EFCore nel progetto MP.Data |
| `Redis` | 2 connessioni | `redisConn`, `redisConnAdmin` per dati IOB |
| `MongoDB` | Via `MpMongoController` | Storage ricette |
### Livello DbContext
| DbContext | ConnStr | Uso |
|-----------|---------|-----|
| `MoonProContext` | `MP.Data` | Tabelle anagrafiche, ODL, produzione, macchine |
| `MoonPro_UtilsContext` | `MP.Utils` | Tabelle utility (VMSFD, MicroStati) |
| `MoonPro_FluxContext` | `MP.Flux` | FluxLog, configurazioni flusso |
### Livello DI
Registrato via `AddIocDataLayer()` in `MP.Data/DataServiceCollectionExtensions.cs`:
```
Singleton: IMtcSetupRepository, IMtcSetupService, ProductionRepository, MpIocController, MpDataService, IFusionCacheSerializer, IConnectionMultiplexer, IFusionCache
Scoped: IIocRepository, IStatsAggrRepository, IStatsDetailRepository, IIocService, IStatsAggrService, IStatsDetailService
```
### Livello Infrastruttura
- **FusionCache** — ✅ **L1 Memory + L2 Redis Distributed + Redis Backplane** (migrazione giugno 2026)
- **MessagePipe** — Broadcasting real-time tra servizi
- **OpenTelemetry** — Tracing configurabile (non abilitato in MP.IOC)
- **Swagger** — Documentazione API in sviluppo
## Refactoring Completati
### ✅ Fase 1: Anti-Pattern Controllers
Rimossi `static IConfiguration _configuration` da tutti i 4 controller (`IOBController`, `BenchController`, `RecipeController`, `RecipeArchiveController`) e rimossa la dead code assignment dai costruttori.
### ✅ Fase 2: FusionCache + Redis Backplane
**Pacchetti aggiunti** (`MP.IOC.csproj`):
- `Microsoft.Extensions.Caching.StackExchangeRedis`
- `ZiggyCreatures.FusionCache`
- `ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis`
- `ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson`
**Configurazione** (`Program.cs`):
- FusionCache attivo con **L1 Memory** + **L2 Redis Distributed** + **Redis Backplane**
- Serializer: `FusionCacheNewtonsoftJsonSerializer`
- Default Duration: 1 minuto con jitter max 5 secondi
**Helper aggiunto** (`MpDataService.cs`):
- `GetOrFetchAsync<T>()` — wrapper FusionCache con ActivitySource tracking e source tagging (MEMORY/REDIS/DB)
**18 metodi migrati** dal pattern manuale Redis (`redisDb.StringGetAsync`/`StringSetAsync`) → FusionCache:
| # | Metodo | Fetch From |
|---|--------|------------|
| 1 | `AnagStatiGetAllAsync` | `IocDbController` |
| 2 | `ArticoliGetLastByMaccAsync` | `IocDbController` |
| 3 | `ConfigGetAllAsync` | `IocDbController` |
| 4 | `DecNumArtGetFiltAsync` | `IocDbController` |
| 5 | `DossierLastByMachAsync` | `IocDbController` |
| 6 | `GetLastOdlAsync` | `IocDbController` |
| 7 | `ListValuesFilt` | `IocDbController` |
| 8 | `Macchine2SlaveGetAllAsync` | `IocDbController` |
| 9 | `MacchineGetFilt` | `_productionRepository` |
| 10 | `MacchineRecipeArchive` | `_productionRepository` |
| 11 | `MseGetAllAsync` | `IocDbController` (+ forceDb support) |
| 12 | `OdlCurrByMaccAsync` | `IocDbController` |
| 13 | `POdlGetByKey` | `_productionRepository` |
| 14 | `POdlGetByMaccArtAsync` | `IocDbController` |
| 15 | `ConfFluxMach` | `IocDbController` |
| 16 | `FluxLogFirstByMachAsync` | `IocDbController` |
| 17 | `FluxLogGetLastByMachAsync` | `IocDbController` |
### ✅ Build Verification
| Soluzione | Errori | Warnings | Stato |
|-----------|--------|----------|-------|
| MP-IOC | 0 | 12 | ✅ |
| MP-SPEC | 0 | vari | ✅ |
| MP-LAND | 0 | 3 | ✅ |
| MP-MON | 0 | vari | ✅ |
## Refactoring in Corso (Riferimento)
### Piano Generale (Vedi refactor_plan.md)
1. ~~**Fase 1 — Anti-Pattern**~~ — ✅ **Completata**
2. ~~**Fase 2 — FusionCache**~~ — ✅ **Completata** (18 metodi migrati)
3. **Fase 3 — Repository IoC** — Scomporre `MpIocController` (1480 righe, 82 metodi) in 7-10 repository specialistici
4. **Fase 4 — Scomposizione MpDataService** — Il monolite da 3516 righe suddiviso in servizi tematici
5. **Fase 5 — Build & Verifica** — Validazione finale
**Stato attuale:** Fasi 1-2 completate. Build stabile 0 errori.
## Prossimi Passi
- **Fase 3**: Creare repository specialistici per `MpIocController` (Vedi refactor_plan.md per il inventario completo)
- **Metodi atipici**: Verificare manualmente 4 metodi con pattern non-standards (`GetCurrOdlAsync`, `StatoProdMacchinaAsync` (private), `verificaIdxMacchinaAsync`, `ListMasterAsync`/`ListSlaveAsync`)
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MP-IOC </i> <i>Modulo MP-IOC </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1212</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.408 8.16.2606.1212
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.408</version> <version>8.16.2606.1212</version>
<url>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+3 -2
View File
@@ -6,7 +6,8 @@
"Microsoft.EntityFrameworkCore.Database.Command": "Warning", "Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning", "Microsoft.EntityFrameworkCore.Infrastructure": "Warning",
"Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware": "None", "Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware": "None",
"Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware": "None" "Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware": "None",
"ZiggyCreatures.Caching.Fusion": "Warning"
} }
}, },
"NLog": { "NLog": {
@@ -24,7 +25,7 @@
"logfile": { "logfile": {
"type": "File", "type": "File",
"fileName": "${basedir}/logs/${shortdate}.log", "fileName": "${basedir}/logs/${shortdate}.log",
"keepFileOpen": false, "keepFileOpen": true,
"archiveEvery": "Day", "archiveEvery": "Day",
"archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log",
"archiveNumbering": "DateAndSequence", "archiveNumbering": "DateAndSequence",
+235
View File
@@ -0,0 +1,235 @@
# Piano di Refactoring: MP.IOC
## Obiettivo
Modernizzare il progetto MP.IOC allineandolo agli standard architettonici definiti in MP.SPEC:
1. Rimuovere l'uso di `MpIocController` come servizio di accesso dati diretto
2. Migrare la logica di `MpDataService` ai repository esistenti (`IIocRepository`, `IStatsAggrRepository`, etc.)
3. Standardizzare il caching manuale Redis → FusionCache `GetOrFetchAsync`
4. Correggere tutti gli anti-pattern statici e DI
## Stato Attuale
| File | Riga | Tipo | Descrizione |
|------|------|------|-------------|
| `IOBController.cs` | 1675 | ✅ Controller ASP.NET | 52 endpoints REST (api/IOB) |
| `BenchController.cs` | 387 | ✅ Controller ASP.NET | Test/bench Redis |
| `RecipeController.cs` | 73 | ✅ Controller ASP.NET | API ricette (2 metodi) |
| `RecipeArchiveController.cs` | 130 | ✅ Controller ASP.NET | API ricette file (2 metodi) |
| `MpDataService.cs` | 3516 | ❌ Data Hub | Singleton, ~100 metodi, bypassa repository |
| `MpIocController.cs` | 1480 | ⚠️ Wrong location | 82 metodi EFCore in MP.Data, usato come servizio |
### Struttura DI Attuale (Post-Refactoring)
```
Program.cs
├── AddIocDataLayer() da MP.Data
│ ├── MpIocController ── Singleton ← usato direttamente da MpDataService
│ ├── IIocRepository ── Scoped ← mai usato da MpDataService
│ ├── IStatsAggrRepository ── Scoped ← mai usato
│ └── IStatsDetailRepository ── Scoped ← mai usato
├── MpDataService ── Singleton ← chiama IocDbController (statico), usa IFusionCache
└── IFusionCache ── L1 Memory + L2 Redis Distributed + Redis Backplane ✅
```
### Problemi Chiave
1. **MpIocController come servizio** — 82 metodi EFCore classificati ingannevolmente come "Controller" ma in realtà sono un servizio di accesso dati, registrato come Singleton con DbContext che non è thread-safe
2. **MpDataService bypassa repository** — ~80% dei metodi chiama `IocDbController.XXX()` direttamente invece di usare `IIocRepository`
3. **Caching misto** — Parte migrato a FusionCache, parte ancora in manual StringSet/StringGet
4. **Singleton lifetime mismatch** — MpDataService è Singleton, registra campi statici per IoC Controller e Mongo Controller che vengono sovrascritti
## Piano di Refactoring
### Fase 1: Pulizia Anti-Pattern (Quick Wins) ✅ COMPLETATA
Rimosso `static IConfiguration _configuration` da tutti i 4 controller e rimossa la dead assignment dai costruttori:
| Controller | Prima | Dopo |
|------------|-------|------|
| `IOBController` | `IConfiguration + static field` | Nessun param (dead code), field rimosso |
| `BenchController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso |
| `RecipeController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso |
| `RecipeArchiveController` | `IConfiguration + static field` | Solo `MpDataService`, field rimosso |
### Fase 2: FusionCache + Redis Backplane ✅ COMPLETATA
#### 2.1 Pacchetti Aggiunti (MP.IOC.csproj)
| Pacchetto | Stato |
|-----------|-------|
| `Microsoft.Extensions.Caching.StackExchangeRedis` | ✅ |
| `ZiggyCreatures.FusionCache` | ✅ |
| `ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis` | ✅ |
| `ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson` | ✅ |
#### 2.2 Configurazione FusionCache (Program.cs)
```csharp
// L1 Memory + L2 Redis Distributed + Redis Backplane
builder.Services.AddStackExchangeRedisCache(options =>
options.Configuration = confRedis);
builder.Services.AddFusionCache()
.WithDistributedCache(sp => sp.GetRequiredService<IDistributedCache>())
.WithSerializer(new FusionCacheNewtonsoftJsonSerializer())
.WithBackplane(new RedisBackplane(new RedisBackplaneOptions {
ConnectionMultiplexerFactory = () => Task.FromResult<IConnectionMultiplexer>(redisMux)
}))
.WithDefaultEntryOptions(options => {
options.Duration = TimeSpan.FromMinutes(1);
options.JitterMaxDuration = TimeSpan.FromSeconds(5);
});
```
#### 2.3 Migrate 14 metodi da manual Redis a FusionCache
| # | Metodo | Categoria | Fetch From |
|---|--------|-----------|------------|
| 1 | `AnagStatiGetAllAsync` | Anagrafica | `IocDbController.AnagStatiGetAllAsync()` |
| 2 | `ArticoliGetLastByMaccAsync` | Anagrafica | `IocDbController.ArticoliGetLastByMaccAsync()` |
| 3 | `ConfigGetAllAsync` | Anagrafica | `IocDbController.ConfigGetAllAsync()` |
| 4 | `DecNumArtGetFiltAsync` | Anagrafica | `IocDbController.DecNumArtGetFiltAsync()` |
| 5 | `DossierLastByMachAsync` | Dossier | `IocDbController.DossGetLastByMaccAsync()` |
| 6 | `GetLastOdlAsync` | Produzione | `IocDbController.OdlLastByMaccAsync()` |
| 7 | `ListValuesFilt` | Configurazione | `IocDbController.ListValuesFiltAsync()` |
| 8 | `Macchine2SlaveGetAllAsync` | Machine | `IocDbController.Macchine2SlaveAsync()` |
| 9 | `MacchineGetFilt` | Machine | `_productionRepository.MacchineGetFiltAsync()` |
| 10 | `MacchineRecipeArchive` | Machine | `_productionRepository.MacchineGetFiltAsync("*")` |
| 11 | `MseGetAllAsync` | Stato | `IocDbController.MseGetAllAsync(maxAge)` (+ forceDb) |
| 12 | `OdlCurrByMaccAsync` | Produzione | `IocDbController.OdlCurrByMaccAsync()` |
| 13 | `POdlGetByKey` | Produzione | `_productionRepository.PODL_getByKeyAsync()` |
| 14 | `POdlGetByMaccArtAsync` | Produzione | `IocDbController.POdlGetByMaccArtAsync()` |
| 15 | `ConfFluxMach` | FluxLog | `IocDbController.ConfFluxFiltAsync()` |
| 16 | `FluxLogFirstByMachAsync` | FluxLog | `IocDbController.FluxLogFirstByMaccAsync()` |
| 17 | `FluxLogGetLastByMachAsync` | FluxLog | `IocDbController.FluxLogGetLastFiltAsync()` |
### Fase 3: Decomposizione MpIocController in Repository (NON INIZIATA)
`MpIocController` (1480 righe, 82 metodi) va scomposto in repository specialistici, come fatto per MP.SPEC:
| Attuale (82 metodi) | Nuovo Repository | Ambito |
|----------------------|-------------------|--------|
| `AnagStatiGetAllAsync`, `ArticoliGetLastByMaccAsync`, `ConfigGetAllAsync`, `ConfigUpdateAsync`, `DecNumArtGetFiltAsync`, `ListValuesFiltAsync`, `ListLinkFiltAsync`, `EvListInsert` | `IAnagRepository` | **Usato da SPEC** — già migrato! |
| `AutoStartOdlAsync`, `ConfirmaProdMacchinaAsync/Full`, `OdlCurrByMaccAsync`, `OdlLastByMaccAsync`, `OdlListByMaccPeriodoAsync`, `PezziProdMacchinaAsync`, `StatoProdMacchinaAsync` | `IProduzioneRepository` (nuovo) | Produzione IOB-specifica |
| `OdlAutoDayGenAsync/Full`, `POdlGetByMaccArtAsync`, `OdlFixMachineSlave` | `IProduzioneRepository` (nuovo) | Pianificazione produzione |
| `FluxLogInsertAsync`, `FluxLogGetLastFiltAsync`, `FluxLogFirstByMaccAsync`, `FluxLogTakeSnapshotLastAsync`, `ConfFluxFiltAsync` | `IFluxLogRepository` | **Usato da SPEC** — già migrato! |
| `DossGetLastByMaccAsync`, `SignalLogInsertAsync` | `IDossierRepository` | **Usato da SPEC** — già migrato! |
| `DatiMacchineGetAllAsync`, `MacchineGetByIdxAsync`, `MacchineUpsertAsync`, `MacchineGetFiltAsync`, `MacchineGetAllAsync`, `Macchine2Slave` | `IMacchineRepository` (nuovo) | Gestione macchine |
| `EvListMicroStatoInsertAsync`, `MicroStatoMacchinaUpsertAsync`, `MicroStatoMacchinaGetByIdxMaccAsync` | `IMicroStatoRepository` (nuovo) | Micro-stati macchine |
| `MSE_getUserForcedAsync`, `SMES_getHwTransitionsAsync`, `DDB_InsStatoBatchAsync`, `CheckCambiaStatoBatchAsync`, `RecalcMseAsync` | `IMseRepository` (nuovo) | Mappa stati macchin |
| `StateMachineIngressiAsync`, `VMSFDGetByMaccAsync`, `VMSFDGetMultiByMaccAsync`, `VMSFDGetAllAsync` | `IStateMachineRepository` (nuovo) | State machine |
| `KeepAliveUpsertAsync` | `IMacchineRepository` (nuovo) | Keep-alive |
| `AlarmLogInsertAsync`, `RegScartiInsertAsync`, `RegControlliInsertAsync`, `RegDichiarInsertAsync` | `IRegistroRepository` (nuovo) | Registri qualità |
| `RemRebootLog*` methods (6) | `IRemRebootRepository` (nuovo) | Reboot remote logging |
### Fase 4: Scomposizione MpDataService (NON INIZIATA)
`MpDataService.cs` (3516 righe) è troppo grande per essere un unico servizio. Proposta di scomposizione:
| Servizio | Responsabilità | Metodi |
|----------|---------------|--------|
| `MpDataService` (core) | Gateway, orchestrazione | ~50 |
| `IodlManagementService` | ODL lifecycle (apri, chiudi, split, conferma) | ~15 |
| `FluxLogProcessingService` | Processing flux log, snapshot, pareto | ~10 |
| `MachineCommunicationService` | Keep-alive, dati macchin, micro-stati, state machine | ~20 |
| `ProductionTrackingService` | Conferma produzione, pezzi prodotti, stato produzione | ~8 |
| `RedisCacheService` | Wrapper methods per Redis operations (Hash, String, Set) | ~15 |
**Nota:** La scomposizione va fatta **dopo** la migrazione a repository (Fase 3) per non dover modificare ogni singola chiamata.
### Fase 5: Build & Verifica ✅
Dopo ogni fase completa:
1. `dotnet build MP-IOC.sln` → ✅ 0 errori, 12 warnings (tutti preesistenti)
2. `dotnet build MP-SPEC.sln` → ✅ OK
3. `dotnet build MP-LAND.sln` → ✅ OK
4. `dotnet build MP-MON.sln` → ✅ OK
## Build Status Aggiornato
| Fase | File Modificati | Righe Modificate | Rischio | Stato |
|------|-----------------|-------------------|---------|-------|
| F1: Anti-Pattern 4 controller | 4 Controllers | ~20 | Basso | ✅ **Completato** |
| F2: FusionCache config + metodi | 2 Files + Helper | ~250 | Basso | ✅ **Completato** |
| F3: Repository IoC | 7+ nuovi + MpDataService | ~2000 | Alto | ⏳ Da fare |
| F4: Scomposizione MpDataService | 5 nuovi + 1 esistente | ~3516 | Alto | ⏳ Da fare |
| F5: Build & Verifica | — | — | Basso | ✅ 0 errori |
## Stato Completato
### ✅ Fase 1: Anti-Pattern Controller — COMPLETATA
Rimosso `static IConfiguration _configuration` da tutti i 4 controller.
### ✅ Fase 2: FusionCache + Redis Backplane — COMPLETATA
- Pacchetti FusionCache aggiunti a `MP.IOC.csproj`
- `IFusionCache` configurato con L1 Memory + L2 Redis + Backplane in `Program.cs`
- Campo `_cache` aggiunto a `MpDataService` con helper `GetOrFetchAsync<T>`
- **18 metodi migrati** dal pattern manuale Redis → FusionCache
- Build: ✅ **0 errori**, 12 warnings (tutti nullable reference preesistenti)
## Invntario Completo Metodi (Post-Refactoring)
### ✅ MIGRATI A FUSIONCACHE (18 metodi)
| # | Metodo | Fetch From | Tag |
|---|--------|------------|-----|
| 1 | `AnagStatiGetAllAsync` | `IocDbController.AnagStatiGetAllAsync()` | `Utils.redisAnagStati` |
| 2 | `ArticoliGetLastByMaccAsync` | `IocDbController.ArticoliGetLastByMaccAsync()` | `Utils.redisArtList:Last:{idxMacc}` |
| 3 | `ConfigGetAllAsync` | `IocDbController.ConfigGetAllAsync()` | `Utils.redisConfKey` |
| 4 | `DecNumArtGetFiltAsync` | `IocDbController.DecNumArtGetFiltAsync()` | `Utils.redisDecNumArtKey:{tag}` |
| 5 | `DossierLastByMachAsync` | `IocDbController.DossGetLastByMaccAsync()` | `Utils.redisDossByMacLast:{idxMacc}` |
| 6 | `GetLastOdlAsync` | `IocDbController.OdlLastByMaccAsync()` | `Utils.redisOdlLastByMac:{idxMacc}` |
| 7 | `ListValuesFilt` | `IocDbController.ListValuesFiltAsync()` | `Utils.redisConfFlux:{tag}` |
| 8 | `Macchine2SlaveGetAllAsync` | `IocDbController.Macchine2SlaveAsync()` | `Utils.redisBaseAddr:M2STab` |
| 9 | `MacchineGetFilt` | `_productionRepository.MacchineGetFiltAsync()` | `Utils.redisMacList:{keyGrp}` |
| 10 | `MacchineRecipeArchive` | `_productionRepository.MacchineGetFiltAsync("*")` → LINQ | `Utils.redisMacRecipePath:{idxMacc}` |
| 11 | `MseGetAllAsync` | `IocDbController.MseGetAllAsync(maxAge)` | `Constants.redisMseKey` (+ forceDb) |
| 12 | `OdlCurrByMaccAsync` | `IocDbController.OdlCurrByMaccAsync()` | `Utils.redisOdlList:Current:{idxMacc}` |
| 13 | `POdlGetByKey` | `_productionRepository.PODL_getByKeyAsync()` | `Utils.redisPOdlByPOdl:{idxPODL}` |
| 14 | `POdlGetByMaccArtAsync` | `IocDbController.POdlGetByMaccArtAsync()` | `Utils.redisPOdlByMaccArt:{idxMacc}...` |
| 15 | `ConfFluxMach` | `IocDbController.ConfFluxFiltAsync()` → LINQ | `Utils.redisConfFlux:{tag}` |
| 16 | `FluxLogFirstByMachAsync` | `IocDbController.FluxLogFirstByMaccAsync()` → LINQ | `Utils.redisFluxLogFirstByMac:{key}` |
| 17 | `FluxLogGetLastByMachAsync` | `IocDbController.FluxLogGetLastFiltAsync()` → LINQ | `Utils.redisFluxLogByMac:{key}` |
### ❌ NON MIGRATI (Scrittura / TTL / Hash-only — 30+ metodi)
Questi metodi usano Redis ma **NON** hanno pattern cache-aside (StringGet → DB → StringSet). Sono dati transituali IOB o write-through:
| Categoria | Metodi | Motivazione |
|-----------|--------|-------------|
| **Scrittura Only (StringSet)** | `MachineParamListSetAsync`, `SaveMachine2Iob`, `SaveMachineIobConf`, `SetIobConfYamlAsync`, `SetIobMemMap`, `UpsertCurrObjItemsAsync` | Solo StringSet, zero StringGet |
| **Lock/TTL-based** | `AutoStartOdlAsync` (veto), `ScriviKeepAliveAsync` (TTL 30s) | Pattern di lock, non di cache |
| **Hash-based IOB State** | `MachineParamListAsync`, `AddCheckTask4Machine`, `AddOptPar4Machine`, `AddTask4Machine`, `AddTask4MacListAsync`, `mOptParMacchina`, `mSavedTaskMacchina`, `mTaskMacchina`, `mTaskMacchinaAsync`, `mDatiMacchineAsync`, `mTabMSMIAsync`, `StateMachInByKeyAsync`, `ValoreSmiAsync`, `StateMachInByKeyAsync` | Redis Hash per dati in tempo reale IOB (transazionali), non Database-backed |
| **Hash Reset Helpers** | `ResetDatiMacchinaAsync`, `resetMSMIAsync`, `resetSMIAsync` | Reset di tabelle Hash Redis, non cache |
| **Redis GetHash Helpers** | `RedisGetHash`, `RedisGetHashAsync`, `RedisGetHashDict`, `RedisGetHashDictAsync`, `RedisSetHash`, `RedisSetHashAsync`, `RedisSetHashDict`, `RedisSetHashDictAsync`, `RedisCountKey`, `RedisDelKey`, `RedisKeyPresent`, `RedisGetHashField` | Helper utility Redis, non metodi di business |
### ⚠️ DA VERIFICARE (Pattern atipico - 4 metodi)
Questi metodi hanno Redis ma il pattern non è lo standard cache-aside. Vanno analizzati caso per caso:
| # | Metodo | Riga | Motivo Verifica |
|---|--------|------|-----------------|
| 1 | `GetCurrOdlAsync` | ~892 | Chiama `GetCurrOdlByProdAsync()` (metodo interno complesso), non `IocDbController.XXX()` direttamente. Pattern: StringGet → DB → StringSet. Verificare se `GetCurrOdlByProdAsync` è una stored procedure. |
| 2 | `StatoProdMacchinaAsync` (private) | ~3267 | Ha parametro `forceDb`, cache breve (60s pattern). Pattern valido ma private. |
| 3 | `verificaIdxMacchinaAsync` | ~3267 | Verifica esistenza macchina, non un cache-aside standard. |
| 4 | `ListMasterAsync` / `ListSlaveAsync` | ~2820/~2840 | Cache-aside valido ma derivano da `Macchine2SlaveGetAllAsync()` (già migrata). Potrebbero essere rimossi in favore di un unico repository. |
## Priorità Operativa
1. ~~**F1 immediata**~~ — ✅ Completata
2. ~~**F2 FusionCache**~~ — ✅ Completata (18 metodi migrati)
3. **F3 Repository IoC** — Prossimo step maggiore: decomporre `MpIocController` (1480 righe, 82 metodi) in 7-10 repository specialistici
4. **F4 Scomposizione MpDataService** — Quando Fase 3 è stabile, dividere il monolite da 3516 righe
5. **Verifica metodi atipici** — I 4 metodi con pattern non-standards vanno valutati manualmente
## Note
- MpIocController usa già `IDbContextFactory` correttamente — il problema è che è un Singleton usato da un altro Singleton (MpDataService)
- `IIocRepository` (già esistente) ha solo 16 metodi ed è mai usato da MpDataService
- I repository SPEC (Anag, Dossier, FluxLog, Production) sono già usati da alcuni metodi — sfruttarli dove possibile
- La struttura `MP.Data/Controllers/` è ingannevole: `MpIocController` è un servizio, non un controller Web API
- `redisConnAdmin` (connessione Redis admin) sembra non essere mai usata — verificare eliminazione
- **Build finale:** ✅ 0 errori, 12 warnings (tutti nullable reference preesistenti)
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace>MP.Land</RootNamespace> <RootNamespace>MP.Land</RootNamespace>
<Version>8.16.2606.0408</Version> <Version>8.16.2606.1117</Version>
<Configurations>Debug;Release;Debug_LiManDebug</Configurations> <Configurations>Debug;Release;Debug_LiManDebug</Configurations>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<RunAnalyzersDuringBuild>True</RunAnalyzersDuringBuild> <RunAnalyzersDuringBuild>True</RunAnalyzersDuringBuild>
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo Tablet MAPO - DotNet6</i> <i>Modulo Tablet MAPO - DotNet6</i>
<h4>Versione: 8.16.2606.0408</h4> <h4>Versione: 8.16.2606.1117</h4>
<br /> <br />
Note di rilascio: Note di rilascio:
<ul> <ul>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.0408 8.16.2606.1117
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.0408</version> <version>8.16.2606.1117</version>
<url>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+1 -1
View File
@@ -6,7 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.MON</RootNamespace> <RootNamespace>MP.MON</RootNamespace>
<AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName> <AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1117</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MAPOSPEC </i> <i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1117</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.408 8.16.2606.1117
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.408</version> <version>8.16.2606.1117</version>
<url>https://nexus.steamware.net/repository/SWS/MP-MON/stable/LAST/MP.MON.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-MON/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+1 -1
View File
@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RootNamespace>MP.Prog</RootNamespace> <RootNamespace>MP.Prog</RootNamespace>
<Version>8.16.2606.0408</Version> <Version>8.16.2606.1117</Version>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo gestione Programmi MAPO</i> <i>Modulo gestione Programmi MAPO</i>
<h4>Versione: 8.16.2606.0408</h4> <h4>Versione: 8.16.2606.1117</h4>
<br /> <br />
Note di rilascio: Note di rilascio:
<ul> <ul>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.0408 8.16.2606.1117
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.0408</version> <version>8.16.2606.1117</version>
<url>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/MP.Prog.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-PROG/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+2 -1
View File
@@ -5,7 +5,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.RIOC</RootNamespace> <RootNamespace>MP.RIOC</RootNamespace>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1218</Version>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+35 -3
View File
@@ -10,14 +10,30 @@ using System.Diagnostics;
using System.Net; using System.Net;
using System.Reflection; 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); var builder = WebApplication.CreateBuilder(args);
// recupero env corrente // RECUPERO L'AMBIENTE REALE (Che ora IIS passa correttamente come 'Staging')
var env = builder.Environment; 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() var logger = LogManager.Setup()
.LoadConfigurationFromAppSettings() .LoadConfigurationFromAppSettings()
.GetCurrentClassLogger(); .GetCurrentClassLogger();
builder.Logging.ClearProviders();
builder.Host.UseNLog();
var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
logger.Info($"MP.RIOC | Program.cs: startup | v.{assemblyVersion}"); logger.Info($"MP.RIOC | Program.cs: startup | v.{assemblyVersion}");
logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}");
@@ -57,23 +73,35 @@ builder.Services.AddDbContextFactory<MoonPro_UtilsContext>(options =>
builder.Services.AddIocDataLayer(); builder.Services.AddIocDataLayer();
// 1. Configurazione dell'invoker personalizzato (Risolve i tuoi errori) // 1. Configurazione dell'invoker personalizzato (Risolve i tuoi errori)
// 1. Configurazione dell'invoker personalizzato (Potenziato con il Pooling)
var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler
{ {
UseProxy = false, UseProxy = false,
AllowAutoRedirect = false, AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.None, AutomaticDecompression = DecompressionMethods.None,
UseCookies = false, UseCookies = false,
// Correzione per il tracing: usa il propagatore corrente di sistema
ActivityHeadersPropagator = DistributedContextPropagator.Current, ActivityHeadersPropagator = DistributedContextPropagator.Current,
ConnectTimeout = TimeSpan.FromSeconds(60), 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 SslOptions = new System.Net.Security.SslClientAuthenticationOptions
{ {
RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
} }
}); });
// Registrazione nella Dependency Injection (Invariata)
builder.Services.AddSingleton(httpClientInvoker); builder.Services.AddSingleton(httpClientInvoker);
builder.Services.AddHttpForwarder(); 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 // 6. Definizione degli Endpoints locali
app.MapRazorPages(); app.MapRazorPages();
@@ -23,6 +23,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun> <PublishReadyToRun>true</PublishReadyToRun>
<AllowUntrustedCertificate>True</AllowUntrustedCertificate>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<MsDeploySkipRules Include="SkipLogFiles"> <MsDeploySkipRules Include="SkipLogFiles">
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MP-RIOC </i> <i>Modulo MP-RIOC </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1218</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>
+1 -1
View File
@@ -1 +1 @@
8.16.2606.408 8.16.2606.1218
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>8.16.2606.408</version> <version>8.16.2606.1218</version>
<url>https://nexus.steamware.net/repository/SWS/MP-RIOC/stable/LAST/MP.RIOC.zip</url> <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> <changelog>https://nexus.steamware.net/repository/SWS/MP-RIOC/stable/LAST/ChangeLog.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
+16
View File
@@ -128,10 +128,15 @@ namespace MP.RIOC.Services
SentinelValue SentinelValue
})); }));
// 1b. Imposta TTL 30 giorni su bucket hourly per prevenire chiavi orfane
tasks.Add(batch.KeyExpireAsync(hourKey, TimeSpan.FromDays(30)));
// 2. INVIO DISTRIBUZIONE STATUS CODES // 2. INVIO DISTRIBUZIONE STATUS CODES
if (stat.StatusCodes.Any()) if (stat.StatusCodes.Any())
{ {
var statusKey = hourKey + ":status"; var statusKey = hourKey + ":status";
// Imposta TTL 30 giorni su chiavi ausiliarie per prevenire chiavi orfane
tasks.Add(batch.KeyExpireAsync(statusKey, TimeSpan.FromDays(30)));
foreach (var status in stat.StatusCodes) foreach (var status in stat.StatusCodes)
{ {
tasks.Add(batch.HashIncrementAsync(statusKey, status.Key.ToString(), status.Value)); tasks.Add(batch.HashIncrementAsync(statusKey, status.Key.ToString(), status.Value));
@@ -144,6 +149,8 @@ namespace MP.RIOC.Services
if (stat.ErrorMessages.Any()) if (stat.ErrorMessages.Any())
{ {
var errorKey = hourKey + ":errors"; var errorKey = hourKey + ":errors";
// Imposta TTL 30 giorni su chiavi ausiliarie per prevenire chiavi orfane
tasks.Add(batch.KeyExpireAsync(errorKey, TimeSpan.FromDays(30)));
foreach (var error in stat.ErrorMessages) foreach (var error in stat.ErrorMessages)
{ {
// Usiamo HashIncrement per aggregare messaggi uguali // Usiamo HashIncrement per aggregare messaggi uguali
@@ -159,6 +166,9 @@ namespace MP.RIOC.Services
var daysIndex = DaysIndexKey(dest, machineId); var daysIndex = DaysIndexKey(dest, machineId);
var dayScore = ToEpochSeconds(dayStart); var dayScore = ToEpochSeconds(dayStart);
// 5. Imposta TTL 30 giorni anche sulle chiavi index per pulizia automatica
tasks.Add(batch.KeyExpireAsync(hoursIndex, TimeSpan.FromDays(30)));
if (!dailyAggregates.TryGetValue(dayKey, out var agg)) if (!dailyAggregates.TryGetValue(dayKey, out var agg))
{ {
agg = new AggregatedStats(); agg = new AggregatedStats();
@@ -174,6 +184,9 @@ namespace MP.RIOC.Services
// Aggiungiamo l'indice al batch // Aggiungiamo l'indice al batch
tasks.Add(batch.SortedSetAddAsync(daysIndex, dayKey, dayScore)); tasks.Add(batch.SortedSetAddAsync(daysIndex, dayKey, dayScore));
// 5. Imposta TTL 30 giorni su chiave daily per prevenire chiavi orfane
tasks.Add(batch.KeyExpireAsync(dayKey, TimeSpan.FromDays(30)));
} }
// --- INVIO AGGREGATI DAILY A REDIS --- // --- INVIO AGGREGATI DAILY A REDIS ---
@@ -196,6 +209,9 @@ namespace MP.RIOC.Services
agg.NoReply.ToString(CultureInfo.InvariantCulture), agg.NoReply.ToString(CultureInfo.InvariantCulture),
SentinelValue SentinelValue
})); }));
// Imposta TTL 30 giorni su bucket daily aggregato
tasks.Add(batch.KeyExpireAsync(key, TimeSpan.FromDays(30)));
} }
// Esegui tutto il batch in un unico round-trip // Esegui tutto il batch in un unico round-trip
+142 -83
View File
@@ -33,8 +33,23 @@ namespace MP.RIOC.Services
{ {
try try
{ {
//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 ProcessDayLiveMetricsAsync();
await ProcessHourLiveMetricsAsync(); await ProcessHourLiveMetricsAsync();
}, stoppingToken);
// poi attendo...
await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken);
} }
catch (TaskCanceledException) { break; } catch (TaskCanceledException) { break; }
@@ -66,6 +81,68 @@ namespace MP.RIOC.Services
#region Private Methods #region Private Methods
/// <summary>
/// Cancellazione ricorsiva chiavi ausiliarie (:status, :errors) e rimozione dall'indice
/// </summary>
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";
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";
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:"))
{
string daysIndex = statusKey.Replace(":stats:hours:", ":stats:days:");
try
{
pendingOps.Add(batch.SortedSetRemoveAsync(daysIndex, statusKey));
}
catch { }
}
// Cancella chiave ausiliaria :errors anche da un eventuale indice days se presente
if (errorKey.Contains(":stats:hours:"))
{
string daysIndex = errorKey.Replace(":stats:hours:", ":stats:days:");
try
{
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>
private TimeSpan? GetKeyTtl(RedisKey key)
{
try
{
return _db.KeyTimeToLive(key);
}
catch { return null; }
}
#endif
/// <summary> /// <summary>
/// Processing dati giornalieri (da Redis a DB) /// Processing dati giornalieri (da Redis a DB)
/// </summary> /// </summary>
@@ -97,6 +174,7 @@ namespace MP.RIOC.Services
}; };
var batch = _db.CreateBatch(); var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
foreach (var pattern in patternsToScan) foreach (var pattern in patternsToScan)
{ {
// Nota: KeyScanAsync/KeysAsync e' disponibile su IServer // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer
@@ -110,21 +188,36 @@ namespace MP.RIOC.Services
foreach (var statKey in memberKeys) foreach (var statKey in memberKeys)
{ {
var sKey = (RedisKey)$"{statKey}"; var sKey = (RedisKey)$"{statKey}";
#if false
if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType))
continue;
#endif
if (!TryParseKeyMetadata(sKey, out var meta) || meta.IsHourType) continue; if (!TryParseKeyMetadata(sKey, out var meta) || meta.IsHourType) continue;
// Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione // Verifica se la chiave è "orfana"
if (meta.Timestamp < currentDayStart && deleteConfirmed) bool isOrphanKey = meta.Timestamp < currentDayStart;
#if false
bool isOrphanKey = false;
var keyTtl = GetKeyTtl(sKey);
if (keyTtl == null)
{
isOrphanKey = meta.Timestamp < currentDayStart;
}
else
{
isOrphanKey = keyTtl.Value.TotalSeconds < 0 || keyTtl.Value.TotalDays < 1;
}
#endif
// 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 // 1. Segna la chiave Hash (Dati) per l'eliminazione
keysToDelete.Add(sKey); keysToDelete.Add(sKey);
// 2. CORREZIONE: Devi rimuovere il riferimento dal Sorted Set (Indice) // 2. Rimuovi il riferimento dal Sorted Set (Indice)
// Usiamo il batch per essere efficienti pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, statKey));
_ = batch.SortedSetRemoveAsync(indexKey, statKey);
// 3. Cancellazione ricorsiva delle chiavi ausiliarie (:status, :errors, :days)
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch, pendingOps);
} }
// Recupero dati dalla Hash // Recupero dati dalla Hash
@@ -137,7 +230,6 @@ namespace MP.RIOC.Services
dict.TryGetValue("totalMs", out var totalMsStr)) dict.TryGetValue("totalMs", out var totalMsStr))
{ {
long count = long.Parse(countStr); long count = long.Parse(countStr);
count = long.Parse(countStr);
double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture);
double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0;
@@ -172,6 +264,8 @@ namespace MP.RIOC.Services
} }
} }
batch.Execute(); batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
} }
// --- FASE UPSERT DB --- // --- FASE UPSERT DB ---
@@ -187,13 +281,16 @@ namespace MP.RIOC.Services
if (deleteConfirmed && keysToDelete.Any()) if (deleteConfirmed && keysToDelete.Any())
{ {
var batch = _db.CreateBatch(); var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
int deletedCount = 0; int deletedCount = 0;
foreach (var key in keysToDelete) foreach (var key in keysToDelete)
{ {
_ = batch.KeyDeleteAsync(key); pendingOps.Add(batch.KeyDeleteAsync(key));
deletedCount++; deletedCount++;
} }
batch.Execute(); batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
Log.Info($"[CLEANUP DAY] Deleted {deletedCount} expired metric keys from Redis"); Log.Info($"[CLEANUP DAY] Deleted {deletedCount} expired metric keys from Redis");
} }
} }
@@ -230,6 +327,7 @@ namespace MP.RIOC.Services
}; };
var batch = _db.CreateBatch(); var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
foreach (var pattern in patternsToScan) foreach (var pattern in patternsToScan)
{ {
// Nota: KeyScanAsync/KeysAsync e' disponibile su IServer // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer
@@ -244,26 +342,37 @@ namespace MP.RIOC.Services
{ {
var sKey = (RedisKey)$"{statKey}"; var sKey = (RedisKey)$"{statKey}";
if (!TryParseKeyMetadata(sKey, out var meta) || !meta.IsHourType) continue; if (!TryParseKeyMetadata(sKey, out var meta) || !meta.IsHourType) continue;
#if false
if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType))
continue;
// Verifica se la chiave fosse scaduta rispetto all'orario corrente // Verifica se la chiave è "orfana"
bool isExpired = isHourType bool isOrphanKey = meta.Timestamp < currentHourStart;
? timestamp < currentHourStart #if false
: false; bool isOrphanKey = false;
//: timestamp < currentDayStart; var keyTtl = GetKeyTtl(sKey);
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 #endif
// Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione // Se è orfana e abbiamo il permesso, segnamola per la cancellazione
if (meta.Timestamp < currentHourStart && deleteConfirmed) // 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 // 1. Segna la chiave Hash (Dati) per l'eliminazione
keysToDelete.Add(sKey); keysToDelete.Add(sKey);
// 2. CORREZIONE: Devi rimuovere il riferimento dal Sorted Set (Indice) // 2. Rimuovi il riferimento dal Sorted Set (Indice)
// Usiamo il batch per essere efficienti pendingOps.Add(batch.SortedSetRemoveAsync(indexKey, statKey));
_ = batch.SortedSetRemoveAsync(indexKey, statKey);
// 3. Cancellazione ricorsiva chiavi ausiliarie
await DeleteAuxKeysAndIndexAsync(sKey, indexKey, batch, pendingOps);
} }
// Recupero dati dalla Hash // Recupero dati dalla Hash
@@ -276,7 +385,6 @@ namespace MP.RIOC.Services
dict.TryGetValue("totalMs", out var totalMsStr)) dict.TryGetValue("totalMs", out var totalMsStr))
{ {
long count = long.Parse(countStr); long count = long.Parse(countStr);
count = long.Parse(countStr);
double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture);
double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0;
@@ -310,6 +418,8 @@ namespace MP.RIOC.Services
} }
} }
batch.Execute(); batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
} }
// --- FASE UPSERT DB --- // --- FASE UPSERT DB ---
@@ -325,16 +435,20 @@ namespace MP.RIOC.Services
if (deleteConfirmed && keysToDelete.Count > 0) if (deleteConfirmed && keysToDelete.Count > 0)
{ {
var batch = _db.CreateBatch(); var batch = _db.CreateBatch();
var pendingOps = new List<Task>();
int deletedCount = 0; int deletedCount = 0;
foreach (var key in keysToDelete) foreach (var key in keysToDelete)
{ {
_ = batch.KeyDeleteAsync(key); pendingOps.Add(batch.KeyDeleteAsync(key));
deletedCount++; deletedCount++;
} }
batch.Execute(); batch.Execute();
// attendo conclusione
await Task.WhenAll(pendingOps);
Log.Info($"[CLEANUP HOUR] Deleted {deletedCount} expired metric keys from Redis"); Log.Info($"[CLEANUP HOUR] Deleted {deletedCount} expired metric keys from Redis");
} }
} }
private bool TryParseKeyMetadata(RedisKey key, out KeyMeta meta) private bool TryParseKeyMetadata(RedisKey key, out KeyMeta meta)
{ {
meta = new KeyMeta(); meta = new KeyMeta();
@@ -345,7 +459,7 @@ namespace MP.RIOC.Services
var p = relativeKey.Split(':'); var p = relativeKey.Split(':');
if (p.Length < 4) return false; 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]; meta.Dest = p[2];
if (meta.IsHourType) if (meta.IsHourType)
{ {
@@ -363,60 +477,7 @@ namespace MP.RIOC.Services
catch { return false; } catch { return false; }
} }
#if false #endregion Private Methods
private bool TryParseKeyMetadata(RedisKey key, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType)
{
dest = "NA";
method = "NA";
machId = "ALL";
timestamp = DateTime.MinValue;
isHourType = true;
try
{
string k = key.ToString();
string relativeKey = k.Replace($"{_redisBaseKey}:", "");
var parts = relativeKey.Split(':');
if (parts.Length < 4) return false;
string type = parts[1]; // "hour" o "day"
dest = parts[2];
if (type == "hour")
{
isHourType = true;
method = parts[3];
if (parts.Length >= 5 && DateTime.TryParseExact(parts[4], "yyyyMMddHH", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt))
{
timestamp = dt;
return true;
}
}
else if (type == "day")
{
isHourType = false;
method = "DAILY";
string rawDate = "";
if (parts.Length >= 5)
{
machId = parts[3];
rawDate = parts[4];
}
else
{
rawDate = parts[3];
}
if (DateTime.TryParseExact(rawDate, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt))
{
timestamp = dt;
return true;
}
}
}
catch { }
return false;
}
#endif
private record KeyMeta private record KeyMeta
{ {
@@ -426,7 +487,5 @@ namespace MP.RIOC.Services
public DateTime Timestamp = DateTime.MinValue; public DateTime Timestamp = DateTime.MinValue;
public bool IsHourType = true; public bool IsHourType = true;
} }
#endregion Private Methods
} }
} }
+10 -3
View File
@@ -98,9 +98,16 @@ namespace MP.RIOC.Services
// ESECUZIONE FORWARDING // ESECUZIONE FORWARDING
var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, HttpTransformer.Default, context.RequestAborted); var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, HttpTransformer.Default, context.RequestAborted);
// commento transformer custom // Se YARP fallisce per un problema di connessione interrotta (come l'errore 995)
//var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, _transformer, context.RequestAborted); // e NON è un problema di sintassi della richiesta o di autorizzazione
if (error != ForwarderError.None &&
error != ForwarderError.RequestBodyClient &&
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(); sw.Stop();
_stats.RecordDuration(sKey, sw.Elapsed); _stats.RecordDuration(sKey, sw.Elapsed);
+1
View File
@@ -2,6 +2,7 @@
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Yarp": "Warning",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
} }
+1
View File
@@ -2,6 +2,7 @@
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Yarp": "Warning",
"Microsoft.AspNetCore": "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": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",
"Yarp": "Debug", "Yarp": "Information",
"Microsoft.AspNetCore": "Warning", "Microsoft.AspNetCore.Hosting.Diagnostics": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore": "Warning", "Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
@@ -24,7 +25,7 @@
"logfile": { "logfile": {
"type": "File", "type": "File",
"fileName": "${basedir}/logs/${shortdate}.log", "fileName": "${basedir}/logs/${shortdate}.log",
"keepFileOpen": false, "keepFileOpen": true,
"archiveEvery": "Day", "archiveEvery": "Day",
"archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log",
"archiveNumbering": "DateAndSequence", "archiveNumbering": "DateAndSequence",
@@ -39,9 +40,24 @@
} }
}, },
"rules": [ "rules": [
{
"logger": "Microsoft.EntityFrameworkCore.*",
"maxLevel": "Info",
"final": true
},
{
"logger": "Microsoft.AspNetCore.Hosting.*",
"minLevel": "Info",
"finalMinLevel": "Info"
},
{
"logger": "Microsoft.AspNetCore.*",
"maxLevel": "Info",
"finalMinLevel": "Info"
},
{ {
"logger": "*", "logger": "*",
"minLevel": "Trace", "minLevel": "Info",
"writeTo": "logconsole" "writeTo": "logconsole"
}, },
{ {
+10 -1
View File
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using MP.AppAuth.Services; using MP.AppAuth.Services;
using MP.Data.DbModels;
using MP.SPEC.Data; using MP.SPEC.Data;
using MP.SPEC.Extensions; using MP.SPEC.Extensions;
@@ -52,6 +53,14 @@ namespace MP.SPEC.Components
protected async Task FlushCache() protected async Task FlushCache()
{ {
// svuoto cache redis...
ConfigModel updRec = new ConfigModel()
{
Chiave = "AZIENDA",
Valore = "*"
};
await MDService.ConfigUpdateAsync(updRec);
await MDService.ForceFlushRedisCache(); await MDService.ForceFlushRedisCache();
await MDService.ForceFlushFusionCacheAsync(); await MDService.ForceFlushFusionCacheAsync();
await ForceReload(); await ForceReload();
@@ -122,7 +131,7 @@ namespace MP.SPEC.Components
private void SetPageData() private void SetPageData()
{ {
// update testo pagina da URI // DoUpdate testo pagina da URI
string pageUri = NavManager.Page().ToLower(); string pageUri = NavManager.Page().ToLower();
// se home/index/vuoto... home! // se home/index/vuoto... home!
if (string.IsNullOrEmpty(pageUri) || pageUri == "index" || pageUri == "home") if (string.IsNullOrEmpty(pageUri) || pageUri == "index" || pageUri == "home")
+1 -1
View File
@@ -131,7 +131,7 @@ namespace MP.SPEC.Components.Fermate
} }
/// <summary> /// <summary>
/// Richiesta update /// Richiesta DoUpdate
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private async Task RaiseRefresh() private async Task RaiseRefresh()
-1
View File
@@ -160,7 +160,6 @@ namespace MP.SPEC.Components
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
await Task.Delay(1);
// se sono cambiati --> rileggo... // se sono cambiati --> rileggo...
if (!lastFilter.Equals(SelFilter)) if (!lastFilter.Equals(SelFilter))
{ {
+10 -11
View File
@@ -1,6 +1,5 @@
@using MP.SPEC.Components @using MP.SPEC.Components
@using MP.SPEC.Components.ProdKit @using MP.SPEC.Components.ProdKit
@using MP.SPEC.Data
@if (ListRecords == null || isLoading) @if (ListRecords == null || isLoading)
{ {
@@ -24,7 +23,7 @@ else
<th> <th>
@if (currRecord != null) @if (currRecord != null)
{ {
<button @onclick="() => resetSel(true)" class="btn btn-primary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></button> <button @onclick="() => DoResetSel(true)" class="btn btn-primary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></button>
} }
</th> </th>
<th>Cod</th> <th>Cod</th>
@@ -47,10 +46,10 @@ else
<td class="text-nowrap" style="width: 8rem;"> <td class="text-nowrap" style="width: 8rem;">
@if (!(showRecipeConf || showRecipeArch)) @if (!(showRecipeConf || showRecipeArch))
{ {
<button @onclick="() => selRecord(record)" class="btn btn-primary btn-sm mx-0" title="Dettaglio Record"><i class="bi bi-check2"></i></button> <button @onclick="() => DoSelRecord(record)" class="btn btn-primary btn-sm mx-0" title="Dettaglio Record"><i class="bi bi-check2"></i></button>
@if (!record.IsKit) @if (!record.IsKit)
{ {
<button @onclick="() => cloneRecord(record)" class="btn btn-info btn-sm mx-0" title="Duplica Record"><i class="bi bi-clipboard-check"></i></button> <button @onclick="() => DoCloneRecord(record)" class="btn btn-info btn-sm mx-0" title="Duplica Record"><i class="bi bi-clipboard-check"></i></button>
} }
else else
{ {
@@ -60,7 +59,7 @@ else
{ {
@if (!record.IsKit) @if (!record.IsKit)
{ {
<button @onclick="() => editRecord(record)" class="btn btn-primary btn-sm mx-1" title="Modifica Record"><i class="bi bi-pencil-square"></i></button> <button @onclick="() => DoEditRecord(record)" class="btn btn-primary btn-sm mx-1" title="Modifica Record"><i class="bi bi-pencil-square"></i></button>
} }
else else
{ {
@@ -70,7 +69,7 @@ else
{ {
@if (canStartOdl(record.IdxMacchina)) @if (canStartOdl(record.IdxMacchina))
{ {
<button @onclick="() => startOdl(record)" class="btn btn-success btn-sm mx-1" title="Avvia PODL"> <button @onclick="() => DoStartOdl(record)" class="btn btn-success btn-sm mx-1" title="Avvia PODL">
<i class="far fa-play-circle"></i> <i class="far fa-play-circle"></i>
</button> </button>
} }
@@ -93,13 +92,13 @@ else
{ {
@if (machineHasRecipeConf(record.IdxMacchina)) @if (machineHasRecipeConf(record.IdxMacchina))
{ {
<button class="btn btn-dark btn-sm mx-0" title="Gestione Ricetta" @onclick="() => doShowRecipeConf(record)"> <button class="btn btn-dark btn-sm mx-0" title="Gestione Ricetta" @onclick="() => DoShowRecipeConf(record)">
<i class="fa-solid fa-flask"></i> <i class="fa-solid fa-flask"></i>
</button> </button>
} }
else if (machineHasRecipeArch(record.IdxMacchina)) else if (machineHasRecipeArch(record.IdxMacchina))
{ {
<button class="btn btn-primary btn-sm mx-0" title="Gestione Ricetta" @onclick="() => doShowRecipeArch(record)"> <button class="btn btn-primary btn-sm mx-0" title="Gestione Ricetta" @onclick="() => DoShowRecipeArch(record)">
<i class="fa-solid fa-flask"></i> <i class="fa-solid fa-flask"></i>
</button> </button>
} }
@@ -190,7 +189,7 @@ else
<td> <td>
@if (POdlDelEnabled(record.IdxOdl) && !record.IsKit) @if (POdlDelEnabled(record.IdxOdl) && !record.IsKit)
{ {
<button @onclick="() => deleteRecord(record)" class="btn btn-danger btn-sm"><i class="bi bi-trash-fill"></i></button> <button @onclick="() => DoDeleteRecord(record)" class="btn btn-danger btn-sm"><i class="bi bi-trash-fill"></i></button>
} }
else else
{ {
@@ -208,13 +207,13 @@ else
@if (showRecipeConf) @if (showRecipeConf)
{ {
<div class="col-6 ps-0"> <div class="col-6 ps-0">
<RecipeConfMan IdxPODL="@currRecord.IdxPromessa" RecipePath="@currRecipePath" CancelEvent="resetSel"></RecipeConfMan> <RecipeConfMan IdxPODL="@currRecord.IdxPromessa" RecipePath="@currRecipePath" CancelEvent="DoResetSel"></RecipeConfMan>
</div> </div>
} }
else if (showRecipeArch) else if (showRecipeArch)
{ {
<div class="col-6 ps-0"> <div class="col-6 ps-0">
<RecipeArchMan IdxPODL="@currRecord.IdxPromessa" RecipeCode="@currRecord.Recipe" RecipeArchPath="@currRecipeArchPath" IdxMacc="@currRecord.IdxMacchina" ReqCloseEvent="resetSel"></RecipeArchMan> <RecipeArchMan IdxPODL="@currRecord.IdxPromessa" RecipeCode="@currRecord.Recipe" RecipeArchPath="@currRecipeArchPath" IdxMacc="@currRecord.IdxMacchina" ReqCloseEvent="DoResetSel"></RecipeArchMan>
</div> </div>
} }
} }
+299 -307
View File
@@ -52,128 +52,13 @@ namespace MP.SPEC.Components
#endregion Public Methods #endregion Public Methods
#region Protected Fields
protected bool enableForceSync = true;
protected bool enableStartPODL = true;
protected bool enableStopODL = true;
#endregion Protected Fields
#region Protected Properties
protected string header
{
get => actFilter.Header;
set => actFilter.Header = value;
}
[Inject]
protected IJSRuntime JSRuntime { get; set; } = null!;
[Inject]
protected MpDataService MDService { get; set; } = null!;
[Inject]
protected IOApiService MpIoApiCall { get; set; } = null!;
[Inject]
protected NavigationManager NavManager { get; set; } = null!;
#endregion Protected Properties
#region Protected Methods #region Protected Methods
/// <summary>
/// Verifica se sia un articolo di tipo "KIT" x mostrare show dettaglio
/// </summary>
/// <param name="CodArticolo"></param>
/// <returns></returns>
protected bool CheckIsKit(string CodArticolo)
{
bool answ = false;
if (ListArtKit != null && ListArtKit.Count > 0)
{
answ = ListArtKit.Count(x => x.CodArticolo == CodArticolo) > 0;
}
return answ;
}
protected async Task cloneRecord(PODLExpModel selRec)
{
currRecord = null;
// clono resettando ODL
var clonedRec = Utils.POdlExt.clone(selRec, true);
currRecord = clonedRec;
await RecordEdit.InvokeAsync(clonedRec);
header = "Duplica PODL";
}
/// <summary>
/// Eliminazione record selezionato (previa conferma)
/// </summary>
/// <param name="selRec"></param>
/// <returns></returns>
protected async Task deleteRecord(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Eliminazione Record: sei sicuro di voler procedere?"))
return;
await Task.Delay(1);
var done = await MDService.POdlDeleteRecord(selRec);
await callSyncDb(selRec.IdxMacchina);
currRecord = null;
await ReloadDataAsync();
await Task.Delay(1);
}
protected async Task doShowRecipeArch(PODLExpModel selRec)
{
currRecord = selRec;
currRecipeArchPath = await MDService.MacchineRecipeArchiveAsync(selRec.IdxMacchina);
showRecipeArch = true;
showRecipeConf = false;
}
protected async Task doShowRecipeConf(PODLExpModel selRec)
{
currRecord = selRec;
currRecipePath = await MDService.MacchineRecipeConfAsync(selRec.IdxMacchina);
showRecipeArch = false;
showRecipeConf = true;
}
protected async Task editRecord(PODLExpModel? selRec)
{
currRecord = selRec;
header = "Modifica PODL";
await RecordEdit.InvokeAsync(selRec);
}
protected async Task KitToggleDetailAsync(PODLExpModel? recSel)
{
if (recSel != null)
{
ListKitTemplate = await MDService.TemplateKitFiltAsync(recSel.CodArticolo, "");
ListPOdlKit = await MDService.POdlListByKitParentAsync(recSel.IdxPromessa);
}
else
{
ListKitTemplate = null;
ListPOdlKit = null;
}
showKitDetail = !showKitDetail;
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await ReloadBaseData(); await ReloadBaseData();
} }
private string? _lastPadCodXdl;
private bool _initialized;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
// esempio: verifica parametri minimi // esempio: verifica parametri minimi
@@ -196,207 +81,27 @@ namespace MP.SPEC.Components
await InvokeAsync(() => await InvokeAsync(() =>
{ {
PagerResetReq.InvokeAsync(true); PagerResetReq.InvokeAsync(true);
Task task = UpdateData(); Task task = DoUpdateData();
StateHasChanged(); StateHasChanged();
}); });
} }
protected bool POdlDelEnabled(int idxOdl)
{
return idxOdl == 0;
}
/// <summary>
/// Caricamento dati di base
/// </summary>
/// <returns></returns>
protected async Task ReloadBaseData()
{
ListRecords = null;
isLoading = true;
var list = await MDService.MachineWithOdlAsync();
_odlCurrSet = list.ToHashSet();
var machines = await MDService.MacchineGetFiltAsync("*");
_machinesWithConf = machines
.Where(x => !string.IsNullOrEmpty(x.RecipePath))
.Select(x => x.IdxMacchina)
.ToHashSet();
_machinesWithArch = machines
.Where(x => !string.IsNullOrEmpty(x.RecipeArchivePath))
.Select(x => x.IdxMacchina)
.ToHashSet();
ListStati = await MDService.AnagStatiCommAsync();
ListArtKit = await MDService.ArticoliGetByTipoAsync("KIT", "*");
string strMachRecipe = await MDService.ConfigTryGetAsync("MachineWithRecipe");
if (!string.IsNullOrEmpty(strMachRecipe))
{
bool.TryParse(strMachRecipe, out MachineWithRecipe);
}
string SPEC_PODL_gest = await MDService.ConfigTryGetAsync("SPEC_PODL_gest");
if (!string.IsNullOrEmpty(SPEC_PODL_gest))
{
bool.TryParse(SPEC_PODL_gest, out enableStartPODL);
}
string SPEC_ODL_gest = await MDService.ConfigTryGetAsync("SPEC_ODL_gest");
if (!string.IsNullOrEmpty(SPEC_ODL_gest))
{
bool.TryParse(SPEC_ODL_gest, out enableStopODL);
}
string SPEC_XODL_sync = await MDService.ConfigTryGetAsync("SPEC_XODL_sync");
if (!string.IsNullOrEmpty(SPEC_XODL_sync))
{
bool.TryParse(SPEC_XODL_sync, out enableForceSync);
}
}
protected async Task ReloadDataAsync()
{
isLoading = true;
ListRecords = null;
// ✅ lancia in parallelo
var odlTask = UpdateOdlList();
Task<List<PODLExpModel>> searchTask;
if (actFilter.ShowKit)
{
searchTask = MDService.POdlListGetFiltAsync(hasOdl, StatoSel, macchina, reparto, selDtStart, selDtEnd);
}
else
{
searchTask = MDService.POdlToKitListGetFiltAsync(hasOdl, StatoSel, macchina, reparto, selDtStart, selDtEnd);
}
// ✅ aspetta tutto insieme
await Task.WhenAll(odlTask, searchTask);
var rawList = searchTask.Result;
// se abilitata ricerca filtro ulteriormente..
if (string.IsNullOrEmpty(actFilter.SearchVal))
{
SearchRecords = rawList;
}
else
{
SearchRecords = rawList
.Where(x =>
x.CodArticolo.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
|| x.CodFase.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
|| x.DescArticolo.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
).ToList();
}
totalCount = SearchRecords.Count;
ListRecords = SearchRecords
.Skip(numRecord * (currPage - 1))
.Take(numRecord)
.ToList();
isLoading = false;
}
protected async Task resetSel(bool forceUpdate)
{
currRecord = null;
currRecipePath = "";
showRecipeArch = false;
showRecipeConf = false;
if (forceUpdate)
{
await ReloadDataAsync();
}
await RecordEdit.InvokeAsync(null);
}
protected async Task selRecord(PODLExpModel? selRec)
{
currRecord = selRec;
header = "Dettaglio PODL";
await RecordSel.InvokeAsync(selRec);
}
protected async Task startOdl(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler avviare PODL selezionato?"))
return;
if (selRec != null)
{
int idxEvento = 0;
string evMess = "";
// verifico ancora NON ci sia ODL corrente/aperto
if (canStartOdl(selRec.IdxMacchina))
{
await callStartSetup(selRec.IdxMacchina);
await Task.Delay(1);
// chiamo stored stp_ODL_inizioSetupPromessa e recupero ODL corrente
bool fatto = await MDService.POdlDoSetup(selRec);
if (fatto)
{
var currPOdl = await MDService.POdlGetByKey(selRec.IdxPromessa);
var newOdl = await MDService.OdlByKeyAsync(currPOdl.IdxOdl);
//var newOdl = await MDService.OdlGetByKey(currPOdl.IdxOdl);
// registro evento...
idxEvento = 2;
evMess = $"Inizio Setup | PODL {selRec.IdxPromessa}";
processaEvento(selRec.IdxMacchina, idxEvento, evMess, newOdl.IdxOdl, newOdl.CodArticolo);
// aspetto 1 sec
await Task.Delay(1000);
// registro inizio produzione
idxEvento = 1;
evMess = $"Registrata inizio Produzione | PODL {selRec.IdxPromessa} | ODL {newOdl.IdxOdl} | ART {newOdl.CodArticolo}";
processaEvento(selRec.IdxMacchina, idxEvento, evMess, newOdl.IdxOdl, newOdl.CodArticolo);
// imposto task x setComm, setArt, SetPzComm
await callTask2Exe(selRec.IdxMacchina, "setArt", newOdl.CodArticolo);
string odlPad = newOdl.IdxOdl.ToString(padCodXdl);
await callTask2Exe(selRec.IdxMacchina, "setComm", $"ODL{odlPad}");
await callTask2Exe(selRec.IdxMacchina, "setPzComm", $"{newOdl.NumPezzi}");
await Task.Delay(1);
// chiamo task x IOB
await callForceUpdate(selRec.IdxMacchina);
//await Task.Delay(1);
//await callForceUpdate(selRec.IdxMacchina);
await Task.Delay(1);
await callSyncDb(selRec.IdxMacchina);
// svuoto memorie pagina...
await MDService.ForceFlushRedisCache();
// svuoto cache MpIoNsCache
await MDService.FlushRedisCacheMpIoOdl();
// svuoto altra cache
await MDService.ForceFlushFusionCacheAsync();
// ricarico pagina!
NavManager.NavigateTo(NavManager.Uri, true);
}
}
}
}
protected async Task UpdateData()
{
currRecord = null;
await ReloadDataAsync();
}
#endregion Protected Methods #endregion Protected Methods
#region Private Fields #region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private bool _initialized;
private string? _lastPadCodXdl;
private HashSet<string> _machinesWithArch = new(); private HashSet<string> _machinesWithArch = new();
private HashSet<string> _machinesWithConf = new(); private HashSet<string> _machinesWithConf = new();
private HashSet<string> _odlCurrSet = new(); private HashSet<string> _odlCurrSet = new();
private string currRecipeArchPath = ""; private string currRecipeArchPath = "";
/// <summary> /// <summary>
@@ -406,13 +111,21 @@ namespace MP.SPEC.Components
private PODLExpModel? currRecord = null; private PODLExpModel? currRecord = null;
private bool enableForceSync = true;
private bool enableStartPODL = true;
private bool enableStopODL = true;
/// <summary> /// <summary>
/// Elenco articoli tipo KIT /// Elenco articoli tipo KIT
/// </summary> /// </summary>
private List<AnagArticoliModel>? ListArtKit; private List<AnagArticoliModel>? ListArtKit;
private List<TemplateKitModel>? ListKitTemplate = null; private List<TemplateKitModel>? ListKitTemplate = null;
private List<PODLExpModel>? ListPOdlKit; private List<PODLExpModel>? ListPOdlKit;
private List<PODLExpModel>? ListRecords; private List<PODLExpModel>? ListRecords;
/// <summary> /// <summary>
@@ -433,8 +146,11 @@ namespace MP.SPEC.Components
private List<string> odlCurrList = new List<string>(); private List<string> odlCurrList = new List<string>();
private List<PODLExpModel>? SearchRecords; private List<PODLExpModel>? SearchRecords;
private bool showKitDetail = false; private bool showKitDetail = false;
private bool showRecipeArch = false; private bool showRecipeArch = false;
private bool showRecipeConf = false; private bool showRecipeConf = false;
#endregion Private Fields #endregion Private Fields
@@ -455,8 +171,17 @@ namespace MP.SPEC.Components
set => actFilter.HasOdl = value; set => actFilter.HasOdl = value;
} }
private string header
{
get => actFilter.Header;
set => actFilter.Header = value;
}
private bool isLoading { get; set; } = false; private bool isLoading { get; set; } = false;
[Inject]
private IJSRuntime JSRuntime { get; set; } = null!;
private SelectXdlParams lastFilter { get; set; } = new SelectXdlParams() { CurrPage = -1 }; private SelectXdlParams lastFilter { get; set; } = new SelectXdlParams() { CurrPage = -1 };
private string macchina private string macchina
@@ -470,6 +195,15 @@ namespace MP.SPEC.Components
get => (showRecipeConf || showRecipeArch) ? "col-6" : "col-12"; get => (showRecipeConf || showRecipeArch) ? "col-6" : "col-12";
} }
[Inject]
private MpDataService MDService { get; set; } = null!;
[Inject]
private IOApiService MpIoApiCall { get; set; } = null!;
[Inject]
private NavigationManager NavManager { get; set; } = null!;
private int numRecord private int numRecord
{ {
get => actFilter.NumRec; get => actFilter.NumRec;
@@ -611,12 +345,173 @@ namespace MP.SPEC.Components
return !_odlCurrSet.Contains(idxMacchina); return !_odlCurrSet.Contains(idxMacchina);
} }
private async Task UpdateOdlList() /// <summary>
/// Verifica se sia un articolo di tipo "KIT" x mostrare show dettaglio
/// </summary>
/// <param name="CodArticolo"></param>
/// <returns></returns>
private bool CheckIsKit(string CodArticolo)
{ {
var list = await MDService.MachineWithOdlAsync(); bool answ = false;
_odlCurrSet = list.ToHashSet(); if (ListArtKit != null && ListArtKit.Count > 0)
{
answ = ListArtKit.Count(x => x.CodArticolo == CodArticolo) > 0;
}
return answ;
} }
private async Task DoCloneRecord(PODLExpModel selRec)
{
currRecord = null;
// clono resettando ODL
var clonedRec = Utils.POdlExt.clone(selRec, true);
currRecord = clonedRec;
await RecordEdit.InvokeAsync(clonedRec);
header = "Duplica PODL";
}
/// <summary>
/// Eliminazione record selezionato (previa conferma)
/// </summary>
/// <param name="selRec"></param>
/// <returns></returns>
private async Task DoDeleteRecord(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Eliminazione Record: sei sicuro di voler procedere?"))
return;
await Task.Delay(1);
var done = await MDService.POdlDeleteRecord(selRec);
await callSyncDb(selRec.IdxMacchina);
currRecord = null;
await ReloadDataAsync();
await Task.Delay(1);
}
private async Task DoResetSel(bool forceUpdate)
{
currRecord = null;
currRecipePath = "";
showRecipeArch = false;
showRecipeConf = false;
if (forceUpdate)
{
await ReloadDataAsync();
}
await RecordEdit.InvokeAsync(null);
}
private async Task DoSelRecord(PODLExpModel? selRec)
{
currRecord = selRec;
header = "Dettaglio PODL";
await RecordSel.InvokeAsync(selRec);
}
private async Task DoShowRecipeArch(PODLExpModel selRec)
{
currRecord = selRec;
currRecipeArchPath = await MDService.MacchineRecipeArchiveAsync(selRec.IdxMacchina);
showRecipeArch = true;
showRecipeConf = false;
}
private async Task DoShowRecipeConf(PODLExpModel selRec)
{
currRecord = selRec;
currRecipePath = await MDService.MacchineRecipeConfAsync(selRec.IdxMacchina);
showRecipeArch = false;
showRecipeConf = true;
}
private async Task DoStartOdl(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler avviare PODL selezionato?"))
return;
if (selRec != null)
{
int idxEvento = 0;
string evMess = "";
// verifico ancora NON ci sia ODL corrente/aperto
if (canStartOdl(selRec.IdxMacchina))
{
await callStartSetup(selRec.IdxMacchina);
await Task.Delay(1);
// chiamo stored stp_ODL_inizioSetupPromessa e recupero ODL corrente
bool fatto = await MDService.POdlDoSetup(selRec);
if (fatto)
{
var currPOdl = await MDService.POdlGetByKey(selRec.IdxPromessa);
var newOdl = await MDService.OdlByKeyAsync(currPOdl.IdxOdl);
//var newOdl = await MDService.OdlGetByKey(currPOdl.IdxOdl);
// registro evento...
idxEvento = 2;
evMess = $"Inizio Setup | PODL {selRec.IdxPromessa}";
processaEvento(selRec.IdxMacchina, idxEvento, evMess, newOdl.IdxOdl, newOdl.CodArticolo);
// aspetto 1 sec
await Task.Delay(1000);
// registro inizio produzione
idxEvento = 1;
evMess = $"Registrata inizio Produzione | PODL {selRec.IdxPromessa} | ODL {newOdl.IdxOdl} | ART {newOdl.CodArticolo}";
processaEvento(selRec.IdxMacchina, idxEvento, evMess, newOdl.IdxOdl, newOdl.CodArticolo);
// imposto task x setComm, setArt, SetPzComm
await callTask2Exe(selRec.IdxMacchina, "setArt", newOdl.CodArticolo);
string odlPad = newOdl.IdxOdl.ToString(padCodXdl);
await callTask2Exe(selRec.IdxMacchina, "setComm", $"ODL{odlPad}");
await callTask2Exe(selRec.IdxMacchina, "setPzComm", $"{newOdl.NumPezzi}");
await Task.Delay(1);
// chiamo task x IOB
await callForceUpdate(selRec.IdxMacchina);
//await Task.Delay(1);
//await callForceUpdate(selRec.IdxMacchina);
await Task.Delay(1);
await callSyncDb(selRec.IdxMacchina);
// svuoto memorie pagina...
await MDService.ForceFlushRedisCache();
// svuoto cache MpIoNsCache
await MDService.FlushRedisCacheMpIoOdl();
// svuoto altra cache
await MDService.ForceFlushFusionCacheAsync();
// ricarico pagina!
NavManager.NavigateTo(NavManager.Uri, true);
}
}
}
}
private async Task DoUpdateData()
{
currRecord = null;
await ReloadDataAsync();
}
private async Task DoEditRecord(PODLExpModel? selRec)
{
currRecord = selRec;
header = "Modifica PODL";
await RecordEdit.InvokeAsync(selRec);
}
private async Task KitToggleDetailAsync(PODLExpModel? recSel)
{
if (recSel != null)
{
ListKitTemplate = await MDService.TemplateKitFiltAsync(recSel.CodArticolo, "");
ListPOdlKit = await MDService.POdlListByKitParentAsync(recSel.IdxPromessa);
}
else
{
ListKitTemplate = null;
ListPOdlKit = null;
}
showKitDetail = !showKitDetail;
}
/// <summary> /// <summary>
/// Verifica se la idxMaccSel abbia associata un path x ricette (elenco) /// Verifica se la idxMaccSel abbia associata un path x ricette (elenco)
@@ -638,6 +533,8 @@ namespace MP.SPEC.Components
return _machinesWithConf.Contains(idxMacchina); return _machinesWithConf.Contains(idxMacchina);
} }
private bool POdlDelEnabled(int idxOdl) => idxOdl == 0;
/// <summary> /// <summary>
/// processa evento richiesto /// processa evento richiesto
/// </summary> /// </summary>
@@ -673,6 +570,95 @@ namespace MP.SPEC.Components
await MDService.EvListInsert(newRec); await MDService.EvListInsert(newRec);
} }
/// <summary>
/// Caricamento dati di base
/// </summary>
/// <returns></returns>
private async Task ReloadBaseData()
{
ListRecords = null;
isLoading = true;
var list = await MDService.MachineWithOdlAsync();
_odlCurrSet = list.ToHashSet();
var machines = await MDService.MacchineGetFiltAsync("*");
_machinesWithConf = machines
.Where(x => !string.IsNullOrEmpty(x.RecipePath))
.Select(x => x.IdxMacchina)
.ToHashSet();
_machinesWithArch = machines
.Where(x => !string.IsNullOrEmpty(x.RecipeArchivePath))
.Select(x => x.IdxMacchina)
.ToHashSet();
ListStati = await MDService.AnagStatiCommAsync();
ListArtKit = await MDService.ArticoliGetByTipoAsync("KIT", "*");
string strMachRecipe = await MDService.ConfigTryGetAsync("MachineWithRecipe");
if (!string.IsNullOrEmpty(strMachRecipe))
{
bool.TryParse(strMachRecipe, out MachineWithRecipe);
}
string SPEC_PODL_gest = await MDService.ConfigTryGetAsync("SPEC_PODL_gest");
if (!string.IsNullOrEmpty(SPEC_PODL_gest))
{
bool.TryParse(SPEC_PODL_gest, out enableStartPODL);
}
string SPEC_ODL_gest = await MDService.ConfigTryGetAsync("SPEC_ODL_gest");
if (!string.IsNullOrEmpty(SPEC_ODL_gest))
{
bool.TryParse(SPEC_ODL_gest, out enableStopODL);
}
string SPEC_XODL_sync = await MDService.ConfigTryGetAsync("SPEC_XODL_sync");
if (!string.IsNullOrEmpty(SPEC_XODL_sync))
{
bool.TryParse(SPEC_XODL_sync, out enableForceSync);
}
}
private async Task ReloadDataAsync()
{
isLoading = true;
ListRecords = null;
// ✅ lancia in parallelo
var odlTask = UpdateOdlListAsync();
Task<List<PODLExpModel>> searchTask;
// imposto con sempre kit, opzionale kit child, opzionale attive
searchTask = MDService.POdlToKitListGetFiltAsync(hasOdl, StatoSel, macchina, reparto, selDtStart, selDtEnd, actFilter.IsActive, actFilter.ShowKit);
// ✅ aspetta tutto insieme
await Task.WhenAll(odlTask, searchTask);
var rawList = searchTask.Result;
// se abilitata ricerca filtro ulteriormente..
if (string.IsNullOrEmpty(actFilter.SearchVal))
{
SearchRecords = rawList;
}
else
{
SearchRecords = rawList
.Where(x =>
x.CodArticolo.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
|| x.CodFase.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
|| x.DescArticolo.Contains(actFilter.SearchVal, StringComparison.InvariantCulture)
).ToList();
}
totalCount = SearchRecords.Count;
ListRecords = SearchRecords
.Skip(numRecord * (currPage - 1))
.Take(numRecord)
.ToList();
isLoading = false;
}
private string tradFase(string codFase) private string tradFase(string codFase)
{ {
string answ = codFase; string answ = codFase;
@@ -687,6 +673,12 @@ namespace MP.SPEC.Components
return answ; return answ;
} }
private async Task UpdateOdlListAsync()
{
var list = await MDService.MachineWithOdlAsync();
_odlCurrSet = list.ToHashSet();
}
#endregion Private Methods #endregion Private Methods
} }
} }
+5 -5
View File
@@ -70,7 +70,7 @@
</div> </div>
<div class="px-2 input-group"> <div class="px-2 input-group">
<label class="input-group-text" for="macchina" title="Selezionare impianto"><i class="fa-solid fa-hard-drive"></i></label> <label class="input-group-text" for="macchina" title="Selezionare impianto"><i class="fa-solid fa-hard-drive"></i></label>
<select @bind="@selMacchina" class="form-select" id="macchina" title="Selezionare impianto"> <select @bind="@selMacchina" @bind:after="ReportChangeMacc" class="form-select" id="macchina" title="Selezionare impianto">
<option value="*">--- Tutti ---</option> <option value="*">--- Tutti ---</option>
@if (ListMacchine != null) @if (ListMacchine != null)
{ {
@@ -102,21 +102,21 @@
</div> </div>
<div class="px-2 input-group"> <div class="px-2 input-group">
<label class="input-group-text" for="dtMin" title="Selezionare inizio periodo"><i class="fa-regular fa-calendar-minus"></i></label> <label class="input-group-text" for="dtMin" title="Selezionare inizio periodo"><i class="fa-regular fa-calendar-minus"></i></label>
<input class="form-control" @bind="@selDtMin" id="dtMin" type="datetime-local" title="Data minima eventi da visualizzare"> <input class="form-control" @bind="@selDtMin" @bind:after="ReportChangeAsync" id="dtMin" type="datetime-local" title="Data minima eventi da visualizzare">
</div> </div>
<div class="small mt-2"> <div class="small mt-2">
<label class="px-2" for="dtMax" title="Selezionare fine periodo">Fine Periodo</label> <label class="px-2" for="dtMax" title="Selezionare fine periodo">Fine Periodo</label>
</div> </div>
<div class="px-2 input-group"> <div class="px-2 input-group">
<label class="input-group-text" for="dtMax" title="Selezionare fine periodo"><i class="fa-regular fa-calendar-plus"></i></label> <label class="input-group-text" for="dtMax" title="Selezionare fine periodo"><i class="fa-regular fa-calendar-plus"></i></label>
<input class="form-control" @bind="@selDtMax" id="dtMax" type="datetime-local" title="Selezionare fine periodo"> <input class="form-control" @bind="@selDtMax" @bind:after="ReportChangeAsync" id="dtMax" type="datetime-local" title="Selezionare fine periodo">
</div> </div>
<div class="small mt-2"> <div class="small mt-2">
<label class="px-2" for="tempoAgg" title="Selezionare refresh rate (sec) periodo">Refresh rate (sec)</label> <label class="px-2" for="tempoAgg" title="Selezionare refresh rate (sec) periodo">Refresh rate (sec)</label>
</div> </div>
<div class="px-2 input-group"> <div class="px-2 input-group">
<label class="input-group-text" for="tempoAgg" title="Selezionare refresh rate (sec)"><i class="fa-solid fa-clock"></i></label> <label class="input-group-text" for="tempoAgg" title="Selezionare refresh rate (sec)"><i class="fa-solid fa-clock"></i></label>
<select @bind="@selTempoAgg" class="form-select" id="tempoAgg" title="Selezionare refresh rate (sec)" style="width: 3em;"> <select @bind="@selTempoAgg" @bind:after="ReportChangeAsync" class="form-select" id="tempoAgg" title="Selezionare refresh rate (sec)" style="width: 3em;">
<option value="2">2</option> <option value="2">2</option>
<option value="5">5</option> <option value="5">5</option>
<option value="10">10</option> <option value="10">10</option>
@@ -129,7 +129,7 @@
</div> </div>
<div class="px-2 input-group"> <div class="px-2 input-group">
<label class="input-group-text" for="maxRecord" title="Numero massimo record da mostrare"><i class="fa-solid fa-list-ol"></i></label> <label class="input-group-text" for="maxRecord" title="Numero massimo record da mostrare"><i class="fa-solid fa-list-ol"></i></label>
<select @bind="@selMaxRecord" class="form-select" id="maxRecord" title="Numero massimo record da mostrare"> <select @bind="@selMaxRecord" @bind:after="ReportChangeAsync" class="form-select" id="maxRecord" title="Numero massimo record da mostrare">
<option value="50">50</option> <option value="50">50</option>
<option value="100">100</option> <option value="100">100</option>
<option value="250">250</option> <option value="250">250</option>
+24 -17
View File
@@ -40,7 +40,7 @@ namespace MP.SPEC.Components
snapshotDone = false; snapshotDone = false;
aTimer.Stop(); aTimer.Stop();
aTimer.Enabled = false; aTimer.Enabled = false;
//reportChange(); //ReportChangeAsync();
var pUpd = Task.Run(async () => var pUpd = Task.Run(async () =>
{ {
await Task.Delay(1); await Task.Delay(1);
@@ -71,7 +71,7 @@ namespace MP.SPEC.Components
{ {
SelFilter.CurrPage = 0; SelFilter.CurrPage = 0;
} }
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -91,12 +91,13 @@ namespace MP.SPEC.Components
{ {
SelFilter.CurrPage = 1; SelFilter.CurrPage = 1;
SelFilter.CodFlux = value; SelFilter.CodFlux = value;
StateHasChanged(); //StateHasChanged();
Task.Delay(1); //Task.Delay(1);
reportChange(); //ReportChangeAsync();
} }
} }
} }
protected bool showParam { get; set; } = false; protected bool showParam { get; set; } = false;
protected bool selDt { get; set; } = false; protected bool selDt { get; set; } = false;
protected string selMacchina protected string selMacchina
@@ -109,14 +110,19 @@ namespace MP.SPEC.Components
SelFilter.CurrPage = 1; SelFilter.CurrPage = 1;
SelFilter.IdxMacchina = value; SelFilter.IdxMacchina = value;
SelFilter.CodFlux = "*"; SelFilter.CodFlux = "*";
ListFlux = MDService.ParametriGetFiltAsync(selMacchina).Result; //StateHasChanged();
StateHasChanged(); //Task.Delay(1);
Task.Delay(1); //ReportChangeAsync();
reportChange();
} }
} }
} }
private async Task ReportChangeMacc()
{
ListFlux = await MDService.ParametriGetFiltAsync(selMacchina);
await ReportChangeAsync();
}
protected int selMaxRecord protected int selMaxRecord
{ {
get get
@@ -129,7 +135,7 @@ namespace MP.SPEC.Components
if (!SelFilter.MaxRecord.Equals(value)) if (!SelFilter.MaxRecord.Equals(value))
{ {
SelFilter.MaxRecord = value; SelFilter.MaxRecord = value;
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -147,7 +153,7 @@ namespace MP.SPEC.Components
if (!SelFilter.TempoAgg.Equals(tempoMS)) if (!SelFilter.TempoAgg.Equals(tempoMS))
{ {
SelFilter.TempoAgg = tempoMS; SelFilter.TempoAgg = tempoMS;
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -181,7 +187,8 @@ namespace MP.SPEC.Components
setDtSnap(); setDtSnap();
DateTime dtStart = SelFilter.dtMin != null ? (DateTime)SelFilter.dtMin : DateTime.Now.AddMonths(-1); DateTime dtStart = SelFilter.dtMin != null ? (DateTime)SelFilter.dtMin : DateTime.Now.AddMonths(-1);
DateTime dtEnd = SelFilter.dtMax != null ? (DateTime)SelFilter.dtMax : DateTime.Today.AddDays(1); DateTime dtEnd = SelFilter.dtMax != null ? (DateTime)SelFilter.dtMax : DateTime.Today.AddDays(1);
ListMacchine = await MDService.MacchineWithFluxAsync(dtStart, dtEnd); var rawListMacc = await MDService.MacchineWithFluxAsync(dtStart, dtEnd);
ListMacchine = rawListMacc.OrderBy(x => x).ToList();
ListFlux = await MDService.ParametriGetFiltAsync(selMacchina); ListFlux = await MDService.ParametriGetFiltAsync(selMacchina);
var configData = await MDService.ConfigGetAllAsync(); var configData = await MDService.ConfigGetAllAsync();
@@ -295,7 +302,7 @@ namespace MP.SPEC.Components
currFilt.lastUpdate = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss}"; currFilt.lastUpdate = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss}";
currFilt.dtMax = value; currFilt.dtMax = value;
SelFilter = currFilt; SelFilter = currFilt;
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -315,7 +322,7 @@ namespace MP.SPEC.Components
currFilt.lastUpdate = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss}"; currFilt.lastUpdate = $"{DateTime.Now:yyyy/MM/dd HH:mm:ss}";
currFilt.dtMin = value; currFilt.dtMin = value;
SelFilter = currFilt; SelFilter = currFilt;
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -328,7 +335,7 @@ namespace MP.SPEC.Components
if (SelFilter.dtSnapMin != value) if (SelFilter.dtSnapMin != value)
{ {
SelFilter.dtSnapMin = value; SelFilter.dtSnapMin = value;
reportChange(); //ReportChangeAsync();
} }
} }
} }
@@ -344,9 +351,9 @@ namespace MP.SPEC.Components
#region Private Methods #region Private Methods
private void reportChange() private async Task ReportChangeAsync()
{ {
FilterChanged.InvokeAsync(SelFilter); await FilterChanged.InvokeAsync(SelFilter);
} }
#endregion Private Methods #endregion Private Methods
@@ -169,6 +169,7 @@ namespace MP.SPEC.Components.ProdKit
protected async Task toggleClosed() protected async Task toggleClosed()
{ {
hasOdl = !hasOdl; hasOdl = !hasOdl;
currPage = 1;
await EC_HasOdl.InvokeAsync(hasOdl); await EC_HasOdl.InvokeAsync(hasOdl);
} }
@@ -220,12 +220,6 @@ namespace MP.SPEC.Components.ProdKit
set => ActFilter.CurrPage = value; set => ActFilter.CurrPage = value;
} }
private bool hasOdl
{
get => ActFilter.HasOdl;
set => ActFilter.HasOdl = value;
}
private bool isLoading { get; set; } = false; private bool isLoading { get; set; } = false;
private SelectXdlParams lastFilter { get; set; } = new SelectXdlParams() { CurrPage = -1 }; private SelectXdlParams lastFilter { get; set; } = new SelectXdlParams() { CurrPage = -1 };
+2 -2
View File
@@ -54,10 +54,10 @@
</table> </table>
} }
<div runat="server" id="divPODL"> <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) @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 else
{ {
@@ -100,7 +100,7 @@ namespace MP.SPEC.Components.ProdKit
// eseguo stored... // eseguo stored...
bool fatto = await MDService.IstKitInsertByWKSAsync(currRec.CodArtParent, KeyFilt); bool fatto = await MDService.IstKitInsertByWKSAsync(currRec.CodArtParent, KeyFilt);
// segnalo update // segnalo DoUpdate
await EC_KitCreated.InvokeAsync(fatto); await EC_KitCreated.InvokeAsync(fatto);
} }
+2 -2
View File
@@ -83,7 +83,7 @@ namespace MP.SPEC.Components.ProdKit
{ {
if (selRec != null) if (selRec != null)
{ {
// chiamo tentativo update! // chiamo tentativo DoUpdate!
WipSetupKitModel newRec = new WipSetupKitModel() WipSetupKitModel newRec = new WipSetupKitModel()
{ {
KeyFilt = keyFilt, KeyFilt = keyFilt,
@@ -188,7 +188,7 @@ namespace MP.SPEC.Components.ProdKit
{ {
listPOdlCheck = new List<PODLExpModel>(); listPOdlCheck = new List<PODLExpModel>();
listPOdlAct = await MDService.POdlListGetFiltAsync(ActFilt.HasOdl, ActFilt.CodFase, ActFilt.IdxMacchina, ActFilt.CodReparto, ActFilt.DtStart, ActFilt.DtEnd); 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); listPOdl2Kit = await MDService.POdlToKitListGetFiltAsync(false, ActFilt.CodFase, ActFilt.IdxMacchina, ActFilt.CodReparto, ActFilt.DtStart, ActFilt.DtEnd, true, false);
listWSM = await MDService.WipKitFiltAsync(keyFilt); listWSM = await MDService.WipKitFiltAsync(keyFilt);
listTSM = await MDService.TksScoreAsync(keyFilt, 1000, true); listTSM = await MDService.TksScoreAsync(keyFilt, 1000, true);
listIKP = await MDService.IstKitFiltAsync("", ""); listIKP = await MDService.IstKitFiltAsync("", "");
@@ -36,7 +36,10 @@
<table class="table table-sm table-striped small"> <table class="table table-sm table-striped small">
<thead> <thead>
<tr> <tr>
@if (SelEnabled)
{
<th><button class="btn btn-sm btn-info" title="Reset" @onclick="DoReset"><i class="fa-solid fa-rotate"></i></button></th> <th><button class="btn btn-sm btn-info" title="Reset" @onclick="DoReset"><i class="fa-solid fa-rotate"></i></button></th>
}
<th><i class="fa-solid fa-key"></i> Matr.</th> <th><i class="fa-solid fa-key"></i> Matr.</th>
<th><i class="fa-solid fa-object-group"></i> Anagr.</th> <th><i class="fa-solid fa-object-group"></i> Anagr.</th>
@if (DelEnabled) @if (DelEnabled)
@@ -53,9 +56,13 @@
@foreach (var item in ListRecords) @foreach (var item in ListRecords)
{ {
<tr class="@cssRow(item)"> <tr class="@cssRow(item)">
@if (SelEnabled)
{
<td> <td>
<button class="btn btn-sm btn-info" title="Mostra Assegnazioni" @onclick="() => DoSelect(item)"><i class="fa-solid fa-search"></i></button> <button class="btn btn-sm btn-info" title="Mostra Assegnazioni" @onclick="() => DoSelect(item)"><i class="fa-solid fa-search"></i></button>
</td> </td>
}
<td class="@cssCol(item)"> <td class="@cssCol(item)">
<div>@item.MatrOpr</div> <div>@item.MatrOpr</div>
</td> </td>
@@ -23,6 +23,8 @@ namespace MP.SPEC.Components.Reparti
[Parameter] [Parameter]
public bool DelEnabled { get; set; } = true; public bool DelEnabled { get; set; } = true;
[Parameter]
public bool SelEnabled { get; set; } = true;
[Parameter] [Parameter]
public EventCallback<bool> EC_RecChange { get; set; } public EventCallback<bool> EC_RecChange { get; set; }
@@ -156,7 +158,7 @@ namespace MP.SPEC.Components.Reparti
private async Task DoReset() private async Task DoReset()
{ {
selRec = null; selRec = null;
await EC_RecSel.InvokeAsync(null); await EC_RecChange.InvokeAsync(false);
} }
private async Task ReportUpdate(bool force) private async Task ReportUpdate(bool force)
@@ -204,6 +204,7 @@ namespace MP.SPEC.Components.Reparti
SelRecord = null; SelRecord = null;
EditRec = null; EditRec = null;
UpdateTable(); UpdateTable();
await EC_RecordUpdated.InvokeAsync(true);
await EC_RecordSel.InvokeAsync(""); await EC_RecordSel.InvokeAsync("");
} }
+1 -1
View File
@@ -69,7 +69,7 @@ namespace MP.SPEC.Components
private async Task OnSelectionChanged(ChangeEventArgs e) private async Task OnSelectionChanged(ChangeEventArgs e)
{ {
Value = e.Value?.ToString(); Value = e.Value?.ToString() ?? "";
// Notifica il componente padre della variazione // Notifica il componente padre della variazione
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
+1 -1
View File
@@ -87,7 +87,7 @@
</div> </div>
</div> </div>
} }
@if (!isActive || hasOdl) @if (true || !isActive || hasOdl)
{ {
<div class="small mt-2"> <div class="small mt-2">
<label class="px-2" for="dtMin" title="Selezionare inizio periodo">Inizio Periodo</label> <label class="px-2" for="dtMin" title="Selezionare inizio periodo">Inizio Periodo</label>
@@ -15,7 +15,6 @@ namespace MP.SPEC.Controllers
public RecipeArchiveController(IConfiguration configuration, MpDataService DataService) public RecipeArchiveController(IConfiguration configuration, MpDataService DataService)
{ {
Log.Info("Starting RecipeArchiveController"); Log.Info("Starting RecipeArchiveController");
_configuration = configuration;
DService = DataService; DService = DataService;
Log.Info("Avviata classe RecipeArchiveController"); Log.Info("Avviata classe RecipeArchiveController");
} }
@@ -71,8 +70,6 @@ namespace MP.SPEC.Controllers
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields #endregion Private Fields
+1 -4
View File
@@ -14,10 +14,9 @@ namespace MP.SPEC.Controllers
{ {
#region Public Constructors #region Public Constructors
public RecipeController(IConfiguration configuration, MpDataService DataService) public RecipeController(MpDataService DataService)
{ {
Log.Info("Starting RecipeController"); Log.Info("Starting RecipeController");
_configuration = configuration;
DService = DataService; DService = DataService;
Log.Info("Avviata classe RecipeController"); Log.Info("Avviata classe RecipeController");
} }
@@ -66,8 +65,6 @@ namespace MP.SPEC.Controllers
#region Private Fields #region Private Fields
private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields #endregion Private Fields
+63 -54
View File
@@ -25,12 +25,6 @@ namespace MP.SPEC.Data
{ {
#region Public Constructors #region Public Constructors
private readonly IAnagRepository _anagRepository;
private readonly ISystemRepository _systemRepository;
private readonly IDossierRepository _dossierRepository;
private readonly IFluxLogRepository _fluxLogRepository;
private readonly IProductionRepository _productionRepository;
public MpDataService( public MpDataService(
IConnectionMultiplexer connMPlex, IConnectionMultiplexer connMPlex,
IConfiguration configuration, IConfiguration configuration,
@@ -46,12 +40,6 @@ namespace MP.SPEC.Data
redisConn = connMPlex; redisConn = connMPlex;
redisDb = redisConn.GetDatabase(); redisDb = redisConn.GetDatabase();
#if false
// setup compoenti REDIS
redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis") ?? "localhost:6379");
redisConnAdmin = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("RedisAdmin") ?? "localhost:6379");
redisDb = redisConn.GetDatabase();
#endif
// leggo cache lungo/cordo periodo // leggo cache lungo/cordo periodo
int.TryParse(_configuration.GetValue<string>("ServerConf:redisShortTimeCache"), out redisShortTimeCache); int.TryParse(_configuration.GetValue<string>("ServerConf:redisShortTimeCache"), out redisShortTimeCache);
int.TryParse(_configuration.GetValue<string>("ServerConf:redisLongTimeCache"), out redisLongTimeCache); int.TryParse(_configuration.GetValue<string>("ServerConf:redisLongTimeCache"), out redisLongTimeCache);
@@ -376,15 +364,15 @@ namespace MP.SPEC.Data
/// </summary> /// </summary>
/// <param name="currRec"></param> /// <param name="currRec"></param>
/// <returns></returns> /// <returns></returns>
public async Task<bool> ArticoliUpdateRecord(AnagArticoliModel currRec) public async Task<bool> ArticoliUpsertRecord(AnagArticoliModel currRec)
{ {
using var activity = ActivitySource.StartActivity("ArticoliUpdateRecord"); using var activity = ActivitySource.StartActivity("ArticoliUpsertRecord");
string source = "DB"; string source = "DB";
bool fatto = await _anagRepository.ArticoliUpdateRecord(currRec); bool fatto = await _anagRepository.ArticoliUpsertAsync(currRec);
await FlushFusionCacheArticoli(); await FlushFusionCacheArticoli();
activity?.SetTag("data.source", source); activity?.SetTag("data.source", source);
activity?.Stop(); activity?.Stop();
LogTrace($"ArticoliUpdateRecord | {source} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"ArticoliUpsertRecord | {source} | {activity?.Duration.TotalMilliseconds}ms");
return fatto; return fatto;
} }
@@ -700,8 +688,13 @@ namespace MP.SPEC.Data
/// Restitusice elenco Reparti /// Restitusice elenco Reparti
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public async Task<List<RepartiDTO>> ElencoRepartiDtoAsync() public async Task<List<RepartiDTO>> ElencoRepartiDtoAsync(bool forceReload)
{ {
if (forceReload)
{
await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp();
}
return await GetOrFetchAsync( return await GetOrFetchAsync(
operationName: "ElencoRepartiDtoAsync", operationName: "ElencoRepartiDtoAsync",
cacheKey: $"{Utils.redisAnagGruppi}:REPARTO", cacheKey: $"{Utils.redisAnagGruppi}:REPARTO",
@@ -710,20 +703,6 @@ namespace MP.SPEC.Data
tagList: [Utils.redisAnagGruppi] tagList: [Utils.redisAnagGruppi]
); );
} }
/// <summary>
/// Restitusice elenco Reparti
/// </summary>
/// <returns></returns>
public async Task<List<RepartiDTO>> GruppiRepartoDtoByOperAsync(int matrOpr)
{
return await GetOrFetchAsync(
operationName: "ElencoRepartiDtoAsync",
cacheKey: $"{Utils.redisAnagGruppiOpr}:{matrOpr}",
expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _anagRepository.GruppiRepartoDtoByOperAsync(matrOpr) ?? new(),
tagList: [Utils.redisAnagGruppiOpr]
);
}
/// <summary> /// <summary>
/// Caricamento asincrono della cache degli articoli (Used/Unused) /// Caricamento asincrono della cache degli articoli (Used/Unused)
@@ -780,6 +759,17 @@ namespace MP.SPEC.Data
return result; return result;
} }
public async Task<bool> FlushFusionCacheArticoli()
{
using var activity = ActivitySource.StartActivity("FlushFusionCacheArticoli");
string source = "FUSION";
bool answ = await FlushFusionCacheAsync(new List<string>() { Utils.redisArtList, Utils.redisArtByDossier });
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"FlushFusionCacheArticoli | {source} | {activity?.Duration.TotalMilliseconds}ms");
return answ;
}
/// <summary> /// <summary>
/// Flush cache relativa a MP-IO x dati ODL /// Flush cache relativa a MP-IO x dati ODL
/// </summary> /// </summary>
@@ -952,6 +942,7 @@ namespace MP.SPEC.Data
result = await _productionRepository.Grp2MaccDeleteAsync(rec2del); result = await _productionRepository.Grp2MaccDeleteAsync(rec2del);
// elimino cache redis... // elimino cache redis...
await FlushFusionCacheMacGrp(); await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp();
activity?.SetTag("data.source", "DB"); activity?.SetTag("data.source", "DB");
activity?.Stop(); activity?.Stop();
LogTrace($"Grp2MaccDeleteAsync | CodGruppo {rec2del.CodGruppo} | IdxMacc {rec2del.IdxMacchina} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"Grp2MaccDeleteAsync | CodGruppo {rec2del.CodGruppo} | IdxMacc {rec2del.IdxMacchina} | {activity?.Duration.TotalMilliseconds}ms");
@@ -970,6 +961,7 @@ namespace MP.SPEC.Data
result = await _productionRepository.Grp2MaccInsertAsync(upsRec); result = await _productionRepository.Grp2MaccInsertAsync(upsRec);
// elimino cache redis... // elimino cache redis...
await FlushFusionCacheMacGrp(); await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp();
activity?.SetTag("data.source", "DB"); activity?.SetTag("data.source", "DB");
activity?.Stop(); activity?.Stop();
LogTrace($"Grp2MaccInsertAsync | CodGruppo {upsRec.CodGruppo} | IdxMacc {upsRec.IdxMacchina} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"Grp2MaccInsertAsync | CodGruppo {upsRec.CodGruppo} | IdxMacc {upsRec.IdxMacchina} | {activity?.Duration.TotalMilliseconds}ms");
@@ -987,6 +979,7 @@ namespace MP.SPEC.Data
bool result = false; bool result = false;
result = await _productionRepository.Grp2OperDeleteAsync(rec2del); result = await _productionRepository.Grp2OperDeleteAsync(rec2del);
// elimino cache redis... // elimino cache redis...
await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp(); await FlushFusionCacheOprGrp();
activity?.SetTag("data.source", "DB"); activity?.SetTag("data.source", "DB");
activity?.Stop(); activity?.Stop();
@@ -1005,6 +998,7 @@ namespace MP.SPEC.Data
bool result = false; bool result = false;
result = await _productionRepository.Grp2OperInsertAsync(upsRec); result = await _productionRepository.Grp2OperInsertAsync(upsRec);
// elimino cache redis... // elimino cache redis...
await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp(); await FlushFusionCacheOprGrp();
activity?.SetTag("data.source", "DB"); activity?.SetTag("data.source", "DB");
activity?.Stop(); activity?.Stop();
@@ -1012,6 +1006,21 @@ namespace MP.SPEC.Data
return result; return result;
} }
/// <summary>
/// Restitusice elenco Reparti
/// </summary>
/// <returns></returns>
public async Task<List<RepartiDTO>> GruppiRepartoDtoByOperAsync(int matrOpr)
{
return await GetOrFetchAsync(
operationName: "ElencoRepartiDtoAsync",
cacheKey: $"{Utils.redisAnagGruppiOpr}:{matrOpr}",
expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _anagRepository.GruppiRepartoDtoByOperAsync(matrOpr) ?? new(),
tagList: [Utils.redisAnagGruppiOpr]
);
}
/// <summary> /// <summary>
/// Init ricetta /// Init ricetta
/// </summary> /// </summary>
@@ -1442,7 +1451,8 @@ namespace MP.SPEC.Data
bool fatto = false; bool fatto = false;
// salvo // salvo
fatto = await _productionRepository.OperatoriUpsertAsync(updRec); fatto = await _productionRepository.OperatoriUpsertAsync(updRec);
await FlushFusionCacheAsync(Utils.redisOprList); await FlushFusionCacheMacGrp();
await FlushFusionCacheOprGrp();
activity?.SetTag("data.source", source); activity?.SetTag("data.source", source);
activity?.Stop(); activity?.Stop();
LogTrace($"OperatoriUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"OperatoriUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms");
@@ -1669,10 +1679,15 @@ namespace MP.SPEC.Data
/// <param name="codGruppo">Gruppo</param> /// <param name="codGruppo">Gruppo</param>
/// <param name="startDate">Data inizio</param> /// <param name="startDate">Data inizio</param>
/// <param name="endDate">Data fine</param> /// <param name="endDate">Data fine</param>
/// <param name="flagAttive">se true = solo attive</param>
/// <param name="flagKitChild">se true = mostra Kit Child</param>
/// <returns></returns> /// <returns></returns>
public async Task<List<PODLExpModel>> POdlToKitListGetFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) public async Task<List<PODLExpModel>> POdlToKitListGetFiltAsync(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate, bool flagAttive, bool flagKitChild)
{ {
string redisKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; string codLanc = lanciato ? "ALL" : "READY";
string codFlagAtt = flagAttive ? "ACT" : "ALL";
string codFlagKCh = flagKitChild ? "KALL" : "KP";
string redisKey = $"{Utils.redisPOdlList}_kit:{codGruppo}:{idxMacchina}:{keyRichPart}:{codLanc}:{codFlagAtt}:{codFlagKCh}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}";
return await GetOrFetchAsync( return await GetOrFetchAsync(
operationName: "POdlToKitListGetFiltAsync", operationName: "POdlToKitListGetFiltAsync",
@@ -1685,7 +1700,9 @@ namespace MP.SPEC.Data
idxMacchina, idxMacchina,
codGruppo, codGruppo,
startDate, startDate,
endDate endDate,
flagAttive,
flagKitChild
) ?? new List<PODLExpModel>(), ) ?? new List<PODLExpModel>(),
tagList: [Utils.redisPOdlList, $"{Utils.redisPOdlList}_kit"] tagList: [Utils.redisPOdlList, $"{Utils.redisPOdlList}_kit"]
); );
@@ -1909,7 +1926,7 @@ namespace MP.SPEC.Data
return await GetOrFetchAsync( return await GetOrFetchAsync(
operationName: "TksScoreAsync", operationName: "TksScoreAsync",
cacheKey: currKey, cacheKey: currKey,
expiration: TimeSpan.FromMinutes(redisLongTimeCache), expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _productionRepository.TksScoreAsync(KeyFilt, MaxResult) ?? new List<TksScoreModel>(), fetchFunc: async () => await _productionRepository.TksScoreAsync(KeyFilt, MaxResult) ?? new List<TksScoreModel>(),
tagList: [Utils.redisKitScore] tagList: [Utils.redisKitScore]
); );
@@ -1999,7 +2016,7 @@ namespace MP.SPEC.Data
return await GetOrFetchAsync( return await GetOrFetchAsync(
operationName: "WipKitFiltAsync", operationName: "WipKitFiltAsync",
cacheKey: currKey, cacheKey: currKey,
expiration: TimeSpan.FromMinutes(redisLongTimeCache), expiration: GetRandTOut(redisLongTimeCache),
fetchFunc: async () => await _productionRepository.WipKitFiltAsync(KeyFilt) ?? new List<WipSetupKitModel>(), fetchFunc: async () => await _productionRepository.WipKitFiltAsync(KeyFilt) ?? new List<WipSetupKitModel>(),
tagList: [Utils.redisKitWip] tagList: [Utils.redisKitWip]
); );
@@ -2018,6 +2035,7 @@ namespace MP.SPEC.Data
fatto = await _productionRepository.WipKitUpsertAsync(currRecord); fatto = await _productionRepository.WipKitUpsertAsync(currRecord);
// svuoto cache KitWip // svuoto cache KitWip
await FlushFusionCacheAsync(Utils.redisKitWip); await FlushFusionCacheAsync(Utils.redisKitWip);
await FlushFusionCacheAsync(Utils.redisKitScore);
activity?.SetTag("data.source", source); activity?.SetTag("data.source", source);
activity?.Stop(); activity?.Stop();
LogTrace($"WipKitUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"WipKitUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms");
@@ -2034,11 +2052,13 @@ namespace MP.SPEC.Data
private static readonly ActivitySource ActivitySource = new ActivitySource("MP.DATA.Tracer"); private static readonly ActivitySource ActivitySource = new ActivitySource("MP.DATA.Tracer");
private static IConfiguration _configuration = null!; private static IConfiguration _configuration = null!;
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly IAnagRepository _anagRepository;
private readonly IFusionCache _cache; private readonly IFusionCache _cache;
private readonly IDossierRepository _dossierRepository;
private readonly IFluxLogRepository _fluxLogRepository;
private readonly IProductionRepository _productionRepository;
private readonly ISystemRepository _systemRepository;
private DateTime _artCacheExpiry = DateTime.MinValue; private DateTime _artCacheExpiry = DateTime.MinValue;
private Dictionary<string, string> _configData = new(); private Dictionary<string, string> _configData = new();
@@ -2100,8 +2120,6 @@ namespace MP.SPEC.Data
#region Private Properties #region Private Properties
private static MpSpecController dbController { get; set; } = null!;
private static MpMongoController mongoController { get; set; } = null!; private static MpMongoController mongoController { get; set; } = null!;
#endregion Private Properties #endregion Private Properties
@@ -2170,17 +2188,6 @@ namespace MP.SPEC.Data
return answ; return answ;
} }
private async Task<bool> FlushFusionCacheArticoli()
{
using var activity = ActivitySource.StartActivity("FlushFusionCacheArticoli");
string source = "FUSION";
bool answ = await FlushFusionCacheAsync(new List<string>() { Utils.redisArtList, Utils.redisArtByDossier });
activity?.SetTag("data.source", source);
activity?.Stop();
LogTrace($"FlushFusionCacheArticoli | {source} | {activity?.Duration.TotalMilliseconds}ms");
return answ;
}
/// <summary> /// <summary>
/// Cancellazione FusionCache (totale) /// Cancellazione FusionCache (totale)
/// </summary> /// </summary>
@@ -2196,6 +2203,7 @@ namespace MP.SPEC.Data
/// <summary> /// <summary>
/// Cancellazione FusionCache dato singolo tag /// Cancellazione FusionCache dato singolo tag
/// </summary> /// </summary>
/// <param name="tag"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> FlushFusionCacheAsync(string tag) private async Task<bool> FlushFusionCacheAsync(string tag)
{ {
@@ -2209,6 +2217,7 @@ namespace MP.SPEC.Data
/// <summary> /// <summary>
/// Cancellazione FusionCache dato elenco tags /// Cancellazione FusionCache dato elenco tags
/// </summary> /// </summary>
/// <param name="listTags"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> FlushFusionCacheAsync(List<string> listTags) private async Task<bool> FlushFusionCacheAsync(List<string> listTags)
{ {
@@ -2279,7 +2288,7 @@ namespace MP.SPEC.Data
{ {
using var activity = ActivitySource.StartActivity("FlushFusionCacheOprGrp"); using var activity = ActivitySource.StartActivity("FlushFusionCacheOprGrp");
string source = "FUSION"; string source = "FUSION";
bool answ = await FlushFusionCacheAsync(new List<string> { Utils.redisAnagGruppi, Utils.redisOprList }); bool answ = await FlushFusionCacheAsync(new List<string> { Utils.redisAnagGruppi, Utils.redisOprList, Utils.redisAnagGruppiOpr });
activity?.SetTag("data.source", source); activity?.SetTag("data.source", source);
activity?.Stop(); activity?.Stop();
LogTrace($"FlushFusionCacheOprGrp | {source} | {activity?.Duration.TotalMilliseconds}ms"); LogTrace($"FlushFusionCacheOprGrp | {source} | {activity?.Duration.TotalMilliseconds}ms");
+1 -1
View File
@@ -12,7 +12,7 @@
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Bool: indica se il toggleClosed è attivo /// Bool: indica se il DoToggleClosed è attivo
/// </summary> /// </summary>
public bool isActive { get; set; } = true; public bool isActive { get; set; } = true;
+6 -1
View File
@@ -42,12 +42,13 @@ namespace MP.SPEC.Data
DtEnd = this.DtEnd, DtEnd = this.DtEnd,
DtStart = this.DtStart, DtStart = this.DtStart,
HasOdl = this.HasOdl, HasOdl = this.HasOdl,
Header = this.Header,
IdxMacchina = this.IdxMacchina, IdxMacchina = this.IdxMacchina,
IsActive = this.IsActive, IsActive = this.IsActive,
MaxRecord = this.MaxRecord, MaxRecord = this.MaxRecord,
NumRec = this.NumRec, NumRec = this.NumRec,
SearchVal = this.SearchVal, SearchVal = this.SearchVal,
Header = this.Header, ShowKit = this.ShowKit,
TotCount = this.TotCount TotCount = this.TotCount
}; };
return clonedData; return clonedData;
@@ -82,6 +83,9 @@ namespace MP.SPEC.Data
if (IsActive != item.IsActive) if (IsActive != item.IsActive)
return false; return false;
if (ShowKit != item.ShowKit)
return false;
if (MaxRecord != item.MaxRecord) if (MaxRecord != item.MaxRecord)
return false; return false;
@@ -90,6 +94,7 @@ namespace MP.SPEC.Data
if (SearchVal != item.SearchVal) if (SearchVal != item.SearchVal)
return false; return false;
if (Header != item.Header) if (Header != item.Header)
return false; return false;
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MP.SPEC</RootNamespace> <RootNamespace>MP.SPEC</RootNamespace>
<Version>8.16.2606.408</Version> <Version>8.16.2606.1218</Version>
<UserSecretsId>1800a78a-6ff1-40f9-b490-87fb8bfc1394</UserSecretsId> <UserSecretsId>1800a78a-6ff1-40f9-b490-87fb8bfc1394</UserSecretsId>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup> </PropertyGroup>
+2 -5
View File
@@ -1,8 +1,5 @@
@page "/ART" @page "/ART"
@using MP.SPEC.Components
@using MP.SPEC.Data
<div class="card mb-5"> <div class="card mb-5">
<div class="card-header table-primary"> <div class="card-header table-primary">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
@@ -158,7 +155,7 @@
</div> </div>
<div class="card-body"> <div class="card-body">
@if (ListRecords == null || isLoading) @if (isLoading && false)
{ {
<LoadingData></LoadingData> <LoadingData></LoadingData>
} }
@@ -176,7 +173,7 @@
<th> <th>
@if (currRecord != null) @if (currRecord != null)
{ {
<button @onclick="() => resetSel()" class="btn btn-primary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></button> <button @onclick="ResetSel" class="btn btn-primary btn-sm"><i class="bi bi-arrow-counterclockwise"></i></button>
} }
</th> </th>
<th><i class="fa-solid fa-file"></i> Articolo</th> <th><i class="fa-solid fa-file"></i> Articolo</th>
+154 -161
View File
@@ -28,20 +28,88 @@ namespace MP.SPEC.Pages
#endregion Public Methods #endregion Public Methods
#region Protected Properties #region Protected Methods
protected override async Task OnInitializedAsync()
{
numRecord = 10;
await ReloadBaseData();
}
protected override async Task OnParametersSetAsync()
{
await ReloadDataAsync();
}
#endregion Protected Methods
#region Private Fields
private int _currPage = 1;
private int _numRecord = 10;
private string _selAzienda = "*";
private int availRecord = 1000;
private SelectArticoliParams currFilter = new SelectArticoliParams();
private AnagArticoliModel? currRecord = null;
private bool isLoading = false;
private bool isNewArt = false;
private List<AnagGruppiModel>? ListAziende;
private List<AnagArticoliModel>? ListRecords;
private List<ListValuesModel>? ListTipoArt;
private int maxNumRecord = 1000;
private List<AnagArticoliModel>? SearchRecords;
private string selTipoArt = "*";
private int totalCount = 0;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Verifica cablata x add tutto tranne KIT
/// </summary>
private bool CanAdd => !selTipoArt.Equals("KIT");
private int currPage
{
get => _currPage;
set
{
if (_currPage != value)
{
_currPage = value;
UpdateTable();
}
}
}
[Inject] [Inject]
protected IJSRuntime JSRuntime { get; set; } = null!; private IJSRuntime JSRuntime { get; set; } = null!;
[Inject] [Inject]
protected MpDataService MDService { get; set; } = null!; private MpDataService MDService { get; set; } = null!;
[Inject] [Inject]
protected NavigationManager NavManager { get; set; } = null!; private NavigationManager NavManager { get; set; } = null!;
private int numRecord
{
get => _numRecord;
set
{
if (_numRecord != value)
{
_numRecord = value;
UpdateTable();
}
}
}
private string searchCss => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary"; private string searchCss => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary";
protected string SearchVal private string searchVal { get; set; } = "";
private string SearchVal
{ {
get => searchVal; get => searchVal;
set set
@@ -54,17 +122,29 @@ namespace MP.SPEC.Pages
} }
} }
protected int totalCount = 0; private string selAzienda
{
get => _selAzienda;
set
{
if (value != _selAzienda)
{
_selAzienda = value;
}
}
}
#endregion Protected Properties private bool ShowCharts { get; set; } = false;
#region Protected Methods #endregion Private Properties
#region Private Methods
/// <summary> /// <summary>
/// Crea nuovo record e va in editing... /// Crea nuovo record e va in editing...
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected void AddNew() private void AddNew()
{ {
isNewArt = true; isNewArt = true;
currRecord = new AnagArticoliModel() currRecord = new AnagArticoliModel()
@@ -79,11 +159,24 @@ namespace MP.SPEC.Pages
}; };
} }
/// <summary>
/// Verifica cancellabilità record
/// </summary>
/// <param name="selRec"></param>
/// <returns></returns>
private bool ArticoloDelEnabled(AnagArticoliModel currRec)
{
if (currRec.Tipo.Equals("KIT"))
return false;
return MDService.ArticoloDelEnabled(currRec.CodArticolo);
}
/// <summary> /// <summary>
/// Cloning record /// Cloning record
/// </summary> /// </summary>
/// <param name="selRec"></param> /// <param name="selRec"></param>
protected void cloneRecord(AnagArticoliModel selRec) private void cloneRecord(AnagArticoliModel selRec)
{ {
isNewArt = true; isNewArt = true;
// creo record duplicato... // creo record duplicato...
@@ -105,7 +198,7 @@ namespace MP.SPEC.Pages
/// </summary> /// </summary>
/// <param name="selRec"></param> /// <param name="selRec"></param>
/// <returns></returns> /// <returns></returns>
protected async Task deleteRecord(AnagArticoliModel selRec) private async Task deleteRecord(AnagArticoliModel selRec)
{ {
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Eliminazione Articolo: sei sicuro di voler procedere?")) if (!await JSRuntime.InvokeAsync<bool>("confirm", "Eliminazione Articolo: sei sicuro di voler procedere?"))
return; return;
@@ -116,138 +209,16 @@ namespace MP.SPEC.Pages
await Task.Delay(1); await Task.Delay(1);
} }
protected void ForceReload(int newNum) private void ForceReload(int newNum)
{ {
numRecord = newNum; numRecord = newNum;
} }
protected void ForceReloadPage(int newNum) private void ForceReloadPage(int newNum)
{ {
currPage = newNum; currPage = newNum;
} }
protected override async Task OnInitializedAsync()
{
numRecord = 10;
await ReloadBaseData();
}
protected override async Task OnParametersSetAsync()
{
await ReloadDataAsync();
}
protected void ResetData()
{
isNewArt = false;
currRecord = null;
}
protected async Task ResetSearch()
{
SearchVal = "";
await ResetDataAsync();
}
protected async Task resetSel()
{
isNewArt = false;
currRecord = null;
await Task.Delay(1);
}
protected async Task selRecord(AnagArticoliModel selRec)
{
isNewArt = false;
currRecord = selRec;
await Task.Delay(1);
}
protected async Task UpdateAsync(AnagArticoliModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Confermi di voler salvare le modifiche?"))
return;
var done = await MDService.ArticoliUpdateRecord(selRec);
await ResetDataAsync();
}
protected async Task ResetDataAsync()
{
currPage = 1;
currRecord = null;
await ReloadDataAsync();
}
#endregion Protected Methods
#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;
private int maxNumRecord = 1000;
private int availRecord = 1000;
private int totRecord = 0;
private List<AnagArticoliModel>? SearchRecords;
#endregion Private Fields
#region Private Properties
private int _currPage = 1;
private int _numRecord = 10;
private int currPage
{
get => _currPage;
set
{
if (_currPage != value)
{
_currPage = value;
UpdateTable();
}
}
}
private bool isLoading { get; set; } = false;
private int numRecord
{
get => _numRecord;
set
{
if (_numRecord != value)
{
_numRecord = value;
UpdateTable();
}
}
}
private string searchVal { get; set; } = "";
private string selAzienda
{
get => _selAzienda;
set
{
if (value != _selAzienda)
{
_selAzienda = value;
}
}
}
/// <summary>
/// Tipo articolo selezionato
/// </summary>
private string selTipoArt = "*";
private async Task ReloadAziendaAsync() private async Task ReloadAziendaAsync()
{ {
isLoading = true; isLoading = true;
@@ -263,28 +234,9 @@ namespace MP.SPEC.Pages
await ResetDataAsync(); await ResetDataAsync();
} }
private bool ShowCharts { get; set; } = false;
#endregion Private Properties
#region Private Methods
/// <summary>
/// Verifica cancellabilità record
/// </summary>
/// <param name="selRec"></param>
/// <returns></returns>
private bool ArticoloDelEnabled(AnagArticoliModel currRec)
{
if (currRec.Tipo.Equals("KIT"))
return false;
return MDService.ArticoloDelEnabled(currRec.CodArticolo);
}
private async Task ReloadBaseData() private async Task ReloadBaseData()
{ {
isLoading = true;
await MDService.EnsureArtCacheLoadedAsync(true); await MDService.EnsureArtCacheLoadedAsync(true);
selAzienda = await MDService.ConfigTryGetAsync("AZIENDA"); selAzienda = await MDService.ConfigTryGetAsync("AZIENDA");
if (string.IsNullOrEmpty(selAzienda)) if (string.IsNullOrEmpty(selAzienda))
@@ -295,11 +247,6 @@ namespace MP.SPEC.Pages
ListTipoArt = await MDService.AnagTipoArtLvAsync(); ListTipoArt = await MDService.AnagTipoArtLvAsync();
} }
/// <summary>
/// Verifica cablata x add tutto tranne KIT
/// </summary>
private bool CanAdd => !selTipoArt.Equals("KIT");
private async Task ReloadDataAsync() private async Task ReloadDataAsync()
{ {
isLoading = true; isLoading = true;
@@ -309,9 +256,55 @@ namespace MP.SPEC.Pages
UpdateTable(); UpdateTable();
} }
private void ResetData()
{
isNewArt = false;
currRecord = null;
}
private async Task ResetDataAsync()
{
currPage = 1;
currRecord = null;
await MDService.FlushFusionCacheArticoli();
await ReloadDataAsync();
}
private async Task ResetSearch()
{
SearchVal = "";
await ResetDataAsync();
}
private async Task ResetSel()
{
isNewArt = false;
currRecord = null;
await ResetDataAsync();
}
private async Task selRecord(AnagArticoliModel selRec)
{
isNewArt = false;
currRecord = selRec;
await Task.Delay(1);
}
private async Task UpdateAsync(AnagArticoliModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Confermi di voler salvare le modifiche?"))
return;
var done = await MDService.ArticoliUpsertRecord(selRec);
await ResetDataAsync();
}
private void UpdateTable() private void UpdateTable()
{ {
ListRecords = SearchRecords?.Skip(numRecord * (currPage - 1)).Take(numRecord).ToList() ?? new(); ListRecords = SearchRecords?
.Skip(numRecord * (currPage - 1))
.Take(numRecord)
.ToList() ?? new();
isLoading = false; isLoading = false;
} }
+8
View File
@@ -1,4 +1,5 @@
@page "/force-reset" @page "/force-reset"
@page "/ForceReset"
@using MP.AppAuth.Services @using MP.AppAuth.Services
@using MP.SPEC.Data @using MP.SPEC.Data
@@ -47,6 +48,13 @@
nextVal = 10; nextVal = 10;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
await Task.Delay(bDelay); await Task.Delay(bDelay);
// svuoto cache redis...
ConfigModel updRec = new ConfigModel()
{
Chiave = "AZIENDA",
Valore = "*"
};
await MDService.ConfigUpdateAsync(updRec);
title = "UserDataReload..."; title = "UserDataReload...";
currVal = 50; currVal = 50;
nextVal = 80; nextVal = 80;
-2
View File
@@ -68,8 +68,6 @@ else
</div> </div>
} }
</div> </div>
@*<div class="card-footer">
</div>*@
</div> </div>
} }
+23 -28
View File
@@ -1,27 +1,13 @@
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using MP.Data.DbModels; using MP.Data.DbModels;
using MP.Data.Services;
using MP.SPEC.Data; using MP.SPEC.Data;
namespace MP.SPEC.Pages namespace MP.SPEC.Pages
{ {
public partial class Giacenze 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 #region Protected Methods
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -41,34 +27,43 @@ namespace MP.SPEC.Pages
odlExp = await MDService.OdlByKeyAsync(IdxOdl); odlExp = await MDService.OdlByKeyAsync(IdxOdl);
} }
protected void saveBatch(string newBatch)
{
BatchSel = newBatch;
}
#endregion Protected Methods #endregion Protected Methods
#region Private Fields
private List<ODLModel>? elencoOdl;
private string giacenzeConf = "false";
#endregion Private Fields
#region Private Properties #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] [Inject]
private MpDataService MDService { get; set; } = null!; private MpDataService MDService { get; set; } = null!;
[Inject] [Inject]
private NavigationManager NavManager { get; set; } = null!; private NavigationManager NavManager { get; set; } = null!;
private ODLExpModel? odlExp { get; set; } = null!;
private string padCodXdl { get; set; } = "00000"; private string padCodXdl { get; set; } = "00000";
private string giacenzeConf = "false";
[Inject] [Inject]
private ISessionStorageService sessionStorage { get; set; } = null!; 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 #endregion Private Properties
} }
} }
+13 -2
View File
@@ -11,7 +11,18 @@
</div> </div>
</div> </div>
<div class="px-0 align-content-center d-flex justify-content-end"> <div class="px-0 align-content-center d-flex justify-content-end">
@* <small class="fs-5">Edit Massivo Fermi</small> *@ <div class="input-group">
<span class="input-group-text"><i class="fa fa-search"></i></span>
@if (ShowDetail)
{
<input type="text" class="form-control disabled" disabled placeholder="Search Ctr-R" @bind="@SearchVal" @bind:after="ReloadDataAsync" accesskey="R">
}
else
{
<input type="text" class="form-control" placeholder="Search Alt-R" @bind="@SearchVal" @bind:after="ReloadDataAsync" accesskey="R">
}
<button class="btn @btnSearchCss" @onclick="DoReset"><i class="fa fa-ban"></i></button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -24,7 +35,7 @@
{ {
<div class="row"> <div class="row">
<div class="@cssMain"> <div class="@cssMain">
<ListOperatori CurrRecords="@ListOperatori" AllRecords="@ListOperatori" AddEnabled="false" DelEnabled="false" StaChgEnab="true" EC_RecSel="ShowDetail"></ListOperatori> <ListOperatori CurrRecords="@ListOperatori" AllRecords="@ListOperatori" AddEnabled="false" DelEnabled="false" StaChgEnab="true" EC_RecSel="DoSelect"></ListOperatori>
</div> </div>
@if (SelRec != null) @if (SelRec != null)
{ {
+25 -6
View File
@@ -26,28 +26,31 @@ namespace MP.SPEC.Pages
#region Private Fields #region Private Fields
private bool isLoading = false; private bool isLoading = false;
private List<AnagOperatoriModel> ListOperatori = new();
private List<RepartiDTO> ListGruppi = new(); private List<RepartiDTO> ListGruppi = new();
private List<AnagOperatoriModel> ListOperatori = new();
private string SearchVal = "";
private AnagOperatoriModel? SelRec = null; private AnagOperatoriModel? SelRec = null;
#endregion Private Fields #endregion Private Fields
#region Private Properties #region Private Properties
private string btnSearchCss => string.IsNullOrWhiteSpace(SearchVal) ? "btn-secondary" : "btn-primary";
private string cssMain => SelRec == null ? "col-12" : "col-6"; private string cssMain => SelRec == null ? "col-12" : "col-6";
private bool ShowDetail => SelRec != null;
#endregion Private Properties #endregion Private Properties
#region Private Methods #region Private Methods
private async Task ReloadDataAsync() private async Task DoReset()
{ {
isLoading = true; SearchVal = "";
ListOperatori = await MDService.OperatoriGetFiltAsync("*"); await ReloadDataAsync();
isLoading = false;
} }
private async Task ShowDetail(AnagOperatoriModel? newRec) private async Task DoSelect(AnagOperatoriModel? newRec)
{ {
SelRec = newRec; SelRec = newRec;
if (SelRec == null) if (SelRec == null)
@@ -61,6 +64,22 @@ namespace MP.SPEC.Pages
} }
} }
private async Task ReloadDataAsync()
{
isLoading = true;
var rawList = await MDService.OperatoriGetFiltAsync("*");
if (string.IsNullOrEmpty(SearchVal))
{
ListOperatori = rawList;
}
else
{
ListOperatori = rawList
.Where(x => x.Cognome.Contains(SearchVal, StringComparison.InvariantCultureIgnoreCase) || x.Nome.Contains(SearchVal, StringComparison.InvariantCultureIgnoreCase)).ToList();
}
isLoading = false;
}
#endregion Private Methods #endregion Private Methods
} }
} }
+1 -1
View File
@@ -224,7 +224,7 @@ namespace MP.SPEC.Pages
{ {
newParams.CurrPage = 1; newParams.CurrPage = 1;
} }
await InvokeAsync(() => StateHasChanged()); await InvokeAsync(StateHasChanged);
currFilter = newParams; currFilter = newParams;
isLoading = false; isLoading = false;
} }
+34 -10
View File
@@ -12,13 +12,13 @@
<div class="input-group-text"> <div class="input-group-text">
<span class="me-1" title="Elenco PODL disponibili da produrre">Da Produrre</span> <span class="me-1" title="Elenco PODL disponibili da produrre">Da Produrre</span>
<div class="form-check form-check-sm form-switch py-1" title="Modalità display (Disponibili / Lanciati)"> <div class="form-check form-check-sm form-switch py-1" title="Modalità display (Disponibili / Lanciati)">
<input class="form-check-input" type="checkbox" name="setupAlarms" @onclick="() => toggleClosed()"> <input class="form-check-input" type="checkbox" name="setupAlarms" @onclick="() => DoToggleClosed()">
</div> </div>
<span class="" title="Elenco PODL già lanciati/prodotti">Lanciati</span> <span class="" title="Elenco PODL già lanciati/prodotti">Lanciati</span>
</div> </div>
@if (addEnabled) @if (addEnabled)
{ {
<button class="btn btn-success" @onclick="() => reqNewPODL()">Nuovo PODL <i class="bi bi-plus-square"></i></button> <button class="btn btn-success" @onclick="() => ReqNewPODL()">Nuovo PODL <i class="bi bi-plus-square"></i></button>
} }
else else
{ {
@@ -29,6 +29,30 @@
</div> </div>
<div class="d-flex justify-content-end col-6"> <div class="d-flex justify-content-end col-6">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="p-1">
<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 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">
</div>
</div>
</div>
</div>
<div class="p-1">
<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="Attivabile">
Attivi
</div>
<div class="form-check form-check-sm form-switch py-1" title="Mostra Solo Attivi">
<input class="form-check-input" type="checkbox" @bind="@OnlyAtt">
</div>
</div>
</div>
</div>
<div class="p-1"> <div class="p-1">
<div class="input-group me-2" style="min-width: 20rem;"> <div class="input-group me-2" style="min-width: 20rem;">
<span class="input-group-text"><i class="fa fa-search"></i></span> <span class="input-group-text"><i class="fa fa-search"></i></span>
@@ -41,15 +65,15 @@
<div class=" rounded small d-flex justify-content-between" title="Filtri attivi"> <div class=" rounded small d-flex justify-content-between" title="Filtri attivi">
@if (selReparto != "*") @if (selReparto != "*")
{ {
<button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => resetReparto()" title="Rimuovi Filtro Reparto"><i class="fa-solid fa-building"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button> <button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => DoResetReparto()" title="Rimuovi Filtro Reparto"><i class="fa-solid fa-building"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button>
} }
@if (macchina != "*") @if (macchina != "*")
{ {
<button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => resetMacchina()" title="Rimuovi Filtro Impianto"><i class="fa-solid fa-hard-drive"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button> <button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => DoResetMacchina()" title="Rimuovi Filtro Impianto"><i class="fa-solid fa-hard-drive"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button>
} }
@if (StatoSel != "*") @if (StatoSel != "*")
{ {
<button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => resetFase()" title="Rimuovi Filtro Parametro"><i class="fa-solid fa-sliders"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button> <button class="btn btn-outline-primary btn-sm mx-2" @onclick="() => DoResetFase()" title="Rimuovi Filtro Parametro"><i class="fa-solid fa-sliders"></i> &nbsp <i class="fa-solid fa-xmark text-warning"></i></button>
} }
</div> </div>
} }
@@ -251,7 +275,7 @@
{ {
<div class="col-3"> <div class="col-3">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button class="btn btn-warning" @onclick="() => cancel()">Annulla <i class="bi bi-x-circle"></i></button> <button class="btn btn-warning" @onclick="() => DoCancel()">Annulla <i class="bi bi-x-circle"></i></button>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3">
@@ -259,7 +283,7 @@
@* @if (currRecord.CodArticolo != "" && currRecord.CodFase != "" && selReparto != "*" && currRecord.IdxMacchina != "") *@ @* @if (currRecord.CodArticolo != "" && currRecord.CodFase != "" && selReparto != "*" && currRecord.IdxMacchina != "") *@
@if (canSaveEdit) @if (canSaveEdit)
{ {
<button class="btn btn-success" @onclick="() => update(currRecord)">Salva <i class="bi bi-save"></i></button> <button class="btn btn-success" @onclick="() => DoUpdate(currRecord)">Salva <i class="bi bi-save"></i></button>
} }
</div> </div>
</div> </div>
@@ -268,14 +292,14 @@
{ {
<div class="col-3"> <div class="col-3">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button class="btn btn-warning" @onclick="() => cancel()">Chiudi <i class="bi bi-x-circle"></i></button> <button class="btn btn-warning" @onclick="() => DoCancel()">Chiudi <i class="bi bi-x-circle"></i></button>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
@if (enableForceSync) @if (enableForceSync)
{ {
<button @onclick="() => forceSyncDb()" class="btn btn-success">Forza sync &rarr; macchina <i class="bi bi-fast-forward-circle"></i></button> <button @onclick="() => DoForceSyncDb()" class="btn btn-success">Forza sync &rarr; macchina <i class="bi bi-fast-forward-circle"></i></button>
} }
else else
{ {
@@ -301,7 +325,7 @@
} }
else else
{ {
<ListPODL PagerResetReq="pgResetReq" RecordEdit="@editRecord" RecordSel="@selRecord" updateRecordCount="UpdateTotCount" actFilter="@currFilter" padCodXdl="@padCodXdl"></ListPODL> <ListPODL PagerResetReq="DoResetReqPager" RecordEdit="@DoEditRecord" RecordSel="@DoSelRecord" updateRecordCount="UpdateTotCount" actFilter="@currFilter" padCodXdl="@padCodXdl"></ListPODL>
} }
</div> </div>
<div class="card-footer py-1"> <div class="card-footer py-1">
+236 -234
View File
@@ -1,6 +1,7 @@
#if false #if false
using Blazored.LocalStorage; using Blazored.LocalStorage;
#endif #endif
using EgwCoreLib.Razor; using EgwCoreLib.Razor;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@@ -14,117 +15,11 @@ namespace MP.SPEC.Pages
{ {
public partial class PODL public partial class PODL
{ {
#region Protected Fields
protected bool enableForceSync = true;
protected bool enableStartPODL = true;
protected bool enableStopODL = true;
protected DataPager? pagerODL = null!;
protected bool reqNew = false;
#endregion Protected Fields
#region Protected Properties
protected string addMessage
{
get => addEnabled ? "" : "Manca Selezione Impianto / Fase";
}
protected bool canSaveEdit
{
get
{
bool answ = false;
if (currRecord != null)
{
// controllo le condizioni di selezione fase
bool okSelReparto = (useFasi4KeyRich == "FASE" && selReparto != "*") || useFasi4KeyRich != "FASE";
// controllo condizione record valido
bool okCurrRecord = currRecord.CodArticolo != "" && currRecord.CodFase != "" && currRecord.IdxMacchina != "";
answ = okCurrRecord && okSelReparto;
}
return answ;
}
}
protected string header
{
get => currFilter.Header;
set => currFilter.Header = value;
}
[Inject]
protected IJSRuntime JSRuntime { get; set; } = null!;
[Inject]
protected ILocalStorageService localStorage { get; set; } = null!;
[Inject]
protected MpDataService MDService { get; set; } = null!;
[Inject]
protected IOApiService MpIoApiCall { get; set; } = null!;
[Inject]
protected NavigationManager NavManager { get; set; } = null!;
protected string useFasi4KeyRich { get; set; } = "";
#endregion Protected Properties
#region Protected Methods #region Protected Methods
protected async Task cancel()
{
currRecord = null;
await ReloadDataAsync();
await Task.Delay(1);
}
protected async Task editRecord(PODLExpModel selRec)
{
canEdit = true;
// preseleziono ricerca articolo
if (selRec != null)
{
artSearch = selRec.CodArticolo.Length > nArtSearch ? selRec.CodArticolo.Substring(0, nArtSearch) : selRec.CodArticolo;
}
currRecord = selRec;
await Task.Delay(1);
}
protected async Task forceSyncDb()
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler (re)inviare i dati (Articoli, PODL) all'impianto?"))
return;
var clonedRec = MP.Data.Utils.POdlExt.convertToPOdl(currRecord);
await callSyncDb(clonedRec);
currRecord = null;
NavManager.NavigateTo(NavManager.Uri, true);
}
protected async Task getReparto()
{
string keyStor = "reparto";
string localReparto = await localStorage.GetItemAsync<string>(keyStor, "") ?? "";
if (!string.IsNullOrEmpty(localReparto))
{
selReparto = localReparto;
}
else
{
selReparto = "*";
await localStorage.SetItemAsync(keyStor, selReparto);
}
}
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await getReparto(); await GetReparto();
ListAziende = await MDService.ElencoAziendeAsync(); ListAziende = await MDService.ElencoAziendeAsync();
var allGruppiData = await MDService.ElencoGruppiFaseAsync(); var allGruppiData = await MDService.ElencoGruppiFaseAsync();
if (allGruppiData != null) if (allGruppiData != null)
@@ -164,132 +59,6 @@ namespace MP.SPEC.Pages
await ReloadDataAsync(); await ReloadDataAsync();
} }
protected async Task pgResetReq(bool doReset)
{
if (doReset)
{
await Task.Delay(1);
if (pagerODL != null)
{
pagerODL.resetCurrPage();
}
}
}
/// <summary>
/// Crea nuovo record e va in editing...
/// </summary>
/// <returns></returns>
protected async Task reqNewPODL()
{
canEdit = true;
header = "Nuovo PODL";
artSearch = "";
string codExt = $"{currFase}";
string codGruppo = "";
if (ListGruppiFase != null && ListGruppiFase.Count > 0)
{
var firstFase = ListGruppiFase.FirstOrDefault(x => x.CodGruppo.StartsWith(currAzienda));
if (firstFase != null)
{
codGruppo = firstFase.CodGruppo;
}
}
string codMacc = "";
if (ListMacchine != null && ListMacchine.Count > 0)
{
var firstMacc = ListMacchine.FirstOrDefault(x => x.Nome.Contains(currAzienda));
if (firstMacc != null)
{
codMacc = firstMacc.IdxMacchina;
}
}
currRecord = new PODLExpModel()
{
CodArticolo = "",//currArticolo,
KeyBCode = codExt,
KeyRichiesta = codExt,
CodGruppo = codGruppo,
IdxMacchina = codMacc,
NumPezzi = 1,
DueDate = DateTime.Now.AddDays(30)
};
await Task.Delay(1);
}
protected void resetFase()
{
StatoSel = "*";
}
protected void resetMacchina()
{
macchina = "*";
}
protected async Task resetReparto()
{
string keyStor = "reparto";
selReparto = "*";
await localStorage.SetItemAsync(keyStor, selReparto);
}
protected async Task selRecord(PODLExpModel selRec)
{
canEdit = false;
currRecord = selRec;
await Task.Delay(1);
}
protected void SetNumRec(int newNum)
{
currPage = 1;
numRecord = newNum;
}
protected void SetPage(int newNum)
{
currPage = newNum;
}
protected async Task toggleClosed()
{
hasOdl = !hasOdl;
await Task.Delay(1);
}
protected async Task update(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Confermi di voler salvare le modifiche?"))
return;
await Task.Delay(1);
var clonedRec = MP.Data.Utils.POdlExt.convertToPOdl(selRec);
// se x qualche motivo mancasse codGruppo --> sistemo!
if (string.IsNullOrEmpty(clonedRec.CodGruppo))
{
clonedRec.CodGruppo = currGruppoSel.CodGruppo;
Log.Error($"CodGruppo mancante: messo valore selezionato: {currGruppoSel.CodGruppo}");
}
// se selezionato sovrascrivo...
if (useFasi4KeyRich == "FASE")
{
clonedRec.CodGruppo = currGruppoSel.CodGruppo;
}
var done = await MDService.POdlUpdateRecord(clonedRec);
// se attivabile chiamo sync
if (clonedRec.Attivabile)
{
await callSyncDb(clonedRec);
}
currRecord = null;
NavManager.NavigateTo(NavManager.Uri, true);
}
protected void UpdateTotCount(int newTotCount)
{
totalCount = newTotCount;
}
#endregion Protected Methods #endregion Protected Methods
#region Private Fields #region Private Fields
@@ -298,12 +67,21 @@ namespace MP.SPEC.Pages
private PODLExpModel? _currRecord = null; private PODLExpModel? _currRecord = null;
private AnagGruppiModel currGruppoSel = new AnagGruppiModel(); private AnagGruppiModel currGruppoSel = new AnagGruppiModel();
private PODLExpModel currRecordControlli = new PODLExpModel(); private PODLExpModel currRecordControlli = new PODLExpModel();
private bool enableForceSync = true;
private bool enableStartPODL = true;
private bool enableStopODL = true;
private List<AnagArticoliModel>? ListArticoli; private List<AnagArticoliModel>? ListArticoli;
private List<AnagGruppiModel>? ListAziende; private List<AnagGruppiModel>? ListAziende;
private List<AnagGruppiModel>? ListGruppiFase; private List<AnagGruppiModel>? ListGruppiFase;
private List<MacchineModel>? ListMacchine; private List<MacchineModel>? ListMacchine;
private List<ListValuesModel>? ListStati; private List<ListValuesModel>? ListStati;
private int nArtSearch = 5; private int nArtSearch = 5;
private DataPager? pagerODL = null!;
#if false
private bool reqNew = false;
#endif
private string useFasi4KeyRich = "";
#endregion Private Fields #endregion Private Fields
@@ -323,6 +101,11 @@ namespace MP.SPEC.Pages
} }
} }
private string addMessage
{
get => addEnabled ? "" : "Manca Selezione Impianto / Fase";
}
private string artSearch private string artSearch
{ {
get => _artSearch; get => _artSearch;
@@ -346,6 +129,23 @@ namespace MP.SPEC.Pages
private bool canEdit { get; set; } = false; private bool canEdit { get; set; } = false;
private bool canSaveEdit
{
get
{
bool answ = false;
if (currRecord != null)
{
// controllo le condizioni di selezione fase
bool okSelReparto = (useFasi4KeyRich == "FASE" && selReparto != "*") || useFasi4KeyRich != "FASE";
// controllo condizione record valido
bool okCurrRecord = currRecord.CodArticolo != "" && currRecord.CodFase != "" && currRecord.IdxMacchina != "";
answ = okCurrRecord && okSelReparto;
}
return answ;
}
}
private string currAzienda private string currAzienda
{ {
get => _currAzienda; get => _currAzienda;
@@ -407,20 +207,47 @@ namespace MP.SPEC.Pages
} }
} }
private string header
{
get => currFilter.Header;
set => currFilter.Header = value;
}
private bool isLoading { get; set; } = false; private bool isLoading { get; set; } = false;
[Inject]
private IJSRuntime JSRuntime { get; set; } = null!;
[Inject]
private ILocalStorageService localStorage { get; set; } = null!;
private string macchina private string macchina
{ {
get => currFilter.IdxMacchina; get => currFilter.IdxMacchina;
set => currFilter.IdxMacchina = value; set => currFilter.IdxMacchina = value;
} }
[Inject]
private MpDataService MDService { get; set; } = null!;
[Inject]
private IOApiService MpIoApiCall { get; set; } = null!;
[Inject]
private NavigationManager NavManager { get; set; } = null!;
private int numRecord private int numRecord
{ {
get => currFilter.NumRec; get => currFilter.NumRec;
set => currFilter.NumRec = value; set => currFilter.NumRec = value;
} }
private bool OnlyAtt
{
get => currFilter.IsActive;
set => currFilter.IsActive = value;
}
private string padCodXdl { get; set; } = "00000"; private string padCodXdl { get; set; } = "00000";
private DateTime selDtEnd private DateTime selDtEnd
@@ -455,6 +282,14 @@ namespace MP.SPEC.Pages
set => currFilter.CodReparto = value; set => currFilter.CodReparto = value;
} }
private bool ShowKit
{
get => currFilter.ShowKit;
set => currFilter.ShowKit = value;
}
private string sSearchCss => string.IsNullOrEmpty(currFilter.SearchVal) ? "btn-secondary" : "btn-primary";
private string StatoSel private string StatoSel
{ {
get => currFilter.CodFase; get => currFilter.CodFase;
@@ -491,6 +326,118 @@ namespace MP.SPEC.Pages
} }
} }
private async Task DoCancel()
{
currRecord = null;
await ReloadDataAsync();
await Task.Delay(1);
}
private async Task DoEditRecord(PODLExpModel selRec)
{
canEdit = true;
// preseleziono ricerca articolo
if (selRec != null)
{
artSearch = selRec.CodArticolo.Length > nArtSearch ? selRec.CodArticolo.Substring(0, nArtSearch) : selRec.CodArticolo;
}
currRecord = selRec;
await Task.Delay(1);
}
private async Task DoForceSyncDb()
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler (re)inviare i dati (Articoli, PODL) all'impianto?"))
return;
var clonedRec = MP.Data.Utils.POdlExt.convertToPOdl(currRecord);
await callSyncDb(clonedRec);
currRecord = null;
NavManager.NavigateTo(NavManager.Uri, true);
}
private void DoResetFase()
{
StatoSel = "*";
}
private void DoResetMacchina()
{
macchina = "*";
}
private async Task DoResetReparto()
{
string keyStor = "reparto";
selReparto = "*";
await localStorage.SetItemAsync(keyStor, selReparto);
}
private void DoResetReqPager(bool doReset)
{
if (doReset)
{
if (pagerODL != null)
{
pagerODL.resetCurrPage();
}
}
}
private async Task DoSelRecord(PODLExpModel selRec)
{
canEdit = false;
currRecord = selRec;
await Task.Delay(1);
}
private void DoToggleClosed()
{
hasOdl = !hasOdl;
}
private async Task DoUpdate(PODLExpModel selRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", "Confermi di voler salvare le modifiche?"))
return;
await Task.Delay(1);
var clonedRec = MP.Data.Utils.POdlExt.convertToPOdl(selRec);
// se x qualche motivo mancasse codGruppo --> sistemo!
if (string.IsNullOrEmpty(clonedRec.CodGruppo))
{
clonedRec.CodGruppo = currGruppoSel.CodGruppo;
Log.Error($"CodGruppo mancante: messo valore selezionato: {currGruppoSel.CodGruppo}");
}
// se selezionato sovrascrivo...
if (useFasi4KeyRich == "FASE")
{
clonedRec.CodGruppo = currGruppoSel.CodGruppo;
}
var done = await MDService.POdlUpdateRecord(clonedRec);
// se attivabile chiamo sync
if (clonedRec.Attivabile)
{
await callSyncDb(clonedRec);
}
currRecord = null;
NavManager.NavigateTo(NavManager.Uri, true);
}
private async Task GetReparto()
{
string keyStor = "reparto";
string localReparto = await localStorage.GetItemAsync<string>(keyStor, "") ?? "";
if (!string.IsNullOrEmpty(localReparto))
{
selReparto = localReparto;
}
else
{
selReparto = "*";
await localStorage.SetItemAsync(keyStor, selReparto);
}
}
private async Task ReloadDataAsync() private async Task ReloadDataAsync()
{ {
isLoading = true; isLoading = true;
@@ -507,7 +454,46 @@ namespace MP.SPEC.Pages
isLoading = false; isLoading = false;
} }
private string sSearchCss => string.IsNullOrEmpty(currFilter.SearchVal) ? "btn-secondary" : "btn-primary"; /// <summary>
/// Crea nuovo record e va in editing...
/// </summary>
/// <returns></returns>
private async Task ReqNewPODL()
{
canEdit = true;
header = "Nuovo PODL";
artSearch = "";
string codExt = $"{currFase}";
string codGruppo = "";
if (ListGruppiFase != null && ListGruppiFase.Count > 0)
{
var firstFase = ListGruppiFase.FirstOrDefault(x => x.CodGruppo.StartsWith(currAzienda));
if (firstFase != null)
{
codGruppo = firstFase.CodGruppo;
}
}
string codMacc = "";
if (ListMacchine != null && ListMacchine.Count > 0)
{
var firstMacc = ListMacchine.FirstOrDefault(x => x.Nome.Contains(currAzienda));
if (firstMacc != null)
{
codMacc = firstMacc.IdxMacchina;
}
}
currRecord = new PODLExpModel()
{
CodArticolo = "",//currArticolo,
KeyBCode = codExt,
KeyRichiesta = codExt,
CodGruppo = codGruppo,
IdxMacchina = codMacc,
NumPezzi = 1,
DueDate = DateTime.Now.AddDays(30)
};
await Task.Delay(1);
}
private async Task ResetSearch() private async Task ResetSearch()
{ {
@@ -515,6 +501,17 @@ namespace MP.SPEC.Pages
await ReloadDataAsync(); await ReloadDataAsync();
} }
private void SetNumRec(int newNum)
{
currPage = 1;
numRecord = newNum;
}
private void SetPage(int newNum)
{
currPage = newNum;
}
private async Task UpdateFilter(SelectXdlParams newParams) private async Task UpdateFilter(SelectXdlParams newParams)
{ {
isLoading = true; isLoading = true;
@@ -529,6 +526,11 @@ namespace MP.SPEC.Pages
isLoading = false; isLoading = false;
} }
private void UpdateTotCount(int newTotCount)
{
totalCount = newTotCount;
}
#endregion Private Methods #endregion Private Methods
} }
} }
+1 -1
View File
@@ -45,7 +45,7 @@
<MP.SPEC.Components.Reparti.ListMacchine CodGruppoCurr="@CodGruppo" AllRecords="ListMacchineAll" CurrRecords="ListMacchine" EC_RecChange="ForceReload"></MP.SPEC.Components.Reparti.ListMacchine> <MP.SPEC.Components.Reparti.ListMacchine CodGruppoCurr="@CodGruppo" AllRecords="ListMacchineAll" CurrRecords="ListMacchine" EC_RecChange="ForceReload"></MP.SPEC.Components.Reparti.ListMacchine>
</div> </div>
<div class="col-4"> <div class="col-4">
<MP.SPEC.Components.Reparti.ListOperatori CodGruppoCurr="@CodGruppo" AllRecords="ListOperatoriAll" CurrRecords="ListOperatori" EC_RecChange="ForceReload"></MP.SPEC.Components.Reparti.ListOperatori> <MP.SPEC.Components.Reparti.ListOperatori CodGruppoCurr="@CodGruppo" AllRecords="ListOperatoriAll" CurrRecords="ListOperatori" EC_RecChange="ForceReload" SelEnabled="false"></MP.SPEC.Components.Reparti.ListOperatori>
</div> </div>
} }
</div> </div>
+3 -7
View File
@@ -57,10 +57,7 @@ namespace MP.SPEC.Pages
#region Private Properties #region Private Properties
private string btnSearchCss private string btnSearchCss => string.IsNullOrWhiteSpace(SearchVal) ? "btn-secondary" : "btn-primary";
{
get => string.IsNullOrWhiteSpace(SearchVal) ? "btn-secondary" : "btn-primary";
}
private string CssMain => ShowDetail ? "col-4" : "col-12"; private string CssMain => ShowDetail ? "col-4" : "col-12";
@@ -89,11 +86,10 @@ namespace MP.SPEC.Pages
private async Task ReloadDataAsync() private async Task ReloadDataAsync()
{ {
isLoading = true; isLoading = true;
//ListMacchine?.Clear(); if (string.IsNullOrEmpty(CodGruppo))
if (string.IsNullOrEmpty(CodGruppo) || true)
{ {
ListReparti?.Clear(); ListReparti?.Clear();
var rawList = await MDService.ElencoRepartiDtoAsync(); var rawList = await MDService.ElencoRepartiDtoAsync(true);
if (string.IsNullOrEmpty(SearchVal)) if (string.IsNullOrEmpty(SearchVal))
{ {
ListReparti = rawList; ListReparti = rawList;
-15
View File
@@ -192,21 +192,6 @@ builder.Services.TryAddScoped<MpDataService>();
builder.Services.TryAddSingleton<IOApiService>(); builder.Services.TryAddSingleton<IOApiService>();
builder.Services.TryAddScoped<MsgServiceSpec>(); 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(); builder.Services.AddHttpClient();
logger.Info("Aggiunti services"); logger.Info("Aggiunti services");
+89
View File
@@ -0,0 +1,89 @@
# MP.SPEC
MAPO SPEC: WebApp / Sito in versione dotNetCore 8 per la gestione del MES MAPO in particolare per le gestioni SPECiali (ina ttesa di finire di migrare SITE e ADM).
Comprende funzionalità amministrative avanzate e funzionalità standard.
## Sezioni Principali
Sono gestiti
* Articoli
* Operatori
* Assegnazione Operatori/Macchine a reparti
* PODL (Promesse ODL) prima della produzione
* ODL (Ordini di lavoro) legate ad effettive attività di produzione
* Gestione speciale dei KIT
* Gestione Dossier (es caso Baglietto)
* Gestione Ricette (tramite dossier)
* Gestione Parametri macchina
* Gestione giacenze magazzino (ove gestite)
Ci sono anche alcune pagine speciali di admin (ad esempio FluxLogStatus, usata per deduplicare i dati di FluxLog)
## Architettura
### Livello Dati (MP.Data)
Il layer dati centralizzato in `MP.Data` fornisce:
- **8 Repository**: Anag, Production, Dossier, FluxLog, System, MpVoc, MpMon, MpLand
- **Cache FusionCache** (Memory + Redis + DB) con invalidazione per tag
- **DI Registrations** attraverso `DataServiceCollectionExtensions` (`AddSpecDataLayer`, `AddLandDataLayer`, etc.)
- **MpDataService** come servizio singleton centrale per accesso a DB, Redis, MongoDB
### Livello Applicazione (MP.SPEC)
- **MpDataService** (Scoped, singleton in Program.cs): service wrapper sul DAL con caching FusionCache
- **Componenti Blazor Server**: layout interattivo server-side con `AddInteractiveServerComponents()`
- **API Controllers**: RecipeController, RecipeArchiveController per operazioni ricette
- **Autenticazione**: Windows Authentication (Negotiate) con Autorizzazione Blazor Server
### Livello Infrastruttura
- **SQL Server**: 4 DbContext (MoonProContext, MoonPro_VocContext, MoonPro_FluxContext, MoonPro_STATSContext)
- **MongoDB**: storage ricette
- **Redis**: caching distribuito (FusionCache) + backplane
- **OpenTelemetry**: tracing su Uptrace (abilitabile via conf)
- **MessagePipe**: broadcasting messaggi real-time
## Refactoring Completati (Giugno 2026)
### Repository Pattern - Decomposizione MpSpecController
Il grande `MpSpecController/MpSpecRepository.cs` è stato scomposto in 8 repository specialistici:
| # | Repository | Interfaccia | Metodi | DbContext |
|---|---|---|---|---|
| 1 | **Anag** | `IAnagRepository` | 26 | `MoonProContext` |
| 2 | **Production** | `IProductionRepository` | 32 | `MoonProContext` |
| 3 | **Dossier** | `IDossierRepository` | 6 | `MoonPro_FluxContext` |
| 4 | **FluxLog** | `IFluxLogRepository` | 3 | `MoonPro_FluxContext` |
| 5 | **System** | `ISystemRepository` | 7 | `MoonProContext` + `MoonProAdminContext` |
| 6 | **MpVoc** | `IMpVocRepository` | 3 | `MoonPro_VocContext` |
| 7 | **MpMon** | `IMpMonRepository` | 4 | `MoonProContext` |
| 8 | **MpLand** | `IMpLandRepository` | 6 | `MoonProContext` |
Tutti i reference a `dbController.XXX()` nei servizi sono stati rimossi. I metodi originali rimangono nel file di repository come fallback documentato.
### Migrazione FusionCache
Tutti i metodi di lettura in `MpDataService.cs` sono stati migrati al pattern `GetOrFetchAsync<T>()`:
- **L1 MemoryCache**: 1/3 della scadenza totale
- **L2 Redis (Distributed)**: TTL configurabile
- **L3 Database**: fetch diretto dal DbContext
- **Invalidazione**: per tag (es. `Utils.redisArtList`, `Utils.redisOdlByKey`)
- **48+ metodi** migrati o confermati corretti
### Fix DI e Static State (MP.AppAuth)
Risolto il null reference error originario (`Cannot provide a value for property 'AAService' on type 'CmpTop'`):
- Rimpiazzati tutti i `static IConfiguration _configuration` con `readonly` istanza nei controllers
- `AppAuthService` ora riceve i controllers via constructor DI invece di crearli con `new()`
- Registrazioni DI centralizzate in `DataServiceCollectionExtensions.cs`
- Tutti i controllers e services mp.appauth registrati come Scoped
### Build
Tutte le 10 soluzioni compilano con successo (0 errori):
MP.SPEC, MP.Data, MP.Land, MP.MON, MP.TAB3, MP.Stats, MP.INVE, MP.IOC, MP.RIOC, MP.Prog, IobConf
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
<body> <body>
<i>Modulo MAPOSPEC </i> <i>Modulo MAPOSPEC </i>
<h4>Versione: 8.16.2606.408</h4> <h4>Versione: 8.16.2606.1218</h4>
<br /> Note di rilascio: <br /> Note di rilascio:
<ul> <ul>
<li> <li>

Some files were not shown because too many files have changed in this diff Show More