diff --git a/MP.Data/Controllers/MpSpecController.cs b/MP.Data/Controllers/MpSpecController.cs index a0b20991..d6819e96 100644 --- a/MP.Data/Controllers/MpSpecController.cs +++ b/MP.Data/Controllers/MpSpecController.cs @@ -1264,26 +1264,21 @@ namespace MP.Data.Controllers /// Elimina record /// /// - public bool IstKitDelete(IstanzeKitModel rec2del) + public async Task IstKitDeleteAsync(IstanzeKitModel rec2del) { - bool fatto = false; - using (var dbCtx = new MoonProContext(options)) - { - var actRec = dbCtx + using var dbCtx = new MoonProContext(options); + var actRec = await dbCtx .DbSetInstKit .Where(x => x.KeyKit == rec2del.KeyKit && x.KeyExtOrd == rec2del.KeyExtOrd) - .FirstOrDefault(); - // se ci fosse aggiorno... - if (actRec != null) - { - dbCtx - .DbSetInstKit - .Remove(actRec); - } - var res = dbCtx.SaveChanges(); - fatto = res != 0; + .FirstOrDefaultAsync(); + // se ci fosse aggiorno... + if (actRec != null) + { + dbCtx + .DbSetInstKit + .Remove(actRec); } - return fatto; + return await dbCtx.SaveChangesAsync() > 0; } /// @@ -1324,35 +1319,30 @@ namespace MP.Data.Controllers /// Esegue upsert record /// /// - public bool IstKitUpsert(IstanzeKitModel editRec) + public async Task IstKitUpsertAsync(IstanzeKitModel editRec) { - bool fatto = false; - using (var dbCtx = new MoonProContext(options)) - { - var actRec = dbCtx + using var dbCtx = new MoonProContext(options); + var actRec = await dbCtx .DbSetInstKit .Where(x => x.KeyKit == editRec.KeyKit && x.KeyExtOrd == editRec.KeyExtOrd) - .FirstOrDefault(); + .FirstOrDefaultAsync(); - // se ci fosse aggiorno... - if (actRec == null) - { - dbCtx + // se ci fosse aggiorno... + if (actRec == null) + { + await dbCtx .DbSetInstKit - .Add(editRec); - } - else - { - actRec.CodArtParent = editRec.CodArtParent; - actRec.CodArtChild = editRec.CodArtChild; - actRec.QtyART = editRec.QtyART; - actRec.QtyKIT = editRec.QtyKIT; - dbCtx.Entry(actRec).State = EntityState.Modified; - } - var res = dbCtx.SaveChanges(); - fatto = res != 0; + .AddAsync(editRec); } - return fatto; + else + { + actRec.CodArtParent = editRec.CodArtParent; + actRec.CodArtChild = editRec.CodArtChild; + actRec.QtyART = editRec.QtyART; + actRec.QtyKIT = editRec.QtyKIT; + dbCtx.Entry(actRec).State = EntityState.Modified; + } + return await dbCtx.SaveChangesAsync() > 0; } /// @@ -1468,22 +1458,17 @@ namespace MP.Data.Controllers /// /// True = aperti (=senza ODL) /// - public List ListPODL_ByCodArt(string CodArticolo, bool OnlyAvail) + public async Task> ListPODL_ByCodArtAsync(string CodArticolo, bool OnlyAvail) { - List dbResult = new List(); - using (var dbCtx = new MoonProContext(options)) - { - var pCodArticolo = new SqlParameter("@CodArticolo", CodArticolo); - var pOnlyAvail = new SqlParameter("@onlyAvail", OnlyAvail); + using var dbCtx = new MoonProContext(options); + var pCodArticolo = new SqlParameter("@CodArticolo", CodArticolo); + var pOnlyAvail = new SqlParameter("@onlyAvail", OnlyAvail); - dbResult = dbCtx + return await dbCtx .DbSetPODLExp .FromSqlRaw("EXEC stp_PODL_getByCodArt @CodArticolo, @onlyAvail", pCodArticolo, pOnlyAvail) .AsNoTracking() - .ToList(); - } - - return dbResult; + .ToListAsync(); } /// @@ -1491,20 +1476,16 @@ namespace MP.Data.Controllers /// /// IDX PODL parent /// - public List ListPODL_ByKitParent(int IdxPodlParent) + public async Task> ListPODL_ByKitParentAsync(int IdxPodlParent) { - List dbResult = new List(); - using (var dbCtx = new MoonProContext(options)) - { - var pIdxPodlParent = new SqlParameter("@IdxPodlParent", IdxPodlParent); + using var dbCtx = new MoonProContext(options); + var pIdxPodlParent = new SqlParameter("@IdxPodlParent", IdxPodlParent); - dbResult = dbCtx + return await dbCtx .DbSetPODLExp .FromSqlRaw("EXEC stp_PODL_getByParentKitIdx @IdxPodlParent", pIdxPodlParent) .AsNoTracking() - .ToList(); - } - return dbResult; + .ToListAsync(); } /// @@ -2369,19 +2350,15 @@ namespace MP.Data.Controllers /// Effettua il task di eliminazione PODL KIT + istanze + riattivazione PODL originali disattivate tramite stored /// /// IdxPODL parent - public bool PodlIstKitDelete(int IdxPODL) + public async Task PodlIstKitDeleteAsync(int IdxPODL) { - bool answ = false; - using (var dbCtx = new MoonProContext(options)) - { - var pIdxPODL = new SqlParameter("@IdxPODL", IdxPODL); + using var dbCtx = new MoonProContext(options); + var pIdxPODL = new SqlParameter("@IdxPODL", IdxPODL); - var dbResult = dbCtx - .Database - .ExecuteSqlRaw("EXEC dbo.stp_PodlIstKit_delete @IdxPODL", pIdxPODL); - answ = dbResult != 0; - } - return answ; + var dbResult = await dbCtx + .Database + .ExecuteSqlRawAsync("EXEC dbo.stp_PodlIstKit_delete @IdxPODL", pIdxPODL); + return dbResult != 0; } /// diff --git a/MP.SPEC/Components/ListPODL.razor.cs b/MP.SPEC/Components/ListPODL.razor.cs index 0ba0d75e..fbcb863b 100644 --- a/MP.SPEC/Components/ListPODL.razor.cs +++ b/MP.SPEC/Components/ListPODL.razor.cs @@ -156,7 +156,7 @@ namespace MP.SPEC.Components if (recSel != null) { ListKitTemplate = await MDService.TemplateKitFiltAsync(recSel.CodArticolo, ""); - ListPOdlKit = MDService.POdlListByKitParent(recSel.IdxPromessa); + ListPOdlKit = await MDService.POdlListByKitParentAsync(recSel.IdxPromessa); } else { diff --git a/MP.SPEC/Components/ProdKit/GestKitPodl.razor.cs b/MP.SPEC/Components/ProdKit/GestKitPodl.razor.cs index af54bc10..e15fd13e 100644 --- a/MP.SPEC/Components/ProdKit/GestKitPodl.razor.cs +++ b/MP.SPEC/Components/ProdKit/GestKitPodl.razor.cs @@ -101,8 +101,7 @@ namespace MP.SPEC.Components.ProdKit if (!await JSRuntime.InvokeAsync("confirm", "Eliminazione PODL + Istanze KIT: sei sicuro di voler procedere?")) return; - // da provare... - var done = MDService.PodlIstKitDelete(selRec.IdxPromessa); + var done = await MDService.PodlIstKitDeleteAsync(selRec.IdxPromessa); ReloadData(); await EC_ListUpdated.InvokeAsync(true); } @@ -301,7 +300,7 @@ namespace MP.SPEC.Components.ProdKit { isLoading = true; // reset preliminare... - ListRecordsPODL = new List(); + ListRecordsPODL?.Clear(); var filtRecordsPODL = PodlRecords .Where(x => !string.IsNullOrEmpty(x.KeyRichiesta) && x.KeyRichiesta.StartsWith("KIT")) diff --git a/MP.SPEC/Components/ProdKit/KitPodlMan.razor.cs b/MP.SPEC/Components/ProdKit/KitPodlMan.razor.cs index 5f8d0254..0bb9c05f 100644 --- a/MP.SPEC/Components/ProdKit/KitPodlMan.razor.cs +++ b/MP.SPEC/Components/ProdKit/KitPodlMan.razor.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; -using MP.Data; using MP.Data.DbModels; using MP.SPEC.Data; using MP.SPEC.Services; @@ -12,6 +11,9 @@ namespace MP.SPEC.Components.ProdKit { #region Public Properties + [Parameter] + public SelectXdlParams ActFilter { get; set; } = new SelectXdlParams(); + [Parameter] public EventCallback EC_RecordSel { get; set; } @@ -22,10 +24,11 @@ namespace MP.SPEC.Components.ProdKit public EventCallback PagerResetReq { get; set; } [Parameter] - public EventCallback UpdateRecordCount { get; set; } + public List AllRecords { get; set; } = null!; + private List SelRecords = new(); [Parameter] - public SelectXdlParams ActFilter { get; set; } = new SelectXdlParams(); + public EventCallback UpdateRecordCount { get; set; } #endregion Public Properties @@ -49,7 +52,6 @@ namespace MP.SPEC.Components.ProdKit public void Dispose() { currRecord = null; - SearchRecords = null; ListRecords = null; GC.Collect(); } @@ -88,6 +90,11 @@ namespace MP.SPEC.Components.ProdKit get => string.IsNullOrEmpty(SearchVal) ? "btn-secondary" : "btn-primary"; } + protected string sSearchtCss + { + get => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary"; + } + #endregion Protected Properties #region Protected Methods @@ -110,7 +117,7 @@ namespace MP.SPEC.Components.ProdKit if (!lastFilter.Equals(ActFilter) || true) { lastFilter = ActFilter.clone(); - await ReloadData(); + await ReloadDataAsync(); } } @@ -124,15 +131,17 @@ namespace MP.SPEC.Components.ProdKit }); } - protected async Task ReloadData() + protected async Task ReloadDataAsync() { ListRecords = null; isLoading = true; - SearchRecords = await MDService.POdlToKitListGetFiltAsync(hasOdl, StatoSel, macchina, reparto, selDtStart, selDtEnd); +#if false + AllRecords = await MDService.POdlToKitListGetFiltAsync(hasOdl, StatoSel, macchina, reparto, selDtStart, selDtEnd); +#endif // rivedere filtro FixMe ToDo!!! // filtro tenendo SOLO se hanno keyRichiesta CodExt + ATTIVI + NON KIT | Hard Coded... - SearchRecords = SearchRecords + SelRecords = AllRecords .Where(x => !string.IsNullOrEmpty(x.KeyRichiesta) && x.Attivabile && !x.KeyRichiesta.StartsWith("KIT")) .ToList(); @@ -152,7 +161,7 @@ namespace MP.SPEC.Components.ProdKit currPage = 1; if (forceUpdate) { - await ReloadData(); + await ReloadDataAsync(); } } @@ -179,7 +188,7 @@ namespace MP.SPEC.Components.ProdKit protected async Task UpdateData() { currRecord = null; - await ReloadData(); + await ReloadDataAsync(); } #endregion Protected Methods @@ -197,15 +206,8 @@ namespace MP.SPEC.Components.ProdKit /// private List odlCurrList = new List(); - private List? SearchRecords; - private string searchVal = ""; - protected string sSearchtCss - { - get => string.IsNullOrEmpty(searchVal) ? "btn-secondary" : "btn-primary"; - } - #endregion Private Fields #region Private Properties @@ -298,9 +300,9 @@ namespace MP.SPEC.Components.ProdKit private void UpdateTable() { totalCount = 0; - if (SearchRecords != null) + if (SelRecords != null) { - var filtRec = new List(SearchRecords); + var filtRec = new List(SelRecords); // se ho ricerca filtro! if (!string.IsNullOrEmpty(searchVal)) { @@ -310,7 +312,7 @@ namespace MP.SPEC.Components.ProdKit { int.TryParse(searchVal.Replace("PODL", ""), out idxPodl); } - filtRec = SearchRecords + filtRec = AllRecords .Where(x => (x.KeyRichiesta.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase) || x.KeyBCode.Contains(searchVal, StringComparison.InvariantCultureIgnoreCase) || (x.IdxPromessa == idxPodl && idxPodl > 0)) diff --git a/MP.SPEC/Components/ProdKit/Manager.razor b/MP.SPEC/Components/ProdKit/Manager.razor index 68957397..5f0f7405 100644 --- a/MP.SPEC/Components/ProdKit/Manager.razor +++ b/MP.SPEC/Components/ProdKit/Manager.razor @@ -10,7 +10,7 @@ @if (DoAddNew) {
- +
diff --git a/MP.SPEC/Components/ProdKit/Manager.razor.cs b/MP.SPEC/Components/ProdKit/Manager.razor.cs index 1183f648..f42c073f 100644 --- a/MP.SPEC/Components/ProdKit/Manager.razor.cs +++ b/MP.SPEC/Components/ProdKit/Manager.razor.cs @@ -129,6 +129,7 @@ namespace MP.SPEC.Components.ProdKit private List listPOdlCheck = new List(); private List listTSM = new List(); private List listWSM = new List(); + private List listPOdl2Kit = new List(); private string padCodXdl = "00000"; private string userName = ""; private string userNameFull = ""; @@ -187,6 +188,7 @@ namespace MP.SPEC.Components.ProdKit { listPOdlCheck = new List(); 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); listWSM = await MDService.WipKitFiltAsync(keyFilt); listTSM = await MDService.TksScoreAsync(keyFilt, 1000, true); listIKP = await MDService.IstKitFiltAsync("", ""); @@ -199,7 +201,7 @@ namespace MP.SPEC.Components.ProdKit // continuo con PODL if (!string.IsNullOrEmpty(codArtKit)) { - listPOdlCheck = MDService.ListPODL_ByCodArt(codArtKit, true); + listPOdlCheck = await MDService.ListPODL_ByCodArtAsync(codArtKit, true); } isLoading = false; } diff --git a/MP.SPEC/Data/MpDataService.cs b/MP.SPEC/Data/MpDataService.cs index e7ba4a1a..90f0a7a3 100644 --- a/MP.SPEC/Data/MpDataService.cs +++ b/MP.SPEC/Data/MpDataService.cs @@ -1150,20 +1150,28 @@ namespace MP.SPEC.Data /// public async Task IstKitDelete(IstanzeKitModel currRecord) { - using var activity = ActivitySource.StartActivity("IstKitDelete"); - string source = "DB+REDIS"; + using var activity = ActivitySource.StartActivity("IstKitDeleteAsync"); + string source = "DB"; bool fatto = false; // salvo - fatto = dbController.IstKitDelete(currRecord); + fatto = await dbController.IstKitDeleteAsync(currRecord); // svuoto cache - RedisValue pattern = $"{Utils.redisKitInst}:*"; - await ExecFlushRedisPatternAsync(pattern); + await FlushKitCache(); activity?.SetTag("data.source", source); activity?.Stop(); - LogTrace($"IstKitDelete | Read from {source}: {activity?.Duration.TotalMilliseconds}ms"); + LogTrace($"IstKitDeleteAsync | Read from {source}: {activity?.Duration.TotalMilliseconds}ms"); return fatto; } + private async Task FlushKitCache() + { +#if false + RedisValue pattern = $"{Utils.redisKitInst}:*"; + await ExecFlushRedisPatternAsync(pattern); +#endif + await FlushCacheByTagsAsync(new List() { Utils.redisPOdlList, Utils.redisKitInst, Utils.redisKitWip, Utils.redisKitScore, Utils.redisPOdlByCodArt }); + } + /// /// Elenco Istanze KIT da ricerca /// @@ -1195,7 +1203,7 @@ namespace MP.SPEC.Data // salvo fatto = await dbController.IstKitInsertByWKSAsync(CodArtParent, KeyFilt); // svuoto cache - await FlushCacheByTagAsync(Utils.redisPOdlList); + await FlushKitCache(); activity?.SetTag("data.source", source); activity?.Stop(); LogTrace($"IstKitInsertByWKSAsync | {source} | {activity?.Duration.TotalMilliseconds}ms"); @@ -1208,17 +1216,16 @@ namespace MP.SPEC.Data /// public async Task IstKitUpsert(IstanzeKitModel currRecord) { - using var activity = ActivitySource.StartActivity("IstKitUpsert"); - string source = "DB+REDIS"; + using var activity = ActivitySource.StartActivity("IstKitUpsertAsync"); + string source = "DB"; bool fatto = false; // salvo - fatto = dbController.IstKitUpsert(currRecord); + fatto = await dbController.IstKitUpsertAsync(currRecord); // svuoto cache - RedisValue pattern = $"{Utils.redisKitInst}:*"; - await ExecFlushRedisPatternAsync(pattern); + await FlushKitCache(); activity?.SetTag("data.source", source); activity?.Stop(); - LogTrace($"IstKitUpsert | {source} | {activity?.Duration.TotalMilliseconds}ms"); + LogTrace($"IstKitUpsertAsync | {source} | {activity?.Duration.TotalMilliseconds}ms"); return fatto; } @@ -1245,15 +1252,24 @@ namespace MP.SPEC.Data /// /// True = aperti (=senza ODL) /// - public List ListPODL_ByCodArt(string CodArticolo, bool OnlyAvail) + public async Task> ListPODL_ByCodArtAsync(string CodArticolo, bool OnlyAvail) { + string avType = OnlyAvail ? "Avail" : "ALL"; + string currKey = $"{Utils.redisPOdlByCodArt}:{CodArticolo}:{avType}"; + return await GetOrFetchAsync( + operationName: "ListPODL_ByCodArtAsync", + cacheKey: currKey, + expiration: getRandTOut(redisLongTimeCache), + fetchFunc: async () => await dbController.ListPODL_ByCodArtAsync(CodArticolo, OnlyAvail) ?? new(), + tagList: [Utils.redisPOdlByCodArt] + ); + +#if false List result = new List(); if (!string.IsNullOrEmpty(CodArticolo)) { using var activity = ActivitySource.StartActivity("ListPODL_ByCodArt"); string source = "DB"; - string avType = OnlyAvail ? "Avail" : "ALL"; - string currKey = $"{Utils.redisPOdlByCodArt}:{CodArticolo}:{avType}"; // cerco in redis dato valore sel idxMaccSel... RedisValue rawData = redisDb.StringGet(currKey); if (rawData.HasValue && rawData.Length() > 2) @@ -1285,7 +1301,8 @@ namespace MP.SPEC.Data { Log.Debug("Errore CodArt vuoto"); } - return result; + return result; +#endif } /// @@ -1757,19 +1774,22 @@ namespace MP.SPEC.Data /// Effettua il task di eliminazione PODL KIT + istanze + riattivazione PODL originali disattivate tramite stored /// /// IdxPODL parent - public bool PodlIstKitDelete(int IdxPODL) + public async Task PodlIstKitDeleteAsync(int IdxPODL) { - using var activity = ActivitySource.StartActivity("PodlIstKitDelete"); + using var activity = ActivitySource.StartActivity("PodlIstKitDeleteAsync"); bool fatto = false; // salvo - fatto = dbController.PodlIstKitDelete(IdxPODL); + fatto = await dbController.PodlIstKitDeleteAsync(IdxPODL); // svuoto cache + await FlushCacheByTagsAsync(new List() { Utils.redisPOdlList }); +#if false string pattern = $"{Utils.redisKit}:*"; if (!string.IsNullOrEmpty(pattern)) { ExecFlushRedisPattern(pattern); - } - activity?.SetTag("data.source", "DB+REDIS"); + } +#endif + activity?.SetTag("data.source", "DB"); return fatto; } @@ -1778,8 +1798,18 @@ namespace MP.SPEC.Data ///
/// IDX PODL parent /// - public List POdlListByKitParent(int IdxPodlParent) + public async Task> POdlListByKitParentAsync(int IdxPodlParent) { + string currKey = $"{Utils.redisPOdlList}_kit:ByParent:{IdxPodlParent}"; + return await GetOrFetchAsync( + operationName: "POdlListByKitParentAsync", + cacheKey: currKey, + expiration: getRandTOut(redisShortTimeCache), + fetchFunc: async () => await dbController.ListPODL_ByKitParentAsync(IdxPodlParent) ?? new(), + tagList: [Utils.redisPOdlList] + ); + +#if false using var activity = ActivitySource.StartActivity("POdlListByKitParent"); List? result = new List(); string source = "DB"; @@ -1793,7 +1823,7 @@ namespace MP.SPEC.Data } else { - result = dbController.ListPODL_ByKitParent(IdxPodlParent); + result = await dbController.ListPODL_ByKitParentAsync(IdxPodlParent); // serializzo e salvo... rawData = JsonConvert.SerializeObject(result); redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); @@ -1806,7 +1836,8 @@ namespace MP.SPEC.Data activity?.SetTag("result.count", result.Count); activity?.Stop(); LogTrace($"POdlListByKitParent | Read from {source}: {activity?.Duration.TotalMilliseconds}ms"); - return result; + return result; +#endif } /// diff --git a/MP.SPEC/Pages/Podl2Kit.razor.cs b/MP.SPEC/Pages/Podl2Kit.razor.cs index ab58f170..47361bd6 100644 --- a/MP.SPEC/Pages/Podl2Kit.razor.cs +++ b/MP.SPEC/Pages/Podl2Kit.razor.cs @@ -294,7 +294,7 @@ namespace MP.SPEC.Pages get => doAddNew ? (isComposing ? "Completare o Resettare" : "Chiudi Composizione KIT") : "Composizione Nuovo KIT"; } - private SelectXdlParams currFilter { get; set; } = new SelectXdlParams(); + private SelectXdlParams currFilter { get; set; } = new SelectXdlParams() { NumRec = 5 }; private int currPage { diff --git a/Refactor_Plan.md b/Refactor_Plan.md index ed85dfe8..8120444a 100644 --- a/Refactor_Plan.md +++ b/Refactor_Plan.md @@ -19,7 +19,7 @@ Migrare la logica di caching manuale (Redis + DB) verso l'utilizzo di `IFusionCa ### Fase 2: Refactoring Metodi di Lettura (Cache-aside) (In corso) -#### ✅ Metodi Migrati (Usano già `GetOrFetchAsync` o `FusionCache.GetOrSet`) +#### ✅ Metodi Migrati (Usano già `GetOrFetchAsync`, `FusionCache.GetOrSet` o `FlushCacheByTagAsync`) - `AnagEventiGeneralAsync` - `AnagStatiCommAsync` - `AnagTipoArtLvAsync` @@ -46,7 +46,7 @@ Migrare la logica di caching manuale (Redis + DB) verso l'utilizzo di `IFusionCa - `OdlListGetFiltAsync` - `OperatoriGetFiltAsync` - `ParametriGetFiltAsync` -- `PODL_getDictOdlPodlAsync` (Migrato con gestione manuale L1/L2) +- `PODL_getDictOdlPodlAsync` (Gestione manuale L1/L2 con FusionCache) - `POdlGetByOdlAsync` - `POdlToKitListGetFiltAsync` - `StatoMacchinaAsync` @@ -59,11 +59,16 @@ Migrare la logica di caching manuale (Redis + DB) verso l'utilizzo di `IFusionCa - `TemplateKitUpsertAsync` (Migrato con tag invalidazione) - `WipKitDeleteAsync` (Migrato con tag invalidazione) - `WipKitUpsertAsync` (Migrato con tag invalidazione) +- `AnagGruppiDeleteAsync` (Migrato con tag invalidazione) +- `AnagGruppiUpsertAsync` (Migrato con tag invalidazione) +- `Grp2MaccDeleteAsync` (Migrato con tag invalidazione) +- `Grp2MaccInsertAsync` (Migrato con tag invalidazione) +- `Grp2OperDeleteAsync` (Migrato con tag invalidazione) +- `Grp2OperInsertAsync` (Migrato con tag invalidazione) #### 🛠️ Metodi da Migrare (Usano ancora Redis/DB manuale) - [ ] Migrazione di `ActionGetReq` (linea 110: usa `redisDb.StringGetAsync`). - [ ] Migrazione di `ActionSetReq` (linea 136: usa `BroadastMsgPipe.saveAndSendMessage`). -- [ ] Migrazione di `AnagGruppiDelete`/`Upsert` (linea 189/208: usa `ExecFlushRedisPattern`). - [ ] Migrazione di `ArticoliDeleteRecord`/`UpdateRecord` (linea 296/372: usa `resetCacheArticoli`). - [ ] Migrazione di `DbDedupStats` (linea 516: usa `redisDb.StringGet`). - [ ] Migrazione di `DossiersDeleteRecord` (linea 554: usa `ExecFlushRedisPatternAsync`). @@ -88,3 +93,4 @@ Migrare la logica di caching manuale (Redis + DB) verso l'utilizzo di `IFusionCa +