Spostamento altri metodi...

This commit is contained in:
Samuele Locatelli
2026-03-19 16:42:19 +01:00
parent 72a880fb11
commit 07eacd022e
7 changed files with 311 additions and 163 deletions
@@ -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
/// </summary>
/// <param name="uID"></param>
/// <param name="prodEstim"></param>
#if false
internal async Task<bool> 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
/// <summary>
/// Elenco record Fasi da DB
@@ -602,118 +604,6 @@ namespace EgwCoreLib.Lux.Data.Controllers
}
#endif
#if false
/// <summary>
/// Aggiorna record ProdOdl (se trovato) con BOM (raw) ricevuta
/// </summary>
/// <param name="uID"></param>
/// <param name="bomRaw"></param>
/// <returns></returns>
internal async Task<bool> 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;
}
/// <summary>
/// Aggiorna record ProdOdl (se trovato) con ItemListRaw (raw) inviata x calcolo PROD
/// </summary>
/// <param name="uID"></param>
/// <param name="itemListRaw"></param>
/// <returns></returns>
internal async Task<bool> 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;
}
/// <summary>
/// Aggiorna record ProdOdl (se trovato) con RawMaterialList (raw) ricevuta da calcolo PROD
/// </summary>
/// <param name="uID"></param>
/// <param name="materialListRaw"></param>
/// <returns></returns>
internal async Task<bool> 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
/// <summary>
/// Esegue merge dei dati nella tab profili del DB con le info accessorie...
@@ -26,10 +26,14 @@ namespace EgwCoreLib.Lux.Data.Repository.Sales
Task<List<ItemGroupModel>> GetItemGroupsAsync();
Task<bool> SaveProdEstAsync(string uID, string prodEstim);
Task<bool> SaveRowsAsync(List<OrderRowModel> rows);
Task<bool> UpdateAsync(OrderRowModel entity);
Task<int> ValidateAsync(List<OrderRowModel> list2chk);
#endregion Public Methods
}
}
@@ -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<bool> 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<string> listMaccCurr = currWLD.MachineCalcResults.Select(x => x.Name).OrderBy(x => x).ToList() ?? new List<string>();
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<ProductionItemModel> 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<ProductionGroupModel> 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<ProductionGroupModel> listProdGroup2Add = new List<ProductionGroupModel>();
// 1: non lavorabili...
if (currWLD.ListUnWorkable.Count > 0)
{
// calcolo il dizionario degli elementi...
Dictionary<string, ProdMachineDetailDto> 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<string, ProdMachineDetailDto> 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<int> ValidateAsync(List<OrderRowModel> 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
}
}
@@ -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<IOfferRowService>();
public IOrderRowService OrderRowServ => _serviceProvider.GetRequiredService<IOrderRowService>();
public IProductionOdlService PrOdlServ => _serviceProvider.GetRequiredService<IProductionOdlService>();
#endregion Lazy InitServices
#endregion Public Constructors
#endregion Public Properties
#region Public Methods
@@ -181,27 +179,6 @@ namespace EgwCoreLib.Lux.Data.Services
return answ;
}
/// <summary>
/// Validazione record:
/// - controllo state vs estimate
/// - segnala il numero dei record aggiornati
/// </summary>
/// <param name="list2chk">Elenco record da verificare</param>
public async Task<int> OrderRowListValidate(List<OrderRowModel> 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;
}
/// <summary>
/// Elenco completo Fasi
/// </summary>
@@ -289,25 +266,6 @@ namespace EgwCoreLib.Lux.Data.Services
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms");
return fatto;
}
#if false
/// <summary>
/// Aggiorna record ProdOdl (se trovato) con ItemListRaw (raw) inviata x calcolo PROD
/// </summary>
/// <param name="uID"></param>
/// <param name="itemListRaw"></param>
/// <returns></returns>
public async Task<bool> 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
/// <summary>
/// Esegue salvataggio BOM sul DB
@@ -439,8 +397,11 @@ namespace EgwCoreLib.Lux.Data.Services
/// <param name="execEnvironment">Environment dell'item</param>
/// <param name="prodEstim">Stima ProdEstimate serializzata</param>
/// <returns></returns>
public async Task SaveProdEstimateAsync(string uID, Constants.EXECENVIRONMENTS execEnvironment, string prodEstim)
public async Task<bool> 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
}
/// <summary>
@@ -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
/// <summary>
/// Validazione record:
/// - controllo state vs estimate
/// - segnala il numero dei record aggiornati
/// </summary>
/// <param name="list2chk">Elenco record da verificare</param>
public async Task<int> OrderRowListValidate(List<OrderRowModel> 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
@@ -36,6 +36,10 @@ namespace EgwCoreLib.Lux.Data.Services.Sales
Task<OrderRowModel?> UpsertAsync(OrderRowModel upsRec);
Task<bool> SaveProdEstAsync(string uID, string prodEstim);
Task<int> ValidateAsync(List<OrderRowModel> list2chk);
#endregion Public Methods
}
}
@@ -392,6 +392,30 @@ namespace EgwCoreLib.Lux.Data.Services.Sales
});
}
/// <summary>
/// Validazione record:
/// - controllo state vs estimate
/// - segnala il numero dei record aggiornati
/// </summary>
/// <param name="list2chk">Elenco record da verificare</param>
public async Task<int> ValidateAsync(List<OrderRowModel> 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;
});
}
/// <summary>
/// Upsert record OrderRow
/// </summary>
@@ -436,6 +460,27 @@ namespace EgwCoreLib.Lux.Data.Services.Sales
});
}
/// <summary>
/// Esegue salvataggio ProdEstimate (per riga ordine) sul DB
/// </summary>
/// <param name="uID">UID dell'item offerta di cui si è ricevuto la ProdEstim</param>
/// <param name="execEnvironment">Environment dell'item</param>
/// <param name="prodEstim">Stima ProdEstimate serializzata</param>
/// <returns></returns>
public async Task<bool> 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
+1 -1
View File
@@ -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);
}
}