Update finance session follow-ups
This commit is contained in:
@@ -152,6 +152,8 @@
|
|||||||
<MudTh>@T("Quelle", "Source")</MudTh>
|
<MudTh>@T("Quelle", "Source")</MudTh>
|
||||||
<MudTh>@T("Waehrung", "Currency")</MudTh>
|
<MudTh>@T("Waehrung", "Currency")</MudTh>
|
||||||
<MudTh>@T("Ist", "Actual")</MudTh>
|
<MudTh>@T("Ist", "Actual")</MudTh>
|
||||||
|
<MudTh>@T("IC/2nd-party", "IC/2nd-party")</MudTh>
|
||||||
|
<MudTh>@T("Ist ohne IC", "Actual excl. IC")</MudTh>
|
||||||
<MudTh>@T("Soll", "Reference")</MudTh>
|
<MudTh>@T("Soll", "Reference")</MudTh>
|
||||||
<MudTh>@T("Differenz", "Difference")</MudTh>
|
<MudTh>@T("Differenz", "Difference")</MudTh>
|
||||||
<MudTh>@T("Zeilen", "Rows")</MudTh>
|
<MudTh>@T("Zeilen", "Rows")</MudTh>
|
||||||
@@ -163,6 +165,8 @@
|
|||||||
<MudTd>@context.SourceSystems</MudTd>
|
<MudTd>@context.SourceSystems</MudTd>
|
||||||
<MudTd>@context.Currency</MudTd>
|
<MudTd>@context.Currency</MudTd>
|
||||||
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
||||||
|
<MudTd>@FormatValue(context.IntercompanyValue, context.Currency)</MudTd>
|
||||||
|
<MudTd>@FormatValue(context.NetSalesActualExcludingIntercompany, context.Currency)</MudTd>
|
||||||
<MudTd>@FormatNullableValue(context.ReferenceValue, context.Currency)</MudTd>
|
<MudTd>@FormatNullableValue(context.ReferenceValue, context.Currency)</MudTd>
|
||||||
<MudTd>@FormatNullableValue(context.Difference, context.Currency)</MudTd>
|
<MudTd>@FormatNullableValue(context.Difference, context.Currency)</MudTd>
|
||||||
<MudTd>@context.IncludedRows.ToString("N0") / @context.ExcludedRows.ToString("N0")</MudTd>
|
<MudTd>@context.IncludedRows.ToString("N0") / @context.ExcludedRows.ToString("N0")</MudTd>
|
||||||
@@ -720,6 +724,7 @@
|
|||||||
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@T("Laender", "Countries")</MudText><MudText Typo="Typo.h6">@_centralResult.Summary.CountryCount.ToString("N0")</MudText></MudPaper></MudItem>
|
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@T("Laender", "Countries")</MudText><MudText Typo="Typo.h6">@_centralResult.Summary.CountryCount.ToString("N0")</MudText></MudPaper></MudItem>
|
||||||
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@_centralResult.Summary.ValueFieldLabel</MudText><MudText Typo="Typo.h6">@FormatValue(_centralResult.Summary.ValueTotal, _centralResult.Summary.DisplayCurrency)</MudText></MudPaper></MudItem>
|
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@_centralResult.Summary.ValueFieldLabel</MudText><MudText Typo="Typo.h6">@FormatValue(_centralResult.Summary.ValueTotal, _centralResult.Summary.DisplayCurrency)</MudText></MudPaper></MudItem>
|
||||||
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@T("Nicht umgerechnet", "Not converted")</MudText><MudText Typo="Typo.h6">@_centralResult.Summary.MissingExchangeRateCount.ToString("N0")</MudText></MudPaper></MudItem>
|
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@T("Nicht umgerechnet", "Not converted")</MudText><MudText Typo="Typo.h6">@_centralResult.Summary.MissingExchangeRateCount.ToString("N0")</MudText></MudPaper></MudItem>
|
||||||
|
<MudItem xs="12" md="2"><MudPaper Class="pa-4"><MudText Typo="Typo.caption">@T("Kursdatum", "Rate date")</MudText><MudText Typo="Typo.body2">@_centralResult.Summary.ExchangeRateDateLabel</MudText></MudPaper></MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
<MudPaper Class="pa-4 mb-4" Elevation="1">
|
<MudPaper Class="pa-4 mb-4" Elevation="1">
|
||||||
@@ -881,6 +886,7 @@
|
|||||||
private List<ManagementCockpitValueFieldOption> _valueFieldOptions = [];
|
private List<ManagementCockpitValueFieldOption> _valueFieldOptions = [];
|
||||||
private readonly List<CurrencySelectOption> _currencyOptions =
|
private readonly List<CurrencySelectOption> _currencyOptions =
|
||||||
[
|
[
|
||||||
|
new(ManagementCockpitCurrencyOptions.Chf, "CHF"),
|
||||||
new(ManagementCockpitCurrencyOptions.Eur, "EUR"),
|
new(ManagementCockpitCurrencyOptions.Eur, "EUR"),
|
||||||
new(ManagementCockpitCurrencyOptions.Usd, "USD"),
|
new(ManagementCockpitCurrencyOptions.Usd, "USD"),
|
||||||
new(ManagementCockpitCurrencyOptions.Native, "Original")
|
new(ManagementCockpitCurrencyOptions.Native, "Original")
|
||||||
|
|||||||
@@ -269,6 +269,15 @@
|
|||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudSwitch @bind-Value="_exportSettings.TimerEnabled" Label="Timer aktiviert" Color="Color.Primary" />
|
<MudSwitch @bind-Value="_exportSettings.TimerEnabled" Label="Timer aktiviert" Color="Color.Primary" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
<MudItem xs="12" md="4">
|
||||||
|
<MudSelect T="string" @bind-Value="_exportSettings.ExchangeRateDateField"
|
||||||
|
Label="Wechselkurse anwenden auf"
|
||||||
|
HelperText="Datumsfeld fuer Kursgueltigkeit in Management-Analysen.">
|
||||||
|
<MudSelectItem Value="@ExchangeRateDateFields.PostingDate">PostingDate / Buchungsdatum</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@ExchangeRateDateFields.InvoiceDate">InvoiceDate / Rechnungsdatum</MudSelectItem>
|
||||||
|
<MudSelectItem Value="@ExchangeRateDateFields.ExtractionDate">ExtractionDate / Extraktionsdatum</MudSelectItem>
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="Debug Live-Logging" Color="Color.Warning" />
|
<MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="Debug Live-Logging" Color="Color.Warning" />
|
||||||
<MudText Typo="Typo.caption">
|
<MudText Typo="Typo.caption">
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class ConfigTransferExportSettings
|
|||||||
public bool DebugLoggingEnabled { get; set; }
|
public bool DebugLoggingEnabled { get; set; }
|
||||||
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
||||||
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
||||||
|
public string ExchangeRateDateField { get; set; } = ExchangeRateDateFields.PostingDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigTransferCurrencyExchangeRate
|
public class ConfigTransferCurrencyExchangeRate
|
||||||
|
|||||||
@@ -10,4 +10,12 @@ public class ExportSettings
|
|||||||
public bool DebugLoggingEnabled { get; set; }
|
public bool DebugLoggingEnabled { get; set; }
|
||||||
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
||||||
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
||||||
|
public string ExchangeRateDateField { get; set; } = ExchangeRateDateFields.PostingDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExchangeRateDateFields
|
||||||
|
{
|
||||||
|
public const string PostingDate = nameof(PostingDate);
|
||||||
|
public const string InvoiceDate = nameof(InvoiceDate);
|
||||||
|
public const string ExtractionDate = nameof(ExtractionDate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public static class ManagementCockpitValueFieldKeys
|
|||||||
public static class ManagementCockpitCurrencyOptions
|
public static class ManagementCockpitCurrencyOptions
|
||||||
{
|
{
|
||||||
public const string Native = "NATIVE";
|
public const string Native = "NATIVE";
|
||||||
|
public const string Chf = "CHF";
|
||||||
public const string Eur = "EUR";
|
public const string Eur = "EUR";
|
||||||
public const string Usd = "USD";
|
public const string Usd = "USD";
|
||||||
}
|
}
|
||||||
@@ -107,6 +108,8 @@ public class ManagementCockpitCentralSummary
|
|||||||
public string DisplayCurrency { get; set; } = string.Empty;
|
public string DisplayCurrency { get; set; } = string.Empty;
|
||||||
public decimal ValueTotal { get; set; }
|
public decimal ValueTotal { get; set; }
|
||||||
public int MissingExchangeRateCount { get; set; }
|
public int MissingExchangeRateCount { get; set; }
|
||||||
|
public string ExchangeRateDateField { get; set; } = ExchangeRateDateFields.PostingDate;
|
||||||
|
public string ExchangeRateDateLabel { get; set; } = "PostingDate / Buchungsdatum";
|
||||||
public DateTime? PeriodStart { get; set; }
|
public DateTime? PeriodStart { get; set; }
|
||||||
public DateTime? PeriodEnd { get; set; }
|
public DateTime? PeriodEnd { get; set; }
|
||||||
}
|
}
|
||||||
@@ -175,6 +178,8 @@ public class ManagementFinanceCountryStatusRow : ManagementFinanceSummaryRow
|
|||||||
{
|
{
|
||||||
public string SourceSystems { get; set; } = string.Empty;
|
public string SourceSystems { get; set; } = string.Empty;
|
||||||
public string Tscs { get; set; } = string.Empty;
|
public string Tscs { get; set; } = string.Empty;
|
||||||
|
public decimal IntercompanyValue { get; set; }
|
||||||
|
public decimal NetSalesActualExcludingIntercompany { get; set; }
|
||||||
public decimal? ReferenceValue { get; set; }
|
public decimal? ReferenceValue { get; set; }
|
||||||
public decimal? Difference { get; set; }
|
public decimal? Difference { get; set; }
|
||||||
public decimal? DifferencePercent { get; set; }
|
public decimal? DifferencePercent { get; set; }
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
# Sage Spain Export
|
# Sage Spain Export
|
||||||
|
|
||||||
Stand: 2026-05-05
|
Stand: 2026-06-01
|
||||||
|
|
||||||
|
Nachtrag 2026-06-01:
|
||||||
|
|
||||||
|
- Finance/Andreas bestaetigt: Spanien hat keine echte Ist-Abweichung.
|
||||||
|
- Der Wert `3'082'320.18 EUR` ist fachlich plausibel und wird als ES-Referenz 2025 verwendet.
|
||||||
|
- Der alte Sollwert `3'102'333.61 EUR` war ein Referenz-/Excel-Fehler.
|
||||||
|
- Die historischen Abschnitte unten dokumentieren den frueheren Analysepfad und sind nicht mehr als aktueller ES-Status zu lesen.
|
||||||
|
|
||||||
## Aktueller Kurzstatus
|
## Aktueller Kurzstatus
|
||||||
|
|
||||||
- Spanien-v2-Export ist technisch lauffaehig und im Testprogramm sichtbar.
|
- Spanien-v2-Export ist technisch lauffaehig und im Testprogramm sichtbar.
|
||||||
- Datei: `sagespain/v2/Spain_Sales_2025.csv`
|
- Datei: `sagespain/v2/Spain_Sales_2025.csv`
|
||||||
- Ist 2025: `3'082'320.18` EUR
|
- Ist 2025: `3'082'320.18` EUR
|
||||||
- Soll aus `check.xlsx`: `3'102'333.61`
|
- Korrigierte Referenz: `3'082'320.18`
|
||||||
- Differenz: `-20'013.43`
|
- Differenz: `0.00`
|
||||||
- Status FinanceProbe: Gelb / Pruefen
|
- Status FinanceProbe: OK, sofern die korrigierte Referenz geladen ist
|
||||||
- Finale Aussage: technisch importierbar, aber fachlich noch nicht abgestimmt.
|
- Finale Aussage: technisch importierbar und laut Sitzung fachlich plausibel; alter Sollwert war falsch.
|
||||||
|
|
||||||
FinanceProbe lokal:
|
FinanceProbe lokal:
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
TimerEnabled = exportSettings.TimerEnabled,
|
TimerEnabled = exportSettings.TimerEnabled,
|
||||||
DebugLoggingEnabled = exportSettings.DebugLoggingEnabled,
|
DebugLoggingEnabled = exportSettings.DebugLoggingEnabled,
|
||||||
LocalSiteExportFolder = exportSettings.LocalSiteExportFolder,
|
LocalSiteExportFolder = exportSettings.LocalSiteExportFolder,
|
||||||
LocalConsolidatedExportFolder = exportSettings.LocalConsolidatedExportFolder
|
LocalConsolidatedExportFolder = exportSettings.LocalConsolidatedExportFolder,
|
||||||
|
ExchangeRateDateField = SettingsPageService.NormalizeExchangeRateDateField(exportSettings.ExchangeRateDateField)
|
||||||
},
|
},
|
||||||
SourceSystemDefinitions = sourceSystems.Select(system => new ConfigTransferSourceSystemDefinition
|
SourceSystemDefinitions = sourceSystems.Select(system => new ConfigTransferSourceSystemDefinition
|
||||||
{
|
{
|
||||||
@@ -283,7 +284,8 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
TimerEnabled = importedSettings.TimerEnabled,
|
TimerEnabled = importedSettings.TimerEnabled,
|
||||||
DebugLoggingEnabled = importedSettings.DebugLoggingEnabled,
|
DebugLoggingEnabled = importedSettings.DebugLoggingEnabled,
|
||||||
LocalSiteExportFolder = importedSettings.LocalSiteExportFolder,
|
LocalSiteExportFolder = importedSettings.LocalSiteExportFolder,
|
||||||
LocalConsolidatedExportFolder = importedSettings.LocalConsolidatedExportFolder
|
LocalConsolidatedExportFolder = importedSettings.LocalConsolidatedExportFolder,
|
||||||
|
ExchangeRateDateField = SettingsPageService.NormalizeExchangeRateDateField(importedSettings.ExchangeRateDateField)
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var sourceSystem in importedSourceSystems)
|
foreach (var sourceSystem in importedSourceSystems)
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ CREATE TABLE ExportSettings (
|
|||||||
TimerEnabled INTEGER NOT NULL,
|
TimerEnabled INTEGER NOT NULL,
|
||||||
DebugLoggingEnabled INTEGER NOT NULL DEFAULT 0,
|
DebugLoggingEnabled INTEGER NOT NULL DEFAULT 0,
|
||||||
LocalSiteExportFolder TEXT NOT NULL DEFAULT '',
|
LocalSiteExportFolder TEXT NOT NULL DEFAULT '',
|
||||||
LocalConsolidatedExportFolder TEXT NOT NULL DEFAULT ''
|
LocalConsolidatedExportFolder TEXT NOT NULL DEFAULT '',
|
||||||
|
ExchangeRateDateField TEXT NOT NULL DEFAULT 'PostingDate'
|
||||||
);";
|
);";
|
||||||
|
|
||||||
internal static string GetHanaServersCreateSql() => @"
|
internal static string GetHanaServersCreateSql() => @"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class DatabaseSchemaMaintenanceService : IDatabaseSchemaMaintenanceServic
|
|||||||
AddColumnIfMissing(db, "ExportSettings", "DebugLoggingEnabled", "INTEGER NOT NULL DEFAULT 0");
|
AddColumnIfMissing(db, "ExportSettings", "DebugLoggingEnabled", "INTEGER NOT NULL DEFAULT 0");
|
||||||
AddColumnIfMissing(db, "ExportSettings", "LocalSiteExportFolder", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "ExportSettings", "LocalSiteExportFolder", "TEXT NOT NULL DEFAULT ''");
|
||||||
AddColumnIfMissing(db, "ExportSettings", "LocalConsolidatedExportFolder", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "ExportSettings", "LocalConsolidatedExportFolder", "TEXT NOT NULL DEFAULT ''");
|
||||||
|
AddColumnIfMissing(db, "ExportSettings", "ExchangeRateDateField", "TEXT NOT NULL DEFAULT 'PostingDate'");
|
||||||
AddColumnIfMissing(db, "SharePointConfigs", "CentralExportFolder", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "SharePointConfigs", "CentralExportFolder", "TEXT NOT NULL DEFAULT ''");
|
||||||
AddColumnIfMissing(db, "ExportLogs", "FilePath", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "ExportLogs", "FilePath", "TEXT NOT NULL DEFAULT ''");
|
||||||
EnsureTransformationTable(db);
|
EnsureTransformationTable(db);
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
TimerEnabled = true,
|
TimerEnabled = true,
|
||||||
DebugLoggingEnabled = false,
|
DebugLoggingEnabled = false,
|
||||||
LocalSiteExportFolder = "",
|
LocalSiteExportFolder = "",
|
||||||
LocalConsolidatedExportFolder = ""
|
LocalConsolidatedExportFolder = "",
|
||||||
|
ExchangeRateDateField = ExchangeRateDateFields.PostingDate
|
||||||
});
|
});
|
||||||
|
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
@@ -868,7 +869,7 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
new FinanceReference { Key = "CN", Label = "Trafag CN", Year = 2025 },
|
new FinanceReference { Key = "CN", Label = "Trafag CN", Year = 2025 },
|
||||||
new FinanceReference { Key = "CZ", Label = "Trafag CZ", Year = 2025, LocalCurrencyValue = 95458782m },
|
new FinanceReference { Key = "CZ", Label = "Trafag CZ", Year = 2025, LocalCurrencyValue = 95458782m },
|
||||||
new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3652394.46m },
|
new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3652394.46m },
|
||||||
new FinanceReference { Key = "ES", Label = "Trafag ES", Year = 2025, LocalCurrencyValue = 3102333.61m },
|
new FinanceReference { Key = "ES", Label = "Trafag ES", Year = 2025, LocalCurrencyValue = 3082320.18m, Notes = "Sitzung 2026-06-01: ES-Ist 3'082'320.18 EUR fachlich bestaetigt; alter Sollwert 3'102'333.61 war Referenz-/Excel-Fehler." },
|
||||||
new FinanceReference { Key = "FR", Label = "Trafag FR", Year = 2025, LocalCurrencyValue = 1450582m, CheckValue = 1471218m },
|
new FinanceReference { Key = "FR", Label = "Trafag FR", Year = 2025, LocalCurrencyValue = 1450582m, CheckValue = 1471218m },
|
||||||
new FinanceReference { Key = "GFS", Label = "Trafag GfS", Year = 2025, LocalCurrencyValue = 6495513m },
|
new FinanceReference { Key = "GFS", Label = "Trafag GfS", Year = 2025, LocalCurrencyValue = 6495513m },
|
||||||
new FinanceReference { Key = "IN", Label = "Trafag IN", Year = 2025, LocalCurrencyValue = 747341702m, CheckValue = 750936591m },
|
new FinanceReference { Key = "IN", Label = "Trafag IN", Year = 2025, LocalCurrencyValue = 747341702m, CheckValue = 750936591m },
|
||||||
@@ -904,9 +905,11 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current.Key == "ES" && current.Year == 2025 && current.LocalCurrencyValue != 3102333.61m)
|
if (current.Key == "ES" && current.Year == 2025 && current.LocalCurrencyValue != 3082320.18m)
|
||||||
{
|
{
|
||||||
current.LocalCurrencyValue = 3102333.61m;
|
current.LocalCurrencyValue = 3082320.18m;
|
||||||
|
current.CheckValue = null;
|
||||||
|
current.Notes = "Sitzung 2026-06-01: ES-Ist 3'082'320.18 EUR fachlich bestaetigt; alter Sollwert 3'102'333.61 war Referenz-/Excel-Fehler.";
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
var aggregation = ResolveAggregation(options);
|
var aggregation = ResolveAggregation(options);
|
||||||
|
|
||||||
using var db = await _dbFactory.CreateDbContextAsync();
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var settings = await db.ExportSettings.AsNoTracking().FirstOrDefaultAsync() ?? new ExportSettings();
|
||||||
|
var exchangeRateDateField = SettingsPageService.NormalizeExchangeRateDateField(settings.ExchangeRateDateField);
|
||||||
var baseRows = await db.CentralSalesRecords
|
var baseRows = await db.CentralSalesRecords
|
||||||
.Select(r => new CentralCockpitRow
|
.Select(r => new CentralCockpitRow
|
||||||
{
|
{
|
||||||
@@ -196,10 +198,17 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
Quantity = r.Quantity,
|
Quantity = r.Quantity,
|
||||||
StandardCost = r.StandardCost,
|
StandardCost = r.StandardCost,
|
||||||
SalesValue = r.SalesPriceValue,
|
SalesValue = r.SalesPriceValue,
|
||||||
PeriodDate = r.InvoiceDate ?? r.ExtractionDate
|
PostingDate = r.PostingDate,
|
||||||
|
InvoiceDate = r.InvoiceDate,
|
||||||
|
ExtractionDate = r.ExtractionDate,
|
||||||
|
PeriodDate = r.InvoiceDate ?? r.ExtractionDate,
|
||||||
|
ExchangeRateDate = r.ExtractionDate
|
||||||
})
|
})
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var row in baseRows)
|
||||||
|
row.ExchangeRateDate = ResolveExchangeRateDate(exchangeRateDateField, row.PostingDate, row.InvoiceDate, row.ExtractionDate);
|
||||||
|
|
||||||
if (baseRows.Count == 0)
|
if (baseRows.Count == 0)
|
||||||
throw new InvalidOperationException("Die zentrale Tabelle enthält noch keine Datensätze.");
|
throw new InvalidOperationException("Die zentrale Tabelle enthält noch keine Datensätze.");
|
||||||
|
|
||||||
@@ -246,13 +255,15 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
DisplayCurrency = BuildDisplayCurrencyLabel(selectedRows.Select(x => x.DisplayCurrency)),
|
DisplayCurrency = BuildDisplayCurrencyLabel(selectedRows.Select(x => x.DisplayCurrency)),
|
||||||
ValueTotal = selectedRows.Sum(x => x.Value),
|
ValueTotal = selectedRows.Sum(x => x.Value),
|
||||||
MissingExchangeRateCount = selectedRows.Count(x => x.MissingExchangeRate),
|
MissingExchangeRateCount = selectedRows.Count(x => x.MissingExchangeRate),
|
||||||
|
ExchangeRateDateField = exchangeRateDateField,
|
||||||
|
ExchangeRateDateLabel = BuildExchangeRateDateLabel(exchangeRateDateField),
|
||||||
PeriodStart = selectedRows.Min(x => x.PeriodDate),
|
PeriodStart = selectedRows.Min(x => x.PeriodDate),
|
||||||
PeriodEnd = selectedRows.Max(x => x.PeriodDate)
|
PeriodEnd = selectedRows.Max(x => x.PeriodDate)
|
||||||
},
|
},
|
||||||
AdditionalValueFields = aggregation.AdditionalValueFields
|
AdditionalValueFields = aggregation.AdditionalValueFields
|
||||||
.Select(ToValueFieldOption)
|
.Select(ToValueFieldOption)
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Notices = BuildCentralNotices(aggregation, selectedRows.Count(x => x.MissingExchangeRate), options),
|
Notices = BuildCentralNotices(aggregation, selectedRows.Count(x => x.MissingExchangeRate), options, exchangeRateDateField),
|
||||||
YearlyTotals = yearlyRows
|
YearlyTotals = yearlyRows
|
||||||
.GroupBy(x => new { x.PeriodDate.Year, x.DisplayCurrency })
|
.GroupBy(x => new { x.PeriodDate.Year, x.DisplayCurrency })
|
||||||
.OrderBy(g => g.Key.Year)
|
.OrderBy(g => g.Key.Year)
|
||||||
@@ -316,6 +327,10 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
if (financeRules.Count == 0)
|
if (financeRules.Count == 0)
|
||||||
financeRules = FinanceRuleEngine.CreateDefaultRules().ToList();
|
financeRules = FinanceRuleEngine.CreateDefaultRules().ToList();
|
||||||
|
|
||||||
|
var intercompanyRules = await db.FinanceIntercompanyRules
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(rule => rule.IsActive)
|
||||||
|
.ToListAsync();
|
||||||
var financeRuleEngine = new FinanceRuleEngine(financeRules);
|
var financeRuleEngine = new FinanceRuleEngine(financeRules);
|
||||||
var records = await db.CentralSalesRecords
|
var records = await db.CentralSalesRecords
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -374,6 +389,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
Include = include,
|
Include = include,
|
||||||
Value = value,
|
Value = value,
|
||||||
RawSalesValue = record.SalesPriceValue,
|
RawSalesValue = record.SalesPriceValue,
|
||||||
|
IsIntercompany = IsIntercompanyCustomer(record, intercompanyRules),
|
||||||
Quantity = record.Quantity,
|
Quantity = record.Quantity,
|
||||||
InvoiceNumber = record.InvoiceNumber,
|
InvoiceNumber = record.InvoiceNumber,
|
||||||
DocumentType = record.DocumentType,
|
DocumentType = record.DocumentType,
|
||||||
@@ -439,7 +455,8 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
"Diese Sicht verwendet dieselbe FinanceRuleEngine wie das zentrale Excel-Blatt Finance Summary.",
|
"Diese Sicht verwendet dieselbe FinanceRuleEngine wie das zentrale Excel-Blatt Finance Summary.",
|
||||||
"Jahr, Land und Waehrung werden auf das Endergebnis angewendet.",
|
"Jahr, Land und Waehrung werden auf das Endergebnis angewendet.",
|
||||||
"Finance-Jahr basiert auf PostingDate, danach InvoiceDate, danach ExtractionDate; DE-Regeln koennen das Jahr erzwingen.",
|
"Finance-Jahr basiert auf PostingDate, danach InvoiceDate, danach ExtractionDate; DE-Regeln koennen das Jahr erzwingen.",
|
||||||
"Include/Exclude, Gutschriften-Negierung und IT-Deduplizierung folgen den gepflegten Finance Regeln."
|
"Include/Exclude, Gutschriften-Negierung und IT-Deduplizierung folgen den gepflegten Finance Regeln.",
|
||||||
|
"Intercompany / 2nd-party wird als Diagnosebetrag ausgewiesen; der Standard-Ist bleibt inklusive dieser Positionen."
|
||||||
};
|
};
|
||||||
if (scopedRows.Count == 0)
|
if (scopedRows.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -461,6 +478,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
var countryRows = BuildFinanceCountryStatusRows(scopedRows, referenceByKey);
|
var countryRows = BuildFinanceCountryStatusRows(scopedRows, referenceByKey);
|
||||||
var productAssignmentRows = BuildProductAssignmentRows(scopedRows, allRows);
|
var productAssignmentRows = BuildProductAssignmentRows(scopedRows, allRows);
|
||||||
var productFinanceSummary = BuildProductFinanceSummary(productAssignmentRows, resultCurrencies);
|
var productFinanceSummary = BuildProductFinanceSummary(productAssignmentRows, resultCurrencies);
|
||||||
|
notices.AddRange(BuildProductAssignmentNotices(productAssignmentRows, productFinanceSummary));
|
||||||
|
|
||||||
return new ManagementFinanceSummaryResult
|
return new ManagementFinanceSummaryResult
|
||||||
{
|
{
|
||||||
@@ -578,6 +596,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
var rowList = group.ToList();
|
var rowList = group.ToList();
|
||||||
referenceByKey.TryGetValue(group.Key.CountryKey, out var referenceValue);
|
referenceByKey.TryGetValue(group.Key.CountryKey, out var referenceValue);
|
||||||
var actual = rowList.Sum(row => row.Value);
|
var actual = rowList.Sum(row => row.Value);
|
||||||
|
var intercompanyValue = rowList.Where(row => row.IsIntercompany).Sum(row => row.Value);
|
||||||
var difference = referenceValue.HasValue ? actual - referenceValue.Value : (decimal?)null;
|
var difference = referenceValue.HasValue ? actual - referenceValue.Value : (decimal?)null;
|
||||||
return new ManagementFinanceCountryStatusRow
|
return new ManagementFinanceCountryStatusRow
|
||||||
{
|
{
|
||||||
@@ -587,6 +606,8 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
IncludedRows = rowList.Count(row => row.Include),
|
IncludedRows = rowList.Count(row => row.Include),
|
||||||
ExcludedRows = rowList.Count(row => !row.Include),
|
ExcludedRows = rowList.Count(row => !row.Include),
|
||||||
NetSalesActual = actual,
|
NetSalesActual = actual,
|
||||||
|
IntercompanyValue = intercompanyValue,
|
||||||
|
NetSalesActualExcludingIntercompany = actual - intercompanyValue,
|
||||||
SourceSystems = JoinDistinct(rowList.Select(row => row.SourceSystem)),
|
SourceSystems = JoinDistinct(rowList.Select(row => row.SourceSystem)),
|
||||||
Tscs = JoinDistinct(rowList.Select(row => row.Tsc)),
|
Tscs = JoinDistinct(rowList.Select(row => row.Tsc)),
|
||||||
ReferenceValue = referenceValue,
|
ReferenceValue = referenceValue,
|
||||||
@@ -871,7 +892,34 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static string NormalizeMaterialKey(string value)
|
private static string NormalizeMaterialKey(string value)
|
||||||
=> string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim().ToUpperInvariant();
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var normalized = new string(value
|
||||||
|
.Trim()
|
||||||
|
.ToUpperInvariant()
|
||||||
|
.Where(ch => !char.IsWhiteSpace(ch))
|
||||||
|
.ToArray());
|
||||||
|
|
||||||
|
var withoutLeadingZeros = normalized.TrimStart('0');
|
||||||
|
return string.IsNullOrWhiteSpace(withoutLeadingZeros) ? "0" : withoutLeadingZeros;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> BuildProductAssignmentNotices(
|
||||||
|
IReadOnlyCollection<ManagementProductAssignmentRow> rows,
|
||||||
|
ManagementProductFinanceSummary summary)
|
||||||
|
{
|
||||||
|
if (rows.Count == 0 || summary.TotalValue == 0m)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var unresolvedValuePercent = summary.UnassignedValuePercent + summary.MissingReferenceValuePercent;
|
||||||
|
if (unresolvedValuePercent >= 90m)
|
||||||
|
{
|
||||||
|
yield return $"Spartenanalyse auffaellig: {unresolvedValuePercent:N1}% des Umsatzes sind nicht zugeordnet oder nicht im TR-AG-Stamm. Das ist fachlich unplausibel und weist auf Mapping-/Referenzprobleme hin.";
|
||||||
|
yield return "Pruefpunkte Spartenmapping: ProductDivisionRefSet-Fuellung, Join Z.Matnr = P.Matnr, fuehrende Nullen in Materialnummern, lokale Artikelnummern und letzter ZSCHWEIZ-Export.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static decimal PercentOf(decimal value, decimal total)
|
private static decimal PercentOf(decimal value, decimal total)
|
||||||
=> total == 0m ? 0m : value * 100m / total;
|
=> total == 0m ? 0m : value * 100m / total;
|
||||||
@@ -919,6 +967,43 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
return string.Join(", ", reasons.Distinct(StringComparer.OrdinalIgnoreCase));
|
return string.Join(", ", reasons.Distinct(StringComparer.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsIntercompanyCustomer(SalesRecord record, IReadOnlyList<FinanceIntercompanyRule> rules)
|
||||||
|
{
|
||||||
|
var customerNumber = record.CustomerNumber?.Trim() ?? string.Empty;
|
||||||
|
var customerName = record.CustomerName?.Trim() ?? string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(customerNumber) && string.IsNullOrWhiteSpace(customerName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var normalizedCustomerName = NormalizeRuleText(customerName);
|
||||||
|
var referenceKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
|
||||||
|
|
||||||
|
foreach (var rule in rules)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(rule.ScopeKey) &&
|
||||||
|
!rule.ScopeKey.Equals(referenceKey, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!rule.ScopeKey.Equals(record.Tsc, StringComparison.OrdinalIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(rule.CustomerNumber) &&
|
||||||
|
customerNumber.Equals(rule.CustomerNumber.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(rule.CustomerNameContains) &&
|
||||||
|
normalizedCustomerName.Contains(NormalizeRuleText(rule.CustomerNameContains), StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeRuleText(string value)
|
||||||
|
=> (value ?? string.Empty)
|
||||||
|
.Replace("\u00e4", "ae", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("\u00f6", "oe", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("\u00fc", "ue", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Trim()
|
||||||
|
.ToUpperInvariant();
|
||||||
|
|
||||||
private static string JoinDistinct(IEnumerable<string> values)
|
private static string JoinDistinct(IEnumerable<string> values)
|
||||||
{
|
{
|
||||||
var distinct = values
|
var distinct = values
|
||||||
@@ -1019,7 +1104,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var targetCurrency = (options?.TargetCurrency ?? ManagementCockpitCurrencyOptions.Native).Trim().ToUpperInvariant();
|
var targetCurrency = (options?.TargetCurrency ?? ManagementCockpitCurrencyOptions.Native).Trim().ToUpperInvariant();
|
||||||
if (targetCurrency is not ManagementCockpitCurrencyOptions.Eur and not ManagementCockpitCurrencyOptions.Usd)
|
if (targetCurrency is not ManagementCockpitCurrencyOptions.Chf and not ManagementCockpitCurrencyOptions.Eur and not ManagementCockpitCurrencyOptions.Usd)
|
||||||
targetCurrency = ManagementCockpitCurrencyOptions.Native;
|
targetCurrency = ManagementCockpitCurrencyOptions.Native;
|
||||||
|
|
||||||
return new AggregationSelection(
|
return new AggregationSelection(
|
||||||
@@ -1047,14 +1132,14 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
{
|
{
|
||||||
var value = ResolveValue(row, aggregation.ValueField);
|
var value = ResolveValue(row, aggregation.ValueField);
|
||||||
var currency = ResolveCurrency(row, aggregation.ValueField);
|
var currency = ResolveCurrency(row, aggregation.ValueField);
|
||||||
var converted = ConvertValue(value, currency, aggregation.ValueField, aggregation, row.PeriodDate);
|
var converted = ConvertValue(value, currency, aggregation.ValueField, aggregation, row.ExchangeRateDate);
|
||||||
var additionalValues = aggregation.AdditionalValueFields.ToDictionary(
|
var additionalValues = aggregation.AdditionalValueFields.ToDictionary(
|
||||||
field => field.Key,
|
field => field.Key,
|
||||||
field =>
|
field =>
|
||||||
{
|
{
|
||||||
var additionalValue = ResolveValue(row, field);
|
var additionalValue = ResolveValue(row, field);
|
||||||
var additionalCurrency = ResolveCurrency(row, field);
|
var additionalCurrency = ResolveCurrency(row, field);
|
||||||
return ConvertValue(additionalValue, additionalCurrency, field, aggregation, row.PeriodDate);
|
return ConvertValue(additionalValue, additionalCurrency, field, aggregation, row.ExchangeRateDate);
|
||||||
},
|
},
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -1108,6 +1193,22 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
private static string BuildRateCacheKey(string fromCurrency, string toCurrency, DateTime date)
|
private static string BuildRateCacheKey(string fromCurrency, string toCurrency, DateTime date)
|
||||||
=> $"{fromCurrency}|{toCurrency}|{date:yyyy-MM-dd}";
|
=> $"{fromCurrency}|{toCurrency}|{date:yyyy-MM-dd}";
|
||||||
|
|
||||||
|
private static DateTime ResolveExchangeRateDate(string exchangeRateDateField, DateTime? postingDate, DateTime? invoiceDate, DateTime extractionDate)
|
||||||
|
=> exchangeRateDateField switch
|
||||||
|
{
|
||||||
|
ExchangeRateDateFields.InvoiceDate => invoiceDate ?? postingDate ?? extractionDate,
|
||||||
|
ExchangeRateDateFields.ExtractionDate => extractionDate,
|
||||||
|
_ => postingDate ?? invoiceDate ?? extractionDate
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string BuildExchangeRateDateLabel(string exchangeRateDateField)
|
||||||
|
=> exchangeRateDateField switch
|
||||||
|
{
|
||||||
|
ExchangeRateDateFields.InvoiceDate => "InvoiceDate / Rechnungsdatum",
|
||||||
|
ExchangeRateDateFields.ExtractionDate => "ExtractionDate / Extraktionsdatum",
|
||||||
|
_ => "PostingDate / Buchungsdatum"
|
||||||
|
};
|
||||||
|
|
||||||
private static decimal ResolveValue(CockpitRow row, ValueFieldDefinition field)
|
private static decimal ResolveValue(CockpitRow row, ValueFieldDefinition field)
|
||||||
=> field.Key switch
|
=> field.Key switch
|
||||||
{
|
{
|
||||||
@@ -1161,7 +1262,8 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
private static List<string> BuildCentralNotices(
|
private static List<string> BuildCentralNotices(
|
||||||
AggregationSelection aggregation,
|
AggregationSelection aggregation,
|
||||||
int missingExchangeRateCount,
|
int missingExchangeRateCount,
|
||||||
ManagementCockpitAnalysisOptions? options)
|
ManagementCockpitAnalysisOptions? options,
|
||||||
|
string exchangeRateDateField)
|
||||||
{
|
{
|
||||||
var notices = new List<string>
|
var notices = new List<string>
|
||||||
{
|
{
|
||||||
@@ -1169,7 +1271,8 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
$"Summenfeld: {aggregation.ValueField.Label}.",
|
$"Summenfeld: {aggregation.ValueField.Label}.",
|
||||||
"Keine Intercompany-Bereinigung angewendet.",
|
"Keine Intercompany-Bereinigung angewendet.",
|
||||||
"Kein Budget- und kein Spartemapping angewendet.",
|
"Kein Budget- und kein Spartemapping angewendet.",
|
||||||
"Periodenlogik basiert auf Invoice Date, falls vorhanden, sonst auf Extraction Date."
|
"Periodenlogik basiert auf Invoice Date, falls vorhanden, sonst auf Extraction Date.",
|
||||||
|
$"Wechselkurse werden auf {BuildExchangeRateDateLabel(exchangeRateDateField)} angewendet."
|
||||||
};
|
};
|
||||||
|
|
||||||
var landFilter = NormalizeOptionalFilter(options?.LandFilter);
|
var landFilter = NormalizeOptionalFilter(options?.LandFilter);
|
||||||
@@ -1561,7 +1664,11 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
public decimal Quantity { get; set; }
|
public decimal Quantity { get; set; }
|
||||||
public decimal StandardCost { get; set; }
|
public decimal StandardCost { get; set; }
|
||||||
public decimal SalesValue { get; set; }
|
public decimal SalesValue { get; set; }
|
||||||
|
public DateTime? PostingDate { get; set; }
|
||||||
|
public DateTime? InvoiceDate { get; set; }
|
||||||
|
public DateTime ExtractionDate { get; set; }
|
||||||
public DateTime PeriodDate { get; set; }
|
public DateTime PeriodDate { get; set; }
|
||||||
|
public DateTime ExchangeRateDate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CentralAggregationRow
|
private class CentralAggregationRow
|
||||||
@@ -1588,6 +1695,7 @@ public class ManagementCockpitService : IManagementCockpitService
|
|||||||
public bool Include { get; set; }
|
public bool Include { get; set; }
|
||||||
public decimal Value { get; set; }
|
public decimal Value { get; set; }
|
||||||
public decimal RawSalesValue { get; set; }
|
public decimal RawSalesValue { get; set; }
|
||||||
|
public bool IsIntercompany { get; set; }
|
||||||
public decimal Quantity { get; set; }
|
public decimal Quantity { get; set; }
|
||||||
public string InvoiceNumber { get; set; } = string.Empty;
|
public string InvoiceNumber { get; set; } = string.Empty;
|
||||||
public string DocumentType { get; set; } = string.Empty;
|
public string DocumentType { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ public sealed class SettingsPageService : ISettingsPageService
|
|||||||
var existing = await db.ExportSettings.FirstOrDefaultAsync();
|
var existing = await db.ExportSettings.FirstOrDefaultAsync();
|
||||||
if (existing is null)
|
if (existing is null)
|
||||||
{
|
{
|
||||||
|
settings.ExchangeRateDateField = NormalizeExchangeRateDateField(settings.ExchangeRateDateField);
|
||||||
db.ExportSettings.Add(settings);
|
db.ExportSettings.Add(settings);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -107,6 +108,7 @@ public sealed class SettingsPageService : ISettingsPageService
|
|||||||
existing.DebugLoggingEnabled = settings.DebugLoggingEnabled;
|
existing.DebugLoggingEnabled = settings.DebugLoggingEnabled;
|
||||||
existing.LocalSiteExportFolder = settings.LocalSiteExportFolder;
|
existing.LocalSiteExportFolder = settings.LocalSiteExportFolder;
|
||||||
existing.LocalConsolidatedExportFolder = settings.LocalConsolidatedExportFolder;
|
existing.LocalConsolidatedExportFolder = settings.LocalConsolidatedExportFolder;
|
||||||
|
existing.ExchangeRateDateField = NormalizeExchangeRateDateField(settings.ExchangeRateDateField);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@@ -281,6 +283,18 @@ public sealed class SettingsPageService : ISettingsPageService
|
|||||||
|
|
||||||
public static string NormalizeConfigValue(string? value) => value?.Trim() ?? string.Empty;
|
public static string NormalizeConfigValue(string? value) => value?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
public static string NormalizeExchangeRateDateField(string? value)
|
||||||
|
{
|
||||||
|
var normalized = NormalizeConfigValue(value);
|
||||||
|
return normalized switch
|
||||||
|
{
|
||||||
|
ExchangeRateDateFields.PostingDate => ExchangeRateDateFields.PostingDate,
|
||||||
|
ExchangeRateDateFields.InvoiceDate => ExchangeRateDateFields.InvoiceDate,
|
||||||
|
ExchangeRateDateFields.ExtractionDate => ExchangeRateDateFields.ExtractionDate,
|
||||||
|
_ => ExchangeRateDateFields.PostingDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static string BuildSharePointTestPreview(string tenantId, string clientId, string clientSecret, string siteUrl)
|
public static string BuildSharePointTestPreview(string tenantId, string clientId, string clientSecret, string siteUrl)
|
||||||
{
|
{
|
||||||
var maskedSecret = string.IsNullOrEmpty(clientSecret)
|
var maskedSecret = string.IsNullOrEmpty(clientSecret)
|
||||||
|
|||||||
@@ -556,7 +556,7 @@ static SpainSalesCsvProbe? LoadSpainSalesCsvProbe(string? path)
|
|||||||
AddGroupValue(bySeries, series, sales);
|
AddGroupValue(bySeries, series, sales);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decimal reference = 3102333.61m;
|
const decimal reference = 3082320.18m;
|
||||||
return new SpainSalesCsvProbe
|
return new SpainSalesCsvProbe
|
||||||
{
|
{
|
||||||
Path = path,
|
Path = path,
|
||||||
|
|||||||
@@ -171,6 +171,43 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
Assert.Equal(1, exchangeRates.ResolveRateCallCount);
|
Assert.Equal(1, exchangeRates.ResolveRateCallCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzeCentralAsync_Uses_Configured_Exchange_Rate_Date_Field()
|
||||||
|
{
|
||||||
|
var exchangeRates = new CountingCurrencyExchangeRateService();
|
||||||
|
var service = new ManagementCockpitService(_dbFactory, exchangeRates);
|
||||||
|
|
||||||
|
await using (var db = await _dbFactory.CreateDbContextAsync())
|
||||||
|
{
|
||||||
|
db.ExportSettings.Add(new ExportSettings
|
||||||
|
{
|
||||||
|
DateFilter = "2025-01-01",
|
||||||
|
ExchangeRateDateField = ExchangeRateDateFields.InvoiceDate
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow(
|
||||||
|
"SAP",
|
||||||
|
"USA",
|
||||||
|
"TRUS",
|
||||||
|
"INV-1",
|
||||||
|
"USD",
|
||||||
|
100m,
|
||||||
|
new DateTime(2025, 2, 10),
|
||||||
|
postingDate: new DateTime(2025, 1, 10)));
|
||||||
|
|
||||||
|
var result = await service.AnalyzeCentralAsync(2025, 2, new ManagementCockpitAnalysisOptions
|
||||||
|
{
|
||||||
|
ValueField = ManagementCockpitValueFieldKeys.SalesPriceValue,
|
||||||
|
TargetCurrency = ManagementCockpitCurrencyOptions.Eur
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(ExchangeRateDateFields.InvoiceDate, result.Summary.ExchangeRateDateField);
|
||||||
|
Assert.Equal(new DateTime(2025, 2, 10), Assert.Single(exchangeRates.EffectiveDates));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AnalyzeCentralAsync_Can_Sum_Quantity_Without_Currency_Conversion()
|
public async Task AnalyzeCentralAsync_Can_Sum_Quantity_Without_Currency_Conversion()
|
||||||
{
|
{
|
||||||
@@ -318,7 +355,7 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
{
|
{
|
||||||
await SeedCentralRowsAsync(
|
await SeedCentralRowsAsync(
|
||||||
CreateRow("SAP", "Schweiz", "ZSCHWEIZ", "CH-1", "CHF", 100m, new DateTime(2025, 1, 10),
|
CreateRow("SAP", "Schweiz", "ZSCHWEIZ", "CH-1", "CHF", 100m, new DateTime(2025, 1, 10),
|
||||||
material: "MAT-OK",
|
material: "000MAT-OK",
|
||||||
name: "Reference article",
|
name: "Reference article",
|
||||||
productHierarchyCode: "0414",
|
productHierarchyCode: "0414",
|
||||||
productHierarchyText: "Industat innen",
|
productHierarchyText: "Industat innen",
|
||||||
@@ -393,6 +430,32 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
Assert.Equal(80m, deFinanceCoverage.AssignedValuePercent);
|
Assert.Equal(80m, deFinanceCoverage.AssignedValuePercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AnalyzeFinanceSummaryAsync_Warns_When_Product_Assignment_Coverage_Is_Implausibly_Low()
|
||||||
|
{
|
||||||
|
await SeedCentralRowsAsync(
|
||||||
|
CreateRow("SAP", "Schweiz", "ZSCHWEIZ", "CH-1", "CHF", 10m, new DateTime(2025, 1, 10),
|
||||||
|
material: "MAT-OK",
|
||||||
|
productHierarchyCode: "0414",
|
||||||
|
productFamilyCode: "0004",
|
||||||
|
productDivisionCode: "0001",
|
||||||
|
productDivisionText: "Thermostate",
|
||||||
|
productMappingAssigned: "X"),
|
||||||
|
CreateRow("MANUAL_EXCEL", "Deutschland", "TRDE", "DE-1", "EUR", 90m, new DateTime(2025, 1, 11),
|
||||||
|
material: "MAT-MISSING"));
|
||||||
|
|
||||||
|
var result = await _service.AnalyzeFinanceSummaryAsync(2025, null, null);
|
||||||
|
|
||||||
|
Assert.Equal(100m, result.ProductFinanceSummary.TotalValue);
|
||||||
|
Assert.Equal(90m, result.ProductFinanceSummary.MissingReferenceValue);
|
||||||
|
Assert.Contains(result.Notices, notice =>
|
||||||
|
notice.Contains("Spartenanalyse auffaellig", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
notice.Contains("90.0%", StringComparison.OrdinalIgnoreCase));
|
||||||
|
Assert.Contains(result.Notices, notice =>
|
||||||
|
notice.Contains("ProductDivisionRefSet", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
notice.Contains("fuehrende Nullen", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SeedCentralRowsAsync(params CentralSalesRecord[] rows)
|
private async Task SeedCentralRowsAsync(params CentralSalesRecord[] rows)
|
||||||
{
|
{
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
@@ -440,7 +503,8 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
string productFamilyText = "",
|
string productFamilyText = "",
|
||||||
string productDivisionCode = "",
|
string productDivisionCode = "",
|
||||||
string productDivisionText = "",
|
string productDivisionText = "",
|
||||||
string productMappingAssigned = "")
|
string productMappingAssigned = "",
|
||||||
|
DateTime? postingDate = null)
|
||||||
{
|
{
|
||||||
return new CentralSalesRecord
|
return new CentralSalesRecord
|
||||||
{
|
{
|
||||||
@@ -476,6 +540,7 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
SalesCurrency = currency,
|
SalesCurrency = currency,
|
||||||
Incoterms2020 = "DAP",
|
Incoterms2020 = "DAP",
|
||||||
SalesResponsibleEmployee = "Alice",
|
SalesResponsibleEmployee = "Alice",
|
||||||
|
PostingDate = postingDate,
|
||||||
InvoiceDate = invoiceDate,
|
InvoiceDate = invoiceDate,
|
||||||
OrderDate = invoiceDate?.AddDays(-2),
|
OrderDate = invoiceDate?.AddDays(-2),
|
||||||
Land = land,
|
Land = land,
|
||||||
@@ -501,10 +566,12 @@ public class ManagementCockpitServiceTests : IDisposable
|
|||||||
private sealed class CountingCurrencyExchangeRateService : ICurrencyExchangeRateService
|
private sealed class CountingCurrencyExchangeRateService : ICurrencyExchangeRateService
|
||||||
{
|
{
|
||||||
public int ResolveRateCallCount { get; private set; }
|
public int ResolveRateCallCount { get; private set; }
|
||||||
|
public List<DateTime?> EffectiveDates { get; } = [];
|
||||||
|
|
||||||
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate)
|
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate)
|
||||||
{
|
{
|
||||||
ResolveRateCallCount++;
|
ResolveRateCallCount++;
|
||||||
|
EffectiveDates.Add(effectiveDate);
|
||||||
return 2m;
|
return 2m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# Finance Berechnungsformeln pro Land
|
# Finance Berechnungsformeln pro Land
|
||||||
|
|
||||||
Stand: 2026-05-19
|
Stand: 2026-06-01
|
||||||
|
|
||||||
|
Nachtrag 2026-06-01:
|
||||||
|
|
||||||
|
- ES-Referenz 2025 wurde nach Finance-Sitzung auf `3'082'320.18 EUR` korrigiert. Der alte Wert `3'102'333.61 EUR` war ein Referenz-/Excel-Fehler.
|
||||||
|
- In Management-Analysen ist das Wechselkurs-Anwendungsdatum konfigurierbar: `PostingDate`, `InvoiceDate` oder `ExtractionDate`.
|
||||||
|
- Sparten-Materialabgleich normalisiert fuehrende Nullen und warnt bei >=90% ungeklaerter Abdeckung.
|
||||||
|
|
||||||
Zweck: Dieses Dokument beschreibt die aktuell im Programm verwendeten Formeln fuer den Soll/Ist-Vergleich 2025. Es ist fuer eine zweite KI oder eine fachliche Gegenpruefung geschrieben.
|
Zweck: Dieses Dokument beschreibt die aktuell im Programm verwendeten Formeln fuer den Soll/Ist-Vergleich 2025. Es ist fuer eine zweite KI oder eine fachliche Gegenpruefung geschrieben.
|
||||||
|
|
||||||
@@ -92,7 +98,7 @@ Der IC-Abzug veraendert die Originaldaten und den Haupt-Ist-Wert nicht.
|
|||||||
| Schweiz | CH | SAP OData `ZSCHWEIZ`, falls importiert | CHF | leer | kein Sollwert im Seed |
|
| Schweiz | CH | SAP OData `ZSCHWEIZ`, falls importiert | CHF | leer | kein Sollwert im Seed |
|
||||||
| Oesterreich | AT | SAP OData `ZSCHWEIZ`, falls importiert | EUR | 3'443'863 | gemeinsame Logik |
|
| Oesterreich | AT | SAP OData `ZSCHWEIZ`, falls importiert | EUR | 3'443'863 | gemeinsame Logik |
|
||||||
| Deutschland | DE | nur falls Daten in `CentralSalesRecords` vorhanden | EUR | 3'635'923 | gemeinsame Logik |
|
| Deutschland | DE | nur falls Daten in `CentralSalesRecords` vorhanden | EUR | 3'635'923 | gemeinsame Logik |
|
||||||
| Spanien | ES | Sage SQL CSV / Manual Excel | EUR | 3'102'333.61 | SalesPriceValue aus Sage `ImporteNeto` |
|
| Spanien | ES | Sage SQL CSV / Manual Excel | EUR | 3'082'320.18 | SalesPriceValue aus Sage `ImporteNeto` |
|
||||||
| Frankreich | FR | SAP B1/HANA Schema `fr01_p` | EUR | CheckValue 1'471'218 | SalesPriceValue / B1 Positions-Netto |
|
| Frankreich | FR | SAP B1/HANA Schema `fr01_p` | EUR | CheckValue 1'471'218 | SalesPriceValue / B1 Positions-Netto |
|
||||||
| Indien | IN | Sage/HANA `TRAFAG_LIVE` | INR | CheckValue 750'936'591 | Hauswaehrung INR |
|
| Indien | IN | Sage/HANA `TRAFAG_LIVE` | INR | CheckValue 750'936'591 | Hauswaehrung INR |
|
||||||
| Italien | IT | SAP B1/HANA Schema `it01_p` | EUR | 7'669'840 | B1 Positions-Netto mit provisorischem Filter |
|
| Italien | IT | SAP B1/HANA Schema `it01_p` | EUR | 7'669'840 | B1 Positions-Netto mit provisorischem Filter |
|
||||||
@@ -250,22 +256,21 @@ Formel im Vergleich:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
Ist ES = Sum(SalesPriceValue)
|
Ist ES = Sum(SalesPriceValue)
|
||||||
Soll ES = 3'102'333.61 EUR
|
Soll ES = 3'082'320.18 EUR
|
||||||
```
|
```
|
||||||
|
|
||||||
Bekannter Stand:
|
Bekannter Stand:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Ist ca. 3'082'320.18 EUR
|
Ist ca. 3'082'320.18 EUR
|
||||||
Differenz ca. -20'013.43 EUR
|
Differenz ca. 0.00 EUR
|
||||||
```
|
```
|
||||||
|
|
||||||
Offen:
|
Offen:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Abweichung ist ca. 0.65%.
|
Die fruehere Abweichung entstand aus einem falschen Soll-/Referenzwert.
|
||||||
Wahrscheinliche Pruefpunkte: Fracht, Portes, Zuschlaege, Rundungen, Versicherung,
|
Falls Audit gefragt ist, muss die Herkunft des alten Werts 3'102'333.61 EUR nachvollzogen werden.
|
||||||
Finanzierung, nicht-artikelbezogene Belegpositionen oder abweichende Rhino-Auswertung.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## FR
|
## FR
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
# Finance-Entscheide fuer Net Sales Actuals
|
# Finance-Entscheide fuer Net Sales Actuals
|
||||||
|
|
||||||
Stand: 2026-05-20
|
Stand: 2026-06-01
|
||||||
|
|
||||||
|
## Nachtrag 2026-06-01 Finance-Sitzung
|
||||||
|
|
||||||
|
Umgesetzt:
|
||||||
|
|
||||||
|
- ES-Referenz 2025 ist auf `3'082'320.18 EUR` korrigiert; alter Sollwert `3'102'333.61 EUR` war Referenz-/Excel-Fehler.
|
||||||
|
- `Management Analyse > Laender` zeigt IC/2nd-party und `Ist ohne IC` als Diagnosewerte.
|
||||||
|
- Wechselkurs-Anwendungsdatum ist in den Settings konfigurierbar und wird in der Rohdaten-Diagnose angezeigt.
|
||||||
|
- Sparten-Materialabgleich normalisiert fuehrende Nullen; bei >=90% nicht zugeordnet / nicht im TR-AG-Stamm wird ein Warnhinweis angezeigt.
|
||||||
|
|
||||||
|
Weiter fachlich zu klaeren:
|
||||||
|
|
||||||
|
- Pro Standort bestaetigen, ob Intercompany bereits in der gelieferten Quelle herausgerechnet ist.
|
||||||
|
- Fuer Wechselkurse final bestaetigen, ob `PostingDate`, `InvoiceDate` oder ein anderes Datum fuehrend ist.
|
||||||
|
- Spartenanalyse fachlich pruefen, falls die ungeklaerte Abdeckung weiterhin extrem hoch bleibt.
|
||||||
|
|
||||||
## Nachtrag 2026-05-20 Finance Summary / Management Analyse
|
## Nachtrag 2026-05-20 Finance Summary / Management Analyse
|
||||||
|
|
||||||
@@ -91,7 +106,7 @@ Ergebnis im Reporting:
|
|||||||
- IT: IC-Kundenliste final bestaetigen.
|
- IT: IC-Kundenliste final bestaetigen.
|
||||||
- CH / AT: echtes SAP-Buchungsdatum pruefen, falls `ZSCHWEIZ` aktuell nur Fakturadatum liefert.
|
- CH / AT: echtes SAP-Buchungsdatum pruefen, falls `ZSCHWEIZ` aktuell nur Fakturadatum liefert.
|
||||||
- DE: finaler Alphaplan-Jahresfile liegt vor und ist technisch mappbar. Rohsumme `NettoPreisGesamtX` komplett ist `4'154'690.05 EUR`; nur `Land Kunde = Deutschland` ist `3'455'276.64 EUR`; Sollwert ist `3'635'923.00 EUR`. Offene Fachfrage: welche Kundenlaender/Abgrenzungen gehoeren offiziell zu DE?
|
- DE: finaler Alphaplan-Jahresfile liegt vor und ist technisch mappbar. Rohsumme `NettoPreisGesamtX` komplett ist `4'154'690.05 EUR`; nur `Land Kunde = Deutschland` ist `3'455'276.64 EUR`; Sollwert ist `3'635'923.00 EUR`. Offene Fachfrage: welche Kundenlaender/Abgrenzungen gehoeren offiziell zu DE?
|
||||||
- ES: Aktuell `3'082'320.18 EUR` gegen Soll `3'102'333.61`; Differenz `-20'013.43 EUR`. CSV nutzt `ImporteNeto`; Credit Notes/REC sind negativ. Offen bleiben Perioden-/Serienabgrenzung und ob Rhino eine andere Sage-Auswertung nutzt.
|
- ES: `3'082'320.18 EUR` ist laut Sitzung fachlich plausibel und entspricht der korrigierten Referenz. CSV nutzt `ImporteNeto`; Credit Notes/REC sind negativ. Der fruehere Sollwert `3'102'333.61` war ein Referenz-/Excel-Fehler.
|
||||||
|
|
||||||
## Pruefstand 2026-05-11
|
## Pruefstand 2026-05-11
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Finance Dashboard - Kurzmemo fuer Andreas
|
||||||
|
|
||||||
|
Stand: 2026-06-01
|
||||||
|
|
||||||
|
## Aktueller Stand
|
||||||
|
|
||||||
|
- `Finance Summary` ist die fuehrende Sicht fuer Soll/Ist.
|
||||||
|
- `Management Analyse` ist die Diagnoseebene fuer Laender, Datenstatus, Abweichungen, Gutschriften, Datenqualitaet, Spartenanalyse und Rohdaten.
|
||||||
|
- Das Dashboard ist technisch produktiv nutzbar.
|
||||||
|
- Letzter dokumentierter Testlauf: `80/80` Tests gruen.
|
||||||
|
- Standard-Ist bleibt inklusive aller Positionen.
|
||||||
|
- Intercompany / 2nd-party wird separat ausgewiesen, aber nicht automatisch herausgerechnet.
|
||||||
|
|
||||||
|
## Sitzungsergebnis 2026-06-01
|
||||||
|
|
||||||
|
- Spanien hat laut Sitzung keine echte Ist-Abweichung.
|
||||||
|
- ES-Ist `3'082'320.18 EUR` ist fachlich plausibel.
|
||||||
|
- Der bisherige ES-Sollwert `3'102'333.61 EUR` war falsch bzw. wahrscheinlich ein Excel-/Referenzfehler.
|
||||||
|
- ES-Referenz 2025 ist technisch auf `3'082'320.18 EUR` korrigiert.
|
||||||
|
- Intercompany ist in einzelnen Standortzahlen anscheinend bereits bereinigt, muss aber pro Standort bestaetigt werden.
|
||||||
|
- `Management Analyse > Laender` zeigt nun IC/2nd-party und `Ist ohne IC` als Diagnose.
|
||||||
|
- Bei den 2025-Wechselkursen ist das Anwendungsdatum jetzt in den Settings konfigurierbar.
|
||||||
|
- In der Sparten-Finanzanalyse sind mehr als 90% nicht zugeordnet; Andreas sagt, das kann fachlich nicht stimmen.
|
||||||
|
- Der Materialabgleich normalisiert jetzt fuehrende Nullen und zeigt bei >=90% ungeklaerter Spartenabdeckung einen Warnhinweis.
|
||||||
|
|
||||||
|
## Fuehrende Regeln
|
||||||
|
|
||||||
|
| Thema | Regel |
|
||||||
|
| --- | --- |
|
||||||
|
| Vergleich | Je Land in Hauswaehrung |
|
||||||
|
| Wertbasis | Nettofakturawert pro Position |
|
||||||
|
| Jahresabgrenzung | `PostingDate`, sonst `InvoiceDate`, sonst `ExtractionDate` |
|
||||||
|
| Gutschriften / Storno | Negative Beleg-/Positionszeilen |
|
||||||
|
| CHF | Reporting-/Kontrollsicht, nicht Standardvergleich |
|
||||||
|
| Intercompany | Separat ausweisen, nicht still entfernen |
|
||||||
|
|
||||||
|
## Wichtig fuer die Diskussion
|
||||||
|
|
||||||
|
### 1. Lokaler Soll/Ist zuerst in Hauswaehrung
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
|
||||||
|
- UK in `GBP`
|
||||||
|
- Indien in `INR`
|
||||||
|
- USA in `USD`
|
||||||
|
- EUR-Laender in `EUR`
|
||||||
|
|
||||||
|
Erst wenn die lokale Zahl stimmt, ist eine konsolidierte CHF-Sicht sinnvoll.
|
||||||
|
|
||||||
|
### 2. CHF als separate Management-Sicht
|
||||||
|
|
||||||
|
Offen ist der offizielle Kurstyp:
|
||||||
|
|
||||||
|
- Budgetkurs
|
||||||
|
- Monatskurs
|
||||||
|
- Transaktionskurs aus ERP
|
||||||
|
- Konzern-/Treasury-Kurs
|
||||||
|
- Stichtagskurs
|
||||||
|
|
||||||
|
Zusaetzlich offen:
|
||||||
|
|
||||||
|
- Auf welches Datum soll der Kurs fachlich final angewendet werden?
|
||||||
|
- `DocDate`?
|
||||||
|
- `PostingDate`?
|
||||||
|
- `InvoiceDate`?
|
||||||
|
- anderes Periodendatum?
|
||||||
|
|
||||||
|
Ohne offiziellen Kurstyp ist eine CHF-Zahl technisch berechenbar, aber fachlich nicht sauber verteidigbar.
|
||||||
|
|
||||||
|
Umsetzung: In den Settings gibt es `Wechselkurse anwenden auf`; die Rohdaten-Diagnose zeigt das verwendete Kursdatum an.
|
||||||
|
|
||||||
|
### 3. Kosten nicht mit Umsatzfreigabe vermischen
|
||||||
|
|
||||||
|
Kosten / Marge sollten als separate Ausbaustufe behandelt werden.
|
||||||
|
|
||||||
|
Zu klaeren:
|
||||||
|
|
||||||
|
- Standardkosten?
|
||||||
|
- Ist-Kosten?
|
||||||
|
- Group Cost?
|
||||||
|
- Budgetkosten?
|
||||||
|
- Finance-Kostentabelle?
|
||||||
|
|
||||||
|
Solange die Kostenquelle nicht freigegeben ist, sollte keine offizielle Marge ausgewiesen werden.
|
||||||
|
|
||||||
|
## Offene Laenderpunkte
|
||||||
|
|
||||||
|
| Land | Offener Punkt |
|
||||||
|
| --- | --- |
|
||||||
|
| DE | Welche Kundenlaender / Filter gehoeren offiziell zum deutschen Ist? |
|
||||||
|
| ES | Keine echte Ist-Abweichung laut Sitzung; Sollwert technisch auf `3'082'320.18 EUR` korrigiert |
|
||||||
|
| UK | Sage-Differenz ca. `-5.3k GBP`; Discounts, Freight, Charges und 2nd-party klaeren |
|
||||||
|
| IT | Fachliche Methode dokumentiert; neuer Export und finale Abgrenzung pruefen |
|
||||||
|
| CH / AT | Klaeren, ob `FKDAT` als Periodendatum akzeptiert ist |
|
||||||
|
|
||||||
|
## Offene Strukturpunkte
|
||||||
|
|
||||||
|
| Thema | Punkt |
|
||||||
|
| --- | --- |
|
||||||
|
| Intercompany | Pro Standort klaeren, ob IC bereits in der Quelle herausgerechnet ist; Dashboard zeigt IC-Diagnose |
|
||||||
|
| Wechselkurse | Kursanwendungsdatum ist konfigurierbar; fachliche Finalfreigabe fehlt |
|
||||||
|
| Spartenanalyse | >90% nicht zugeordnet ist fachlich unplausibel; Mapping / TR-AG-Referenz trotz technischer Normalisierung pruefen |
|
||||||
|
|
||||||
|
## Entscheidbedarf von Finance
|
||||||
|
|
||||||
|
Finance sollte pro Land bestaetigen:
|
||||||
|
|
||||||
|
- Quelle
|
||||||
|
- Datum
|
||||||
|
- Wertfeld
|
||||||
|
- Waehrung
|
||||||
|
- Filter
|
||||||
|
- Intercompany-Behandlung
|
||||||
|
|
||||||
|
Zusaetzlich braucht es Entscheide zu:
|
||||||
|
|
||||||
|
- offiziellem CHF-Kurstyp
|
||||||
|
- Datumsfeld fuer CHF-Kursanwendung
|
||||||
|
- Kurstabelle / Kursquelle
|
||||||
|
- Kostenumfang im Dashboard
|
||||||
|
- Behandlung kleiner Restabweichungen
|
||||||
|
- Korrektur ES-Sollwert
|
||||||
|
- Pruefung der Sparten-Zuordnung
|
||||||
|
|
||||||
|
## Kernaussage
|
||||||
|
|
||||||
|
Das technische Fundament steht. Die wichtigsten naechsten Punkte sind Referenzkorrekturen, fachliche Abgrenzungen und Mapping-Pruefungen, nicht primaer technische Grundlagenprobleme.
|
||||||
@@ -10,6 +10,37 @@ Das Finance Dashboard ist technisch produktiv nutzbar. Die fuehrende Sicht ist `
|
|||||||
|
|
||||||
Offen sind nicht primaer technische Grundlagen, sondern fachliche Abgrenzungen je Land: Welche lokale Auswertung ist offiziell fuehrend, welche Filter gelten, und ob bestimmte Differenzen akzeptiert oder durch zusaetzliche Quell-/Filterlogik erklaert werden muessen.
|
Offen sind nicht primaer technische Grundlagen, sondern fachliche Abgrenzungen je Land: Welche lokale Auswertung ist offiziell fuehrend, welche Filter gelten, und ob bestimmte Differenzen akzeptiert oder durch zusaetzliche Quell-/Filterlogik erklaert werden muessen.
|
||||||
|
|
||||||
|
## Nachtrag Sitzung 2026-06-01
|
||||||
|
|
||||||
|
Aus der Sitzung mit Finance / Andreas ergeben sich diese aktualisierten Punkte:
|
||||||
|
|
||||||
|
1. Intercompany
|
||||||
|
- Frage aus Finance: Sind Intercompany-Umsaetze bereits in den Standortdaten herausgerechnet?
|
||||||
|
- Aktueller Eindruck aus der Sitzung: Anscheinend sind IC-Anteile in einzelnen Standortauswertungen bereits bereinigt, trotzdem bleiben Abweichungen.
|
||||||
|
- Umsetzung: `Management Analyse > Laender` zeigt jetzt IC/2nd-party und `Ist ohne IC` als Diagnosewerte.
|
||||||
|
- Wichtig fuer die App: Das Dashboard entfernt IC weiterhin nicht automatisch aus dem Standard-Ist.
|
||||||
|
- Folgeaktion: Pro Standort klaeren, ob die Quellzahl bereits netto ohne IC geliefert wird oder ob das Dashboard IC noch fachlich abziehen soll.
|
||||||
|
|
||||||
|
2. Spanien
|
||||||
|
- Aussage Sitzung: Spanien hat fachlich keine echte Soll/Ist-Abweichung.
|
||||||
|
- Ist-Wert im Dashboard: `3'082'320.18 EUR`.
|
||||||
|
- Der bisherige Sollwert `3'102'333.61 EUR` ist falsch bzw. wahrscheinlich ein Excel-/Referenzfehler.
|
||||||
|
- Umsetzung: ES-FinanceReference 2025 wird auf `3'082'320.18 EUR` gesetzt; `FinanceProbe` nutzt denselben Referenzwert.
|
||||||
|
- Folgeaktion: Quelle der falschen Excel-/Referenzzahl weiterhin fachlich nachvollziehen, falls Audit gefragt ist.
|
||||||
|
|
||||||
|
3. Wechselkurse 2025
|
||||||
|
- In den Settings / Kurstabellen fehlt ein Feld, auf welches Datum der Kurs angewendet wird.
|
||||||
|
- Zu klaeren: Anwendung auf `DocDate`, `PostingDate`, `InvoiceDate` oder ein anderes Periodendatum.
|
||||||
|
- Umsetzung: In `Settings > Export Einstellungen` ist `Wechselkurse anwenden auf` konfigurierbar.
|
||||||
|
- Umsetzung: `Management Analyse > Rohdaten Diagnose` zeigt das verwendete Kursdatum an.
|
||||||
|
|
||||||
|
4. Sparten-Finanzanalyse
|
||||||
|
- Aktueller Befund: Mehr als 90% der Werte sind nicht zugeordnet.
|
||||||
|
- Aussage Andreas: Das kann fachlich nicht stimmen.
|
||||||
|
- Umsetzung: Sparten-Materialabgleich normalisiert fuehrende Nullen in Materialnummern.
|
||||||
|
- Umsetzung: Bei >=90% nicht zugeordnet / nicht im Stamm zeigt die Management-Analyse einen Warnhinweis mit Pruefpunkten.
|
||||||
|
- Folgeaktion: Zentrale Spartenzuordnung pruefen, insbesondere Mapping gegen TR-AG-/SAP-Referenz, Materialnummernformat, fuehrende Nullen, lokale Artikelnummern und Fuellung von `ProductDivisionRefSet`.
|
||||||
|
|
||||||
## Was aktuell vorhanden ist
|
## Was aktuell vorhanden ist
|
||||||
|
|
||||||
### Finance Summary
|
### Finance Summary
|
||||||
@@ -200,7 +231,7 @@ Kosten sollten nicht direkt in die aktuelle Umsatzfreigabe gemischt werden. Sinn
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| CH / AT | SAP OData `ZSCHWEIZ`; Trennung ueber Buchungskreis / Reporting-Land | Pruefen, ob `FKDAT` fachlich als Buchungsdatum akzeptiert ist |
|
| CH / AT | SAP OData `ZSCHWEIZ`; Trennung ueber Buchungskreis / Reporting-Land | Pruefen, ob `FKDAT` fachlich als Buchungsdatum akzeptiert ist |
|
||||||
| DE | Alphaplan Excel; `NettoPreisGesamtX`; finaler 2025-File liegt technisch vor | Finance muss bestaetigen, welche Kundenlaender / Filter zum offiziellen DE-Ist gehoeren |
|
| DE | Alphaplan Excel; `NettoPreisGesamtX`; finaler 2025-File liegt technisch vor | Finance muss bestaetigen, welche Kundenlaender / Filter zum offiziellen DE-Ist gehoeren |
|
||||||
| ES | Sage CSV; `ImporteNeto`; Credit Notes / REC negativ | Differenz ca. `-20'013.43 EUR` gegen Soll fachlich klaeren |
|
| ES | Sage CSV; `ImporteNeto`; Credit Notes / REC negativ; Ist `3'082'320.18 EUR` fachlich bestaetigt | Bisheriger Sollwert `3'102'333.61 EUR` ist falsch bzw. Excel-/Referenzfehler |
|
||||||
| FR | SAP B1/HANA; Positions-Netto passt praktisch gegen Soll | Kein grosser offener Punkt dokumentiert |
|
| FR | SAP B1/HANA; Positions-Netto passt praktisch gegen Soll | Kein grosser offener Punkt dokumentiert |
|
||||||
| IN | Hauswaehrung INR; Vergleich in INR | Keine CHF-Tageskurslogik fuer Standardvergleich verwenden |
|
| IN | Hauswaehrung INR; Vergleich in INR | Keine CHF-Tageskurslogik fuer Standardvergleich verwenden |
|
||||||
| IT | SAP B1/HANA; Finance-Methode mit IT-Abgrenzung dokumentiert | Nach neuem IT-Export pruefen, ob Summe und Abgrenzung final passen |
|
| IT | SAP B1/HANA; Finance-Methode mit IT-Abgrenzung dokumentiert | Nach neuem IT-Export pruefen, ob Summe und Abgrenzung final passen |
|
||||||
@@ -256,19 +287,18 @@ Aktueller Stand:
|
|||||||
- `ImporteNeto` wird als Nettozeile verwendet.
|
- `ImporteNeto` wird als Nettozeile verwendet.
|
||||||
- Credit Notes / REC laufen negativ.
|
- Credit Notes / REC laufen negativ.
|
||||||
- Ist aktuell ca. `3'082'320.18 EUR`.
|
- Ist aktuell ca. `3'082'320.18 EUR`.
|
||||||
- Sollwert ca. `3'102'333.61 EUR`.
|
- Sitzung 2026-06-01: Dieser Ist-Wert entspricht fachlich dem erwarteten Wert.
|
||||||
- Differenz ca. `-20'013.43 EUR`.
|
- Der bisherige Sollwert ca. `3'102'333.61 EUR` ist falsch bzw. wahrscheinlich ein Excel-/Referenzfehler.
|
||||||
|
|
||||||
Klaerung:
|
Klaerung:
|
||||||
|
|
||||||
- Ist `FechaFactura` das richtige Periodendatum?
|
- Falschen Sollwert in `check.xlsx` / FinanceReference korrigieren.
|
||||||
- Sind alle Serien enthalten (`REG`, `LAT`, `PRO`, `REC`)?
|
- Klaeren, woher der falsche Sollwert `3'102'333.61 EUR` kam.
|
||||||
- Gibt es Fracht, Portes, Zuschlaege, Versicherung, Finanzierung oder Nebenpositionen, die Rhino anders behandelt?
|
- Danach ES nicht mehr als fachliche Abweichung fuehren, sofern die Referenz korrigiert ist.
|
||||||
- Gibt es eine offizielle Sage-Auswertung, die den Sollwert erzeugt, inklusive Filterbeschreibung?
|
|
||||||
|
|
||||||
Argument:
|
Argument:
|
||||||
|
|
||||||
Spanien ist technisch angebunden. Die Restabweichung ist klein genug fuer eine gezielte fachliche Filterpruefung, aber noch nicht fachlich freigegeben.
|
Spanien ist technisch angebunden und fachlich plausibel. Die bisherige Abweichung entsteht aus einer falschen Soll-/Referenzzahl, nicht aus dem Sage-Ist.
|
||||||
|
|
||||||
### 4. UK
|
### 4. UK
|
||||||
|
|
||||||
@@ -317,6 +347,7 @@ Die Datenquelle ist angebunden. Der kritische Punkt ist, ob Finance die aktuelle
|
|||||||
- UK ist Sage, nicht SAP B1.
|
- UK ist Sage, nicht SAP B1.
|
||||||
- DE ist Alphaplan, nicht SAP B1.
|
- DE ist Alphaplan, nicht SAP B1.
|
||||||
- Spartenanalyse nutzt TR-AG-/SAP-Referenz als zentrale Wahrheit.
|
- Spartenanalyse nutzt TR-AG-/SAP-Referenz als zentrale Wahrheit.
|
||||||
|
- Wenn in der Sparten-Finanzanalyse mehr als 90% nicht zugeordnet sind, ist das nicht als fachliche Wahrheit zu akzeptieren, sondern als Mapping-/Referenzproblem zu pruefen.
|
||||||
- Budget-CHF ist Kontrollsicht, nicht Standardabgleich.
|
- Budget-CHF ist Kontrollsicht, nicht Standardabgleich.
|
||||||
- Eine Zahl, die zufaellig naeher am Soll ist, ist nicht automatisch die richtige fachliche Methode.
|
- Eine Zahl, die zufaellig naeher am Soll ist, ist nicht automatisch die richtige fachliche Methode.
|
||||||
|
|
||||||
@@ -334,6 +365,8 @@ Die Datenquelle ist angebunden. Der kritische Punkt ist, ob Finance die aktuelle
|
|||||||
- Hauswaehrung bleibt fuehrend fuer lokale Freigabe
|
- Hauswaehrung bleibt fuehrend fuer lokale Freigabe
|
||||||
- CHF als separate Reporting-Sicht
|
- CHF als separate Reporting-Sicht
|
||||||
- offizieller Kurstyp und Kurstabelle
|
- offizieller Kurstyp und Kurstabelle
|
||||||
|
- Datumsfeld fuer Kursanwendung, z. B. `DocDate`, `PostingDate` oder `InvoiceDate`
|
||||||
|
- Anzeige im Dashboard, welches Datum fuer den Kurs verwendet wird
|
||||||
|
|
||||||
3. Finance entscheidet den Kostenumfang:
|
3. Finance entscheidet den Kostenumfang:
|
||||||
- vorerst keine offizielle Kosten-KPI
|
- vorerst keine offizielle Kosten-KPI
|
||||||
@@ -342,9 +375,10 @@ Die Datenquelle ist angebunden. Der kritische Punkt ist, ob Finance die aktuelle
|
|||||||
|
|
||||||
4. Finance priorisiert die offenen Laender:
|
4. Finance priorisiert die offenen Laender:
|
||||||
- DE: Kundenlaender / Filter
|
- DE: Kundenlaender / Filter
|
||||||
- ES: Sage-Differenz
|
- ES: Referenz-/Sollwert korrigieren, keine echte Ist-Abweichung laut Sitzung
|
||||||
- UK: Sage-Differenz
|
- UK: Sage-Differenz
|
||||||
- IT: neuer Export und finale Abgrenzung
|
- IT: neuer Export und finale Abgrenzung
|
||||||
|
- Spartenanalyse: >90% nicht zugeordnet fachlich unplausibel, Mapping pruefen
|
||||||
|
|
||||||
5. Finance liefert fuer jede offene Differenz entweder:
|
5. Finance liefert fuer jede offene Differenz entweder:
|
||||||
- offizielle Reportfilter,
|
- offizielle Reportfilter,
|
||||||
@@ -363,6 +397,9 @@ Die Datenquelle ist angebunden. Der kritische Punkt ist, ob Finance die aktuelle
|
|||||||
8. Welcher Kurstyp ist fuer CHF verbindlich?
|
8. Welcher Kurstyp ist fuer CHF verbindlich?
|
||||||
9. Sollen Kosten jetzt Bestandteil des Finance Dashboards werden oder separat als naechste Ausbaustufe?
|
9. Sollen Kosten jetzt Bestandteil des Finance Dashboards werden oder separat als naechste Ausbaustufe?
|
||||||
10. Welche Kostenquelle waere fachlich fuehrend?
|
10. Welche Kostenquelle waere fachlich fuehrend?
|
||||||
|
11. Sind Intercompany-Umsaetze in den Standortquellen bereits herausgerechnet?
|
||||||
|
12. Auf welches Datum muessen 2025-Wechselkurse angewendet werden?
|
||||||
|
13. Warum sind in der Sparten-Finanzanalyse mehr als 90% nicht zugeordnet, obwohl Andreas das fachlich ausschliesst?
|
||||||
|
|
||||||
## Quellen im Repo
|
## Quellen im Repo
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# RAG Finance
|
# RAG Finance
|
||||||
|
|
||||||
Stand: 2026-05-29
|
Stand: 2026-06-01
|
||||||
|
|
||||||
## Kurzstand
|
## Kurzstand
|
||||||
|
|
||||||
@@ -15,6 +15,10 @@ Stand: 2026-05-29
|
|||||||
- Finance-Schulung dokumentiert die neuen Spartenfunktionen im Tab `Spartenanalyse`.
|
- Finance-Schulung dokumentiert die neuen Spartenfunktionen im Tab `Spartenanalyse`.
|
||||||
- Filter fuer Jahr, Land und Waehrung wirken auf das Finance-Endergebnis.
|
- Filter fuer Jahr, Land und Waehrung wirken auf das Finance-Endergebnis.
|
||||||
- Standard-Ist bleibt inklusive Positionen; Intercompany/2nd-party wird separat ausgewiesen.
|
- Standard-Ist bleibt inklusive Positionen; Intercompany/2nd-party wird separat ausgewiesen.
|
||||||
|
- Nach Sitzung 2026-06-01: ES-Referenz 2025 ist auf `3'082'320.18 EUR` korrigiert; alter Sollwert `3'102'333.61 EUR` war Referenz-/Excel-Fehler.
|
||||||
|
- Management Analyse zeigt in `Laender` jetzt IC/2nd-party und `Ist ohne IC` als Diagnose.
|
||||||
|
- Wechselkurs-Anwendungsdatum ist in Settings konfigurierbar und wird in der Rohdaten-Diagnose angezeigt.
|
||||||
|
- Spartenanalyse war mit >90% nicht zugeordnet fachlich unplausibel; Materialabgleich normalisiert fuehrende Nullen und warnt bei >=90% ungeklaerter Abdeckung.
|
||||||
|
|
||||||
## Wichtige Regeln
|
## Wichtige Regeln
|
||||||
|
|
||||||
@@ -28,12 +32,13 @@ Stand: 2026-05-29
|
|||||||
|
|
||||||
- DE: Finance/Munir muss bestaetigen, welche Kundenlaender/Filter zum offiziellen DE-Ist gehoeren.
|
- DE: Finance/Munir muss bestaetigen, welche Kundenlaender/Filter zum offiziellen DE-Ist gehoeren.
|
||||||
- IT: Nach neuem IT-Export pruefen, ob die vollstaendige `Trafag Italia`-Summe sichtbar wird.
|
- IT: Nach neuem IT-Export pruefen, ob die vollstaendige `Trafag Italia`-Summe sichtbar wird.
|
||||||
- ES: Differenz zu Rhino/check.xlsx bleibt fachlich zu klaeren.
|
- UK: Sage-Restdifferenz ueber Exportvollstaendigkeit, Discounts, Freight/Charges und 2nd-party klaeren.
|
||||||
|
- Spartenanalyse: Falls weiterhin >90% nicht zugeordnet, TR-AG-Referenz/Join/Materialnummern pruefen.
|
||||||
|
|
||||||
## Management-Analyse-Reiter
|
## Management-Analyse-Reiter
|
||||||
|
|
||||||
- `Finance Summary`: KPI-Karten und Summen wie im zentralen Excel.
|
- `Finance Summary`: KPI-Karten und Summen wie im zentralen Excel.
|
||||||
- `Laender`: Ist, Soll, Differenz, Status, Quelle und TSC je Land/Waehrung.
|
- `Laender`: Ist, IC/2nd-party, Ist ohne IC, Soll, Differenz, Status, Quelle und TSC je Land/Waehrung.
|
||||||
- `Datenstatus`: Standortbestand, letzte Speicherung, letzter Export, Manual-Import-Hinweise.
|
- `Datenstatus`: Standortbestand, letzte Speicherung, letzter Export, Manual-Import-Hinweise.
|
||||||
- `Abweichungen`: Soll/Ist-Abweichungen sortiert nach Betrag.
|
- `Abweichungen`: Soll/Ist-Abweichungen sortiert nach Betrag.
|
||||||
- `Gutschriften`: technische Kandidaten ueber negative Werte und erkennbare Belegtypen/-nummern.
|
- `Gutschriften`: technische Kandidaten ueber negative Werte und erkennbare Belegtypen/-nummern.
|
||||||
@@ -63,7 +68,7 @@ Stand: 2026-05-29
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| CH/AT | SAP OData `ZSCHWEIZ`, Trennung ueber Buchungskreis/Reporting-Land |
|
| CH/AT | SAP OData `ZSCHWEIZ`, Trennung ueber Buchungskreis/Reporting-Land |
|
||||||
| DE | Alphaplan Excel, `NettoPreisGesamtX`, 2025-Zwang |
|
| DE | Alphaplan Excel, `NettoPreisGesamtX`, 2025-Zwang |
|
||||||
| ES | Sage CSV, `ImporteNeto`, REC/Credit negativ |
|
| ES | Sage CSV, `ImporteNeto`, REC/Credit negativ; Referenz 2025 korrigiert auf `3'082'320.18 EUR` |
|
||||||
| IT | Hauswaehrung, `Trafag Italia` ausgeschlossen, Duplikatlogik fuer leeres Supplier country |
|
| IT | Hauswaehrung, `Trafag Italia` ausgeschlossen, Duplikatlogik fuer leeres Supplier country |
|
||||||
| UK | Sage/Manual Excel, GBP, `[Sales Price/Value] * [Quantity]`, Credit Notes negativ |
|
| UK | Sage/Manual Excel, GBP, `[Sales Price/Value] * [Quantity]`, Credit Notes negativ |
|
||||||
| IN | INR als Hauswaehrung |
|
| IN | INR als Hauswaehrung |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Last Change
|
# Last Change
|
||||||
|
|
||||||
Stand: 2026-05-29
|
Stand: 2026-06-01
|
||||||
|
|
||||||
Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
|
Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
|
||||||
|
|
||||||
@@ -8,8 +8,16 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
|
|||||||
|
|
||||||
- Fuehrender Kurzkontext: `docs/rag/PROJECT.md`.
|
- Fuehrender Kurzkontext: `docs/rag/PROJECT.md`.
|
||||||
- Themenrouter: `docs/RAG_ROUTER.md`.
|
- Themenrouter: `docs/RAG_ROUTER.md`.
|
||||||
- Letzter dokumentierter Code-Stand: `36ca822 Add browser favicon`, alle Aenderungen bis 2026-05-29 13:47 deployt.
|
- Letzter dokumentierter Code-Stand: Finance-Sitzungsnachtrag 2026-06-01 noch nicht deployt.
|
||||||
- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-favicon` mit `80/80` Tests gruen.
|
- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-finance-session-proof` mit `82/82` Tests gruen.
|
||||||
|
- Neu umgesetzt: ES-Referenz 2025 auf `3'082'320.18 EUR` korrigiert; alter Sollwert `3'102'333.61 EUR` als Referenz-/Excel-Fehler dokumentiert.
|
||||||
|
- Neu umgesetzt: `FinanceProbe` nutzt dieselbe korrigierte ES-Referenz.
|
||||||
|
- Neu umgesetzt: Wechselkurs-Anwendungsdatum in Settings konfigurierbar (`PostingDate`, `InvoiceDate`, `ExtractionDate`) und in Rohdaten-Diagnose sichtbar.
|
||||||
|
- Neu umgesetzt: CHF als Anzeige-Waehrung in Management Analyse verfuegbar.
|
||||||
|
- Neu umgesetzt: `Management Analyse > Laender` zeigt IC/2nd-party und `Ist ohne IC` als Diagnosewerte.
|
||||||
|
- Neu umgesetzt: Sparten-Materialabgleich normalisiert fuehrende Nullen.
|
||||||
|
- Neu umgesetzt: Warnhinweis bei >=90% nicht zugeordnet / nicht im TR-AG-Stamm, mit Test abgesichert.
|
||||||
|
- Neu erstellt: kompaktes Andreas-Memo `docs/FINANCE_MEMO_ANDREAS_2026-06-01.md`.
|
||||||
- Neu dokumentiert: Produktsparten-Mapping fuer Group Sales Report ueber TR-AG-Artikelstamm und separate Mapping-Tabelle.
|
- Neu dokumentiert: Produktsparten-Mapping fuer Group Sales Report ueber TR-AG-Artikelstamm und separate Mapping-Tabelle.
|
||||||
- Neu dokumentiert: Upgreat-Firewall-Freigabe muss fuer den publizierten Webserver `10.120.1.17` erfolgen, nicht fuer den lokalen Entwicklungs-PC.
|
- Neu dokumentiert: Upgreat-Firewall-Freigabe muss fuer den publizierten Webserver `10.120.1.17` erfolgen, nicht fuer den lokalen Entwicklungs-PC.
|
||||||
- Neu umgesetzt: `Management Analyse` im Finance Cockpit hat zusaetzliche Reiter fuer Laender, Datenstatus, Abweichungen, Gutschriften-Kandidaten und Datenqualitaet.
|
- Neu umgesetzt: `Management Analyse` im Finance Cockpit hat zusaetzliche Reiter fuer Laender, Datenstatus, Abweichungen, Gutschriften-Kandidaten und Datenqualitaet.
|
||||||
@@ -24,7 +32,56 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
|
|||||||
- Neu umgesetzt und deployed: Finance-Schulung hat einen neuen Tab `Spartenanalyse` mit Navigation, Gruppierung, Top 10, Flaggen, Icons und Statusinterpretation.
|
- Neu umgesetzt und deployed: Finance-Schulung hat einen neuen Tab `Spartenanalyse` mit Navigation, Gruppierung, Top 10, Flaggen, Icons und Statusinterpretation.
|
||||||
- Neu umgesetzt und deployed: Browser-Favicon `wwwroot/favicon.svg` und Head-Link in `Components/App.razor`.
|
- Neu umgesetzt und deployed: Browser-Favicon `wwwroot/favicon.svg` und Head-Link in `Components/App.razor`.
|
||||||
- Letzter Deploy: 2026-05-29 13:47 auf `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`.
|
- Letzter Deploy: 2026-05-29 13:47 auf `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`.
|
||||||
- Letzte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-favicon` mit `80/80` Tests gruen.
|
- Aktueller Stand 2026-06-01 ist validiert, aber noch nicht deployt.
|
||||||
|
- Letzte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-finance-session-proof` mit `82/82` Tests gruen.
|
||||||
|
|
||||||
|
## Nachtrag 2026-06-01 Finance-Sitzung Andreas
|
||||||
|
|
||||||
|
Umgesetzt:
|
||||||
|
|
||||||
|
- ES hat laut Sitzung keine echte Ist-Abweichung. `DatabaseSeedService` setzt `FinanceReference ES 2025` auf `3'082'320.18 EUR`; `CheckValue` wird fuer ES entfernt.
|
||||||
|
- `Tools/FinanceProbe` verwendet fuer den Spain-CSV-Check ebenfalls `3'082'320.18 EUR`.
|
||||||
|
- `Settings > Export Einstellungen` hat neu `Wechselkurse anwenden auf` mit Optionen:
|
||||||
|
- `PostingDate / Buchungsdatum`
|
||||||
|
- `InvoiceDate / Rechnungsdatum`
|
||||||
|
- `ExtractionDate / Extraktionsdatum`
|
||||||
|
- `Management Analyse > Rohdaten Diagnose` zeigt `Kursdatum` bzw. das fuer Wechselkurse verwendete Datumsfeld.
|
||||||
|
- `Management Analyse` erlaubt `CHF` als Anzeige-Waehrung.
|
||||||
|
- `Management Analyse > Laender` zeigt zusaetzlich:
|
||||||
|
- `IC/2nd-party`
|
||||||
|
- `Ist ohne IC`
|
||||||
|
- Intercompany bleibt Diagnose: Der Standard-Ist wird nicht automatisch bereinigt.
|
||||||
|
- Sparten-Zuordnung normalisiert Materialnummern fuer den Vergleich gegen TR-AG-Referenz, insbesondere fuehrende Nullen.
|
||||||
|
- Bei >=90% Umsatz in `Nicht zugeordnet` + `Nicht im TR-AG-Stamm` erzeugt die Management-Analyse einen Warnhinweis mit Pruefpunkten (`ProductDivisionRefSet`, Join, fuehrende Nullen, lokale Materialnummern, letzter ZSCHWEIZ-Export).
|
||||||
|
- Der Warnhinweis ist per Test `AnalyzeFinanceSummaryAsync_Warns_When_Product_Assignment_Coverage_Is_Implausibly_Low` abgesichert.
|
||||||
|
- Bestehender Sparten-Test prueft weiterhin, dass `000MAT-OK` in der TR-AG-Referenz zu `MAT-OK` aus einem lokalen Standort matcht.
|
||||||
|
|
||||||
|
Dokumentiert:
|
||||||
|
|
||||||
|
- `docs/FINANCE_STATUS_OFFENE_PUNKTE_2026-06-01.md`
|
||||||
|
- `docs/FINANCE_MEMO_ANDREAS_2026-06-01.md`
|
||||||
|
- `docs/rag/FINANCE.md`
|
||||||
|
- `docs/FINANCE_ENTSCHEIDE.md`
|
||||||
|
- `docs/FINANCE_BERECHNUNGSFORMELN_LAENDER_2026-05-19.md`
|
||||||
|
- `SAGE_SPAIN_EXPORT_2026-05-05.md`
|
||||||
|
|
||||||
|
Validierung:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-finance-session-proof
|
||||||
|
```
|
||||||
|
|
||||||
|
Ergebnis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
82/82 Tests gruen
|
||||||
|
```
|
||||||
|
|
||||||
|
Offen / fachlich:
|
||||||
|
|
||||||
|
- Pro Standort bestaetigen, ob Intercompany bereits in der gelieferten Quelle herausgerechnet ist.
|
||||||
|
- Fuer Wechselkurse fachlich final bestaetigen, welches Datumsfeld fuehrend ist.
|
||||||
|
- Falls die Spartenanalyse weiterhin >90% ungeklaert bleibt, TR-AG-Referenz, `ProductDivisionRefSet`, Join und lokale Materialnummern mit Andreas/Kendra pruefen.
|
||||||
|
|
||||||
## Nachtrag 2026-05-29 Management Analyse UX / Spartenanalyse / Favicon
|
## Nachtrag 2026-05-29 Management Analyse UX / Spartenanalyse / Favicon
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user