using Egw.Window.Data; using EgwCoreLib.Lux.Core; using EgwCoreLib.Lux.Core.RestPayload; using EgwCoreLib.Lux.Data.DbModel.Config; using EgwCoreLib.Lux.Data.DbModel.Sales; using EgwCoreLib.Lux.Data.DbModel.Utils; using EgwCoreLib.Lux.Data.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using Newtonsoft.Json; using NLog; using WebWindowComplex; using WebWindowComplex.DTO; using static EgwCoreLib.Lux.Core.Enums; using static WebWindowComplex.LayoutConst; namespace Lux.UI.Components.Compo { public partial class OfferRowMan : IDisposable { #region Public Properties [Parameter] public OfferModel CurrRecord { get; set; } = null!; [Parameter] public DisplayMode DisplayMode { get; set; } = DisplayMode.Standard; [Parameter] public EventCallback EC_Updated { get; set; } #endregion Public Properties #region Public Methods /// /// Dispose sottoscrizione canale /// public void Dispose() { DLService.PipeUpdate.EA_NewMessage -= PipeUpdate_EA_NewMessage; DLService.PipePng.EA_NewMessage -= PipePng_EA_NewMessage; DLService.PipeSvg.EA_NewMessage -= PipeSvg_EA_NewMessage; DLService.PipeHwOpt.EA_NewMessage -= PipeHwOpt_EA_NewMessage; DLService.PipeProfElement.EA_NewMessage -= PipeProfElement_EA_NewMessage; DLService.PipeProfList.EA_NewMessage -= PipeProfList_EA_NewMessage; DLService.PipeShape.EA_NewMessage -= PipeShape_EA_NewMessage; } #endregion Public Methods #region Protected Fields /// /// Predisposizione valori live SVG/JWD /// protected LivePayload CurrData = new LivePayload(); /// /// Configurazione elenchi anagrafiche /// protected BaseListPayload SetupList = new BaseListPayload(); #endregion Protected Fields #region Protected Properties /// /// Margine medio calcolato x offerta /// protected double GrandTotMargin { get { double answ = 0; if (AllRecords != null && AllRecords.Count > 0) { double totPrice = AllRecords.Sum(x => x.TotalPrice); double totCost = AllRecords.Sum(x => x.TotalCost); if (totPrice > 0) { answ = (totPrice - totCost) / totPrice; } } return answ; } } /// /// Importo totale calcolato x offerta /// protected double GrandTotNumItems { get { double answ = 0; if (AllRecords != null && AllRecords.Count > 0) { answ = AllRecords.Sum(x => x.ProdItemQtyTot); } return answ; } } /// /// Importo totale calcolato x offerta /// protected double GrandTotPrice { get => AllRecords != null && AllRecords.Count > 0 ? AllRecords.Sum(x => x.TotalPrice) : 0; } /// /// Num totale obj calcolato x offerta /// protected double GrandTotQty { get => AllRecords != null && AllRecords.Count > 0 ? AllRecords.Sum(x => x.Qty) : 0; } [Inject] protected IWebHostEnvironment HostEnv { get; set; } = null!; #endregion Protected Properties #region Protected Methods protected void ClosePopup() { CurrEditMode = EditMode.None; EditRecord = null; addFromTemplate = false; } /// /// Aggiunge una nuova riga vuota come nota sotto il record selezionato oppure in coda... /// /// protected async Task DoAddNote() { int numRow = AllRecords.Count + 1; if (EditRecord != null) { numRow = EditRecord.RowNum + 1; } OfferRowModel newNote = new OfferRowModel() { OfferID = CurrRecord.OfferID, Envir = CurrRecord.Envir, RowNum = numRow, OfferRowUID = "", Qty = 0, BomCost = 0, BomPrice = 0, StepCost = 0, StepPrice = 0 }; await DLService.OffertRowUpsert(newNote); await ReloadData(); UpdateTable(); } /// /// Aggiunge una nuova riga ordine in coda... /// /// protected async Task DoAddOrderRow(TemplateRowModel selTemplate) { int numRow = AllRecords.Count + 1; if (EditRecord != null) { numRow = EditRecord.RowNum + 1; } OfferRowModel newSOR = new OfferRowModel() { AwaitBom = true, AwaitPrice = true, OfferID = CurrRecord.OfferID, Envir = CurrRecord.Envir, Inserted = DateTime.Now, RowNum = numRow, OfferRowUID = "", Qty = 1, SellingItemID = selTemplate.SellingItemID, SerStruct = selTemplate.SerStruct, BomCost = 0, BomPrice = 0, StepCost = 0, StepPrice = 0 }; #if false // se è window aggiungo "{}" come serStruct sennò non la prende bene... if (CurrRecord.Envir == EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { newSOR.SerStruct = "{}"; } #endif addFromTemplate = false; await DLService.OffertRowUpsert(newSOR); await ReloadData(); UpdateTable(); } #if false /// /// Aggiunge una nuova riga ordine in coda... /// /// protected async Task DoAddOrderRowFromSellItem(int sellItemID) { int numRow = AllRecords.Count + 1; if (EditRecord != null) { numRow = EditRecord.RowNum + 1; } OfferRowModel newSOR = new OfferRowModel() { AwaitBom = true, AwaitPrice = true, OfferID = CurrRecord.OfferID, Envir = CurrRecord.Envir, Inserted = DateTime.Now, RowNum = numRow, OfferRowUID = "", Qty = 1, SellingItemID = sellItemID, BomCost = 0, BomPrice = 0, StepCost = 0, StepPrice = 0 }; // se è window aggiungo "{}" come serStruct sennò non la prende bene... if (CurrRecord.Envir == EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { newSOR.SerStruct = "{}"; } addFromTemplate = false; await DLService.OffertRowUpsert(newSOR); await ReloadData(); UpdateTable(); } #endif /// /// Annullamento modifica /// /// /// protected async Task DoCancel() { isLoading = true; CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); await DLService.FlushCacheOffersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); isLoading = false; } /// /// Clona riga richiesta /// /// protected async Task DoClone(OfferRowModel rec2clone) { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler duplicare la riga corrente?")) return; // calcolo indice riga... int numRow = totalCount + 1; OfferRowModel newRec = new OfferRowModel() { AwaitBom = true, AwaitPrice = true, Envir = rec2clone.Envir, FileName = rec2clone.FileName, FileResource = rec2clone.FileResource, FileSize = rec2clone.FileSize, Inserted = DateTime.Now, ItemBOM = rec2clone.ItemBOM, ItemSteps = rec2clone.ItemSteps, Modified = DateTime.Now, Note = rec2clone.Note, OfferID = OfferID, OfferRowUID = "", Qty = rec2clone.Qty, RowNum = numRow, SellingItemID = rec2clone.SellingItemID, SerStruct = rec2clone.SerStruct, StepCost = rec2clone.StepCost, StepPrice = rec2clone.StepPrice, }; // salvo sul DB await DLService.OffertRowUpsert(newRec); // chiamo update record che non hanno UID x questo ordine var list2fix = await DLService.OffertRowFixUid(OfferID); if (list2fix != null && list2fix.Count > 0) { // rileggo i miei record... await ReloadData(); var listCalc = SorListCalc(); foreach (var item in listCalc) { // se UID � tra quelli da ricalcolare... if (list2fix.Contains(item.OfferRowUID)) { // chiedo BOM e immagine await reqBomUpdate(item); } } } await ReloadData(); UpdateTable(); } /// /// Eliminazione riga offerta /// /// /// protected async Task DoDelete(OfferRowModel rec2del) { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler eliminare la riga corrente?
Codice: {rec2del.OfferRowUID} | {rec2del.Note} | importo tot: {rec2del.TotalPrice}")) return; await DLService.OffertRowDelete(rec2del); // elimino cache img await ICService.DeleteSvgAsync(rec2del.OfferRowUID, rec2del.Envir); await ReloadData(); UpdateTable(); } /// /// Va in edit della riga richiesta /// /// protected void DoEdit(OfferRowModel curRec) { // imposto edit record EditRecord = curRec; /// modalita edit: gestione valori campi record CurrEditMode = EditMode.RecData; isLoading = false; } /// /// Edit del file: /// - abilitazione fileUpload /// - anteprima grande (live) /// /// protected void DoEditFile(OfferRowModel curRec) { EditRecord = curRec; /// modalit�edit: gestione JWD CurrEditMode = EditMode.File; } /// /// Apre editor finestre del record richiesto /// /// protected void DoEditJwd(OfferRowModel curRec) { EditRecord = curRec; /// modalit�edit: gestione JWD CurrEditMode = EditMode.SerStruc; // preparazione dati da record corrente PrepareWindowData(EditRecord.SerStruct); // reset prev prevJwd = ""; } /// /// Salvataggio edit record + reload /// /// /// protected async Task DoSave(OfferRowModel curRec) { isLoading = true; // salvo record modificato... await DLService.OffertRowUpsert(curRec); // reset CurrEditMode = EditMode.None; EditRecord = null; await DLService.FlushCacheOffersAsync(); await ReloadData(); UpdateTable(); isLoading = false; } /// /// Seleziono riga senza cambiare modalit� editing /// /// protected void DoSelect(OfferRowModel curRec) { // imposto edit record EditRecord = curRec; /// modalit�edit: gestione valori campi record CurrEditMode = EditMode.None; } private List ListAllCatalog = new(); protected async Task DoSelectItem() { addFromTemplate = true; ListCataloghi = ListAllCatalog.Where(x => x.Envir == CurrRecord.Envir).ToList(); } /// /// Imposta modalita edit ciclo di lavoro /// /// protected void DoSwapJobCycle(OfferRowModel currRow) { CurrEditMode = EditMode.JobCycle; selectBom(currRow); } /// /// Imposta modalita ad edit BOM /// /// protected void DoSwapMat(OfferRowModel currRow) { CurrEditMode = EditMode.BOM; selectBom(currRow); } /// /// Display fileSize scalato /// /// /// protected string fSize(long size) { return EgwCoreLib.Utils.FileHelpers.SizeSuffix(size, 1); } /// /// Formattazione testo come html x display /// protected MarkupString HtmlConv(string rawData) { return (MarkupString)rawData.Replace(Environment.NewLine, "
").Replace("\n", "
");//.Replace(" ", " "); } /// /// Calcolo URL immagine /// /// /// /// protected string imgUrl(string imgUid, string env) { // cast string su env.. EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW; Enum.TryParse(env, out envir); return ICService.ImageUrl($"{apiUrl}/{imgBasePath}", false, imgUid, envir); } /// /// Forza parametri generali selezionati nell'offerta /// /// protected async Task OfferForceParameters() { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler impostare i parametri selezionati per l'offerta?")) return; // recupero obj dizionario x i parametri compresi... ParamDict CurrSel = new ParamDict(CurrRecord.DictPresel); // metto a waiting tutte le righe con bom... var listCalc = SorListCalc(); foreach (var item in listCalc) { await DLService.OffertRowUpdateAwaitStateAsync(item.OfferRowID, true, true); // poich� non � gestito evento ritorno update window interno si "scassa" --> try catch/ if FALSE try { string rColor = CurrSel.GetVal("Color"); string rGlass = CurrSel.GetVal("Glass"); string rProfile = CurrSel.GetVal("Profile"); string rWood = CurrSel.GetVal("Wood"); var newSerStruct = SerialMan.MassUpdate((string)item.SerStruct, null, null, rColor, rWood, rGlass, rProfile); await DLService.OffertRowUpdateSerStruct(item.OfferRowID, newSerStruct); } catch { } } await InvokeAsync(StateHasChanged); // verifica preliminare UID await DLService.OffertRowFixUid(OfferID); // ricalcolo di tutte le BOM e relativi prezzi... foreach (var item in listCalc) { // chiedo BOM e immagine await reqBomUpdate(item); } //await DoRecalcTemplate(); } /// /// Aggiornamento costing completo: /// - verifica UID /// - ricalcolo BOM /// - update prezzi /// /// protected async Task OfferUpdateAllCosting() { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler ricalcolare/validare in toto l'offerta?")) return; // metto a waiting tutte le righe con bom... var listCalc = SorListCalc(); foreach (var item in listCalc) { await DLService.OffertRowUpdateAwaitStateAsync(item.OfferRowID, true, true); } await InvokeAsync(StateHasChanged); // verifica preliminare UID await DLService.OffertRowFixUid(OfferID); // fixme todo da riverificare con calcolo BOM funzionante #if false // rileggo i record... await ReloadData(); #endif // ricalcolo di tutte le BOM e relativi prezzi... foreach (var item in listCalc) { // chiedo BOM e immagine await reqBomUpdate(item); } #if false await DoRecalcOffer(); #endif } /// /// Verifica e ricalcolo dei prezzi degli items nell'offerta /// /// protected async Task OfferUpdatePrices() { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler ricalcolare a costi correnti offerta?")) return; await DoRecalcOffer(true); } /// /// Verifica after render x stato interattivo pagina /// /// protected override void OnAfterRender(bool firstRender) { if (firstRender) { // JS interop or data fetches go here isInteractive = true; } } /// /// init obj /// protected override async Task OnInitializedAsync() { ConfInit(); prevJwd = ""; await ReloadBaseList(); DLService.PipeUpdate.EA_NewMessage += PipeUpdate_EA_NewMessage; DLService.PipePng.EA_NewMessage += PipePng_EA_NewMessage; DLService.PipeSvg.EA_NewMessage += PipeSvg_EA_NewMessage; DLService.PipeHwOpt.EA_NewMessage += PipeHwOpt_EA_NewMessage; DLService.PipeProfElement.EA_NewMessage += PipeProfElement_EA_NewMessage; DLService.PipeProfList.EA_NewMessage += PipeProfList_EA_NewMessage; DLService.PipeShape.EA_NewMessage += PipeShape_EA_NewMessage; } protected override async Task OnParametersSetAsync() { await ReloadData(); UpdateTable(); } /// /// Lancia la richiesta di ricaolo della BOM dal JWD (o equivalente) /// /// protected async Task RequestBom(OfferRowModel currRec) { if (!await JSRuntime.InvokeAsync("confirm", $"Confermi di voler ricalcolare la BOM?")) return; await reqBomUpdate(currRec); } /// /// Css di verifica riga selezionata /// /// /// protected string RowClass(OfferRowModel selRow) { return EditRecord != null && EditRecord.OfferRowID == selRow.OfferRowID ? "table-info" : ""; } #endregion Protected Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); private List AllColors = new(); private List AllConfEnvir = new(); private List AllConfGlass = new(); private List AllConfHardware = new(); private List AllConfWood = new(); private List AllRecords = new List(); private string apiUrl = ""; private List AvailColorMaterialList = new List(); private List AvailFamilyHardwareList = new List(); private List AvailGlassList = new List(); private List AvailHardwareList = new(); private List AvailMaterialList = new List(); /// /// Lista profili da DB /// private List AvailProfileList = new(); /// /// Lista profili da Redis (old way) /// private List AvailProfileListOld = new List(); private Dictionary> AvailThresholdDict = new Dictionary>(); /// /// Base path x network share files /// private string basePath = "unsafe_uploads"; private string calcTag = "calc"; private EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS cEnvir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW; /// /// Channel update HwOptions /// private string chHwOpt = ""; /// /// Channel update PNG /// private string chPng = ""; /// /// Channel update Profile List /// private string chProfElem = ""; /// /// Channel update Profile List /// private string chProfList = ""; /// /// Channel update Shape /// private string chShape = ""; /// /// Channel update SVG /// private string chSvg = ""; private List currAreaProfiles = new(); private List? CurrBomList = null; /// /// Modalit� editint attiva /// private EditMode CurrEditMode = EditMode.None; private Dictionary currGroupShape = new(); private Dictionary currHwOption = new(); private int currPage = 1; private string currPng = ""; private List currProfList = new(); private string currSvg = ""; /// /// Record in Edit corrente x modifica file/serializzato /// private OfferRowModel? EditRecord = null; /// /// Abilita edit massivo record ITEM /// private bool enableMassEdit = false; private string genericBasePath = ""; private string imgBasePath = ""; /// /// Semaforo x definire se sia gia in modalita ionterattiva o di prerendering /// private bool isInteractive = false; private bool isLoading = false; private List ListRecords = new(); #if false private List ListSellItems = new List(); #endif private List ListCataloghi = new(); private List ListTemplateAll = new(); private List ListTemplateCurr = new(); private int CurrCatalog { get => _currCatalog; set { if (_currCatalog != value) { _currCatalog = value; // imposto template relativi ListTemplateCurr = ListTemplateAll.Where(x => x.TemplateID == value).ToList(); } } } private int _currCatalog = 0; private int numRecord = 10; /// /// Versione originale (pre edit) /// private string origJwd = ""; private List PreparedFile = new(); /// /// Versione precedente JWD x test e confronto /// private string prevJwd = ""; /// /// Dizionario richieste /// private Dictionary reqDict = new Dictionary(); /// /// Boolean selezione prodotto da aggiungere (template) /// private bool addFromTemplate = false; private int totalCount = 0; #endregion Private Fields #region Private Properties private Dictionary> AvailThreshold { get; set; } = new Dictionary>() { {"Profilo78", new List() { new Threshold(3, "Bottom")}}, {"ProfiloSaomad", new List(){ new Threshold(3, "Bottom")}} #if false {"Profilo78", new List() { new Threshold(3, "Bottom"), new Threshold(1, "Threshold")}}, {"ProfiloSaomad", new List(){ new Threshold(3, "Bottom"), new Threshold(2, "BottomWaterdrip"), new Threshold(1, "Threshold")}} #endif }; [Inject] private ConfigDataService CDService { get; set; } = null!; [Inject] private IConfiguration Config { get; set; } = null!; [Inject] private CalcRuidService CRService { get; set; } = null!; [Inject] private CalcRequestService CService { get; set; } = null!; [Inject] private IWebHostEnvironment CurrEnv { get; set; } = null!; [Inject] private DataLayerServices DLService { get; set; } = null!; /// /// Costo totale calcolato x offerta /// private double GrandTotCost { get => AllRecords != null && AllRecords.Count > 0 ? AllRecords.Sum(x => x.TotalCost) : 0; } [Inject] private ImageCacheService ICService { get; set; } = null!; [Inject] private IJSRuntime JSRuntime { get; set; } = null!; /// /// ID Offerta corrente /// private int OfferID { get => CurrRecord.OfferID; } #endregion Private Properties #region Private Methods /// /// Effettua vera richiesta della BOM /// /// private async Task callRefreshProfList() { Dictionary DictExec = new Dictionary(); var cMode = Egw.Window.Data.Enums.QuestionModes.CONFIG; var cSubMode = Egw.Window.Data.Enums.QuestionConfSubModes.PROFILELIST; // compongo righiesta string reqUid = "Default"; DictExec.Add("Mode", $"{(int)cMode}"); DictExec.Add("UID", reqUid); // creo registrazione richiesta... var ruid = await CRService.AddRequestAsync($"{cEnvir}", $"{cMode}-{cSubMode}", reqUid); // aggiungo RUID effettivo DictExec.Add("RUID", ruid); DictExec.Add("SubMode", $"{(int)cSubMode}"); CalcRequestDTO req = new CalcRequestDTO() { EnvType = cEnvir, DictExec = DictExec }; // chiamo la chiamata POST alla API, che manda la richiesta via REDIS await CService.CallRestPost($"{apiUrl}/{genericBasePath}", $"{calcTag}/{reqUid}", req); } /// /// Chiude edit andando eventualmente a salvare /// /// /// private async Task CloseEdit(bool doSave) { // Proseguo solo se sono in interattivo (NO prerender pagina) if (isInteractive) { // ...se ho editing if (EditRecord != null) { bool updateBom = false; // SE richiesto salvataggio... if (doSave) { // salvo su DB! await DLService.OffertRowUpdateSerStruct(EditRecord.OfferRowID, prevJwd); // salvo nel record corrente! EditRecord.SerStruct = prevJwd; updateBom = true; } else // altrimenti ricalcolo valore salvato { prevJwd = EditRecord.SerStruct; CurrData.CurrJwd = EditRecord.SerStruct; } if (updateBom) { await reqBomUpdate(EditRecord); } // aggiorno nel dizionari if (reqDict.ContainsKey("SerializedData")) { reqDict["SerializedData"] = prevJwd; } if (reqDict != null && reqDict.Count > 0) { // chiamo richiesta update CalcRequestDTO calcRequestDTO = new CalcRequestDTO(); calcRequestDTO.EnvType = EditRecord.Envir; calcRequestDTO.DictExec = reqDict; // chiamo la chiamata POST alla API, che manda la richiesta via REDIS await ICService.CallRestPost($"{apiUrl}/{genericBasePath}", $"{calcTag}/{EditRecord.OfferRowUID}", calcRequestDTO); } EditRecord = null; CurrEditMode = EditMode.None; } } } /// /// Chiude edit con preprocess x caso JWD /// /// /// private Task CloseEditJwd(DataSave infoSave) { prevJwd = infoSave.currJwd; return CloseEdit(infoSave.ForceSave); } private void ConfInit() { basePath = Config.GetValue("ServerConf:FileSharePath") ?? "unsafe_uploads"; apiUrl = Config.GetValue("ServerConf:Prog.ApiUrl") ?? ""; imgBasePath = Config.GetValue("ServerConf:ImageBaseUrl") ?? ""; genericBasePath = Config.GetValue("ServerConf:GenericBaseUrl") ?? ""; calcTag = Config.GetValue("ServerConf:CalcTag") ?? "calc"; chHwOpt = Config.GetValue("ServerConf:ChannelHwOpt") ?? ""; chPng = Config.GetValue("ServerConf:ChannelPng") ?? ""; chProfElem = Config.GetValue("ServerConf:ChannelProfElem") ?? ""; chProfList = Config.GetValue("ServerConf:ChannelProfList") ?? ""; chShape = Config.GetValue("ServerConf:ChannelShape") ?? ""; chSvg = Config.GetValue("ServerConf:ChannelSvg") ?? ""; } /// /// Eliminazione file (old) /// /// Nome secure da impiegare /// Contenuto file private bool DeleteOldFile(string folderPath, string secureName) { bool answ = false; if (!string.IsNullOrEmpty(folderPath)) { // calcolo path file... string filePath = Path.Combine(basePath, folderPath, secureName); // se esiste... if (File.Exists(filePath)) { File.Delete(filePath); } } return answ; } /// /// Esecuzione azione richiesta /// /// Azione richiesta private void DoAction(LayoutConst.DataAction actReq) { switch (actReq) { case LayoutConst.DataAction.None: break; case LayoutConst.DataAction.ResetDictShape: CurrData.DictShape = new Dictionary(); break; case LayoutConst.DataAction.ResetHwOpt: CurrData.DictOptionsXml = new Dictionary(); break; case DataAction.ResetDimElem: CurrData.ProfElementList = new List(); break; default: break; } } private async Task DoRecalcOffer(bool forceResetCalc) { isLoading = true; if (forceResetCalc) { await setAwaitPrice(true, false); UpdateTable(); await InvokeAsync(StateHasChanged); await Task.Delay(300); } await DLService.OffertUpdateCost(OfferID); if (forceResetCalc) { await Task.Delay(300); await setAwaitPrice(false, true); } // rileggo dati await ReloadData(); UpdateTable(); isLoading = false; await EC_Updated.InvokeAsync(true); } /// /// Prepara URL x download file JWD /// /// /// /// /// private string DownloadUrl(string currUid, string objType = "SOR", EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS envir = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW) { return $"{apiUrl}/file/{currUid}?objType={objType}&env={envir}"; } /// /// Salvataggio del JWD aggiornato nella mia riga di offerta /// /// private async Task ExecRequest(Dictionary CurrArgs) { // Proseguo solo se sono in interattivo (NO prerender pagina) if (isInteractive) { // ...se ho editing if (EditRecord != null) { // SE contiene il mio Jwd... if (CurrArgs.ContainsKey("SerializedData")) { string serStruct = CurrArgs["SerializedData"]; // controllo SE variato... if (!prevJwd.Equals(serStruct) || !EgwCoreLib.Utils.DictUtils.DictAreEqual(reqDict, CurrArgs)) { // aggiorno val prev reqDict = CurrArgs; prevJwd = serStruct; // aggiorno live data CurrData.CurrJwd = serStruct; // chiamo richiesta update CalcRequestDTO calcRequestDTO = new CalcRequestDTO(); calcRequestDTO.EnvType = EditRecord.Envir; calcRequestDTO.DictExec = reqDict; // chiamo la chiamata POST alla API, che manda la richiesta via REDIS await ICService.CallRestPost($"{apiUrl}/{genericBasePath}", $"{calcTag}/{EditRecord.OfferRowUID}", calcRequestDTO); #if false // salvo su DB! await DLService.OffertRowUpdateSerStruct(EditRecord.OfferRowID, serStruct); #endif } } } } } /// /// Path da parent record /// /// /// private string FolderPath(int objID) { return $"SO-{objID:X8}"; } /// /// Svuota cache corrente + rilegge dati /// private async Task ForceReloadData() { isLoading = true; CurrEditMode = EditMode.None; EditRecord = null; await Task.Delay(20); await DLService.FlushCacheOffersAsync(); await Task.Delay(20); await ReloadData(); UpdateTable(); isLoading = false; } /// /// Restituisce il contenuto del file salvato /// /// /// /// private string LoadFileContent(string folderPath, string secureName) { string answ = ""; if (!string.IsNullOrEmpty(folderPath)) { try { // calcolo path file... string filePath = Path.Combine(basePath, folderPath, secureName); if (File.Exists(filePath)) { answ = File.ReadAllText(filePath); } } catch (Exception exc) { Log.Error($"Exception on LoadFileContent{Environment.NewLine}{exc}"); } } return answ; } /// /// Ricevuto HwOpt, processo /// /// /// private async void PipeHwOpt_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.Equals($"{chHwOpt}:{EditRecord.OfferRowUID}")) { // se non è vuoto deserializzo if (currArgs.newMessage.Count() > 2) { var rawDict = JsonConvert.DeserializeObject>(currArgs.newMessage) ?? new Dictionary(); currHwOption = rawDict; } else { currHwOption = new Dictionary(); } // salvo in live data... CurrData.DictOptionsXml = currHwOption; } await InvokeAsync(StateHasChanged); } } } private async void PipePng_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.Equals($"{chPng}:{EditRecord.OfferRowUID}")) { currPng = currArgs.newMessage; // non devo passarlo al componente... #if false // salvo in live data... CurrData.SvgPreview = currSvg; #endif } await InvokeAsync(StateHasChanged); } } } /// /// Ricevuto ProfElem, processo /// /// /// private async void PipeProfElement_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.StartsWith($"{chProfElem}:{EditRecord.OfferRowUID}")) { // deserializzo il dizionario delle risposte... var rawDict = JsonConvert.DeserializeObject>(currArgs.newMessage); currAreaProfiles = rawDict ?? new List(); CurrData.ProfElementList = currAreaProfiles; } await InvokeAsync(StateHasChanged); } } } /// /// Ricevuta profile list, processo /// /// /// private async void PipeProfList_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.Equals($"{chProfList}:{EditRecord.OfferRowUID}")) { try { // nuova gestione da DB: attendo 500ms che il DB sia aggiornato await Task.Delay(500); // rileggo DB x info profili AvailProfileList = await DLService.ConfProfileGetAllAsync(); // converto i profili nel nuovo formato x payload... var profList = AvailProfileList.Select(x => new ProfilePayload() { ProfileName = x.Code, ThresholdList = x.ThresholdList, ParameterDict = x.ProfileDataDict }).ToList(); SetupList.ProfileList = profList; } catch { } } await InvokeAsync(StateHasChanged); } } } /// /// Ricevuta shape, procersso /// /// /// private async void PipeShape_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { #if false // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.Equals($"{shapeChannel}:{EditRecord.OfferRowUID}")) { currGroupShape = currArgs.newMessage; // salvo in live data... CurrData.DictShape = currGroupShape; } await InvokeAsync(StateHasChanged); } #endif // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.StartsWith($"{chShape}:{EditRecord.OfferRowUID}")) { // deserializzo il dizionario delle risposte... var rawDict = JsonConvert.DeserializeObject>(currArgs.newMessage); #if false int groupId = 0; // verifica del groupID... int.TryParse(currArgs.msgUid.Replace($"{shapeChannel}:{windowUid}:", ""), out groupId); if (currGroupShape.ContainsKey(groupId)) { currGroupShape[groupId] = currArgs.newMessage; } else { currGroupShape.Add(groupId, currArgs.newMessage); } #endif currGroupShape = rawDict ?? new Dictionary(); CurrData.DictShape = currGroupShape; } await InvokeAsync(StateHasChanged); } } } /// /// Ricevuto SVG, se � il mio lo aggiorno... /// /// /// private async void PipeSvg_EA_NewMessage(object? sender, EventArgs e) { // vale SOLO SE sono in editing... if (EditRecord != null) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { if (currArgs.msgUid.Equals($"{chSvg}:{EditRecord.OfferRowUID}")) { currSvg = currArgs.newMessage; // salvo in live data... CurrData.SvgPreview = currSvg; } await InvokeAsync(StateHasChanged); } } } /// /// Task verifica update ricevuti /// /// /// private async void PipeUpdate_EA_NewMessage(object? sender, EventArgs e) { // aggiorno visualizzazione PubSubEventArgs currArgs = (PubSubEventArgs)e; // conversione on-the-fly SVG da mostrare if (!string.IsNullOrEmpty(currArgs.newMessage)) { // cerco se faccia parte dei record correnti... var recFound = AllRecords.Any(x => x.OfferRowUID == currArgs.newMessage); if (recFound) { isLoading = true; await Task.Delay(1); // se si tratta dell'UID corrente --> fa update await DoRecalcOffer(false); await InvokeAsync(StateHasChanged); } } await Task.Delay(1); } /// /// Preparazione dati x componente edit JWD /// /// private void PrepareWindowData(string currJwd) { // converto i profili nel nuovo formato x payload... var profList = AvailProfileList.Select(x => new ProfilePayload() { ProfileName = x.Code, ThresholdList = x.ThresholdList, ParameterDict = x.ProfileDataDict }).ToList(); // preparo conf oggetti x controllo SetupList = new BaseListPayload() { ColorMaterial = AvailColorMaterialList, FamilyHardware = AvailFamilyHardwareList, Glass = AvailGlassList, Hardware = AvailHardwareList, Material = AvailMaterialList, ProfileList = profList }; CurrData = new LivePayload() { CurrJwd = currJwd, SvgPreview = currSvg }; } /// /// init classi configurazione /// /// private async Task ReloadBaseList() { // leggo cataloghi e template relativi... ListAllCatalog = await DLService.TemplateGetAllAsync(); ListTemplateAll = await DLService.TemplateRowGetAllAsync(); // lettura config setup varie da DB/Cache Redis AllConfEnvir = await DLService.ConfEnvirParamGetAllAsync(); AllConfGlass = await DLService.ConfGlassGetAllAsync(); AvailProfileList = await DLService.ConfProfileGetAllAsync(); // FixMe Todo: eliminare da REDIS e usare elenco DB solamente... var rawProfiles = CDService.ProfileList(cEnvir, "Default"); // se fosse vuoto chiamo update... if (rawProfiles == null || rawProfiles.Count == 0) { await callRefreshProfList(); // aspetto 200ms... e richiedo! await Task.Delay(200); rawProfiles = CDService.ProfileList(cEnvir, "Default"); } AvailProfileListOld = rawProfiles; #if false // dizionario dei profili soglia AvailThresholdDict = CDService.ProfileThreshDict(cEnvir); #endif var rawHw = CDService.HwModelList(cEnvir, "HW.AGB"); // hw filtro solo validi... AllConfHardware = rawHw .Where(x => !x.FamilyName.Equals(x.Description, StringComparison.OrdinalIgnoreCase)) .ToList(); AllConfWood = await DLService.ConfWoodGetAllAsync(); AllColors = await DLService.GenValGetFiltAsync("WoodCol"); // conversione tipi AvailGlassList = AllConfGlass .Select(x => x.Description) .ToList(); AvailFamilyHardwareList = AllConfHardware .DistinctBy(x => x.FamilyName) .OrderBy(x => x.FamilyName) .Select(x => x.FamilyName) .ToList(); AvailHardwareList = AllConfHardware .Select(x => new Egw.Window.Data.Hardware(x.Id, x.FamilyName, x.Description, x.OpeningType, x.Shape, x.SashQty, x.SashPosition)) .ToList(); AvailMaterialList = AllConfWood .Select(x => x.Description) .ToList(); // FixMe Todo: aggiunta profili (manca anche nel costruttore...) AvailColorMaterialList = AllColors .OrderBy(x => x.Index) .Select(x => x.ValString) .ToList(); } /// /// Legge i dati dei record completi /// private async Task ReloadData() { if (OfferID > 0) { AllRecords = await DLService.OfferRowGetByOffer(OfferID); totalCount = AllRecords.Count(); } } /// /// Effettua vera richiesta della BOM /// /// /// private async Task reqBomUpdate(OfferRowModel currRec) { // salvo richiesta BOM su record currRec.AwaitBom = true; currRec.AwaitPrice = true; await DLService.OffertRowUpdateAwaitStateAsync(currRec.OfferRowID, true, true); // preparo la domanda serializzata Dictionary DictExec = new Dictionary(); // verifico parametri da conf envir... var envRec = AllConfEnvir.FirstOrDefault(x => x.EnvirID == currRec.Envir); string serKey = envRec != null ? envRec.SerStrucKey : "SerializedData"; // cablata la BOM DictExec.Add("Mode", $"{(int)Egw.Window.Data.Enums.QuestionModes.BOM}"); // UID cablato x ora... DictExec.Add("UID", currRec.OfferRowUID); // aggiungo file secondo ambiente... switch (currRec.Envir) { case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW: DictExec.Add(serKey, currRec.SerStruct); break; case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.BEAM: case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WALL: case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.CABINET: // rileggo da file... string folderPath = FolderPath(currRec.OfferID); string rawData = LoadFileContent(folderPath, currRec.FileResource); DictExec.Add(serKey, rawData); DictExec.Add("FileName", currRec.FileName); break; case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.NULL: default: break; } CalcRequestDTO req = new CalcRequestDTO() { EnvType = currRec.Envir, DictExec = DictExec }; await InvokeAsync(StateHasChanged); // chiamo la chiamata POST alla API, che manda la richiesta via REDIS await CService.CallRestPost($"{apiUrl}/{genericBasePath}", $"{calcTag}/{currRec.OfferRowUID}", req); } /// /// Salvataggio dei dati del file caricato /// /// Dizionario info file private void SaveFile(Dictionary fileDict) { // verifico di essere in edit... if (EditRecord != null) { string folderPath = FolderPath(EditRecord.OfferID); // verifico di avere parametri... if (fileDict != null && fileDict.Count > 0) { string secureName = ""; string content = ""; if (fileDict.ContainsKey("secureName")) { secureName = fileDict["secureName"]; } if (fileDict.ContainsKey("content")) { content = fileDict["content"]; } if (!string.IsNullOrEmpty(folderPath) && !string.IsNullOrEmpty(secureName) && !string.IsNullOrEmpty(content)) { // salvo! SaveFileContent(folderPath, secureName, content); } } // altrimenti signifca cleanup eventuale vecchio file... else { DeleteOldFile(folderPath, EditRecord.FileResource); } } } /// /// Esegue salvataggio del file ricevuto /// /// Path relativo x file (tipicamente UID parent order) /// Nome secure da impiegare /// Contenuto file private bool SaveFileContent(string folderPath, string secureName, string content) { bool answ = false; if (!string.IsNullOrEmpty(folderPath)) { // calcolo path file... string filePath = Path.Combine(basePath, folderPath, secureName); string? directoryPath = Path.GetDirectoryName(filePath); try { if (!string.IsNullOrEmpty(directoryPath)) { Directory.CreateDirectory(directoryPath); } File.WriteAllText(filePath, content); } catch (Exception exc) { Log.Error($"Exception on save{Environment.NewLine}{exc}"); } } return answ; } /// /// Selezione e fix dati BOM /// /// /// private void selectBom(OfferRowModel currRow) { EditRecord = currRow; CurrBomList = DLService.OffertGetBomList(EditRecord); if (CurrBomList.Any(x => x.ItemID == 0)) { CurrBomList = DLService.BomFixItemId(CurrBomList); } } private async Task setAwaitPrice(bool awaitPrice, bool flushCache) { foreach (var item in AllRecords) { item.AwaitPrice = awaitPrice; await DLService.OffertRowUpdateAwaitStateAsync(item.OfferRowID, null, awaitPrice, flushCache); } } /// /// Verifia ammissibilit� display btn ricalcolo BOM da item /// /// /// private bool ShowBom(OfferRowModel reqItem) { bool answ = false; if (DisplayMode == EgwCoreLib.Lux.Core.Enums.DisplayMode.Edit) { switch (reqItem.Envir) { case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WINDOW: answ = !string.IsNullOrEmpty(reqItem.SerStruct) && reqItem.SerStruct.Length > 2; break; case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.BEAM: case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.WALL: case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.CABINET: // da cambiare con ricerca file su disco?!? answ = !string.IsNullOrEmpty(reqItem.FileResource) && !string.IsNullOrEmpty(reqItem.FileName); break; case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.NULL: default: break; } } return answ; } /// /// Elenco SalesOfferRows calcolabili: /// - contengono serializzazione come JWD /// - contengono file come BTL /// private List SorListCalc() { var rawList = AllRecords .Where(x => (!string.IsNullOrEmpty(x.SerStruct) && x.SerStruct.Length > 2) || (!string.IsNullOrEmpty(x.FileName) && !string.IsNullOrEmpty(x.FileResource))) .ToList(); return rawList ?? new List(); } /// /// Toggle visibilit� modifica file indicando ID della OfferRow corrente (o zero se deselect) /// private void ToggleFileEdit(OfferRowModel? currRec) { CurrEditMode = currRec == null ? EditMode.None : EditMode.File; EditRecord = currRec; } /// /// Salva nel record corrente la BOM aggiornata e poi ricalcola importo... /// /// /// private async Task UpdateBom(List newBomList) { if (EditRecord != null) { // salvo BOM nel record corrente... bool fatto = await DLService.OffertRowUpdateBom(EditRecord.OfferRowID, newBomList); // ricalcolo offerta completa await ReloadData(); UpdateTable(); // rilegge il record da elenco appena rinfrescato... int offerRowId = EditRecord.OfferRowID; var updRec = AllRecords.FirstOrDefault(x => x.OfferRowID == offerRowId); if (updRec != null) { CurrBomList = new List(); // fa refresh dei dati della BOM visualizzata selectBom(updRec); await InvokeAsync(StateHasChanged); } } } /// /// Filtro e paginazione /// private void UpdateTable() { // fix paginazione ListRecords = AllRecords .OrderBy(x => x.RowNum) .Skip(numRecord * (currPage - 1)) .Take(numRecord) .ToList(); isLoading = false; } /// /// Esegue lettura file + invio richiesta specifica /// /// /// private async Task UploadFile(InputFileChangeEventArgs e) { // Proseguo solo se sono in interattivo (NO prerender pagina) if (isInteractive) { if (EditRecord != null) { // init dizionari arg richiesta update Dictionary fileArgs = new Dictionary(); // leggo il contenuto del PRIMO (singolo) file IBrowserFile file = e.File; // limite file size (al momento 10 MB) var maxAllowedSize = 10 * 1024 * 1024; using var stream = file.OpenReadStream(maxAllowedSize); using var reader = new StreamReader(stream); string rawContent = await reader.ReadToEndAsync(); // calcolo il nome del file trusted... string trustedFileName = Path.GetRandomFileName(); EditRecord.FileResource = trustedFileName; EditRecord.FileName = file.Name; EditRecord.FileSize = rawContent.LongCount(); // salvo sul DB i dati (nome, nome sicuro, size...) await DLService.OffertRowUpdateFileData(EditRecord); // parametri richiesta fileArgs.Add("Mode", $"{(int)Egw.Window.Data.Enums.QuestionModes.PREVIEW}"); fileArgs.Add("SubMode", "2"); fileArgs.Add("FileName", $"{file.Name}"); fileArgs.Add("Height", "1200"); fileArgs.Add("Width", "1800"); //fileArgs.Add("Btl", rawContent); fileArgs.Add("SerializedData", rawContent); // invio! CalcRequestDTO calcRequestDTO = new CalcRequestDTO(); calcRequestDTO.EnvType = EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.BEAM; calcRequestDTO.DictExec = fileArgs; await ICService.CallRestPost($"{apiUrl}/{genericBasePath}", $"{calcTag}/{EditRecord.OfferRowUID}", calcRequestDTO); // ora chiedo anche la BOM! #if false // salvo in locale il file: SISTEMARE PERMESSI saveFileContent(EditFileRecord.OfferRowUID, trustedFileName, rawContent); #endif } } } #endregion Private Methods } }