diff --git a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs deleted file mode 100644 index 39bf865a..00000000 --- a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs +++ /dev/null @@ -1,307 +0,0 @@ -using EgwCoreLib.Lux.Data.DbModel.Production; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using NLog; -using StackExchange.Redis; -using System.Data; - -namespace EgwCoreLib.Lux.Data.Controllers -{ - internal class LuxController - { - - #region Internal Methods - - -#if true - /// - /// Assegnazione in blocco degli item agli ODL corrispondenti - /// - /// - /// - /// - internal async Task ProdItem2ODL_AssignAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts) - { - int totalCreated = 0; - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query - List batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList(); - - if (batchIds != null && batchIds.Count > 0) - { - // 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM) - var itemsList = await dbCtx.DbSetProdItem - .Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "") - .Select(x => new { x.ProdItemID, x.ProdItemTag }) - .ToListAsync(); - - // 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag - // perché il filtro .Where sopra garantisce che non sia null. - var itemLookup = itemsList - .GroupBy(x => x.ProdItemTag!) - .ToDictionary( - g => g.Key, - g => g.First().ProdItemID, - StringComparer.OrdinalIgnoreCase - ); - - using var transaction = await dbCtx.Database.BeginTransactionAsync(); - try - { - var relationsToInsert = new List(); - - foreach (var odl in dbList) - { - var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index); - - if (dictParts.TryGetValue(key, out List tagList)) - { - foreach (var tag in tagList) - { - // 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale - if (itemLookup.TryGetValue(tag, out int realItemId)) - { - relationsToInsert.Add(new ProductionItem2ODLModel - { - ProdODLID = odl.ProdODLID, - ProdItemID = realItemId, - DtAssign = DateTime.Now - }); - } - else - { - //Log.Warning($"Tag {tag} non trovato nel database per i batch selezionati."); - } - } - } - } - - if (relationsToInsert.Any()) - { - await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert); - totalCreated = relationsToInsert.Count; - await dbCtx.SaveChangesAsync(); - } - - await transaction.CommitAsync(); - } - catch (Exception exc) - { - await transaction.RollbackAsync(); - Log.Error($"Errore nel salvataggio relazioni ODL-Parts: {exc.Message}"); - throw; - } - } - } - return totalCreated; - } -#endif - -#if false - /// - /// Elenco da DB delle stats aggregate dato periodo inizio/fine - /// - /// - /// - /// - internal async Task> StatsAggrGetAsync(DateTime dtStart, DateTime dtEnd) - { - List answ = new List(); - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // recupero ed ordino per data-ora - answ = await dbCtx - .DbSetStatsAggr - .Where(x => x.Hour >= dtStart && x.Hour <= dtEnd) - .AsNoTracking() - .OrderBy(x => x.Hour) - .ToListAsync(); - } - return answ; - } - - /// - /// Range periodo per chiamate aggregate - /// - /// - internal async Task StatsAggrRangeAsync() - { - Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today); - using (DataLayerContext dbCtx = new DataLayerContext()) - { - var query = dbCtx.DbSetStatsAggr.AsQueryable(); - - var minHour = await query.MinAsync(x => x.Hour); - var maxHour = await query.MaxAsync(x => x.Hour); - answ.Inizio = minHour; - answ.Fine = maxHour; - return answ; - } - } - - /// - /// Esegue insert statistiche aggregate sul DB - /// - /// Elenco dei record da inserire - /// Se true preventivamente elimina record nel periodo richiesto - /// - internal async Task StatsAggrUpsertAsync(List listRecords, bool removeOld) - { - int answ = 0; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // in primis se richiesto calcolo range periodo e svuoto... - if (removeOld) - { - var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault(); - var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault(); - - if (firstRec != null && lastRec != null) - { - DateTime startDate = firstRec.Hour; - DateTime endDate = lastRec.Hour; - // uso direttamente ExecuteDelete - await dbCtx - .DbSetStatsAggr - .Where(x => x.Hour >= startDate && x.Hour <= endDate) - .ExecuteDeleteAsync(); - } - } - - // ora preparo inserimento massivo - await dbCtx - .DbSetStatsAggr - .AddRangeAsync(listRecords); - - // salvo! - answ = await dbCtx.SaveChangesAsync(); - - // libero memoria del changeTracker - dbCtx.ChangeTracker.Clear(); - } - return answ; - } -#endif -#if false - - /// - /// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo - /// - /// - /// - /// - /// - /// - internal async Task> StatsDetailModelGetAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "") - { - List answ = new List(); - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // recupero ed ordino per data-ora - var query = dbCtx.DbSetStatsDet - .Where(x => x.Hour >= dtStart && x.Hour <= dtEnd); - - if (!string.IsNullOrEmpty(sEnvir)) - query = query.Where(x => x.Environment == sEnvir); - - if (!string.IsNullOrEmpty(sType)) - query = query.Where(x => x.Type == sType); - - answ = await query - .AsNoTracking() - .OrderBy(x => x.Hour) - .ThenBy(x => x.Environment) - .ThenBy(x => x.Type) - .ToListAsync(); - } - return answ; - } - - /// - /// Range periodo x chiamate detail eventualmente filtrate - /// - /// - /// - /// - internal async Task StatsDetailModelRangeAsync(string sEnvir, string sType) - { - Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today); - using (DataLayerContext dbCtx = new DataLayerContext()) - { - var query = dbCtx.DbSetStatsDet.AsQueryable(); - - if (!string.IsNullOrEmpty(sEnvir)) - query = query.Where(x => x.Environment == sEnvir); - - if (!string.IsNullOrEmpty(sType)) - query = query.Where(x => x.Type == sType); - - var minHour = await query.MinAsync(x => x.Hour); - var maxHour = await query.MaxAsync(x => x.Hour); - answ.Inizio = minHour; - answ.Fine = maxHour; - return answ; - } - } - - /// - /// Esegue insert statistiche di dettaglio sul DB - /// - /// Elenco dei record da inserire - /// Se true preventivamente elimina record nel periodo richiesto - /// - internal async Task StatsDetailModelUpsertAsync(List listRecords, bool removeOld) - { - int answ = 0; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // in primis se richiesto calcolo range periodo e svuoto... - if (removeOld) - { - var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault(); - var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault(); - - if (firstRec != null && lastRec != null) - { - DateTime startDate = firstRec.Hour; - DateTime endDate = lastRec.Hour; - // uso direttamente ExecuteDelete - await dbCtx - .DbSetStatsDet - .Where(x => x.Hour >= startDate && x.Hour <= endDate) - .ExecuteDeleteAsync(); - } - } - - // ora preparo inserimento massivo - await dbCtx - .DbSetStatsDet - .AddRangeAsync(listRecords); - - // salvo! - answ = await dbCtx.SaveChangesAsync(); - - // libero memoria del changeTracker - dbCtx.ChangeTracker.Clear(); - } - return answ; - } -#endif - - - #endregion Internal Methods - - #region Private Fields - - private static IConfiguration _configuration; - - private static Logger Log = LogManager.GetCurrentClassLogger(); - - #endregion Private Fields - } -} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRepository.cs b/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRepository.cs index 39d004ec..22b5ceca 100644 --- a/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRepository.cs @@ -34,7 +34,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { DateTime now = DateTime.Now; @@ -163,7 +163,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity when saving multiple rows - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) diff --git a/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRowRepository.cs index b6ea44ae..86793c04 100644 --- a/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Catalog/TemplateRowRepository.cs @@ -32,7 +32,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var currRec = await dbCtx.DbSetTemplateRow @@ -136,7 +136,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity when saving multiple rows - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) diff --git a/EgwCoreLib.Lux.Data/Repository/Job/JobStepRepository.cs b/EgwCoreLib.Lux.Data/Repository/Job/JobStepRepository.cs index 3059ab1b..54339891 100644 --- a/EgwCoreLib.Lux.Data/Repository/Job/JobStepRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Job/JobStepRepository.cs @@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var dbResult = await dbCtx.DbSetJobStep @@ -93,7 +93,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var currRec = await dbCtx.DbSetJobStep diff --git a/EgwCoreLib.Lux.Data/Repository/Job/JobTaskRepository.cs b/EgwCoreLib.Lux.Data/Repository/Job/JobTaskRepository.cs index ae2e749c..ce4af2b5 100644 --- a/EgwCoreLib.Lux.Data/Repository/Job/JobTaskRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Job/JobTaskRepository.cs @@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var dbResult = await dbCtx.DbSetJobTask @@ -86,7 +86,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row add + multi-row remove) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var currRec = await dbCtx.DbSetJobTask @@ -144,7 +144,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Job await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { var currRec = await dbCtx.DbSetJobTask diff --git a/EgwCoreLib.Lux.Data/Repository/Production/IProductionOdlRepository.cs b/EgwCoreLib.Lux.Data/Repository/Production/IProductionOdlRepository.cs index 3e075f09..8ae15981 100644 --- a/EgwCoreLib.Lux.Data/Repository/Production/IProductionOdlRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Production/IProductionOdlRepository.cs @@ -14,6 +14,8 @@ namespace EgwCoreLib.Lux.Data.Repository.Production /// Task AddAsync(ProductionODLModel entity); + Task AssignProdItem2OdlAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts); + /// /// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items) /// diff --git a/EgwCoreLib.Lux.Data/Repository/Production/ProductionGroupRepository.cs b/EgwCoreLib.Lux.Data/Repository/Production/ProductionGroupRepository.cs index 75189754..6bcf42bf 100644 --- a/EgwCoreLib.Lux.Data/Repository/Production/ProductionGroupRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Production/ProductionGroupRepository.cs @@ -65,7 +65,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production } await using var dbCtx = await CreateContextAsync(); - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // Tentativo di deserializzazione diff --git a/EgwCoreLib.Lux.Data/Repository/Production/ProductionItemRepository.cs b/EgwCoreLib.Lux.Data/Repository/Production/ProductionItemRepository.cs index 7590c50b..46ec6de3 100644 --- a/EgwCoreLib.Lux.Data/Repository/Production/ProductionItemRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Production/ProductionItemRepository.cs @@ -25,7 +25,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production { await using var dbCtx = await CreateContextAsync(); int totUpd = 0; - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { int rowsAffected = await dbCtx.DbSetProdItem @@ -56,7 +56,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production { await using var dbCtx = await CreateContextAsync(); int totUpd = 0; - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var entry in itemsToAssign) @@ -93,7 +93,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production { await using var dbCtx = await CreateContextAsync(); int numItem = 0; - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { numItem = await dbCtx.DbSetProdItem diff --git a/EgwCoreLib.Lux.Data/Repository/Production/ProductionOdlRepository.cs b/EgwCoreLib.Lux.Data/Repository/Production/ProductionOdlRepository.cs index 4b4ffe76..0c3b17fe 100644 --- a/EgwCoreLib.Lux.Data/Repository/Production/ProductionOdlRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Production/ProductionOdlRepository.cs @@ -24,6 +24,83 @@ namespace EgwCoreLib.Lux.Data.Repository.Production return await dbCtx.SaveChangesAsync() > 0; } + /// + /// Assegnazione in blocco degli item agli ODL corrispondenti + /// + /// + /// + /// + public async Task AssignProdItem2OdlAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts) + { + int totalCreated = 0; + await using var dbCtx = await CreateContextAsync(); + + // 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query + List batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList(); + + if (batchIds != null && batchIds.Count > 0) + { + // 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM) + var itemsList = await dbCtx.DbSetProdItem + .Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "") + .Select(x => new { x.ProdItemID, x.ProdItemTag }) + .ToListAsync(); + + // 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag + // perché il filtro .Where sopra garantisce che non sia null. + var itemLookup = itemsList + .GroupBy(x => x.ProdItemTag!) + .ToDictionary( + g => g.Key, + g => g.First().ProdItemID, + StringComparer.OrdinalIgnoreCase + ); + + await using var tx = await dbCtx.Database.BeginTransactionAsync(); + try + { + var relationsToInsert = new List(); + + foreach (var odl in dbList) + { + var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index); + + if (dictParts.TryGetValue(key, out List tagList)) + { + foreach (var tag in tagList) + { + // 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale + if (itemLookup.TryGetValue(tag, out int realItemId)) + { + relationsToInsert.Add(new ProductionItem2ODLModel + { + ProdODLID = odl.ProdODLID, + ProdItemID = realItemId, + DtAssign = DateTime.Now + }); + } + } + } + } + + if (relationsToInsert.Any()) + { + await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert); + totalCreated = relationsToInsert.Count; + await dbCtx.SaveChangesAsync(); + } + + await tx.CommitAsync(); + } + catch + { + await tx.RollbackAsync(); + throw; + } + } + return totalCreated; + } + /// /// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items) /// @@ -35,8 +112,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Production await using var dbCtx = await CreateContextAsync(); // avvio transazione - using var transaction = await dbCtx.Database.BeginTransactionAsync(); - + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { int cYear = DateTime.Today.Year; @@ -55,16 +131,16 @@ namespace EgwCoreLib.Lux.Data.Repository.Production await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdOdl_UpdateTag(@pProdBatchID, @pPrefix, @pYear)", pProdBatchID, pPref, pYear); // 4. Conferma transazione - await transaction.CommitAsync(); + await tx.CommitAsync(); // A questo punto, ogni oggetto in 'listOdl2ins' ha il ProdODLID aggiornato dal DB return listOdl2ins; } - catch (Exception ex) + catch { - await transaction.RollbackAsync(); + await tx.RollbackAsync(); // Logga l'errore secondo le tue necessità - throw new Exception("Errore durante la creazione massiva degli ODL", ex); + throw; } } diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs index 75647785..b83a3817 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs @@ -65,7 +65,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (parent + children clone) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { DateTime now = DateTime.Now; @@ -224,7 +224,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs index 186e66de..86c71b0f 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRowRepository.cs @@ -29,7 +29,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 1. Recupero il record da eliminare @@ -116,7 +116,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs index d0455019..66316380 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRepository.cs @@ -38,7 +38,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales OrderModel? newRec = null; await using var dbCtx = await CreateContextAsync(); // avvio transazione - using var transaction = await dbCtx.Database.BeginTransactionAsync(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { DateTime now = DateTime.Now; @@ -168,13 +168,12 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await dbCtx.Database.ExecuteSqlRawAsync("CALL stp_ProdItem_UpdateProdItemTag(0,0);"); // committo in un unica transazione (da provare!!!) - await transaction.CommitAsync(); + await tx.CommitAsync(); } } catch { - await transaction.RollbackAsync(); - // sollevo exception + await tx.RollbackAsync(); throw; } return newRec; @@ -246,17 +245,17 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) dbCtx.Entry(row).State = EntityState.Modified; bool done = await dbCtx.SaveChangesAsync() > 0; - + if (done) tx.Commit(); - + return done; } catch diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs index 77b99722..d4ea7623 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs @@ -33,7 +33,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 1. Recupero il record da eliminare @@ -137,7 +137,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { foreach (var row in rows) @@ -183,7 +183,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // recupero offerta... @@ -357,7 +357,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales // context await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (batch update multiple rows) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // verifica preliminare: serve SSE stato e estimate non corrispondono... diff --git a/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs index 2baec0ad..fb72381a 100644 --- a/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs @@ -60,7 +60,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Stats { int answ = 0; await using var dbCtx = await CreateContextAsync(); - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // in primis se richiesto calcolo range periodo e svuoto... diff --git a/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs index 4a16a0a1..cf5e3e5a 100644 --- a/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs @@ -85,7 +85,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Stats { int answ = 0; await using var dbCtx = await CreateContextAsync(); - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // in primis se richiesto calcolo range periodo e svuoto... diff --git a/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs b/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs index 49927e47..a48bf5b7 100644 --- a/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Utils/GenValRepository.cs @@ -30,7 +30,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update + delete) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 1. Recupero il record da eliminare @@ -92,7 +92,7 @@ namespace EgwCoreLib.Lux.Data.Repository.Utils await using var dbCtx = await CreateContextAsync(); // Wrap in transaction for atomicity (multi-row update - swap positions) - await using var tx = dbCtx.Database.BeginTransaction(); + await using var tx = await dbCtx.Database.BeginTransactionAsync(); try { // 1. Recupero il record corrente diff --git a/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs index 7bbb3df3..9294043b 100644 --- a/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs +++ b/EgwCoreLib.Lux.Data/Services/CalcRuidService.cs @@ -1,13 +1,10 @@ using EgwCoreLib.Lux.Core.Stats; -using EgwCoreLib.Lux.Data.Controllers; using EgwCoreLib.Lux.Data.DbModel.Stats; using EgwCoreLib.Lux.Data.Services.Stats; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using NLog; using StackExchange.Redis; -using System.Text; using static Egw.Window.Data.Enums; namespace EgwCoreLib.Lux.Data.Services @@ -23,7 +20,6 @@ namespace EgwCoreLib.Lux.Data.Services IConfiguration configuration, IConnectionMultiplexer redisConn, IServiceProvider serviceProvider) : base(configuration, redisConn) - //public CalcRuidService(IConfiguration configuration, IConnectionMultiplexer redisConn, string redisBaseKey, TimeSpan retention, TimeSpan archivePeriod) : base(configuration, redisConn) { // leggo conf retention/archive da config... string cleanupDayTTL = configuration.GetValue("ServerConf:CleanupDayTTL") ?? "360"; @@ -38,28 +34,11 @@ namespace EgwCoreLib.Lux.Data.Services _base = rBaseKey.TrimEnd(':'); _retention = TimeSpan.FromDays(dayTTL); _archivePeriod = TimeSpan.FromDays(archTTL); - - // conf DB - string connStr = _config.GetConnectionString("Lux.All") ?? ""; - if (string.IsNullOrEmpty(connStr)) - { - Log.Error("ConnString empty!"); - } - else - { - //dbController = new Controllers.LuxController(_config); - dbController = new LuxController(); - StringBuilder sb = new StringBuilder(); - sb.AppendLine($"CalcRuidService | LuxController OK"); - Log.Info(sb.ToString()); - } + Log.Info($"CalcRuidService | Started"); } #endregion Public Constructors - public IStatsAggrService StAggServ => _serviceProvider.GetRequiredService(); - public IStatsDetailService StDetServ => _serviceProvider.GetRequiredService(); - private readonly IServiceProvider _serviceProvider; #region Public Enums /// @@ -82,6 +61,13 @@ namespace EgwCoreLib.Lux.Data.Services #endregion Public Enums + #region Public Properties + + public IStatsAggrService StAggServ => _serviceProvider.GetRequiredService(); + public IStatsDetailService StDetServ => _serviceProvider.GetRequiredService(); + + #endregion Public Properties + #region Public Methods /// @@ -138,109 +124,6 @@ namespace EgwCoreLib.Lux.Data.Services return ruid; } -#if false - /// - /// Metodo di Cleanup periodico - /// - /// Environment calcolo - /// Tipologia richiesta - /// - public async Task CleanupOldRequestsAsync(string environment, string tipo) - { - var cutoff = DateTimeOffset.Now.Add(-_retention).ToUnixTimeMilliseconds(); - var setKey = GetSortedSetKey(environment, tipo); - - var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); - if (oldIds.Length == 0) return; - - var batch = _db.CreateBatch(); - var tasks = new List(); - - foreach (var id in oldIds) - { - var ruid = id.ToString(); - var hashKey = GetRequestKey(ruid); - - var uid = await _db.HashGetAsync(hashKey, "UID"); - if (!uid.IsNull) - { - var uidKey = GetUidSetKey(uid); - tasks.Add(batch.SetRemoveAsync(uidKey, ruid)); - - tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(uidKey); - })); - } - - tasks.Add(batch.KeyDeleteAsync(hashKey)); - } - - tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff)); - - tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(setKey); - })); - - batch.Execute(); - await Task.WhenAll(tasks); - } - - /// - /// Metodo di Cleanup dato periodo limite prima del quale va eliminato l'elenco dei dati - /// - /// Environment calcolo - /// Tipologia richiesta - /// Limite temporale dati da eliminare - /// - public async Task CleanupOldRequestsAsync(string environment, string tipo, TimeSpan timeLimit) - { - var cutoff = DateTimeOffset.Now.Add(-timeLimit).ToUnixTimeMilliseconds(); - var setKey = GetSortedSetKey(environment, tipo); - - var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); - if (oldIds.Length == 0) return; - - var batch = _db.CreateBatch(); - var tasks = new List(); - - foreach (var id in oldIds) - { - var ruid = id.ToString(); - var hashKey = GetRequestKey(ruid); - - var uid = await _db.HashGetAsync(hashKey, "UID"); - if (!uid.IsNull) - { - var uidKey = GetUidSetKey(uid); - tasks.Add(batch.SetRemoveAsync(uidKey, ruid)); - - tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(uidKey); - })); - } - - tasks.Add(batch.KeyDeleteAsync(hashKey)); - } - - tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff)); - - tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t => - { - if (t.Result == 0) - _db.KeyDelete(setKey); - })); - - batch.Execute(); - await Task.WhenAll(tasks); - } -#endif - /// /// Effettua pulizia delle statistiche collezionate una volta migrate sul DB /// @@ -845,24 +728,117 @@ namespace EgwCoreLib.Lux.Data.Services #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); - private readonly TimeSpan _archivePeriod; - private readonly string _base; - private readonly IDatabase _db; - private readonly TimeSpan _retention; - private readonly Random _rnd = new Random(); + private readonly IServiceProvider _serviceProvider; #endregion Private Fields - #region Private Properties +#if false + /// + /// Metodo di Cleanup periodico + /// + /// Environment calcolo + /// Tipologia richiesta + /// + public async Task CleanupOldRequestsAsync(string environment, string tipo) + { + var cutoff = DateTimeOffset.Now.Add(-_retention).ToUnixTimeMilliseconds(); + var setKey = GetSortedSetKey(environment, tipo); - private static LuxController dbController { get; set; } = null!; + var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); + if (oldIds.Length == 0) return; - #endregion Private Properties + var batch = _db.CreateBatch(); + var tasks = new List(); + + foreach (var id in oldIds) + { + var ruid = id.ToString(); + var hashKey = GetRequestKey(ruid); + + var uid = await _db.HashGetAsync(hashKey, "UID"); + if (!uid.IsNull) + { + var uidKey = GetUidSetKey(uid); + tasks.Add(batch.SetRemoveAsync(uidKey, ruid)); + + tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(uidKey); + })); + } + + tasks.Add(batch.KeyDeleteAsync(hashKey)); + } + + tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff)); + + tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(setKey); + })); + + batch.Execute(); + await Task.WhenAll(tasks); + } + + /// + /// Metodo di Cleanup dato periodo limite prima del quale va eliminato l'elenco dei dati + /// + /// Environment calcolo + /// Tipologia richiesta + /// Limite temporale dati da eliminare + /// + public async Task CleanupOldRequestsAsync(string environment, string tipo, TimeSpan timeLimit) + { + var cutoff = DateTimeOffset.Now.Add(-timeLimit).ToUnixTimeMilliseconds(); + var setKey = GetSortedSetKey(environment, tipo); + + var oldIds = await _db.SortedSetRangeByScoreAsync(setKey, stop: cutoff); + if (oldIds.Length == 0) return; + + var batch = _db.CreateBatch(); + var tasks = new List(); + + foreach (var id in oldIds) + { + var ruid = id.ToString(); + var hashKey = GetRequestKey(ruid); + + var uid = await _db.HashGetAsync(hashKey, "UID"); + if (!uid.IsNull) + { + var uidKey = GetUidSetKey(uid); + tasks.Add(batch.SetRemoveAsync(uidKey, ruid)); + + tasks.Add(batch.SetLengthAsync(uidKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(uidKey); + })); + } + + tasks.Add(batch.KeyDeleteAsync(hashKey)); + } + + tasks.Add(batch.SortedSetRemoveRangeByScoreAsync(setKey, double.NegativeInfinity, cutoff)); + + tasks.Add(batch.SortedSetLengthAsync(setKey).ContinueWith(t => + { + if (t.Result == 0) + _db.KeyDelete(setKey); + })); + + batch.Execute(); + await Task.WhenAll(tasks); + } +#endif #region Private Methods diff --git a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs index 1146310e..e74b4e37 100644 --- a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs +++ b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs @@ -1,7 +1,6 @@ using EgwCoreLib.Lux.Core.Generic; using EgwCoreLib.Lux.Core.RestPayload; using EgwCoreLib.Lux.Data.Controllers; -using EgwCoreLib.Lux.Data.DbModel.Production; using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.Services.Config; using EgwCoreLib.Lux.Data.Services.Items; @@ -15,7 +14,6 @@ using NLog; using StackExchange.Redis; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Text; namespace EgwCoreLib.Lux.Data.Services { @@ -29,23 +27,8 @@ namespace EgwCoreLib.Lux.Data.Services IServiceProvider serviceProvider) : base(configuration, RedisConn) { _serviceProvider = serviceProvider; - // conf DB - string connStr = _config.GetConnectionString("Lux.All") ?? ""; - if (string.IsNullOrEmpty(connStr)) - { - Log.Error("ConnString empty!"); - } - else - { - StringBuilder sb = new StringBuilder(); - //dbController = new Controllers.LuxController(_config); - dbController = new LuxController(); - sb.AppendLine($"LuxController OK"); - dataSimController = new DataSimulatorController(); - sb.AppendLine($"DataSimController OK"); - sb.AppendLine($"LuxController OK"); - Log.Info(sb.ToString()); - } + dataSimController = new DataSimulatorController(); + Log.Info($"DataSimController OK"); } #endregion Public Constructors @@ -128,29 +111,6 @@ namespace EgwCoreLib.Lux.Data.Services return result; } - /// - /// Assegnazione in blocco degli item agli ODL corrispondenti - /// - /// - /// - /// - public async Task ProdItem2ODL_AssignAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts) - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - // calcolo - int fatto = await dbController.ProdItem2ODL_AssignAsync(dbList, dictParts); - // svuoto cache... - //await ExecFlushRedisPatternAsync((RedisValue)$"{_redisBaseKey}:ProdItems:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdItems:OrdRowId:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdGroup:OrdRowId:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRows:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRowsByState:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return fatto; - } - /// /// Esegue salvataggio BOM sul DB /// @@ -502,7 +462,6 @@ namespace EgwCoreLib.Lux.Data.Services #region Private Properties private static DataSimulatorController dataSimController { get; set; } = null!; - private static LuxController dbController { get; set; } = null!; #endregion Private Properties } diff --git a/EgwCoreLib.Lux.Data/Services/Production/IProductionOdlService.cs b/EgwCoreLib.Lux.Data/Services/Production/IProductionOdlService.cs index 1cfeed55..5e1e4a0e 100644 --- a/EgwCoreLib.Lux.Data/Services/Production/IProductionOdlService.cs +++ b/EgwCoreLib.Lux.Data/Services/Production/IProductionOdlService.cs @@ -7,6 +7,8 @@ namespace EgwCoreLib.Lux.Data.Services.Production { #region Public Methods + Task AssignProdItem2OdlAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts); + /// /// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items) /// diff --git a/EgwCoreLib.Lux.Data/Services/Production/ProductionOdlService.cs b/EgwCoreLib.Lux.Data/Services/Production/ProductionOdlService.cs index 87cbd509..c31e3467 100644 --- a/EgwCoreLib.Lux.Data/Services/Production/ProductionOdlService.cs +++ b/EgwCoreLib.Lux.Data/Services/Production/ProductionOdlService.cs @@ -23,6 +23,27 @@ namespace EgwCoreLib.Lux.Data.Services.Production #region Public Methods + /// + /// Assegnazione in blocco degli item agli ODL corrispondenti + /// + /// + /// + /// + public async Task AssignProdItem2OdlAsync(List dbList, Dictionary<(int phaseId, int resId, string machine, int index), List> dictParts) + { + return await TraceAsync($"{_className}.Upsert", async (activity) => + { + string operation = "AssignProdItem2Odl"; + var result = await _repo.AssignProdItem2OdlAsync(dbList, dictParts); + + activity?.SetTag("db.operation", operation); + + await ClearCacheAsync($"{_redisBaseKey}:{_className}"); + + return result; + }); + } + /// /// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items) /// diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index 286d0a1f..963bb4da 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.1.2603.2111 + 1.1.2603.2112 diff --git a/Lux.UI/Components/Pages/WorkLoadBalance.razor.cs b/Lux.UI/Components/Pages/WorkLoadBalance.razor.cs index c589bc2e..063d4355 100644 --- a/Lux.UI/Components/Pages/WorkLoadBalance.razor.cs +++ b/Lux.UI/Components/Pages/WorkLoadBalance.razor.cs @@ -265,7 +265,7 @@ namespace Lux.UI.Components.Pages } // ora lavoro sugli items x collegarli agli ODL... parto dal dizionario e cerco nell'elenco degli ODL creati... - int fatto = await DLService.ProdItem2ODL_AssignAsync(prodODL_DbList, dictParts); + int fatto = await POService.AssignProdItem2OdlAsync(prodODL_DbList, dictParts); // aggiorno sales order status a ProdOdl creato foreach (var ordRowId in ordRowIdList) diff --git a/Lux.UI/Lux.UI.csproj b/Lux.UI/Lux.UI.csproj index b938fdf5..58998bbf 100644 --- a/Lux.UI/Lux.UI.csproj +++ b/Lux.UI/Lux.UI.csproj @@ -5,7 +5,7 @@ enable enable aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50 - 1.1.2603.2111 + 1.1.2603.2112 diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index 06fa929d..ea5e4363 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 1.1.2603.2111

+

Versione: 1.1.2603.2112


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index 17f8de52..2b9f4319 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -1.1.2603.2111 +1.1.2603.2112 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index ba23e1d7..0265dd38 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 1.1.2603.2111 + 1.1.2603.2112 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false