Update forzato in massa importi BOM BEAM

This commit is contained in:
Samuele Locatelli
2025-11-07 17:14:43 +01:00
parent 4b9916cf11
commit 2075cfa0bd
12 changed files with 687 additions and 281 deletions
+230 -63
View File
@@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using NLog;
using System.Globalization;
using static EgwCoreLib.Lux.Core.Enums;
namespace EgwCoreLib.Lux.Data.Controllers
@@ -1026,6 +1027,134 @@ namespace EgwCoreLib.Lux.Data.Controllers
return dbResult;
}
/// <summary>
/// Esegue un mass update dei valori di margine, qtyMax, costo di un set di dati ricevuto
/// </summary>
/// <param name="list2upd">Elenco items da aggiornare</param>
/// <param name="setCost">Costo standard (al volume m3)</param>
/// <param name="defMargin">Margine da impostare per tutti</param>
/// <param name="defQtyMax">Valore qty max da impostare per tutti</param>
/// <param name="defUM">Valore UM da impostare per tutti</param>
/// <param name="roundVal">Valore di arrotondamento richiesto (0 = non arrotondo)</param>
/// <param name="scaleFactor">Valore di scala da unità in ingresso x unità di costo (mm x mm x m --> m3)</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
internal async Task<bool> ItemMassUpdate(List<BomItemDTO> list2upd, double setCost, double defMargin, double defQtyMax, string defUM, int roundVal, double scaleFactor)
{
bool answ = false;
if (list2upd == null || !list2upd.Any())
return answ;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
// Validate input
if (setCost <= 0)
throw new ArgumentException("setCost must be greater than 0.");
// Step 1: Extract width and height from ExtItemCode
var itemUpdates = new List<ItemModel>();
foreach (var item in list2upd)
{
if (string.IsNullOrWhiteSpace(item.ItemCode))
continue;
// Try to parse ExtItemCode: "Pine-200.0x360.0"
if (!item.ItemCode.Contains("-") || !item.ItemCode.Contains("x"))
{
// Skip invalid format
continue;
}
// Split by "-" to get prefix and number part
var parts = item.ItemCode.Split('-', 2);
if (parts.Length < 2)
{
continue;
}
string numberPart = parts[1]; // e.g. "200.0x360.0"
// Split by "x" to get width and height
var widthHeight = numberPart.Split('x', 2);
if (widthHeight.Length < 2)
{
continue;
}
if (!double.TryParse(widthHeight[0], NumberStyles.Any, CultureInfo.InvariantCulture, out double width) ||
!double.TryParse(widthHeight[1], NumberStyles.Any, CultureInfo.InvariantCulture, out double height))
{
continue;
}
// Step 2: Calculate Cost using formula:
// Cost = setCost / 1,000,000 * width * height
double calculatedCost = (setCost / scaleFactor) * width * height;
// if requested do round ceiling
if (roundVal > 0)
{
calculatedCost = Math.Ceiling(calculatedCost / roundVal) * roundVal;
}
// Optional: you can also apply margin to cost if needed, but you said "Cost = ..."
// So we're just computing it based on width/height
// Step 4: Create a new ItemModel with updated values
var updatedItem = new ItemModel
{
ItemID = item.ItemID,
ExtItemCode = item.ItemCode, // keep original
Cost = calculatedCost,
Margin = defMargin,
QtyMax = defQtyMax,
UM = defUM
};
itemUpdates.Add(updatedItem);
}
// Step 5: Update database using EF Core (EF Core doesn't support "update" on entire list directly)
// We'll use .UpdateRange() or a raw update via .Where() and .SetProperty()
// ✅ Use EF Core's Update method (for bulk update)
if (itemUpdates.Any())
{
// Update only the ones that exist in the DB
var existingItems = await dbCtx.DbSetItem
.Where(i => itemUpdates.Select(ui => ui.ItemID).Contains(i.ItemID))
.ToListAsync();
if (existingItems.Any())
{
// Update existing records using EF Core's Update method
foreach (var updatedItem in itemUpdates)
{
var existing = existingItems.FirstOrDefault(i => i.ItemID == updatedItem.ItemID);
if (existing != null)
{
// Update only the fields we're setting
existing.Cost = updatedItem.Cost;
existing.Margin = updatedItem.Margin;
existing.QtyMax = updatedItem.QtyMax;
}
}
await dbCtx.SaveChangesAsync();
}
}
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ItemMassUpdate{Environment.NewLine}{exc}");
}
}
return answ;
}
internal bool ItemUpsert(ItemModel newRec)
{
bool answ = false;
@@ -1113,17 +1242,6 @@ namespace EgwCoreLib.Lux.Data.Controllers
return result;
}
internal bool UpdateCodGroup(List<BomItemDTO> bomList)
{
bool answ = false;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
}
return answ;
}
/// <summary>
/// Upsert item ricevuti da BOM calcolata
/// </summary>
@@ -1137,30 +1255,15 @@ namespace EgwCoreLib.Lux.Data.Controllers
{
try
{
// 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>();
#if false
// in primis calcolo ii 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(cg => !itemGroupList.Where(x => x.CodGroup == cg))
.ToList();
#endif
// ciclo x ogni elemento della BOM, cercando x gruppo e ExtItemCode
foreach (var item in item2save)
{
@@ -1172,7 +1275,7 @@ namespace EgwCoreLib.Lux.Data.Controllers
// se nullo --> verifico x inserire!!!
if (currRec == null)
{
// verifico NON sia tra gli items già in fase di inserimento
// 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()
@@ -1527,37 +1630,6 @@ namespace EgwCoreLib.Lux.Data.Controllers
return answ;
}
#if false
/// <summary>
/// Elenco item Child da ID Parent (per sostituzione)
/// </summary>
/// <param name="ItemIdParent">ID parent (valido quindi >0)</param>
/// <returns></returns>
internal List<ItemModel> ItemGetChild(int ItemIdParent)
{
List<ItemModel> dbResult = new List<ItemModel>();
if (ItemIdParent > 0)
{
//using (DataLayerContext dbCtx = new DataLayerContext(configuration))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbResult = dbCtx
.DbSetItem
.Where(x => x.ItemID == ItemIdParent || x.ItemIDParent == ItemIdParent)
.ToList();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ItemGetChild{Environment.NewLine}{exc}");
}
}
}
return dbResult;
}
#endif
/// <summary>
/// Upsert record
/// </summary>
@@ -1652,6 +1724,35 @@ namespace EgwCoreLib.Lux.Data.Controllers
return dbResult;
}
#if false
/// <summary>
/// Ritorna direttamente 1 riga offerta
/// </summary>
/// <param name="offerRowID"></param>
/// <returns></returns>
internal OfferRowModel? OfferRowGetByOfferRowID(int offerRowID)
{
OfferRowModel? dbResult = null;
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbResult = dbCtx
.DbSetOfferRow
.Where(x => x.OfferRowID == offerRowID)
.Include(s => s.SellingItemNav)
.FirstOrDefault();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante OfferRowGetByOfferRowID{Environment.NewLine}{exc}");
}
}
return dbResult;
}
#endif
/// <summary>
/// Elimina riga e sposta eventuali righe successive...
/// </summary>
@@ -2263,8 +2364,74 @@ namespace EgwCoreLib.Lux.Data.Controllers
return answ;
}
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;
}
#endregion Internal Methods
#if false
/// <summary>
/// Elenco item Child da ID Parent (per sostituzione)
/// </summary>
/// <param name="ItemIdParent">ID parent (valido quindi >0)</param>
/// <returns></returns>
internal List<ItemModel> ItemGetChild(int ItemIdParent)
{
List<ItemModel> dbResult = new List<ItemModel>();
if (ItemIdParent > 0)
{
//using (DataLayerContext dbCtx = new DataLayerContext(configuration))
using (DataLayerContext dbCtx = new DataLayerContext())
{
try
{
dbResult = dbCtx
.DbSetItem
.Where(x => x.ItemID == ItemIdParent || x.ItemIDParent == ItemIdParent)
.ToList();
}
catch (Exception exc)
{
Log.Error($"Eccezione durante ItemGetChild{Environment.NewLine}{exc}");
}
}
}
return dbResult;
}
#endif
#region Private Fields
private static IConfiguration _configuration;
@@ -2285,8 +2452,8 @@ namespace EgwCoreLib.Lux.Data.Controllers
/// <param name="bomList">Lista BOM precedente da confrontare x scelta alternativi</param>
/// <param name="totCost">Costo netto componenti BOM calcolato</param>
/// <param name="totPrice">Prezzo complessivo calcolato (con aggiunta marginalità)</param>
/// <param name="numGroupOk">Controllo coerenza calcoli sui gruppi items</param>
/// <param name="numItemOk">Controllo coerenza calcoli su num items</param>
/// <param name="numGroupOk">Controllo coerenza calcoli sui gruppi list2upd</param>
/// <param name="numItemOk">Controllo coerenza calcoli su num list2upd</param>
private static void validateBom(List<ItemGroupModel> itemGroupList, List<ItemModel> bomGenList, ref List<BomItemDTO> bomList, List<BomItemDTO>? bomListPrev, ref double totCost, ref double totPrice, ref int numGroupOk, ref int numItemOk)
{
double margin = 0;
@@ -17,6 +17,7 @@ using System.ComponentModel.Design;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using static EgwCoreLib.Lux.Core.Enums;
@@ -50,6 +51,28 @@ namespace EgwCoreLib.Lux.Data.Services
#region Public Methods
/// <summary>
/// Sistema gli item che mancassero di ItemID leggendo da DB
/// </summary>
/// <param name="listOrg"></param>
/// <returns></returns>
public List<BomItemDTO> BomFixItemId(List<BomItemDTO> listOrg)
{
List<BomItemDTO> listFix = listOrg;
// sistemo ItemID per gli item della BOM...
var listItemDB = dbController.ItemGetFilt("", ItemClassType.Bom);
// cerco i record da sistemare ed 1:1 li fixo...
foreach (var item in listFix.Where(x => x.ItemID == 0))
{
var dbRec = listItemDB.FirstOrDefault(x => x.ExtItemCode == item.ItemCode);
if ((dbRec != null))
{
item.ItemID = dbRec.ItemID;
}
}
return listFix;
}
/// <summary>
/// Elenco completo Config Envir
/// </summary>
@@ -608,7 +631,6 @@ namespace EgwCoreLib.Lux.Data.Services
/// <summary>
/// Elenco item da ricerca completa Async
/// </summary>
/// <param name="SearchVal"></param>
/// <param name="CodGroup"></param>
/// <param name="ItemType"></param>
/// <returns></returns>
@@ -713,6 +735,24 @@ namespace EgwCoreLib.Lux.Data.Services
return result;
}
/// <summary>
/// Esecuzione mass update di un set di item cui manca pezzo/quantità max
/// </summary>
/// <param name="list2upd"></param>
/// <param name="setCost"></param>
/// <param name="defMargin"></param>
/// <param name="defQtyMax"></param>
/// <param name="defUM">Valore UM da impostare per tutti</param>
/// <param name="roundVal"></param>
/// <param name="scaleFactor">Valore di scala da unità in ingresso x unità di costo (mm x mm x m --> m3)</param>
/// <returns></returns>
public async Task<bool> ItemMassUpdate(List<BomItemDTO> list2upd, double setCost, double defMargin, double defQtyMax, string defUM, int roundVal = 0, double scaleFactor = 1_000_000.0)
{
bool result = await dbController.ItemMassUpdate(list2upd, setCost, defMargin, defQtyMax, defUM, roundVal, scaleFactor);
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Item:*");
return result;
}
/// <summary>
/// Update / Insert record item
/// </summary>
@@ -931,6 +971,26 @@ namespace EgwCoreLib.Lux.Data.Services
return result;
}
#if false
/// <summary>
/// Recupero nuovo record BOM diretto dal DB
/// </summary>
/// <param name="offerRowID"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public OfferRowModel? OfferRowGetByOfferRowID(int offerRowID)
{
string source = "DB";
Stopwatch sw = new Stopwatch();
sw.Start();
OfferRowModel? result = null;
result = dbController.OfferRowGetByOfferRowID(offerRowID);
sw.Stop();
Log.Debug($"OfferRowGetByOfferRowID | {source} | {sw.Elapsed.TotalMilliseconds}ms");
return result;
}
#endif
/// <summary>
/// Converte il campo raw della BOM in lista oggetti da gestire
/// </summary>
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.9.2511.0618</Version>
<Version>0.9.2511.0717</Version>
</PropertyGroup>
<ItemGroup>
@@ -159,55 +159,5 @@ namespace Lux.API.Services
#endregion Private Fields
#if false
/// <summary>
/// Restituisce una risposta all'esecuzione
/// </summary>
/// <param name="ProcessArgsResult"></param>
private void ProcessMan_m_AnswerReceived(ProcessArgsResult result)
{
// verifico che sia la mia richiesta con id immagine... FARE!!!
// salvo il risultato...
if (result.Args != null && result.Args.Count > 0)
{
if (result.Args.ContainsKey("Svg"))
{
// salvo SVG
string newSvg = result.Args["Svg"];
// salvo nel dizionario
lastSvg = newSvg;
// salvo su redis
_imgService.SaveSvg("123456", lastSvg);
}
}
}
/// <summary>
/// Salva risultato calcolo da broadcast channel REDIS
/// </summary>
/// <param name="channel"></param>
/// <param name="message"></param>
private void SaveCalcData(RedisChannel channel, RedisValue message)
{
string rawData = $"{message}";
if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2)
{
// provo a deserializzare
try
{
var retData = JsonConvert.DeserializeObject<ProcessArgsResult>(rawData);
if (retData != null)
{
// verifico nId di risposta x salvare correttamente
ProcessMan_m_AnswerReceived(retData);
}
}
catch (Exception exc)
{
_logger.LogError($"Errore in fase decodifica messaggio da REDIS Channel{Environment.NewLine}{exc}");
}
}
}
#endif
}
}
+146 -73
View File
@@ -1,83 +1,156 @@
<table class="table table-sm table-striped">
<thead>
<tr class="table-dark">
<td>#</td>
<td class="text-center"><i class="fa-solid fa-repeat"></i></td>
<td>Class</td>
<td>Descrizione</td>
@* <td>Cod</td> *@
@if (ShowVolume)
{
<td class="text-end">Vol</td>
}
<td class="text-end">Qty</td>
<td class="text-end">UnitPrice</td>
<td class="text-end">Importo</td>
</tr>
</thead>
<tbody>
@foreach (var item in bomDict)
{
@if (EditRecord != null && item.Key == currIdx)
{
<tr class="table-info">
<td>@(item.Key + 1)</td>
<td colspan="3">
<div class="input-group">
<button class="btn btn-sm btn-warning" title="Effettua Cambio" @onclick="() => DoCancel()"><i class="fa-solid fa-ban"></i></button>
<select @bind="@EditRecord.ItemID" class="form-select">
@foreach (var itemAlt in ListItemAlt)
{
<option value="@itemAlt.ItemID">@itemAlt.Description | @($"{itemAlt.Cost:C2}")</option>
}
</select>
<button class="btn btn-sm btn-success" title="Effettua Cambio" @onclick="() => DoSave()"><i class="fa-solid fa-check"></i></button>
@if (isLoading)
{
<LoadingData></LoadingData>
}
else
{
@if (MassEdit)
{
<div class="row">
<div class="col-md-2">
<div class="form-floating">
<input type="text" class="form-control form-control-lg text-end" @bind="@defUM">
<label class="small bg-opacity-50">UM</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<InputDouble CssClass="form-control form-control-lg text-end" Decimals="2" @bind-Value="@totCost"></InputDouble>
<label class="small bg-opacity-50">Tot. Cost</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<InputPercent CssClass="form-control form-control-lg text-end" ForceInvariantParsing="true" Decimals="1" @bind-Value="@defMargin"></InputPercent>
<label class="small bg-opacity-50">Margin</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<input type="number" class="form-control form-control-lg text-end" @bind="@defQtyMax">
<label class="small bg-opacity-50">Qty Max</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<input type="number" class="form-control form-control-lg text-end" @bind="@defRound">
<label class="small bg-opacity-50">Round</label>
</div>
</div>
<div class="col-md-2">
@if (showMassEditSave)
{
<button class="btn btn-lg btn-success w-100" @onclick="ForceItemPrice">Save <i class="fa-solid fa-cloud-arrow-up"></i></button>
}
else
{
<button class="btn btn-lg btn-secondary w-100" disabled>Save <i class="fa-solid fa-cloud-arrow-up"></i></button>
}
</div>
</div>
}
<table class="table table-sm table-striped">
<thead>
<tr class="table-dark">
<td>
@if (MassEdit)
{
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" title="Seleziona/Deseleziona tutti" @bind="SelAll">
</div>
</td>
@if (ShowVolume)
{
<td class="text-end">@($"{item.Value.Volume:N3}")</td>
}
<td class="text-end">@($"{item.Value.Qty:N3}")</td>
<td class="text-end">@($"{item.Value.PriceEff:C2}")</td>
<td class="text-end">@($"{item.Value.TotalCost:C2}")</td>
</tr>
}
else
</td>
<td class="text-center"><i class="fa-solid fa-repeat"></i></td>
<td>Class</td>
<td>Descrizione</td>
@* <td>Cod</td> *@
@if (ShowVolume)
{
<td class="text-end">Vol</td>
}
<td class="text-end">Qty</td>
<td class="text-end">UnitPrice</td>
<td class="text-end">Importo</td>
</tr>
</thead>
<tbody>
@foreach (var item in bomPaged)
{
<tr>
<td>@(item.Key + 1)</td>
<td class="text-center">
@if (item.Value.Price == 0)
@if (EditRecord != null && item.numRow == EditRecord.numRow)
{
<tr class="table-info">
<td>@(item.numRow)</td>
<td colspan="3">
<div class="input-group">
<button class="btn btn-sm btn-warning" title="Effettua Cambio" @onclick="() => DoCancel()"><i class="fa-solid fa-ban"></i></button>
<select @bind="@EditRecord.ItemID" class="form-select">
@foreach (var itemAlt in ListItemAlt)
{
<option value="@itemAlt.ItemID">@itemAlt.Description | @($"{itemAlt.Cost:C2}")</option>
}
</select>
<button class="btn btn-sm btn-success" title="Effettua Cambio" @onclick="() => DoSave()"><i class="fa-solid fa-check"></i></button>
</div>
</td>
@if (ShowVolume)
{
<button class="btn btn-sm btn-primary" title="Effettua Cambio" @onclick="() => DoEdit(item.Key, item.Value)"><i class="fa-solid fa-arrow-right-arrow-left"></i></button>
<td class="text-end">@($"{item.Volume:N3}")</td>
}
</td>
<td>@item.Value.ClassCode</td>
<td>@item.Value.ItemCode</td>
@* <td>@item.Value.DescriptionCode</td> *@
<td class="text-end">@($"{item.Qty:N3}")</td>
<td class="text-end">@($"{item.PriceEff:C2}")</td>
<td class="text-end">@($"{item.TotalCost:C2}")</td>
</tr>
}
else
{
<tr>
<td>
@(item.numRow)
@if (MassEdit)
{
<input class="form-check-input" type="checkbox" role="switch" title="Seleziona per Mass Update" @bind="item.isSelected">
}
</td>
<td class="text-center">
@if (item.Price == 0)
{
<button class="btn btn-sm btn-primary" title="Effettua Cambio" @onclick="() => DoEdit(item)"><i class="fa-solid fa-arrow-right-arrow-left"></i></button>
}
</td>
<td>@item.ClassCode</td>
<td>@item.ItemCode</td>
@* <td>@item.Value.DescriptionCode</td> *@
@if (ShowVolume)
{
<td class="text-end">@($"{item.Volume:N3}")</td>
}
<td class="text-end">@($"{item.Qty:N3}")</td>
<td class="text-end">@($"{item.PriceEff:C2}")</td>
<td class="text-end">@($"{item.TotalCost:C2}")</td>
</tr>
}
}
</tbody>
<tfoot>
@{
int numCol = ShowVolume ? 8 : 7;
<tr class="table-primary">
<td colspan="4" class="text-start"><b>@BomList.Count</b> materiali</td>
@if (ShowVolume)
{
<td class="text-end">@($"{item.Value.Volume:N3}")</td>
<td class="text-end">@($"{VolTotale:N3}")</td>
}
<td class="text-end">@($"{item.Value.Qty:N3}")</td>
<td class="text-end">@($"{item.Value.PriceEff:C2}")</td>
<td class="text-end">@($"{item.Value.TotalCost:C2}")</td>
<td class="text-end">@($"{QtyTotale:N3}")</td>
<td class="text-end">tot:</td>
<td class="text-end fw-bold">@($"{ImportoTotale:C2}")</td>
</tr>
<tr>
<td colspan="@numCol">
<EgwCoreLib.Razor.DataPager currPage="@currPage" PageSize="@numRecord" totalCount="@totalCount" numPageChanged="SavePage" numRecordChanged="SaveNumRec"></EgwCoreLib.Razor.DataPager>
</td>
</tr>
}
}
</tbody>
<tfoot>
<tr class="table-primary">
<td colspan="4" class="text-start"><b>@BomList.Count</b> materiali</td>
@if (ShowVolume)
{
<td class="text-end">@($"{VolTotale:N3}")</td>
}
<td class="text-end">@($"{QtyTotale:N3}")</td>
<td class="text-end">tot:</td>
<td class="text-end fw-bold">@($"{ImportoTotale:C2}")</td>
</tr>
</tfoot>
</table>
</tfoot>
</table>
}
+149 -29
View File
@@ -4,6 +4,8 @@ using EgwCoreLib.Lux.Data.DbModel.Sales;
using EgwCoreLib.Lux.Data.Services;
using EgwMultiEngineManager.Data;
using Microsoft.AspNetCore.Components;
using RestSharp.Serializers.Xml;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Lux.UI.Components.Compo
@@ -21,6 +23,9 @@ namespace Lux.UI.Components.Compo
[Parameter]
public EventCallback<List<BomItemDTO>> EC_Updated { get; set; }
[Parameter]
public bool MassEdit { get; set; } = false;
#endregion Public Properties
#region Protected Properties
@@ -35,7 +40,7 @@ namespace Lux.UI.Components.Compo
double valTot = 0;
if (bomDict != null)
{
valTot = bomDict.Sum(x => x.Value.TotalCost);
valTot = bomDict.Sum(x => x.TotalCost);
}
return valTot;
}
@@ -48,12 +53,28 @@ namespace Lux.UI.Components.Compo
double valTot = 0;
if (bomDict != null)
{
valTot = bomDict.Sum(x => x.Value.Qty);
valTot = bomDict.Sum(x => x.Qty);
}
return valTot;
}
}
protected bool SelAll
{
get => selAll;
set
{
if (selAll != value)
{
selAll = value;
foreach (var item in bomDict)
{
item.isSelected = selAll;
}
}
}
}
protected double VolTotale
{
get
@@ -61,7 +82,7 @@ namespace Lux.UI.Components.Compo
double valTot = 0;
if (bomDict != null)
{
valTot = bomDict.Sum(x => x.Value.Volume);
valTot = bomDict.Sum(x => x.Volume);
}
return valTot;
}
@@ -69,21 +90,15 @@ namespace Lux.UI.Components.Compo
#endregion Protected Properties
private bool ShowVolume
{
get => CurrRowRec.Envir == Constants.EXECENVIRONMENTS.BEAM || CurrRowRec.Envir == Constants.EXECENVIRONMENTS.WALL;
}
#region Protected Methods
protected void DoCancel()
{
EditRecord = null;
currIdx = -1;
isLoading = false;
}
protected void DoEdit(int key, BomItemDTO editRec)
protected void DoEdit(BomDtoSel editRec)
{
if (editRec.ItemID > 0)
{
@@ -99,7 +114,6 @@ namespace Lux.UI.Components.Compo
{
ListItemAlt = new List<ItemModel>();
}
currIdx = key;
EditRecord = editRec;
}
@@ -108,13 +122,46 @@ namespace Lux.UI.Components.Compo
/// </summary>
protected override void OnParametersSet()
{
if (BomList != null)
if (BomList != null && BomList.Count > 0)
{
int idx = 0;
// Convert List to Dictionary with index as key
bomDict = BomList
.Select((item, index) => new { index, item })
.ToDictionary(x => x.index, x => x.item);
.Select(x => new BomDtoSel()
{
isSelected = false,
numRow = idx++,
ClassCode = x.ClassCode,
DescriptionCode = x.DescriptionCode,
ItemCode = x.ItemCode,
ItemID = x.ItemID,
ItemQty = x.ItemQty,
Price = x.Price,
PriceEff = x.PriceEff,
Qty = x.Qty,
Volume = x.Volume
})
.ToList();
totalCount = BomList.Count();
UpdateTable();
isLoading = false;
}
else
{
isLoading = true;
}
}
protected void SaveNumRec(int newNum)
{
numRecord = newNum;
UpdateTable();
}
protected void SavePage(int newNum)
{
currPage = newNum;
UpdateTable();
}
#endregion Protected Methods
@@ -124,14 +171,76 @@ namespace Lux.UI.Components.Compo
/// <summary>
/// Dizionario interno oggetti x editare con indice
/// </summary>
private Dictionary<int, BomItemDTO> bomDict = new Dictionary<int, BomItemDTO>();
private List<BomDtoSel> bomDict = new List<BomDtoSel>();
private List<BomDtoSel> bomPaged = new List<BomDtoSel>();
private int currPage = 1;
private decimal defMargin = 0.2M;
private double defQtyMax = 999;
private int defRound = 5;
private string defUM = "m";
private BomDtoSel? EditRecord = null;
private bool isLoading = false;
private int currIdx = -1;
private BomItemDTO? EditRecord = null;
private List<ItemModel> ListItemAlt = new List<ItemModel>();
private int numRecord = 10;
private bool selAll = false;
private int totalCount = 0;
private double totCost = 100;
#endregion Private Fields
protected async Task ForceItemPrice()
{
isLoading = true;
// chiamo metodo update x i soli valori selezionati...
var list2upd = bomDict.Where(x => x.isSelected).Cast<BomItemDTO>().ToList();
await DLService.ItemMassUpdate(list2upd, totCost, (double)defMargin, defQtyMax, defUM, defRound);
// ...e passo al controller parent LINQ projection to base type
List<BomItemDTO> baseList = bomDict.Cast<BomItemDTO>().ToList();
await EC_Updated.InvokeAsync(baseList);
SelAll = false;
}
protected bool showMassEditSave
{
get => bomDict != null && bomDict.Any(x => x.isSelected);
}
#region Protected Classes
protected class BomDtoSel : BomItemDTO
{
#region Public Properties
public bool isSelected { get; set; } = false;
public int numRow { get; set; } = 0;
#endregion Public Properties
}
#endregion Protected Classes
#region Private Properties
private bool ShowVolume
{
get => CurrRowRec.Envir == Constants.EXECENVIRONMENTS.BEAM || CurrRowRec.Envir == Constants.EXECENVIRONMENTS.WALL;
}
#endregion Private Properties
#region Private Methods
/// <summary>
@@ -139,21 +248,18 @@ namespace Lux.UI.Components.Compo
/// </summary>
private async Task DoSave()
{
isLoading = true;
// aggiorno l'oggetto nel mio dizionario
if (EditRecord != null)
{
// modifico il valore ID con quello selezionato nel selettore x l'oggetto
UpdateItem(currIdx, EditRecord);
UpdateItem(EditRecord);
}
// converto il dizionario in lista
var updList = bomDict
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value)
.ToList();
// deseleziono...
DoCancel();
// ...e passo al controller parent
await EC_Updated.InvokeAsync(updList);
// ...e passo al controller parent LINQ projection to base type
List<BomItemDTO> baseList = bomDict.Cast<BomItemDTO>().ToList();
await EC_Updated.InvokeAsync(baseList);
}
/// <summary>
@@ -161,14 +267,28 @@ namespace Lux.UI.Components.Compo
/// </summary>
/// <param name="key"></param>
/// <param name="updatedItem"></param>
private void UpdateItem(int key, BomItemDTO updatedItem)
private void UpdateItem(BomDtoSel updatedItem)
{
if (bomDict.ContainsKey(key))
int index = bomDict.FindIndex(x => x.numRow == updatedItem.numRow);
if (index >= 0)
{
bomDict[key] = updatedItem;
bomDict[index] = updatedItem;
}
}
/// <summary>
/// Filtro e paginazione
/// </summary>
private void UpdateTable()
{
// fix paginazione
bomPaged = bomDict
.Skip(numRecord * (currPage - 1))
.Take(numRecord)
.ToList();
isLoading = false;
}
#endregion Private Methods
}
}
+6 -2
View File
@@ -304,17 +304,21 @@ else
<div class="col-4 fs-3">
Materiali (BOM)
</div>
<div class="col-4 text-center border border-2 rounded">
<div class="col-4 text-center text-bg-secondary bg-gradient border border-2 rounded">
<div class="fw-bold">@EditRecord.Note</div>
<small class="small">@EditRecord.OfferRowUID</small>
</div>
<div class="col-4 text-end fs-4">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="ClosePopup">
</button>
<div class="form-check form-switch small" title="Abilita editing massivo">
<input class="form-check-input" type="checkbox" role="switch" @bind="enableMassEdit">
<label class="form-check-label">Mass Edit</label>
</div>
</div>
</div>
<div class="modal-body">
<EditBom CurrRowRec="@EditRecord" BomList="@CurrBomList" EC_Updated="UpdateBom"></EditBom>
<EditBom CurrRowRec="@EditRecord" MassEdit="enableMassEdit" BomList="CurrBomList" EC_Updated="UpdateBom"></EditBom>
</div>
</div>
</div>
+90 -58
View File
@@ -381,25 +381,23 @@ namespace Lux.UI.Components.Compo
}
/// <summary>
/// Imposta modalit edit ciclo di lavoro
/// Imposta modalita edit ciclo di lavoro
/// </summary>
/// <param name="currRow"></param>
protected void DoSwapJobCycle(OfferRowModel currRow)
{
CurrEditMode = EditMode.JobCycle;
EditRecord = currRow;
CurrBomList = DLService.OffertGetBomList(EditRecord);
selectBom(currRow);
}
/// <summary>
/// Imposta modalit ad edit BOM
/// Imposta modalita ad edit BOM
/// </summary>
/// <param name="currRow"></param>
protected void DoSwapMat(OfferRowModel currRow)
{
CurrEditMode = EditMode.BOM;
EditRecord = currRow;
CurrBomList = DLService.OffertGetBomList(EditRecord);
selectBom(currRow);
}
/// <summary>
@@ -590,6 +588,7 @@ namespace Lux.UI.Components.Compo
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private List<GenValueModel> AllColors = new();
private List<EnvirParamModel> AllConfEnvir = new();
@@ -623,6 +622,31 @@ namespace Lux.UI.Components.Compo
private string calcTag = "calc";
/// <summary>
/// Channel update HwOptions
/// </summary>
private string chHwOpt = "";
/// <summary>
/// Channel update PNG
/// </summary>
private string chPng = "";
/// <summary>
/// Channel update Profile List
/// </summary>
private string chProfList = "";
/// <summary>
/// Channel update Shape
/// </summary>
private string chShape = "";
/// <summary>
/// Channel update SVG
/// </summary>
private string chSvg = "";
private List<BomItemDTO>? CurrBomList = null;
/// <summary>
@@ -637,6 +661,7 @@ namespace Lux.UI.Components.Compo
private int currPage = 1;
private string currPng = "";
private List<string> currProfList = new List<string>();
private string currSvg = "";
@@ -646,12 +671,12 @@ namespace Lux.UI.Components.Compo
/// </summary>
private OfferRowModel? EditRecord = null;
private string genericBasePath = "";
/// <summary>
/// Channel update HwOptions
/// Abilita edit massivo record ITEM
/// </summary>
private string chHwOpt = "";
private bool enableMassEdit = false;
private string genericBasePath = "";
private string imgBasePath = "";
@@ -671,36 +696,16 @@ namespace Lux.UI.Components.Compo
/// </summary>
private string origJwd = "";
/// <summary>
/// Channel update PNG
/// </summary>
private string chPng = "";
/// <summary>
/// Versione precedente JWD x test e confronto
/// </summary>
private string prevJwd = "";
/// <summary>
/// Channel update Profile List
/// </summary>
private string chProfList = "";
/// <summary>
/// Dizionario richieste
/// </summary>
private Dictionary<string, string> reqDict = new Dictionary<string, string>();
/// <summary>
/// Channel update Shape
/// </summary>
private string chShape = "";
/// <summary>
/// Channel update SVG
/// </summary>
private string chSvg = "";
private int totalCount = 0;
#endregion Private Fields
@@ -804,6 +809,34 @@ namespace Lux.UI.Components.Compo
isLoading = false;
}
/// <summary>
/// Restituisce il contenuto del file salvato
/// </summary>
/// <param name="folderPath"></param>
/// <param name="secureName"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Ricevuto HwOpt, processo
/// </summary>
@@ -1119,6 +1152,7 @@ namespace Lux.UI.Components.Compo
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:
@@ -1128,6 +1162,7 @@ namespace Lux.UI.Components.Compo
DictExec.Add(serKey, rawData);
DictExec.Add("FileName", currRec.FileName);
break;
case EgwMultiEngineManager.Data.Constants.EXECENVIRONMENTS.NULL:
default:
break;
@@ -1212,34 +1247,6 @@ namespace Lux.UI.Components.Compo
return answ;
}
/// <summary>
/// Restituisce il contenuto del file salvato
/// </summary>
/// <param name="folderPath"></param>
/// <param name="secureName"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Salvataggio del JWD aggiornato nella mia riga di offerta
/// </summary>
@@ -1280,6 +1287,21 @@ namespace Lux.UI.Components.Compo
}
}
/// <summary>
/// Selezione e fix dati BOM
/// </summary>
/// <param name="currRow"></param>
/// <returns></returns>
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)
@@ -1355,6 +1377,16 @@ namespace Lux.UI.Components.Compo
// 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<BomItemDTO>();
// fa refresh dei dati della BOM visualizzata
selectBom(updRec);
await InvokeAsync(StateHasChanged);
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50</UserSecretsId>
<Version>0.9.2511.0618</Version>
<Version>0.9.2511.0717</Version>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>LUX - Web Windows MES</i>
<h4>Versione: 0.9.2511.0618</h4>
<h4>Versione: 0.9.2511.0717</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
0.9.2511.0618
0.9.2511.0717
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>0.9.2511.0618</version>
<version>0.9.2511.0717</version>
<url>http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html</changelog>
<mandatory>false</mandatory>