Stabilize export dashboard and fill purchasing KPIs
This commit is contained in:
@@ -191,12 +191,12 @@
|
|||||||
private double _purchasing3dFactor = 1d;
|
private double _purchasing3dFactor = 1d;
|
||||||
private double _purchasing3dLabelScale = 1.5d;
|
private double _purchasing3dLabelScale = 1.5d;
|
||||||
|
|
||||||
private readonly List<PurchasingKpiCard> KpiCards =
|
private IReadOnlyList<PurchasingKpiCard> KpiCards =>
|
||||||
[
|
[
|
||||||
new("Spend total", "Total spend", "-", "Netwr CHF historisch", "Historic Netwr CHF", Icons.Material.Filled.Payments, Color.Primary),
|
new("Spend total", "Total spend", FormatChf(Purchasing3dBaseRows.Sum(x => x.Spend)), "Simulationsbasis bis SAP-Liveimport", "Simulation basis until SAP live import", Icons.Material.Filled.Payments, Color.Primary),
|
||||||
new("Offene Bestellungen", "Open orders", "-", "Wert und Menge offen", "Open value and quantity", Icons.Material.Filled.PendingActions, Color.Warning),
|
new("Offene Bestellungen", "Open orders", FormatChf(Purchasing3dBaseRows.Sum(x => x.OpenValue)), "Wert offen aus Simulationsbasis", "Open value from simulation basis", Icons.Material.Filled.PendingActions, Color.Warning),
|
||||||
new("Kontrakte", "Contracts", "-", "Restverpflichtungen", "Remaining commitments", Icons.Material.Filled.Assignment, Color.Info),
|
new("Kontrakte", "Contracts", FormatChf(Purchasing3dBaseRows.Sum(x => x.ContractValue)), "Restverpflichtungen aus Simulationsbasis", "Remaining commitments from simulation basis", Icons.Material.Filled.Assignment, Color.Info),
|
||||||
new("Lieferantenperformance", "Supplier performance", "-", "Bewertung / Liefertermintreue", "Rating / delivery reliability", Icons.Material.Filled.Verified, Color.Success)
|
new("Lieferantenperformance", "Supplier performance", $"{Purchasing3dBaseRows.Average(x => x.SupplierScore):N1}%", "Durchschnitt Score aus Simulationsbasis", "Average score from simulation basis", Icons.Material.Filled.Verified, Color.Success)
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly List<PurchasingAxis> AnalysisAxes =
|
private readonly List<PurchasingAxis> AnalysisAxes =
|
||||||
@@ -290,6 +290,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string T(string german, string english) => UiText.Text(german, english);
|
private string T(string german, string english) => UiText.Text(german, english);
|
||||||
|
private static string FormatChf(double value) => $"CHF {value:N0}";
|
||||||
|
|
||||||
private async Task SetPurchasing3dIndicator(string value)
|
private async Task SetPurchasing3dIndicator(string value)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Data;
|
||||||
using TrafagSalesExporter.Data;
|
using TrafagSalesExporter.Data;
|
||||||
using TrafagSalesExporter.Models;
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
@@ -28,14 +29,7 @@ public sealed class DashboardPageService : IDashboardPageService
|
|||||||
.GroupBy(l => l.SiteId)
|
.GroupBy(l => l.SiteId)
|
||||||
.Select(g => g.OrderByDescending(l => l.Timestamp).First())
|
.Select(g => g.OrderByDescending(l => l.Timestamp).First())
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
var appLogs = await db.AppEventLogs
|
var latestAppLogsBySite = await LoadLatestAppLogsBySiteAsync(db);
|
||||||
.Where(l => l.SiteId != null)
|
|
||||||
.OrderByDescending(l => l.Timestamp)
|
|
||||||
.Take(1000)
|
|
||||||
.ToListAsync();
|
|
||||||
var latestAppLogsBySite = appLogs
|
|
||||||
.GroupBy(l => l.SiteId!.Value)
|
|
||||||
.ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.Timestamp).First());
|
|
||||||
|
|
||||||
var rows = sites.Select(s =>
|
var rows = sites.Select(s =>
|
||||||
{
|
{
|
||||||
@@ -86,6 +80,75 @@ public sealed class DashboardPageService : IDashboardPageService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<Dictionary<int, AppEventLog>> LoadLatestAppLogsBySiteAsync(AppDbContext db)
|
||||||
|
{
|
||||||
|
var connection = db.Database.GetDbConnection();
|
||||||
|
var shouldClose = connection.State != ConnectionState.Open;
|
||||||
|
if (shouldClose)
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = """
|
||||||
|
SELECT Id, Timestamp, Level, Category, SiteId, Land, Message, Details
|
||||||
|
FROM AppEventLogs
|
||||||
|
WHERE SiteId IS NOT NULL
|
||||||
|
ORDER BY Id DESC
|
||||||
|
LIMIT 1000;
|
||||||
|
""";
|
||||||
|
|
||||||
|
var logs = new List<AppEventLog>();
|
||||||
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
if (!TryReadInt(reader["SiteId"], out var siteId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!DateTime.TryParse(Convert.ToString(reader["Timestamp"]), out var timestamp))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
logs.Add(new AppEventLog
|
||||||
|
{
|
||||||
|
Id = TryReadInt(reader["Id"], out var id) ? id : 0,
|
||||||
|
Timestamp = timestamp,
|
||||||
|
Level = Convert.ToString(reader["Level"]) ?? string.Empty,
|
||||||
|
Category = Convert.ToString(reader["Category"]) ?? string.Empty,
|
||||||
|
SiteId = siteId,
|
||||||
|
Land = Convert.ToString(reader["Land"]) ?? string.Empty,
|
||||||
|
Message = Convert.ToString(reader["Message"]) ?? string.Empty,
|
||||||
|
Details = Convert.ToString(reader["Details"]) ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs
|
||||||
|
.GroupBy(l => l.SiteId!.Value)
|
||||||
|
.ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.Timestamp).First());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (shouldClose)
|
||||||
|
await connection.CloseAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadInt(object? value, out int number)
|
||||||
|
{
|
||||||
|
if (value is int intValue)
|
||||||
|
{
|
||||||
|
number = intValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is long longValue && longValue >= int.MinValue && longValue <= int.MaxValue)
|
||||||
|
{
|
||||||
|
number = (int)longValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.TryParse(Convert.ToString(value), out number);
|
||||||
|
}
|
||||||
|
|
||||||
private static List<string> BuildReadinessWarnings(List<Site> activeSites, List<SourceSystemDefinition> sourceSystems)
|
private static List<string> BuildReadinessWarnings(List<Site> activeSites, List<SourceSystemDefinition> sourceSystems)
|
||||||
{
|
{
|
||||||
var warnings = new List<string>();
|
var warnings = new List<string>();
|
||||||
|
|||||||
@@ -515,7 +515,7 @@
|
|||||||
|
|
||||||
window.trafagFinance3d = {
|
window.trafagFinance3d = {
|
||||||
render: function (canvas, rows, options) {
|
render: function (canvas, rows, options) {
|
||||||
if (!canvas) return;
|
if (!canvas || typeof canvas.addEventListener !== "function") return;
|
||||||
const normalizedRows = normalizeRows(rows);
|
const normalizedRows = normalizeRows(rows);
|
||||||
if (normalizedRows.length === 0) return;
|
if (normalizedRows.length === 0) return;
|
||||||
const existing = stateByCanvas.get(canvas);
|
const existing = stateByCanvas.get(canvas);
|
||||||
|
|||||||
Reference in New Issue
Block a user