Aggiunta metodi x creazione ODL e item2odl (DA PROVARE!!!)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EgwCoreLib.Lux.Core.Generic
|
||||
{
|
||||
public class GroupDetailDTO
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string MachineName { get; set; } = "";
|
||||
public List<string> TagList { get; set; } = new List<string>();
|
||||
public int TotalBarQty { get; set; } = 0;
|
||||
public int TotalNumPart { get; set; } = 0;
|
||||
public decimal TotalTime { get; set; } = 0;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using NLog;
|
||||
using StackExchange.Redis;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using ZXing;
|
||||
using static EgwCoreLib.Lux.Core.Enums;
|
||||
|
||||
namespace EgwCoreLib.Lux.Data.Controllers
|
||||
@@ -3516,6 +3517,137 @@ namespace EgwCoreLib.Lux.Data.Controllers
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creazione di un Batch con relativo Tag e info x creazione ODL correlati
|
||||
/// </summary>
|
||||
/// <param name="newRec"></param>
|
||||
/// <returns></returns>
|
||||
internal async Task<ProductionBatchModel?> ProductionBatchCreateAsync(ProductionBatchModel newRec)
|
||||
{
|
||||
ProductionBatchModel dbResult = null;
|
||||
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
|
||||
using (DataLayerContext dbCtx = new DataLayerContext())
|
||||
{
|
||||
// avvio transazione
|
||||
using var transaction = await dbCtx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
int currentYear = DateTime.Today.Year;
|
||||
string prefix = "BC.";
|
||||
|
||||
// 1. Chiamata alla Stored Procedure per ottenere il nuovo ID
|
||||
// Definiamo il parametro di output
|
||||
var outputParam = new MySqlParameter
|
||||
{
|
||||
ParameterName = "pValue",
|
||||
DbType = DbType.Int32,
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await dbCtx.Database.ExecuteSqlRawAsync(
|
||||
"CALL GetNextCounter(@pYear, @pName, @pValue)",
|
||||
new MySqlParameter("@pYear", currentYear),
|
||||
new MySqlParameter("@pName", prefix),
|
||||
outputParam
|
||||
);
|
||||
|
||||
int idx = (int)outputParam.Value;
|
||||
|
||||
// 2. Formattazione del BatchTag secondo le tue specifiche
|
||||
// :x8 formatta in esadecimale (lowercase) con padding di 8 zeri
|
||||
string formattedTag = $"{prefix}{currentYear}{idx:x8}";
|
||||
|
||||
// 3. Creazione del nuovo record
|
||||
dbResult = new ProductionBatchModel
|
||||
{
|
||||
Description = newRec.Description,
|
||||
DueDate = newRec.DueDate,
|
||||
BatchTag = formattedTag.ToUpper()
|
||||
};
|
||||
|
||||
dbCtx.DbSetProdBatch.Add(dbResult);
|
||||
// salvo!
|
||||
await dbCtx.SaveChangesAsync();
|
||||
|
||||
// 4. Commit della transazione
|
||||
await transaction.CommitAsync();
|
||||
|
||||
return dbResult;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
|
||||
/// </summary>
|
||||
/// <param name="listOdl2ins"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
internal async Task<List<ProductionODLModel>> ProductionOdlCreateAsync(List<ProductionODLModel> listOdl2ins)
|
||||
{
|
||||
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
|
||||
using (DataLayerContext dbCtx = new DataLayerContext())
|
||||
{
|
||||
// avvio transazione
|
||||
using var transaction = await dbCtx.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
int currentYear = DateTime.Today.Year;
|
||||
string prefix = "ODL.";
|
||||
|
||||
foreach (var odl in listOdl2ins)
|
||||
{
|
||||
// 1. Chiamata alla Stored Procedure per ogni riga
|
||||
var outputParam = new MySqlParameter
|
||||
{
|
||||
ParameterName = "pValue",
|
||||
DbType = DbType.Int32,
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
|
||||
await dbCtx.Database.ExecuteSqlRawAsync(
|
||||
"CALL GetNextCounter(@pYear, @pName, @pValue)",
|
||||
new MySqlParameter("@pYear", currentYear),
|
||||
new MySqlParameter("@pName", prefix),
|
||||
outputParam
|
||||
);
|
||||
|
||||
int idx = (int)outputParam.Value;
|
||||
|
||||
// 2. Assegnazione dati calcolati
|
||||
odl.OdlTag = $"{prefix}{currentYear}{idx:X8}";
|
||||
|
||||
// Assicuriamoci che l'ID sia 0 per l'insert
|
||||
odl.ProdODLID = 0;
|
||||
|
||||
dbCtx.DbSetProdODL.Add(odl);
|
||||
}
|
||||
|
||||
// 3. Salvataggio massivo
|
||||
// EF Core 8 ottimizzerà gli insert in batch dove possibile
|
||||
await dbCtx.SaveChangesAsync();
|
||||
|
||||
// 4. Conferma transazione
|
||||
await transaction.CommitAsync();
|
||||
|
||||
// A questo punto, ogni oggetto in 'listOdl2ins' ha il ProdODLID aggiornato dal DB
|
||||
return listOdl2ins;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
// Logga l'errore secondo le tue necessità
|
||||
throw new Exception("Errore durante la creazione massiva degli ODL", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco record ProductionGroup dato OrderRow
|
||||
/// </summary>
|
||||
@@ -3684,6 +3816,114 @@ namespace EgwCoreLib.Lux.Data.Controllers
|
||||
return totalUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assegnazione in blocco degli item agli ODL corrispondenti
|
||||
/// </summary>
|
||||
/// <param name="dbList"></param>
|
||||
/// <param name="dictParts"></param>
|
||||
/// <returns></returns>
|
||||
internal async Task<int> ProdItem2ODL_AssignAsync(List<ProductionODLModel> dbList, Dictionary<(int phaseId, int resId, string machine, int index), List<string>> dictParts)
|
||||
{
|
||||
int totalCreated = 0;
|
||||
using (DataLayerContext dbCtx = new DataLayerContext())
|
||||
{
|
||||
// 1. Recuperiamo tutti i ProdBatchID coinvolti per fare una sola query
|
||||
var batchIds = dbList.Select(o => o.ProdBatchID).Distinct().ToList();
|
||||
|
||||
if (batchIds != null && batchIds.Count > 0)
|
||||
{
|
||||
// 2. Carichiamo in memoria i ProdItem necessari (solo ID e Tag per risparmiare RAM)
|
||||
// Creiamo un dizionario dove la chiave è il Tag e il valore è l'ID
|
||||
var itemLookup = await dbCtx.DbSetProdItem
|
||||
.Where(x => batchIds.Contains(x.ProdBatchID))
|
||||
.Select(x => new { x.ProdItemID, x.ProdItemTag })
|
||||
.ToDictionaryAsync(x => x.ProdItemTag, x => x.ProdItemID);
|
||||
|
||||
using var transaction = await dbCtx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var relationsToInsert = new List<ProductionItem2ODLModel>();
|
||||
|
||||
foreach (var odl in dbList)
|
||||
{
|
||||
var key = (odl.PhaseID ?? 0, odl.ResourceID ?? 0, odl.ProdPlantCod, odl.Index);
|
||||
|
||||
if (dictParts.TryGetValue(key, out List<string> tagList))
|
||||
{
|
||||
foreach (var tag in tagList)
|
||||
{
|
||||
// 3. Cerchiamo l'ID corrispondente al tag nel nostro lookup locale
|
||||
if (itemLookup.TryGetValue(tag, out int realItemId))
|
||||
{
|
||||
relationsToInsert.Add(new ProductionItem2ODLModel
|
||||
{
|
||||
ProdODLID = odl.ProdODLID,
|
||||
ProdItemID = realItemId,
|
||||
DtAssign = DateTime.Now
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//Log.Warning($"Tag {tag} non trovato nel database per i batch selezionati.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relationsToInsert.Any())
|
||||
{
|
||||
await dbCtx.DbSetProdItem2ODL.AddRangeAsync(relationsToInsert);
|
||||
totalCreated = relationsToInsert.Count;
|
||||
await dbCtx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
Log.Error($"Errore nel salvataggio relazioni ODL-Parts: {exc.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalCreated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdBatch parent (per ora totale x RigaOrd)
|
||||
/// </summary>
|
||||
/// <param name="OrderRowId"></param>
|
||||
/// <param name="ProdBatchId"></param>
|
||||
/// <returns></returns>
|
||||
internal async Task<int> ProdItemBulkAssignProdBatch(int OrderRowId, int ProdBatchId)
|
||||
{
|
||||
int totalUpdated = 0;
|
||||
//using (DataLayerContext dbCtx = new DataLayerContext(_config))
|
||||
using (DataLayerContext dbCtx = new DataLayerContext())
|
||||
{
|
||||
using var transaction = await dbCtx.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
int rowsAffected = await dbCtx.DbSetProdItem
|
||||
.Where(p => p.OrderRowID == OrderRowId)
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(p => p.ProdBatchID, ProdBatchId)
|
||||
);
|
||||
|
||||
totalUpdated += rowsAffected;
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
Log.Error($"Eccezione durante ProdItemBulkAssignProdBatch{Environment.NewLine}{exc}");
|
||||
//throw;
|
||||
}
|
||||
}
|
||||
return totalUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco ProdItem dato OrderRow
|
||||
/// </summary>
|
||||
|
||||
@@ -1784,6 +1784,45 @@ namespace EgwCoreLib.Lux.Data.Services
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creazione record Batch x intestare gli ODL
|
||||
/// </summary>
|
||||
/// <param name="newRec"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<ProductionBatchModel?> ProductionBatchCreateAsync(ProductionBatchModel newRec)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
// calcolo
|
||||
var dbResult = await dbController.ProductionBatchCreateAsync(newRec);
|
||||
// svuoto cache...
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:Batch:*");
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ODL:*");
|
||||
sw.Stop();
|
||||
Log.Debug($"ProductionBatchCreateAsync in {sw.Elapsed.TotalMilliseconds} ms");
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Insert sul DB di un elenco ODL con calcolo della relativa KEY a cui poter, successivamente, collegare i record child (items)
|
||||
/// </summary>
|
||||
/// <param name="listOdl2ins"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public async Task<List<ProductionODLModel>> ProductionOdlCreateAsync(List<ProductionODLModel> listOdl2ins)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
// calcolo
|
||||
var dbResult = await dbController.ProductionOdlCreateAsync(listOdl2ins);
|
||||
// svuoto cache...
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ODL:*");
|
||||
sw.Stop();
|
||||
Log.Debug($"ProductionOdlCreateAsync in {sw.Elapsed.TotalMilliseconds} ms");
|
||||
return dbResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco completo Fasi
|
||||
/// </summary>
|
||||
@@ -2011,6 +2050,29 @@ namespace EgwCoreLib.Lux.Data.Services
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue assegnazione bulk dei ProdItem ad un unico ProdBatch parent (per ora totale x RigaOrd)
|
||||
/// </summary>
|
||||
/// <param name="OrderRowId"></param>
|
||||
/// <param name="ProdBatchId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> ProdItemBulkAssignProdBatch(int OrderRowId, int ProdBatchId)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
// calcolo
|
||||
int fatto = await dbController.ProdItemBulkAssignProdBatch(OrderRowId, ProdBatchId);
|
||||
// svuoto cache...
|
||||
//await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdItems:*");
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdItems:OrdRowId:*");
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:ProdGroup:OrdRowId:*");
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRows:*");
|
||||
await ExecFlushRedisPatternAsync((RedisValue)$"{redisBaseKey}:OrderRowsByState:*");
|
||||
sw.Stop();
|
||||
Log.Debug($"ProdItemBulkAssignProdBatch in {sw.Elapsed.TotalMilliseconds} ms");
|
||||
return fatto;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elenco dei ProductionItem collegati ad una riga d'ordine
|
||||
/// </summary>
|
||||
|
||||
@@ -10,6 +10,7 @@ NOT DETERMINISTIC
|
||||
CONTAINS SQL
|
||||
SQL SECURITY DEFINER
|
||||
COMMENT 'Ritorna il prossimo valore di contatore per il flusso richiesto con upsert'
|
||||
|
||||
BEGIN
|
||||
DECLARE vOld INT;
|
||||
|
||||
@@ -21,13 +22,12 @@ BEGIN
|
||||
|
||||
IF vOld IS NULL THEN
|
||||
-- Primo valore
|
||||
SET vOld = 0;
|
||||
SET vOld = 1;
|
||||
|
||||
INSERT INTO utils_counter (RefYear, CountName, Counter)
|
||||
VALUES (pYear, pName, 1)
|
||||
VALUES (pYear, pName, vOld)
|
||||
ON DUPLICATE KEY UPDATE Counter = VALUES(Counter);
|
||||
|
||||
SET pValue = 1;
|
||||
ELSE
|
||||
-- Incremento
|
||||
SET vOld = vOld + 1;
|
||||
@@ -36,6 +36,6 @@ BEGIN
|
||||
SET Counter = vOld
|
||||
WHERE RefYear = pYear AND CountName = pName;
|
||||
|
||||
SET pValue = vOld;
|
||||
END IF;
|
||||
END;
|
||||
SET pValue = vOld;
|
||||
END
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Version>0.9.2601.1919</Version>
|
||||
<Version>0.9.2601.2110</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,122 +1,158 @@
|
||||
@using EgwCoreLib.Lux.Data.DbModel.Production
|
||||
|
||||
<table class="table table-sm table-striped shadow">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<th></th>
|
||||
<th class="text-center">#</th>
|
||||
<th class="text-start">Ord</th>
|
||||
<th class="text-start">Macchina</th>
|
||||
<th class="text-end" title="Bar"><i class="fa-solid fa-bars-staggered"></i></th>
|
||||
<th class="text-end" title="Parts"><i class="fa-solid fa-folder-tree"></i></th>
|
||||
<th class="text-end" title="Time"><i class="fa-solid fa-clock"></i></th>
|
||||
<th class="text-end">Tot.Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (DictGrouped != null && DictGrouped.Count > 0)
|
||||
{
|
||||
int idx = 1;
|
||||
foreach (var item in DictGrouped)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-start">
|
||||
<button class="btn btn-danger" @onclick="() => RemOrder(item.Key)"><i class="fa-solid fa-angles-left"></i> Rem</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button><i class="fa-solid fa-caret-up"></i></button>
|
||||
@(idx++)
|
||||
<button><i class="fa-solid fa-caret-down"></i></button>
|
||||
</td>
|
||||
<td class="text-start fs-4">
|
||||
@item.Key
|
||||
</td>
|
||||
<td class="text-start">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.PlantListJoin
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.BarQty
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.NumParts
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@FormatEstTime(detail.TotalEstimTime)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end fs-3">
|
||||
<b>@FormatEstTime(item.Value.Sum(x => x.TotalEstimTime))</b>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
<tfoot class="table-dark text-light">
|
||||
<tr class="fw-bold">
|
||||
<td colspan="3"></td>
|
||||
@if (ListBalancedDet != null)
|
||||
{
|
||||
<td class="text-start">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.MachineName
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.TotalBarQty
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.TotalNumPart
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@FormatEstTime(item.TotalTime)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td colspan="4"></td>
|
||||
}
|
||||
<td class="text-end fs-3">
|
||||
<b>@FormatEstTime(BalancedTotalTime)</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<th></th>
|
||||
<th class="text-center">#</th>
|
||||
<th class="text-start">Ord</th>
|
||||
<th class="text-start">Macchina</th>
|
||||
<th class="text-end" title="Bar"><i class="fa-solid fa-bars-staggered"></i></th>
|
||||
<th class="text-end" title="Parts"><i class="fa-solid fa-folder-tree"></i></th>
|
||||
<th class="text-end" title="Time"><i class="fa-solid fa-clock"></i></th>
|
||||
<th class="text-end">Tot.Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (DictGrouped != null && DictGrouped.Count > 0)
|
||||
{
|
||||
int idx = 1;
|
||||
foreach (var item in DictGrouped)
|
||||
{
|
||||
<tr>
|
||||
<td class="text-start">
|
||||
<button class="btn btn-danger" @onclick="() => RemOrder(item.Key)"><i class="fa-solid fa-angles-left"></i> Rem</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button><i class="fa-solid fa-caret-up"></i></button>
|
||||
@(idx++)
|
||||
<button><i class="fa-solid fa-caret-down"></i></button>
|
||||
</td>
|
||||
<td class="text-start fs-4">
|
||||
@item.Key
|
||||
</td>
|
||||
<td class="text-start">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.PlantListJoin
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.BarQty
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@detail.NumParts
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var detail in item.Value)
|
||||
{
|
||||
<div class="px-0">
|
||||
@FormatEstTime(detail.TotalEstimTime)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end fs-3">
|
||||
<b>@FormatEstTime(item.Value.Sum(x => x.TotalEstimTime))</b>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
<tfoot class="table-dark text-light">
|
||||
<tr class="fw-bold">
|
||||
<td colspan="3" class="text-center">
|
||||
<button class="btn btn-lg @CssToggleBatch" @onclick="ToggleCreaBatch">
|
||||
@if (ShowCreateBatch)
|
||||
{
|
||||
<i class="fa-solid fa-chevron-up"></i> <span class="px-1">Nascondi Crea Commessa</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa-solid fa-chevron-down"></i> <span class="px-1">Mostra Crea Commessa</span>
|
||||
}
|
||||
</button>
|
||||
</td>
|
||||
@if (ListBalancedDet != null)
|
||||
{
|
||||
<td class="text-start">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.MachineName
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.TotalBarQty
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@item.TotalNumPart
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
@foreach (var item in ListBalancedDet)
|
||||
{
|
||||
<div class="px-0">
|
||||
@FormatEstTime(item.TotalTime)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td colspan="4"></td>
|
||||
}
|
||||
<td class="text-end fs-3">
|
||||
<b>@FormatEstTime(BalancedTotalTime)</b>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@if (ShowCreateBatch && newBatch != null)
|
||||
{
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label class="form-label small">Descrizione Commessa</label>
|
||||
<input type="text" class="form-control" placeholder="Nome / Descrizione commessa" @bind="newBatch.Description">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-label small">Scadenza Commessa</label>
|
||||
<input type="datetime-local" class="form-control" @bind="newBatch.DueDate">
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<button class="btn btn-lg btn-success" @onclick="CreateAllComm">
|
||||
<i class="fa-solid fa-lock"></i> <span class="px-2">Crea Commesse</span> <i class="fa-solid fa-chart-gantt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using EgwCoreLib.Lux.Core.Generic;
|
||||
using EgwCoreLib.Lux.Data.DbModel.Production;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Lux.UI.Components.Compo.Planner
|
||||
{
|
||||
@@ -13,14 +15,12 @@ namespace Lux.UI.Components.Compo.Planner
|
||||
[Parameter]
|
||||
public EventCallback<int> EC_RemBalance { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<BatchCreateInfo> EC_ReqCreateBatch { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
protected decimal BalancedTotalTime
|
||||
{
|
||||
get => AllRecords?.Sum(x => x.TotalEstimTime) ?? 0;
|
||||
}
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Richiede rimozione da balance dell' OrderRow UID selezionato
|
||||
@@ -45,6 +45,39 @@ namespace Lux.UI.Components.Compo.Planner
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struttura ritorno x creazione Batch e ODL
|
||||
/// </summary>
|
||||
public class BatchCreateInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Info batch da creare
|
||||
/// </summary>
|
||||
public ProductionBatchModel NewBatch { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Dettaglio macchine x cui creare ODL
|
||||
/// </summary>
|
||||
public List<GroupDetailDTO> GroupDetail { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Record di partenza x esecuzione creazione ODL
|
||||
/// </summary>
|
||||
public List<ProductionGroupModel> AllRecords { get; set; } = null!;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Properties
|
||||
|
||||
protected decimal BalancedTotalTime
|
||||
{
|
||||
get => AllRecords?.Sum(x => x.TotalEstimTime) ?? 0;
|
||||
}
|
||||
|
||||
[Inject]
|
||||
protected IJSRuntime JSRuntime { get; set; } = null!;
|
||||
|
||||
#endregion Protected Properties
|
||||
|
||||
#region Protected Methods
|
||||
@@ -71,45 +104,92 @@ namespace Lux.UI.Components.Compo.Planner
|
||||
ListBalancedDet = AllRecords?
|
||||
.SelectMany(pg => pg.WorkGroupList) // appiattisce tutti i dizionari
|
||||
.GroupBy(kvp => kvp.Key) // raggruppa per la chiave del dizionario
|
||||
.Select(g => new GroupDetail
|
||||
.Select(g =>
|
||||
{
|
||||
MachineName = g.Key,
|
||||
TotalBarQty = g.Sum(x => x.Value.BarQty),
|
||||
TotalNumPart = g.Sum(x => x.Value.NumParts),
|
||||
TotalTime = g.Sum(x => x.Value.Time)
|
||||
// Materializziamo i tag separatamente per sicurezza
|
||||
List<string> tags = g.SelectMany(x => x.Value.TagList ?? new List<string>()).ToList();
|
||||
// genero oggetto DTO in return
|
||||
return new GroupDetailDTO
|
||||
{
|
||||
MachineName = g.Key,
|
||||
TagList = tags,
|
||||
TotalBarQty = g.Sum(x => x.Value.BarQty),
|
||||
TotalNumPart = g.Sum(x => x.Value.NumParts),
|
||||
TotalTime = g.Sum(x => x.Value.Time)
|
||||
};
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
protected void ToggleCreaBatch()
|
||||
{
|
||||
ShowCreateBatch = !ShowCreateBatch;
|
||||
if (ShowCreateBatch)
|
||||
{
|
||||
newBatch = new ProductionBatchModel()
|
||||
{
|
||||
Description = $"Nuova Commessa {DateTime.Now:yyyy-MM-dd_HH:mm:ss}",
|
||||
DueDate = DateTime.Today.AddMonths(3),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
newBatch = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private Dictionary<string, List<ProductionGroupModel>> DictGrouped = new();
|
||||
|
||||
private ProductionBatchModel? newBatch = null;
|
||||
private bool ShowCreateBatch = false;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Protected Classes
|
||||
|
||||
protected class GroupDetail
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string MachineName { get; set; } = "";
|
||||
public int TotalBarQty { get; set; } = 0;
|
||||
public int TotalNumPart { get; set; } = 0;
|
||||
public decimal TotalTime { get; set; } = 0;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
|
||||
#endregion Protected Classes
|
||||
|
||||
#region Private Properties
|
||||
|
||||
private List<GroupDetail>? ListBalancedDet { get; set; } = new();
|
||||
private string CssToggleBatch
|
||||
{
|
||||
get => ShowCreateBatch ? "btn-warning" : "btn-success";
|
||||
}
|
||||
|
||||
private List<GroupDetailDTO>? ListBalancedDet { get; set; } = new();
|
||||
|
||||
#endregion Private Properties
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task CreateAllComm(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
|
||||
{
|
||||
if (ListBalancedDet != null && newBatch!=null && AllRecords!=null)
|
||||
{
|
||||
var numMacc = ListBalancedDet.Count;
|
||||
var totBars = ListBalancedDet.Sum(x => x.TotalBarQty);
|
||||
var totNumPart = ListBalancedDet.Sum(x => x.TotalNumPart);
|
||||
|
||||
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Sicuro di voler creare la commessa su ogni impianto?{Environment.NewLine}L'impegno totale è per:" +
|
||||
$"{Environment.NewLine} - macchine: {numMacc}" +
|
||||
$"{Environment.NewLine} - parti: {totNumPart}" +
|
||||
$"{Environment.NewLine} - barre: {totBars}" +
|
||||
$"{Environment.NewLine} - tempo lavorazioni: {FormatEstTime(BalancedTotalTime)}"))
|
||||
return;
|
||||
|
||||
BatchCreateInfo newData = new BatchCreateInfo()
|
||||
{
|
||||
AllRecords = AllRecords,
|
||||
NewBatch = newBatch,
|
||||
GroupDetail = ListBalancedDet
|
||||
};
|
||||
|
||||
// invia info x richiesta creazione batch.
|
||||
await EC_ReqCreateBatch.InvokeAsync(newData);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<BalanceProgGroup AllRecords="@ListBalancedRecords" EC_RemBalance="RemoveBalance"></BalanceProgGroup>
|
||||
<BalanceProgGroup AllRecords="@ListBalancedRecords" EC_RemBalance="RemoveBalance" EC_ReqCreateBatch="CreateBatch"></BalanceProgGroup>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ using Newtonsoft.Json;
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using System.Threading.Tasks;
|
||||
using static EgwCoreLib.Lux.Core.Enums;
|
||||
using static Lux.UI.Components.Compo.Planner.BalanceProgGroup;
|
||||
|
||||
namespace Lux.UI.Components.Pages
|
||||
{
|
||||
@@ -73,6 +74,63 @@ namespace Lux.UI.Components.Pages
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creazione progetti da schedulare...
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
private async Task CreateBatch(BatchCreateInfo batchData)
|
||||
{
|
||||
// per prima cosa creo il batch...
|
||||
var recBatch = await DLService.ProductionBatchCreateAsync(batchData.NewBatch);
|
||||
// se ok batch...
|
||||
if (recBatch != null)
|
||||
{
|
||||
List<ProductionODLModel> listOdl = new List<ProductionODLModel>();
|
||||
// uso una Tupla
|
||||
var dictParts = new Dictionary<(int phaseId, int resId, string machine, int index), List<string>>();
|
||||
// ciclo x ogni macchina...
|
||||
foreach (var item in batchData.GroupDetail)
|
||||
{
|
||||
// dovrei recuperare Ciclo, Articolo, Fasi e Risorse dalle macchine ... x ora ND/ND/0/0
|
||||
int phaseId = 0;
|
||||
int resId = 0;
|
||||
int idx = 10; // fisso come fase/res
|
||||
// preparo la lista degli ODL
|
||||
var newOdl = new ProductionODLModel()
|
||||
{
|
||||
ProdBatchID = recBatch.ProdBatchID,
|
||||
ProdPlantCod = item.MachineName,
|
||||
Index = idx,
|
||||
PhaseID = phaseId,
|
||||
ResourceID = resId,
|
||||
Description = $"Ciclo ND | Art: ND | Fase: {phaseId} | Res: {resId}",
|
||||
EstimTime = item.TotalTime,
|
||||
Qty = item.TotalNumPart
|
||||
};
|
||||
listOdl.Add(newOdl);
|
||||
// costruisco il dizionario delle parts che mi servirà poi ad associare gli items
|
||||
dictParts[(phaseId, resId, item.MachineName, idx)] = item.TagList;
|
||||
}
|
||||
// se ho record li scrivo!
|
||||
if (listOdl.Count > 0)
|
||||
{
|
||||
// creazione ODL
|
||||
List<ProductionODLModel> dbList = await DLService.ProductionOdlCreateAsync(listOdl);
|
||||
|
||||
// fix items sul batch ID, ciclo sui prodGroup
|
||||
int numParts = 0;
|
||||
foreach (var item in batchData.AllRecords)
|
||||
{
|
||||
numParts += await DLService.ProdItemBulkAssignProdBatch(item.OrderRowID, recBatch.ProdBatchID);
|
||||
}
|
||||
|
||||
// ora lavoro sugli items x collegarli agli ODL... parto dal dizionario e cerco nell'elenco degli ODL creati...
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoBalance(int OrderRowID)
|
||||
{
|
||||
if (!await JSRuntime.InvokeAsync<bool>("confirm", $"Sicuro di voler Bilanciare la riga d'ordine?"))
|
||||
@@ -107,6 +165,18 @@ namespace Lux.UI.Components.Pages
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formattazione oraria impegno
|
||||
/// </summary>
|
||||
/// <param name="totSeconds"></param>
|
||||
/// <returns></returns>
|
||||
private string FormatEstTime(decimal totSeconds)
|
||||
{
|
||||
var tSpan = TimeSpan.FromSeconds((double)totSeconds);
|
||||
string answ = EgwCoreLib.Lux.Core.DtUtils.FormatDateTime(tSpan);
|
||||
return answ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ricezione update prod --> rileggo i dati!
|
||||
/// </summary>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50</UserSecretsId>
|
||||
<Version>0.9.2601.1919</Version>
|
||||
<Version>0.9.2601.2110</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<body>
|
||||
<i>LUX - Web Windows MES</i>
|
||||
<h4>Versione: 0.9.2601.1919</h4>
|
||||
<h4>Versione: 0.9.2601.2110</h4>
|
||||
<br /> Note di rilascio:
|
||||
<ul>
|
||||
<li>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.9.2601.1919
|
||||
0.9.2601.2110
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<item>
|
||||
<version>0.9.2601.1919</version>
|
||||
<version>0.9.2601.2110</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>
|
||||
|
||||
Reference in New Issue
Block a user