modernize ticket and accounting
iteration 0
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
,,,Vertrag,Vertrag,Vertrag,Vertrag,Vertrag,Vertrag,Vertrag,Vertrag,"Stunden innerhalb der Soll-Stunden","Stunden innerhalb der Soll-Stunden","Stunden innerhalb der Soll-Stunden","Stunden innerhalb der Soll-Stunden","Stunden innerhalb der Soll-Stunden","Stunden innerhalb der Soll-Stunden",Ist-/Soll-Stunden,Ist-/Soll-Stunden,Ist-/Soll-Stunden,Ist-/Soll-Stunden,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Berechnung,Salden,Salden,Salden,Salden,Salden,Salden,Salden,Salden,Salden,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen,Auszahlen
|
||||
,,,,,,,,,,,"Gearbeitete Stunden","Gearbeitete Stunden","Urlaub nehmen","Urlaub nehmen",Krankheitsstunden,Krankheitsstunden,Gesamt,Gesamt,Vertrag,Vertrag,Plus/Min,Mehrstunden,Überstunden,"Zuschlag P-Schein","Krank ohne AU","Krank ohne AU","Nachtzuschlag 25 (25%)","Nachtzuschlag 25 (25%)","Nachtzuschlag 40 (40%)","Nachtzuschlag 40 (40%)","Sonntagszuschlag (50%)","Sonntagszuschlag (50%)","Feiertagszuschlag (125%)","Feiertagszuschlag (125%)","Verpfl.-pauschale (14%)","Samstagzuschlag (15%)","Samstagzuschlag (15%)",Zeitausgleich,Zeitausgleich,Zeitausgleich,Zeitausgleich,Zeitausgleich,Urlaub,Urlaub,Urlaub,Urlaub,Mehrstunden,"Überstunden auszahlen",Fahrtkosten,Verpflegungspauschale,"Zuschlag P-Schein",25%,40%,50%,125%,14%,15%
|
||||
Persnr.,Nachname,Vorname,Art,P-Schein,"Recht auf Zuschläge?",Start,Tage,Stunden,Max,"Urlaubstage pro Jahr",Tage,Stunden,Tage,Stunden,Tage,Stunden,Tage,Stunden,Tage,Stunden,Stunden,Stunden,Stunden,anzahl,Tage,Stunden,Tage,Stunden,Tage,Stunden,Tage,Stunden,Tage,Stunden,Tage,Tage,Stunden,Start,+/-,Änderungen,Auszahlen,Jetzt,Start,Abbau,Korrektur,Jetzt,Stunden,Stunden,km,Tage,Anzahl,Stunden,Stunden,Stunden,Stunden,Stunden,Stunden
|
||||
169,Safari,"Abdul Latif ",Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,20,20,180.4167,5,40,0,0,25,220.41666666667,21.667,173.333,47.08366666667,0.00033333332999064,47.08333333334,20,0,0,0,0,0,0,0,0,2,16.166666666667,16,2,17.35,0.334,47.08366666667,47.08366666667,47.08366666667,0.334,19,5,0,14,0.00033333332999064,47.08333333334,0.00,20,20,0,0,0,16.166666666667,18.9,17.35
|
||||
93,"Moshtagh Askari",Adel,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,25,218.4167,0,0,0,0,25,218.41666666667,21.667,173.333,45.08366666667,0.00033333332999064,45.08333333334,25,0,0,0,0,0,0,0,0,2,18.65,20,3,24.95,0.007,45.08366666667,45.08366666667,45.08366666667,0.0069999999999979,31,0,0,31,0.00033333332999064,45.08333333334,0.00,25,25,0,0,0,18.65,14.816666666667,24.95
|
||||
18,Mihailescu,"Adrian Lucian",Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,20,21,177.5667,0,0,0,0,21,177.56666666667,21.667,173.333,4.23366666667,0.00033333332999064,4.23333333334,21,0,0,0,0,0,0,0,0,1,8.6333333333333,15,3,24.116666666667,0,4.23366666667,4.23366666667,4.23366666667,0,20,0,0,20,0.00033333332999064,4.23333333334,0.00,18,21,0,0,0,8.6333333333333,11.1,24.116666666667
|
||||
,"Esmaeili Dashtbayaz",Amin,Fest,Nein,Ja,2025-04-01,5.00,40.00,0.00,,20,171.2667,0,0,0,0,20,171.26666666667,21.667,173.333,-2.06633333333,0,0,,0,0,0,0,0,0,0,0,0,0,18,2,16.083333333333,0,-2.06633333333,-2.06633333333,0,-2.06633333333,-3,0,15,12,0,0,0.00,20,0,0,0,0,0,11.183333333333,16.083333333333
|
||||
,Ahmadpour,Amirhossein,Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,24,21,193.6333,2,16,0,0,23,209.63333333333,21.667,173.333,36.30033333333,0.00033333332999064,36.3,21,0,0,0,0,0,0,1,8.4666666666667,1,8.7833333333333,19,0,0,0,36.30033333333,36.30033333333,36.30033333333,0,-2,2,13,9,0.00033333332999064,36.3,0.00,21,21,0,0,8.4666666666667,8.7833333333333,24.383333333333,0
|
||||
188,"Malani Karizi",Arasch,Fest,Nein,Ja,2025-09-01,5.00,40.00,0.00,20,24,211.9833,0,0,0,0,24,211.98333333333,21.667,173.333,38.65033333333,0.00033333332999064,38.65,,0,0,6,2.6,0,0,0,0,1,8.25,20,3,24.7,-147.647,38.65033333333,38.65033333333,0,-108.99666666667,5,0,0,5,0,0,0.00,24,0,2.6,0,0,8.25,16.433333333333,24.7
|
||||
113,Ziaudin,Atiq,Fest,Ja,Ja,2025-02-24,5.00,40.00,0.00,20,23,194.3167,0,0,0,0,23,194.31666666667,21.667,173.333,20.98366666667,0.00033333332999064,20.98333333334,23,0,0,0,0,0,0,0,0,1,6.4333333333333,20,2,15.05,14.047,20.98366666667,20.98366666667,20.98366666667,14.047,9,0,0,9,0.00033333332999064,20.98333333334,0.00,21,23,0,0,0,6.4333333333333,12.833333333333,15.05
|
||||
,Rahmati,Behnam,Fest,Ja,Ja,2025-04-01,5.00,40.00,0.00,,20,176.05,0,0,0,0,20,176.05,21.667,173.333,2.717,0.00033333332999064,2.71666666667,20,1,8.5,0,0,0,0,0,0,0,0,20,0,0,0,2.717,2.717,2.717,0,-2,0,15,13,0.00033333332999064,2.71666666667,0.00,20,20,0,0,0,0,16.05,0
|
||||
,Altner,"Christian Van Manh",Fest,Ja,Ja,2025-10-01,5.00,40.00,0.00,20,22,184.0833,0,0,0,0,22,184.08333333333,21.667,173.333,10.75033333333,0.00033333332999064,10.75,22,0,0,1,0.2,0,0,0,0,1,7.9333333333333,20,1,8.0166666666667,0,10.75033333333,10.75033333333,10.75033333333,0,0,0,2,2,0.00033333332999064,10.75,0.00,21,22,0.2,0,0,7.9333333333333,7.9333333333333,8.0166666666667
|
||||
,Aba,Cuan,Fest,Nein,Ja,2025-08-01,5.00,40.00,0.00,,8,50.25,6,39,0,0,14,89.25,21.667,173.333,-84.083,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,-144.25,-84.083,-84.083,0,-228.333,0,6,10,4,0,0,0.00,,0,0,0,0,0,0,0
|
||||
,Haghighivand,Danial,Fest,Nein,Ja,2025-04-01,5.00,40.00,0.00,,19,166.0333,2,16,0,0,21,182.03333333333,21.667,173.333,8.70033333333,0.00033333332999064,8.7,,0,0,0,0,0,0,0,0,0,0,19,0,0,0,8.70033333333,8.70033333333,8.70033333333,0,-10,2,15,3,0.00033333332999064,8.7,0.00,19,0,0,0,0,0,14.033333333333,0
|
||||
,Mohammadi,Danial,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,21,182.8667,0,0,0,0,21,182.86666666667,21.667,173.333,9.53366666667,0.00033333332999064,9.53333333334,21,0,0,0,0,0,0,0,0,1,8.3333333333333,18,2,16.616666666667,0,9.53366666667,9.53366666667,9.53366666667,0,7,0,0,7,0.00033333332999064,9.53333333334,0.00,20,21,0,0,0,8.3333333333333,13.916666666667,16.616666666667
|
||||
,Shahrasebi,Danial,Fest,,,2021-05-01,0.00,0.00,0.00,,,,0,0,0,0,0,0,0,0,0,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,5,0,0,,,0,0,0,0,0,0,0
|
||||
,Emkani,Daniel,Fest,Nein,Ja,2025-07-15,3.00,25.00,0.00,,14,118.45,0,0,0,0,14,118.45,13,108.333,10.117,10.117,0,,0,0,0,0,0,0,0,0,1,8.6833333333333,0,2,13.3,0,10.117,10.117,10.117,0,0,0,7,7,10.117,0,0.00,12,0,0,0,0,8.6833333333333,0,13.3
|
||||
,Lolies,Danny,Fest,Ja,Ja,2025-03-01,5.00,40.00,0.00,,19,158.9,0,0,1,8.5,20,167.4,21.667,173.333,-5.933,0,0,19,3,24,0,0,0,0,0,0,1,5.55,15,2,17.05,0,-5.933,-5.933,0,-5.933,10,0,0,10,0,0,0.00,17,19,0,0,0,5.55,11.05,17.05
|
||||
,Hastedt,David,Fest,Nein,Ja,2025-01-27,4.00,32.00,0.00,20,14,112.3167,0,0,4,32,18,144.31666666667,17.333,138.667,5.64966666667,5.64966666667,0,,0,0,0,0,0,0,0,0,0,0,11,0,0,0,5.64966666667,5.64966666667,5.64966666667,0,2,0,0,2,5.64966666667,0,0.00,11,0,0,0,0,0,7.4166666666667,0
|
||||
2,Shahrasebi,David,Fest,Ja,Ja,2022-10-01,5.00,40.00,0.00,20,,0,0,0,0,0,0,0,21.667,173.333,0,0,0,,1,8.5,0,0,0,0,0,0,0,0,0,0,0,151.34,0,0,0,151.34,2,0,0,2,0,0,,,0,0,0,0,0,0,0
|
||||
,Golmohammadi,"Davood ",Fest,Ja,Ja,2025-08-01,5.00,40.00,0.00,20,20,182.2167,0,0,0,0,20,182.21666666667,21.667,173.333,8.88366666667,0.00033333332999064,8.88333333334,20,1,8,0,0,0,0,0,0,0,0,20,0,0,0,8.88366666667,8.88366666667,8.88366666667,0,-6,0,8,2,0.00033333332999064,8.88333333334,0.00,20,20,0,0,0,0,22.216666666667,0
|
||||
202,Pouzesh,Esmail,Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,20,24,202.0833,0,0,0,0,24,202.08333333333,21.667,173.333,28.75033333333,0.00033333332999064,28.75,24,0,0,21,70.416666666667,9,20.6,1,9.5,4,17.816666666667,0,0,0,0,28.75033333333,28.75033333333,28.75033333333,0,12,0,0,12,0.00033333332999064,28.75,0.00,24,24,70.416666666667,20.6,9.5,17.816666666667,0,0
|
||||
187,"Sajaei Khouzestani",Farid,Fest,Ja,Ja,2025-04-07,5.00,40.00,0.00,20,22,197.4333,0,0,0,0,22,197.43333333333,21.667,173.333,24.10033333333,0.00033333332999064,24.1,22,0,0,0,0,0,0,0,0,2,17.316666666667,18,2,16.366666666667,0,24.10033333333,24.10033333333,24.10033333333,0,19,0,0,19,0.00033333332999064,24.1,0.00,22,22,0,0,0,17.316666666667,19.75,16.366666666667
|
||||
191,"Faridani Rad",Farzad,Fest,Ja,Ja,2025-02-01,5.00,40.00,0.00,20,21,174.0833,0,0,2,16,23,190.08333333333,21.667,173.333,16.75033333333,0.00033333332999064,16.75,21,0,0,14,44.5,2,5.6666666666667,0,0,3,16.15,5,0,0,0,16.75033333333,16.75033333333,16.75033333333,0,0,0,0,0,0.00033333332999064,16.75,0.00,21,21,44.5,5.6666666666667,0,16.15,1.7666666666667,0
|
||||
,Movafagh,"Farzad ",Fest,Ja,Ja,2025-02-03,5.00,40.00,0.00,20,23,202.6167,0,0,1,8,24,210.61666666667,21.667,173.333,37.28366666667,0.00033333332999064,37.28333333334,23,0,0,0,0,0,0,0,0,0,0,20,3,24.55,0,37.28366666667,37.28366666667,37.28366666667,0,17,0,0,17,0.00033333332999064,37.28333333334,0.00,23,23,0,0,0,0,18.066666666667,24.55
|
||||
89,Hashimazada,Firuz,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,18,155.35,0,0,3,24,21,179.35,21.667,173.333,6.017,0.00033333332999064,6.01666666667,18,1,8,0,0,0,0,0,0,1,8.5333333333333,17,0,0,0,6.017,6.017,6.017,0,8,0,0,8,0.00033333332999064,6.01666666667,0.00,18,18,0,0,0,8.5333333333333,10.816666666667,0
|
||||
,Saeedpour,Fouad,Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,,23,203.2833,0,0,0,0,23,203.28333333333,21.667,173.333,29.95033333333,0.00033333332999064,29.95,23,0,0,2,4.25,0,0,3,26.8,1,9,16,3,25.25,0,29.95033333333,29.95033333333,29.95033333333,0,26,0,0,26,0.00033333332999064,29.95,0.00,23,23,4.25,0,26.8,9,9.9833333333333,25.25
|
||||
55,Khodayaran,Hossein,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,13,105.3,10,80,0,0,23,185.3,21.667,173.333,11.967,0.00033333332999064,11.96666666667,13,0,0,11,31,2,5.6666666666667,0,0,2,8.1333333333333,0,0,0,0,11.967,11.967,11.967,0,16,10,0,6,0.00033333332999064,11.96666666667,0.00,12,13,31,5.6666666666667,0,8.1333333333333,0,0
|
||||
206,Rasulkhani,Hossein,Fest,Ja,Ja,2025-03-06,5.00,40.00,0.00,20,23,194.8667,0,0,0,0,23,194.86666666667,21.667,173.333,21.53366666667,0.00033333332999064,21.53333333334,23,0,0,4,10.433333333333,0,0,1,9.25,1,9,13,4,32.366666666667,0,21.53366666667,21.53366666667,21.53366666667,0,29,0,0,29,0.00033333332999064,21.53333333334,0.00,23,23,10.433333333333,0,9.25,9,7.8166666666667,32.366666666667
|
||||
,"Mohamed Salah","Hussin Noureldin",Fest,Ja,Ja,2025-06-06,5.00,40.00,0.00,20,22,186.5833,0,0,0,0,22,186.58333333333,21.667,173.333,13.25033333333,0.00033333332999064,13.25,22,0,0,0,0,0,0,0,0,1,8.5166666666667,18,3,24.55,0,13.25033333333,13.25033333333,13.25033333333,0,-11,0,11,0,0.00033333332999064,13.25,0.00,21,22,0,0,0,8.5166666666667,9.5166666666667,24.55
|
||||
50,"Rohany Oskoee",Iman,Fest,Nein,Ja,2025-01-01,5.00,40.00,0.00,20,22,190.3833,0,0,0,0,22,190.38333333333,21.667,173.333,17.05033333333,0.00033333332999064,17.05,,0,0,0,0,0,0,0,0,1,8.7333333333333,20,1,8.0333333333333,0,17.05033333333,17.05033333333,17.05033333333,0,36,0,0,36,0.00033333332999064,17.05,0.00,22,0,0,0,0,8.7333333333333,13.616666666667,8.0333333333333
|
||||
7,Salehi,Iman,Fest,Ja,Ja,2025-02-01,5.00,40.00,0.00,20,26,220.1,0,0,0,0,26,220.1,21.667,173.333,46.767,0.00033333332999064,46.76666666667,26,0,0,21,68.766666666667,7,13.483333333333,2,18.2,2,16.383333333333,0,1,8.0166666666667,-14.233,46.767,46.767,32.534,7.105427357601E-15,0,0,0,0,0.00033333332999064,32.53366666667,0.00,26,26,68.766666666667,13.483333333333,18.2,16.383333333333,0,8.0166666666667
|
||||
,Krüger,Julian,Fest,Ja,Ja,2025-01-15,5.00,40.00,0.00,20,23,197.35,0,0,0,0,23,197.35,21.667,173.333,24.017,0.00033333332999064,24.01666666667,23,0,0,0,0,0,0,0,0,2,14.283333333333,0,2,15.116666666667,0,24.017,24.017,24.017,0,-30,0,38,8,0.00033333332999064,24.01666666667,0.00,19,23,0,0,0,14.283333333333,0,15.116666666667
|
||||
,Hazrati,Kamyar,Fest,,Ja,2025-02-24,5.00,40.00,0.00,20,21,182.75,0,0,0,0,21,182.75,21.667,173.333,9.417,0.00033333332999064,9.41666666667,,0,0,0,0,0,0,0,0,0,0,19,2,16.616666666667,0,9.417,9.417,9.417,0,10,0,0,10,0.00033333332999064,9.41666666667,0.00,20,0,0,0,0,0,14.133333333333,16.616666666667
|
||||
,Saei,Kasra,Fest,Nein,Ja,2025-08-01,5.00,40.00,0.00,,6,37.2667,10,80,0,0,16,117.26666666667,21.667,173.333,-56.06633333333,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,-130.782,-56.06633333333,-56.06633333333,0,-186.84833333333,0,10,10,0,0,0,0.00,,0,0,0,0,0,0,0
|
||||
182,Kalantari,Kaveh,Fest,Ja,Ja,2025-03-01,5.00,40.00,0.00,20,18,159.05,5,40,0,0,23,199.05,21.667,173.333,25.717,0.00033333332999064,25.71666666667,18,0,0,2,4.25,0,0,1,10.25,1,9.9,14,2,16.916666666667,0,25.717,25.717,25.717,0,22,5,0,17,0.00033333332999064,25.71666666667,0.00,18,18,4.25,0,10.25,9.9,5.7333333333333,16.916666666667
|
||||
,Sahem,Khalid,Fest,Ja,Ja,2025-06-16,5.00,40.00,0.00,20,24,204.35,0,0,0,0,24,204.35,21.667,173.333,31.017,0.00033333332999064,31.01666666667,24,0,0,0,0,0,0,0,0,2,16.833333333333,19,3,26.033333333333,0,31.017,31.017,31.017,0,15,0,0,15,0.00033333332999064,31.01666666667,0.00,24,24,0,0,0,16.833333333333,9.4833333333333,26.033333333333
|
||||
194,Saffarnajib,Majid,Fest,Ja,Ja,2025-04-01,3.00,21.00,0.00,16,16,133.3667,0,0,0,0,16,133.36666666667,13,91,42.36666666667,42.36666666667,0,16,0,0,0,0,0,0,0,0,0,0,16,0,0,0,42.36666666667,42.36666666667,42.36666666667,0,0,0,16,16,42.36666666667,0,0.00,16,16,0,0,0,0,5.3666666666667,0
|
||||
177,Juyande,Masoud,Fest,Nein,Ja,2025-01-01,5.00,40.00,0.00,20,22,182.9,0,0,0,0,22,182.9,21.667,173.333,9.567,0.00033333332999064,9.56666666667,,0,0,21,68.1,10,23.783333333333,0,0,2,8.5166666666667,0,0,0,0.009,9.567,9.567,9.567,0.0090000000000003,-7,0,0,-7,0.00033333332999064,9.56666666667,0.00,22,0,68.1,23.783333333333,0,8.5166666666667,0,0
|
||||
53,Eggerstedt,Matthias,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,22,190.1333,0,0,0,0,22,190.13333333333,21.667,173.333,16.80033333333,0.00033333332999064,16.8,22,0,0,0,0,0,0,0,0,1,8.6333333333333,20,0,0,0,16.80033333333,16.80033333333,16.80033333333,0,8,0,2,10,0.00033333332999064,16.8,0.00,21,22,0,0,0,8.6333333333333,15.233333333333,0
|
||||
,Arabnia,Mehdi,Fest,Ja,Ja,2025-05-05,5.00,40.00,0.00,,22,194.3833,0,0,0,0,22,194.38333333333,21.667,173.333,21.05033333333,0.00033333332999064,21.05,22,0,0,0,0,0,0,0,0,0,0,20,2,15.783333333333,0,21.05033333333,21.05033333333,21.05033333333,0,-5,0,12,7,0.00033333332999064,21.05,0.00,21,22,0,0,0,0,18.6,15.783333333333
|
||||
,"Jalali Wagener",Mehdi,Fest,Ja,Ja,2025-02-01,5.00,40.00,0.00,,24,204.4833,0,0,0,0,24,204.48333333333,21.667,173.333,31.15033333333,0.00033333332999064,31.15,24,0,0,0,0,0,0,0,0,1,8.5333333333333,21,2,16.35,0,31.15033333333,31.15033333333,31.15033333333,0,-2,0,1,-1,0.00033333332999064,31.15,0.00,24,24,0,0,0,8.5333333333333,11.6,16.35
|
||||
,Taghadosi,Mehdi,Fest,Ja,Ja,2025-08-08,5.00,40.00,0.00,,22,188.9333,0,0,0,0,22,188.93333333333,21.667,173.333,15.60033333333,0.00033333332999064,15.6,22,0,0,0,0,0,0,0,0,1,6.3,19,2,16.6,0,15.60033333333,15.60033333333,15.60033333333,0,10,0,0,10,0.00033333332999064,15.6,0.00,21,22,0,0,0,6.3,14.033333333333,16.6
|
||||
,Mazidi,Mehrdad,Fest,Ja,Ja,2025-07-01,5.00,40.00,0.00,20,21,183.1667,0,0,0,0,21,183.16666666667,21.667,173.333,9.83366666667,0.00033333332999064,9.83333333334,21,0,0,0,0,0,0,0,0,1,8.1166666666667,18,2,17.033333333333,0,9.83366666667,9.83366666667,9.83366666667,0,0,0,10,10,0.00033333332999064,9.83333333334,0.00,21,21,0,0,0,8.1166666666667,14.016666666667,17.033333333333
|
||||
165,Koell,"Melanie Nadine",Fest,Nein,Ja,2025-01-01,5.00,40.00,0.00,20,,0,0,0,4,32,4,32,21.667,173.333,-141.333,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,-165.263,-141.333,-141.333,0,-306.596,12,0,0,12,0,0,,,0,0,0,0,0,0,0
|
||||
119,Pourhossein,Milad,Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,20,15,125.6167,7,56,0,0,22,181.61666666667,21.667,173.333,8.28366666667,0.00033333332999064,8.28333333334,15,0,0,0,0,0,0,0,0,1,7.9333333333333,12,2,16.6,-57.848,8.28366666667,8.28366666667,0,-49.56433333333,-6,7,23,10,0,0,0.00,14,15,0,0,0,7.9333333333333,5.0833333333333,16.6
|
||||
144,Maleki,Mohammad,Fest,Ja,Ja,2025-09-01,3.00,24.00,0.00,20,5,41.1167,0,0,0,0,5,41.116666666667,13,104,-62.883333333333,0,0,5,2,16,0,0,0,0,0,0,0,0,3,0,0,-250.962,-62.883333333333,-62.883333333333,0,-313.84533333333,-1,0,0,-1,0,0,0.00,3,5,0,0,0,0,1.4,0
|
||||
217,"Rasul Khani",Mohammad,Fest,Ja,Ja,2025-03-03,5.00,40.00,0.00,20,24,209.4833,0,0,0,0,24,209.48333333333,21.667,173.333,36.15033333333,0.00033333332999064,36.15,24,0,0,1,2.25,0,0,2,17.45,1,8.25,20,1,8.0166666666667,0,36.15033333333,36.15033333333,36.15033333333,0,20,0,0,20,0.00033333332999064,36.15,0.00,24,24,2.25,0,17.45,8.25,13.516666666667,8.0166666666667
|
||||
96,Sabahi,Mohammad,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,18,152.5833,1,8,4,32,23,192.58333333333,21.667,173.333,19.25033333333,0.00033333332999064,19.25,18,0,0,0,0,0,0,0,0,2,16.583333333333,16,0,0,0,19.25033333333,19.25033333333,19.25033333333,0,10,1,0,9,0.00033333332999064,19.25,0.00,18,18,0,0,0,16.583333333333,8,0
|
||||
78,Tajikzadeh,Mohammad,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,18,152.3167,5,40,0,0,23,192.31666666667,21.667,173.333,18.98366666667,0.00033333332999064,18.98333333334,18,0,0,0,0,0,0,0,0,1,8.25,16,1,8.0833333333333,0,18.98366666667,18.98366666667,18.98366666667,0,7,5,0,2,0.00033333332999064,18.98333333334,0.00,18,18,0,0,0,8.25,7.9833333333333,8.0833333333333
|
||||
,Atrafi,"Mohammad Nasir",Fest,Ja,Ja,2025-10-01,5.00,4.00,0.00,20,19,162.9667,0,0,0,0,19,162.96666666667,21.667,17.333,145.63366666667,145.63366666667,0,19,0,0,0,0,0,0,0,0,0,0,16,3,22.5,0,145.63366666667,145.63366666667,145.63366666667,0,0,0,5,5,145.63366666667,0,0.00,17,19,0,0,0,0,12.466666666667,22.5
|
||||
,Amiri,"Mohammad Samir",Fest,Ja,Ja,2025-09-01,5.00,40.00,0.00,20,22,190.15,0,0,3,24,25,214.15,21.667,173.333,40.817,0.00033333332999064,40.81666666667,22,0,0,5,17.25,5,12.633333333333,1,10.166666666667,0,0,0,2,12.316666666667,0,40.817,40.817,40.817,0,11,0,0,11,0.00033333332999064,40.81666666667,0.00,20,22,17.25,12.633333333333,10.166666666667,0,0,12.316666666667
|
||||
,"El Moustaiqid",Najlae,Fest,,Ja,2025-09-01,5.00,40.00,0.00,,20,151.7,1,8,0,0,21,159.7,21.667,173.333,-13.633,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,-1140.63,-13.633,-13.633,0,-1154.263,-5,1,15,9,0,0,0.00,,0,0,0,0,0,0,0
|
||||
,Jasarevic,Noel,Fest,Nein,Ja,2025-09-15,5.00,40.00,0.00,,14,116.6333,7,56,0,0,21,172.63333333333,21.667,173.333,-0.69966666667,0,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.69966666667,-0.69966666667,0,-0.69966666667,0,7,6,-1,0,0,0.00,12,0,0,0,0,0,0,0
|
||||
,"Sharh Ghaboos",Peyman,Fest,Ja,Ja,2025-03-01,5.00,40.00,0.00,,24,212.6333,0,0,0,0,24,212.63333333333,21.667,173.333,39.30033333333,0.00033333332999064,39.3,24,0,0,6,2.6,0,0,0,0,1,8.25,21,2,16.683333333333,0,39.30033333333,39.30033333333,39.30033333333,0,20,0,0,20,0.00033333332999064,39.3,0.00,24,24,2.6,0,0,8.25,17.1,16.683333333333
|
||||
227,Feilbach,"Raoul Nikolai ",Fest,Nein,Ja,2025-09-01,5.00,40.00,0.00,20,24,215.8833,0,0,0,0,24,215.88333333333,21.667,173.333,42.55033333333,0.00033333332999064,42.55,,0,0,16,55.5,16,40.75,1,8.7,4,17.483333333333,0,0,0,0.007,42.55033333333,42.55033333333,42.55033333333,0.0069999999999979,-14,0,20,6,0.00033333332999064,42.55,0.00,24,0,55.5,40.75,8.7,17.483333333333,0,0
|
||||
,Hatami,Reza,Fest,Ja,Ja,2025-09-15,5.00,40.00,0.00,20,20,173.9667,0,0,0,0,20,173.96666666667,21.667,173.333,0.63366666667,0.00033333332999064,0.63333333334,20,0,0,0,0,0,0,0,0,0,0,18,2,16.6,0,0.63366666667,0.63366666667,0.63366666666999,9.9920072216264E-15,0,0,6,6,0.00033333332999064,0.63333333334,0.00,20,20,0,0,0,0,13.366666666667,16.6
|
||||
137,Pourhossein,Reza,Fest,Ja,Ja,2025-04-01,5.00,40.00,0.00,20,21,181.55,0,0,0,0,21,181.55,21.667,173.333,8.217,0.00033333332999064,8.21666666667,21,0,0,12,36.533333333333,7,15.016666666667,0,0,2,16.316666666667,7,1,8.0166666666667,0,8.217,8.217,8.217,0,29,0,0,29,0.00033333332999064,8.21666666667,0.00,20,21,36.533333333333,15.016666666667,0,16.316666666667,4.1666666666667,8.0166666666667
|
||||
225,"Hosseini Jahed",Sajad,Fest,Ja,Ja,2025-03-03,5.00,40.00,0.00,20,21,193.3833,2,16,0,0,23,209.38333333333,21.667,173.333,36.05033333333,0.00033333332999064,36.05,21,0,0,0,0,0,0,1,8.4666666666667,1,8.7833333333333,18,0,0,0,36.05033333333,36.05033333333,36.05033333333,0,39,2,-17,20,0.00033333332999064,36.05,0.00,20,21,0,0,8.4666666666667,8.7833333333333,24.3,0
|
||||
,Saki,Sara,Fest,Ja,Ja,2025-02-01,5.00,40.00,0.00,,14,117.6333,10,80,0,0,24,197.63333333333,21.667,173.333,24.30033333333,0.00033333332999064,24.3,14,0,0,11,35.05,0,0,1,8.7,2,16.383333333333,0,0,0,0,24.30033333333,24.30033333333,24.30033333333,0,23,10,-2,11,0.00033333332999064,24.3,0.00,14,14,35.05,0,8.7,16.383333333333,0,0
|
||||
,"Perez Y Waldt",Sebastian,Fest,Nein,Ja,2024-09-23,5.00,40.00,0.00,,22,181.9,0,0,0,0,22,181.9,21.667,173.333,8.567,0.00033333332999064,8.56666666667,,0,0,4,14.1,0,0,0,0,1,8.0166666666667,11,3,24.666666666667,-756.446,8.567,8.567,0,-747.879,14,0,0,14,0,0,0.00,17,0,14.1,0,0,8.0166666666667,6.5166666666667,24.666666666667
|
||||
38,Hosseini,"Seyed Hamid",Fest,Ja,Ja,2025-03-10,5.00,40.00,0.00,20,18,153.4167,0,0,5,40,23,193.41666666667,21.667,173.333,20.08366666667,0.00033333332999064,20.08333333334,18,0,0,0,0,0,0,0,0,2,16.5,15,1,8.0833333333333,0,20.08366666667,20.08366666667,20.08366666667,0,26,0,0,26,0.00033333332999064,20.08333333334,0.00,18,18,0,0,0,16.5,8.8333333333333,8.0833333333333
|
||||
,Jabbari,Shahram,Fest,Nein,Ja,2025-04-01,5.00,40.00,0.00,,20,175.2167,0,0,0,0,20,175.21666666667,21.667,173.333,1.88366666667,0.00033333332999064,1.88333333334,,0,0,0,0,0,0,0,0,0,0,18,2,16.95,0,1.88366666667,1.88366666667,1.88366666667,0,-5,0,0,-5,0.00033333332999064,1.88333333334,0.00,20,0,0,0,0,0,14.266666666667,16.95
|
||||
124,Imanian,Shahriyar,Fest,Ja,Ja,2025-03-10,5.00,40.00,0.00,20,12,104.2833,12,96,0,0,24,200.28333333333,21.667,173.333,26.95033333333,0.00033333332999064,26.95,12,0,0,1,0.083333333333333,0,0,1,8.35,0,0,10,1,8.1666666666667,0,26.95033333333,26.95033333333,26.95033333333,0,36,12,0,24,0.00033333332999064,26.95,0.00,12,12,0.083333333333333,0,8.35,0,7.6833333333333,8.1666666666667
|
||||
164,Tokhi,Shokrollah,Fest,Ja,Ja,2025-03-04,5.00,40.00,0.00,20,18,152.6,5,40,0,0,23,192.6,21.667,173.333,19.267,0.00033333332999064,19.26666666667,18,0,0,14,47.75,9,21.483333333333,0,0,2,9.3,2,1,8.6333333333333,0,19.267,19.267,19.267,0,14,5,0,9,0.00033333332999064,19.26666666667,0.00,18,18,47.75,21.483333333333,0,9.3,0.93333333333333,8.6333333333333
|
||||
210,Rahimzadeh,Siamak,Fest,Ja,Ja,2025-03-01,5.00,40.00,0.00,20,23,197.5,0,0,0,0,23,197.5,21.667,173.333,24.167,0.00033333332999064,24.16666666667,23,0,0,0,0,0,0,0,0,2,16.25,18,3,25.016666666667,0.027,24.167,24.167,24.167,0.027000000000001,39,0,0,39,0.00033333332999064,24.16666666667,0.00,23,23,0,0,0,16.25,12.233333333333,25.016666666667
|
||||
,Florescu,Silviu-Cezar,Fest,Nein,Ja,2025-10-01,5.00,40.00,0.00,,20,173.5333,0,0,0,0,20,173.53333333333,21.667,173.333,0.20033333333001,0.00033333332999064,0.20000000000002,,0,0,2,0.28333333333333,0,0,0,0,0,0,19,1,8.35,0,0.20033333333001,0.20033333333001,0.20033333333001,0,9,0,0,9,0.00033333332999064,0.20000000000002,0.00,20,0,0.28333333333333,0,0,0,12.9,8.35
|
||||
,Vafaei,Soheil,Fest,Ja,Ja,2025-04-01,5.00,40.00,0.00,,18,155.4833,3,24,1,8,22,187.48333333333,21.667,173.333,14.15033333333,0.00033333332999064,14.15,18,0,0,0,0,0,0,0,0,0,0,17,1,8.0166666666667,0,14.15033333333,14.15033333333,14.15033333333,0,-10,3,0,-13,0.00033333332999064,14.15,0.00,18,18,0,0,0,0,11.466666666667,8.0166666666667
|
||||
,Toraman,"Tolga Can",Fest,Nein,Ja,2025-09-15,5.00,40.00,0.00,20,22,183.1333,0,0,0,0,22,183.13333333333,21.667,173.333,9.80033333333,0.00033333332999064,9.8,,0,0,0,0,0,0,1,9.5,0,0,17,2,16.3,0,9.80033333333,9.80033333333,9.80033333333,0,0,0,0,0,0.00033333332999064,9.8,0.00,20,0,0,0,9.5,0,6.3666666666667,16.3
|
||||
14,Haugg,Uwe,Fest,Ja,Ja,2025-01-01,4.00,32.00,0.00,18,6,50.6167,8,64,4,32,18,146.61666666667,17.333,138.667,7.94966666667,7.94966666667,0,6,0,0,0,0,0,0,0,0,0,0,5,1,6.9666666666667,-0.2,7.94966666667,7.94966666667,7.74966666667,0,-10,8,0,-18,7.74966666667,0,0.00,5,6,0,0,0,0,3.65,6.9666666666667
|
||||
231,Shokripour,Vahid,Fest,Ja,Ja,2025-01-01,5.00,40.00,0.00,20,24,208.1167,0,0,0,0,24,208.11666666667,21.667,173.333,34.78366666667,0.00033333332999064,34.78333333334,24,0,0,0,0,0,0,1,9.5,2,16.166666666667,18,3,25.333333333333,0,34.78366666667,34.78366666667,34.78366666667,0,35,0,0,35,0.00033333332999064,34.78333333334,0.00,24,24,0,0,9.5,16.166666666667,13.116666666667,25.333333333333
|
||||
226,Konak,Zidar,Fest,Nein,Ja,2025-05-01,5.00,40.00,0.00,20,22,183.65,0,0,0,0,22,183.65,21.667,173.333,10.317,0.00033333332999064,10.31666666667,,0,0,0,0,0,0,2,18.5,1,7,0,2,16.433333333333,0,10.317,10.317,10.317,0,16,0,0,16,0.00033333332999064,10.31666666667,0.00,15,0,0,0,18.5,7,0,16.433333333333
|
||||
,,,,,,,,,,,1293,11086.0499,101,799,32,256.5,1426,12141.55,1438.687,11344.646,970.23699999999,,,,9,73,175,515.91666666667,67,159.08333333333,20,181.8,66,479.58333333333,841,91,738.55,,970.23699999999,970.23699999999,1267.0006666666,-2939.2536666667,,101,241,709,211.53366666651,1055.4670000001,0,1209,,515.91666666667,159.08333333333,181.8,479.58333333333,607.15,738.55
|
||||
|
@@ -0,0 +1,84 @@
|
||||
Nachname;Vorname;Verpflegungspauschale;Vertr. Stundenlohn;Arbeitsstunden lt. Vertrag;Gearbeitet Stunden;Gearbeitet Tage;Pünktlichkeitsprämie anzahl;Krankheitstage x 8 Stunden;davon Überstunden;Tourengeld Anzahl;Tourengeld-Bonus (Platz 1-3;Gesundheitsbonus abzuziehen? (ja/nein);Urlaubsgeld (44 EUR pro tag);P-schein Zulage;Wochenendzuschlag 15%;Sonntagszuschlag 50%;Feiertagszuschlag 125%;Feiertagszuschlag 150%;Nachtzuschlag 25%;Nachtzuschlag 40%
|
||||
Malani Karizi;Arasch;24;14.42;173.32;211.98333333333332;24;24;0;38.651333333333305;41.5;500;;0;0;24.7;0;8.25;0;2.6;0
|
||||
Saeedpour;Fouad;23;14.42;173.32;203.28333333333333;23;23;0;29.951333333333316;41;400;;0;23;25.25;26.8;9;0;4.25;0
|
||||
Sharh Ghaboos;Peyman;24;14.42;173.32;212.63333333333333;24;24;0;39.30133333333331;37;300;;0;24;16.683333333333;0;8.25;0;2.6;0
|
||||
Kalantari;Kaveh;18;14.42;173.32;159.05;18;18;0;25.717999999999996;31;0;;5;18;16.916666666667;10.25;9.9;0;4.25;0
|
||||
Ahmadpour;Amirhossein;21;14.42;173.32;193.63333333333333;21;21;0;36.30133333333331;31;0;;2;21;0;8.4666666666667;8.7833333333333;0;0;0
|
||||
Hosseini Jahed;Sajad;20;14.42;173.32;193.38333333333333;21;21;0;44.05133333333331;30;0;;3;21;0;8.4666666666667;8.7833333333333;0;0;0
|
||||
Rasul Khani;Mohammad;24;14.42;173.32;209.48333333333332;24;24;0;36.151333333333305;29.5;0;;0;24;8.0166666666667;17.45;8.25;0;2.25;0
|
||||
Moshtagh Askari;Adel;25;15.58;173.32;218.41666666666666;25;23;0;45.08466666666664;29;0;;0;25;24.95;0;18.65;0;0;0
|
||||
Rasulkhani;Hossein;23;14.42;173.32;194.86666666666667;23;22;0;21.53466666666666;28;0;;0;23;32.366666666667;9.25;9;0;10.433333333333;0
|
||||
Sajaei Khouzestani;Farid;22;14.42;173.32;197.43333333333334;22;22;0;24.101333333333322;26;0;;0;22;16.366666666667;0;17.316666666667;0;0;0
|
||||
Perez Y Waldt;Sebastian;17;14.42;173.32;181.89999999999998;22;15;0;8.567999999999962;25;0;;0;0;24.666666666667;0;8.0166666666667;0;14.1;0
|
||||
Rahimzadeh;Siamak;23;14.42;173.32;197.5;23;23;0;24.167999999999985;25;0;;0;23;25.016666666667;0;16.25;0;0;0
|
||||
Shokripour;Vahid;24;15;173.32;208.11666666666667;24;24;0;34.78466666666666;23.5;0;;0;24;25.333333333333;9.5;16.166666666667;0;0;0
|
||||
Hosseini;Seyed Hamid;18;14.42;173.32;153.41666666666666;18;18;5;20.084666666666642;23;0;ja;0;18;8.0833333333333;0;16.5;0;0;0
|
||||
Safari;Abdul Latif ;20;14.42;173.32;180.41666666666669;20;20;0;47.08466666666667;23;0;;5;20;17.35;0;16.166666666667;0;0;0
|
||||
Pourhossein;Reza;20;14.42;173.32;181.55;21;21;0;8.217999999999996;20.5;0;;0;21;8.0166666666667;0;16.316666666667;0;36.533333333333;15.016666666667
|
||||
Salehi;Iman;26;17.31;173.32;220.1;26;26;0;46.76799999999998;20;0;;0;26;8.0166666666667;18.2;16.383333333333;0;68.766666666667;13.483333333333
|
||||
Rohany Oskoee;Iman;22;16.15;173.32;190.38333333333333;22;10;0;17.05133333333331;20;0;;0;0;8.0333333333333;0;8.7333333333333;0;0;0
|
||||
Sahem;Khalid;24;14.42;173.32;204.35000000000002;24;24;0;31.018000000000008;18.5;0;;0;24;26.033333333333;0;16.833333333333;0;0;0
|
||||
Toraman;Tolga Can;20;14.42;173.32;183.13333333333333;22;19;0;9.80133333333331;18;0;;0;0;16.3;9.5;0;0;0;0
|
||||
Tokhi;Shokrollah;18;14.42;173.32;152.6;18;18;0;19.26799999999998;17;0;;5;18;8.6333333333333;0;9.3;0;47.75;21.483333333333
|
||||
Eggerstedt;Matthias;21;15.58;173.32;190.13333333333333;22;22;0;16.80133333333331;16;0;;0;22;0;0;8.6333333333333;0;0;0
|
||||
Mihailescu;Adrian Lucian;18;16.73;173.32;177.56666666666666;21;4;0;4.234666666666648;14.5;0;;0;21;24.116666666667;0;8.6333333333333;0;0;0
|
||||
Hastedt;David;11;14.93;138.656;112.31666666666666;14;11;4;5.651066666666651;14;0;ja;0;0;0;0;0;0;0;0
|
||||
Faridani Rad;Farzad;21;15;173.32;174.08333333333334;21;21;2;16.751333333333328;13.5;0;ja;0;21;0;0;16.15;0;44.5;5.6666666666667
|
||||
Tajikzadeh;Mohammad;18;16.15;173.32;152.31666666666666;18;18;0;18.984666666666648;13.5;0;;5;18;8.0833333333333;0;8.25;0;0;0
|
||||
Saki;Sara;14;15;173.32;117.63333333333333;14;14;0;32.30133333333331;13;0;;11;14;0;8.7;16.383333333333;0;35.05;0
|
||||
Imanian;Shahriyar;12;14.42;173.32;104.28333333333333;12;12;0;26.951333333333316;12.5;0;;12;12;8.1666666666667;8.35;0;0;0.083333333333333;0
|
||||
Sabahi;Mohammad;18;16.15;173.32;152.58333333333334;18;17;4;19.251333333333328;11;0;ja;1;18;0;0;16.583333333333;0;0;0
|
||||
Florescu;Silviu-Cezar;20;15;173.32;173.53333333333336;20;20;0;0.2013333333333449;10.5;0;;0;0;8.35;0;0;0;0.28333333333333;0
|
||||
Florescu;Silviu-Cezar;20;15;173.32;173.53333333333336;20;20;0;0.2013333333333449;10.5;0;;0;0;8.35;0;0;0;0.28333333333333;0
|
||||
Altner;Christian Van Manh;21;15;173.32;184.08333333333334;22;22;0;10.751333333333328;9;0;;0;22;8.0166666666667;0;7.9333333333333;0;0.2;0
|
||||
Pourhossein;Milad;14;14.42;173.32;125.61666666666665;15;15;0;8.28466666666663;8.5;0;;7;15;16.6;0;7.9333333333333;0;0;0
|
||||
Pouzesh;Esmail;24;16.15;173.32;202.08333333333334;24;24;0;28.751333333333328;7;0;;0;24;0;9.5;17.816666666667;0;70.416666666667;20.6
|
||||
Khodayaran;Hossein;12;16.15;173.32;105.30000000000001;13;13;0;11.967999999999996;7;0;;10;13;0;0;8.1333333333333;0;31;5.6666666666667
|
||||
Juyande;Masoud;22;15;173.32;182.9;22;22;0;9.56799999999999;7;0;;0;0;0;0;8.5166666666667;0;68.1;23.783333333333
|
||||
Maleki;Mohammad;3;14.44;103.992;41.11666666666667;5;1;2;-46.88253333333334;3.5;0;ja;0;5;0;0;0;0;0;0
|
||||
Saffarnajib;Majid;16;15.38;90.99300000000001;133.36666666666665;16;16;0;42.36736666666664;1;0;;0;16;0;0;0;0;0;0
|
||||
Jasarevic;Noel;12;14.42;173.32;116.63333333333333;14;0;0;-0.6986666666666892;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Movafagh;Farzad ;23;12.82;173.32;202.61666666666667;23;0;1;37.28466666666666;0;0;ja;0;0;24.55;0;0;0;0;0
|
||||
Haralambous;Martin;0;12.41;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Hazrati;Kamyar;20;12.82;173.32;182.75;21;0;0;9.417999999999985;0;0;ja;0;0;16.616666666667;0;0;0;0;0
|
||||
Krüger;Julian;19;22.5;173.32;197.35;23;0;0;24.01799999999998;0;0;ja;0;0;15.116666666667;0;14.283333333333;0;0;0
|
||||
Arabnia;Mehdi;21;12.82;173.32;194.38333333333333;22;0;0;21.05133333333331;0;0;ja;0;0;15.783333333333;0;0;0;0;0
|
||||
Taghadosi;Mehdi;21;12.82;173.32;188.93333333333334;22;0;0;15.601333333333322;0;0;ja;0;0;16.6;0;6.3;0;0;0
|
||||
Jalali Wagener;Mehdi;24;12.82;173.32;204.48333333333332;24;0;0;31.151333333333305;0;0;ja;0;0;16.35;0;8.5333333333333;0;0;0
|
||||
Mazidi;Mehrdad;21;12.82;173.32;183.16666666666666;21;0;0;9.834666666666642;0;0;ja;0;0;17.033333333333;0;8.1166666666667;0;0;0
|
||||
Koell;Melanie Nadine;0;15.58;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Esmaeili Miandehi;Milad;0;12.82;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Mohamed Salah;Hussin Noureldin;21;12.82;173.32;186.58333333333334;22;0;0;13.251333333333328;0;0;ja;0;0;24.55;0;8.5166666666667;0;0;0
|
||||
Tokhi;Haroun;0;14.42;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Feilbach;Raoul Nikolai ;24;19.62;173.32;215.88333333333335;24;0;0;42.55133333333334;0;0;ja;0;0;0;8.7;17.483333333333;0;55.5;40.75
|
||||
Hashimazada;Firuz;18;12.82;173.32;155.35;18;0;4;14.01799999999998;0;0;ja;0;0;0;0;8.5333333333333;0;0;0
|
||||
Esser;Detlef;0;12;121.32400000000001;0;0;0;0;-121.3324;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Atrafi;Mohammad Nasir;17;12.82;17.332;162.96666666666667;19;0;0;145.63346666666666;0;0;ja;0;0;22.5;0;0;0;0;0
|
||||
Amiri;Mohammad Samir;20;19.62;173.32;190.14999999999998;22;0;0;16.817999999999962;0;0;ja;0;0;12.316666666667;10.166666666667;0;0;17.25;12.633333333333
|
||||
Babaei Dezghi;Mojtaba;0;15;160.321;0;0;0;0;-162.49875;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Aydin;Muhammet Ali;0;14.51;73.661;0;0;0;0;-73.6661;0;0;ja;0;0;0;0;0;0;0;0
|
||||
El Moustaiqid;Najlae;9;12.82;173.32;151.7;20;0;0;-5.632000000000026;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Heucke;Niklas;0;15;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Saei;Kasra;0;5.65;173.32;37.266666666666666;6;0;0;-56.06533333333335;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Latza;Lelia Eleonore;0;6.35;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Karimloo;Hamidreza;0;12.82;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Hatami;Reza;20;12.82;173.32;173.9666666666667;20;0;0;0.634666666666682;0;0;ja;0;0;16.6;0;0;0;0;0
|
||||
Abdallah;Delshad;0;12.82;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Sidiqi;Sahel;0;14.42;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Golmohammadi;Davood ;20;12.82;173.32;182.2166666666667;20;0;0;8.884666666666682;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Shahrasebi;David;0;0;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Lolies;Danny;17;12.82;173.32;158.9;19;0;4;18.06799999999999;0;0;ja;0;0;17.05;0;5.55;0;0;0
|
||||
Emkani;Daniel;12;22.48;108.325;118.45;14;0;0;10.117499999999993;0;0;ja;0;0;13.3;0;8.6833333333333;0;0;0
|
||||
Askarian Pour;Seyed Hosein;0;15.58;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Jabbari;Shahram;20;12.82;173.32;175.21666666666667;20;0;0;1.8846666666666536;0;0;ja;0;0;16.95;0;0;0;0;0
|
||||
Haghighivand;Danial;19;12.82;173.32;166.03333333333333;19;0;0;8.701333333333316;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Alami;Shoaib;0;12.82;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Mohammadi;Danial;20;12.82;173.32;182.86666666666665;21;0;0;9.53466666666663;0;0;ja;0;0;16.616666666667;0;8.3333333333333;0;0;0
|
||||
Aba;Cuan;0;5.65;173.32;50.25;8;0;0;-75.08200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Modrau;Cara Marleen;0;14.42;173.32;0;0;0;0;-173.33200000000002;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Rahmati;Behnam;20;12.82;173.32;176.04999999999998;20;0;1;11.217999999999968;0;0;ja;0;0;0;0;0;0;0;0
|
||||
Vafaei;Soheil;18;12.82;173.32;155.48333333333332;18;0;1;14.151333333333305;0;0;ja;0;0;8.0166666666667;0;0;0;0;0
|
||||
Ziaudin;Atiq;21;12.82;173.32;194.31666666666666;23;0;0;20.984666666666648;0;0;ja;0;0;15.05;0;6.4333333333333;0;0;0
|
||||
Haugg;Uwe;5;12.82;138.656;50.616666666666674;6;0;4;-4.848933333333349;0;0;ja;0;0;6.9666666666667;0;0;0;0;0
|
||||
Esmaeili Dashtbayaz;Amin;20;12.82;173.32;171.26666666666665;20;0;0;-2.0653333333333634;0;0;ja;0;0;16.083333333333;0;0;0;0;0
|
||||
Konak;Zidar;15;22.5;173.32;183.65000000000003;22;0;0;10.31800000000002;0;0;ja;0;0;16.433333333333;18.5;7;0;0;0
|
||||
|
@@ -3,6 +3,9 @@
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
"deleteOutDir": true,
|
||||
"assets": [
|
||||
"**/*.hbs"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+3913
-107
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"prisma": "prisma",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
@@ -25,6 +26,7 @@
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.12.0",
|
||||
"@faker-js/faker": "^9.6.0",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/apollo": "^13.0.4",
|
||||
"@nestjs/axios": "^4.0.0",
|
||||
"@nestjs/bullmq": "^11.0.2",
|
||||
@@ -43,14 +45,18 @@
|
||||
"bullmq": "^5.56.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"form-data": "^4.0.4",
|
||||
"graphql": "^16.10.0",
|
||||
"graphql-subscriptions": "^3.0.0",
|
||||
"graphql-ws": "^6.0.4",
|
||||
"handlebars": "^4.7.8",
|
||||
"kysely": "^0.27.6",
|
||||
"nodemailer": "^7.0.10",
|
||||
"pg": "^8.14.1",
|
||||
"prisma-kysely": "^1.8.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.2"
|
||||
"rxjs": "^7.8.2",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^11.0.6",
|
||||
@@ -58,7 +64,9 @@
|
||||
"@nestjs/testing": "^11.0.15",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/nodemailer": "^7.0.3",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
||||
"@typescript-eslint/parser": "^8.29.1",
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "TourFile" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"tourId" TEXT NOT NULL,
|
||||
"ticketId" TEXT NOT NULL,
|
||||
"storagePath" TEXT NOT NULL,
|
||||
"originalFilename" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"fileSizeBytes" INTEGER NOT NULL,
|
||||
"dlUploadStatus" TEXT NOT NULL DEFAULT 'pending',
|
||||
"createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TourFile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TourFile_tourId_ticketId_idx" ON "TourFile"("tourId", "ticketId");
|
||||
@@ -421,6 +421,23 @@ model Tour {
|
||||
ticketId String? @db.Uuid
|
||||
}
|
||||
|
||||
model TourFile {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
tourId String
|
||||
ticketId String
|
||||
|
||||
storagePath String
|
||||
originalFilename String
|
||||
mimeType String
|
||||
fileSizeBytes Int
|
||||
|
||||
dlUploadStatus String @default("pending")
|
||||
|
||||
createdAt DateTime? @default(now())
|
||||
|
||||
@@index([tourId, ticketId])
|
||||
}
|
||||
|
||||
model Patient {
|
||||
id String @id
|
||||
surname String?
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Logger, MiddlewareConsumer, Module } from '@nestjs/common';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
@@ -11,8 +10,11 @@ import { Pool } from 'pg';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { ConfigModule } from './core/config/config.module';
|
||||
import { ConfigService } from './core/config/config.service';
|
||||
import { DatabaseModule } from './core/database/database.module';
|
||||
import { PrismaModule } from './core/database/prisma.module';
|
||||
import { MailModule } from './core/mail/mail.module';
|
||||
import { RequestLoggerMiddleware } from './core/middleware/request-logger.middleware';
|
||||
import { ApplicationAccountingModule } from './modules/app-accounting/app-accounting.module';
|
||||
import { ApplicationControlCenterModule } from './modules/app-control-center/app-control-center.module';
|
||||
import { AppHomeModule } from './modules/app-home/app-home.module';
|
||||
@@ -23,59 +25,36 @@ import { FeatCalculationModule } from './modules/feat-calculation/feat-calculati
|
||||
import { FeatureEtlModule } from './modules/feat-etl/feat-etl.module';
|
||||
import { FeatReportsModule } from './modules/feat-reports/feat-reports.module';
|
||||
import { FeatTicketSystemModule } from './modules/feat-tickets/ticket-system.module';
|
||||
import { FeatBusinessObjectsModule } from './modules/feat-business-objects/feat-business-objects.module';
|
||||
import { OtelMetricsService } from './core/service/otel-metrics.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
// DatabaseModule.forRootAsync({
|
||||
// isGlobal: true,
|
||||
// inject: [ConfigService],
|
||||
// useFactory(config: ConfigService) {
|
||||
// return {
|
||||
// config: {
|
||||
// dialect: new PostgresDialect({
|
||||
// pool: new Pool({
|
||||
// host: config.databaseHost,
|
||||
// port: config.databasePort,
|
||||
// database: config.databaseName,
|
||||
// user: config.databaseUser,
|
||||
// password: config.databasePassword,
|
||||
// }),
|
||||
// }),
|
||||
// },
|
||||
// };
|
||||
// },
|
||||
// }),
|
||||
DatabaseModule.forRoot({
|
||||
// isGlobal: true,
|
||||
dialect: new PostgresDialect({
|
||||
pool: new Pool({
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
database: 'avicenna',
|
||||
user: 'johndoe',
|
||||
password: 'randompassword',
|
||||
DatabaseModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (config: ConfigService) => ({
|
||||
dialect: new PostgresDialect({
|
||||
pool: new Pool({
|
||||
host: config.databaseHost(),
|
||||
port: config.databasePort(),
|
||||
database: config.databaseName(),
|
||||
user: config.databaseUser(),
|
||||
password: config.databasePassword(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
BullModule.forRoot({
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
},
|
||||
BullModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (config: ConfigService) => ({
|
||||
connection: {
|
||||
host: config.redisHost(),
|
||||
port: config.redisPort(),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
// DatabaseModule.forRoot({
|
||||
// dialect: new PostgresDialect({
|
||||
// pool: new Pool({
|
||||
// host: 'localhost',
|
||||
// port: 54322,
|
||||
// database: 'postgres',
|
||||
// user: 'postgres',
|
||||
// password: 'postgres',
|
||||
// }),
|
||||
// }),
|
||||
// }),
|
||||
PrismaModule.forRoot(),
|
||||
ScheduleModule.forRoot(),
|
||||
EventEmitterModule.forRoot(),
|
||||
@@ -88,6 +67,7 @@ import { FeatBusinessObjectsModule } from './modules/feat-business-objects/feat-
|
||||
'graphql-ws': true,
|
||||
},
|
||||
}),
|
||||
MailModule,
|
||||
FeatureEtlModule,
|
||||
FeatReportsModule,
|
||||
FeatCalculationModule,
|
||||
@@ -100,6 +80,10 @@ import { FeatBusinessObjectsModule } from './modules/feat-business-objects/feat-
|
||||
AppHomeModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
providers: [AppService, Logger, OtelMetricsService],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(RequestLoggerMiddleware).forRoutes('*');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ export class ConfigService extends NestConfigService {
|
||||
dyflexisAppPassword(): string {
|
||||
return this.get('DYFLEXIS_APP_PASSWORD');
|
||||
}
|
||||
databasePort(): string {
|
||||
return this.get('DATABASE_PORT');
|
||||
databasePort(): number {
|
||||
return +this.get('DATABASE_PORT');
|
||||
}
|
||||
databaseHost(): string {
|
||||
return this.get('DATABASE_HOST');
|
||||
@@ -54,4 +54,28 @@ export class ConfigService extends NestConfigService {
|
||||
databaseName(): string {
|
||||
return this.get('DATABASE_DATABASE');
|
||||
}
|
||||
redisPort(): number {
|
||||
return +this.get('REDIS_PORT');
|
||||
}
|
||||
redisHost(): string {
|
||||
return this.get('REDIS_HOST');
|
||||
}
|
||||
storagePath(): string {
|
||||
return this.get('STORAGE_PATH');
|
||||
}
|
||||
mailHost(): string {
|
||||
return this.get('SMTP_HOST');
|
||||
}
|
||||
mailPort(): string {
|
||||
return this.get('SMTP_PORT');
|
||||
}
|
||||
mailUser(): string {
|
||||
return this.get('SMTP_USER');
|
||||
}
|
||||
mailPass(): string {
|
||||
return this.get('SMTP_PASS');
|
||||
}
|
||||
mailFrom(): string {
|
||||
return this.get('MAIL_FROM');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Type,
|
||||
} from '@nestjs/common';
|
||||
import { KyselyConfig } from 'kysely';
|
||||
import { createKyselyProvider } from './kysely-client.factory';
|
||||
import { createKyselyClient, createKyselyProvider } from './kysely-client.factory';
|
||||
|
||||
export const KYSELY_CLIENT_PROVIDER_TOKEN = 'KyselyClientProviderToken';
|
||||
export const KYSELY_CONFIG_TOKEN = 'KYSELY_CONFIG_TOKEN';
|
||||
@@ -21,11 +21,10 @@ export interface KyselyModuleAsyncOptions
|
||||
isGlobal?: boolean;
|
||||
useExisting?: Type<KyselyConfigFactory>;
|
||||
useClass?: Type<KyselyConfigFactory>;
|
||||
// FIX 1: Erlaube Promise im Rückgabetyp
|
||||
useFactory?: (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
...args: any[]
|
||||
) => KyselyConfig;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => Promise<KyselyConfig> | KyselyConfig;
|
||||
inject?: any[];
|
||||
}
|
||||
|
||||
@@ -43,11 +42,22 @@ export class DatabaseModule {
|
||||
}
|
||||
|
||||
public static forRootAsync(options: KyselyModuleAsyncOptions): DynamicModule {
|
||||
// Wir erstellen den Client-Provider, der auf den Config-Provider wartet
|
||||
const clientProvider: Provider = {
|
||||
provide: KYSELY_CLIENT_PROVIDER_TOKEN,
|
||||
useFactory: (config: KyselyConfig) => createKyselyClient(config),
|
||||
inject: [KYSELY_CONFIG_TOKEN], // Injiziert das Ergebnis von createAsyncOptionsProvider
|
||||
};
|
||||
|
||||
return {
|
||||
global: options.isGlobal,
|
||||
module: DatabaseModule,
|
||||
imports: options.imports || [],
|
||||
providers: this.createAsyncProviders(options),
|
||||
providers: [
|
||||
...this.createAsyncProviders(options),
|
||||
clientProvider,
|
||||
],
|
||||
exports: [clientProvider],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,12 +65,12 @@ export class DatabaseModule {
|
||||
options: KyselyModuleAsyncOptions,
|
||||
): Provider[] {
|
||||
if (options.useExisting || options.useFactory) {
|
||||
return this.createAsyncOptionsProvider(options);
|
||||
return [this.createAsyncOptionsProvider(options)];
|
||||
}
|
||||
|
||||
if (options.useClass) {
|
||||
return [
|
||||
...this.createAsyncOptionsProvider(options),
|
||||
this.createAsyncOptionsProvider(options),
|
||||
{
|
||||
provide: options.useClass,
|
||||
useClass: options.useClass,
|
||||
@@ -68,56 +78,28 @@ export class DatabaseModule {
|
||||
];
|
||||
}
|
||||
|
||||
return [...this.createAsyncOptionsProvider(options)];
|
||||
throw new Error('Invalid configuration for DatabaseModule: One of useClass, useExisting, or useFactory must be provided.');
|
||||
}
|
||||
|
||||
// Dieser Provider stellt NUR die Konfiguration (KyselyConfig) bereit
|
||||
private static createAsyncOptionsProvider(
|
||||
options: KyselyModuleAsyncOptions,
|
||||
): Provider[] {
|
||||
): Provider {
|
||||
if (options.useFactory) {
|
||||
const config = options.useFactory(options.inject);
|
||||
return [
|
||||
// {
|
||||
// provide: KYSELY_CONFIG_TOKEN,
|
||||
// useFactory: options.useFactory,
|
||||
// inject: options.inject || [],
|
||||
// },
|
||||
{
|
||||
provide: KYSELY_CLIENT_PROVIDER_TOKEN,
|
||||
useExisting: createKyselyProvider(config)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (options.useExisting) {
|
||||
return [
|
||||
{
|
||||
provide: KYSELY_CONFIG_TOKEN,
|
||||
useFactory: async (optionsFactory: KyselyConfigFactory) =>
|
||||
await optionsFactory.createKyselyConfig(),
|
||||
inject: [options.useExisting],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (options.useClass) {
|
||||
return [
|
||||
{
|
||||
provide: KYSELY_CONFIG_TOKEN,
|
||||
useFactory: async (optionsFactory: KyselyConfigFactory) =>
|
||||
await optionsFactory.createKyselyConfig(),
|
||||
inject: [options.useClass],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
return {
|
||||
provide: KYSELY_CONFIG_TOKEN,
|
||||
useFactory: async (optionsFactory: KyselyConfigFactory) =>
|
||||
await optionsFactory.createKyselyConfig(),
|
||||
inject: [],
|
||||
},
|
||||
];
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject || [],
|
||||
};
|
||||
}
|
||||
|
||||
const inject = [options.useClass || options.useExisting];
|
||||
|
||||
return {
|
||||
provide: KYSELY_CONFIG_TOKEN,
|
||||
useFactory: async (optionsFactory: KyselyConfigFactory) =>
|
||||
await optionsFactory.createKyselyConfig(),
|
||||
inject: inject,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ input AccountingFilterArgs {
|
||||
enum AccountingState {
|
||||
Billable
|
||||
Billed
|
||||
Pending
|
||||
PreparedForBilling
|
||||
}
|
||||
|
||||
@@ -138,6 +139,7 @@ type Metric {
|
||||
|
||||
type Mutation {
|
||||
acceptAnomaly(id: String!): Anomaly!
|
||||
sendApprovalRequest(email: String!, ticketId: String!): Boolean!
|
||||
setAnomalyToSleep(id: String!): Anomaly!
|
||||
setDocumentInfo(approval: ApprovalInfoArgs, tdLocation: String, ticketId: String!): Ticket!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { MailerModule } from '@nestjs-modules/mailer';
|
||||
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { join } from 'path';
|
||||
import { ConfigService } from '../config/config.service';
|
||||
import { MailService } from './mail.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MailerModule.forRootAsync({
|
||||
useFactory: (config: ConfigService) => ({
|
||||
transport: {
|
||||
host: config.mailHost(),
|
||||
port: +config.mailPort(),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: config.mailUser(),
|
||||
pass: config.mailPass(),
|
||||
},
|
||||
pool: true,
|
||||
},
|
||||
defaults: {
|
||||
from: config.mailFrom(),
|
||||
},
|
||||
template: {
|
||||
dir: join(process.cwd(), 'dist/core/mail/templates'),
|
||||
adapter: new HandlebarsAdapter(),
|
||||
options: {
|
||||
strict: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
providers: [MailService],
|
||||
exports: [MailService],
|
||||
})
|
||||
export class MailModule {}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { MailerService } from '@nestjs-modules/mailer';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { TourFile } from '@prisma/client';
|
||||
import { ConfigService } from '../config/config.service';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
private readonly logger = new Logger(MailService.name);
|
||||
|
||||
constructor(private readonly mailerService: MailerService, private readonly config: ConfigService) {}
|
||||
|
||||
async sendGenehmigungsanfrage(recipient: string, transportschein: TourFile) {
|
||||
this.logger.log(
|
||||
`sending Genehmigungsanfrage mail to ${recipient} with Transportschein ${transportschein.originalFilename} for Tour ${transportschein.tourId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
await this.mailerService.sendMail({
|
||||
to: recipient,
|
||||
bcc: 'info@avicenna.hamburg',
|
||||
subject: `Antrag auf Kostenübernahme`,
|
||||
template: 'genehmigungsanfrage',
|
||||
attachments: [
|
||||
{
|
||||
filename: transportschein.originalFilename,
|
||||
path: transportschein.storagePath,
|
||||
contentType: transportschein.mimeType,
|
||||
},
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('failed to send mail', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const MAILING_QUEUE = 'mailing_queue';
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<div style="font-family: sans-serif; white-space: pre-wrap;">
|
||||
Sehr geehrte Damen und Herren,
|
||||
|
||||
für den betreffenden Transport wurde bereits eine Kostenübernahme gemäß § 6 Abs. 3 der Krankentransport-Richtlinie beantragt. Leider liegt uns bislang keine Genehmigung vor.
|
||||
|
||||
Da uns ohne Genehmigung keine Abrechnung mit der Krankenkasse möglich ist, verzögert sich der gesamte Abrechnungsprozess. Um eine private Rechnungsstellung an den Patienten zu vermeiden, bitten wir Sie um zeitnahe Übermittlung der Genehmigung.
|
||||
|
||||
Bitte senden Sie die Unterlagen an eine der folgenden Stellen:
|
||||
• E-Mail: info@avicenna.hamburg
|
||||
• Fax: 040 460006820
|
||||
• Postanschrift: Thomas-Mann-Straße 21, 22175 Hamburg
|
||||
• Institutionskennzeichen: 600224841
|
||||
|
||||
Für Rückfragen stehen wir Ihnen selbstverständlich gerne zur Verfügung.
|
||||
Vielen Dank vorab.
|
||||
Mit freundlichen Grüßen
|
||||
Ihr Team von Avicenna
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<div style="font-family: sans-serif; white-space: pre-wrap;">
|
||||
Sehr geehrte Damen und Herren,<br>
|
||||
<br>
|
||||
hiermit beantragen wir im Auftrag unseres Versicherten die Fahrtkostenerstattung gemäß § 6 Abs. 3 der Krankentransport-Richtlinie.<br>
|
||||
<br>
|
||||
Die notwendigen Details entnehmen Sie bitte der beigefügten ärztlichen Verordnung (PDF-Datei).<br>
|
||||
<br>
|
||||
Bitte senden Sie uns die Genehmigung an:<br>
|
||||
• E-Mail: info@avicenna.hamburg<br>
|
||||
• Fax: 040 460006820<br>
|
||||
• Postanschrift: Thomas-Mann-Straße 21, 22175 Hamburg<br>
|
||||
• Institutionskennzeichen: 600224841<br>
|
||||
<br>
|
||||
Ihr Team der Avicenna Ambulance<br>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
|
||||
import { ConfigService } from '../config/config.service';
|
||||
import { diskStorage } from 'multer';
|
||||
import * as path from 'path';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export function createMulterOptions(config: ConfigService): MulterOptions {
|
||||
const storagePath = config.storagePath();
|
||||
|
||||
return {
|
||||
storage: diskStorage({
|
||||
destination: (req, file, cb) => cb(null, storagePath),
|
||||
filename: (req, file, cb) => {
|
||||
const extension = path.extname(file.originalname);
|
||||
const filename = `${uuidv4()}${extension}`;
|
||||
cb(null, filename);
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum AccountingState {
|
||||
Pending = 'Pending',
|
||||
Billable = 'Billable',
|
||||
PreparedForBilling = 'PreparedForBilling',
|
||||
Billed = 'Billed',
|
||||
|
||||
+25
-1
@@ -5,10 +5,34 @@ import { FeatTicketSystemModule } from '../feat-tickets/ticket-system.module';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { TicketsResolver } from './tickets.resolver';
|
||||
import { TicketSystemService } from './ticket-system.service';
|
||||
import { TicketsController } from './tickets.controller';
|
||||
import { MulterModule } from '@nestjs/platform-express';
|
||||
import { ConfigModule } from 'src/core/config/config.module';
|
||||
import { ConfigService } from 'src/core/config/config.service';
|
||||
import { createMulterOptions } from 'src/core/storage/multer.config';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { TOUR_FILE_UPLOAD_QUEUE } from '../feat-business-objects/entities/tour/tour.tokens';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, FeatTicketSystemModule, FeatBusinessObjectsModule],
|
||||
imports: [
|
||||
PrismaModule,
|
||||
FeatTicketSystemModule,
|
||||
FeatBusinessObjectsModule,
|
||||
MulterModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (config: ConfigService) => createMulterOptions(config),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
BullModule.registerQueue({
|
||||
name: TOUR_FILE_UPLOAD_QUEUE,
|
||||
}),
|
||||
BullModule.registerQueue({
|
||||
name: MAILING_QUEUE,
|
||||
}),
|
||||
],
|
||||
providers: [TicketRepository, TicketSystemService, TicketsResolver],
|
||||
controllers: [TicketsController],
|
||||
exports: [TicketSystemService],
|
||||
})
|
||||
export class ApplicationTicketSystemModule {}
|
||||
|
||||
+26
-2
@@ -1,18 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Ticket, TicketValidationState, Tour } from '@prisma/client';
|
||||
import { Queue } from 'bullmq';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
import { TicketService } from '../feat-business-objects/entities/ticket/ticket.service';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
import { TicketLifecycleService } from '../feat-tickets/ticket-lifecycle.service';
|
||||
import {
|
||||
TicketSystemEventTypes,
|
||||
TicketSystemValidatedEvent,
|
||||
} from '../feat-tickets/ticket-system.events';
|
||||
} from '../feat-business-objects/entities/ticket/ticket-system.events';
|
||||
import { DocumentInfoArgs, TicketFilterArgs } from './ticket-arg.types';
|
||||
import {
|
||||
PaginatedTicketObjectType,
|
||||
@@ -20,6 +23,7 @@ import {
|
||||
TicketStateMetaObjectType,
|
||||
} from './ticket.object-type';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { TourFilesService } from '../feat-business-objects/entities/tour-files/tour-files.service';
|
||||
|
||||
@Injectable()
|
||||
export class TicketSystemService {
|
||||
@@ -36,13 +40,33 @@ export class TicketSystemService {
|
||||
private readonly repository: TicketRepository,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly tourService: TourService,
|
||||
private readonly tourFileService: TourFilesService,
|
||||
private readonly lifecycle: TicketLifecycleService,
|
||||
@InjectQueue(MAILING_QUEUE) private readonly approvalQueue: Queue,
|
||||
) {}
|
||||
|
||||
async updateDocumentInfo(ticketId: string, documentInfo: DocumentInfoArgs) {
|
||||
return this.lifecycle.validateDocumentInfoUpdate(ticketId, documentInfo);
|
||||
}
|
||||
|
||||
async addApprovalRequestJob(ticketId: string, recipientEmail: string): Promise<boolean> {
|
||||
const attachment = await this.tourFileService.findByTicketId(ticketId)
|
||||
|
||||
if (!attachment) {
|
||||
throw new NotFoundException(
|
||||
`Kein Dokument für Ticket ${ticketId} gefunden. Bitte erst hochladen.`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.approvalQueue.add('approval_request', {
|
||||
ticketId,
|
||||
tourFileId: attachment.id,
|
||||
recipientEmail,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
findById(id: string): Promise<Ticket> {
|
||||
return this.ticketService.findById(id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import {
|
||||
Controller,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
Param,
|
||||
Post,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Queue } from 'bullmq';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
import { TOUR_FILE_UPLOAD_QUEUE } from '../feat-business-objects/entities/tour/tour.tokens';
|
||||
import { TourFilesService } from '../feat-business-objects/entities/tour-files/tour-files.service';
|
||||
|
||||
@Controller('api/tickets')
|
||||
export class TicketsController {
|
||||
private readonly logger = new Logger(TicketsController.name);
|
||||
|
||||
constructor(
|
||||
private readonly tourService: TourService,
|
||||
private readonly tourFilesService: TourFilesService,
|
||||
@InjectQueue(TOUR_FILE_UPLOAD_QUEUE)
|
||||
private readonly transportscheinQueue: Queue,
|
||||
) {}
|
||||
|
||||
@Post(':ticketId/files')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@HttpCode(HttpStatus.ACCEPTED)
|
||||
async uploadFile(
|
||||
@Param('ticketId') ticketId: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
this.logger.log(
|
||||
`file received for ticket ${ticketId ?? ''}: ${file.filename}`,
|
||||
);
|
||||
|
||||
const existingFile = await this.tourFilesService.findByTicketId(ticketId);
|
||||
if (existingFile) {
|
||||
return {message: 'File already exists!'}
|
||||
}
|
||||
const storage = file.path;
|
||||
|
||||
const tours = await this.tourService.findByTicketId(ticketId);
|
||||
|
||||
for (const tour of tours) {
|
||||
const tourFile = await this.tourFilesService.createTourFile({
|
||||
ticketId,
|
||||
tourId: tour.id,
|
||||
storagePath: storage,
|
||||
file: file,
|
||||
});
|
||||
|
||||
await this.transportscheinQueue.add('Transportschein', {
|
||||
tourFileId: tourFile.id,
|
||||
});
|
||||
}
|
||||
return { message: 'File accepted and processing started' };
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,14 @@ export class TicketsResolver {
|
||||
return this.ticketsService.getTicketStateKeyFigures(ticketState);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
sendApprovalRequest(
|
||||
@Args('ticketId', {type: () => String}) ticketId: string,
|
||||
@Args('email', {type: () => String}) email: string,
|
||||
) {
|
||||
return this.ticketsService.addApprovalRequestJob(ticketId, email)
|
||||
}
|
||||
|
||||
@Mutation(() => TicketObjectType)
|
||||
setDocumentInfo(
|
||||
@Args('ticketId', { type: () => String }) ticketId: string,
|
||||
|
||||
-1
@@ -19,7 +19,6 @@ export class EmployeeStillWorkingDetector extends AnomalyDetector {
|
||||
const longWorkingEmployees =
|
||||
await this.dataService.findEmployeeStillWorking(day);
|
||||
|
||||
console.log(longWorkingEmployees)
|
||||
if (longWorkingEmployees.length > 0) {
|
||||
anomalies.push(
|
||||
this.createAnomaly(
|
||||
|
||||
-1
@@ -17,7 +17,6 @@ export class IsWorkTimePlannedDetector extends AnomalyDetector {
|
||||
|
||||
for (const day of eachDayOfInterval(inInterval)){
|
||||
const workTimeWithoutPlan = await this.dataService.findWorkForPlannedTime(day)
|
||||
console.log(day, workTimeWithoutPlan)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
+10
-6
@@ -44,11 +44,7 @@ export class TicketRepository {
|
||||
async findAllCreated(): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
},
|
||||
},
|
||||
currentState: TicketValidationState.CREATED,
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
@@ -185,7 +181,9 @@ export class TicketRepository {
|
||||
}
|
||||
|
||||
async createTicket(tours: Tour[]): Promise<Ticket> {
|
||||
this.logger.log(`Creating Ticket for ${tours.length} tours`);
|
||||
this.logger.log(
|
||||
`Creating Ticket for tours: ${tours.map(({ operationId }) => operationId).join(', ')}`,
|
||||
);
|
||||
try {
|
||||
return await this.db.transaction().execute(async (trx) => {
|
||||
const ticket = await trx
|
||||
@@ -224,6 +222,9 @@ export class TicketRepository {
|
||||
}
|
||||
|
||||
async addToursToTicket(ticketId: string, tours: Tour[]): Promise<Ticket> {
|
||||
this.logger.log(
|
||||
`Adding tours to ticket: ${tours.map(({ operationId }) => operationId).join(', ')}`,
|
||||
);
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
@@ -288,6 +289,9 @@ export class TicketRepository {
|
||||
approvalPath?: string;
|
||||
},
|
||||
): Promise<Ticket & { tours: Tour[] }> {
|
||||
this.logger.log(
|
||||
`updating ticket (${ticketId}) with updateset ${JSON.stringify(data)}`,
|
||||
);
|
||||
const { state, ...ticketData } = data;
|
||||
|
||||
await this.db
|
||||
|
||||
+23
-3
@@ -1,20 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Ticket,
|
||||
TicketStateHistory,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
Tour
|
||||
} from '@prisma/client';
|
||||
import {
|
||||
TicketFilterDto,
|
||||
UpdateDocumentInfoDto,
|
||||
} from 'src/modules/feat-business-objects/entities/ticket/tickets.dto';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { TicketSystemEventTypes, TicketSystemValidatedEvent } from './ticket-system.events';
|
||||
|
||||
@Injectable()
|
||||
export class TicketService {
|
||||
constructor(private readonly repository: TicketRepository) {}
|
||||
constructor(private readonly repository: TicketRepository, private readonly eventEmitter: EventEmitter2) {}
|
||||
|
||||
async findById(ticketId: string): Promise<Ticket> {
|
||||
return this.repository.findById(ticketId);
|
||||
@@ -78,6 +80,24 @@ export class TicketService {
|
||||
return this.repository.setStateOfTicket(ticketId, state);
|
||||
}
|
||||
|
||||
async approvalRequestedForTicket(ticketId: string) {
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
state: TicketValidationState.DOCUMENTS_MISSING,
|
||||
approvalState: ApprovalState.REQUESTED,
|
||||
documentInfo: {
|
||||
tdLocation: 'unknown',
|
||||
approval: {
|
||||
whoRequested: 'Avicenna',
|
||||
answeredFrom: 'System',
|
||||
isNeeded: true,
|
||||
},
|
||||
},
|
||||
}).then(() => this.eventEmitter.emit(
|
||||
TicketSystemEventTypes.Validated,
|
||||
new TicketSystemValidatedEvent(0)
|
||||
));
|
||||
}
|
||||
|
||||
async updateTicket(
|
||||
ticketId: string,
|
||||
data: {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export interface TourFileDto {
|
||||
tourId: string;
|
||||
ticketId: string;
|
||||
storagePath: string;
|
||||
file: Express.Multer.File
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
|
||||
import { DB } from 'src/core/database/types';
|
||||
import { TourFileDto } from './tour-files.dto';
|
||||
import { Kysely, UpdateResult } from 'kysely';
|
||||
import { TourFile } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class TourFilesRepository {
|
||||
constructor(
|
||||
@Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely<DB>,
|
||||
) {}
|
||||
|
||||
createTourFile(tourFileDto: TourFileDto): Promise<TourFile> {
|
||||
return this.db
|
||||
.insertInto('TourFile')
|
||||
.values({
|
||||
tourId: tourFileDto.tourId,
|
||||
ticketId: tourFileDto.ticketId,
|
||||
storagePath: tourFileDto.storagePath,
|
||||
originalFilename: tourFileDto.file.originalname,
|
||||
mimeType: tourFileDto.file.mimetype,
|
||||
fileSizeBytes: tourFileDto.file.size,
|
||||
dlUploadStatus: 'pending',
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
findById(id: string): Promise<TourFile> {
|
||||
return this.db
|
||||
.selectFrom('TourFile')
|
||||
.selectAll()
|
||||
.where('TourFile.id', '=', id)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
findByTicketId(ticketId: string): Promise<TourFile> {
|
||||
return this.db
|
||||
.selectFrom('TourFile')
|
||||
.selectAll()
|
||||
.where('TourFile.ticketId', '=', ticketId)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
findByTourId(tourId: string): Promise<TourFile> {
|
||||
return this.db
|
||||
.selectFrom('TourFile')
|
||||
.selectAll()
|
||||
.where('TourFile.tourId', '=', tourId)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
updateStatus(id: string, newStatus: string): Promise<UpdateResult> {
|
||||
return this.db
|
||||
.updateTable('TourFile')
|
||||
.where('id', '=', id)
|
||||
.set('dlUploadStatus', newStatus)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { TourFileDto } from './tour-files.dto';
|
||||
import { TourFilesRepository } from './tour-files.repository';
|
||||
import { TourFile } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class TourFilesService {
|
||||
constructor(private readonly repository: TourFilesRepository) {}
|
||||
|
||||
createTourFile(tourFileDto: TourFileDto): Promise<TourFile> {
|
||||
return this.repository.createTourFile(tourFileDto);
|
||||
}
|
||||
|
||||
findById(tourFileId: string): Promise<TourFile> {
|
||||
return this.repository.findById(tourFileId);
|
||||
}
|
||||
|
||||
findByTicketId(ticketId: string): Promise<TourFile> {
|
||||
return this.repository.findByTicketId(ticketId);
|
||||
}
|
||||
|
||||
findByTourId(tourId: string): Promise<TourFile> {
|
||||
return this.repository.findByTourId(tourId);
|
||||
}
|
||||
|
||||
updateStatus(id: string, newStatus: 'pending' | 'failed' | 'success') {
|
||||
return this.repository.updateStatus(id, newStatus);
|
||||
}
|
||||
}
|
||||
+165
-183
@@ -1,16 +1,13 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Prisma, Tour } from '@prisma/client';
|
||||
import { Prisma, TicketValidationState, Tour } from '@prisma/client';
|
||||
import { endOfDay, Interval, startOfDay } from 'date-fns';
|
||||
import { ExpressionBuilder, Kysely } from 'kysely';
|
||||
import { ExpressionBuilder, ExpressionWrapper, Kysely, SqlBool } from 'kysely';
|
||||
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { DB } from 'src/core/database/types';
|
||||
import { AccountingState } from 'src/modules/app-accounting/accounting-state';
|
||||
import { chunk } from 'src/utils';
|
||||
import {
|
||||
createPrismaQueryFromAccountingFilters,
|
||||
TourAccountingFilterDto,
|
||||
} from './tours.filter';
|
||||
import { TourAccountingFilterDto } from './tours.filter';
|
||||
|
||||
type TourEb = ExpressionBuilder<DB, 'Tour'>;
|
||||
|
||||
@@ -120,35 +117,27 @@ export class TourRepository {
|
||||
interval: Interval,
|
||||
ordinanceTypes: string[] = [],
|
||||
): Promise<Tour[]> {
|
||||
return await this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
...(ordinanceTypes.length
|
||||
? ordinanceTypes.map((ot) => ({
|
||||
ordinanceType: { contains: ot },
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
},
|
||||
...onlyRealTours(),
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
return await this.db
|
||||
.selectFrom('Tour')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
eb('Tour.startDate', '>=', new Date(interval.start)),
|
||||
eb('Tour.startDate', '<', new Date(interval.end)),
|
||||
...(ordinanceTypes.length > 0
|
||||
? [
|
||||
eb.or(
|
||||
ordinanceTypes.map((ot) =>
|
||||
eb('Tour.ordinanceType', 'like', `%${ot}%`),
|
||||
),
|
||||
),
|
||||
]
|
||||
: []),
|
||||
onlyRealToursKysely(eb),
|
||||
]),
|
||||
)
|
||||
.orderBy('Tour.startDate asc')
|
||||
.execute();
|
||||
}
|
||||
|
||||
async findUpdatedAtBetween(
|
||||
@@ -289,60 +278,45 @@ export class TourRepository {
|
||||
async findFirstRecurring(tour: Tour): Promise<Tour | null> {
|
||||
const institutionStreet =
|
||||
tour.direction === 'Hinfahrt' ? tour.targetStreet : tour.startStreet;
|
||||
return await this.prisma.tour.findFirst({
|
||||
where: {
|
||||
type: 'Serie',
|
||||
category: tour.category,
|
||||
ordinanceType: tour.ordinanceType,
|
||||
patientId: tour.patientId,
|
||||
patientName: tour.patientName,
|
||||
patientSurname: tour.patientSurname,
|
||||
patientStreet: tour.patientStreet,
|
||||
patientCity: tour.patientCity,
|
||||
patientZip: tour.patientZip,
|
||||
OR: [
|
||||
{ targetStreet: institutionStreet },
|
||||
{ startStreet: institutionStreet },
|
||||
],
|
||||
rangeEndDate: {
|
||||
gte: tour.startDate,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
return await this.db
|
||||
.selectFrom('Tour')
|
||||
.selectAll()
|
||||
.where('type', '=', 'Serie')
|
||||
.where('category', '=', tour.category)
|
||||
.where('ordinanceType', '=', tour.ordinanceType)
|
||||
.where('patientId', '=', tour.patientId)
|
||||
.where('patientName', '=', tour.patientName)
|
||||
.where('patientSurname', '=', tour.patientSurname)
|
||||
.where('patientStreet', '=', tour.patientStreet)
|
||||
.where('patientCity', '=', tour.patientCity)
|
||||
.where('patientZip', '=', tour.patientZip)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb('targetStreet', '=', institutionStreet),
|
||||
eb('startStreet', '=', institutionStreet),
|
||||
]),
|
||||
)
|
||||
.where('rangeEndDate', '>=', tour.startDate)
|
||||
.where('ticketId', 'is not', null)
|
||||
.orderBy('startDate', 'desc')
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async findOnSameDayWithOppositeDirection(tour: Tour): Promise<Tour | null> {
|
||||
return await this.prisma.tour.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gt: startOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startStreet: tour.targetStreet,
|
||||
},
|
||||
{
|
||||
targetStreet: tour.startStreet,
|
||||
},
|
||||
{
|
||||
direction: tour.direction === 'Hinfahrt' ? 'Rückfahrt' : 'Hinfahrt',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const dayStart = startOfDay(new Date(tour.startDate));
|
||||
const dayEnd = endOfDay(new Date(tour.startDate));
|
||||
const oppositeDirection =
|
||||
tour.direction === 'Hinfahrt' ? 'Rückfahrt' : 'Hinfahrt';
|
||||
|
||||
return await this.db
|
||||
.selectFrom('Tour')
|
||||
.selectAll()
|
||||
.where('startDate', '>', dayStart)
|
||||
.where('startDate', '<', dayEnd)
|
||||
.where('startStreet', '=', tour.targetStreet)
|
||||
.where('targetStreet', '=', tour.startStreet)
|
||||
.where('direction', '=', oppositeDirection)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async findWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
@@ -389,40 +363,36 @@ export class TourRepository {
|
||||
take?: number,
|
||||
skip?: number,
|
||||
): Promise<{ tours: Tour[]; total: number }> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const query: Prisma.TourFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 && !filter?.deviation
|
||||
? { revenueDeviation: { not: 0 } }
|
||||
: {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [
|
||||
...createPrismaQueryFromAccountingFilters(filter),
|
||||
...onlyRealTours(),
|
||||
],
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
return await this.db.transaction().execute(async (trx) => {
|
||||
const tours = await trx
|
||||
.selectFrom('Tour')
|
||||
.selectAll()
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
this.getFilterForAccountingState(eb, state),
|
||||
onlyRealToursKysely(eb),
|
||||
]),
|
||||
)
|
||||
.offset(skip)
|
||||
.limit(take)
|
||||
.execute();
|
||||
|
||||
const [tours, count] = await this.prisma.$transaction([
|
||||
this.prisma.tour.findMany({ ...query }),
|
||||
this.prisma.tour.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tours,
|
||||
total: count,
|
||||
};
|
||||
const total = await trx
|
||||
.selectFrom('Tour')
|
||||
.where((eb) =>
|
||||
eb.and([
|
||||
this.getFilterForAccountingState(eb, state),
|
||||
onlyRealToursKysely(eb),
|
||||
]),
|
||||
)
|
||||
.select((eb) => eb.fn.countAll().as('total'))
|
||||
.executeTakeFirst();
|
||||
|
||||
return {
|
||||
tours,
|
||||
total: Number(total.total),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async calculateAccountingStateMeta(state: AccountingState): Promise<{
|
||||
@@ -431,78 +401,90 @@ export class TourRepository {
|
||||
positiveRevenueDeviation?: number;
|
||||
negativeRevenueDeviation?: number;
|
||||
}> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const ticketWithCurrentStateAndTours: Prisma.TourWhereInput = {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 ? { revenueDeviation: { not: 0 } } : {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [...onlyRealTours()],
|
||||
};
|
||||
|
||||
const [
|
||||
count,
|
||||
revenueSum,
|
||||
positiveRevenueDeviation,
|
||||
negativeRevenueDeviation,
|
||||
] = await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...ticketWithCurrentStateAndTours },
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
},
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
revenueDeviation: {
|
||||
gt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
revenueDeviation: {
|
||||
lt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
const result = await this.db
|
||||
.selectFrom('Tour')
|
||||
.where((eb) => {
|
||||
const conditions = [
|
||||
this.getFilterForAccountingState(eb, state),
|
||||
onlyRealToursKysely(eb),
|
||||
eb('empty', 'is not', null),
|
||||
eb('ticketId', 'is not', null),
|
||||
];
|
||||
return eb.and(conditions);
|
||||
})
|
||||
.select((eb) => [
|
||||
eb.fn.countAll().as('count'),
|
||||
eb.fn.sum<number>('revenue').as('revenueSum'),
|
||||
eb.fn
|
||||
.sum<number>(
|
||||
eb
|
||||
.case()
|
||||
.when('revenueDeviation', '>', 0)
|
||||
.then(eb.ref('revenueDeviation'))
|
||||
.else(0)
|
||||
.end(),
|
||||
)
|
||||
.as('positiveRevenueDeviation'),
|
||||
eb.fn
|
||||
.sum<number>(
|
||||
eb
|
||||
.case()
|
||||
.when('revenueDeviation', '<', 0)
|
||||
.then(eb.ref('revenueDeviation'))
|
||||
.else(0)
|
||||
.end(),
|
||||
)
|
||||
.as('negativeRevenueDeviation'),
|
||||
])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return {
|
||||
count,
|
||||
revenueSum: revenueSum._sum.revenue ?? 0,
|
||||
positiveRevenueDeviation:
|
||||
positiveRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
negativeRevenueDeviation:
|
||||
negativeRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
count: Number(result.count),
|
||||
revenueSum: Number(result.revenueSum ?? 0),
|
||||
positiveRevenueDeviation: Number(result.positiveRevenueDeviation ?? 0),
|
||||
negativeRevenueDeviation: Number(result.negativeRevenueDeviation ?? 0),
|
||||
};
|
||||
}
|
||||
|
||||
private getCheckValueForAccountingState(state: AccountingState): number {
|
||||
private getFilterForAccountingState(
|
||||
eb: ExpressionBuilder<DB, 'Tour'>,
|
||||
state: AccountingState,
|
||||
): ExpressionWrapper<DB, 'Tour', SqlBool> {
|
||||
switch (state) {
|
||||
case AccountingState.Pending:
|
||||
return eb.and([
|
||||
eb('Tour.check', '=', 0),
|
||||
eb('Tour.revenueDeviation', '<>', 0),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Ticket')
|
||||
.select('Ticket.id')
|
||||
.whereRef('Ticket.id', '=', 'Tour.ticketId')
|
||||
.where(
|
||||
'Ticket.currentState',
|
||||
'<>',
|
||||
TicketValidationState.ARCHIVED,
|
||||
),
|
||||
),
|
||||
]);
|
||||
case AccountingState.Billable:
|
||||
return 0;
|
||||
return eb.and([
|
||||
eb('Tour.check', '=', 0),
|
||||
eb('Tour.revenueDeviation', '=', 0),
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('Ticket')
|
||||
.select('Ticket.id')
|
||||
.whereRef('Ticket.id', '=', 'Tour.ticketId')
|
||||
.where(
|
||||
'Ticket.currentState',
|
||||
'=',
|
||||
TicketValidationState.ARCHIVED,
|
||||
),
|
||||
),
|
||||
]);
|
||||
case AccountingState.PreparedForBilling:
|
||||
return 1;
|
||||
case AccountingState.Billed:
|
||||
return 2;
|
||||
return eb.and([eb('Tour.check', '=', 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -1 +1,2 @@
|
||||
export const TOURS_QUEUE = 'tours';
|
||||
export const TOUR_FILE_UPLOAD_QUEUE = 'tour_file_upload'
|
||||
+5
@@ -19,6 +19,8 @@ import { TourRepository } from './entities/tour/tour.repository';
|
||||
import { TourService } from './entities/tour/tour.service';
|
||||
import { WorkTimeRepository } from './entities/work-time/work-time.repository';
|
||||
import { WorkTimeService } from './entities/work-time/work-time.service';
|
||||
import { TourFilesRepository } from './entities/tour-files/tour-files.repository';
|
||||
import { TourFilesService } from './entities/tour-files/tour-files.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
@@ -32,6 +34,7 @@ import { WorkTimeService } from './entities/work-time/work-time.service';
|
||||
TariffService,
|
||||
TicketService,
|
||||
TourService,
|
||||
TourFilesService,
|
||||
WorkTimeService,
|
||||
],
|
||||
providers: [
|
||||
@@ -52,6 +55,8 @@ import { WorkTimeService } from './entities/work-time/work-time.service';
|
||||
TicketService,
|
||||
TourRepository,
|
||||
TourService,
|
||||
TourFilesRepository,
|
||||
TourFilesService,
|
||||
WorkTimeRepository,
|
||||
WorkTimeService,
|
||||
],
|
||||
|
||||
+37
@@ -19,12 +19,16 @@ import {
|
||||
of,
|
||||
reduce,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs';
|
||||
import { EmployeeDispoLiveRaw, PatientRaw, TourRaw } from './source.interface';
|
||||
import { SourceRepository } from './source.repository';
|
||||
import { chunk } from 'src/utils';
|
||||
import { ConfigService } from 'src/core/config/config.service';
|
||||
import { TourExtractor } from './extractors/tour.extractor';
|
||||
import * as fs from 'fs';
|
||||
import * as FormData from 'form-data';
|
||||
import * as path from 'path';
|
||||
|
||||
@Injectable()
|
||||
export class DispoLiveExtractorsService {
|
||||
@@ -36,6 +40,39 @@ export class DispoLiveExtractorsService {
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
uploadFileToTour(tourId: string, storagePath: string, filename: string) {
|
||||
const fileUploadEndpoint = 'custom/upload/userFiles';
|
||||
|
||||
const fileStream = fs.createReadStream(storagePath);
|
||||
const fileid = path.basename(storagePath);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('_id', fileid);
|
||||
formData.append('coll', 'userFiles');
|
||||
formData.append('uploadFromColl', 'fahrberichte');
|
||||
formData.append('dataId', tourId);
|
||||
formData.append('file', fileStream, filename);
|
||||
|
||||
return this.dispoliveLogin(
|
||||
this.config.dispoliveUsername(),
|
||||
this.config.dispolivePassword(),
|
||||
).pipe(
|
||||
switchMap((authCookies) =>
|
||||
this.http.post(
|
||||
`${this.config.dispoliveBaseUrl()}/${fileUploadEndpoint}`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
Cookie: authCookies,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
tap((res) => console.log(res.data)),
|
||||
);
|
||||
}
|
||||
|
||||
extractTour(id: string, jobId: string) {
|
||||
return this.fetchTour(id).pipe(map((tour) => ({ ...tour, jobId })));
|
||||
}
|
||||
|
||||
+8
@@ -11,6 +11,10 @@ import { ToursConsumer } from './processors/tours.consumer';
|
||||
import { SchichtplanungSynchronizer } from './producer/schichtplanung';
|
||||
import { TourUpdateChecker } from './producer/tour-update';
|
||||
import { SCHICHTPLANUNG_QUEUE } from './tokens';
|
||||
import { TourFileUploadConsumer } from './processors/tour-file-upload.consumer';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
import { MailConsumer } from './processors/mail.consumer';
|
||||
import { MailModule } from 'src/core/mail/mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -18,8 +22,10 @@ import { SCHICHTPLANUNG_QUEUE } from './tokens';
|
||||
DataAccessExtractorsModule,
|
||||
DataAccessTransformersModule,
|
||||
DataAccessLoadersModule,
|
||||
MailModule,
|
||||
BullModule.registerQueue({ name: TOURS_QUEUE }),
|
||||
BullModule.registerQueue({ name: SCHICHTPLANUNG_QUEUE }),
|
||||
BullModule.registerQueue({ name: MAILING_QUEUE }),
|
||||
],
|
||||
providers: [
|
||||
ImporterService,
|
||||
@@ -27,6 +33,8 @@ import { SCHICHTPLANUNG_QUEUE } from './tokens';
|
||||
TourUpdateChecker,
|
||||
SchichtplanungSynchronizer,
|
||||
SchichtplanungConsumer,
|
||||
TourFileUploadConsumer,
|
||||
MailConsumer
|
||||
],
|
||||
exports: [ImporterService],
|
||||
})
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Job } from 'bullmq';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
import { MailService } from 'src/core/mail/mail.service';
|
||||
import { TourFilesService } from 'src/modules/feat-business-objects/entities/tour-files/tour-files.service';
|
||||
import { TicketService } from 'src/modules/feat-business-objects/entities/ticket/ticket.service';
|
||||
|
||||
interface ApprovalJobPayload {
|
||||
ticketId: string;
|
||||
tourFileId: string;
|
||||
recipientEmail: string;
|
||||
}
|
||||
|
||||
@Processor(MAILING_QUEUE)
|
||||
export class MailConsumer extends WorkerHost {
|
||||
private readonly logger = new Logger(MailConsumer.name);
|
||||
|
||||
constructor(
|
||||
private readonly mailService: MailService,
|
||||
private readonly tourFileService: TourFilesService,
|
||||
private readonly ticketService: TicketService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async process(job: Job<ApprovalJobPayload>): Promise<void> {
|
||||
const { ticketId, tourFileId, recipientEmail } = job.data;
|
||||
this.logger.log(
|
||||
`Processing approval request for Ticket ${ticketId} -> ${recipientEmail}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const tourFile = await this.tourFileService.findById(tourFileId);
|
||||
await this.mailService.sendGenehmigungsanfrage(recipientEmail, tourFile);
|
||||
await this.ticketService.approvalRequestedForTicket(ticketId);
|
||||
|
||||
this.logger.log(
|
||||
`Approval email sent successfully for Ticket ${ticketId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to send approval email for Ticket ${ticketId}`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { TOUR_FILE_UPLOAD_QUEUE } from 'src/modules/feat-business-objects/entities/tour/tour.tokens';
|
||||
import { DispoLiveExtractorsService } from '../../feat-extractors/dispo-live-extractors.service';
|
||||
import { TourFilesService } from 'src/modules/feat-business-objects/entities/tour-files/tour-files.service';
|
||||
|
||||
@Processor(TOUR_FILE_UPLOAD_QUEUE)
|
||||
export class TourFileUploadConsumer extends WorkerHost {
|
||||
private readonly logger = new Logger(TourFileUploadConsumer.name);
|
||||
|
||||
constructor(
|
||||
private readonly tourFileService: TourFilesService,
|
||||
private readonly dlService: DispoLiveExtractorsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async process(job: Job, token?: string): Promise<any> {
|
||||
const { tourFileId } = job.data;
|
||||
try {
|
||||
const tourFile = await this.tourFileService.findById(tourFileId);
|
||||
|
||||
this.logger.log(`starting upload for tourId: ${tourFile.tourId}`);
|
||||
|
||||
const res = await lastValueFrom(
|
||||
this.dlService.uploadFileToTour(
|
||||
tourFile.tourId,
|
||||
tourFile.storagePath,
|
||||
tourFile.originalFilename,
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(`file upload success (Status: ${res.status})`);
|
||||
await this.tourFileService.updateStatus(tourFile.id, 'success');
|
||||
} catch (error) {
|
||||
console.error('upload failed', error);
|
||||
await this.tourFileService.updateStatus(tourFileId, 'failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
set,
|
||||
subDays,
|
||||
subHours,
|
||||
subWeeks
|
||||
subWeeks,
|
||||
} from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
@@ -253,13 +253,13 @@ export class KpiService {
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<KpiInfoObjectType> {
|
||||
return {
|
||||
main: await this.fastMovingTicketPercentMetric(ticketState),
|
||||
secondary: await this.averageTimePerStateMetric(ticketState),
|
||||
main: { label: '', value: 0 }, // await this.fastMovingTicketPercentMetric(ticketState),
|
||||
secondary: { label: '', value: 0 }, // await this.averageTimePerStateMetric(ticketState),
|
||||
additional: [
|
||||
await this.ticketCountPerStateMetric(ticketState),
|
||||
...(ticketState === TicketValidationState.USER_INTERACTION_REQUIRED
|
||||
? [await this.ticketApprovalRequiredCountMetric(ticketState)]
|
||||
: []),
|
||||
// ...(ticketState === TicketValidationState.USER_INTERACTION_REQUIRED
|
||||
// ? [await this.ticketApprovalRequiredCountMetric(ticketState)]
|
||||
// : []),
|
||||
await this.tourCountPerTicketStateMetric(ticketState),
|
||||
],
|
||||
};
|
||||
@@ -317,9 +317,9 @@ export class KpiService {
|
||||
state: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: 'asc'
|
||||
id: 'asc',
|
||||
},
|
||||
take: 1000
|
||||
take: 1000,
|
||||
});
|
||||
const timePerTickets = movedTickets
|
||||
.map(({ state }) => {
|
||||
|
||||
+26
-33
@@ -59,13 +59,19 @@ export class TicketLifecycleService {
|
||||
|
||||
switch (ticket.currentState) {
|
||||
case TicketValidationState.USER_INTERACTION_REQUIRED:
|
||||
const approvalStateUpdate = documentInfo.approval.isNeeded
|
||||
? ApprovalState.REQUESTED
|
||||
: ApprovalState.FREE;
|
||||
let approvalStateUpdate: ApprovalState;
|
||||
if (documentInfo.approval.whoRequested == 'no') {
|
||||
approvalStateUpdate = ApprovalState.REQUIRED;
|
||||
} else {
|
||||
approvalStateUpdate = documentInfo.approval.isNeeded
|
||||
? ApprovalState.REQUESTED
|
||||
: ApprovalState.FREE;
|
||||
}
|
||||
|
||||
let stateUpdate: TicketValidationState =
|
||||
TicketValidationState.USER_INTERACTION_REQUIRED;
|
||||
if (
|
||||
if (approvalStateUpdate === ApprovalState.REQUIRED) {
|
||||
} else if (
|
||||
documentInfo.tdLocation === 'company' &&
|
||||
approvalStateUpdate === ApprovalState.FREE
|
||||
)
|
||||
@@ -137,29 +143,12 @@ export class TicketLifecycleService {
|
||||
: ApprovalState.REQUIRED,
|
||||
});
|
||||
} else {
|
||||
// NOTE: Condition required to get these tickets out of USER_INTERACTION_REQUIRED
|
||||
if (
|
||||
ticket.tours.every(
|
||||
({ ordinanceType }) =>
|
||||
ordinanceType.indexOf('BTW') > -1 ||
|
||||
ordinanceType.indexOf('TSW') > -1,
|
||||
) &&
|
||||
ticket.currentState != TicketValidationState.TD_UNCERTAIN
|
||||
) {
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.TD_UNCERTAIN,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
} else {
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
}
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.TD_UNCERTAIN,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await this.ticketService.setActionablesOnTicket(
|
||||
@@ -260,13 +249,17 @@ export class TicketLifecycleService {
|
||||
|
||||
if (
|
||||
[
|
||||
'Einweisung',
|
||||
'Entlassung',
|
||||
'Entlassung Vorbestellung',
|
||||
'Konsil KH - Dia',
|
||||
'1. Entlassung',
|
||||
'2. Entlassung Vorbestellung',
|
||||
'3. Einweisung',
|
||||
'5. Verlegung',
|
||||
'6. Barzahlung',
|
||||
'Privatfahrt Rechnung',
|
||||
'Tagesklinik',
|
||||
'Verlegung',
|
||||
'Reha',
|
||||
'Tagesklinik - AKW',
|
||||
'Tagesklinik - ASK',
|
||||
'Tagesklinik - MKH',
|
||||
'Transportschein abholen',
|
||||
].includes(tour.category) ||
|
||||
tour.healthInsurance === 'KOSTENTRÄGER PATIENT'
|
||||
) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TicketLifecycleService } from './ticket-lifecycle.service';
|
||||
import {
|
||||
TicketSystemEventTypes,
|
||||
TicketSystemValidatedEvent,
|
||||
} from './ticket-system.events';
|
||||
} from '../feat-business-objects/entities/ticket/ticket-system.events';
|
||||
|
||||
@Injectable()
|
||||
export class TicketService {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
id: syn-extraction
|
||||
namespace: dev
|
||||
description: Run Avicenna extraction with synapse
|
||||
|
||||
labels:
|
||||
env: dev
|
||||
project: avicenna
|
||||
|
||||
# triggers:
|
||||
# - id: schedule
|
||||
# type: io.kestra.plugin.core.trigger.Schedule
|
||||
# cron: "*/5 * * * *"
|
||||
|
||||
tasks:
|
||||
- id: synapseConfig
|
||||
type: io.kestra.plugin.core.storage.Write
|
||||
content: |
|
||||
pipelines:
|
||||
- name: "patient"
|
||||
source:
|
||||
type: "rest_api"
|
||||
config:
|
||||
base_url: "https://avicenna.dispolive.de"
|
||||
path: "/data/patientenStammdaten"
|
||||
method: POST
|
||||
records_path: "data"
|
||||
auth:
|
||||
strategy: "dispolive"
|
||||
dispolive:
|
||||
username: Marcel
|
||||
password: arndt1.!
|
||||
rate_limit:
|
||||
requests_per_second: 5
|
||||
burst: 1
|
||||
destination:
|
||||
type: "csv"
|
||||
config:
|
||||
path: "./patient.csv"
|
||||
transform:
|
||||
- strategy: keep_fields
|
||||
config:
|
||||
fields:
|
||||
- "_id"
|
||||
- "name"
|
||||
- "surname"
|
||||
- "street"
|
||||
- "zip"
|
||||
- "city"
|
||||
- "birthday"
|
||||
- "kkId"
|
||||
- "krankenkasse"
|
||||
- strategy: rename_fields
|
||||
config:
|
||||
mapping:
|
||||
_id: id
|
||||
- strategy: add_fields
|
||||
config:
|
||||
fields: |
|
||||
{
|
||||
"jobId": "{{ execution.id }}"
|
||||
}
|
||||
- name: "attendanceRegistration"
|
||||
sync:
|
||||
strategy: "time_range_iteration"
|
||||
time_range_iteration:
|
||||
start_date: "2025-02-01"
|
||||
end_date: "2025-02-28"
|
||||
date_format: "2006-01-02"
|
||||
interval: 1d
|
||||
source:
|
||||
type: "rest_api"
|
||||
config:
|
||||
base_url: "https://app.planning.nu"
|
||||
{% raw %}path: "/avicennaambulance/api2/attendance-registration/{{.SliceStart}}"{% endraw %}
|
||||
records_path: "attendanceRegistration"
|
||||
auth:
|
||||
strategy: "header"
|
||||
header:
|
||||
header: "authorization"
|
||||
token: "Token ${SYN_DYFLEXIS_V2_TOKEN}"
|
||||
rate_limit:
|
||||
requests_per_second: 5
|
||||
burst: 1
|
||||
destination:
|
||||
type: "csv"
|
||||
config:
|
||||
path: "./_output/02-2025-attendance-registrations.csv"
|
||||
- id: run
|
||||
type: io.kestra.plugin.docker.Run
|
||||
containerImage: thedevilisdero/synapse:latest
|
||||
pullPolicy: ALWAYS
|
||||
inputFiles:
|
||||
synapse.yaml: "{{ outputs.synapseConfig.uri }}"
|
||||
commands:
|
||||
- --config
|
||||
- synapse.yaml
|
||||
- run
|
||||
- patient
|
||||
outputFiles:
|
||||
- patient.csv
|
||||
- id: log
|
||||
type: io.kestra.plugin.core.log.Log
|
||||
message: "{{ read(outputs.run.outputFiles['patient.csv']) }}"
|
||||
- id: deleteSource
|
||||
type: io.kestra.plugin.jdbc.postgresql.Query
|
||||
url: jdbc:postgresql://avicenna_db:5432/avicenna
|
||||
username: johndoe
|
||||
password: randompassword
|
||||
sql: DELETE FROM "PatientSource";
|
||||
- id: copyin
|
||||
type: io.kestra.plugin.jdbc.postgresql.CopyIn
|
||||
url: jdbc:postgresql://avicenna_db:5432/avicenna
|
||||
username: johndoe
|
||||
password: randompassword
|
||||
format: CSV
|
||||
from: "{{ outputs.run.outputFiles['patient.csv'] }}"
|
||||
table: "\"PatientSource\""
|
||||
header: true
|
||||
columns:
|
||||
- birthday
|
||||
- city
|
||||
- id
|
||||
- "\"jobId\""
|
||||
- "\"kkId\""
|
||||
- krankenkasse
|
||||
- name
|
||||
- street
|
||||
- surname
|
||||
- zip
|
||||
# - id: loadStatic
|
||||
# type: io.kestra.plugin.core.flow.EachSequential
|
||||
# value: '{{ outputs.getPartitions.vars.partitions }}'
|
||||
# - id: getPartitions
|
||||
# type: io.kestra.plugin.scripts.python.Script
|
||||
# taskRunner:
|
||||
# type: io.kestra.plugin.scripts.runner.docker.Docker
|
||||
# containerImage: ghcr.io/kestra-io/pydata:latest
|
||||
# beforeCommands:
|
||||
# - pip install -U arrow
|
||||
# script: |
|
||||
# from kestra import Kestra
|
||||
# import arrow
|
||||
# partitions = []
|
||||
|
||||
# now = arrow.utcnow() #.shift(months=-1)
|
||||
# partitions.append("from={}&to={}".format(now.span('month')[0].format('YYYY-MM-DD'), now.shift(days=14).format('YYYY-MM-DD')))
|
||||
# lastMonth = now.shift(months=-1)
|
||||
# partitions.append("from={}&to={}".format(lastMonth.span('month')[0].format('YYYY-MM-DD'), lastMonth.span('month')[-1].format('YYYY-MM-DD')))
|
||||
# monthBeforeLast = lastMonth.shift(months=-1)
|
||||
# partitions.append("from={}&to={}".format(monthBeforeLast.span('month')[0].format('YYYY-MM-DD'), monthBeforeLast.span('month')[-1].format('YYYY-MM-DD')))
|
||||
# monthBeforeLast = monthBeforeLast.shift(months=-1)
|
||||
# partitions.append("from={}&to={}".format(monthBeforeLast.span('month')[0].format('YYYY-MM-DD'), monthBeforeLast.span('month')[-1].format('YYYY-MM-DD')))
|
||||
# Kestra.outputs({'partitions': partitions})
|
||||
# - id: loadThreeMonths
|
||||
# type: io.kestra.plugin.core.flow.EachSequential
|
||||
# value: '{{ outputs.getPartitions.vars.partitions }}'
|
||||
# tasks:
|
||||
# - id: loadTours
|
||||
# type: "io.kestra.plugin.docker.Run"
|
||||
# containerImage: thedevilisdero/synapse:latest
|
||||
# - id: "request"
|
||||
# type: "io.kestra.plugin.core.http.Request"
|
||||
# uri: "http://avicenna_data:3000/etl?{{ taskrun.value }}"
|
||||
# headers:
|
||||
# user-agent: "kestra-io"
|
||||
# options:
|
||||
# readTimeout: "PT180S"
|
||||
# method: "GET"
|
||||
@@ -0,0 +1,99 @@
|
||||
id: main_sync_workflow
|
||||
namespace: ceo.genius.etl
|
||||
|
||||
tasks:
|
||||
# Schritt 1: Definiere alle Entitäten und berechne die Zeiträume für die Synchronisation
|
||||
- id: setup_parameters
|
||||
type: io.kestra.plugin.scripts.python.Script
|
||||
containerImage: python:3.11-slim
|
||||
beforeCommands:
|
||||
- "pip install python-dateutil"
|
||||
- "pip install kestra"
|
||||
script: |
|
||||
from kestra import Kestra
|
||||
import json
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
today = date.today()
|
||||
|
||||
# 1. Definiere die Zeiträume: aktueller Monat + die letzten 3 vollen Monate
|
||||
date_ranges = []
|
||||
# Aktueller Monat (vom 1. bis heute)
|
||||
date_ranges.append({
|
||||
"start": today.replace(day=1).strftime("%Y-%m-%d"),
|
||||
"end": today.strftime("%Y-%m-%d")
|
||||
})
|
||||
# Letzte 3 volle Monate
|
||||
for i in range(1, 4):
|
||||
month = today - relativedelta(months=i)
|
||||
start_of_month = month.replace(day=1)
|
||||
end_of_month = (start_of_month + relativedelta(months=1)) - relativedelta(days=1)
|
||||
date_ranges.append({
|
||||
"start": start_of_month.strftime("%Y-%m-%d"),
|
||||
"end": end_of_month.strftime("%Y-%m-%d")
|
||||
})
|
||||
|
||||
# 2. Definiere die Entitäten mit ihrem Schema.
|
||||
# HIER IST DER ZENTRALE ORT, UM NEUE ENTITÄTEN HINZUZUFÜGEN ODER ZU ÄNDERN.
|
||||
config = {
|
||||
"static_entities": [
|
||||
{
|
||||
"name": "patient",
|
||||
"db_table": "PatientSource",
|
||||
"columns": ["birthday", "city", "id", "\"jobId\"", "\"kkId\"", "krankenkasse", "name", "street", "surname", "zip"]
|
||||
}
|
||||
# FÜGE HIER WEITERE STATISCHE ENTITÄTEN HINZU
|
||||
# {
|
||||
# "name": "neue_entitaet",
|
||||
# "db_table": "NeueEntitaetSource",
|
||||
# "columns": ["spalte1", "spalte2"]
|
||||
# }
|
||||
],
|
||||
"dynamic_entities": [
|
||||
{
|
||||
"name": "attendanceRegistration",
|
||||
"db_table": "AttendanceRegistrationSource",
|
||||
"columns": ["\"attendanceRegistrationId\"", "\"dateTime\"", "\"employeeId\"", "event", "\"jobId\"", "\"personnelNumber\""]
|
||||
}
|
||||
# FÜGE HIER WEITERE DYNAMISCHE ENTITÄTEN HINZU
|
||||
],
|
||||
"date_ranges": date_ranges
|
||||
}
|
||||
Kestra.outputs({'entity_config': config})
|
||||
print(json.dumps(config))
|
||||
|
||||
- id: sync_static_entities
|
||||
type: io.kestra.plugin.core.flow.EachParallel
|
||||
value: "{{ outputs.setup_parameters.vars.entity_config.static_entities }}"
|
||||
tasks:
|
||||
- id: run_static_subflow
|
||||
type: io.kestra.plugin.core.flow.Subflow
|
||||
namespace: ceo.genius.etl
|
||||
flowId: subflow_static_entity
|
||||
inputs:
|
||||
entity_name: "{{ taskrun.value.name }}"
|
||||
db_table: "{{ taskrun.value.db_table }}"
|
||||
columns: "{{ taskrun.value.columns }}"
|
||||
|
||||
- id: sync_dynamic_entities
|
||||
type: io.kestra.plugin.core.flow.EachParallel
|
||||
value: |
|
||||
{%- set result = [] -%}
|
||||
{%- for entity in outputs.setup_parameters.output.dynamic_entities -%}
|
||||
{%- for range in outputs.setup_parameters.output.date_ranges -%}
|
||||
{%- do result.append({ "entity": entity, "range": range }) -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
{{ result | json }}
|
||||
tasks:
|
||||
- id: run_dynamic_subflow
|
||||
type: io.kestra.plugin.core.flow.Subflow
|
||||
namespace: ceo.genius.etl # Passe dies an, falls die Subflows in einem anderen Namespace liegen
|
||||
flowId: subflow_dynamic_entity
|
||||
inputs:
|
||||
entity_name: "{{ taskrun.value.entity.name }}"
|
||||
db_table: "{{ taskrun.value.entity.db_table }}"
|
||||
columns: "{{ taskrun.value.entity.columns }}"
|
||||
startDate: "{{ taskrun.value.range.start }}"
|
||||
endDate: "{{ taskrun.value.range.end }}"
|
||||
@@ -0,0 +1,51 @@
|
||||
id: subflow_static_entity
|
||||
namespace: ceo.genius.etl
|
||||
|
||||
inputs:
|
||||
- id: entity_name
|
||||
type: STRING
|
||||
- id: db_table
|
||||
type: STRING
|
||||
- id: columns
|
||||
type: JSON
|
||||
|
||||
tasks:
|
||||
# - id: render_synapse_config
|
||||
# type: io.kestra.core.tasks.storages.template
|
||||
# templateUri: "kestra://{{ flow.namespace }}/_templates/{{ inputs.entity_name }}.yml"
|
||||
|
||||
- id: render_synapse_config
|
||||
type: io.kestra.plugin.core.templating.TemplatedTask
|
||||
spec: |
|
||||
{{ read('nsfile://' ~ flow.namespace ~ '/configs/' ~ inputs.entity_name ~ '.yml') }}
|
||||
|
||||
- id: run_synapse
|
||||
type: io.kestra.plugin.docker.Run
|
||||
containerImage: thedevilisdero/synapse:latest
|
||||
pullPolicy: ALWAYS
|
||||
inputFiles:
|
||||
synapse.yaml: "{{ read('nsfile://' ~ flow.namespace ~ '/configs/' ~ inputs.entity_name ~ '.yml') }}"
|
||||
commands:
|
||||
- --config
|
||||
- synapse.yaml
|
||||
- run
|
||||
- "{{ inputs.entity_name }}"
|
||||
outputFiles:
|
||||
- "{{ inputs.entity_name }}.csv"
|
||||
|
||||
- id: delete_from_table
|
||||
type: io.kestra.plugin.jdbc.postgresql.Query
|
||||
url: jdbc:postgresql://avicenna_db:5432/avicenna # ANPASSEN
|
||||
username: johndoe # ANPASSEN
|
||||
password: randompassword # ANPASSEN
|
||||
sql: 'DELETE FROM "{{ inputs.db_table }}";'
|
||||
|
||||
- id: copy_to_table
|
||||
type: io.kestra.plugin.jdbc.postgresql.CopyIn
|
||||
url: jdbc:postgresql://avicenna_db:5432/avicenna # ANPASSEN
|
||||
username: johndoe # ANPASSEN
|
||||
password: randompassword # ANPASSEN
|
||||
from: "{{ outputs.run_synapse.outputFiles[inputs.entity_name ~ '.csv'] }}"
|
||||
table: '"{{ inputs.db_table }}"'
|
||||
header: true
|
||||
columns: "{{ inputs.columns }}"
|
||||
@@ -58,8 +58,7 @@ async function main() {
|
||||
const schichtplanung = await getSchichtplanungForDay(new Date('2025-08-11')).then(response => response.json());
|
||||
const chunks = chunk(schichtplanung.map(({id}) => id), 1)
|
||||
for(const chunk of chunks) {
|
||||
const res = await deleteSchichtplanungItems(chunk as string[])
|
||||
console.log(res)
|
||||
await deleteSchichtplanungItems(chunk as string[])
|
||||
await sleep(200)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user