Files
lux/Lux.UI/Components/Pages/WorkLoadBalance.razor.cs
T
2026-03-24 12:55:16 +01:00

562 lines
23 KiB
C#

using EgwCoreLib.Lux.Core.Generic;
using EgwCoreLib.Lux.Core.RestPayload;
using EgwCoreLib.Lux.Data.DbModel.Production;
using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.DbModel.Utils;
using EgwCoreLib.Lux.Data.Services;
using EgwCoreLib.Lux.Data.Services.General;
using EgwCoreLib.Lux.Data.Services.Production;
using EgwCoreLib.Lux.Data.Services.Sales;
using EgwCoreLib.Lux.Data.Services.Utils;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Newtonsoft.Json;
using static EgwCoreLib.Lux.Core.Enums;
using static Lux.UI.Components.Compo.Planner.BalanceProgGroup;
namespace Lux.UI.Components.Pages
{
public partial class WorkLoadBalance : IDisposable
{
#region Public Methods
/// <summary>
/// Dispose sottoscrizione canale
/// </summary>
public void Dispose()
{
DLService.PipeProd.EA_NewMessage -= PipeProd_EA_NewMessage;
}
#endregion Public Methods
#region Protected Methods
protected override void OnInitialized()
{
PeriodoSel = new EgwCoreLib.Utils.DtUtils.Periodo(EgwCoreLib.Utils.DtUtils.PeriodSet.ThisYear);
PeriodoSel.Inizio = PeriodoSel.Inizio.AddYears(-1);
DLService.PipeProd.EA_NewMessage += PipeProd_EA_NewMessage;
}
#endregion Protected Methods
#region Private Fields
private List<BomItemDTO>? currBomList = null;
private List<GenValueModel>? currDefaultSet = null;
private Dictionary<string, List<ItemRawDTO>>? currDictItem = null;
/// <summary>
/// Oggetto ODL con assegnazione item x richiesta prod
/// </summary>
private OdlAssignDto? currOdlAssign = null;
private List<ProductionGroupModel>? ListBalancedRecords = null;
private List<OrderRowModel>? ListEstimRecords = null;
private List<OdlAssignDto>? ListOdl = null;
/// <summary>
/// Periodo selezionato attuale
/// </summary>
private EgwCoreLib.Utils.DtUtils.Periodo PeriodoSel = new EgwCoreLib.Utils.DtUtils.Periodo(EgwCoreLib.Utils.DtUtils.PeriodSet.ThisYear);
private EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS SelEnvir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.BEAM;
#endregion Private Fields
#region Private Properties
[Inject]
private ICalcRuidService CRService { get; set; } = null!;
[Inject]
private ICalcRequestService CService { get; set; } = null!;
[Inject]
private IDataLayerServices DLService { get; set; } = null!;
[Inject]
private IGenValService GVService { get; set; } = default!;
[Inject]
private IJSRuntime JSRuntime { get; set; } = null!;
[Inject]
private IOrderRowService OrdRService { get; set; } = null!;
[Inject]
private IOrderService OrdService { get; set; } = null!;
[Inject]
private IProductionBatchService PBService { get; set; } = null!;
[Inject]
private IProductionItemService PIService { get; set; } = null!;
[Inject]
private IProductionGroupService PGService { get; set; } = null!;
[Inject]
private IProductionOdlService POService { get; set; } = null!;
[Inject]
private IProdService PService { get; set; } = null!;
#endregion Private Properties
#region Private Methods
private async Task DoBalance(int OrderRowID)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Sicuro di voler Bilanciare la riga d'ordine?"))
return;
// verifico se mancassero le righe di ProdGroup e rigenero...
List<ProductionGroupModel> listProgGroup = ListBalancedRecords?.Where(x => x.OrderRowID == OrderRowID).ToList() ?? new();
// se nullo...
if (listProgGroup == null || listProgGroup.Count == 0)
{
// recupero riga d'ordine...
if (ListEstimRecords != null)
{
var currRec = ListEstimRecords.FirstOrDefault(x => x.OrderRowID == OrderRowID);
if (currRec != null)
{
await DLService.SaveProdEstimateAsync(currRec.OrderRowUID, currRec.Envir, currRec.ProdEstimate);
// rileggo...
var ListEstimated = await PGService.GetByOrderStateAsync(OrderStates.Estimated);
listProgGroup = ListEstimated?.Where(x => x.OrderRowID == OrderRowID).ToList() ?? new();
}
}
}
// proseguo se ho i prodGroup e richiedo...
if (listProgGroup != null && listProgGroup.Count > 0)
{
foreach (var item in listProgGroup)
{
await ReqBalance(item);
}
}
}
/// <summary>
/// Richiede calcolo del prod
/// </summary>
/// <param name="odlRec"></param>
/// <returns></returns>
private async Task DoCalculateProd(Dictionary<string, List<ItemRawDTO>> itemAvaiDict)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Stai per inviare richiesta di generazione del file di produzione ODL, confermi?"))
return;
if (currOdlAssign != null)
{
// preparo oggetto richiesta generazione PROD ODL file all'Engine...
Egw.Window.Data.Enums.QuestionModes cMode = Egw.Window.Data.Enums.QuestionModes.ORDER;
Egw.Window.Data.Enums.QuestionOrderSubModes cSubMode = Egw.Window.Data.Enums.QuestionOrderSubModes.CALC_PRODUCTION;
string reqKey = "";
// preparo la richiesta di creazione file
CalcRequestDTO calcReq = new CalcRequestDTO();
// serializzo e salvo sul DB il record dei materiali inviati x il calcolo...
string rawMatList = JsonConvert.SerializeObject(itemAvaiDict);
await POService.UpdateItemRawAsync(currOdlAssign.OdlTag, rawMatList);
// preparo obj che contiene info x creazione Proj dell'ODL
ProdCalcOdlReqDTO projReq = new ProdCalcOdlReqDTO()
{
ProdName = currOdlAssign.OdlTag,
MachineName = currOdlAssign.ProdPlantCod,
ItemAvaiList = itemAvaiDict
};
// preparo richiesta serializzata e la accodo (viene inviata richiesta calcolo)
Dictionary<string, string> dictArgs = new Dictionary<string, string>();
// creo registrazione richiesta...
var ruid = await CRService.AddRequestAsync($"{currOdlAssign.Envir}", $"{(int)cMode}-{(int)cSubMode}", currOdlAssign.OdlTag);
dictArgs.Add("UID", currOdlAssign.OdlTag);
// aggiungo RUID effettivo
dictArgs.Add("RUID", ruid);
dictArgs.Add("Mode", $"{(int)cMode}");
dictArgs.Add("SubMode", $"{(int)cSubMode}");
var serRequest = JsonConvert.SerializeObject(projReq);
dictArgs.Add("ProdCalc", serRequest);
calcReq = new CalcRequestDTO()
{
DictExec = dictArgs,
EnvType = currOdlAssign.Envir
};
// chiave: composta da cMode, submode, UID riga...
reqKey = $"{cMode}:{cSubMode}:{currOdlAssign.OdlTag}";
// invio richiesta
await PService.EnqueueRequestAsync("ProdCalc", reqKey, calcReq);
// chiudo
currBomList = null;
currOdlAssign = null;
}
// rileggo comunque i dati...
await ReloadDataAsync();
UpdateTable();
}
private void DoCloseRawPart(bool force)
{
currBomList = null;
currOdlAssign = null;
}
/// <summary>
/// Creazione progetti da schedulare...
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task DoCreateBatch(BatchCreateInfo batchData)
{
// per prima cosa creo il batch...
var recBatch = await PBService.CreateAsync(batchData.NewBatch);
List<int> ordRowIdList = new List<int>();
// se ok batch...
if (recBatch != null)
{
List<ProductionODLModel> listOdl = new List<ProductionODLModel>();
// uso una Tupla
var dictParts = new Dictionary<(int phaseId, int resId, string machine, int index), List<string>>();
// ciclo x ogni macchina...
foreach (var item in batchData.GroupDetail)
{
// dovrei recuperare Ciclo, Articolo, Fasi e Risorse dalle macchine ... x ora ND/ND/0/0
int phaseId = 0;
int resId = 0;
int idx = 10; // fisso come fase/res
// preparo la lista degli ODL
var newOdl = new ProductionODLModel()
{
ProdBatchID = recBatch.ProdBatchID,
ProdPlantCod = item.MachineName,
Index = idx,
PhaseID = phaseId,
ResourceID = resId,
Description = $"Ciclo ND | Art: ND | Fase: {phaseId} | Res: {resId}",
EstimTime = item.TotalTime,
Qty = item.TotalNumPart
};
listOdl.Add(newOdl);
// costruisco il dizionario delle parts che mi servirà poi ad associare gli items
dictParts[(phaseId, resId, item.MachineName, idx)] = item.TagList;
}
// se ho record li scrivo!
if (listOdl.Count > 0)
{
// creazione ODL
List<ProductionODLModel> prodODL_DbList = await POService.CreateAsync(listOdl);
// fix items sul batch ID, ciclo sui prodGroup
int numParts = 0;
foreach (var item in batchData.AllRecords)
{
numParts += await PIService.BulkAssignProdBatchAsync(item.OrderRowID, recBatch.ProdBatchID);
if (!ordRowIdList.Contains(item.OrderRowID))
{
ordRowIdList.Add(item.OrderRowID);
}
}
// ora lavoro sugli items x collegarli agli ODL... parto dal dizionario e cerco nell'elenco degli ODL creati...
int fatto = await POService.AssignProdItem2OdlAsync(prodODL_DbList, dictParts);
// aggiorno sales order status a ProdOdl creato
foreach (var ordRowId in ordRowIdList)
{
await OrdRService.UpdateStateAsync(ordRowId, OrderStates.ProdOdlCreated);
}
}
}
// rileggo comunque i dati...
await ReloadDataAsync();
UpdateTable();
}
/// <summary>
/// Invio richiesta creazione file x elenco PODL ricevuto
/// </summary>
/// <param name="listOdl"></param>
/// <returns></returns>
private async Task DoCreateProd(OdlAssignDto odlRec)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Sicuro di voler (ri)creare il file di produzione ODL?"))
return;
// preparo oggetto richiesta generazione PROD ODL file all'Engine...
Egw.Window.Data.Enums.QuestionModes cMode = Egw.Window.Data.Enums.QuestionModes.ORDER;
Egw.Window.Data.Enums.QuestionOrderSubModes cSubMode = Egw.Window.Data.Enums.QuestionOrderSubModes.CREATE_PRODUCTION;
string reqKey = "";
// preparo la richiesta di creazione file
CalcRequestDTO calcReq = new CalcRequestDTO();
// genero struttura richiesta ANNIDIATA
var summary = odlRec
.ItemList // appiattisce tutte le righe
.GroupBy(i => i.OrderTag)
.ToDictionary(
g => g.Key,
g => g
.GroupBy(i => i.OrderRowTag)
.ToDictionary(
gg => gg.Key,
gg => gg
.Select(i => i.ProdItemTag)
.Distinct()
.ToList()
)
);
// preparo obj che contiene info x creazione Proj dell'ODL
ProdOdlReqDTO projReq = new ProdOdlReqDTO()
{
ProdName = odlRec.OdlTag,
MachineName = odlRec.ProdPlantCod,
OrderTagDict = summary
};
// preparo richiesta serializzata e la accodo (viene inviata richiesta calcolo)
Dictionary<string, string> dictArgs = new Dictionary<string, string>();
// creo registrazione richiesta...
var ruid = await CRService.AddRequestAsync($"{odlRec.Envir}", $"{(int)cMode}-{(int)cSubMode}", odlRec.OdlTag);
dictArgs.Add("UID", odlRec.OdlTag);
// aggiungo RUID effettivo
dictArgs.Add("RUID", ruid);
dictArgs.Add("Mode", $"{(int)cMode}");
dictArgs.Add("SubMode", $"{(int)cSubMode}");
var serRequest = JsonConvert.SerializeObject(projReq);
dictArgs.Add("ProdCreate", serRequest);
calcReq = new CalcRequestDTO()
{
DictExec = dictArgs,
EnvType = odlRec.Envir
};
// chiave: composta da cMode, submode, UID riga...
reqKey = $"{cMode}:{cSubMode}:{odlRec.OdlTag}";
// invio richiesta
await PService.EnqueueRequestAsync("ProdCreate", reqKey, calcReq);
// rileggo comunque i dati...
await ReloadDataAsync();
UpdateTable();
}
/// <summary>
/// Prepara selezione materiali x calcolo ODL
/// </summary>
/// <param name="odlRec"></param>
/// <returns></returns>
private async Task DoPrepareRawMatSel(OdlAssignDto odlRec)
{
// recupero bomOdl da passare...
var prodOdlRec = await POService.GetByUidAsync(odlRec.OdlTag);
// se ho qualcosa e contiene bom...
if (prodOdlRec != null && prodOdlRec.ListBoM.Count > 0)
{
//se dal DB non avessi elenco lo calcolo da bom...
currDictItem = prodOdlRec.DictItemRaw;
currBomList = prodOdlRec.ListBoM;
if (currDictItem == null || currDictItem.Count == 0)
{
if (currBomList != null && currBomList.Count > 0)
{
// converto dalla BOM...
currDictItem = currBomList.ToDictionary(x => x.ItemCode, x => new List<ItemRawDTO>());
}
}
// sistemo i valori di default, qui un pò "cablati"...
currDefaultSet = await GVService.GetFiltAsync("DefaultLenghtBW");
// salvo info Dizionario + odlRec così da mostrare finestra modale selezione barre...
currOdlAssign = odlRec;
}
}
/// <summary>
/// Formattazione oraria impegno
/// </summary>
/// <param name="totSeconds"></param>
/// <returns></returns>
private string FormatEstTime(decimal totSeconds)
{
var tSpan = TimeSpan.FromSeconds((double)totSeconds);
string answ = EgwCoreLib.Lux.Core.DtUtils.FormatDateTime(tSpan);
return answ;
}
/// <summary>
/// Ricezione update prod --> rileggo i dati!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private async void PipeProd_EA_NewMessage(object? sender, EventArgs e)
{
await ReloadDataAsync();
}
/// <summary>
/// Legge i dati dei record completi
/// </summary>
private async Task ReloadDataAsync()
{
// prendo solo ordini stimati (NON ancora bilanciati o pianificati)
ListEstimRecords = await OrdRService.GetByStateMinAsync(OrderStates.Estimated, PeriodoSel.Inizio, PeriodoSel.Fine);
ListBalancedRecords = await PGService.GetByOrderStateAsync(OrderStates.Assigned);
ListOdl = await POService.GetUnassignOdlDtoAsync();
}
/// <summary>
/// Rimuove balance e riporta ordine ad estimated, reload dati
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task RemoveBalance(int OrderRowID)
{
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Sicuro di voler rimuovere la riga d'ordine dal carico attuale?"))
return;
// resetto riga ordine...
await OrdRService.UpdateStateAsync(OrderRowID, OrderStates.Estimated);
await ReloadDataAsync();
}
/// <summary>
/// Invio richiesta balance x un record di ProdGroup
/// </summary>
/// <param name="prodGroupItem"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task ReqBalance(ProductionGroupModel prodGroupItem)
{
// da rivedere come compone la richiesta...
if (prodGroupItem.WorkGroupList != null)
{
BalanceReqDto balData = new BalanceReqDto()
{
BarLenght = 10000,
};
var currPOR = ListEstimRecords?.FirstOrDefault(x => x.OrderRowID == prodGroupItem.OrderRowID) ?? new OrderRowModel();
var currRec = await OrdService.GetByIdAsync(currPOR.OrderID, true);
var dictReq = new Dictionary<string, double>();
// verifico se ho 1 sola macchina o molte...
int numMacc = prodGroupItem.WorkGroupList.Count;
if (numMacc == 1)
{
// SE vuoto il nome --> manual!
var curRec = prodGroupItem.WorkGroupList.FirstOrDefault();
string mName = string.IsNullOrEmpty(curRec.Key) ? "Manual" : curRec.Key;
dictReq.Add(mName, 1);
balData.MachineBalance = dictReq;
balData.TagList = curRec.Value.TagList;
}
else
{
List<string> tagList = new();
foreach (var cMacc in prodGroupItem.WorkGroupList)
{
dictReq.Add(cMacc.Key, 1.0 / numMacc);
tagList.AddRange(cMacc.Value.TagList);
}
// verifico tags distinct
balData.MachineBalance = dictReq;
balData.TagList = tagList.Distinct().ToList();
}
// preparo oggetto richiesta
Egw.Window.Data.Enums.QuestionModes cMode = Egw.Window.Data.Enums.QuestionModes.ORDER;
Egw.Window.Data.Enums.QuestionOrderSubModes cSubMode = Egw.Window.Data.Enums.QuestionOrderSubModes.BALANCE;
string reqKey = "";
// preparo la richiesta di bilanciamento
CalcRequestDTO calcReq = new CalcRequestDTO();
// elenco tags ricevuto...
List<string> TagList = balData.TagList;
string serTagList = string.Join(",", TagList);
// preparo richiesta serializzata e la accodo (viene inviata richiesta calcolo)
Dictionary<string, string> dictArgs = new Dictionary<string, string>();
// creo registrazione richiesta...
var ruid = await CRService.AddRequestAsync($"{currPOR.Envir}", $"{(int)cMode}-{(int)cSubMode}", currPOR.OrderRowUID);
dictArgs.Add("UID", currPOR.OrderRowUID);
dictArgs.Add("Group", $"{prodGroupItem.GrpIdx}");
// aggiungo RUID effettivo
dictArgs.Add("RUID", ruid);
dictArgs.Add("OrderUID", currRec.OrderCode);
dictArgs.Add("Mode", $"{(int)cMode}");
dictArgs.Add("SubMode", $"{(int)cSubMode}");
dictArgs.Add("TagList", serTagList);
// serializzo la richiesta di bilanciamento...
string reqBalance = JsonConvert.SerializeObject(balData.MachineBalance);
dictArgs.Add("ReqBalance", reqBalance);
dictArgs.Add("BarLenght", $"{balData.BarLenght}");
calcReq = new CalcRequestDTO()
{
DictExec = dictArgs,
EnvType = currPOR.Envir
};
// chiave: composta da cMode, submode, UID riga...
reqKey = $"{cMode}:{cSubMode}:{currPOR.OrderRowUID}.{prodGroupItem.GrpIdx}";
// invio richiesta
await PService.EnqueueRequestAsync("Balance", reqKey, calcReq);
// parto dalla history attuale
var currHist = currRec.LogHistory;
// aggiungo evento...
currHist.Add(new TaskHistDTO()
{
DtEvent = DateTime.Now,
UserName = "Default User",
Message = $"{reqKey}",
IconCss = "fa-solid fa-hourglass-start"
});
currRec.LogHistory = currHist;
await OrdService.UpsertAsync(currRec);
}
}
/// <summary>
/// Imposta periodo da filtro
/// </summary>
/// <param name="newPeriod"></param>
/// <returns></returns>
private async Task SetPeriodo(EgwCoreLib.Utils.DtUtils.Periodo newPeriod)
{
PeriodoSel = newPeriod;
await ReloadDataAsync();
UpdateTable();
}
/// <summary>
/// Filtro e paginazione
/// </summary>
private void UpdateTable()
{
#if false
// fix paginazione
ListRecords = ListFilt
.Skip(numRecord * (currPage - 1))
.Take(numRecord)
.ToList();
private global::System.Threading.Tasks.Task DoBalance(global::System.Int32 args)
{
throw new global::System.NotImplementedException();
}
private global::System.Threading.Tasks.Task DoBalance(global::System.Int32 args)
{
throw new global::System.NotImplementedException();
}
#endif
}
#endregion Private Methods
}
}