diff --git a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs index 53c8ae6e..e3ead987 100644 --- a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs +++ b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs @@ -1,324 +1,18 @@ -using EgwCoreLib.Lux.Core.RestPayload; -using EgwCoreLib.Lux.Data.DbModel.Items; -using EgwCoreLib.Lux.Data.DbModel.Job; -using EgwCoreLib.Lux.Data.DbModel.Production; +using EgwCoreLib.Lux.Data.DbModel.Production; using EgwCoreLib.Lux.Data.DbModel.Stats; -using EgwCoreLib.Lux.Data.Domains; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; using NLog; using StackExchange.Redis; using System.Data; -using static EgwCoreLib.Lux.Core.Enums; namespace EgwCoreLib.Lux.Data.Controllers { internal class LuxController { - // manca costruttore parametrico contoller... #region Internal Methods - /// - /// Add item ricevuti da BOM calcolata - /// - /// - /// - internal bool ItemUpsertFromBom(List bomList) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // Controllo ed inserisco eventuali gruppi mancanti - UpdateCodGroup(bomList); - - // prendo solo elementi a prezzo 0 da salvare sul DB - var item2save = bomList - .Where(x => x.Price == 0) - .ToList(); - List listInserted = new List(); - - // ciclo x ogni elemento della BOM, cercando x gruppo e ExtItemCode - foreach (var item in item2save) - { - var currRec = dbCtx - .DbSetItem - .Where(x => x.CodGroup == item.ClassCode && x.ExtItemCode == item.ItemCode) - .FirstOrDefault(); - - // se nullo --> verifico x inserire!!! - if (currRec == null) - { - // verifico NON sia tra gli list2upd già in fase di inserimento - if (!listInserted.Any(x => x.CodGroup == item.ClassCode && x.ExtItemCode == item.ItemCode)) - { - ItemModel newRec = new ItemModel() - { - CodGroup = item.ClassCode, - ItemType = Core.Enums.ItemClassType.Bom, - IsService = false, - // da calcolare meglio x gruppo - ItemCode = 0, - ExtItemCode = item.ItemCode, - SupplCode = "BOM ITEM", - Description = $"BOM | {item.ClassCode} | {item.ItemCode}", - Cost = 0, - Margin = 0, - QtyMin = 0, - QtyMax = 0, - UM = "#" - }; - dbCtx.DbSetItem.Add(newRec); - listInserted.Add(newRec); - } - } - } - - // salvo... - dbCtx.SaveChanges(); - - } - return answ; - } - /// - /// Elenco item da ricerca filtro x gruppo/tipo - /// - /// - /// - /// - internal List ItemGetFilt(string CodGroup, ItemClassType ItemType) - { - List dbResult = new List(); - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - dbResult = dbCtx - .DbSetItem - .Where(x => (string.IsNullOrEmpty(CodGroup) || x.CodGroup == CodGroup) - && (ItemType == ItemClassType.ND || x.ItemType == ItemType)) - .ToList(); - } - catch (Exception exc) - { - Log.Error($"Eccezione durante ItemGetFilt{Environment.NewLine}{exc}"); - } - } - return dbResult; - } - - internal async Task OffersCheckExpired() - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - DateTime adesso = DateTime.Now; - // recupero offerta... - var listExpired = dbCtx - .DbSetOffer - .Where(x => x.ValidUntil < adesso && x.OffertState == OfferStates.Open) - .ToList(); - - // se trovo le aggiorno come stato - if (listExpired != null) - { - foreach (var item in listExpired) - { - item.OffertState = OfferStates.Expired; - dbCtx.Entry(item).State = EntityState.Modified; - } - // salvo TUTTI i cambiamenti... - var result = await dbCtx.SaveChangesAsync(); - answ = result > 0; - } - } - catch (Exception exc) - { - Log.Error($"Eccezione durante OffersCheckExpired{Environment.NewLine}{exc}"); - } - } - return answ; - } - -#if true - /// - /// Esegue upsert del record offerta data la BOM ricevuta - /// - /// - /// - internal bool OfferUpsertFromBom(string uID, List bomList) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - var currRec = dbCtx - .DbSetOfferRow - .Where(x => x.OfferRowUID == uID) - .FirstOrDefault(); - // se trovato --> salvo BOM e calcolo costi - if (currRec != null) - { - // recupero l'elenco degli itemGroup gestiti - var itemGroupList = dbCtx - .DbSetItemGroup - .ToList(); - - // recupero il subset item da BOM... - var bomGenList = dbCtx - .DbSetItem - //.Where(x => x.sourceType == Core.Enums.ItemClassType.Bom) - .Where(x => (x.ItemType == Core.Enums.ItemClassType.Bom || x.ItemType == Core.Enums.ItemClassType.BomAlt)) - .ToList(); - - // recupero la BOM list precedente - var bomListPrev = JsonConvert.DeserializeObject>(currRec.ItemBOM); - - // calcolo il NUOVO costo e lo aggiorno... - double totCost = 0; - double totPrice = 0; - int totItemQty = 0; - int numGroupOk = 0; - int numItemOk = 0; - int numElems = bomList.Count; - // validazione e completamento BOM - BomCalculator.Validate(itemGroupList, bomGenList, ref bomList, bomListPrev, ref totCost, ref totPrice, ref totItemQty, ref numGroupOk, ref numItemOk); - // salvo BOM... - string itemBom = JsonConvert.SerializeObject(bomList); - currRec.ItemBOM = itemBom; - // salvo arrotondato alla 3° decimale - currRec.BomCost = Math.Round(totCost, 3); - currRec.BomPrice = Math.Round(totPrice, 3); - currRec.BomOk = numElems == numGroupOk; - currRec.ItemOk = numElems == numItemOk; - // setto ok await di BOM e Price - currRec.AwaitBom = false; - currRec.AwaitPrice = false; - currRec.ProdItemQty = totItemQty; - dbCtx.Entry(currRec).State = EntityState.Modified; - } - - // salvo... - var result = dbCtx.SaveChanges(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante OfferUpsertFromBom{Environment.NewLine}{exc}"); - } - } - return answ; - } -#endif - - /// - /// Elenco record Fasi da DB - /// - /// - internal async Task> PhasesGetAllAsync() - { - List dbResult = new List(); - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - dbResult = await dbCtx - .DbSetPhase - .ToListAsync(); - } - catch (Exception exc) - { - Log.Error($"Eccezione durante PhasesGetAllAsync{Environment.NewLine}{exc}"); - } - } - return dbResult; - } - -#if false - /// - /// Add record di un singolo ProdGroup da fase Balance - /// - /// UID dell'item offerta di cui si è ricevuto l'oggetto Balance' - /// Prod Group di riferimento - /// - internal async Task ProdGroupUpsertBalance(string uID, string rGroup, string rawBalance) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - // Tentativo di deserializzazione - var data = JsonConvert.DeserializeObject>(rawBalance); - // proseguo solo se è valida la deserializzazione... - if (data != null) - { - // Togliamo la 'G' e convertiamo in int (gestisce automaticamente "01" -> 1) - int grpIdx = int.Parse(rGroup.TrimStart('G')); - // recupero ord row (parent)... - var ordRowRec = dbCtx - .DbSetOrderRow - .Where(x => x.OrderRowUID == uID) - .FirstOrDefault(); - if (ordRowRec != null) - { - // recupero record specifico - var currRec = dbCtx - .DbSetProdGroup - .Where(x => x.OrderRowID == ordRowRec.OrderRowID && x.GrpIdx == grpIdx) - .FirstOrDefault(); - - // se trovato aggiorno - if (currRec != null) - { - currRec.WorkGroupListRaw = rawBalance; - dbCtx.Entry(currRec).State = EntityState.Modified; - } - // altrimenti aggiungo - else - { - ProductionGroupModel newRec = new ProductionGroupModel() - { - OrderRowID = ordRowRec.OrderRowID, - GrpIdx = grpIdx, - WorkGroupListRaw = rawBalance - }; - dbCtx - .DbSetProdGroup - .Add(newRec); - } - - // segno ordine come Assigned se non lo fosse... - if (ordRowRec.OrderRowState != OrderStates.Assigned) - { - ordRowRec.OrderRowState = OrderStates.Assigned; - dbCtx.Entry(ordRowRec).State = EntityState.Modified; - } - - // salvo TUTTI i cambiamenti... - var result = await dbCtx.SaveChangesAsync(); - answ = result > 0; - } - } - } - catch (Exception exc) - { - Log.Error($"Eccezione durante ProdGroupUpsertBalance{Environment.NewLine}{exc}"); - } - } - return answ; - } -#endif #if true /// @@ -405,8 +99,9 @@ namespace EgwCoreLib.Lux.Data.Controllers } #endif +#if true /// - /// Elenco da DB delel stats aggregate dato periodo inizio/fine + /// Elenco da DB delle stats aggregate dato periodo inizio/fine /// /// /// @@ -490,6 +185,8 @@ namespace EgwCoreLib.Lux.Data.Controllers } return answ; } +#endif +#if true /// /// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo @@ -595,44 +292,9 @@ namespace EgwCoreLib.Lux.Data.Controllers } return answ; } - -#if true - internal bool UpdateCodGroup(List bomList) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - // in primis calcolo i distinct dei CodGroup x eventuale insert preventivo - List distCodGroups = bomList - .Select(i => i.ClassCode) - .Distinct() - .Where(c => !string.IsNullOrWhiteSpace(c)) - .ToList(); - - // recupero l'elenco degli itemGroup gestiti - var itemGroupList = dbCtx - .DbSetItemGroup - .ToList(); - // elenco da inserire... - var codGroupsToInsert = distCodGroups - .Where(x => !itemGroupList.Any(i => i.CodGroup == x)) - .Select(x => new ItemGroupModel() { CodGroup = x, Description = x }) - .ToList(); - // se ci sono inserisco! - if (codGroupsToInsert != null && codGroupsToInsert.Count > 0) - { - dbCtx - .DbSetItemGroup - .AddRange(codGroupsToInsert); - // salvo... - dbCtx.SaveChanges(); - } - } - return answ; - } #endif + #endregion Internal Methods #region Private Fields diff --git a/EgwCoreLib.Lux.Data/DataServiceCollectionExtensions.cs b/EgwCoreLib.Lux.Data/DataServiceCollectionExtensions.cs index 574d898c..57532484 100644 --- a/EgwCoreLib.Lux.Data/DataServiceCollectionExtensions.cs +++ b/EgwCoreLib.Lux.Data/DataServiceCollectionExtensions.cs @@ -44,6 +44,7 @@ namespace EgwCoreLib.Lux.Data services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); @@ -73,6 +74,7 @@ namespace EgwCoreLib.Lux.Data services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); diff --git a/EgwCoreLib.Lux.Data/EgwCoreLib.Lux.Data.csproj b/EgwCoreLib.Lux.Data/EgwCoreLib.Lux.Data.csproj index fed9128f..6d5e1dd7 100644 --- a/EgwCoreLib.Lux.Data/EgwCoreLib.Lux.Data.csproj +++ b/EgwCoreLib.Lux.Data/EgwCoreLib.Lux.Data.csproj @@ -22,6 +22,8 @@ + + diff --git a/EgwCoreLib.Lux.Data/Repository/Items/IItemGroupRepository.cs b/EgwCoreLib.Lux.Data/Repository/Items/IItemGroupRepository.cs index 77883751..ae73ac1d 100644 --- a/EgwCoreLib.Lux.Data/Repository/Items/IItemGroupRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Items/IItemGroupRepository.cs @@ -1,9 +1,18 @@ -using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; namespace EgwCoreLib.Lux.Data.Repository.Items { public interface IItemGroupRepository { + #region Public Methods + + Task AddMissingAsync(List bomList); + + Task AddRangeAsync(List entityList); + Task> GetAllAsync(); + + #endregion Public Methods } -} +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Items/ItemGroupRepository.cs b/EgwCoreLib.Lux.Data/Repository/Items/ItemGroupRepository.cs index a48d051f..4f3c35b6 100644 --- a/EgwCoreLib.Lux.Data/Repository/Items/ItemGroupRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Items/ItemGroupRepository.cs @@ -1,4 +1,5 @@ -using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; using Microsoft.EntityFrameworkCore; namespace EgwCoreLib.Lux.Data.Repository.Items @@ -15,6 +16,47 @@ namespace EgwCoreLib.Lux.Data.Repository.Items #region Public Methods + public async Task AddMissingAsync(List bomList) + { + await using var dbCtx = await CreateContextAsync(); + bool fatto = false; + // recupero lista esistenti + var itemGroupList = await dbCtx.DbSetItemGroup + .AsNoTracking() + .ToListAsync(); + + // calcolo i distinct dei CodGroup x eventuale insert preventivo + List distCodGroups = bomList + .Select(i => i.ClassCode) + .Distinct() + .Where(c => !string.IsNullOrWhiteSpace(c)) + .ToList(); + + // elenco da inserire... + var codGroupsToInsert = distCodGroups + .Where(x => !itemGroupList.Any(i => i.CodGroup == x)) + .Select(x => new ItemGroupModel() { CodGroup = x, Description = x }) + .ToList(); + // se ci sono inserisco! + if (codGroupsToInsert != null && codGroupsToInsert.Count > 0) + { + await dbCtx + .DbSetItemGroup + .AddRangeAsync(codGroupsToInsert); + + // e salvo + fatto = await dbCtx.SaveChangesAsync() > 0; + } + return fatto; + } + + public async Task AddRangeAsync(List entityList) + { + await using var dbCtx = await CreateContextAsync(); + await dbCtx.DbSetItemGroup.AddRangeAsync(entityList); + return await dbCtx.SaveChangesAsync() > 0; + } + public async Task> GetAllAsync() { await using var dbCtx = await CreateContextAsync(); @@ -25,4 +67,4 @@ namespace EgwCoreLib.Lux.Data.Repository.Items #endregion Public Methods } -} +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Items/ItemRepository.cs b/EgwCoreLib.Lux.Data/Repository/Items/ItemRepository.cs index 6a269e09..0077993a 100644 --- a/EgwCoreLib.Lux.Data/Repository/Items/ItemRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Items/ItemRepository.cs @@ -221,6 +221,8 @@ namespace EgwCoreLib.Lux.Data.Repository.Items { await using var dbCtx = await CreateContextAsync(); + // Spostato in ItemGroupRepository.AddMissingAsync +#if false // in primis calcolo i distinct dei CodGroup x eventuale insert preventivo List distCodGroups = bomList .Select(i => i.ClassCode) @@ -245,7 +247,8 @@ namespace EgwCoreLib.Lux.Data.Repository.Items .AddRange(codGroupsToInsert); // salvo... await dbCtx.SaveChangesAsync(); - } + } +#endif // prendo solo elementi a prezzo 0 da salvare sul DB var item2save = bomList diff --git a/EgwCoreLib.Lux.Data/Repository/Job/IPhaseRepository.cs b/EgwCoreLib.Lux.Data/Repository/Job/IPhaseRepository.cs new file mode 100644 index 00000000..fbd293a2 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Job/IPhaseRepository.cs @@ -0,0 +1,9 @@ +using EgwCoreLib.Lux.Data.DbModel.Job; + +namespace EgwCoreLib.Lux.Data.Repository.Job +{ + public interface IPhaseRepository + { + Task> GetAllAsync(); + } +} diff --git a/EgwCoreLib.Lux.Data/Repository/Job/PhaseRepository.cs b/EgwCoreLib.Lux.Data/Repository/Job/PhaseRepository.cs new file mode 100644 index 00000000..0ca63dbe --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Job/PhaseRepository.cs @@ -0,0 +1,28 @@ +using EgwCoreLib.Lux.Data.DbModel.Job; +using Microsoft.EntityFrameworkCore; + +namespace EgwCoreLib.Lux.Data.Repository.Job +{ + public class PhaseRepository : BaseRepository, IPhaseRepository + { + #region Public Constructors + + public PhaseRepository(IDbContextFactory ctxFactory) : base(ctxFactory) + { + } + + #endregion Public Constructors + + #region Public Methods + + public async Task> GetAllAsync() + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx.DbSetPhase + .AsNoTracking() + .ToListAsync(); + } + + #endregion Public Methods + } +} diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs index 6b0a2f2b..67c55576 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/IOfferRepository.cs @@ -9,6 +9,8 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales Task AddAsync(OfferModel entity); + Task CheckExpiredAsync(); + Task CloneAsync(OfferModel rec2clone); Task DeleteAsync(OfferModel entity); diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs index e36993a2..75647785 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OfferRepository.cs @@ -27,6 +27,34 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales return await dbCtx.SaveChangesAsync() > 0; } + public async Task CheckExpiredAsync() + { + await using var dbCtx = await CreateContextAsync(); + DateTime adesso = DateTime.Now; + // recupero offerta... + var listExpired = await dbCtx + .DbSetOffer + .Where(x => x.ValidUntil < adesso && x.OffertState == OfferStates.Open) + .ToListAsync(); + + // se trovo le aggiorno come stato + if (listExpired != null) + { + foreach (var item in listExpired) + { + item.OffertState = OfferStates.Expired; + dbCtx.Entry(item).State = EntityState.Modified; + } + + // salvo TUTTI i cambiamenti... + return await dbCtx.SaveChangesAsync() > 0; + } + else + { + return false; + } + } + /// /// Esegue il cloning completo di un Offerta e di TUTTE le relative righe... /// @@ -203,10 +231,10 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales 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/Stats/IStatsAggrRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/IStatsAggrRepository.cs new file mode 100644 index 00000000..3107632e --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Stats/IStatsAggrRepository.cs @@ -0,0 +1,18 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; + +namespace EgwCoreLib.Lux.Data.Repository.Stats +{ + public interface IStatsAggrRepository + { + #region Public Methods + + Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd); + + Task GetRangeAsync(); + + Task UpsertManyAsync(List listRecords, bool removeOld); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Stats/IStatsDetailRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/IStatsDetailRepository.cs new file mode 100644 index 00000000..c311469c --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Stats/IStatsDetailRepository.cs @@ -0,0 +1,18 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; + +namespace EgwCoreLib.Lux.Data.Repository.Stats +{ + public interface IStatsDetailRepository + { + #region Public Methods + + Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = ""); + + Task GetRangeAsync(string sEnvir, string sType); + + Task UpsertAsync(List listRecords, bool removeOld); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs new file mode 100644 index 00000000..f980efe4 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Stats/StatsAggrRepository.cs @@ -0,0 +1,108 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; +using Microsoft.EntityFrameworkCore; + +namespace EgwCoreLib.Lux.Data.Repository.Stats +{ + public class StatsAggrRepository : BaseRepository, IStatsAggrRepository + { + #region Public Constructors + + public StatsAggrRepository(IDbContextFactory ctxFactory) : base(ctxFactory) + { + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Elenco da DB delel stats aggregate dato periodo inizio/fine + /// + /// + /// + /// + public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd) + { + await using var dbCtx = await CreateContextAsync(); + return await dbCtx + .DbSetStatsAggr + .Where(x => x.Hour >= dtStart && x.Hour <= dtEnd) + .AsNoTracking() + .OrderBy(x => x.Hour) + .ToListAsync(); + } + + /// + /// Range periodo per chiamate aggregate + /// + /// + public async Task GetRangeAsync() + { + await using var dbCtx = await CreateContextAsync(); + DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today); + 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; + // ritorno! + return answ; + } + + /// + /// Esegue insert statistiche aggregate sul DB + /// + /// Elenco dei record da inserire + /// Se true preventivamente elimina record nel periodo richiesto + /// + public async Task UpsertManyAsync(List listRecords, bool removeOld) + { + int answ = 0; + await using var dbCtx = await CreateContextAsync(); + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // 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(); + + // commit transazione + tx.Commit(); + + // libero memoria del changeTracker + dbCtx.ChangeTracker.Clear(); + return answ; + } + catch + { + tx.Rollback(); + throw; + } + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs b/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs new file mode 100644 index 00000000..10d1a8db --- /dev/null +++ b/EgwCoreLib.Lux.Data/Repository/Stats/StatsDetailRepository.cs @@ -0,0 +1,132 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; +using Microsoft.EntityFrameworkCore; + +namespace EgwCoreLib.Lux.Data.Repository.Stats +{ + public class StatsDetailRepository : BaseRepository, IStatsDetailRepository + { + #region Public Constructors + + public StatsDetailRepository(IDbContextFactory ctxFactory) : base(ctxFactory) + { + } + + #endregion Public Constructors + + #region Internal Methods + + /// + /// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo + /// + /// + /// + /// + /// + /// + public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "") + { + await using var dbCtx = await CreateContextAsync(); + List answ = new List(); + + // 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 + /// + /// + /// + /// + public async Task GetRangeAsync(string sEnvir, string sType) + { + await using var dbCtx = await CreateContextAsync(); + DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today); + + 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 + /// + public async Task UpsertAsync(List listRecords, bool removeOld) + { + int answ = 0; + await using var dbCtx = await CreateContextAsync(); + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // 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(); + // commit transazione + tx.Commit(); + + // libero memoria del changeTracker + dbCtx.ChangeTracker.Clear(); + return answ; + } + catch + { + tx.Rollback(); + throw; + } + } + + #endregion Internal Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs index 9113d8a8..1146310e 100644 --- a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs +++ b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs @@ -1,10 +1,10 @@ using EgwCoreLib.Lux.Core.Generic; using EgwCoreLib.Lux.Core.RestPayload; using EgwCoreLib.Lux.Data.Controllers; -using EgwCoreLib.Lux.Data.DbModel.Job; using EgwCoreLib.Lux.Data.DbModel.Production; using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.Services.Config; +using EgwCoreLib.Lux.Data.Services.Items; using EgwCoreLib.Lux.Data.Services.Production; using EgwCoreLib.Lux.Data.Services.Sales; using EgwMultiEngineManager.Data; @@ -16,7 +16,6 @@ using StackExchange.Redis; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; -using static EgwCoreLib.Lux.Core.Enums; namespace EgwCoreLib.Lux.Data.Services { @@ -54,6 +53,8 @@ namespace EgwCoreLib.Lux.Data.Services #region Public Properties public IConfProfileService ConfProfServ => _serviceProvider.GetRequiredService(); + public IItemGroupService ItmGrService => _serviceProvider.GetRequiredService(); + public IItemService ItmService => _serviceProvider.GetRequiredService(); public IOfferRowService OfferRowServ => _serviceProvider.GetRequiredService(); public IOrderRowService OrderRowServ => _serviceProvider.GetRequiredService(); public IProductionGroupService PrGrpServ => _serviceProvider.GetRequiredService(); @@ -63,46 +64,6 @@ namespace EgwCoreLib.Lux.Data.Services #region Public Methods - /// - /// Sistema gli item che mancassero di ItemID leggendo da DB - /// - /// - /// - public List BomFixItemId(List listOrg) - { - using var activity = StartActivity(); - string source = "DB"; - List listFix = listOrg; - // sistemo ItemID per gli item della BOM... - var listItemDB = dbController.ItemGetFilt("", ItemClassType.Bom); - // cerco i record da sistemare ed 1:1 li fixo... - foreach (var item in listFix.Where(x => x.ItemID == 0)) - { - var dbRec = listItemDB.FirstOrDefault(x => x.ExtItemCode == item.ItemCode); - if ((dbRec != null)) - { - item.ItemID = dbRec.ItemID; - } - } - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return listFix; - } - - /// - /// Reset completo cache sistema - /// - public bool FlushCache() - { - using var activity = StartActivity(); - string source = "REDIS"; - bool answ = false; - RedisValue pattern = new RedisValue($"{redisBaseKey}:*"); - answ = ExecFlushRedisPattern(pattern); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return answ; - } /// /// Reset completo cache sistema modalità async @@ -119,53 +80,6 @@ namespace EgwCoreLib.Lux.Data.Services return answ; } - /// - /// Reset cache sistema x Offerte modalità async - /// - public async Task FlushCacheOffersAsync() - { - using var activity = StartActivity(); - string source = "REDIS"; - bool answ = false; - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return answ; - } - - /// - /// Reset cache sistema x Ordini modalità async - /// - public async Task FlushCacheOrdersAsync() - { - using var activity = StartActivity(); - string source = "REDIS"; - bool answ = false; - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Orders:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRows:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return answ; - } - - /// - /// Verifica offerte scadute, con update sul DB - /// - public async Task OffersCheckExpired() - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - // calcolo - bool fatto = await dbController.OffersCheckExpired(); - // svuoto cache... - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Offers:*"); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OfferRows:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return fatto; - } - /// /// Converte il campo raw della BOM in lista oggetti da gestire /// @@ -182,39 +96,6 @@ namespace EgwCoreLib.Lux.Data.Services return answ; } - /// - /// Elenco completo Fasi - /// - /// - public async Task> PhasesGetAllAsync() - { - using var activity = StartActivity(); - string source = "DB"; - List? result = new List(); - // cerco in redis... - string currKey = $"{redisBaseKey}:Phases:ALL"; - RedisValue rawData = await _redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - source = "REDIS"; - } - else - { - result = await dbController.PhasesGetAllAsync(); - // serializzo e salvo con config x evitare loop... - rawData = JsonConvert.SerializeObject(result, JSSettings); - await _redisDb.StringSetAsync(currKey, rawData, LongCache); - } - if (result == null) - { - result = new List(); - } - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return result; - } - /// /// Restituisce un dizionario di da usare nel planner /// @@ -295,7 +176,9 @@ namespace EgwCoreLib.Lux.Data.Services if (bomList != null) { // verifico 1:1 gli item ricevuti dalla BOM sul DB con eventuale insert in anagrafica dei nuovi - dbController.ItemUpsertFromBom(bomList); + await ItmGrService.AddMissingAsync(bomList); + await ItmService.UpsertFromBomAsync(bomList); + // se mode = 2 è offerta, se 5 = ODL... switch (qMode) { diff --git a/EgwCoreLib.Lux.Data/Services/Items/IItemGroupService.cs b/EgwCoreLib.Lux.Data/Services/Items/IItemGroupService.cs index d2c3daeb..19ccdfa8 100644 --- a/EgwCoreLib.Lux.Data/Services/Items/IItemGroupService.cs +++ b/EgwCoreLib.Lux.Data/Services/Items/IItemGroupService.cs @@ -1,9 +1,13 @@ -using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; namespace EgwCoreLib.Lux.Data.Services.Items { public interface IItemGroupService { + Task AddMissingAsync(List bomList); + Task> GetAllAsync(); + } } diff --git a/EgwCoreLib.Lux.Data/Services/Items/IItemService.cs b/EgwCoreLib.Lux.Data/Services/Items/IItemService.cs index 34ddfa64..ce9ea677 100644 --- a/EgwCoreLib.Lux.Data/Services/Items/IItemService.cs +++ b/EgwCoreLib.Lux.Data/Services/Items/IItemService.cs @@ -12,6 +12,8 @@ namespace EgwCoreLib.Lux.Data.Services.Items Task> GetAltAsync(int recId); + Task> GetBomFixItemIdAsync(List listOrg); + Task> GetFiltAsync(string CodGroup, ItemClassType ItemType); Task> GetSearchAsync(string term); @@ -20,8 +22,8 @@ namespace EgwCoreLib.Lux.Data.Services.Items Task UpsertAsync(ItemModel upsRec); - Task UpsertFromBom(List bomList); + Task UpsertFromBomAsync(List bomList); #endregion Public Methods } -} +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Items/ItemGroupService.cs b/EgwCoreLib.Lux.Data/Services/Items/ItemGroupService.cs index 27809f3c..565557a0 100644 --- a/EgwCoreLib.Lux.Data/Services/Items/ItemGroupService.cs +++ b/EgwCoreLib.Lux.Data/Services/Items/ItemGroupService.cs @@ -1,4 +1,5 @@ -using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; using EgwCoreLib.Lux.Data.Repository.Items; using Microsoft.Extensions.Configuration; using StackExchange.Redis; @@ -22,6 +23,24 @@ namespace EgwCoreLib.Lux.Data.Services.Items #region Public Methods + public async Task AddMissingAsync(List bomList) + { + return await TraceAsync($"{_className}.AddMissing", async (activity) => + { + string operation = "AddMissing"; + bool success = await _repo.AddMissingAsync(bomList); + + activity?.SetTag("db.operation", operation); + + if (success) + { + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + } + + return success; + }); + } + public async Task> GetAllAsync() { return await TraceAsync($"{_className}.GetAll", async (activity) => diff --git a/EgwCoreLib.Lux.Data/Services/Items/ItemService.cs b/EgwCoreLib.Lux.Data/Services/Items/ItemService.cs index 0e4540c1..8cabdb39 100644 --- a/EgwCoreLib.Lux.Data/Services/Items/ItemService.cs +++ b/EgwCoreLib.Lux.Data/Services/Items/ItemService.cs @@ -55,9 +55,28 @@ namespace EgwCoreLib.Lux.Data.Services.Items }); } + public async Task> GetBomFixItemIdAsync(List listOrg) + { + return await TraceAsync($"{_className}.GetBomFixItemIdAsync", async (activity) => + { + List listFix = listOrg; + // sistemo ItemID per gli item della BOM... + var listItemDB = await _repo.GetFiltAsync("", ItemClassType.Bom); + // cerco i record da sistemare ed 1:1 li fixo... + foreach (var item in listFix.Where(x => x.ItemID == 0)) + { + var dbRec = listItemDB.FirstOrDefault(x => x.ExtItemCode == item.ItemCode); + if ((dbRec != null)) + { + item.ItemID = dbRec.ItemID; + } + } + return listFix; + }); + } + public async Task> GetFiltAsync(string CodGroup, ItemClassType ItemType) { - // Uso helper TraceAsync che gestisce automaticamente StartActivity, Log e Exception tracking return await TraceAsync($"{_className}.GetFilt", async (activity) => { return await GetOrSetCacheAsync( @@ -70,7 +89,6 @@ namespace EgwCoreLib.Lux.Data.Services.Items public async Task> GetSearchAsync(string term) { - // Uso helper TraceAsync che gestisce automaticamente StartActivity, Log e Exception tracking return await TraceAsync($"{_className}.GetSearch", async (activity) => { return await GetOrSetCacheAsync( @@ -131,13 +149,13 @@ namespace EgwCoreLib.Lux.Data.Services.Items } - public async Task UpsertFromBom(List bomList) + public async Task UpsertFromBomAsync(List bomList) { - return await TraceAsync($"{_className}.UpsertFromBom", async (activity) => + return await TraceAsync($"{_className}.UpsertFromBomAsync", async (activity) => { bool success = await _repo.UpsertFromBomAsync(bomList); - activity?.SetTag("db.operation", "UpsertFromBom"); + activity?.SetTag("db.operation", "UpsertFromBomAsync"); if (success) { diff --git a/EgwCoreLib.Lux.Data/Services/Job/IPhaseService.cs b/EgwCoreLib.Lux.Data/Services/Job/IPhaseService.cs new file mode 100644 index 00000000..781b6d88 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Job/IPhaseService.cs @@ -0,0 +1,9 @@ +using EgwCoreLib.Lux.Data.DbModel.Job; + +namespace EgwCoreLib.Lux.Data.Services.Job +{ + public interface IPhaseService + { + Task> GetAllAsync(); + } +} diff --git a/EgwCoreLib.Lux.Data/Services/Job/PhaseService.cs b/EgwCoreLib.Lux.Data/Services/Job/PhaseService.cs new file mode 100644 index 00000000..7eca4b54 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Job/PhaseService.cs @@ -0,0 +1,50 @@ +using EgwCoreLib.Lux.Data.DbModel.Job; +using EgwCoreLib.Lux.Data.Repository.Job; +using Microsoft.Extensions.Configuration; +using StackExchange.Redis; + +namespace EgwCoreLib.Lux.Data.Services.Job +{ + public class PhaseService : BaseServ, IPhaseService + { + #region Public Constructors + + public PhaseService( + IConfiguration config, + IConnectionMultiplexer redis, + IPhaseRepository repo) : base(config, redis) + { + _className = "Phase"; + _repo = repo; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Elenco completo Phase da DB + /// + /// + public async Task> GetAllAsync() + { + return await TraceAsync($"{_className}.GetAll", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:ALL", + async () => await _repo.GetAllAsync(), + UltraLongCache + ); + }); + } + + #endregion Public Methods + + #region Private Fields + + private readonly string _className; + private readonly IPhaseRepository _repo; + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs b/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs index 967f7235..06d7db90 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/IOfferService.cs @@ -6,8 +6,12 @@ namespace EgwCoreLib.Lux.Data.Services.Sales { #region Public Methods + Task CheckExpiredAsync(); + Task CloneAsync(OfferModel rec2clone); + Task FlushCacheOffersAsync(); + Task> GetAllAsync(); Task> GetFiltAsync(DateTime inizio, DateTime fine); diff --git a/EgwCoreLib.Lux.Data/Services/Sales/IOrderService.cs b/EgwCoreLib.Lux.Data/Services/Sales/IOrderService.cs index 19f57c9b..fca1b12f 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/IOrderService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/IOrderService.cs @@ -8,6 +8,8 @@ namespace EgwCoreLib.Lux.Data.Services.Sales Task CloneOfferAsync(OfferModel rec2clone); + Task FlushCacheOrdersAsync(); + Task> GetFiltAsync(DateTime inizio, DateTime fine); Task GetByIdAsync(int recId, bool doForce); diff --git a/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs b/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs index 3bda19f3..d2648439 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/OfferService.cs @@ -25,6 +25,24 @@ namespace EgwCoreLib.Lux.Data.Services.Sales #region Public Methods + /// + /// Verifica offerte scadute, con update sul DB + /// + public async Task CheckExpiredAsync() + { + return await TraceAsync($"{_className}.CheckExpired", async (activity) => + { + // eseguo clone + var result = await _repo.CheckExpiredAsync(); + + // se eseguito, pulisco la cache correlata + if (result) + { + await FlushCacheOffersAsync(); + } + return result; + }); + } /// /// Esegue il cloning completo di un Offer e di TUTTE le relative righe... /// @@ -40,14 +58,30 @@ namespace EgwCoreLib.Lux.Data.Services.Sales // se eseguito, pulisco la cache correlata if (result) { - // Invalido sia la lista classi che eventuali dettagli correlati - await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); - await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + await FlushCacheOffersAsync(); } return result; }); } + /// + /// Reset cache sistema x Offerte modalità async + /// + public async Task FlushCacheOffersAsync() + { + return await TraceAsync($"{_className}.FlushCache", async (activity) => + { + string operation = "FlushCache"; + activity?.SetTag("db.operation", operation); + + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + //await ClearCacheAsync($"{_redisBaseKey}:Offers:*"); + + return true; + }); + } + /// /// Elenco completo Offer da DB /// @@ -138,8 +172,7 @@ namespace EgwCoreLib.Lux.Data.Services.Sales if (result) { // 4. Invalido cache - await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); - await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + await FlushCacheOffersAsync(); } return result; @@ -173,8 +206,7 @@ namespace EgwCoreLib.Lux.Data.Services.Sales if (success) { - await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); - await ClearCacheAsync($"{_redisBaseKey}:OfferRows:*"); + await FlushCacheOffersAsync(); } return success; diff --git a/EgwCoreLib.Lux.Data/Services/Sales/OrderService.cs b/EgwCoreLib.Lux.Data/Services/Sales/OrderService.cs index e7c68131..a5324715 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/OrderService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/OrderService.cs @@ -51,6 +51,25 @@ namespace EgwCoreLib.Lux.Data.Services.Sales }); } + /// + /// Reset cache sistema x Ordini modalità async + /// + public async Task FlushCacheOrdersAsync() + { + return await TraceAsync($"{_className}.FlushCache", async (activity) => + { + string operation = "FlushCache"; + activity?.SetTag("db.operation", operation); + + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:OrderRows:*"); + await ClearCacheAsync($"{_redisBaseKey}:OrderRowsByState:*"); + //await ClearCacheAsync($"{_redisBaseKey}:Orders:*"); + + return true; + }); + } + public async Task GetByIdAsync(int recId, bool doForce) { // Uso helper TraceAsync che gestisce automaticamente StartActivity, Log e Exception tracking diff --git a/EgwCoreLib.Lux.Data/Services/Stats/IStatsAggrService.cs b/EgwCoreLib.Lux.Data/Services/Stats/IStatsAggrService.cs new file mode 100644 index 00000000..dad0ab78 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Stats/IStatsAggrService.cs @@ -0,0 +1,18 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; + +namespace EgwCoreLib.Lux.Data.Services.Stats +{ + public interface IStatsAggrService + { + #region Public Methods + + Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd); + + Task GetRangeAsync(); + + Task UpsertManyAsync(List listRecords, bool removeOld); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Stats/IStatsDetailService.cs b/EgwCoreLib.Lux.Data/Services/Stats/IStatsDetailService.cs new file mode 100644 index 00000000..ee99f7a1 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Stats/IStatsDetailService.cs @@ -0,0 +1,18 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Utils; + +namespace EgwCoreLib.Lux.Data.Services.Stats +{ + public interface IStatsDetailService + { + #region Public Methods + + Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = ""); + + Task GetRangeAsync(string sEnvir, string sType); + + Task UpsertAsync(List listRecords, bool removeOld); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Stats/StatsAggrService.cs b/EgwCoreLib.Lux.Data/Services/Stats/StatsAggrService.cs new file mode 100644 index 00000000..f153105a --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Stats/StatsAggrService.cs @@ -0,0 +1,93 @@ +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Lux.Data.Repository.Stats; +using EgwCoreLib.Utils; +using Microsoft.Extensions.Configuration; +using StackExchange.Redis; + +namespace EgwCoreLib.Lux.Data.Services.Stats +{ + public class StatsAggrService : BaseServ, IStatsAggrService + { + #region Public Constructors + + public StatsAggrService( + IConfiguration config, + IConnectionMultiplexer redis, + IStatsAggrRepository repo) : base(config, redis) + { + _className = "StatsAggr"; + _repo = repo; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Elenco da DB delel stats aggregate dato periodo inizio/fine + /// + /// + /// + /// + public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd) + { + return await TraceAsync($"{_className}.GetFilt", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:DT:{dtStart:yyyyMMdd}:{dtEnd:yyyyMMdd}", + async () => await _repo.GetFiltAsync(dtStart, dtEnd), + UltraLongCache + ); + }); + } + + /// + /// Range periodo per chiamate aggregate + /// + /// + public async Task GetRangeAsync() + { + return await TraceAsync($"{_className}.GetRange", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:Range", + async () => await _repo.GetRangeAsync(), + UltraFastCache + ); + }); + } + + /// + /// Esegue insert statistiche aggregate sul DB + /// + /// Elenco dei record da inserire + /// Se true preventivamente elimina record nel periodo richiesto + /// + public async Task UpsertManyAsync(List listRecords, bool removeOld) + { + return await TraceAsync($"{_className}.UpsertMany", async (activity) => + { + string operation = "UpsertMany"; + var success = await _repo.UpsertManyAsync(listRecords, removeOld); + + activity?.SetTag("db.operation", operation); + + if (success > 0) + { + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + } + + return success; + }); + } + + #endregion Public Methods + + #region Private Fields + + private readonly string _className; + private readonly IStatsAggrRepository _repo; + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Stats/StatsDetailService.cs b/EgwCoreLib.Lux.Data/Services/Stats/StatsDetailService.cs new file mode 100644 index 00000000..77c3f470 --- /dev/null +++ b/EgwCoreLib.Lux.Data/Services/Stats/StatsDetailService.cs @@ -0,0 +1,169 @@ +using EgwCoreLib.Lux.Data.DbModel.Config; +using EgwCoreLib.Lux.Data.DbModel.Stats; +using EgwCoreLib.Lux.Data.Repository.Config; +using EgwCoreLib.Lux.Data.Repository.Stats; +using EgwCoreLib.Lux.Data.Services.Config; +using Microsoft.Extensions.Configuration; +using StackExchange.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EgwCoreLib.Lux.Data.Services.Stats +{ + public class StatsDetailService : BaseServ, IStatsDetailService + { + #region Public Constructors + + public StatsDetailService( + IConfiguration config, + IConnectionMultiplexer redis, + IStatsDetailRepository repo) : base(config, redis) + { + _className = "StatsDetail"; + _repo = repo; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Elenco completo StatsDetail da DB + /// + /// + public async Task> GetAllAsync() + { + return await TraceAsync($"{_className}.GetAll", async (activity) => + { + return await GetOrSetCacheAsync( + $"{_redisBaseKey}:{_className}:ALL", + async () => await _repo.GetAllAsync(), + UltraLongCache + ); + }); + } + + /// + /// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo + /// + /// + /// + /// + /// + /// + public async Task> GetFiltAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "") + { + await using var dbCtx = await CreateContextAsync(); + List answ = new List(); + + // 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 + /// + /// + /// + /// + public async Task GetRangeAsync(string sEnvir, string sType) + { + await using var dbCtx = await CreateContextAsync(); + DtUtils.Periodo answ = new DtUtils.Periodo(DtUtils.PeriodSet.Today); + + 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 + /// + public async Task UpsertAsync(List listRecords, bool removeOld) + { + int answ = 0; + await using var dbCtx = await CreateContextAsync(); + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // 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(); + // commit transazione + tx.Commit(); + + // libero memoria del changeTracker + dbCtx.ChangeTracker.Clear(); + return answ; + } + catch + { + tx.Rollback(); + throw; + } + } + + #endregion Public Methods + + #region Private Fields + + private readonly string _className; + private readonly IStatsDetailRepository _repo; + + #endregion Private Fields + } +} diff --git a/Lux.API/Lux.API.csproj b/Lux.API/Lux.API.csproj index 02fc2115..1c257d37 100644 --- a/Lux.API/Lux.API.csproj +++ b/Lux.API/Lux.API.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 1.1.2603.2010 + 1.1.2603.2019 diff --git a/Lux.UI/Components/Compo/OfferRowMan.razor.cs b/Lux.UI/Components/Compo/OfferRowMan.razor.cs index 5471e147..0f798c20 100644 --- a/Lux.UI/Components/Compo/OfferRowMan.razor.cs +++ b/Lux.UI/Components/Compo/OfferRowMan.razor.cs @@ -8,6 +8,7 @@ using EgwCoreLib.Lux.Data.DbModel.Utils; using EgwCoreLib.Lux.Data.Domains; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; +using EgwCoreLib.Lux.Data.Services.Items; using EgwCoreLib.Lux.Data.Services.Sales; using EgwCoreLib.Lux.Data.Services.Utils; using Microsoft.AspNetCore.Components; @@ -278,9 +279,15 @@ namespace Lux.UI.Components.Compo [Inject] private ConfigDataService CDService { get; set; } = default!; + [Inject] + private IConfGlassService CGService { get; set; } = null!; + [Inject] private IConfiguration Config { get; set; } = default!; + [Inject] + private IConfProfileService CPService { get; set; } = null!; + [Inject] private CalcRuidService CRService { get; set; } = default!; @@ -290,33 +297,15 @@ namespace Lux.UI.Components.Compo [Inject] private IWebHostEnvironment CurrEnv { get; set; } = default!; - [Inject] - private IConfGlassService CGService { get; set; } = null!; - - [Inject] - private IConfProfileService CPService { get; set; } = null!; - [Inject] private IConfWoodService CWService { get; set; } = null!; [Inject] private DataLayerServices DLService { get; set; } = default!; - [Inject] - private IOfferRowService ORService { get; set; } = default!; - [Inject] private IEnvirParamService EPService { get; set; } = null!; - [Inject] - private IOfferService OService { get; set; } = default!; - - [Inject] - private ITemplateService TService { get; set; } = default!; - - [Inject] - private ITemplateRowService TRService { get; set; } = default!; - [Inject] private FileService FService { get; set; } = default!; @@ -390,6 +379,9 @@ namespace Lux.UI.Components.Compo [Inject] private ImageCacheService ICService { get; set; } = default!; + [Inject] + private IItemService ItemService { get; set; } = null!; + [Inject] private IJSRuntime JSRuntime { get; set; } = default!; @@ -401,6 +393,18 @@ namespace Lux.UI.Components.Compo get => CurrRecord.OfferID; } + [Inject] + private IOfferRowService ORService { get; set; } = default!; + + [Inject] + private IOfferService OService { get; set; } = default!; + + [Inject] + private ITemplateRowService TRService { get; set; } = default!; + + [Inject] + private ITemplateService TService { get; set; } = default!; + #endregion Private Properties #region Private Methods @@ -759,7 +763,7 @@ namespace Lux.UI.Components.Compo CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); - await DLService.FlushCacheOffersAsync(); + await OService.FlushCacheOffersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); @@ -929,7 +933,7 @@ namespace Lux.UI.Components.Compo // reset CurrEditMode = EditMode.None; EditRecord = null; - await DLService.FlushCacheOffersAsync(); + await OService.FlushCacheOffersAsync(); await ReloadData(); UpdateTable(); isLoading = false; @@ -957,20 +961,20 @@ namespace Lux.UI.Components.Compo /// Imposta modalita edit ciclo di lavoro /// /// - private void DoSwapJobCycle(OfferRowModel currRow) + private Task DoSwapJobCycle(OfferRowModel currRow) { CurrEditMode = EditMode.JobCycle; - selectBom(currRow); + return selectBom(currRow); } /// /// Imposta modalita ad edit BOM /// /// - private void DoSwapMat(OfferRowModel currRow) + private Task DoSwapMat(OfferRowModel currRow) { CurrEditMode = EditMode.BOM; - selectBom(currRow); + return selectBom(currRow); } /// @@ -1044,7 +1048,7 @@ namespace Lux.UI.Components.Compo CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); - await DLService.FlushCacheOffersAsync(); + await OService.FlushCacheOffersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); @@ -1652,14 +1656,14 @@ namespace Lux.UI.Components.Compo /// /// /// - private void selectBom(OfferRowModel currRow) + private async Task selectBom(OfferRowModel currRow) { EditRecord = currRow; //CurrBomList = DLService.OffertGetBomList(EditRecord); CurrBomList = BomCalculator.GetBomList(EditRecord.ItemBOM); if (CurrBomList.Any(x => x.ItemID == 0)) { - CurrBomList = DLService.BomFixItemId(CurrBomList); + CurrBomList = await ItemService.GetBomFixItemIdAsync(CurrBomList); } } @@ -1746,7 +1750,7 @@ namespace Lux.UI.Components.Compo { CurrBomList = new List(); // fa refresh dei dati della BOM visualizzata - selectBom(updRec); + await selectBom(updRec); await InvokeAsync(StateHasChanged); } } diff --git a/Lux.UI/Components/Compo/OrderRowMan.razor.cs b/Lux.UI/Components/Compo/OrderRowMan.razor.cs index 36bb04a5..74db479f 100644 --- a/Lux.UI/Components/Compo/OrderRowMan.razor.cs +++ b/Lux.UI/Components/Compo/OrderRowMan.razor.cs @@ -8,6 +8,7 @@ using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.DbModel.Utils; using EgwCoreLib.Lux.Data.Services; using EgwCoreLib.Lux.Data.Services.Config; +using EgwCoreLib.Lux.Data.Services.Items; using EgwCoreLib.Lux.Data.Services.Production; using EgwCoreLib.Lux.Data.Services.Sales; using EgwCoreLib.Lux.Data.Services.Utils; @@ -367,6 +368,9 @@ namespace Lux.UI.Components.Compo [Inject] private IEnvirParamService EPService { get; set; } = null!; + [Inject] + private IItemService ItemService { get; set; } = null!; + [Inject] private IProductionItemService PIService { get; set; } = null!; @@ -699,7 +703,7 @@ namespace Lux.UI.Components.Compo CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); - await DLService.FlushCacheOffersAsync(); + await OrdService.FlushCacheOrdersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); @@ -863,7 +867,7 @@ namespace Lux.UI.Components.Compo // reset CurrEditMode = EditMode.None; EditRecord = null; - await DLService.FlushCacheOrdersAsync(); + await OrdService.FlushCacheOrdersAsync(); await ReloadData(); UpdateTable(); isLoading = false; @@ -885,20 +889,20 @@ namespace Lux.UI.Components.Compo /// Imposta modalita edit ciclo di lavoro /// /// - private void DoSwapJobCycle(OrderRowModel currRow) + private Task DoSwapJobCycle(OrderRowModel currRow) { CurrEditMode = EditMode.JobCycle; - selectBom(currRow); + return selectBom(currRow); } /// /// Imposta modalita ad edit BOM /// /// - private void DoSwapMat(OrderRowModel currRow) + private Task DoSwapMat(OrderRowModel currRow) { CurrEditMode = EditMode.BOM; - selectBom(currRow); + return selectBom(currRow); } /// @@ -975,7 +979,7 @@ namespace Lux.UI.Components.Compo CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); - await DLService.FlushCacheOffersAsync(); + await OrdService.FlushCacheOrdersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); @@ -1716,13 +1720,13 @@ namespace Lux.UI.Components.Compo /// /// /// - private void selectBom(OrderRowModel currRow) + private async Task selectBom(OrderRowModel currRow) { EditRecord = currRow; CurrBomList = DLService.OrderGetBomList(EditRecord); if (CurrBomList.Any(x => x.ItemID == 0)) { - CurrBomList = DLService.BomFixItemId(CurrBomList); + CurrBomList = await ItemService.GetBomFixItemIdAsync(CurrBomList); } } @@ -1834,7 +1838,7 @@ namespace Lux.UI.Components.Compo { CurrBomList = new List(); // fa refresh dei dati della BOM visualizzata - selectBom(updRec); + await selectBom(updRec); await InvokeAsync(StateHasChanged); } } diff --git a/Lux.UI/Components/Pages/JobRoute.razor.cs b/Lux.UI/Components/Pages/JobRoute.razor.cs index e632b9b6..fa309818 100644 --- a/Lux.UI/Components/Pages/JobRoute.razor.cs +++ b/Lux.UI/Components/Pages/JobRoute.razor.cs @@ -60,6 +60,9 @@ namespace Lux.UI.Components.Pages get => selRecord == null ? "col-6" : "col-4"; } + [Inject] + private IPhaseService PhService { get; set; } = null!; + [Inject] private IResourceService ResService { get; set; } = null!; @@ -83,7 +86,7 @@ namespace Lux.UI.Components.Pages var rawTags = await TagService.GetAllAsync(); ListTagsAvailable = rawTags.Select(x => x.CodTag).ToList(); ListCostDrivers = await CDService.GetAllAsync(); - ListPhases = await DLService.PhasesGetAllAsync(); + ListPhases = await PhService.GetAllAsync(); ListResources = await ResService.GetAllAsync(); ListJobTask = await JTaService.GetAllAsync(); ListStep = null; diff --git a/Lux.UI/Components/Pages/Offers.razor.cs b/Lux.UI/Components/Pages/Offers.razor.cs index c4ac54e3..5f890110 100644 --- a/Lux.UI/Components/Pages/Offers.razor.cs +++ b/Lux.UI/Components/Pages/Offers.razor.cs @@ -453,7 +453,7 @@ namespace Lux.UI.Components.Pages /// private async Task ReloadData() { - await DLService.OffersCheckExpired(); + await OffService.CheckExpiredAsync(); AllRecords = await OffService.GetFiltAsync(PeriodoSel.Inizio, PeriodoSel.Fine); DoFilter(); } diff --git a/Lux.UI/Components/Pages/Orders.razor.cs b/Lux.UI/Components/Pages/Orders.razor.cs index 868f9ebd..9cf22531 100644 --- a/Lux.UI/Components/Pages/Orders.razor.cs +++ b/Lux.UI/Components/Pages/Orders.razor.cs @@ -531,7 +531,6 @@ namespace Lux.UI.Components.Pages /// private async Task ReloadData() { - await DLService.OffersCheckExpired(); AllRecords = await OrdService.GetFiltAsync(PeriodoSel.Inizio, PeriodoSel.Fine); DoFilter(); } diff --git a/Lux.UI/Lux.UI.csproj b/Lux.UI/Lux.UI.csproj index 979ac68a..b83502f6 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.2010 + 1.1.2603.2019 diff --git a/Resources/ChangeLog.html b/Resources/ChangeLog.html index 98101281..93fe476a 100644 --- a/Resources/ChangeLog.html +++ b/Resources/ChangeLog.html @@ -1,6 +1,6 @@ LUX - Web Windows MES -

Versione: 1.1.2603.2010

+

Versione: 1.1.2603.2019


Note di rilascio:
  • diff --git a/Resources/VersNum.txt b/Resources/VersNum.txt index af7c0f8c..fdaf61d6 100644 --- a/Resources/VersNum.txt +++ b/Resources/VersNum.txt @@ -1 +1 @@ -1.1.2603.2010 +1.1.2603.2019 diff --git a/Resources/manifest.xml b/Resources/manifest.xml index 995f6add..8ace2160 100644 --- a/Resources/manifest.xml +++ b/Resources/manifest.xml @@ -1,6 +1,6 @@ - 1.1.2603.2010 + 1.1.2603.2019 http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html false