Load purchasing EKPO and EKET metrics
This commit is contained in:
@@ -360,9 +360,9 @@
|
|||||||
|
|
||||||
private IReadOnlyList<PurchasingKpiCard> KpiCards =>
|
private IReadOnlyList<PurchasingKpiCard> KpiCards =>
|
||||||
[
|
[
|
||||||
new("Spend total", "Total spend", _liveState.EkpoLoaded ? "EKPO live" : T("wartet auf EKPO", "waiting for EKPO"), _liveState.EkpoLoaded ? "Positionsdaten verfuegbar" : "EKKO live, Positionswerte fehlen noch", _liveState.EkpoLoaded ? "Position data available" : "EKKO live, position values still missing", Icons.Material.Filled.Payments, Color.Primary),
|
new("Spend total", "Total spend", _liveState.EkpoLoaded ? FormatChf(_liveState.SpendChfSample) : T("wartet auf EKPO", "waiting for EKPO"), _liveState.EkpoLoaded ? "EKPO-Live-Sample" : "EKKO live, Positionswerte fehlen noch", _liveState.EkpoLoaded ? "EKPO live sample" : "EKKO live, position values still missing", Icons.Material.Filled.Payments, Color.Primary),
|
||||||
new("Offene Bestellungen", "Open orders", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", _liveState.EkkoLoaded ? "EKKO-Belege seit Jahresbeginn" : "Noch nicht geladen", _liveState.EkkoLoaded ? "EKKO orders since start of year" : "Not loaded yet", Icons.Material.Filled.PendingActions, Color.Warning),
|
new("Offene Bestellungen", "Open orders", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", _liveState.EkkoLoaded ? "EKKO-Belege seit Jahresbeginn" : "Noch nicht geladen", _liveState.EkkoLoaded ? "EKKO orders since start of year" : "Not loaded yet", Icons.Material.Filled.PendingActions, Color.Warning),
|
||||||
new("Kontrakte", "Contracts", _liveState.EketLoaded ? _liveState.ScheduleSampleCount.ToString("N0") : T("wartet auf EKET", "waiting for EKET"), _liveState.EketLoaded ? "Termin-/Einteilungsprobe verfuegbar" : "EKKO live, Terminwerte fehlen noch", _liveState.EketLoaded ? "Schedule sample available" : "EKKO live, schedule values still missing", Icons.Material.Filled.Assignment, Color.Info),
|
new("Kontrakte", "Contracts", _liveState.EketLoaded ? FormatChf(_liveState.ContractValueSample) : T("wartet auf EKET", "waiting for EKET"), _liveState.EketLoaded ? "Restwert aus EKET/EKPO-Sample" : "EKKO live, Terminwerte fehlen noch", _liveState.EketLoaded ? "Remaining value from EKET/EKPO sample" : "EKKO live, schedule values still missing", Icons.Material.Filled.Assignment, Color.Info),
|
||||||
new("Lieferantenperformance", "Supplier performance", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", _liveState.EkkoLoaded ? "Lieferanten in EKKO-Liveprobe" : "Noch nicht geladen", _liveState.EkkoLoaded ? "Suppliers in EKKO live sample" : "Not loaded yet", Icons.Material.Filled.Verified, Color.Success)
|
new("Lieferantenperformance", "Supplier performance", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", _liveState.EkkoLoaded ? "Lieferanten in EKKO-Liveprobe" : "Noch nicht geladen", _liveState.EkkoLoaded ? "Suppliers in EKKO live sample" : "Not loaded yet", Icons.Material.Filled.Verified, Color.Success)
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionKpi> SpendKpis =>
|
private IReadOnlyList<PurchasingSectionKpi> SpendKpis =>
|
||||||
[
|
[
|
||||||
new("Spend CHF", "Spend CHF", _liveState.EkpoLoaded ? "EKPO live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.Spend)), _liveState.EkpoLoaded ? "aus SAP Positionen" : "Simulation bis EKPO liefert", _liveState.EkpoLoaded ? "from SAP item rows" : "simulation until EKPO delivers"),
|
new("Spend CHF", "Spend CHF", _liveState.EkpoLoaded ? FormatChf(_liveState.SpendChfSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.Spend)), _liveState.EkpoLoaded ? "aus SAP Positionen" : "Simulation bis EKPO liefert", _liveState.EkpoLoaded ? "from SAP item rows" : "simulation until EKPO delivers"),
|
||||||
new("Jahre", "Years", "2024-2026", "aus PBIX-Struktur", "from PBIX structure"),
|
new("Jahre", "Years", "2024-2026", "aus PBIX-Struktur", "from PBIX structure"),
|
||||||
new("Dimensionen", "Dimensions", "4", "Jahr, Lieferant, Warengruppe, Artikel", "year, supplier, material group, article"),
|
new("Dimensionen", "Dimensions", "4", "Jahr, Lieferant, Warengruppe, Artikel", "year, supplier, material group, article"),
|
||||||
new("SAP Status", "SAP status", _liveState.EkpoLoaded ? "live" : "wartet", "EKPOSet fuer Spend notwendig", "EKPOSet required for spend")
|
new("SAP Status", "SAP status", _liveState.EkpoLoaded ? "live" : "wartet", "EKPOSet fuer Spend notwendig", "EKPOSet required for spend")
|
||||||
@@ -442,13 +442,13 @@
|
|||||||
[
|
[
|
||||||
new("Bestellungen", "Orders", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", "EKKO live seit Jahresbeginn", "EKKO live since start of year"),
|
new("Bestellungen", "Orders", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", "EKKO live seit Jahresbeginn", "EKKO live since start of year"),
|
||||||
new("Lieferanten", "Suppliers", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "aus EKKO-Liveprobe", "from EKKO live sample"),
|
new("Lieferanten", "Suppliers", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "aus EKKO-Liveprobe", "from EKKO live sample"),
|
||||||
new("Offener Wert", "Open value", _liveState.EkpoLoaded ? "EKPO live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.OpenValue)), _liveState.EkpoLoaded ? "aus SAP Positionen" : "Simulation bis EKPO liefert", _liveState.EkpoLoaded ? "from SAP item rows" : "simulation until EKPO delivers"),
|
new("Offener Wert", "Open value", _liveState.EketLoaded ? FormatChf(_liveState.OpenValueSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.OpenValue)), _liveState.EketLoaded ? "aus SAP Terminen/Positionen" : "Simulation bis EKET liefert", _liveState.EketLoaded ? "from SAP schedules/items" : "simulation until EKET delivers"),
|
||||||
new("Offene Menge", "Open quantity", _liveState.EkpoLoaded ? "EKPO live" : Purchasing3dBaseRows.Sum(x => x.OpenQuantity).ToString("N0"), _liveState.EkpoLoaded ? "aus SAP Positionen" : "Simulation bis EKPO liefert", _liveState.EkpoLoaded ? "from SAP item rows" : "simulation until EKPO delivers")
|
new("Offene Menge", "Open quantity", _liveState.EketLoaded ? _liveState.OpenQuantitySample.ToString("N0") : Purchasing3dBaseRows.Sum(x => x.OpenQuantity).ToString("N0"), _liveState.EketLoaded ? "aus SAP Terminen" : "Simulation bis EKET liefert", _liveState.EketLoaded ? "from SAP schedules" : "simulation until EKET delivers")
|
||||||
];
|
];
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionKpi> ContractKpis =>
|
private IReadOnlyList<PurchasingSectionKpi> ContractKpis =>
|
||||||
[
|
[
|
||||||
new("Restwert", "Remaining value", _liveState.EketLoaded ? "EKET live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.ContractValue)), _liveState.EketLoaded ? "aus SAP Einteilungen" : "Simulation bis EKET liefert", _liveState.EketLoaded ? "from SAP schedules" : "simulation until EKET delivers"),
|
new("Restwert", "Remaining value", _liveState.EketLoaded ? FormatChf(_liveState.ContractValueSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.ContractValue)), _liveState.EketLoaded ? "aus SAP Einteilungen" : "Simulation bis EKET liefert", _liveState.EketLoaded ? "from SAP schedules" : "simulation until EKET delivers"),
|
||||||
new("Einteilungen", "Schedules", _liveState.EketLoaded ? _liveState.ScheduleSampleCount.ToString("N0") : "-", "EKET-Probe", "EKET sample"),
|
new("Einteilungen", "Schedules", _liveState.EketLoaded ? _liveState.ScheduleSampleCount.ToString("N0") : "-", "EKET-Probe", "EKET sample"),
|
||||||
new("Abrufquote", "Consumption", "offen", "braucht Kontrakt- und Abrufdaten", "needs contract and call-off data"),
|
new("Abrufquote", "Consumption", "offen", "braucht Kontrakt- und Abrufdaten", "needs contract and call-off data"),
|
||||||
new("Faelligkeit", "Due date", _liveState.LatestOrderDate?.ToString("yyyy-MM-dd") ?? "-", "letztes bekanntes EKKO-Datum", "latest known EKKO date")
|
new("Faelligkeit", "Due date", _liveState.LatestOrderDate?.ToString("yyyy-MM-dd") ?? "-", "letztes bekanntes EKKO-Datum", "latest known EKKO date")
|
||||||
@@ -458,7 +458,7 @@
|
|||||||
[
|
[
|
||||||
new("Aktive Lieferanten", "Active suppliers", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "EKKO live seit Jahresbeginn", "EKKO live since start of year"),
|
new("Aktive Lieferanten", "Active suppliers", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "EKKO live seit Jahresbeginn", "EKKO live since start of year"),
|
||||||
new("Performance Score", "Performance score", $"{Purchasing3dBaseRows.Average(x => x.SupplierScore):N1}%", "Simulation bis Bewertungsdaten kommen", "simulation until rating data arrives"),
|
new("Performance Score", "Performance score", $"{Purchasing3dBaseRows.Average(x => x.SupplierScore):N1}%", "Simulation bis Bewertungsdaten kommen", "simulation until rating data arrives"),
|
||||||
new("Preisindikator", "Price indicator", _liveState.EkpoLoaded ? "EKPO live" : "wartet", "Netwr CHF/Stk braucht EKPO", "Netwr CHF/unit needs EKPO"),
|
new("Preisindikator", "Price indicator", _liveState.EkpoLoaded ? FormatChf(_liveState.SpendChfSample) : "wartet", "Netwr CHF/Stk braucht EKPO", "Netwr CHF/unit needs EKPO"),
|
||||||
new("Qualitaet", "Quality", "offen", "Reklamationsquelle noch nicht angebunden", "claim source not connected yet")
|
new("Qualitaet", "Quality", "offen", "Reklamationsquelle noch nicht angebunden", "claim source not connected yet")
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -527,7 +527,9 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionChartRow> SpendChartRows
|
private IReadOnlyList<PurchasingSectionChartRow> SpendChartRows
|
||||||
=> BuildPurchasingChartRows(x => x.Spend, FormatChf);
|
=> _liveState.EkpoLoaded && _liveState.SpendChartRows.Count > 0
|
||||||
|
? BuildLiveChartRows(_liveState.SpendChartRows, FormatChf)
|
||||||
|
: BuildPurchasingChartRows(x => x.Spend, FormatChf);
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionChartRow> OpenOrderChartRows
|
private IReadOnlyList<PurchasingSectionChartRow> OpenOrderChartRows
|
||||||
=> _liveState.EkkoLoaded
|
=> _liveState.EkkoLoaded
|
||||||
@@ -535,7 +537,9 @@
|
|||||||
: BuildPurchasingChartRows(x => x.OpenValue, FormatChf);
|
: BuildPurchasingChartRows(x => x.OpenValue, FormatChf);
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionChartRow> ContractChartRows
|
private IReadOnlyList<PurchasingSectionChartRow> ContractChartRows
|
||||||
=> BuildPurchasingChartRows(x => x.ContractValue, FormatChf);
|
=> _liveState.EketLoaded && _liveState.ContractChartRows.Count > 0
|
||||||
|
? BuildLiveChartRows(_liveState.ContractChartRows, FormatChf)
|
||||||
|
: BuildPurchasingChartRows(x => x.ContractValue, FormatChf);
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionChartRow> SupplierChartRows
|
private IReadOnlyList<PurchasingSectionChartRow> SupplierChartRows
|
||||||
=> BuildPurchasingChartRows(x => x.SupplierScore, value => $"{value:N1}%");
|
=> BuildPurchasingChartRows(x => x.SupplierScore, value => $"{value:N1}%");
|
||||||
@@ -544,13 +548,13 @@
|
|||||||
[
|
[
|
||||||
BuildStatus("EKKO Bestellkoepfe", "EKKO purchase headers", _liveState.EkkoLoaded, _liveState.EkkoLoaded ? $"{_liveState.PurchaseOrderCount:N0}" : "-"),
|
BuildStatus("EKKO Bestellkoepfe", "EKKO purchase headers", _liveState.EkkoLoaded, _liveState.EkkoLoaded ? $"{_liveState.PurchaseOrderCount:N0}" : "-"),
|
||||||
BuildStatus("EKPO Positionen", "EKPO item rows", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? $"{_liveState.PositionSampleCount:N0}" : "0"),
|
BuildStatus("EKPO Positionen", "EKPO item rows", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? $"{_liveState.PositionSampleCount:N0}" : "0"),
|
||||||
BuildStatus("Spend CHF", "Spend CHF", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? "live" : "Simulation")
|
BuildStatus("Spend CHF", "Spend CHF", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? FormatChf(_liveState.SpendChfSample) : "Simulation")
|
||||||
];
|
];
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionStatusRow> OpenOrderStatusRows =>
|
private IReadOnlyList<PurchasingSectionStatusRow> OpenOrderStatusRows =>
|
||||||
[
|
[
|
||||||
BuildStatus("Bestellkoepfe", "Purchase headers", _liveState.EkkoLoaded, _liveState.EkkoLoaded ? "live" : "-"),
|
BuildStatus("Bestellkoepfe", "Purchase headers", _liveState.EkkoLoaded, _liveState.EkkoLoaded ? "live" : "-"),
|
||||||
BuildStatus("Offene Werte", "Open values", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? "live" : "wartet auf EKPO"),
|
BuildStatus("Offene Werte", "Open values", _liveState.EketLoaded, _liveState.EketLoaded ? FormatChf(_liveState.OpenValueSample) : "wartet auf EKET"),
|
||||||
BuildStatus("Faelligkeiten", "Due dates", _liveState.EketLoaded, _liveState.EketLoaded ? "live" : "wartet auf EKET")
|
BuildStatus("Faelligkeiten", "Due dates", _liveState.EketLoaded, _liveState.EketLoaded ? "live" : "wartet auf EKET")
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -558,7 +562,7 @@
|
|||||||
[
|
[
|
||||||
BuildStatus("Kontraktkopf/-position", "Contract header/item", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? "live" : "wartet auf EKPO"),
|
BuildStatus("Kontraktkopf/-position", "Contract header/item", _liveState.EkpoLoaded, _liveState.EkpoLoaded ? "live" : "wartet auf EKPO"),
|
||||||
BuildStatus("Einteilungen", "Schedules", _liveState.EketLoaded, _liveState.EketLoaded ? $"{_liveState.ScheduleSampleCount:N0}" : "0"),
|
BuildStatus("Einteilungen", "Schedules", _liveState.EketLoaded, _liveState.EketLoaded ? $"{_liveState.ScheduleSampleCount:N0}" : "0"),
|
||||||
BuildStatus("Restverpflichtung", "Remaining commitment", _liveState.EkpoLoaded && _liveState.EketLoaded, _liveState.EkpoLoaded && _liveState.EketLoaded ? "live" : "Simulation")
|
BuildStatus("Restverpflichtung", "Remaining commitment", _liveState.EkpoLoaded && _liveState.EketLoaded, _liveState.EkpoLoaded && _liveState.EketLoaded ? FormatChf(_liveState.ContractValueSample) : "Simulation")
|
||||||
];
|
];
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionStatusRow> SupplierStatusRows =>
|
private IReadOnlyList<PurchasingSectionStatusRow> SupplierStatusRows =>
|
||||||
@@ -570,7 +574,7 @@
|
|||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionDetailRow> SpendDetailRows =>
|
private IReadOnlyList<PurchasingSectionDetailRow> SpendDetailRows =>
|
||||||
[
|
[
|
||||||
new("Spend nach Jahr", "Spend by year", _liveState.EkpoLoaded ? "SAP live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.Spend)), "EKKOSet.Bedat Jahr", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
new("Spend nach Jahr", "Spend by year", _liveState.EkpoLoaded ? FormatChf(_liveState.SpendChfSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.Spend)), "EKKOSet.Bedat Jahr", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
||||||
new("Spend nach Lieferant", "Spend by supplier", TopSpendLabel, "Data.Name / EKKOSet.Lifnr", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
new("Spend nach Lieferant", "Spend by supplier", TopSpendLabel, "Data.Name / EKKOSet.Lifnr", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
||||||
new("Spend nach Warengruppe", "Spend by material group", TopMaterialGroupLabel, "Data (2).Warengruppe", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
new("Spend nach Warengruppe", "Spend by material group", TopMaterialGroupLabel, "Data (2).Warengruppe", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
||||||
new("Spend nach Artikel", "Spend by article", TopArticleLabel, "EKPOSet.Matnr / Txz01", _liveState.EkpoLoaded ? "SAP live" : "Wartet auf SAP")
|
new("Spend nach Artikel", "Spend by article", TopArticleLabel, "EKPOSet.Matnr / Txz01", _liveState.EkpoLoaded ? "SAP live" : "Wartet auf SAP")
|
||||||
@@ -580,13 +584,13 @@
|
|||||||
[
|
[
|
||||||
new("Bestellungen seit Jahresbeginn", "Orders since start of year", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", "EKKOSet.Bedat", _liveState.EkkoLoaded ? "SAP live" : "Wartet auf SAP"),
|
new("Bestellungen seit Jahresbeginn", "Orders since start of year", _liveState.EkkoLoaded ? _liveState.PurchaseOrderCount.ToString("N0") : "-", "EKKOSet.Bedat", _liveState.EkkoLoaded ? "SAP live" : "Wartet auf SAP"),
|
||||||
new("Lieferanten mit Bestellung", "Suppliers with order", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "EKKOSet.Lifnr", _liveState.EkkoLoaded ? "SAP live" : "Wartet auf SAP"),
|
new("Lieferanten mit Bestellung", "Suppliers with order", _liveState.EkkoLoaded ? _liveState.SupplierCount.ToString("N0") : "-", "EKKOSet.Lifnr", _liveState.EkkoLoaded ? "SAP live" : "Wartet auf SAP"),
|
||||||
new("Offener Bestellwert", "Open order value", _liveState.EkpoLoaded ? "SAP live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.OpenValue)), "EKPOSet.Netwr CHF", _liveState.EkpoLoaded ? "SAP live" : "Simulation"),
|
new("Offener Bestellwert", "Open order value", _liveState.EketLoaded ? FormatChf(_liveState.OpenValueSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.OpenValue)), "EKPO/EKET", _liveState.EketLoaded ? "SAP live" : "Simulation"),
|
||||||
new("Faelligkeiten", "Due dates", _liveState.EketLoaded ? "SAP live" : "wartet auf EKET", "eketSet.Eindt", _liveState.EketLoaded ? "SAP live" : "Wartet auf SAP")
|
new("Faelligkeiten", "Due dates", _liveState.EketLoaded ? "SAP live" : "wartet auf EKET", "eketSet.Eindt", _liveState.EketLoaded ? "SAP live" : "Wartet auf SAP")
|
||||||
];
|
];
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionDetailRow> ContractDetailRows =>
|
private IReadOnlyList<PurchasingSectionDetailRow> ContractDetailRows =>
|
||||||
[
|
[
|
||||||
new("Restverpflichtung", "Remaining commitment", _liveState.EketLoaded ? "SAP live" : FormatChf(Purchasing3dBaseRows.Sum(x => x.ContractValue)), "EKPO/EKET", _liveState.EketLoaded ? "SAP live" : "Simulation"),
|
new("Restverpflichtung", "Remaining commitment", _liveState.EketLoaded ? FormatChf(_liveState.ContractValueSample) : FormatChf(Purchasing3dBaseRows.Sum(x => x.ContractValue)), "EKPO/EKET", _liveState.EketLoaded ? "SAP live" : "Simulation"),
|
||||||
new("Mengenkontrakte", "Quantity contracts", _liveState.EkpoLoaded ? "SAP live" : "wartet auf EKPO", "EKPOSet.Menge", _liveState.EkpoLoaded ? "SAP live" : "Wartet auf SAP"),
|
new("Mengenkontrakte", "Quantity contracts", _liveState.EkpoLoaded ? "SAP live" : "wartet auf EKPO", "EKPOSet.Menge", _liveState.EkpoLoaded ? "SAP live" : "Wartet auf SAP"),
|
||||||
new("Abrufquote", "Consumption rate", "offen", "Kontraktmenge / Abrufmenge", "Wartet auf SAP"),
|
new("Abrufquote", "Consumption rate", "offen", "Kontraktmenge / Abrufmenge", "Wartet auf SAP"),
|
||||||
new("Faellige Verpflichtungen", "Due commitments", _liveState.EketLoaded ? "SAP live" : "wartet auf EKET", "eketSet", _liveState.EketLoaded ? "SAP live" : "Wartet auf SAP")
|
new("Faellige Verpflichtungen", "Due commitments", _liveState.EketLoaded ? "SAP live" : "wartet auf EKET", "eketSet", _liveState.EketLoaded ? "SAP live" : "Wartet auf SAP")
|
||||||
@@ -614,9 +618,10 @@
|
|||||||
|
|
||||||
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 static string FormatChf(double value) => $"CHF {value:N0}";
|
||||||
private string TopSpendLabel => BuildTopLabel(x => x.Spend, FormatChf);
|
private static string FormatChf(decimal value) => $"CHF {value:N0}";
|
||||||
private string TopMaterialGroupLabel => BuildTopLabel(x => x.Spend, FormatChf, "Warengruppe");
|
private string TopSpendLabel => _liveState.EkpoLoaded && !string.IsNullOrWhiteSpace(_liveState.TopSupplierLabel) ? _liveState.TopSupplierLabel : BuildTopLabel(x => x.Spend, FormatChf);
|
||||||
private string TopArticleLabel => BuildTopLabel(x => x.Spend, FormatChf, "Artikel");
|
private string TopMaterialGroupLabel => _liveState.EkpoLoaded && !string.IsNullOrWhiteSpace(_liveState.TopMaterialGroupLabel) ? _liveState.TopMaterialGroupLabel : BuildTopLabel(x => x.Spend, FormatChf, "Warengruppe");
|
||||||
|
private string TopArticleLabel => _liveState.EkpoLoaded && !string.IsNullOrWhiteSpace(_liveState.TopArticleLabel) ? _liveState.TopArticleLabel : BuildTopLabel(x => x.Spend, FormatChf, "Artikel");
|
||||||
private string PurchasingStatusText
|
private string PurchasingStatusText
|
||||||
=> _liveLoading
|
=> _liveLoading
|
||||||
? T("SAP-Einkaufsdaten werden geladen...", "Loading SAP purchasing data...")
|
? T("SAP-Einkaufsdaten werden geladen...", "Loading SAP purchasing data...")
|
||||||
@@ -654,9 +659,23 @@
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<PurchasingSectionChartRow> BuildLiveChartRows(IReadOnlyList<PurchasingLiveChartPoint> rows, Func<decimal, string> formatter)
|
||||||
|
{
|
||||||
|
var max = rows.Count == 0 ? 0m : rows.Max(row => row.Value);
|
||||||
|
return rows
|
||||||
|
.Select((row, index) => new PurchasingSectionChartRow(
|
||||||
|
row.Label,
|
||||||
|
formatter(row.Value),
|
||||||
|
max <= 0 ? 0 : (double)(row.Value / max * 100m),
|
||||||
|
PurchasingPalette[index % PurchasingPalette.Length]))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private IReadOnlyList<PurchasingSectionChartRow> BuildOpenOrderLiveChartRows()
|
private IReadOnlyList<PurchasingSectionChartRow> BuildOpenOrderLiveChartRows()
|
||||||
{
|
{
|
||||||
var simulatedRows = BuildPurchasingChartRows(x => x.OpenValue, FormatChf).ToList();
|
var simulatedRows = BuildPurchasingChartRows(x => x.OpenValue, FormatChf).ToList();
|
||||||
|
if (_liveState.EketLoaded && _liveState.OpenValueChartRows.Count > 0)
|
||||||
|
simulatedRows = BuildLiveChartRows(_liveState.OpenValueChartRows, FormatChf).ToList();
|
||||||
simulatedRows.Insert(0, new PurchasingSectionChartRow(T("EKKO Bestellungen live", "EKKO orders live"), _liveState.PurchaseOrderCount.ToString("N0"), 100d, "#2e7d32"));
|
simulatedRows.Insert(0, new PurchasingSectionChartRow(T("EKKO Bestellungen live", "EKKO orders live"), _liveState.PurchaseOrderCount.ToString("N0"), 100d, "#2e7d32"));
|
||||||
simulatedRows.Insert(1, new PurchasingSectionChartRow(T("Lieferanten live", "Suppliers live"), _liveState.SupplierCount.ToString("N0"), _liveState.PurchaseOrderCount <= 0 ? 0 : Math.Min(100d, _liveState.SupplierCount / (double)_liveState.PurchaseOrderCount * 100d), "#1565c0"));
|
simulatedRows.Insert(1, new PurchasingSectionChartRow(T("Lieferanten live", "Suppliers live"), _liveState.SupplierCount.ToString("N0"), _liveState.PurchaseOrderCount <= 0 ? 0 : Math.Min(100d, _liveState.SupplierCount / (double)_liveState.PurchaseOrderCount * 100d), "#1565c0"));
|
||||||
return simulatedRows.Take(6).ToList();
|
return simulatedRows.Take(6).ToList();
|
||||||
|
|||||||
@@ -16,5 +16,17 @@ public sealed class PurchasingDashboardLiveState
|
|||||||
public DateTime? LatestOrderDate { get; set; }
|
public DateTime? LatestOrderDate { get; set; }
|
||||||
public int PositionSampleCount { get; set; }
|
public int PositionSampleCount { get; set; }
|
||||||
public int ScheduleSampleCount { get; set; }
|
public int ScheduleSampleCount { get; set; }
|
||||||
|
public decimal SpendChfSample { get; set; }
|
||||||
|
public decimal OpenQuantitySample { get; set; }
|
||||||
|
public decimal OpenValueSample { get; set; }
|
||||||
|
public decimal ContractValueSample { get; set; }
|
||||||
|
public string TopSupplierLabel { get; set; } = string.Empty;
|
||||||
|
public string TopMaterialGroupLabel { get; set; } = string.Empty;
|
||||||
|
public string TopArticleLabel { get; set; } = string.Empty;
|
||||||
|
public List<PurchasingLiveChartPoint> SpendChartRows { get; set; } = [];
|
||||||
|
public List<PurchasingLiveChartPoint> OpenValueChartRows { get; set; } = [];
|
||||||
|
public List<PurchasingLiveChartPoint> ContractChartRows { get; set; } = [];
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed record PurchasingLiveChartPoint(string Label, decimal Value);
|
||||||
|
|||||||
@@ -74,21 +74,26 @@ public sealed class PurchasingDashboardService : IPurchasingDashboardService
|
|||||||
{
|
{
|
||||||
var ekpoRows = await ReadRowsAsync(
|
var ekpoRows = await ReadRowsAsync(
|
||||||
client,
|
client,
|
||||||
$"{baseUrl}EKPOSet?$format=json&$top=50&$filter={Uri.EscapeDataString($"Ebeln eq '{firstEbeln}'")}",
|
$"{baseUrl}EKPOSet?$format=json&$top=1000&$filter={Uri.EscapeDataString($"Ebeln ge '{firstEbeln}'")}",
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
state.PositionSampleCount = ekpoRows.Count;
|
state.PositionSampleCount = ekpoRows.Count;
|
||||||
state.EkpoLoaded = ekpoRows.Count > 0;
|
state.EkpoLoaded = ekpoRows.Count > 0;
|
||||||
|
|
||||||
var eketRows = await ReadRowsAsync(
|
var eketRows = await ReadRowsAsync(
|
||||||
client,
|
client,
|
||||||
$"{baseUrl}eketSet?$format=json&$top=50&$filter={Uri.EscapeDataString($"Ebeln eq '{firstEbeln}'")}",
|
$"{baseUrl}eketSet?$format=json&$top=1000&$filter={Uri.EscapeDataString($"Ebeln ge '{firstEbeln}'")}",
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
state.ScheduleSampleCount = eketRows.Count;
|
state.ScheduleSampleCount = eketRows.Count;
|
||||||
state.EketLoaded = eketRows.Count > 0;
|
state.EketLoaded = eketRows.Count > 0;
|
||||||
|
|
||||||
|
ApplyEkpoMetrics(state, ekkoRows, ekpoRows);
|
||||||
|
ApplyEketMetrics(state, ekpoRows, eketRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.Message = state.EkpoLoaded
|
state.Message = state.EkpoLoaded && state.EketLoaded
|
||||||
? "SAP Einkaufsdaten geladen."
|
? "SAP Einkaufsdaten inkl. EKPO/EKET geladen."
|
||||||
|
: state.EkpoLoaded
|
||||||
|
? "SAP Einkaufsdaten inkl. EKPO geladen; EKET liefert noch keine Termindaten."
|
||||||
: "EKKO ist live geladen; EKPO/EKET liefern aktuell noch keine Positionsdaten.";
|
: "EKKO ist live geladen; EKPO/EKET liefern aktuell noch keine Positionsdaten.";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -99,6 +104,105 @@ public sealed class PurchasingDashboardService : IPurchasingDashboardService
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ApplyEkpoMetrics(
|
||||||
|
PurchasingDashboardLiveState state,
|
||||||
|
List<Dictionary<string, object?>> ekkoRows,
|
||||||
|
List<Dictionary<string, object?>> ekpoRows)
|
||||||
|
{
|
||||||
|
if (ekpoRows.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var supplierByEbeln = ekkoRows
|
||||||
|
.Select(row => new { Ebeln = GetText(row, "Ebeln"), Lifnr = GetText(row, "Lifnr") })
|
||||||
|
.Where(row => !string.IsNullOrWhiteSpace(row.Ebeln))
|
||||||
|
.GroupBy(row => row.Ebeln, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(group => group.Key, group => group.First().Lifnr, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var enriched = ekpoRows
|
||||||
|
.Select(row =>
|
||||||
|
{
|
||||||
|
var ebeln = GetText(row, "Ebeln");
|
||||||
|
supplierByEbeln.TryGetValue(ebeln, out var supplier);
|
||||||
|
var netwr = GetDecimal(row, "Netwr");
|
||||||
|
var quantity = GetDecimal(row, "Menge");
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Ebeln = ebeln,
|
||||||
|
Supplier = string.IsNullOrWhiteSpace(supplier) ? "ohne Lieferant" : supplier,
|
||||||
|
Material = FirstNonEmpty(GetText(row, "Matnr"), GetText(row, "Txz01"), "ohne Artikel"),
|
||||||
|
MaterialGroup = FirstNonEmpty(GetText(row, "Matkl"), "ohne Warengruppe"),
|
||||||
|
NetValue = netwr,
|
||||||
|
Quantity = quantity
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
state.SpendChfSample = enriched.Sum(row => row.NetValue);
|
||||||
|
state.TopSupplierLabel = BuildTopLabel(enriched.GroupBy(row => row.Supplier), row => row.NetValue, "Lieferant");
|
||||||
|
state.TopMaterialGroupLabel = BuildTopLabel(enriched.GroupBy(row => row.MaterialGroup), row => row.NetValue, "Warengruppe");
|
||||||
|
state.TopArticleLabel = BuildTopLabel(enriched.GroupBy(row => row.Material), row => row.NetValue, "Artikel");
|
||||||
|
state.SpendChartRows = enriched
|
||||||
|
.GroupBy(row => row.Supplier)
|
||||||
|
.Select(group => new PurchasingLiveChartPoint($"Lief. {group.Key}", group.Sum(row => row.NetValue)))
|
||||||
|
.OrderByDescending(row => row.Value)
|
||||||
|
.Take(6)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyEketMetrics(
|
||||||
|
PurchasingDashboardLiveState state,
|
||||||
|
List<Dictionary<string, object?>> ekpoRows,
|
||||||
|
List<Dictionary<string, object?>> eketRows)
|
||||||
|
{
|
||||||
|
if (eketRows.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var netPriceByPosition = ekpoRows
|
||||||
|
.Select(row =>
|
||||||
|
{
|
||||||
|
var ebeln = GetText(row, "Ebeln");
|
||||||
|
var ebelp = GetText(row, "Ebelp");
|
||||||
|
var key = $"{ebeln}|{ebelp}";
|
||||||
|
var quantity = GetDecimal(row, "Menge");
|
||||||
|
var netValue = GetDecimal(row, "Netwr");
|
||||||
|
var netPrice = quantity == 0 ? 0 : netValue / quantity;
|
||||||
|
return new { key, netPrice };
|
||||||
|
})
|
||||||
|
.GroupBy(row => row.key, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(group => group.Key, group => group.First().netPrice, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var enriched = eketRows
|
||||||
|
.Select(row =>
|
||||||
|
{
|
||||||
|
var ebeln = GetText(row, "Ebeln");
|
||||||
|
var ebelp = GetText(row, "Ebelp");
|
||||||
|
var key = $"{ebeln}|{ebelp}";
|
||||||
|
netPriceByPosition.TryGetValue(key, out var netPrice);
|
||||||
|
var quantity = GetDecimal(row, "Menge");
|
||||||
|
var received = GetDecimal(row, "Wemng");
|
||||||
|
var openQuantity = Math.Max(0, quantity - received);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Ebeln = ebeln,
|
||||||
|
DueDate = TryParseSapDate(GetText(row, "Eindt")),
|
||||||
|
OpenQuantity = openQuantity,
|
||||||
|
OpenValue = openQuantity * netPrice
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
state.OpenQuantitySample = enriched.Sum(row => row.OpenQuantity);
|
||||||
|
state.OpenValueSample = enriched.Sum(row => row.OpenValue);
|
||||||
|
state.ContractValueSample = state.OpenValueSample;
|
||||||
|
state.OpenValueChartRows = enriched
|
||||||
|
.GroupBy(row => row.DueDate?.ToString("yyyy-MM") ?? "ohne Termin")
|
||||||
|
.Select(group => new PurchasingLiveChartPoint(group.Key, group.Sum(row => row.OpenValue)))
|
||||||
|
.OrderBy(row => row.Label)
|
||||||
|
.Take(6)
|
||||||
|
.ToList();
|
||||||
|
state.ContractChartRows = state.OpenValueChartRows.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private static HttpClient CreateClient(string username, string password)
|
private static HttpClient CreateClient(string username, string password)
|
||||||
{
|
{
|
||||||
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(45) };
|
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(45) };
|
||||||
@@ -151,6 +255,27 @@ public sealed class PurchasingDashboardService : IPurchasingDashboardService
|
|||||||
private static string GetText(Dictionary<string, object?> row, string key)
|
private static string GetText(Dictionary<string, object?> row, string key)
|
||||||
=> row.TryGetValue(key, out var value) ? Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty : string.Empty;
|
=> row.TryGetValue(key, out var value) ? Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty : string.Empty;
|
||||||
|
|
||||||
|
private static decimal GetDecimal(Dictionary<string, object?> row, string key)
|
||||||
|
{
|
||||||
|
var text = GetText(row, key);
|
||||||
|
return decimal.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out var value)
|
||||||
|
|| decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out value)
|
||||||
|
? value
|
||||||
|
: 0m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FirstNonEmpty(params string[] values)
|
||||||
|
=> values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty;
|
||||||
|
|
||||||
|
private static string BuildTopLabel<T>(IEnumerable<IGrouping<string, T>> groups, Func<T, decimal> selector, string fallback)
|
||||||
|
{
|
||||||
|
var top = groups
|
||||||
|
.Select(group => new { Label = group.Key, Value = group.Sum(selector) })
|
||||||
|
.OrderByDescending(row => row.Value)
|
||||||
|
.FirstOrDefault();
|
||||||
|
return top is null ? fallback : $"{top.Label}: CHF {top.Value:N0}";
|
||||||
|
}
|
||||||
|
|
||||||
private static DateTime? TryParseSapDate(string value)
|
private static DateTime? TryParseSapDate(string value)
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var parsed))
|
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var parsed))
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ Das Dashboard wurde fachlich um diese Bereiche erweitert:
|
|||||||
- `Lieferanten`
|
- `Lieferanten`
|
||||||
- `PBIX Vorlage`
|
- `PBIX Vorlage`
|
||||||
- `3D Simulation`
|
- `3D Simulation`
|
||||||
|
- `Ideen`
|
||||||
- Unterpunkt `Einkauf > Datenquellen` fuer SAP/OData-Verbindung, Quellen, Join-Fluss und Zielmappings.
|
- Unterpunkt `Einkauf > Datenquellen` fuer SAP/OData-Verbindung, Quellen, Join-Fluss und Zielmappings.
|
||||||
- Die Seite ist als Cockpit-Struktur umgesetzt und zweisprachig ueber den vorhandenen UI-Sprachservice vorbereitet.
|
- Die Seite ist als Cockpit-Struktur umgesetzt und zweisprachig ueber den vorhandenen UI-Sprachservice vorbereitet.
|
||||||
- Die Kennzahlen sind noch nicht live an SAP gebunden.
|
- EKKO, EKPO und EKET werden live ueber SAP/OData gelesen.
|
||||||
|
- Die Kennzahlen im Cockpit nutzen aktuell eine begrenzte Live-Probe, damit das Dashboard sofort echte Einkaufsdaten zeigt.
|
||||||
|
|
||||||
## SAP/OData-Konfiguration
|
## SAP/OData-Konfiguration
|
||||||
|
|
||||||
@@ -70,6 +72,44 @@ Vorbefuellte Joins:
|
|||||||
|
|
||||||
Die Seite verwendet dieselben Grundtabellen wie die Finance-/Standorte-Quellenpflege: `Sites`, `SapSourceDefinitions`, `SapJoinDefinitions`, `SapFieldMappings`.
|
Die Seite verwendet dieselben Grundtabellen wie die Finance-/Standorte-Quellenpflege: `Sites`, `SapSourceDefinitions`, `SapJoinDefinitions`, `SapFieldMappings`.
|
||||||
|
|
||||||
|
## SAP/OData Live-Stand 2026-06-05
|
||||||
|
|
||||||
|
Der SAP-Test hat bestaetigt, dass die Einkaufstabellen Daten enthalten:
|
||||||
|
|
||||||
|
- `EKKO` ab `01.01.2026`: 2'748 Koepfe.
|
||||||
|
- `EKPO` gesamt: 233'920 Positionen.
|
||||||
|
- `EKET` gesamt: 242'571 Einteilungen.
|
||||||
|
- Join `EKKO -> EKPO` ab `01.01.2026`: 3'464 Zeilen.
|
||||||
|
- Join `EKKO -> EKET` ab `01.01.2026`: 3'458 Zeilen.
|
||||||
|
|
||||||
|
Nach Aktivierung der angepassten SAP-Methoden liefern die OData-Services:
|
||||||
|
|
||||||
|
- `EKPOSet?$top=5`: HTTP 200 mit Daten.
|
||||||
|
- `eketSet?$top=5`: HTTP 200 mit Daten.
|
||||||
|
- `EKPOSet?$filter=Ebeln eq '45148366'`: 1 Zeile.
|
||||||
|
- `eketSet?$filter=Ebeln eq '45148366'`: 1 Zeile.
|
||||||
|
|
||||||
|
Wichtig: Die OData-Property heisst `Ebeln`. Ein Filter mit `EBELN` liefert HTTP 400.
|
||||||
|
|
||||||
|
## Live-Kennzahlen im Dashboard
|
||||||
|
|
||||||
|
Die Seite `/einkauf` zeigt nun echte Werte aus SAP:
|
||||||
|
|
||||||
|
- `Spend total`: Summe `EKPOSet.Netwr` aus der Live-Probe.
|
||||||
|
- `Offene Bestellungen`: Anzahl EKKO-Belege seit Jahresbeginn.
|
||||||
|
- `Kontrakte`: offener Restwert aus `EKET.Menge - EKET.Wemng` bewertet mit EKPO-Netto-Stueckwert.
|
||||||
|
- `Offener Bestellwert`: berechnet aus EKET-Offenmenge und EKPO-Netto-Stueckwert.
|
||||||
|
- `Offene Menge`: Summe offener EKET-Mengen.
|
||||||
|
- Top-Lieferant, Top-Warengruppe und Top-Artikel werden aus EKPO gruppiert.
|
||||||
|
- Spend-, Offenwert- und Kontrakt-Diagramme verwenden Live-Gruppierungen, sofern EKPO/EKET Daten liefern.
|
||||||
|
|
||||||
|
Aktuelle technische Begrenzung:
|
||||||
|
|
||||||
|
- Das Dashboard laedt fuer EKPO/EKET eine begrenzte Probe mit `$top=1000`.
|
||||||
|
- Filter ist `Ebeln ge <erste aktuelle EKKO-Bestellnummer>`.
|
||||||
|
- Damit sind die Werte echte SAP-Werte, aber noch keine vollstaendige Jahresaggregation.
|
||||||
|
- Fuer definitive Management-Summen braucht es als naechsten Schritt serverseitige OData-Filter/Aggregation oder einen eigenen Import-/Cache-Prozess analog Finance.
|
||||||
|
|
||||||
## 3D Simulation
|
## 3D Simulation
|
||||||
|
|
||||||
Das Einkaufsdashboard hat eine eigene 3D-Simulation fuer wichtige Einkaufsindikatoren:
|
Das Einkaufsdashboard hat eine eigene 3D-Simulation fuer wichtige Einkaufsindikatoren:
|
||||||
@@ -84,12 +124,25 @@ Die Simulation nutzt feste Canvas-Groessen, sichtbare Achsen, waehlbare Diagramm
|
|||||||
|
|
||||||
## Naechster Schritt fuer Live-Daten
|
## Naechster Schritt fuer Live-Daten
|
||||||
|
|
||||||
Fuer echte Werte muessen die Einkaufsquellen sauber gemappt werden:
|
Fuer definitive Vollwerte muessen die Live-Quellen noch fachlich fertig aggregiert werden:
|
||||||
|
|
||||||
- Bestellkopf, z. B. `EKKOSet`.
|
- Jahres-/Periodenfilter fuer `EKKOSet.Bedat`.
|
||||||
- Bestellpositionen, z. B. `EKPOSet`.
|
- Vollstaendige Aggregation von `EKPOSet.Netwr` nach Jahr, Lieferant, Warengruppe und Artikel.
|
||||||
- Offene Liefer-/Terminmengen, voraussichtlich Termin-/Schedule-Daten.
|
- Vollstaendige offene Werte/Mengen aus `EKET` und `EKPO`.
|
||||||
- Kontrakte und offene Verpflichtungen.
|
- Kontrakte und offene Verpflichtungen, inkl. fachlicher Abgrenzung von normalen Bestellungen.
|
||||||
- Lieferantenbewertung / Performance, falls im SAP-System als OData- oder HANA-Quelle verfuegbar.
|
- Lieferantenbewertung / Performance, falls im SAP-System als OData- oder HANA-Quelle verfuegbar.
|
||||||
|
|
||||||
Danach koennen Filter, Aggregationen und Delta-/Refresh-Prozess analog zu Finance/Spain umgesetzt werden.
|
Danach koennen Filter, Aggregationen und Delta-/Refresh-Prozess analog zu Finance/Spain umgesetzt werden.
|
||||||
|
|
||||||
|
## Geaenderte Programmstellen
|
||||||
|
|
||||||
|
- `Components/Pages/PurchasingDashboard.razor`
|
||||||
|
- KPI-Karten, Detailtabellen und Diagramme lesen jetzt Live-Werte aus `PurchasingDashboardLiveState`.
|
||||||
|
- Fallback-Simulation bleibt sichtbar, falls SAP/OData nicht antwortet.
|
||||||
|
- `Services/IPurchasingDashboardService.cs`
|
||||||
|
- Live-State um Spend, offene Menge, offenen Wert, Kontraktwert und Live-Diagrammzeilen erweitert.
|
||||||
|
- `Services/PurchasingDashboardService.cs`
|
||||||
|
- Laedt EKKO, EKPO und EKET.
|
||||||
|
- Berechnet Spend aus EKPO.
|
||||||
|
- Berechnet offene Mengen/Werte aus EKET minus Wareneingangsmenge, bewertet mit EKPO-Netto-Stueckwert.
|
||||||
|
- Erstellt Top-Gruppierungen fuer Lieferant, Warengruppe und Artikel.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Stand: 2026-06-05
|
|||||||
- Neu in der Navigation: Menuebaum wird aus `NavigationMenuItems` gerendert; Admins koennen bestehende Punkte unter `Admin > Menuestruktur` umhaengen, sortieren und aus-/einblenden.
|
- Neu in der Navigation: Menuebaum wird aus `NavigationMenuItems` gerendert; Admins koennen bestehende Punkte unter `Admin > Menuestruktur` umhaengen, sortieren und aus-/einblenden.
|
||||||
- Neu als Hauptbereich: `Einkauf` mit Einkaufswagen-Icon und erweitertem `Einkauf Dashboard`.
|
- Neu als Hauptbereich: `Einkauf` mit Einkaufswagen-Icon und erweitertem `Einkauf Dashboard`.
|
||||||
- Einkauf: `x.pbix` wurde als Vorlage analysiert; `/einkauf` enthaelt jetzt Struktur fuer Spend, offene Bestellungen, Mengenkontrakte, Lieferantenperformance, PBIX-Reportseiten und 3D-Simulation.
|
- Einkauf: `x.pbix` wurde als Vorlage analysiert; `/einkauf` enthaelt jetzt Struktur fuer Spend, offene Bestellungen, Mengenkontrakte, Lieferantenperformance, PBIX-Reportseiten und 3D-Simulation.
|
||||||
- Einkauf: `Einkauf > Datenquellen` pflegt die SAP/OData-Konfiguration grafisch und ist mit `EKKOSet`, `EKPOSet`, `eketSet`, `Data`, `Data2`, Joins und Zielmappings vorbefuellt. Realer Kennzahlenimport ist noch offen.
|
- Einkauf: `Einkauf > Datenquellen` pflegt die SAP/OData-Konfiguration grafisch und ist mit `EKKOSet`, `EKPOSet`, `eketSet`, `Data`, `Data2`, Joins und Zielmappings vorbefuellt. `/einkauf` laedt EKKO/EKPO/EKET live und zeigt eine echte, begrenzte SAP-Probe fuer Spend, offene Werte/Mengen und Kontrakt-Restwerte. Vollstaendige Jahresaggregation und Lieferantenperformance sind noch offen.
|
||||||
- Neu im Expertenbereich: `3D Datenanalyse` mit drehbarer 3D-Grafik, Achsen, Diagrammarten, Indikatorauswahl, Labelgroesse und Simulation per Schieberegler.
|
- Neu im Expertenbereich: `3D Datenanalyse` mit drehbarer 3D-Grafik, Achsen, Diagrammarten, Indikatorauswahl, Labelgroesse und Simulation per Schieberegler.
|
||||||
- Spanien: `Run-SpainRangeExportAndUpload-AllInOne.ps1` exportiert Sage-Range direkt und laedt CSV/Summary via rclone nach SharePoint `trafag-bi:Import/Finance/Spanien`.
|
- Spanien: `Run-SpainRangeExportAndUpload-AllInOne.ps1` exportiert Sage-Range direkt und laedt CSV/Summary via rclone nach SharePoint `trafag-bi:Import/Finance/Spanien`.
|
||||||
- Spanien: Default-Range ist heute minus 7 Tage bis heute; `ToDate` ist exklusiv.
|
- Spanien: Default-Range ist heute minus 7 Tage bis heute; `ToDate` ist exklusiv.
|
||||||
|
|||||||
Reference in New Issue
Block a user