Commit pending finance and Power BI work

This commit is contained in:
2026-05-13 07:33:00 +02:00
parent 1cd0ad998f
commit 001e2a73d5
44 changed files with 3210 additions and 104 deletions
+214
View File
@@ -0,0 +1,214 @@
# Fluktuation Nachdokumentation - 2026-05-12
## Ausgangslage
Die Fluktuationsformeln aus `formeln.docx` sollten in die Power-BI-Logik uebernommen werden.
Fachliche Definition laut HR:
- Zaehler: nur Arbeitnehmerkuendigungen
- Nicht zaehlen: befristete Vertraege, Aushilfen, Pensionierungen und Kuendigungen durch Trafag
- Nenner: durchschnittlicher Headcount, nicht FTE
- Monat: Austritte des Monats / Headcount des Monats
- Quartal: Austritte des Quartals / durchschnittlicher Headcount des Quartals
- Jahreshochrechnung: aktuelle Quartals-Fluktuation x 4
- Effektives Jahr: Austritte des Jahres / durchschnittlicher Headcount des Jahres
## Geaenderte Dateien
### `rexx_ausgeschieden.txt`
Die bestehende Power-Query fuer `C:\temp\Personalausgeschieden.xlsx` wurde erweitert.
Neu bzw. angepasst:
- robuste Umwandlung von `Austrittsdatum` und `Eintrittsdatum`
- Date
- DateTime
- Excel-Seriennummer, z.B. `45396.0`
- Text im Format `dd.MM.yyyy`
- Normalisierung von `Austrittsart`
- Kleinbuchstaben
- Umlaute nach ASCII, z.B. `kuendigung`
- neue fachliche Spalten:
- `Austrittsart_Normalisiert`
- `Ist_Arbeitnehmerkuendigung`
- `Ist_Fluktuation_Ausgeschlossen`
- `Ist_Fluktuationsrelevant`
- `Fluktuation_Ausschlussgrund`
Wichtig: `Kündigung AN` aus Rexx wird jetzt als Arbeitnehmerkuendigung erkannt.
### `fluktuation_measures_dax.txt`
Neues DAX-File fuer die Fluktuations-Measures.
Tabellenreferenzen wurden auf `HR_KPI_DATEN_SAP` gesetzt.
Enthaltene Measures:
- `Headcount Festangestellt`
- `Headcount Aktiv Total`
- `Austritte Total Rexx`
- `Austritte Arbeitnehmerkuendigung`
- `Austritte Fluktuationsrelevant`
- `Austritte Nicht Fluktuationsrelevant`
- `Fluktuation Monat %`
- `Avg Headcount Quartal`
- `Austritte Quartal`
- `Fluktuation Quartal %`
- `Fluktuation Hochrechnung Jahr %`
- `Avg Headcount Jahr`
- `Austritte Jahr`
- `Fluktuation Jahr Effektiv %`
- `Fluktuation Ausschlussgrund Anzahl`
Die Austritts-Measures verwenden `TREATAS` auf `Rexx_Ausgeschieden[Austrittsmonat]`, damit die Filterung ueber `HR_KPI_DATEN_SAP[Periode]` auch ohne direkte Beziehung funktionieren kann.
## Konsolenpruefung der Rexx-Datei
Gepruefte Datei:
```text
C:\temp\Personalausgeschieden.xlsx
```
Gefundene Austritte:
```text
104 total
42 Kuendigung AN
34 Kuendigung AG
15 Befristung
7 leer
5 Ruhestand
1 Aufhebungsvertrag
```
Nach der korrigierten Logik:
```text
33 fluktuationsrelevante Austritte
```
Die Differenz zu 42 `Kuendigung AN` entsteht, weil Aushilfen, Praktikanten, Werkstudenten und Lehrlinge nicht in die Fluktuationsberechnung einfliessen.
## Ursache fuer 0/leere Fluktuation
Die erste Erkennung suchte nach Begriffen wie:
```text
arbeitnehmer
mitarbeiter
eigenkuendigung
kuendigung ma
```
Rexx liefert aber:
```text
Kündigung AN
```
Dadurch war `Ist_Arbeitnehmerkuendigung` ueberall `false`, und die Fluktuations-Measures hatten keinen Zaehler.
## Erwartete Kontrollwerte in Power BI
Nach Aktualisierung der Queries sollten ohne zusaetzliche Filter ungefaehr folgende Werte sichtbar sein:
```text
Austritte Total Rexx = 104
Austritte Arbeitnehmerkuendigung = 42
Austritte Fluktuationsrelevant = 33
```
Wenn `Fluktuation Monat %`, `Fluktuation Quartal %` oder `Fluktuation Jahr Effektiv %` leer bleiben, zuerst diese Punkte pruefen:
- ist `Rexx_Ausgeschieden` geladen?
- heisst die Haupttabelle wirklich `HR_KPI_DATEN_SAP`?
- existieren `HR_KPI_DATEN_SAP[Periode]` und `Rexx_Ausgeschieden[Austrittsmonat]` als Date-Spalten?
- liefert `Headcount Festangestellt` einen Wert groesser 0?
- gibt es aktive Filter auf Jahr, Monat, Organisation oder Kostenstelle?
## Nachtrag: Leere Quartals-/Jahres-Measures
Am 2026-05-12 wurden die DAX-Measures in `fluktuation_measures_dax.txt`
nochmals angepasst, weil folgende Kennzahlen in Power BI leer waren:
- `Austritte Jahr`
- `Austritte Quartal`
- `Fluktuation Hochrechnung Jahr %`
- `Fluktuation Quartal %`
- `BU_Tage_Total`
Wahrscheinliche Ursache:
`HR_KPI_DATEN_SAP[Periode]` wird in `hr_kpi_daten_query.txt` aktuell als
aktueller Monat aus `DateTime.LocalNow()` erzeugt. Dadurch enthalten die
Perioden in der Haupttabelle nicht zwingend dieselben Monate wie
`Rexx_Ausgeschieden[Austrittsmonat]`. Die bisherigen `DATESQTD`- und
`DATESYTD`-Measures konnten deshalb keine passenden Austritte finden und
lieferten leere Werte.
Anpassung in `fluktuation_measures_dax.txt`:
- `Austritte Quartal` rechnet jetzt ueber Quartalsstart und Quartalsende.
- `Austritte Jahr` filtert jetzt ueber das Jahr von `Austrittsmonat`.
- Prozent-Measures sind mit `COALESCE(..., 0)` gegen leere Werte abgesichert.
- Basis-Measures fuer Headcount und Austritte geben ebenfalls `0` statt leer zurueck.
- `BU_Tage_Total`, `NBU_Tage_Total` und `Unfalltage Total` wurden ergaenzt.
Wichtig:
Die `.pbix` wurde weiterhin nicht direkt bearbeitet. Die geaenderten Measures
muessen in Power BI Desktop manuell ersetzt bzw. eingefuegt werden. Falls die
Haupttabelle im Modell nicht `HR_KPI_DATEN_SAP`, sondern z.B. `HR_KPI_Daten`
heisst, muss der Tabellenname in den DAX-Measures entsprechend angepasst werden.
## Power-BI-Datei / PBIX
Die `.pbix`-Datei wurde nicht direkt bearbeitet.
Grund:
- `.pbix` ist kein normales Textprojekt.
- Power-Query-Code und DAX-Measures liegen intern in Power-BI-Modellstrukturen.
- Direktes Bearbeiten kann die Datei beschaedigen.
- Ohne Power BI Desktop, Tabular Editor oder ein `.pbip`-Projekt ist das direkte Patchen riskant und unverhaeltnismaessig.
Empfohlener Weg fuer diese Aenderung:
1. Power BI Desktop oeffnen.
2. Query `Rexx_Ausgeschieden` im Power Query Editor oeffnen.
3. Inhalt durch den aktuellen Code aus `rexx_ausgeschieden.txt` ersetzen.
4. Modell aktualisieren.
5. Nur die geaenderten bzw. benoetigten DAX-Measures aus `fluktuation_measures_dax.txt` ersetzen/einfuegen.
Nicht alle DAX-Measures muessen neu kopiert werden. Zwingend relevant sind vor allem:
- `Headcount Festangestellt`
- `Austritte Fluktuationsrelevant`
- `Avg Headcount Quartal`
- `Austritte Quartal`
- `Avg Headcount Jahr`
- `Austritte Jahr`
Optional als Diagnose:
- `Headcount Aktiv Total`
- `Austritte Total Rexx`
- `Austritte Arbeitnehmerkuendigung`
Falls das Projekt spaeter als `.pbip` statt `.pbix` gespeichert wird, koennen Modell-/Query-Dateien deutlich besser versioniert und direkt angepasst werden.
## Nicht geaenderte Dateien
Nicht angepasst wurden:
- `hr_kpi_daten_query.txt`
- `REXX_aBSENZEN.txt`
- `formeln.docx`
- `HANDOFF_2026-05-11.md`
- `HR_KPI_Formeln_CH.xlsx`
- `infos.txt`
- `infos2.txt`
+440
View File
@@ -0,0 +1,440 @@
# Handoff Power BI HR KPI - 2026-05-11
## Kontext
Ziel ist ein Power-BI-Dashboard fuer HR-KPIs Schweiz. HR soll das Dashboard konsumieren, nicht selbst bauen. Die Daten sollen aus SAP und mehreren Rexx-Exports in Power BI zusammengefuehrt werden.
Der aktuelle Fokus ist Phase 1: Zeit und Absenzen, insbesondere Krankheit, Unfall, Ferien, GLZ/Saldo und Vergleich der Quoten mit SAP.
## Dateien im Projektordner
Arbeitsordner:
```text
C:\Users\koi\source\repos\Ai\Powerbi
```
Vorhandene Dateien:
```text
HR_KPI_Formeln_CH.xlsx
infos.txt
infos2.txt
```
`infos.txt` und `infos2.txt` wurden gelesen. Sie enthalten den bisherigen Chat-/Projektverlauf zum HR-KPI-Dashboard.
## Relevante Exportdateien in C:\temp
In `C:\temp` wurden relevante SAP- und Rexx-Dateien gefunden:
```text
C:\temp\HR_KPI_EXPORT.xlsx
C:\temp\Abwesenheitinstunden.xlsx
C:\temp\Exportkommengehen.xlsx
C:\temp\Saldiperstichdatum.xlsx
C:\temp\Saldistundenferien.xlsx
C:\temp\Personalausgeschieden.xlsx
```
Wichtige Beobachtung: `HR_KPI_EXPORT.xlsx` wirkt wie ein SAP-Live-Export. Die ersten gelesenen Daten waren fuer `Geschäftsjahr = 2026` und `Buchungsperiode = 4`, also April 2026. Fuer den von HR gewuenschten Test Q1/2026 muessen vermutlich SAP-Daten fuer Perioden 1, 2 und 3 exportiert bzw. bereitgestellt werden.
## Vorhandene Formelmappe
Datei:
```text
C:\Users\koi\source\repos\Ai\Powerbi\HR_KPI_Formeln_CH.xlsx
```
Sheets:
```text
KPI_Formeln
Beispielrechnung
PowerBI_Mapping
```
Diese Datei ist aktuell eine Formel-/Beispielmappe, keine Live-Datendatei.
### Inhalt KPI_Formeln
Enthaelt mathematische Definitionen fuer:
- Krankheitstage je Mitarbeitenden
- Krankheitstage gesamt
- Krankheitsquote %
- Unfallquote %
- Gesundheitsbedingte Absenzen %
- Soll-Arbeitszeit %
- FTE
- allgemeine Prozentanteile
Wichtige Formelidee:
```text
Krankheitstage je Mitarbeitenden = Krankheitsstunden / persoenliche Sollzeit pro Tag
Krankheitsquote % = Summe Krankheitsstunden / Summe Soll-Arbeitsstunden * 100
Unfallquote % = (BU-Stunden + NBU-Stunden) / Summe Soll-Arbeitsstunden * 100
Gesundheitsbedingte Absenzen % = (Krankheit + BU + NBU) / Sollstunden * 100
```
### Inhalt Beispielrechnung
Enthaelt Beispielwerte fuer drei Mitarbeitende:
- Vollzeit
- Teilzeit 50%
- 80%
Diese Beispielrechnung dient zum Validieren der Formellogik.
### Inhalt PowerBI_Mapping
Enthaelt Mapping von KPI auf Power-BI-Felder/Measures, z.B.:
- `Rexx_Absenzen[Krankheit_Gesamt_Std]`
- `M_Krankheitstage_Gesamt`
- `M_Gesundheitsbedingte_Absenzen_Prozent`
- `HR_KPI_Daten[FTE]`
## Architekturentscheidung
SAP und Rexx werden nicht in SAP zusammengefuehrt.
Stattdessen:
```text
SAP CSV/XLSX + mehrere Rexx XLSX/CSV + manuelle Excel-Dateien
werden in Power BI ueber Personalnummer/PERNR zusammengefuehrt.
```
### SAP-Report
Der SAP-Report `Z_HR_KPI_CONSOLIDATE` liefert nur SAP-Daten:
- Stammdaten
- Organisation/Kostenstelle
- Beschaeftigungsgrad/FTE
- Ein-/Austritt
- Lohn
- SAP-Abwesenheiten
- Stellenplan
- CSV-/Excel-Export fuer Power BI
Rexx-Daten werden nicht aus SAP extrahiert. Rexx-Felder im SAP-Report sind nur Platzhalter bzw. sollten langfristig eher separat aus Rexx kommen.
### Rexx
Rexx kann nicht alles in einem Export liefern, da pro Export maximal ca. 40 Felder moeglich sind. Daher werden thematische Exports benoetigt:
- Abwesenheiten/Krankheit
- Ferien
- GLZ/Salden/Ueberstunden
- Austritte/Fluktuation
- Pulsumfrage/Zufriedenheit
- ggf. weitere Themen
## Gelesene Spalten aus den Live-/Exportdateien
### C:\temp\HR_KPI_EXPORT.xlsx
Sheet:
```text
Data
```
Dimension:
```text
A1:AK1139
```
Relevante Spalten aus Zeile 1:
```text
Personalnummer
Geschäftsjahr
Buchungsperiode
Buchungskreis
Personalbereich
Personalteilbereich
Kostenstelle
Organisationseinheit
Planstelle
Stellenschlüssel
Mitarbeiterkreis
Abrechnungskreis
Teilzeitkraft
Mitarbeitergruppe
Beschäftigungsgrad %
Vorname
Nachname
Geschlecht
Geburtsdatum
Datum
Datum
Bruttolohn Monat
Krankheitstage gesamt
Krankheit < 60 Tage
Krankheit >= 60 Tage (LZK)
Nichtberufsunfall Tage
Berufsunfall Tage
Soll-Stelle vorhanden
Pulsumfrage Score (Rexx)
MA-Zufriedenheit (Rexx)
Kununu Score
Angelegt am
Uhrzeit
Angelegt von
```
Beispiel aus den ersten Datenzeilen:
```text
Personalnummer 2005, Geschäftsjahr 2026, Buchungsperiode 4
Personalnummer 2010, Geschäftsjahr 2026, Buchungsperiode 4
Personalnummer 2012, Geschäftsjahr 2026, Buchungsperiode 4
```
Hinweis: Fuer Q1/2026 brauchen wir Perioden 1, 2, 3 oder einen Q1-Gesamtexport.
### C:\temp\Abwesenheitinstunden.xlsx
Sheet:
```text
Abwesenheit in Stunden
```
Dimension:
```text
A1:Z253
```
Spalten:
```text
Personalnummer
Foto rund
Nachname, Vorname (Link Personal)
Stelle
Organisation
Leitung j/n
Eintrittsdatum
Personal Status
Krank nicht buchbar angetreten (Stunden Ind.)
Krankheit angetreten (Stunden Ind.)
Krank nicht buchbar angetreten (Stunden)
Krankheit angetreten (Stunden)
Krank nicht buchbar angetreten (Zeitraum)
Krankheit angetreten (Zeitraum)
Krank nicht buchbar ausstehend (Stunden Ind.)
Krankheit ausstehend (Stunden Ind.)
Krank nicht buchbar gebucht gesamt (Stunden)
Krankheit genehmigt (Zeitraum)
Krank nicht buchbar genehmigt (Zeitraum)
Krankheit genehmigt (Stunden)
Krank nicht buchbar genehmigt (Stunden)
Krankheit genehmigt (Stunden Ind.)
Krank nicht buchbar genehmigt (Stunden Ind.)
Krankheit gebucht gesamt (Stunden)
Krankheit gebucht gesamt (Stunden Ind.)
Krank nicht buchbar gebucht gesamt (Stunden Ind.)
```
Wichtig: Rexx liefert Krankheit in Stunden. Laut Projektentscheidung ist Rexx fuer Krankheit/Absenzen fuehrend, weil SAP nur Tage liefert.
### C:\temp\Exportkommengehen.xlsx
Sheet:
```text
Export KOMMENGEHEN
```
Dimension:
```text
A1:O253
```
Spalten:
```text
Sozialversicherungsnummer
Nachname, Vorname (Link Personal)
Geburtsdatum
Personal Status
Stelle
Organisation
Arbeitszeitmodell
Arbeitszeit
Ø tägliche Sollarbeitszeit (Woche)
Arbeitszeit Mo.
Arbeitszeit Di.
Arbeitszeit Mi.
Arbeitszeit Do.
Arbeitszeit Fr.
Arbeitszeit Sa.
```
Wichtig: Dieser Export enthaelt keine Personalnummer, sondern Sozialversicherungsnummer und Namen. Fuer stabile Power-BI-Joins ist Personalnummer besser. Falls moeglich, Rexx-Export um `Personalnummer` ergaenzen.
### C:\temp\Saldiperstichdatum.xlsx
Sheet:
```text
Saldi per Stichdatum
```
Dimension:
```text
A1:Q253
```
Spalten:
```text
Personalnummer
Kürzel
Nachname, Vorname (Link Personal)
Stelle
Organisation
Kostenstelle
Leitung j/n
Eintrittsdatum
Personal Status
Anstellungsverhältnis
Stunden Saldo
Urlaubsanspruch
Urlaub Rest
Ferien ausstehend (Tage)
Lohnart
Lohn
Lohn Währung
```
Dieser Export ist wichtig fuer:
- GLZ/Stunden Saldo
- Ferienanspruch
- Urlaub Rest
- Ferien ausstehend
- ggf. Lohn aus Rexx, wobei SAP fuer Lohn fuehrend/sensibler sein sollte
## Fachliche Entscheidungen aus den TXT-Dateien
- HR soll das Dashboard konsumieren, nicht bauen.
- Phase 1 ist Zeit und Absenzen.
- Rexx ist fuer Krankheit/Absenzen fuehrend.
- SAP liefert ergaenzende Basisdaten, die Rexx nicht sauber oder nicht vollstaendig liefert.
- Kununu wird manuell gepflegt.
- Refline/Time-to-hire ist spaeteres Thema.
- Rexx Scheduled Reports wurden gesucht, aber offenbar nicht gefunden/freigeschaltet.
- Manuelles Ablegen der Exports in einem Ordner ist fuer den Start akzeptabel.
## Welche Daten nur aus SAP kommen
Diese Felder sind wichtig und kommen eher aus SAP als aus Rexx:
- Geschlecht
- Beschaeftigungsgrad %
- FTE-Grundlage
- Mitarbeitergruppe/Mitarbeiterkreis
- Planstelle/Stellenschluessel
- Soll-Stelle vorhanden
- Bruttolohn Monat
- SAP-Abwesenheiten fuer Vergleich
- BU-/NBU-Tage, falls nicht in Rexx als Stunden vorhanden
## Welche Daten aus Rexx kommen
Rexx ist wichtig fuer:
- Krankheit in Stunden
- Krankheit genehmigt/gebucht/angetreten/ausstehend
- GLZ/Stunden Saldo
- Ferienanspruch
- Urlaub Rest
- Ferien ausstehend
- Organisation/Kostenstelle aus Rexx fuer operative Sicht
- ggf. Pulsumfrage/Zufriedenheit
## Q1/2026-Test mit Live-Daten
HR-Anfrage:
> Waere es moeglich, dass wir mit den Live Daten das Q1/2026 testen koennten? Dann koennten wir die Quoten mit jenen aus dem SAP vergleichen um zu pruefen, ob wir die identischen Formeln haben.
Interpretation:
Es soll mit Live-Daten fuer Q1/2026 gerechnet werden:
```text
Januar 2026
Februar 2026
Maerz 2026
```
Dann sollen die berechneten Quoten gegen SAP verglichen werden.
### Noetige Daten fuer Q1/2026
Aus SAP:
- Export fuer Perioden 1, 2, 3 im Jahr 2026
- oder ein Export, der Q1 kumuliert enthaelt
Aus Rexx:
- Abwesenheit/Krankheit mit Zeitraum Q1/2026
- Sollzeit pro Mitarbeitenden fuer Q1/2026
- optional BU/NBU, falls Rexx das liefert
- GLZ/Ferien nur falls fuer den Q1-Vergleich benoetigt
### Kritischer Punkt
Die aktuell gefundene SAP-Datei `C:\temp\HR_KPI_EXPORT.xlsx` zeigt in den ersten Datenzeilen Periode 4/2026. Das reicht nicht fuer Q1/2026.
Naechster sinnvoller Schritt:
1. SAP-Report fuer Q1/2026 laufen lassen, also Perioden 1 bis 3 oder Q1-Gesamt.
2. Rexx-Abwesenheitsexport ebenfalls fuer Q1/2026 ziehen.
3. Power-BI-/Excel-Testdatei erstellen, die beide Quellen ueber Personalnummer verbindet.
4. Quoten berechnen:
- Krankheitsquote %
- Unfallquote %
- Gesundheitsbedingte Absenzen %
- Krankheitstage gesamt
5. Ergebnis mit SAP-Quote vergleichen.
## Technische Hinweise aus dieser Session
Excel-Dateien wurden nicht ueber Excel-GUI bearbeitet, sondern als XLSX-Zip/XML gelesen. Das vermeidet Dateisperren.
Ein Versuch mit Excel-COM hing und wurde abgebrochen. Danach wurde ein unsichtbarer Excel-Prozess geprueft. Beim Beenden war der Prozess bereits nicht mehr vorhanden. Es wurden keine Excel-Dateien gespeichert oder veraendert.
In dieser Session wurde bis zur Erstellung dieser Handoff-Datei keine vorhandene Excel-Datei manipuliert.
## Empfohlene Antwort an HR
Vorschlag:
```text
Ja, das koennen wir machen. Fuer den Q1/2026-Test brauche ich die Live-Exports fuer Januar bis Maerz 2026 bzw. einen Q1-Gesamtexport aus SAP sowie den passenden Rexx-Abwesenheitsexport fuer denselben Zeitraum. Dann berechne ich die Quoten mit unserer Formel und stelle sie den SAP-Werten gegenueber, damit wir Abweichungen in Definition oder Zeitraum sofort sehen.
```
## Offene Punkte
- SAP-Q1/2026-Datei fehlt noch oder wurde noch nicht gefunden.
- Rexx-Q1/2026-Abwesenheitsexport muss bereitgestellt werden.
- Klaeren, ob `Exportkommengehen.xlsx` kuenftig Personalnummer enthalten kann. Ohne Personalnummer ist der Join unsauber.
- Klaeren, ob BU/NBU in Rexx als Stunden verfuegbar ist oder fuer Unfallquote weiter aus SAP-Tagen umgerechnet werden muss.
- Definieren, ob Q1-Quote mit Sollstunden aus Kalender/SAP oder Rexx-Sollarbeitszeit gerechnet wird.
- Sicherstellen, dass alle Dateien denselben Zeitraum abdecken.
+85
View File
@@ -0,0 +1,85 @@
// ============================================================
// QUERY 2: Rexx_Absenzen
// ============================================================
// Quelle: Abwesenheitinstunden.xlsx (Rexx #744)
// Krankheitsstunden Detail → fuehrend statt SAP-Tage
// ============================================================
let
Source = Excel.Workbook(File.Contents("C:\temp\Abwesenheitinstunden.xlsx"), null, true),
Data = Source{0}[Data],
Head = Table.PromoteHeaders(Data, [PromoteAllScalars = true]),
Sel = Table.SelectColumns(Head, {
"Personalnummer",
"Nachname, Vorname (Link Personal)",
"Stelle", "Organisation", "Leitung j/n", "Personal Status",
"Krankheit angetreten (Stunden Ind.)",
"Krank nicht buchbar angetreten (Stunden Ind.)",
"Krankheit angetreten (Zeitraum)",
"Krank nicht buchbar angetreten (Zeitraum)",
"Krankheit genehmigt (Stunden Ind.)",
"Krank nicht buchbar genehmigt (Stunden Ind.)",
"Krankheit gebucht gesamt (Stunden Ind.)",
"Krank nicht buchbar gebucht gesamt (Stunden Ind.)"
}),
Ren = Table.RenameColumns(Sel, {
{"Personalnummer", "PERNR_Text"},
{"Nachname, Vorname (Link Personal)", "Name"},
{"Stelle", "Stelle"},
{"Organisation", "Organisation"},
{"Leitung j/n", "Leitung"},
{"Personal Status", "Status"},
{"Krankheit angetreten (Stunden Ind.)", "Krankheit_Kurz_Std"},
{"Krank nicht buchbar angetreten (Stunden Ind.)", "Krankheit_Lang_Std"},
{"Krankheit angetreten (Zeitraum)", "Krankheit_Kurz_Zeitraum"},
{"Krank nicht buchbar angetreten (Zeitraum)", "Krankheit_Lang_Zeitraum"},
{"Krankheit genehmigt (Stunden Ind.)", "Krankheit_Kurz_Genehmigt"},
{"Krank nicht buchbar genehmigt (Stunden Ind.)", "Krankheit_Lang_Genehmigt"},
{"Krankheit gebucht gesamt (Stunden Ind.)", "Krankheit_Kurz_Gebucht"},
{"Krank nicht buchbar gebucht gesamt (Stunden Ind.)", "Krankheit_Lang_Gebucht"}
}),
AddPernr = Table.AddColumn(Ren, "Personalnummer", each
try Number.FromText(Text.Trim(Text.From([PERNR_Text]))) otherwise null, Int64.Type),
SetTypes = Table.TransformColumnTypes(AddPernr, {
{"Krankheit_Kurz_Std", type number},
{"Krankheit_Lang_Std", type number},
{"Krankheit_Kurz_Genehmigt", type number},
{"Krankheit_Lang_Genehmigt", type number},
{"Krankheit_Kurz_Gebucht", type number},
{"Krankheit_Lang_Gebucht", type number}
}),
// Gesamt-Krankheitsstunden
AddGes = Table.AddColumn(SetTypes, "Krankheit_Gesamt_Std", each
(if [Krankheit_Kurz_Std] = null then 0 else [Krankheit_Kurz_Std]) +
(if [Krankheit_Lang_Std] = null then 0 else [Krankheit_Lang_Std]),
type number),
// Krankheitstage (Stunden / 8.4h pro Tag)
AddKTG = Table.AddColumn(AddGes, "Krankheitstage_Gesamt", each
Number.Round([Krankheit_Gesamt_Std] / 8.4, 1), type number),
AddKTK = Table.AddColumn(AddKTG, "Krankheitstage_Kurz", each
let s = if [Krankheit_Kurz_Std] = null then 0 else [Krankheit_Kurz_Std] in
Number.Round(s / 8.4, 1), type number),
AddKTL = Table.AddColumn(AddKTK, "Krankheitstage_Lang", each
let s = if [Krankheit_Lang_Std] = null then 0 else [Krankheit_Lang_Std] in
Number.Round(s / 8.4, 1), type number),
// Krankenquote (kompatibel, Basis 21 Tage)
AddKQ = Table.AddColumn(AddKTL, "Krankenquote_MA", each
if [Krankheitstage_Gesamt] = 0 then 0 else [Krankheitstage_Gesamt] / 21, type number),
AddKQO = Table.AddColumn(AddKQ, "Krankenquote_ohne_LZK", each
if [Krankheitstage_Kurz] = 0 then 0 else [Krankheitstage_Kurz] / 21, type number),
AddAbs = Table.AddColumn(AddKQO, "Absenztage_Total", each [Krankheitstage_Gesamt], type number),
Filter = Table.SelectRows(AddAbs, each [Status] = "Aktiv"),
Clean = Table.RemoveColumns(Filter, {"PERNR_Text"}),
Reorder = Table.ReorderColumns(Clean, {"Personalnummer"} &
List.RemoveItems(Table.ColumnNames(Clean), {"Personalnummer"}))
in
Reorder
+5
View File
@@ -0,0 +1,5 @@
Datenbeschaffung:
SAP Daten kommen von Programm Z_HR_KPI_CONS
Rexx Daten aus Rexx export mit Exportprofil SAP_EXPORT(#711) exporieren nach Excel erstell sapexport.xlss
+198
View File
@@ -0,0 +1,198 @@
// ============================================================
// DAX MEASURES: Fluktuation gemaess formeln.docx
// ============================================================
// Voraussetzung:
// - Query/Tabelle: HR_KPI_DATEN_SAP mit aktiven Mitarbeitenden
// - Query/Tabelle: Rexx_Ausgeschieden aus rexx_ausgeschieden.txt
// - Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] filtert:
// nur Arbeitnehmerkuendigungen, keine Aushilfen/Praktikanten/
// Werkstudenten/Lehrlinge, keine Pensionierungen, keine befristeten
// Vertraege, keine Kuendigungen durch Trafag.
//
// Hinweis:
// HR_KPI_DATEN_SAP ist aktuell eine Stichtags-/Monatstabelle. Falls spaeter
// echte Monats-Snapshots geladen werden, funktionieren die Durchschnitts-
// Headcount-Measures ueber Monat/Quartal/Jahr genauer.
// ============================================================
Headcount Festangestellt =
COALESCE(
CALCULATE(
DISTINCTCOUNT(HR_KPI_DATEN_SAP[Personalnummer]),
HR_KPI_DATEN_SAP[Ist_Aktiv] = TRUE(),
HR_KPI_DATEN_SAP[Mitarbeitertyp] = "Festangestellt"
),
0
)
Headcount Aktiv Total =
COALESCE(
CALCULATE(
DISTINCTCOUNT(HR_KPI_DATEN_SAP[Personalnummer]),
HR_KPI_DATEN_SAP[Ist_Aktiv] = TRUE()
),
0
)
Austritte Total Rexx =
COALESCE(DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]), 0)
Austritte Arbeitnehmerkuendigung =
COALESCE(
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Arbeitnehmerkuendigung] = TRUE()
),
0
)
Austritte Fluktuationsrelevant =
VAR Perioden = VALUES(HR_KPI_DATEN_SAP[Periode])
VAR Basis =
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] = TRUE()
)
VAR NachPeriode =
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] = TRUE(),
TREATAS(Perioden, Rexx_Ausgeschieden[Austrittsmonat])
)
RETURN
COALESCE(
IF(
ISFILTERED(HR_KPI_DATEN_SAP[Periode]) || ISINSCOPE(HR_KPI_DATEN_SAP[Periode]),
NachPeriode,
Basis
),
0
)
Austritte Nicht Fluktuationsrelevant =
COALESCE(
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] = FALSE()
),
0
)
Fluktuation Monat % =
COALESCE(
DIVIDE(
[Austritte Fluktuationsrelevant],
[Headcount Festangestellt]
),
0
)
Avg Headcount Quartal =
COALESCE(
AVERAGEX(
VALUES(HR_KPI_DATEN_SAP[Periode]),
[Headcount Festangestellt]
),
[Headcount Festangestellt],
0
)
Austritte Quartal =
VAR HatPeriodenfilter =
ISFILTERED(HR_KPI_DATEN_SAP[Periode]) ||
ISFILTERED(HR_KPI_DATEN_SAP[Jahr]) ||
ISFILTERED(HR_KPI_DATEN_SAP[Monat])
VAR Auswertungsdatum =
IF(
HatPeriodenfilter,
MAX(HR_KPI_DATEN_SAP[Periode]),
MAX(Rexx_Ausgeschieden[Austrittsmonat])
)
VAR QuartalsStart =
DATE(
YEAR(Auswertungsdatum),
1 + 3 * QUOTIENT(MONTH(Auswertungsdatum) - 1, 3),
1
)
VAR QuartalsEnde = EOMONTH(QuartalsStart, 2)
RETURN
COALESCE(
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] = TRUE(),
FILTER(
ALL(Rexx_Ausgeschieden[Austrittsmonat]),
Rexx_Ausgeschieden[Austrittsmonat] >= QuartalsStart &&
Rexx_Ausgeschieden[Austrittsmonat] <= QuartalsEnde
)
),
0
)
Fluktuation Quartal % =
COALESCE(
DIVIDE(
[Austritte Quartal],
[Avg Headcount Quartal]
),
0
)
Fluktuation Hochrechnung Jahr % =
COALESCE([Fluktuation Quartal %] * 4, 0)
Avg Headcount Jahr =
COALESCE(
AVERAGEX(
VALUES(HR_KPI_DATEN_SAP[Periode]),
[Headcount Festangestellt]
),
[Headcount Festangestellt],
0
)
Austritte Jahr =
VAR HatPeriodenfilter =
ISFILTERED(HR_KPI_DATEN_SAP[Periode]) ||
ISFILTERED(HR_KPI_DATEN_SAP[Jahr]) ||
ISFILTERED(HR_KPI_DATEN_SAP[Monat])
VAR Auswertungsdatum =
IF(
HatPeriodenfilter,
MAX(HR_KPI_DATEN_SAP[Periode]),
MAX(Rexx_Ausgeschieden[Austrittsmonat])
)
VAR Auswertungsjahr = YEAR(Auswertungsdatum)
RETURN
COALESCE(
CALCULATE(
DISTINCTCOUNT(Rexx_Ausgeschieden[Personalnummer]),
Rexx_Ausgeschieden[Ist_Fluktuationsrelevant] = TRUE(),
FILTER(
ALL(Rexx_Ausgeschieden[Austrittsmonat]),
YEAR(Rexx_Ausgeschieden[Austrittsmonat]) = Auswertungsjahr
)
),
0
)
Fluktuation Jahr Effektiv % =
COALESCE(
DIVIDE(
[Austritte Jahr],
[Avg Headcount Jahr]
),
0
)
BU_Tage_Total =
COALESCE(SUM(HR_KPI_DATEN_SAP[BU_Tage]), 0)
NBU_Tage_Total =
COALESCE(SUM(HR_KPI_DATEN_SAP[NBU_Tage]), 0)
Unfalltage Total =
[BU_Tage_Total] + [NBU_Tage_Total]
Fluktuation Ausschlussgrund Anzahl =
COUNTROWS(Rexx_Ausgeschieden)
Binary file not shown.
+304
View File
@@ -0,0 +1,304 @@
let
// ===== REXX #757 LADEN =====
Src757 = Excel.Workbook(File.Contents("C:\temp\Saldiperstichdatum.xlsx"), null, true),
Data757 = Src757{0}[Data],
Head757 = Table.PromoteHeaders(Data757, [PromoteAllScalars = true]),
Ren757 = Table.RenameColumns(Head757, {
{"Personalnummer", "Personalnummer"},
{"Kürzel", "Kuerzel"},
{"Nachname, Vorname (Link Personal)", "Name_Rexx"},
{"Stelle", "Stelle_Rexx"},
{"Organisation", "Organisation_Text"},
{"Kostenstelle", "Kostenstelle_Rexx"},
{"Leitung j/n", "Leitung"},
{"Eintrittsdatum", "Eintrittsdatum_Raw"},
{"Personal Status", "Personal_Status"},
{"Anstellungsverhältnis", "Anstellungsverhaeltnis"},
{"Stunden Saldo", "Stunden_Saldo_Raw"},
{"Urlaubsanspruch", "Urlaubsanspruch_Raw"},
{"Urlaub Rest", "Urlaub_Rest_Raw"},
{"Ferien ausstehend (Tage)", "Ferien_Ausstehend_Raw"},
{"Lohnart", "Lohnart"},
{"Lohn", "Lohn_Raw"},
{"Lohn Währung", "Lohn_Waehrung"}
}),
TypePernr = Table.TransformColumnTypes(Ren757, {{"Personalnummer", Int64.Type}}),
AddKey = Table.AddColumn(TypePernr, "PERNR_Key", each Text.From([Personalnummer]), type text),
// Name_Voll früh erzeugen für Name-Join mit #732
AddNameVollEarly = Table.AddColumn(AddKey, "Name_Voll", each
Text.From(if [Name_Rexx] = null then "" else [Name_Rexx]), type text),
// ===== REXX #732 LADEN (Geburtsdatum + AZ-Modell) =====
// Hat keine Personalnummer → Join über Name
Tbl732 = try
let
S = Excel.Workbook(File.Contents("C:\temp\Exportkommengehen.xlsx"), null, true),
D = S{0}[Data],
H = Table.PromoteHeaders(D, [PromoteAllScalars = true]),
Sel = Table.SelectColumns(H, {
"Nachname, Vorname (Link Personal)",
"Geburtsdatum",
"Arbeitszeitmodell",
"Ø tägliche Sollarbeitszeit (Woche)"
}, MissingField.UseNull),
Ren = Table.RenameColumns(Sel, {
{"Nachname, Vorname (Link Personal)", "Name_732"},
{"Geburtsdatum", "Geburtsdatum_Raw"},
{"Arbeitszeitmodell", "Arbeitszeitmodell"},
{"Ø tägliche Sollarbeitszeit (Woche)", "Avg_Sollzeit_Tag_Raw"}
})
in Ren
otherwise null,
// Merge #757 + #732 ueber Name
Merged732 = if Tbl732 <> null then
let
M = Table.NestedJoin(AddNameVollEarly, {"Name_Voll"}, Tbl732, {"Name_732"}, "R732", JoinKind.LeftOuter),
E = Table.ExpandTableColumn(M, "R732", {"Geburtsdatum_Raw", "Arbeitszeitmodell", "Avg_Sollzeit_Tag_Raw"})
in E
else
let
A1 = Table.AddColumn(AddNameVollEarly, "Geburtsdatum_Raw", each null),
A2 = Table.AddColumn(A1, "Arbeitszeitmodell", each null, type text),
A3 = Table.AddColumn(A2, "Avg_Sollzeit_Tag_Raw", each null)
in A3,
// ===== SAP LADEN (HR_KPI_Export.xlsx) =====
TblSAP = try
let
S = Excel.Workbook(File.Contents("C:\temp\HR_KPI_Export.xlsx"), null, true),
D = S{0}[Data],
H = Table.PromoteHeaders(D, [PromoteAllScalars = true]),
Sel = Table.SelectColumns(H, {
"Personalnummer", "Buchungskreis", "Personalbereich", "Personalteilbereich",
"Mitarbeitergruppe", "Mitarbeiterkreis", "Teilzeitkraft",
"Beschäftigungsgrad %", "Geschlecht", "Planstelle", "Stellenschlüssel",
"Nichtberufsunfall Tage", "Berufsunfall Tage",
"Abrechnungskreis"
}, MissingField.UseNull),
Ren = Table.RenameColumns(Sel, {
{"Personalnummer", "PERNR_SAP"},
{"Teilzeitkraft", "Teilzeitkennzeichen"},
{"Beschäftigungsgrad %", "Beschaeftigungsgrad_Prozent"},
{"Stellenschlüssel", "Soll_Stelle"},
{"Nichtberufsunfall Tage", "NBU_Tage"},
{"Berufsunfall Tage", "BU_Tage"}
}, MissingField.Ignore),
// PERNR-Key normalisiert (entfernt fuehrende Nullen)
AddK = Table.AddColumn(Ren, "PERNR_SAP_Key", each
try Text.From(Number.FromText(Text.Trim(Text.From([PERNR_SAP])))) otherwise null,
type text)
in AddK
otherwise null,
// Merge + SAP
MergedSAP = if TblSAP <> null then
let
M = Table.NestedJoin(Merged732, {"PERNR_Key"}, TblSAP, {"PERNR_SAP_Key"}, "SAP", JoinKind.LeftOuter),
E = Table.ExpandTableColumn(M, "SAP", {
"Buchungskreis", "Personalbereich", "Personalteilbereich",
"Mitarbeitergruppe", "Mitarbeiterkreis", "Teilzeitkennzeichen",
"Beschaeftigungsgrad_Prozent", "Geschlecht", "Planstelle", "Soll_Stelle",
"NBU_Tage", "BU_Tage",
"Abrechnungskreis"
})
in E
else
let
A1 = Table.AddColumn(Merged732, "Buchungskreis", each null),
A2 = Table.AddColumn(A1, "Personalbereich", each null),
A3 = Table.AddColumn(A2, "Personalteilbereich", each null),
A4 = Table.AddColumn(A3, "Mitarbeitergruppe", each null),
A5 = Table.AddColumn(A4, "Mitarbeiterkreis", each null),
A6 = Table.AddColumn(A5, "Teilzeitkennzeichen", each null, type text),
A7 = Table.AddColumn(A6, "Beschaeftigungsgrad_Prozent", each null),
A8 = Table.AddColumn(A7, "Geschlecht", each null),
A9 = Table.AddColumn(A8, "Planstelle", each null),
A10 = Table.AddColumn(A9, "Soll_Stelle", each null, type text),
A11 = Table.AddColumn(A10, "NBU_Tage", each null),
A12 = Table.AddColumn(A11, "BU_Tage", each null),
A13 = Table.AddColumn(A12, "Abrechnungskreis", each null, type text)
in A13,
// ===== BERECHNETE SPALTEN =====
// Eintrittsdatum
AddEintritt = Table.AddColumn(MergedSAP, "Eintrittsdatum", each
let raw = [Eintrittsdatum_Raw] in
if raw = null then null
else if raw is date then raw
else if raw is datetime then Date.From(raw)
else try Date.FromText(Text.From(raw), [Format = "dd.MM.yyyy"]) otherwise null,
type date),
// Geburtsdatum
AddGebdat = Table.AddColumn(AddEintritt, "Geburtsdatum", each
let raw = [Geburtsdatum_Raw] in
if raw = null then null
else if raw is date then raw
else if raw is datetime then Date.From(raw)
else try Date.FromText(Text.From(raw), [Format = "dd.MM.yyyy"]) otherwise null,
type date),
// Numerische Konvertierungen
AddNum = Table.TransformColumnTypes(AddGebdat, {
{"Urlaubsanspruch_Raw", type number},
{"Urlaub_Rest_Raw", type number},
{"Ferien_Ausstehend_Raw", type number},
{"Lohn_Raw", type number},
{"Avg_Sollzeit_Tag_Raw", type number},
{"Beschaeftigungsgrad_Prozent", type number},
{"Geschlecht", Int64.Type},
{"NBU_Tage", type number},
{"BU_Tage", type number}
}),
RenNum = Table.RenameColumns(AddNum, {
{"Urlaubsanspruch_Raw", "Urlaubsanspruch"},
{"Urlaub_Rest_Raw", "Urlaub_Rest"},
{"Ferien_Ausstehend_Raw", "Ferien_Ausstehend"},
{"Lohn_Raw", "Bruttolohn"},
{"Avg_Sollzeit_Tag_Raw", "Avg_Sollzeit_Tag"}
}),
// Abrechnungskreis als Text sicherstellen (fuehrende Null bleibt erhalten)
TypAbkrs = Table.TransformColumnTypes(RenNum, {{"Abrechnungskreis", type text}}),
// Name splitten
AddNachname = Table.AddColumn(TypAbkrs, "Nachname", each
let n = Text.From(if [Name_Rexx] = null then "" else [Name_Rexx]) in
Text.Trim(Text.Split(n, ","){0}), type text),
AddVorname = Table.AddColumn(AddNachname, "Vorname", each
let n = Text.From(if [Name_Rexx] = null then "" else [Name_Rexx]),
p = Text.Split(n, ",") in
if List.Count(p) > 1 then Text.Trim(p{1}) else "", type text),
// Stunden_Saldo parsen: "58:10" → 58.17, "-6:12" → -6.20
AddSaldo = Table.AddColumn(AddVorname, "Stunden_Saldo", each
let
raw = Text.Trim(Text.From(if [Stunden_Saldo_Raw] = null then "0:00" else [Stunden_Saldo_Raw])),
isNeg = Text.StartsWith(raw, "-"),
cleaned = Text.Replace(raw, "-", ""),
parts = Text.Split(cleaned, ":"),
h = try Number.FromText(parts{0}) otherwise 0,
m = if List.Count(parts) > 1 then (try Number.FromText(parts{1}) otherwise 0) else 0,
dec = h + m / 60
in if isNeg then -dec else dec,
type number),
// FTE
AddFTE = Table.AddColumn(AddSaldo, "FTE", each
if [Beschaeftigungsgrad_Prozent] <> null and [Beschaeftigungsgrad_Prozent] > 0
then [Beschaeftigungsgrad_Prozent] / 100
else if [Arbeitszeitmodell] = "Vollzeit" then 1 else 0.5,
type number),
// Alter
AddAlter = Table.AddColumn(AddFTE, "Alter_Jahre", each
if [Geburtsdatum] = null then null
else Number.RoundDown(Duration.TotalDays(Date.From(DateTime.LocalNow()) - [Geburtsdatum]) / 365.25),
Int64.Type),
// Altersgruppe
AddAG = Table.AddColumn(AddAlter, "Altersgruppe", each
if [Alter_Jahre] = null then "Unbekannt"
else if [Alter_Jahre] < 30 then "< 30"
else if [Alter_Jahre] < 40 then "30-39"
else if [Alter_Jahre] < 50 then "40-49"
else if [Alter_Jahre] < 60 then "50-59"
else "60+", type text),
// Geschlecht Text
AddGT = Table.AddColumn(AddAG, "Geschlecht_Text", each
if [Geschlecht] = 1 then "Maennlich"
else if [Geschlecht] = 2 then "Weiblich"
else "Unbekannt", type text),
// Ist Teilzeit
AddTZ = Table.AddColumn(AddGT, "Ist_Teilzeit", each
if [Beschaeftigungsgrad_Prozent] <> null and [Beschaeftigungsgrad_Prozent] > 0
then [Beschaeftigungsgrad_Prozent] < 100
else if [Arbeitszeitmodell] <> null then [Arbeitszeitmodell] = "Teilzeit"
else false, type logical),
// Dienstjahre
AddDJ = Table.AddColumn(AddTZ, "Dienstjahre", each
if [Eintrittsdatum] = null then null
else Number.RoundDown(Duration.TotalDays(Date.From(DateTime.LocalNow()) - [Eintrittsdatum]) / 365.25),
Int64.Type),
// Ist Aktiv
AddAktiv = Table.AddColumn(AddDJ, "Ist_Aktiv", each [Personal_Status] = "Aktiv", type logical),
// Periode
AddPeriode = Table.AddColumn(AddAktiv, "Periode", each Date.StartOfMonth(Date.From(DateTime.LocalNow())), type date),
AddJahr = Table.AddColumn(AddPeriode, "Jahr", each Date.Year([Periode]), Int64.Type),
AddMonat = Table.AddColumn(AddJahr, "Monat", each Date.Month([Periode]), Int64.Type),
AddPT = Table.AddColumn(AddMonat, "Periode_Text", each
Text.From([Jahr]) & "-" & Text.PadStart(Text.From([Monat]), 2, "0"), type text),
// Sollarbeitstage
AddSAT = Table.AddColumn(AddPT, "Sollarbeitstage", each 21, Int64.Type),
// Ferien bezogen
AddFB = Table.AddColumn(AddSAT, "Ferien_Bezogen", each
let a = if [Urlaubsanspruch] = null then 0 else [Urlaubsanspruch],
r = if [Urlaub_Rest] = null then 0 else [Urlaub_Rest],
au = if [Ferien_Ausstehend] = null then 0 else [Ferien_Ausstehend]
in a - r - au, type number),
AddFT = Table.AddColumn(AddFB, "Ferientage", each
let fb = [Ferien_Bezogen] in if fb = null or fb < 0 then 0 else fb, type number),
// GLZ Ampel
AddGLZ = Table.AddColumn(AddFT, "GLZ_Ampel", each
let abs = Number.Abs([Stunden_Saldo]) in
if abs <= 50 then "Gruen" else if abs <= 100 then "Gelb" else "Rot", type text),
AddGLZS = Table.AddColumn(AddGLZ, "GLZ_Ampel_Sort", each
if [GLZ_Ampel] = "Gruen" then 1 else if [GLZ_Ampel] = "Gelb" then 2 else 3, Int64.Type),
// Restferien Ampel
AddRA = Table.AddColumn(AddGLZS, "Restferien_Ampel", each
if [Urlaub_Rest] = null or [Urlaub_Rest] <= 5 then "Gruen" else "Rot", type text),
// Mitarbeitertyp
AddMT = Table.AddColumn(AddRA, "Mitarbeitertyp", each
let s = Text.Lower(Text.From(if [Stelle_Rexx] = null then "" else [Stelle_Rexx])) in
if Text.Contains(s, "praktik") then "Praktikant"
else if Text.Contains(s, "werkstudent") then "Werkstudent"
else if Text.Contains(s, "aushilfe") then "Aushilfe"
else if Text.Contains(s, "lehrling") then "Lehrling"
else "Festangestellt", type text),
// Kostenstelle
AddKNr = Table.AddColumn(AddMT, "Kostenstelle", each
let raw = Text.From(if [Kostenstelle_Rexx] = null then "" else [Kostenstelle_Rexx]),
parts = Text.Split(raw, "/"),
num = Text.Trim(parts{0})
in try Number.FromText(num) otherwise null, Int64.Type),
AddKTxt = Table.AddColumn(AddKNr, "Kostenstelle_Text", each
if [Kostenstelle_Rexx] = null then "" else Text.From([Kostenstelle_Rexx]), type text),
// Organisation + Stelle Klartext
AddOrg = Table.AddColumn(AddKTxt, "Organisationseinheit", each
if [Organisation_Text] = null then "" else Text.From([Organisation_Text]), type text),
AddSt = Table.AddColumn(AddOrg, "Stelle", each
if [Stelle_Rexx] = null then "" else Text.From([Stelle_Rexx]), type text),
// Nur aktive MA
FilterAktiv = Table.SelectRows(AddSt, each [Personal_Status] = "Aktiv"),
// Hilfsspalten entfernen
Clean = Table.RemoveColumns(FilterAktiv, {
"Name_Rexx", "Stelle_Rexx", "Organisation_Text",
"Kostenstelle_Rexx", "Eintrittsdatum_Raw", "Geburtsdatum_Raw",
"Stunden_Saldo_Raw", "Personal_Status", "PERNR_Key"
}),
// Sortieren
Sorted = Table.Sort(Clean, {{"Personalnummer", Order.Ascending}})
in
Sorted
+118
View File
@@ -0,0 +1,118 @@
let
Source = Excel.Workbook(File.Contents("C:\temp\Personalausgeschieden.xlsx"), null, true),
Data = Source{0}[Data],
Head = Table.PromoteHeaders(Data, [PromoteAllScalars = true]),
Ren = Table.RenameColumns(Head, {
{"Personalnummer", "PERNR_Text"},
{"Nachname, Vorname (Link Personal)", "Name_Voll"},
{"Stelle-1", "Stelle"},
{"Organisation-1", "Organisationseinheit"},
{"Leitung j/n", "Leitung"},
{"Austrittsdatum", "Austrittsdatum_Raw"},
{"Austrittsart", "Austrittsart"},
{"Eintrittsdatum", "Eintrittsdatum_Raw"},
{"Personal Status", "Status"}
}),
AddPernr = Table.AddColumn(Ren, "Personalnummer", each
try Number.FromText(Text.Trim(Text.From([PERNR_Text]))) otherwise null, Int64.Type),
// Datumsfelder (Excel liefert evtl. schon als Date)
AddAustritt = Table.AddColumn(AddPernr, "Austrittsdatum", each
let raw = [Austrittsdatum_Raw] in
if raw is date then raw
else if raw is datetime then Date.From(raw)
else if raw is number then Date.AddDays(#date(1899, 12, 30), Number.RoundDown(raw))
else if (try Number.FromText(Text.From(raw)) otherwise null) <> null then
Date.AddDays(#date(1899, 12, 30), Number.RoundDown(Number.FromText(Text.From(raw))))
else try Date.FromText(Text.From(raw), [Format = "dd.MM.yyyy"]) otherwise null,
type date),
AddEintritt = Table.AddColumn(AddAustritt, "Eintrittsdatum", each
let raw = [Eintrittsdatum_Raw] in
if raw is date then raw
else if raw is datetime then Date.From(raw)
else if raw is number then Date.AddDays(#date(1899, 12, 30), Number.RoundDown(raw))
else if (try Number.FromText(Text.From(raw)) otherwise null) <> null then
Date.AddDays(#date(1899, 12, 30), Number.RoundDown(Number.FromText(Text.From(raw))))
else try Date.FromText(Text.From(raw), [Format = "dd.MM.yyyy"]) otherwise null,
type date),
// Verweildauer
AddVW = Table.AddColumn(AddEintritt, "Verweildauer_Monate", each
try Number.Round(Duration.TotalDays([Austrittsdatum] - [Eintrittsdatum]) / 30.44, 1)
otherwise null, type number),
// Mitarbeitertyp
AddMT = Table.AddColumn(AddVW, "Mitarbeitertyp", each
let s = Text.Lower(Text.From(if [Stelle] = null then "" else [Stelle])) in
if Text.Contains(s, "praktik") then "Praktikant"
else if Text.Contains(s, "werkstudent") then "Werkstudent"
else if Text.Contains(s, "aushilfe") then "Aushilfe"
else if Text.Contains(s, "lehrling") then "Lehrling"
else "Festangestellt", type text),
// Fluktuationslogik gemaess HR-Definition aus formeln.docx:
// Zaehlen nur Arbeitnehmerkuendigungen von festangestellten Mitarbeitenden.
// Nicht zaehlen: Aushilfen, Praktikanten, Werkstudenten, Lehrlinge,
// befristete Vertraege, Pensionierungen und Kuendigungen durch Trafag.
AddAustrittsartNorm = Table.AddColumn(AddMT, "Austrittsart_Normalisiert", each
let
raw = Text.Lower(Text.Trim(Text.From(if [Austrittsart] = null then "" else [Austrittsart]))),
ae = Text.Replace(raw, Character.FromNumber(228), "ae"),
oe = Text.Replace(ae, Character.FromNumber(246), "oe"),
ue = Text.Replace(oe, Character.FromNumber(252), "ue"),
ss = Text.Replace(ue, Character.FromNumber(223), "ss")
in ss,
type text),
AddIstArbeitnehmerkuendigung = Table.AddColumn(AddAustrittsartNorm, "Ist_Arbeitnehmerkuendigung", each
let a = [Austrittsart_Normalisiert] in
Text.Contains(a, "arbeitnehmer") or
Text.Contains(a, "mitarbeiter") or
Text.Contains(a, "kuendigung an") or
Text.Contains(a, "an kuendigung") or
Text.Contains(a, "eigenkuendigung") or
Text.Contains(a, "kuendigung ma") or
Text.Contains(a, "kuendigung durch ma"),
type logical),
AddIstAusgeschlossen = Table.AddColumn(AddIstArbeitnehmerkuendigung, "Ist_Fluktuation_Ausgeschlossen", each
let a = [Austrittsart_Normalisiert] in
[Mitarbeitertyp] <> "Festangestellt" or
Text.Contains(a, "befrist") or
Text.Contains(a, "pension") or
Text.Contains(a, "rente") or
Text.Contains(a, "trafag") or
Text.Contains(a, "arbeitgeber") or
Text.Contains(a, "ag-kuendigung") or
Text.Contains(a, "ag kuendigung") or
Text.Contains(a, "kuendigung ag"),
type logical),
AddFluktuationRelevant = Table.AddColumn(AddIstAusgeschlossen, "Ist_Fluktuationsrelevant", each
[Ist_Arbeitnehmerkuendigung] = true and [Ist_Fluktuation_Ausgeschlossen] = false,
type logical),
AddAusschlussgrund = Table.AddColumn(AddFluktuationRelevant, "Fluktuation_Ausschlussgrund", each
let a = [Austrittsart_Normalisiert] in
if [Ist_Fluktuationsrelevant] then null
else if [Mitarbeitertyp] <> "Festangestellt" then [Mitarbeitertyp]
else if Text.Trim(a) = "" then "Austrittsart leer/unklar"
else if Text.Contains(a, "befrist") then "Befristeter Vertrag"
else if Text.Contains(a, "pension") or Text.Contains(a, "rente") then "Pensionierung"
else if Text.Contains(a, "trafag") or Text.Contains(a, "arbeitgeber") or Text.Contains(a, "ag-kuendigung") or Text.Contains(a, "ag kuendigung") or Text.Contains(a, "kuendigung ag") then "Kuendigung durch Trafag"
else if [Ist_Arbeitnehmerkuendigung] = false then "Keine Arbeitnehmerkuendigung"
else "Ausgeschlossen",
type text),
AddAM = Table.AddColumn(AddAusschlussgrund, "Austrittsmonat", each Date.StartOfMonth([Austrittsdatum]), type date),
AddAJ = Table.AddColumn(AddAM, "Austrittsjahr", each Date.Year([Austrittsdatum]), Int64.Type),
AddNN = Table.AddColumn(AddAJ, "Nachname", each
Text.Trim(Text.Split(Text.From([Name_Voll]), ","){0}), type text),
AddVN = Table.AddColumn(AddNN, "Vorname", each
let p = Text.Split(Text.From([Name_Voll]), ",") in
if List.Count(p) > 1 then Text.Trim(p{1}) else "", type text),
Clean = Table.RemoveColumns(AddVN, {"PERNR_Text", "Austrittsdatum_Raw", "Eintrittsdatum_Raw"}),
Reorder = Table.ReorderColumns(Clean, {"Personalnummer"} &
List.RemoveItems(Table.ColumnNames(Clean), {"Personalnummer"}))
in
Reorder