From 07eacd022e715bf7681b2d596bae347be6c7e709 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Thu, 19 Mar 2026 16:42:19 +0100 Subject: [PATCH] Spostamento altri metodi... --- .../Controllers/LuxController.cs | 118 +--------- .../Repository/Sales/IOrderRowRepository.cs | 4 + .../Repository/Sales/OrderRowRepository.cs | 221 +++++++++++++++++- .../Services/DataLayerServices.cs | 80 +++---- .../Services/Sales/IOrderRowService.cs | 4 + .../Services/Sales/OrderRowService.cs | 45 ++++ Lux.UI/Components/Compo/OrderRowMan.razor.cs | 2 +- 7 files changed, 311 insertions(+), 163 deletions(-) diff --git a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs index fdb8f0b3..40a70368 100644 --- a/EgwCoreLib.Lux.Data/Controllers/LuxController.cs +++ b/EgwCoreLib.Lux.Data/Controllers/LuxController.cs @@ -4,7 +4,6 @@ using EgwCoreLib.Lux.Data.DbModel.Config; using EgwCoreLib.Lux.Data.DbModel.Items; using EgwCoreLib.Lux.Data.DbModel.Job; using EgwCoreLib.Lux.Data.DbModel.Production; -using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.DbModel.Stats; using EgwCoreLib.Lux.Data.Domains; using EgwMultiEngineManager.Data; @@ -228,6 +227,8 @@ namespace EgwCoreLib.Lux.Data.Controllers /// /// /// +#if false + internal async Task OrderRowUpsertProdEst(string uID, string prodEstim) { bool answ = false; @@ -293,7 +294,7 @@ namespace EgwCoreLib.Lux.Data.Controllers * Generazione ProdGroup * FixMe ToDo !!! * - * rifare onsiderando le REALI combinazioni scaturite x questo specifico caso e + * rifare considerando le REALI combinazioni scaturite x questo specifico caso e * - ENUMERARE le combinazioni * - ogni combinazione sarà un caso specifico tra 0...N dove N è il totale delle macchine gestite * - i successivi calcoli di balance/stima saranno fatti x questo SPECIFICO ID GROUP così da fare prima... a sto punto GroupIP potrebbe essere un int 0...n oppure l'id del record... forse meglio il counter 0..n @@ -417,6 +418,7 @@ namespace EgwCoreLib.Lux.Data.Controllers } return numDone; } +#endif /// /// Elenco record Fasi da DB @@ -602,118 +604,6 @@ namespace EgwCoreLib.Lux.Data.Controllers } #endif -#if false - /// - /// Aggiorna record ProdOdl (se trovato) con BOM (raw) ricevuta - /// - /// - /// - /// - internal async Task ProdOdlUpdateBomAsync(string uID, string bomRaw) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - var currRec = dbCtx - .DbSetProdODL - .Where(x => x.OdlTag == uID) - .FirstOrDefault(); - // se trovato --> salvo BOM e calcolo costi - if (currRec != null) - { - currRec.RawBoM = bomRaw; - dbCtx.Entry(currRec).State = EntityState.Modified; - } - - // salvo... - var result = dbCtx.SaveChanges(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante ProdOdlUpdateBomAsync{Environment.NewLine}{exc}"); - } - } - return answ; - } - - /// - /// Aggiorna record ProdOdl (se trovato) con ItemListRaw (raw) inviata x calcolo PROD - /// - /// - /// - /// - internal async Task ProdOdlUpdateItemRawAsync(string uID, string itemListRaw) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - var currRec = dbCtx - .DbSetProdODL - .Where(x => x.OdlTag == uID) - .FirstOrDefault(); - // se trovato --> salvo BOM e calcolo costi - if (currRec != null) - { - currRec.RawItemRawList = itemListRaw; - dbCtx.Entry(currRec).State = EntityState.Modified; - } - - // salvo... - var result = dbCtx.SaveChanges(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante ProdOdlUpdateItemRawAsync{Environment.NewLine}{exc}"); - } - } - return answ; - } - - /// - /// Aggiorna record ProdOdl (se trovato) con RawMaterialList (raw) ricevuta da calcolo PROD - /// - /// - /// - /// - internal async Task ProdOdlUpdateRawMaterialAsync(string uID, string materialListRaw) - { - bool answ = false; - //using (DataLayerContext dbCtx = new DataLayerContext(_config)) - using (DataLayerContext dbCtx = new DataLayerContext()) - { - try - { - var currRec = dbCtx - .DbSetProdODL - .Where(x => x.OdlTag == uID) - .FirstOrDefault(); - // se trovato --> salvo BOM e calcolo costi - if (currRec != null) - { - currRec.RawMaterials = materialListRaw; - dbCtx.Entry(currRec).State = EntityState.Modified; - } - - // salvo... - var result = dbCtx.SaveChanges(); - answ = result > 0; - } - catch (Exception exc) - { - Log.Error($"Eccezione durante ProdOdlUpdateRawMaterialAsync{Environment.NewLine}{exc}"); - } - } - return answ; - } -#endif /// /// Esegue merge dei dati nella tab profili del DB con le info accessorie... diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRowRepository.cs index a5fe46c0..141c7c0b 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/IOrderRowRepository.cs @@ -26,10 +26,14 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales Task> GetItemGroupsAsync(); + Task SaveProdEstAsync(string uID, string prodEstim); + Task SaveRowsAsync(List rows); Task UpdateAsync(OrderRowModel entity); + Task ValidateAsync(List list2chk); + #endregion Public Methods } } \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs index 9e668842..77b99722 100644 --- a/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs +++ b/EgwCoreLib.Lux.Data/Repository/Sales/OrderRowRepository.cs @@ -1,6 +1,10 @@ -using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Core.Generic; +using EgwCoreLib.Lux.Core.RestPayload; +using EgwCoreLib.Lux.Data.DbModel.Items; +using EgwCoreLib.Lux.Data.DbModel.Production; using EgwCoreLib.Lux.Data.DbModel.Sales; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using static EgwCoreLib.Lux.Core.Enums; namespace EgwCoreLib.Lux.Data.Repository.Sales @@ -171,6 +175,221 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales return await dbCtx.SaveChangesAsync() > 0; } + public async Task SaveProdEstAsync(string uID, string prodEstim) + { + // Add validation for null or empty list + if (string.IsNullOrWhiteSpace(uID) || string.IsNullOrWhiteSpace(prodEstim)) return false; + + await using var dbCtx = await CreateContextAsync(); + + // Wrap in transaction for atomicity (batch update multiple rows) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // recupero offerta... + var currRec = dbCtx + .DbSetOrderRow + .Where(x => x.OrderRowUID == uID) + .FirstOrDefault(); + + // se trovo aggiorno + if (currRec != null) + { + // genero WLD x controllo + var currWLD = new WorkLoadDetailDTO(currRec.OrderRowUID, currRec.ProdEstimate); + + // faccio update in cascata dei record collegati x macchine e items + var listMachDb = dbCtx + .DbSetProdPlant + .Select(x => x.ProdPlantCod) + .ToList(); + List listMaccCurr = currWLD.MachineCalcResults.Select(x => x.Name).OrderBy(x => x).ToList() ?? new List(); + if (listMaccCurr != null && listMaccCurr.Any()) + { + var listDiff = listMaccCurr.Except(listMachDb).ToList(); + if (listDiff.Any()) + { + foreach (var macch in listDiff) + { + dbCtx + .DbSetProdPlant + .Add(new ProductionPlantModel() { ProdPlantCod = macch, ProdPlantDescript = macch }); + } + } + } + // resetta assegnazioni prodgroup agli items... + List listItem2upd = await dbCtx + .DbSetProdItem + .Where(x => x.OrderRowID == currRec.OrderRowID && x.ProdGroupID != null) + .ToListAsync(); + if (listItem2upd != null && listItem2upd.Count > 0) + { + // li aggiorna tutti resettando ProdGroupID + listItem2upd.ForEach(x => x.ProdGroupID = null); + } + + // elimina eventuali oggetti ProductionGroup precedenti + List listProdGroup2Rem = await dbCtx + .DbSetProdGroup + .Where(x => x.OrderRowID == currRec.OrderRowID) + .ToListAsync(); + // rimuovo... + if (listProdGroup2Rem != null && listProdGroup2Rem.Count > 0) + { + dbCtx.DbSetProdGroup.RemoveRange(listProdGroup2Rem); + } + + /*---------------------------------- + * Generazione ProdGroup + * FixMe ToDo !!! + * + * rifare considerando le REALI combinazioni scaturite x questo specifico caso e + * - ENUMERARE le combinazioni + * - ogni combinazione sarà un caso specifico tra 0...N dove N è il totale delle macchine gestite + * - i successivi calcoli di balance/stima saranno fatti x questo SPECIFICO ID GROUP così da fare prima... a sto punto GroupIP potrebbe essere un int 0...n oppure l'id del record... forse meglio il counter 0..n + * + * */ + int grpIdx = 1; + // preparo x add nuovi ProductionGroup da analisi ritorno stime + List listProdGroup2Add = new List(); + // 1: non lavorabili... + if (currWLD.ListUnWorkable.Count > 0) + { + // calcolo il dizionario degli elementi... + Dictionary newWorkGroupList = new(); + ProdMachineDetailDto detProd = new ProdMachineDetailDto() + { + TagList = currWLD.ListUnWorkable + }; + newWorkGroupList.Add("", detProd); + string rawWGL = JsonConvert.SerializeObject(newWorkGroupList); + ProductionGroupModel newRec = new ProductionGroupModel() + { + OrderRowID = currRec.OrderRowID, + GrpIdx = grpIdx++, + WorkGroupListRaw = rawWGL + }; + listProdGroup2Add.Add(newRec); + } + + // dizionario x macchina delle parts LAVORABILI su impianto.. + var machineTags = currWLD.MachineCalcResults + .ToDictionary( + m => m.Name, + m => m.PartList + .Where(p => p.CalcResult == EgwCoreLib.Lux.Core.Enums.PartVerificationResult.MACHINABLE) + .ToList() + ); + + // ciclo x tutte le combinazioni di gruppi lavorabilità... + foreach (var item in currWLD.LoadDetail) + { + // calcolo il dizionario degli elementi... + Dictionary newWorkGroupList = new(); + foreach (var machineName in item.Machines) + { + decimal effectiveTime = 0; + // Recuperiamo i dati della macchina dal dizionario + if (machineTags.TryGetValue(machineName, out var machineParts)) + { + // Creiamo un set dei tag del gruppo per una ricerca veloce O(1) + var groupTagsSet = item.Tags.ToHashSet(); + + // Sommiamo il tempo solo per i pezzi che appartengono a questo gruppo + effectiveTime = machineParts + .Where(p => groupTagsSet.Contains(p.Tag)) + .Sum(p => p.Time); + } + + ProdMachineDetailDto detProd = new ProdMachineDetailDto() + { + TagList = item.Tags, + Time = effectiveTime // Tempo reale specifico per questa macchina/gruppo + }; + newWorkGroupList.Add(machineName, detProd); + } + string rawWGL = JsonConvert.SerializeObject(newWorkGroupList); + ProductionGroupModel newRec = new ProductionGroupModel() + { + OrderRowID = currRec.OrderRowID, + GrpIdx = grpIdx++, + WorkGroupListRaw = rawWGL + }; + listProdGroup2Add.Add(newRec); + } + // aggiungo i record... + dbCtx.DbSetProdGroup.AddRange(listProdGroup2Add); + + // aggiorno info Estimation, tempi e stato + currRec.ProdEstimate = prodEstim; + if (!string.IsNullOrEmpty(prodEstim)) + { + currRec.OrderRowState = OrderStates.Estimated; + } + var totEstim = listProdGroup2Add.Sum(x => x.TotalEstimTime); + currRec.ProdEstimTime = totEstim; + dbCtx.Entry(currRec).State = EntityState.Modified; + } + + // salvo TUTTI i cambiamenti... + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return done; + } + catch + { + tx.Rollback(); + throw; + } + } + + public async Task ValidateAsync(List list2chk) + { + int numDone = 0; + + // Add validation for null or empty list + if (list2chk.Count == 0) + return numDone; + + // context + await using var dbCtx = await CreateContextAsync(); + // Wrap in transaction for atomicity (batch update multiple rows) + await using var tx = dbCtx.Database.BeginTransaction(); + try + { + // verifica preliminare: serve SSE stato e estimate non corrispondono... + var list2fix = list2chk + .Where(x => x.OrderRowState == OrderStates.Created && !string.IsNullOrEmpty(x.ProdEstimate)) + .ToList(); + + if (list2fix.Any()) + { + // per ogni record processo intera validazione + foreach (var item in list2fix) + { + bool fatto = await SaveProdEstAsync(item.OrderRowUID, item.ProdEstimate); + numDone += fatto ? 1 : 0; + } + } + + // salvo TUTTI i cambiamenti... + bool done = await dbCtx.SaveChangesAsync() > 0; + + if (done) + tx.Commit(); + + return numDone; + } + catch + { + tx.Rollback(); + throw; + } + } + #endregion Public Methods } } \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs index 70b9c022..0f888882 100644 --- a/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs +++ b/EgwCoreLib.Lux.Data/Services/DataLayerServices.cs @@ -21,8 +21,6 @@ namespace EgwCoreLib.Lux.Data.Services { public class DataLayerServices : BaseServ { - private readonly IServiceProvider _serviceProvider; - #region Public Constructors public DataLayerServices( @@ -50,15 +48,15 @@ namespace EgwCoreLib.Lux.Data.Services } } + #endregion Public Constructors - #region Lazy InitServices + #region Public Properties public IOfferRowService OfferRowServ => _serviceProvider.GetRequiredService(); + public IOrderRowService OrderRowServ => _serviceProvider.GetRequiredService(); public IProductionOdlService PrOdlServ => _serviceProvider.GetRequiredService(); - #endregion Lazy InitServices - - #endregion Public Constructors + #endregion Public Properties #region Public Methods @@ -181,27 +179,6 @@ namespace EgwCoreLib.Lux.Data.Services return answ; } - /// - /// Validazione record: - /// - controllo state vs estimate - /// - segnala il numero dei record aggiornati - /// - /// Elenco record da verificare - public async Task OrderRowListValidate(List list2chk) - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - // calcolo - int numDone = await dbController.OrderRowListValidate(list2chk); - // svuoto cache... - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Orders:*"); - 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 numDone; - } - /// /// Elenco completo Fasi /// @@ -289,25 +266,6 @@ namespace EgwCoreLib.Lux.Data.Services LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); return fatto; } -#if false - - /// - /// Aggiorna record ProdOdl (se trovato) con ItemListRaw (raw) inviata x calcolo PROD - /// - /// - /// - /// - public async Task ProdOdlUpdateItemRawAsync(string uID, string itemListRaw) - { - using var activity = StartActivity(); - string source = "DB+REDIS"; - bool result = await dbController.ProdOdlUpdateItemRawAsync(uID, itemListRaw); - await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdOdl:*"); - activity?.SetTag("data.source", source); - LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); - return result; - } -#endif /// /// Esegue salvataggio BOM sul DB @@ -439,8 +397,11 @@ namespace EgwCoreLib.Lux.Data.Services /// Environment dell'item /// Stima ProdEstimate serializzata /// - public async Task SaveProdEstimateAsync(string uID, Constants.EXECENVIRONMENTS execEnvironment, string prodEstim) + public async Task SaveProdEstimateAsync(string uID, Constants.EXECENVIRONMENTS execEnvironment, string prodEstim) { + var result = await OrderRowServ.SaveProdEstAsync(uID, prodEstim); + return result; +#if false using var activity = StartActivity(); string source = "DB"; // salvo sul DB il risultato della BOM @@ -451,6 +412,7 @@ namespace EgwCoreLib.Lux.Data.Services } activity?.SetTag("data.source", source); LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms"); +#endif } /// @@ -730,6 +692,30 @@ namespace EgwCoreLib.Lux.Data.Services #region Private Fields private new static Logger Log = LogManager.GetCurrentClassLogger(); + private readonly IServiceProvider _serviceProvider; +#if false + + /// + /// Validazione record: + /// - controllo state vs estimate + /// - segnala il numero dei record aggiornati + /// + /// Elenco record da verificare + public async Task OrderRowListValidate(List list2chk) + { + using var activity = StartActivity(); + string source = "DB+REDIS"; + // calcolo + int numDone = await dbController.OrderRowListValidate(list2chk); + // svuoto cache... + await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Orders:*"); + 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 numDone; + } +#endif private string redisBaseKey = "Lux:Cache"; #endregion Private Fields diff --git a/EgwCoreLib.Lux.Data/Services/Sales/IOrderRowService.cs b/EgwCoreLib.Lux.Data/Services/Sales/IOrderRowService.cs index 621d7ea8..b644c1ea 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/IOrderRowService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/IOrderRowService.cs @@ -36,6 +36,10 @@ namespace EgwCoreLib.Lux.Data.Services.Sales Task UpsertAsync(OrderRowModel upsRec); + Task SaveProdEstAsync(string uID, string prodEstim); + + Task ValidateAsync(List list2chk); + #endregion Public Methods } } \ No newline at end of file diff --git a/EgwCoreLib.Lux.Data/Services/Sales/OrderRowService.cs b/EgwCoreLib.Lux.Data/Services/Sales/OrderRowService.cs index 10856e87..3fe2899d 100644 --- a/EgwCoreLib.Lux.Data/Services/Sales/OrderRowService.cs +++ b/EgwCoreLib.Lux.Data/Services/Sales/OrderRowService.cs @@ -392,6 +392,30 @@ namespace EgwCoreLib.Lux.Data.Services.Sales }); } + + /// + /// Validazione record: + /// - controllo state vs estimate + /// - segnala il numero dei record aggiornati + /// + /// Elenco record da verificare + public async Task ValidateAsync(List list2chk) + { + return await TraceAsync($"{_className}.Validate", async (activity) => + { + activity?.SetTag("db.operation", "Validate"); + + var success = await _repo.ValidateAsync(list2chk); + + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + await ClearCacheAsync($"{_redisBaseKey}:Order:*"); + await ClearCacheAsync($"{_redisBaseKey}:Orders:*"); + await ClearCacheAsync($"{_redisBaseKey}:OrderRowsByState:*"); + + return success; + }); + } + /// /// Upsert record OrderRow /// @@ -436,6 +460,27 @@ namespace EgwCoreLib.Lux.Data.Services.Sales }); } + /// + /// Esegue salvataggio ProdEstimate (per riga ordine) sul DB + /// + /// UID dell'item offerta di cui si è ricevuto la ProdEstim + /// Environment dell'item + /// Stima ProdEstimate serializzata + /// + public async Task SaveProdEstAsync(string uID, string prodEstim) + { + return await TraceAsync($"{_className}.UpsertProdEst", async (activity) => + { + activity?.SetTag("db.operation", "UpsertProdEst"); + + var success = await _repo.SaveProdEstAsync(uID, prodEstim); + + await ClearCacheAsync($"{_redisBaseKey}:{_className}:*"); + + return success; + }); + } + #endregion Public Methods #region Private Fields diff --git a/Lux.UI/Components/Compo/OrderRowMan.razor.cs b/Lux.UI/Components/Compo/OrderRowMan.razor.cs index 3ca4d58a..36bb04a5 100644 --- a/Lux.UI/Components/Compo/OrderRowMan.razor.cs +++ b/Lux.UI/Components/Compo/OrderRowMan.razor.cs @@ -1450,7 +1450,7 @@ namespace Lux.UI.Components.Compo AllRecords = await OrdRService.GetByParentAsync(OrderID); totalCount = AllRecords.Count(); // faccio un controllo/fix dei record - int numFix = await DLService.OrderRowListValidate(AllRecords); + int numFix = await OrdRService.ValidateAsync(AllRecords); } }