diff --git a/TrafagSalesExporter/Services/HrKpi/HrKpiDashboardBuilder.cs b/TrafagSalesExporter/Services/HrKpi/HrKpiDashboardBuilder.cs index 4a5315e..39c3537 100644 --- a/TrafagSalesExporter/Services/HrKpi/HrKpiDashboardBuilder.cs +++ b/TrafagSalesExporter/Services/HrKpi/HrKpiDashboardBuilder.cs @@ -8,6 +8,15 @@ namespace TrafagSalesExporter.Services; internal sealed class HrKpiDashboardBuilder { private readonly HrKpiDataSourceOptions _dataSources; + private static readonly HashSet ExcludedPersonNameKeys = new(StringComparer.OrdinalIgnoreCase) + { + NormalizePersonExclusionKey("Angelina Jolie"), + NormalizePersonExclusionKey("Brad Pitt"), + NormalizePersonExclusionKey("Peter Muster"), + NormalizePersonExclusionKey("ICT Trafag"), + NormalizePersonExclusionKey("Empfanger Reminder"), + NormalizePersonExclusionKey("Empfänger Reminder") + }; public HrKpiDashboardBuilder(HrKpiDataSourceOptions dataSources) { @@ -40,6 +49,12 @@ internal sealed class HrKpiDashboardBuilder var employees = LoadEmployees(context, timeRows, sapRows); var absences = LoadAbsences(context); var leavers = LoadLeavers(context); + var excludedRows = + employees.RemoveAll(x => IsExcludedTestPerson(x.NameVoll)) + + absences.RemoveAll(x => IsExcludedTestPerson(x.Name)) + + leavers.RemoveAll(x => IsExcludedTestPerson(x.NameVoll)); + if (excludedRows > 0) + result.Notices.Add($"{excludedRows:N0} Testpersonen-Zeilen wurden aus dem HR-KPI-Dashboard ausgeschlossen."); result.OrganisationOptions = employees .Select(x => x.Organisationseinheit) @@ -939,6 +954,18 @@ internal sealed class HrKpiDashboardBuilder private static string NormalizeKey(string value) => value.Trim().ToUpperInvariant(); + private static bool IsExcludedTestPerson(string? name) + => !string.IsNullOrWhiteSpace(name) && + ExcludedPersonNameKeys.Contains(NormalizePersonExclusionKey(name)); + + private static string NormalizePersonExclusionKey(string value) + { + var normalized = NormalizeReason(value) + .Replace(",", " ", StringComparison.OrdinalIgnoreCase); + var parts = normalized.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return string.Join(" ", parts.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)); + } + private static int? ParseCostCenter(string value) { var raw = value.Split('/')[0].Trim(); diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/HrKpiServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/HrKpiServiceTests.cs index 3b0889d..1365306 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/HrKpiServiceTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/HrKpiServiceTests.cs @@ -211,6 +211,45 @@ public sealed class HrKpiServiceTests : IDisposable Assert.Contains(result.Leavers, row => row.Austrittsart == "Ruhestand" && row.FluktuationAusschlussgrund == "Pensionierung"); } + [Fact] + public async Task BuildAsync_Excludes_Configured_Test_Persons_From_All_Hr_Kpi_Views() + { + RewriteEmployeeRows( + [ + [1001, "Alpha, Anna", "Org A", "100 / Org A", "Engineer", "n", new DateTime(2020, 1, 1), "Aktiv", "0:00", 25, 0, 0, 100000, "CHF"], + [9001, "Jolie, Angelina", "Test", "999 / Test", "Engineer", "n", new DateTime(2020, 1, 1), "Aktiv", "0:00", 25, 0, 0, 100000, "CHF"], + [9002, "Brad Pitt", "Test", "999 / Test", "Engineer", "n", new DateTime(2020, 1, 1), "Aktiv", "0:00", 25, 0, 0, 100000, "CHF"], + [9003, "Peter Muster", "Test", "999 / Test", "Engineer", "n", new DateTime(2020, 1, 1), "Aktiv", "0:00", 25, 0, 0, 100000, "CHF"] + ]); + WriteWorkbook(Path.Combine(_folder, "Abwesenheitinstunden.xlsx"), + [ + "Personalnummer", "Nachname, Vorname (Link Personal)", "Organisation", "Stelle", "Personal Status", + "Krankheit angetreten (Stunden Ind.)", "Krank nicht buchbar angetreten (Stunden Ind.)" + ], + [ + [1001, "Alpha, Anna", "Org A", "Engineer", "Aktiv", 8.4, 0], + [9004, "ICT Trafag", "Test", "Engineer", "Aktiv", 8.4, 0] + ]); + RewriteLeaverRows( + [ + [1001, "Alpha, Anna", "Org A", "Engineer", "Inaktiv", new DateTime(2025, 3, 10), new DateTime(2020, 1, 1), "Arbeitnehmer Kuendigung"], + [9005, "Empfänger Reminder", "Test", "Engineer", "Inaktiv", new DateTime(2025, 3, 10), new DateTime(2020, 1, 1), "Arbeitnehmer Kuendigung"] + ]); + + var result = await _service.BuildAsync(new HrKpiOptions + { + DataFolder = _folder, + Year = 2025 + }); + + Assert.DoesNotContain(result.Employees, row => row.NameVoll.Contains("Angelina", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(result.Employees, row => row.NameVoll.Contains("Brad Pitt", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(result.Employees, row => row.NameVoll.Contains("Peter Muster", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(result.Absences, row => row.Name.Contains("ICT Trafag", StringComparison.OrdinalIgnoreCase)); + Assert.DoesNotContain(result.Leavers, row => row.NameVoll.Contains("Reminder", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(result.Notices, notice => notice.Contains("Testpersonen", StringComparison.OrdinalIgnoreCase)); + } + private static void WriteFixtureFiles(string folder) { WriteWorkbook(Path.Combine(folder, "Saldiperstichdatum.xlsx"), diff --git a/TrafagSalesExporter/docs/CFO_Kurzbericht_270515.docx b/TrafagSalesExporter/docs/CFO_Kurzbericht_270515.docx index 459edea..aa91349 100644 Binary files a/TrafagSalesExporter/docs/CFO_Kurzbericht_270515.docx and b/TrafagSalesExporter/docs/CFO_Kurzbericht_270515.docx differ diff --git a/TrafagSalesExporter/docs/FINANCE_DASHBOARD_TODO_2026-05-15.md b/TrafagSalesExporter/docs/FINANCE_DASHBOARD_TODO_2026-05-15.md index 0053fef..f994df4 100644 --- a/TrafagSalesExporter/docs/FINANCE_DASHBOARD_TODO_2026-05-15.md +++ b/TrafagSalesExporter/docs/FINANCE_DASHBOARD_TODO_2026-05-15.md @@ -11,6 +11,7 @@ Ziel: Aufbau eines modernen, uebersichtlichen Intranet-Dashboards fuer das Group | 1 | Fuehrendes CFO-Dokument verwenden: `FINANCE_CHEF_SUMMARY_2026-05-15.docx` | Offen | | 1 | Offene Finance-Entscheide mit Andreas/Finance durchgehen | Offen | | 1 | Italien-Abweichung klaeren: Berechnungsart, Deduplizierung, Intercompany | Offen | +| 1 | Italien IC-Diagnose besprechen: Trafag, Magnetic Sense/Magnets Sense und Gesellschaft fuer Sensorik erklaeren einen grossen Teil, aber nicht die ganze Abweichung | Offen | | 1 | Deutschland: finalen Jahresfile 2025 beschaffen | Offen | | 2 | UK/England: Jahresvollstaendigkeit und Restdifferenz pruefen | Offen | | 2 | CH/AT: Sollzuordnung und Trennung final bestaetigen | Offen | @@ -29,3 +30,29 @@ Ziel im Termin: - Offene Laenderabweichungen priorisieren. - Pro Land festlegen, welche Datenquelle und Berechnungslogik final gilt. +## IT / Intercompany Diagnose + +Aktuelle Diagnose fuer Italien: + +| Kennzahl | Wert | +| --- | ---: | +| IT Ist vor IC-Abzug | 14.704.336,29 EUR | +| Rhino/check.xlsx Soll | 7.669.840,00 EUR | +| Abweichung vor IC | +7.034.496,29 EUR | +| Erkannter IC-/2nd-party-Abzug | 4.397.746,90 EUR | +| IT Ist exkl. IC | 10.306.589,39 EUR | +| Restabweichung nach IC | +2.636.749,39 EUR | + +Verwendete IC-/2nd-party-Marker: + +- `TRAFAG` +- `MAGNETIC SENSE` +- `MAGNETS SENSE` +- `GESELLSCHAFT FUER SENSORIK` +- `GESELLSCHAFT FUR SENSORIK` + +Bewertung: + +- Intercompany/2nd-party erklaert einen grossen Teil der IT-Abweichung. +- Die Summe passt dadurch deutlich besser, aber noch nicht vollstaendig. +- Restabweichung weiter pruefen: Summenlogik, Beleg/Position-Deduplizierung, Gutschriften/Storno und weitere lokale IC-Kunden oder Schreibweisen. diff --git a/TrafagSalesExporter/lastchange.md b/TrafagSalesExporter/lastchange.md index dbf8b2d..dca3725 100644 --- a/TrafagSalesExporter/lastchange.md +++ b/TrafagSalesExporter/lastchange.md @@ -1349,6 +1349,66 @@ Ergebnis: - Build erfolgreich. - 3 bestehende MudBlazor-Analyzer-Warnungen in `Logs.razor`, `Transformations.razor` und `Standorte.razor`. +## CFO-Bericht IT/Intercompany Diagnose 2026-05-15 + +Ergaenzt: + +- `docs/CFO_Kurzbericht_270515.docx` +- `docs/FINANCE_DASHBOARD_TODO_2026-05-15.md` + +Inhalt: + +- IT/Intercompany-Diagnose fuer die grosse Italien-Abweichung. +- Marker dokumentiert: `TRAFAG`, `MAGNETIC SENSE`, `MAGNETS SENSE`, `GESELLSCHAFT FUER SENSORIK`, `GESELLSCHAFT FUR SENSORIK`. +- Zahlen: + - IT Ist vor IC-Abzug: `14.704.336,29 EUR` + - IC-/2nd-party-Abzug: `4.397.746,90 EUR` + - IT Ist exkl. IC: `10.306.589,39 EUR` + - Rhino/check.xlsx Soll: `7.669.840,00 EUR` + - Restabweichung nach IC: `+2.636.749,39 EUR` + +Bewertung: + +- Intercompany/2nd-party erklaert einen grossen Teil der IT-Abweichung. +- Restabweichung bleibt offen und muss ueber Summenlogik, Beleg/Position-Deduplizierung, Gutschriften/Storno und weitere lokale IC-Kunden oder Schreibweisen geprueft werden. + +## HR KPI Testpersonen-Ausschluss 2026-05-15 + +Geaendert: + +- Folgende Testpersonen werden zentral aus dem HR-KPI-Dashboard ausgeschlossen: + - Angelina Jolie + - Brad Pitt + - Peter Muster + - ICT Trafag + - Empfanger Reminder / Empfaenger Reminder +- Der Ausschluss erfolgt vor KPI-, Filter- und Tabellenberechnung. +- Betroffen sind aktive Mitarbeitende, Absenzen und Austritte. +- Im Dashboard erscheint eine Notice, wie viele Testpersonen-Zeilen ausgeschlossen wurden. + +Verifikation: + +```text +dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --no-restore -p:UseAppHost=false -p:OutDir=.\obj\verify_hr_exclusions\ --verbosity minimal +``` + +Ergebnis: + +- 70/70 Tests erfolgreich. +- 3 bestehende MudBlazor-Analyzer-Warnungen in `Logs.razor`, `Transformations.razor` und `Standorte.razor`. + +## KI-Arbeitsanweisung 2026-05-15 + +Erstellt: + +- `persona.md` + +Inhalt: + +- Rolle der KI als Entwicklungs-, Analyse- und Dokumentationswerkzeug. +- Grenzen der KI bei fachlicher Verantwortung, Finance, HR, Datenschutz und Freigaben. +- Arbeitsprinzipien fuer dieses Projekt: bestehende Architektur nutzen, kritisch testen, sauber dokumentieren und offene fachliche Punkte als Pruefpunkte markieren. + ## Navigation in Finance/HR/Admin gegliedert 2026-05-15 Geaendert: diff --git a/TrafagSalesExporter/persona.md b/TrafagSalesExporter/persona.md new file mode 100644 index 0000000..53e5378 --- /dev/null +++ b/TrafagSalesExporter/persona.md @@ -0,0 +1,47 @@ +# KI-Arbeitsanweisung fuer dieses Projekt + +Stand: 2026-05-15 + +## Rolle + +Die KI unterstuetzt in diesem Projekt als Entwicklungs-, Analyse- und Dokumentationswerkzeug. + +Sie hilft bei: + +- Codeaenderungen +- Tests und Fehleranalyse +- Strukturierung von Anforderungen +- Dokumentation und Berichten +- Vorbereitung von Finance-/HR-Entscheiden +- Plausibilisierung von Daten und Formeln + +## Grenzen + +Die KI ersetzt keine fachliche Verantwortung. + +Fachliche Entscheide, Freigaben und Verantwortung bleiben bei den zustaendigen Personen. Das gilt besonders fuer: + +- Finance-Kennzahlen +- HR-Kennzahlen +- Lohndaten +- Personendaten +- Datenschutz und Berechtigungen +- Intercompany-/2nd-party-Abgrenzungen +- offizielle Reporting-Logik + +## Arbeitsprinzipien + +- Bestehenden Programmcode und bestehende Architektur moeglichst wiederverwenden. +- Aenderungen nachvollziehbar dokumentieren. +- Kritische Berechnungen mit Tests absichern. +- Bei Finance- und HR-Zahlen klar zwischen Ist, Soll, Diagnose und offizieller Freigabe unterscheiden. +- Unsichere fachliche Punkte als offene Pruefpunkte markieren, nicht still als Wahrheit behandeln. +- Keine sensiblen Daten unnoetig ausgeben oder duplizieren. +- HR-Bereiche mit separater Zugriffskontrolle behandeln. + +## Verantwortung + +KI kann Umsetzung, Analyse und Dokumentation beschleunigen. + +Die finale fachliche Entscheidung liegt beim Menschen. +