Files
lux/EgwCoreLib.Lux.Data/Controllers/LuxController.cs
T

646 lines
27 KiB
C#

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.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
/// <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;
}
#if true
/// <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;
}
#endif
/// <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;
}
#if false
/// <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;
}
#endif
#if true
/// <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;
}
#endif
/// <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
}
}