Files
lux/EgwCoreLib.Lux.Data/Controllers/LuxController.cs
T
2026-03-18 19:25:50 +01:00

1228 lines
54 KiB
C#

using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Config;
using EgwCoreLib.Lux.Data.DbModel.Cost;
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;
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
/// <summary>
/// Elenco CostDrivers
/// </summary>
/// <returns></returns>
internal async Task<List<CostDriverModel>> CostDriverGetAllAsync()
{
List<CostDriverModel> dbResult = new List<CostDriverModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbResult = await dbCtx
.DbSetCostDriver
.ToListAsync();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante CostDriverGetAllAsync{Environment.NewLine}{exc}");
}
}
return dbResult;
}
/// <summary>
/// Add item ricevuti da BOM calcolata
/// </summary>
/// <param name="bomList"></param>
/// <returns></returns>
internal bool ItemUpsertFromBom(List<BomItemDTO> 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<ItemModel> listInserted = new List<ItemModel>();
// 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;
}
/// <summary>
/// Elenco item da ricerca filtro x gruppo/tipo
/// </summary>
/// <param name="CodGroup"></param>
/// <param name="ItemType"></param>
/// <returns></returns>
internal List<ItemModel> ItemGetFilt(string CodGroup, ItemClassType ItemType)
{
List<ItemModel> dbResult = new List<ItemModel>();
//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<bool> 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;
}
/// <summary>
/// Esegue upsert del record offerta data la BOM ricevuta
/// </summary>
/// <param name="uID"></param>
/// <param name="bomList"></param>
internal bool OfferUpsertFromBom(string uID, List<BomItemDTO> 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<List<BomItemDTO>>(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;
}
/// <summary>
/// Add record del solo ProdEstimate
/// </summary>
/// <param name="uID"></param>
/// <param name="prodEstim"></param>
internal async Task<bool> OrderRowUpsertProdEst(string uID, string prodEstim)
{
bool answ = false;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
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 onsiderando 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...
var result = await dbCtx.SaveChangesAsync();
answ = result > 0;
}
catch (Exception exc)
{
Log.Error($"Eccezione durante OrderRowUpsertProdEst{Environment.NewLine}{exc}");
}
}
return answ;
}
/// <summary>
/// Validazione record:
/// - controllo state vs estimate
/// - segnala il numero dei record aggiornati
/// </summary>
/// <param name="list2chk">Elenco record da verificare</param>
internal async Task<int> OrderRowListValidate(List<OrderRowModel> list2chk)
{
int numDone = 0;
// 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 OrderRowUpsertProdEst(item.OrderRowUID, item.ProdEstimate);
numDone += fatto ? 1 : 0;
}
}
return numDone;
}
/// <summary>
/// Elenco record Fasi da DB
/// </summary>
/// <returns></returns>
internal async Task<List<PhaseModel>> PhasesGetAllAsync()
{
List<PhaseModel> dbResult = new List<PhaseModel>();
//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;
}
/// <summary>
/// Add record di un singolo ProdGroup da fase Balance
/// </summary>
/// <param name="uID">UID dell'item offerta di cui si è ricevuto l'oggetto Balance'</param>
/// <param name="rGroup">Prod Group di riferimento</param>
/// <param name="rawBalance"></param>
internal async Task<bool> 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<Dictionary<string, ProdMachineDetailDto>>(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;
}
/// <summary>
/// Assegnazione in blocco degli item agli ODL corrispondenti
/// </summary>
/// <param name="dbList"></param>
/// <param name="dictParts"></param>
/// <returns></returns>
internal async Task<int> ProdItem2ODL_AssignAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
{
int totalCreated = 0;
using (DataLayerContext dbCtx = new DataLayerContext())
{
// 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query
List<int> batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList();
if (batchIds != null && batchIds.Count > 0)
{
// 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM)
var itemsList = await dbCtx.DbSetProdItem
.Where(x => batchIds.Contains(x.ProdBatchID ?? 0) && x.ProdItemTag != null && x.ProdItemTag != "")
.Select(x => new { x.ProdItemID, x.ProdItemTag })
.ToListAsync();
// 1. Usiamo il "!" (null-forgiving operator) dopo x.ProdItemTag
// perché il filtro .Where sopra garantisce che non sia null.
var itemLookup = itemsList
.GroupBy(x => x.ProdItemTag!)
.ToDictionary(
g => g.Key,
g => g.First().ProdItemID,
StringComparer.OrdinalIgnoreCase
);
using var transaction = await dbCtx.Database.BeginTransactionAsync();
try
{
var relationsToInsert = new List<ProductionItem2ODLModel>();
foreach (var odl in dbList)
{
var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index);
if (dictParts.TryGetValue(key, out List<string> tagList))
{
foreach (var tag in tagList)
{
// 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale
if (itemLookup.TryGetValue(tag, out int realItemId))
{
relationsToInsert.Add(new ProductionItem2ODLModel
{
ProdODLID = odl.ProdODLID,
ProdItemID = realItemId,
DtAssign = DateTime.Now
});
}
else
{
//Log.Warning($"Tag {tag} non trovato nel database per i batch selezionati.");
}
}
}
}
if (relationsToInsert.Any())
{
await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert);
totalCreated = relationsToInsert.Count;
await dbCtx.SaveChangesAsync();
}
await transaction.CommitAsync();
}
catch (Exception exc)
{
await transaction.RollbackAsync();
Log.Error($"Errore nel salvataggio relazioni ODL-Parts: {exc.Message}");
throw;
}
}
}
return totalCreated;
}
/// <summary>
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdBatch parent (per ora totale x RigaOrd)
/// </summary>
/// <param name="OrderRowId"></param>
/// <param name="ProdBatchId"></param>
/// <returns></returns>
internal async Task<int> ProdItemBulkAssignProdBatch(int OrderRowId, int ProdBatchId)
{
int totalUpdated = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
using var transaction = await dbCtx.Database.BeginTransactionAsync();
try
{
int rowsAffected = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == OrderRowId)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdBatchID, ProdBatchId)
);
totalUpdated += rowsAffected;
await transaction.CommitAsync();
}
catch (Exception exc)
{
await transaction.RollbackAsync();
Log.Error($"Eccezione durante ProdItemBulkAssignProdBatch{Environment.NewLine}{exc}");
//throw;
}
}
return totalUpdated;
}
/// <summary>
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdGroup parent
/// </summary>
/// <param name="OrderRowId"></param>
/// <param name="ProdGroupId"></param>
/// <param name="itemsToAssign"></param>
/// <returns></returns>
internal async Task<int> ProdItemBulkAssignProdGroup(int OrderRowId, int ProdGroupId, Dictionary<string, double> itemsToAssign)
{
int totalUpdated = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
using var transaction = await dbCtx.Database.BeginTransactionAsync();
try
{
foreach (var entry in itemsToAssign)
{
// Esegue un UPDATE diretto: UPDATE production_item SET ProdAssignID = x, EstimTime = y WHERE ProdItemTag = z
int rowsAffected = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == OrderRowId && p.ProdItemTag == entry.Key)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdGroupID, ProdGroupId)
.SetProperty(p => p.EstimTime, entry.Value)
);
totalUpdated += rowsAffected;
}
await transaction.CommitAsync();
}
catch (Exception exc)
{
await transaction.RollbackAsync();
Log.Error($"Eccezione durante ProdItemBulkAssignProdGroup{Environment.NewLine}{exc}");
//throw;
}
}
return totalUpdated;
}
/// <summary>
/// Elenco ProdItem dato OrderRow
/// </summary>
/// <param name="orderRowId"></param>
/// <returns></returns>
internal async Task<List<ProductionItemModel>> ProdItemByOrderRow(int orderRowId)
{
List<ProductionItemModel> dbResult = new List<ProductionItemModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbResult = await dbCtx
.DbSetProdItem
.Where(x => x.OrderRowID == orderRowId)
.ToListAsync();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ProdItemByOrderRow{Environment.NewLine}{exc}");
}
}
return dbResult;
}
/// <summary>
/// Reset impostazione ProdItem x ripartire senza setting ProdGroup
/// </summary>
/// <param name="orderRowID"></param>
/// <returns></returns>
internal async Task<int> ProdItemResetProdGroup(int orderRowID)
{
int numItem = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
numItem = await dbCtx.DbSetProdItem
.Where(p => p.OrderRowID == orderRowID)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ProdGroupID, (int?)null)
.SetProperty(p => p.EstimTime, 0)
);
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ProdItemResetProdGroup{Environment.NewLine}{exc}");
}
}
return numItem;
}
/// <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;
}
/// <summary>
/// Elenco PODL non assegnati
/// </summary>
/// <returns></returns>
internal async Task<List<ProductionODLModel>?> ProductionOdlUnassignAsync()
{
List<ProductionODLModel>? dbRestults = null;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbRestults = await dbCtx
.DbSetProdODL
.Where(x => !x.DateAssign.HasValue)
.Include(x => x.Item2OdlNav)
//.ThenInclude(i => i.ProductionItemNav)
//.ThenInclude(o => o.OrderRowNav)
//.ThenInclude(o => o.OrderNav)
.ToListAsync();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ProductionOdlUnassignAsync{Environment.NewLine}{exc}");
}
}
return dbRestults;
}
/// <summary>
/// Esegue merge dei dati nella tab profili del DB con le info accessorie...
/// </summary>
/// <param name="uID"></param>
/// <param name="execEnvironment"></param>
/// <param name="rawContent"></param>
/// <returns></returns>
internal async Task<bool> SaveProfileListAsync(string uID, Constants.EXECENVIRONMENTS execEnvironment, string rawContent)
{
bool answ = false;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// solo se ho qualcosa da controllare...
if (!string.IsNullOrEmpty(rawContent))
{
// in primis recupero profili attuali
var dbList = await dbCtx
.DbSetConfProfile
.ToListAsync();
// ciclo sul contenuto ricevuto, se mancasse aggiungo!
List<string> list2check = JsonConvert.DeserializeObject<List<string>>(rawContent) ?? new List<string>();
List<ProfileModel> rec2ins = new List<ProfileModel>();
foreach (var item in list2check)
{
if (!dbList.Any(x => x.Code == item))
{
rec2ins.Add(new ProfileModel() { Code = item, Description = $"{item} - NEW" });
}
}
// se ho dati li inserisco...
if (rec2ins.Count > 0)
{
dbCtx.DbSetConfProfile.AddRange(rec2ins);
// salvo...
int numDone = await dbCtx.SaveChangesAsync();
answ = numDone > 0;
}
}
}
return answ;
}
/// <summary>
/// Salvataggio info serializzate x soglie e dati opzionali sul DB
/// </summary>
/// <param name="uID"></param>
/// <param name="execEnvironment"></param>
/// <param name="rawThreshold"></param>
/// <param name="rawData"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal async Task<bool> SaveProfileThreshAsync(string uID, Constants.EXECENVIRONMENTS execEnvironment, string rawThreshold, string rawData)
{
bool answ = false;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// solo se ho qualcosa da controllare...
if (!string.IsNullOrEmpty(rawThreshold) || !string.IsNullOrEmpty(rawData))
{
// in primis recupero profilo da aggiornare
var dbRec = await dbCtx
.DbSetConfProfile
.Where(x => x.Code == uID)
.FirstOrDefaultAsync();
// se ho record aggiorno...
if (dbRec != null)
{
dbRec.ProfDataRaw = rawData;
dbRec.ThreshDataRaw = rawThreshold;
dbCtx.Entry(dbRec).State = EntityState.Modified;
}
// alrimenti creo + aggiorno
else
{
dbCtx.DbSetConfProfile.Add(new ProfileModel()
{
Code = uID,
Description = $"{uID} - NEW PROFILE",
ProfDataRaw = rawData,
ThreshDataRaw = rawThreshold
});
}
// salvo...
int numDone = await dbCtx.SaveChangesAsync();
answ = numDone > 0;
}
}
return answ;
}
/// <summary>
/// Elenco da DB delel stats aggregate dato periodo inizio/fine
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <returns></returns>
internal async Task<List<StatsAggregatedModel>> StatsAggrGetAsync(DateTime dtStart, DateTime dtEnd)
{
List<StatsAggregatedModel> answ = new List<StatsAggregatedModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// recupero ed ordino per data-ora
answ = await dbCtx
.DbSetStatsAggr
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd)
.AsNoTracking()
.OrderBy(x => x.Hour)
.ToListAsync();
}
return answ;
}
/// <summary>
/// Range periodo per chiamate aggregate
/// </summary>
/// <returns></returns>
internal async Task<Utils.DtUtils.Periodo> StatsAggrRangeAsync()
{
Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today);
using (DataLayerContext dbCtx = new DataLayerContext())
{
var query = dbCtx.DbSetStatsAggr.AsQueryable();
var minHour = await query.MinAsync(x => x.Hour);
var maxHour = await query.MaxAsync(x => x.Hour);
answ.Inizio = minHour;
answ.Fine = maxHour;
return answ;
}
}
/// <summary>
/// Esegue insert statistiche aggregate sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
internal async Task<long> StatsAggrUpsertAsync(List<StatsAggregatedModel> listRecords, bool removeOld)
{
int answ = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// in primis se richiesto calcolo range periodo e svuoto...
if (removeOld)
{
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
if (firstRec != null && lastRec != null)
{
DateTime startDate = firstRec.Hour;
DateTime endDate = lastRec.Hour;
// uso direttamente ExecuteDelete
await dbCtx
.DbSetStatsAggr
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
.ExecuteDeleteAsync();
}
}
// ora preparo inserimento massivo
await dbCtx
.DbSetStatsAggr
.AddRangeAsync(listRecords);
// salvo!
answ = await dbCtx.SaveChangesAsync();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
}
return answ;
}
/// <summary>
/// Recupera dati stats di dettaglio dato filtro envir/tipo (opzionali) e periodo
/// </summary>
/// <param name="dtStart"></param>
/// <param name="dtEnd"></param>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
internal async Task<List<StatsDetailModel>> StatsDetailModelGetAsync(DateTime dtStart, DateTime dtEnd, string sEnvir = "", string sType = "")
{
List<StatsDetailModel> answ = new List<StatsDetailModel>();
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// recupero ed ordino per data-ora
var query = dbCtx.DbSetStatsDet
.Where(x => x.Hour >= dtStart && x.Hour <= dtEnd);
if (!string.IsNullOrEmpty(sEnvir))
query = query.Where(x => x.Environment == sEnvir);
if (!string.IsNullOrEmpty(sType))
query = query.Where(x => x.Type == sType);
answ = await query
.AsNoTracking()
.OrderBy(x => x.Hour)
.ThenBy(x => x.Environment)
.ThenBy(x => x.Type)
.ToListAsync();
}
return answ;
}
/// <summary>
/// Range periodo x chiamate detail eventualmente filtrate
/// </summary>
/// <param name="sEnvir"></param>
/// <param name="sType"></param>
/// <returns></returns>
internal async Task<Utils.DtUtils.Periodo> StatsDetailModelRangeAsync(string sEnvir, string sType)
{
Utils.DtUtils.Periodo answ = new Utils.DtUtils.Periodo(Utils.DtUtils.PeriodSet.Today);
using (DataLayerContext dbCtx = new DataLayerContext())
{
var query = dbCtx.DbSetStatsDet.AsQueryable();
if (!string.IsNullOrEmpty(sEnvir))
query = query.Where(x => x.Environment == sEnvir);
if (!string.IsNullOrEmpty(sType))
query = query.Where(x => x.Type == sType);
var minHour = await query.MinAsync(x => x.Hour);
var maxHour = await query.MaxAsync(x => x.Hour);
answ.Inizio = minHour;
answ.Fine = maxHour;
return answ;
}
}
/// <summary>
/// Esegue insert statistiche di dettaglio sul DB
/// </summary>
/// <param name="listRecords">Elenco dei record da inserire</param>
/// <param name="removeOld">Se true preventivamente elimina record nel periodo richiesto</param>
/// <returns></returns>
internal async Task<long> StatsDetailModelUpsertAsync(List<StatsDetailModel> listRecords, bool removeOld)
{
int answ = 0;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
// in primis se richiesto calcolo range periodo e svuoto...
if (removeOld)
{
var firstRec = listRecords.OrderBy(x => x.Hour).FirstOrDefault();
var lastRec = listRecords.OrderByDescending(x => x.Hour).FirstOrDefault();
if (firstRec != null && lastRec != null)
{
DateTime startDate = firstRec.Hour;
DateTime endDate = lastRec.Hour;
// uso direttamente ExecuteDelete
await dbCtx
.DbSetStatsDet
.Where(x => x.Hour >= startDate && x.Hour <= endDate)
.ExecuteDeleteAsync();
}
}
// ora preparo inserimento massivo
await dbCtx
.DbSetStatsDet
.AddRangeAsync(listRecords);
// salvo!
answ = await dbCtx.SaveChangesAsync();
// libero memoria del changeTracker
dbCtx.ChangeTracker.Clear();
}
return answ;
}
#if true
internal bool UpdateCodGroup(List<BomItemDTO> 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<string> 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
private static IConfiguration _configuration;
private static Logger Log = LogManager.GetCurrentClassLogger();
#endregion Private Fields
}
}