prima versione dettaglio lavorabilità

This commit is contained in:
Samuele Locatelli
2025-12-05 18:34:16 +01:00
parent 36cb6f170d
commit 2000813740
17 changed files with 392 additions and 38 deletions
@@ -21,8 +21,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Egw.Lux.WebWindow.Base" Version="2.7.11.1811" />
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.11-beta.2017" />
<PackageReference Include="Egw.Lux.WebWindow.Base" Version="2.7.12.515" />
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.12.515" />
<PackageReference Include="Egw.Window.Data" Version="2.7.11.2116" />
<PackageReference Include="EgwMultiEngineManager.Data" Version="2.7.11.1" />
</ItemGroup>
+117
View File
@@ -0,0 +1,117 @@
using EgwCoreLib.Lux.Core.RestPayload;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EgwCoreLib.Lux.Core.MachineCalc
{
public class Utils
{
/// <summary>
/// Helper calcolo Intersezione lista macchine
/// </summary>
/// <param name="machines"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static IEnumerable<string> IntersectTags(List<MachineCalcResultDTO> machines, Func<PartCalcDTO, bool> predicate)
{
return machines
.Select(m => m.PartList.Where(predicate).Select(p => p.Tag).ToHashSet())
.Aggregate((set1, set2) => { set1.IntersectWith(set2); return set1; });
}
/// <summary>
/// Calcolo delle intersezioni Macchine/Tags(Parts)
/// </summary>
/// <param name="machineResult"></param>
/// <returns></returns>
public static List<MachineTagDTO> CalculateIntersections(List<MachineCalcResultDTO> machineResult)
{
// Step 1: extract workable tags per machine
var machineTags = machineResult
.ToDictionary(
m => m.Name,
m => m.PartList
.Where(p => p.CalcResult == Enums.PartVerificationResult.MACHINABLE)
.ToList()
);
var machineNames = machineTags.Keys.ToList();
var results = new List<MachineTagDTO>();
// Step 2: generate all combinations of machines
for (int size = 1; size <= machineNames.Count; size++)
{
foreach (var combo in GetCombinations(machineNames, size))
{
var comboList = combo.ToList();
// Intersection of tags across all machines in this combo
var intersectionTags = comboList
.Select(name => machineTags[name].Select(p => p.Tag).ToHashSet())
.Aggregate((set1, set2) => { set1.IntersectWith(set2); return set1; });
// Compute per-machine sums of Time for these tags
var sums = comboList.Select(name =>
machineTags[name]
.Where(p => intersectionTags.Contains(p.Tag))
.Sum(p => p.Time)
).ToList();
results.Add(new MachineTagDTO
{
Machines = comboList,
Tags = intersectionTags.ToList(),
MinTime = sums.Any() ? sums.Min() : 0,
MaxTime = sums.Any() ? sums.Max() : 0
});
}
}
// Step 3: add unique (non-intersecting) tags per machine
foreach (var name in machineNames)
{
var uniqueTags = machineTags[name]
.Select(p => p.Tag)
.Except(machineNames.Where(n => n != name)
.SelectMany(n => machineTags[n].Select(p => p.Tag)))
.ToList();
var sum = machineTags[name]
.Where(p => uniqueTags.Contains(p.Tag))
.Sum(p => p.Time);
results.Add(new MachineTagDTO
{
Machines = new List<string> { name },
Tags = uniqueTags,
MinTime = sum,
MaxTime = sum
});
}
return results;
}
/// <summary>
/// Helper generazione combinazioni di items per una data length
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="length"></param>
/// <returns></returns>
public static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> items, int length)
{
if (length == 1)
return items.Select(i => new T[] { i });
return GetCombinations(items, length - 1)
.SelectMany(c => items.Where(i => !c.Contains(i)),
(c, i) => c.Concat(new T[] { i }));
}
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
@@ -11,14 +12,90 @@ namespace EgwCoreLib.Lux.Core.RestPayload
/// </summary>
public class MachineCalcResultDTO
{
#region Public Properties
/// <summary>
/// Nome macchina
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Numero di parts totali
/// </summary>
public int NumParts
{
get => PartList.Count;
}
/// <summary>
/// Numero di Parts con errori di calcolo
/// </summary>
public int NumPartsCalcFail
{
get => PartListCalcFail.Count();
}
/// <summary>
/// Numero di Parts NON OK (errori calcolo o non lavorabili)
/// </summary>
public int NumPartsKo
{
get => PartListKo.Count();
}
/// <summary>
/// Numero di Parts NON lavorabili
/// </summary>
public int NumPartsNotMach
{
get => PartListNotMach.Count();
}
/// <summary>
/// Numero di Parts lavorabili dalla macchina
/// </summary>
public int NumPartsOk
{
get => PartListOk.Count();
}
/// <summary>
/// Elenco delle parts ed esito stima lavorabilità
/// </summary>
public List<PartCalcDTO> PartList { get; set; } = new List<PartCalcDTO>();
/// <summary>
/// Elenco parts con errori calcolo
/// </summary>
public List<PartCalcDTO> PartListCalcFail
{
get => PartList.Where(x => x.CalcResult == Enums.PartVerificationResult.CALCULATIONFAILED).ToList();
}
/// <summary>
/// Elenco delle parts KO / non "healthy" (non lavorabili o errore calcolo)
/// </summary>
public List<PartCalcDTO> PartListKo
{
get => PartList.Where(x => x.CalcResult != Enums.PartVerificationResult.MACHINABLE).ToList();
}
/// <summary>
/// Elenco parts NON lavorabili
/// </summary>
public List<PartCalcDTO> PartListNotMach
{
get => PartList.Where(x => x.CalcResult == Enums.PartVerificationResult.NOTMACHINABLE).ToList();
}
/// <summary>
/// Elenco parts lavorabili
/// </summary>
public List<PartCalcDTO> PartListOk
{
get => PartList.Where(x => x.CalcResult == Enums.PartVerificationResult.MACHINABLE).ToList();
}
#endregion Public Properties
}
}
}
@@ -0,0 +1,10 @@
namespace EgwCoreLib.Lux.Core.RestPayload
{
public class MachineTagDTO
{
public List<string> Machines { get; set; } = new List<string>();
public List<string> Tags { get; set; } = new List<string>();
public decimal MinTime { get; set; }
public decimal MaxTime { get; set; }
}
}
@@ -0,0 +1,99 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
namespace EgwCoreLib.Lux.Core.RestPayload
{
/// <summary>
/// Classe per definizione WorkLoad in dettaglio x un dato task (POR tipicamente)
/// </summary>
public class WorkLoadDetailDTO
{
#region Public Constructors
/// <summary>
/// Init classe aprtendo dal valore serializzato di una stima di lavorabilità di un item d'ordine minimo (POR)
/// </summary>
/// <param name="UID"></param>
/// <param name="rawData"></param>
public WorkLoadDetailDTO(string UID, string rawData)
{
uID = UID;
// deserializzo risultati calcolo
machineCalcResults = JsonConvert.DeserializeObject<List<MachineCalcResultDTO>>(rawData) ?? new List<MachineCalcResultDTO>();
// calcolo "esito sinteico" della lavorabilità (NESSUNA part UnHealthy su ogni macchina)
workable = machineCalcResults.Sum(x => x.NumPartsKo) == 0;
// se almeno 1 verifico SE ci siano part unhealty su OGNI macchina (altrimenti è cmq lavorabile
if (!workable)
{
var listKo = new List<PartCalcDTO>();
// cerco l'insieme dei pezzi DAVVERO non lavorabili
var listUnWorkable = MachineCalc.Utils.IntersectTags(machineCalcResults, p => p.CalcResult != Enums.PartVerificationResult.MACHINABLE);
workable = !listUnWorkable.Any();
numKo = listUnWorkable.Count();
}
LoadDetail = MachineCalc.Utils.CalculateIntersections(machineCalcResults);
}
/// <summary>
/// Dettaglio combinazioni carico di lavoro
/// </summary>
public List<MachineTagDTO> LoadDetail { get; set; } = new List<MachineTagDTO>();
/// <summary>
/// Tempo minimo complessivo
/// </summary>
public decimal TotMinTime
{
get => LoadDetail.Sum(x => x.MinTime);
}
/// <summary>
/// Tempo massimo complessivo
/// </summary>
public decimal TotMaxTime
{
get => LoadDetail.Sum(x => x.MaxTime);
}
#endregion Public Constructors
#region Public Properties
public List<MachineCalcResultDTO> MachineCalcResults
{
get => machineCalcResults;
}
public string UID
{
get => uID;
}
public bool Workable
{
get => workable;
}
public int NumKo
{
get => numKo;
}
#endregion Public Properties
#region Private Fields
private List<MachineCalcResultDTO> machineCalcResults = new List<MachineCalcResultDTO>();
private string uID = "";
private int numKo = 0;
private bool workable = false;
#endregion Private Fields
}
}
@@ -27,7 +27,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Egw.Lux.WebWindow.Base" Version="2.7.11.1811" />
<PackageReference Include="Egw.Lux.WebWindow.Base" Version="2.7.12.515" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.21" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.21" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.21" />
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.9.2512.0418</Version>
<Version>0.9.2512.0518</Version>
</PropertyGroup>
<ItemGroup>
+3 -1
View File
@@ -154,7 +154,9 @@ namespace Lux.API.Services
// deserializzo
try
{
machineEstimList = JsonConvert.DeserializeObject<List<MachineCalcResultDTO>>(rawAnsw);
#if false
machineEstimList = JsonConvert.DeserializeObject<List<MachineCalcResultDTO>>(rawAnsw);
#endif
await dbService.SaveProdEstimateAsync(UID, retData.ExecEnvironment, rawAnsw);
}
catch (Exception exc)
+1 -1
View File
@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.11-beta.2017" />
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.12.515" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.21" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.21" />
</ItemGroup>
+4 -4
View File
@@ -1277,18 +1277,18 @@ namespace Lux.UI.Components.Compo
/// Esecuzione azione richiesta
/// </summary>
/// <param name="actReq">Azione richiesta</param>
private void DoAction(TableComp.DataAction actReq)
private void DoAction(LayoutConst.DataAction actReq)
{
switch (actReq)
{
case TableComp.DataAction.None:
case LayoutConst.DataAction.None:
break;
case TableComp.DataAction.ResetDictShape:
case LayoutConst.DataAction.ResetDictShape:
CurrData.DictShape = new Dictionary<int, string>();
break;
case TableComp.DataAction.ResetHwOpt:
case LayoutConst.DataAction.ResetHwOpt:
CurrData.DictOptionsXml = new Dictionary<int, string>();
break;
+30 -5
View File
@@ -83,15 +83,18 @@ else
<th>Codice</th>
<th>Descrizione</th>
<th class="text-end" title="Quantità Articoli">Qty <i class="fa-regular fa-file-lines"></i></th>
<th class="text-end">Importo</th>
@* <th class="text-end">Importo</th> *@
<th class="text-end" title="Quantità Prodotti"># Prod</th>
@if (DisplayMode == EgwCoreLib.Lux.Core.Enums.DisplayMode.Edit)
{
<th class="text-end" title="Cambio Materiali">Mat.</th>
<th class="text-end" title="Ciclo Lavoro">Lav.</th>
<th class="text-end" title="# Componenti Riga (Make + Buy)"><i class="fa-solid fa-folder-tree"></i></th>
}
<th class="text-end">Totale</th>
<th class="text-end">Marg.</th>
@* <th class="text-end">Totale</th>
<th class="text-end">Marg.</th> *@
<th class="text-end" title="# Prodotti non validi/producibili"># KO</th>
<th class="text-end" title="Range tempi prod. previsti">Timing</th>
@if (DisplayMode == EgwCoreLib.Lux.Core.Enums.DisplayMode.Edit)
{
<th></th>
@@ -102,6 +105,7 @@ else
@foreach (var item in ListRecords)
{
bool isNote = item.SellingItemID == null;
var itemWLD = WorkLoadDetail(item.OrderRowUID, item.ProdEstimate);
<tr class="@RowClass(item)">
<td class="text-nowrap">
<span class="px-1">
@@ -214,7 +218,7 @@ else
<b>@item.Qty</b>
</td>
}
<td class="text-end text-nowrap">
@* <td class="text-end text-nowrap">
<div class="fw-bold" title="Prezzo Finito">
@if (!(item.BomOk && item.ItemOk))
{
@@ -227,6 +231,19 @@ else
@($"{item.UnitPrice:C2}")
</div>
<div class="small text-secondary" title="RockBottom Price">(@item.UnitCost.ToString("C2"))</div>
</td> *@
<td class="text-end text-nowrap">
<div class="fw-bold" title="# Totale Prodotti">
@($"{item.ProdItemQtyTot:N0}")
</div>
@if (@itemWLD.Workable)
{
<i class="fa-solid fa-thumbs-up text-success"></i>
}
else
{
<i class="fa-solid fa-thumbs-down text-danger"></i>
}
</td>
@if (DisplayMode == EgwCoreLib.Lux.Core.Enums.DisplayMode.Edit)
{
@@ -251,7 +268,7 @@ else
@item.ProdItemQtyTot
</td>
}
<td class="text-end text-nowrap">
@* <td class="text-end text-nowrap">
<div class="fw-bold" title="Prezzo Finito">
@if (item.AwaitPrice)
{
@@ -263,6 +280,14 @@ else
</td>
<td class="text-end text-nowrap" title="Margine / Sconto MAX applicabile">
@item.MaxDiscount.ToString("P2")
</td> *@
<td class="text-end text-nowrap">
<div class="fw-bold" title="# Pezz KO">
@($"{itemWLD.NumKo}")
</div>
</td>
<td class="text-end text-nowrap" title="Tempi Stimati">
@($"{itemWLD.TotMinTime} - {itemWLD.TotMaxTime}")
</td>
}
@if (DisplayMode == EgwCoreLib.Lux.Core.Enums.DisplayMode.Edit)
+18 -5
View File
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Components.Forms;
using Microsoft.JSInterop;
using Newtonsoft.Json;
using NLog;
using System.Xml;
using WebWindowComplex;
using WebWindowComplex.DTO;
using static EgwCoreLib.Lux.Core.Enums;
@@ -226,7 +227,7 @@ namespace Lux.UI.Components.Compo
await DLService.OffertRowUpsert(newNote);
#endif
await ReloadData();
UpdateTable();
UpdateTable();
}
/// <summary>
@@ -1277,18 +1278,18 @@ namespace Lux.UI.Components.Compo
/// Esecuzione azione richiesta
/// </summary>
/// <param name="actReq">Azione richiesta</param>
private void DoAction(TableComp.DataAction actReq)
private void DoAction(LayoutConst.DataAction actReq)
{
switch (actReq)
{
case TableComp.DataAction.None:
case LayoutConst.DataAction.None:
break;
case TableComp.DataAction.ResetDictShape:
case LayoutConst.DataAction.ResetDictShape:
CurrData.DictShape = new Dictionary<int, string>();
break;
case TableComp.DataAction.ResetHwOpt:
case LayoutConst.DataAction.ResetHwOpt:
CurrData.DictOptionsXml = new Dictionary<int, string>();
break;
@@ -1297,6 +1298,18 @@ namespace Lux.UI.Components.Compo
}
}
/// <summary>
/// Restituisce struttura dettaglio WorkLoad x item
/// </summary>
/// <param name="UID"></param>
/// <param name="rawData"></param>
/// <returns></returns>
protected WorkLoadDetailDTO WorkLoadDetail(string UID, string rawData)
{
var currWLD = new WorkLoadDetailDTO(UID, rawData);
return currWLD;
}
/// <summary>
/// Salvataggio del JWD aggiornato nella mia riga di offerta
/// </summary>
+23 -12
View File
@@ -261,8 +261,11 @@ else
<th>ID</th>
<th>Date</th>
<th>Stato</th>
<th>Codice</th>
<th>Agente/Riv</th>
<th>Codice</th>
@if (SelRecord == null)
{
<th>Agente/Riv</th>
}
<th>Cliente</th>
@if (SelRecord == null)
{
@@ -271,8 +274,11 @@ else
<th class="text-end" title="# Righe"><i class="fa-solid fa-list"></i></th>
<th class="text-end" title="# Articoli"><i class="fa-regular fa-file-lines"></i></th>
<th class="text-end" title="# Componenti Ordine"><i class="fa-solid fa-folder-tree"></i></th>
<th class="text-end">Importo</th>
<th class="text-end">Marg.</th>
@if (SelRecord == null)
{
<th class="text-end">Importo</th>
<th class="text-end">Marg.</th>
}
</tr>
</thead>
<tbody>
@@ -295,13 +301,16 @@ else
@item.OrderCode
<div class="small text-secondary">@item.Envir</div>
</td>
<td>
@if (item.DealerNav != null)
{
<div class=""><b>@item.DealerNav.FirstName</b> @item.DealerNav.LastName</div>
<div class="small">@item.DealerNav.VAT</div>
}
</td>
@if (SelRecord == null)
{
<td>
@if (item.DealerNav != null)
{
<div class=""><b>@item.DealerNav.FirstName</b> @item.DealerNav.LastName</div>
<div class="small">@item.DealerNav.VAT</div>
}
</td>
}
<td>
@if (item.CustomerNav != null)
{
@@ -321,7 +330,8 @@ else
</td>
<td class="text-end">
@item.NumProdItems
</td>
</td> @if (SelRecord == null)
{
<td class="text-end">
<div class="fw-bold" title="Prezzo Finito">@item.TotalPrice.ToString("C2")</div>
<div class="small text-secondary" title="RockBottom Price">(@item.TotalCost.ToString("C2"))</div>
@@ -329,6 +339,7 @@ else
<td class="text-end" title="Margine / Sconto MAX applicabile">
@item.MaxDiscount.ToString("P2")
</td>
}
</tr>
}
</tbody>
+2 -2
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.2512.0418</Version>
<Version>0.9.2512.0518</Version>
</PropertyGroup>
<ItemGroup>
@@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.11-beta.2017" />
<PackageReference Include="Egw.Lux.WebWindowComplex" Version="2.7.12.515" />
<PackageReference Include="EgwCoreLib.Razor" Version="1.5.2511.312" />
<PackageReference Include="EgwCoreLib.Utils" Version="1.5.2511.312" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.21" />
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>LUX - Web Windows MES</i>
<h4>Versione: 0.9.2512.0418</h4>
<h4>Versione: 0.9.2512.0518</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
0.9.2512.0418
0.9.2512.0518
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>0.9.2512.0418</version>
<version>0.9.2512.0518</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>