using EgwCoreLib.Lux.Core.Generic; using EgwCoreLib.Razor; 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 /// /// Dispose sottoscrizione canale /// 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? currBomList = null; private List? currDefaultSet = null; private Dictionary>? currDictItem = null; /// /// Oggetto ODL con assegnazione item x richiesta prod /// private OdlAssignDto? currOdlAssign = null; private List? ListBalancedRecords = null; private List? ListEstimRecords = null; private List? ListOdl = null; /// /// Periodo selezionato attuale /// 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; private BootstrapModal Modal = new(); private string mTitle = ""; private string mMessage = ""; private BootstrapModal.ModalMode mMode = BootstrapModal.ModalMode.ND; private Dictionary modalOpt = new Dictionary(); #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) { mTitle = "Attenzione"; mMessage = "Sicuro di voler Bilanciare la riga d'ordine?"; mMode = BootstrapModal.ModalMode.Confirm; modalOpt = new(); modalOpt.Add(true, "Si"); modalOpt.Add(false, "No"); if (!await Modal!.ShowAsync()) return; // verifico se mancassero le righe di ProdGroup e rigenero... List 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); } } } /// /// Richiede calcolo del prod /// /// /// private async Task DoCalculateProd(Dictionary> itemAvaiDict) { mTitle = "Attenzione"; mMessage = "Stai per inviare richiesta di generazione del file di produzione ODL, confermi?"; mMode = BootstrapModal.ModalMode.Confirm; modalOpt = new(); modalOpt.Add(true, "Si"); modalOpt.Add(false, "No"); if (!await Modal!.ShowAsync()) 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 dictArgs = new Dictionary(); // 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; } /// /// Creazione progetti da schedulare... /// /// /// private async Task DoCreateBatch(BatchCreateInfo batchData) { // per prima cosa creo il batch... var recBatch = await PBService.CreateAsync(batchData.NewBatch); List ordRowIdList = new List(); // se ok batch... if (recBatch != null) { List listOdl = new List(); // uso una Tupla var dictParts = new Dictionary<(int phaseId, int resId, string machine, int index), List>(); // 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 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(); } /// /// Invio richiesta creazione file x elenco PODL ricevuto /// /// /// private async Task DoCreateProd(OdlAssignDto odlRec) { mTitle = "Attenzione"; mMessage = "Sicuro di voler (ri)creare il file di produzione ODL?"; mMode = BootstrapModal.ModalMode.Confirm; modalOpt = new(); modalOpt.Add(true, "Si"); modalOpt.Add(false, "No"); if (!await Modal!.ShowAsync()) 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 dictArgs = new Dictionary(); // 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(); } /// /// Prepara selezione materiali x calcolo ODL /// /// /// 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()); } } // 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; } } /// /// Formattazione oraria impegno /// /// /// private string FormatEstTime(decimal totSeconds) { var tSpan = TimeSpan.FromSeconds((double)totSeconds); string answ = EgwCoreLib.Lux.Core.DateTimeUtils.FormatDateTime(tSpan); return answ; } /// /// Ricezione update prod --> rileggo i dati! /// /// /// /// private async void PipeProd_EA_NewMessage(object? sender, EventArgs e) { await ReloadDataAsync(); } /// /// Legge i dati dei record completi /// 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(); } /// /// Rimuove balance e riporta ordine ad estimated, reload dati /// /// /// private async Task RemoveBalance(int OrderRowID) { mTitle = "Attenzione"; mMessage = "Sicuro di voler rimuovere la riga d'ordine dal carico attuale?"; mMode = BootstrapModal.ModalMode.Confirm; modalOpt = new(); modalOpt.Add(true, "Si"); modalOpt.Add(false, "No"); if (!await Modal!.ShowAsync()) return; // resetto riga ordine... await OrdRService.UpdateStateAsync(OrderRowID, OrderStates.Estimated); await ReloadDataAsync(); } /// /// Invio richiesta balance x un record di ProdGroup /// /// /// /// 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(); // 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 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 TagList = balData.TagList; string serTagList = string.Join(",", TagList); // preparo richiesta serializzata e la accodo (viene inviata richiesta calcolo) Dictionary dictArgs = new Dictionary(); // 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); } } /// /// Imposta periodo da filtro /// /// /// private async Task SetPeriodo(EgwCoreLib.Utils.DtUtils.Periodo newPeriod) { PeriodoSel = newPeriod; await ReloadDataAsync(); UpdateTable(); } /// /// Filtro e paginazione /// 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 } }