diff --git a/ReportImp/ClaudeStundenkontoTest/ClaudeStundenkontoTest.csproj b/ReportImp/ClaudeStundenkontoTest/ClaudeStundenkontoTest.csproj new file mode 100644 index 000000000..927dd692a --- /dev/null +++ b/ReportImp/ClaudeStundenkontoTest/ClaudeStundenkontoTest.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {C1FED668-ADB9-47E0-B589-1389618E1B6E} + Library + Properties + ClaudeStundenkontoTest + 9999999999_Reports + v4.7.2 + 512 + + + + true + full + false + ..\..\CustomerDlls\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\CustomerDlls\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {B0D73E3D-4AE7-4024-93A6-DB1F46D7CCEE} + Data + + + {40d8b312-ea64-49e3-a31a-5e57ed4d5654} + Report + + + {094331c3-ecee-4c89-bbfd-4c9ded89f0ef} + Service + + + {2f50b83d-a3f0-4ec4-979a-3f9b7e3d8ed4} + Shared + + + + \ No newline at end of file diff --git a/ReportImp/ClaudeStundenkontoTest/CustomMitarbeiterstundenkontoBerechnung.cs b/ReportImp/ClaudeStundenkontoTest/CustomMitarbeiterstundenkontoBerechnung.cs new file mode 100644 index 000000000..e0a8f5c79 --- /dev/null +++ b/ReportImp/ClaudeStundenkontoTest/CustomMitarbeiterstundenkontoBerechnung.cs @@ -0,0 +1,3285 @@ +using BeWo.Data.Access; +using BeWo.Data.Entities; +using BeWo.Report.ReportObjects; +using BeWo.Service.Configuration; +using BeWo.Service.DCEntityMapper; +using BeWo.Service.Plugins; +using BeWo.Service.ServiceImplementations; +using BS.Shared; +using BS.Shared.Core; +using BS.Shared.DataContracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using BeWo.Report; +using BS.Shared.Extensions; + +namespace ClaudeStundenkontoTest +{ + public class CustomMitarbeiterstundenkontoBerechnung : MitarbeiterstundenkontoBerechnung + { + private decimal maxWochenstunden = 40; + private AppSettings appSettings; + private const int ANZ_WOCHENTAGE = 5; + protected new VacationService vacationService; + + // Claude: Caches für Performance-Optimierung (Instanz-scoped, eine Instanz pro Report-Lauf) + // Claude: Feiertagsliste pro Jahr cachen, damit vacationService.GetFeiertage nicht mehrfach aufgerufen wird + private Dictionary> _feiertageListeCache; + // Claude: HashSet pro Jahr für O(1)-Lookup in IstFeiertag statt linearer Suche + private Dictionary> _feiertageSetCache; + // Claude: Cache für FeiertagDC-Objekt pro Datum (verwendet in CreateDayDetail) + private Dictionary _feiertagDCByDateCache; + // Claude: Cache der gesamten DienstEintraege pro Mitarbeiter, einmal pro Report-Lauf geladen + private Dictionary> _allDienstEintraegeByEmpCache; + // Claude: Cache der gesamten Dienstvertretungen pro Mitarbeiter, einmal pro Report-Lauf geladen + private Dictionary> _allDienstvertretungenByEmpCache; + // Claude: Cache von HatArbeitszeitImZeitraum (nur start==ende-Fall) pro Arbeitszeiten-Liste und Datum + // Claude: Wir nutzen einen ReferenceEqualityComparer um sicherzustellen, dass NHibernate-Proxy-Collections nicht via überschriebenes Equals kollidieren + private Dictionary, Dictionary> _hatArbeitszeitImZeitraumCache; + + // Claude: Comparer, der ausschließlich Referenz-Gleichheit nutzt (verhindert unbeabsichtigte Treffer bei NHibernate-Collections mit überschriebenem Equals) + private sealed class ReferenceEqualityComparer : IEqualityComparer where T : class + { + public static readonly ReferenceEqualityComparer Default = new ReferenceEqualityComparer(); + public bool Equals(T x, T y) { return ReferenceEquals(x, y); } + public int GetHashCode(T obj) { return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); } + } + + protected new AppSettings Settings + { + get + { + return appSettings; + } + + } + + public new bool CheckIst { get; set; } + + public override void CreateReport(MitarbeiterstundenkontoRO ro) + { + appSettings = AppSettings.CreateSettings(); + vacationService = PluginLoader.FindClass(); + + CreateEmployeeDetails(ro); + + CreateTeamDetails(ro); + + CreateTotals(ro); + } + + public override void CreateTotals(MitarbeiterstundenkontoRO ro) + { + foreach (var empDetail in ro.EmployeeDetailList) + { + + ro.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit; + ro.FlsSollGesamt += empDetail.FlsSoll; + ro.FlsIstGesamt += empDetail.FlsIst; + ro.MittelbarIst += empDetail.MittelbarIst; + ro.FlsDiffGesamt += empDetail.FlsDiff; + ro.StundenGesamt += empDetail.StundenGesamtIst; + } + + if (ro.FlsSollGesamt != 0) + ro.RelationIstSollGesamt = (ro.FlsIstGesamt / ro.FlsSollGesamt) * 100m; + + if (ro.FlsIstGesamt + ro.MittelbarIst != 0) + ro.RelationFlsZuMittelbar = (ro.FlsIstGesamt / (ro.FlsIstGesamt + ro.MittelbarIst)) * 100; + } + + public override void CreateTeamDetails(MitarbeiterstundenkontoRO ro) + { + foreach (var teamDetail in ro.TeamDetailList) + { + teamDetail.EmployeeDetailList = FilterEmployeeList(teamDetail.EmployeeDetailList); + foreach (var empDetail in teamDetail.EmployeeDetailList) + { + teamDetail.SollGesamt += empDetail.StundenSollOhneUrlaubKrankheit; + teamDetail.FlsSollGesamt += empDetail.FlsSoll; + teamDetail.FlsIstGesamt += empDetail.FlsIst; + teamDetail.MittelbarIst += empDetail.MittelbarIst; + teamDetail.FlsDiffGesamt += empDetail.FlsDiff; + teamDetail.StundenGesamt += empDetail.StundenGesamtIst; + } + if (teamDetail.FlsSollGesamt != 0) + teamDetail.RelationIstSollGesamt = (teamDetail.FlsIstGesamt / teamDetail.FlsSollGesamt) * 100m; + + if (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst != 0) + teamDetail.RelationFlsZuMittelbar = (teamDetail.FlsIstGesamt / (teamDetail.FlsIstGesamt + teamDetail.MittelbarIst)) * 100; + } + + + } + + public override void CreateEmployeeDetails(MitarbeiterstundenkontoRO ro) + { + foreach (var empDetail in ro.EmployeeDetailList) + { + + DateTime monatsAnfang = ro.ReportStartDate; + DateTime monatsEnde = monatsAnfang.AddMonths(1); + DateTime berichtsAnfang = monatsAnfang; + DateTime berichtsEnde = monatsEnde; + + Employee emp = empDetail.Employee; + + decimal bu = 0; + + var c = GetContractForDate(emp, ro.ReportStartDate, ro.ReportEndDate); + + if (c != null) + { + if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang) + { + berichtsAnfang = c.EntryDate.Value; + } + if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde) + { + berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1); + } + empDetail.HasContract = true; + } + + if (BerechnungsStartdatum > berichtsAnfang) + { + berichtsAnfang = BerechnungsStartdatum; + } + + ServiceRecordSummary summaryTotal = new ServiceRecordSummary(); + summaryTotal.RecordsImBerichtszeitraum = new List(); + + decimal pause1 = 0; + decimal pause2 = 0; + decimal weeklyTotalHours = 0; + decimal flsSollGesamt = 0; + decimal? fehlZeitenGesamt = null; + decimal? tageUrlaubGesamt = null; + decimal? tageKrankheitGesamt = null; + decimal? abwesenheitTageGesamt = null; + decimal workdaysTotal = 0; + decimal stundenSollGesamt = 0; + decimal tatSollStdGesamt = 0; + decimal azDurchFeiertage = 0; + + IList groupFilteredRecords = empDetail.AllServiceRecords; + if (groupFilteredRecords == null) + { + IList recordsAll = DAOFactory.SearchDAO.FindServiceRecords(emp.Oid, null); + recordsAll = AnalysisServiceImp.FilterActiveServiceRecords(recordsAll); + + // + groupFilteredRecords = new List(); + Dictionary groupBookingDict = new Dictionary(); + foreach (var item in recordsAll) + { + + if (!item.GroupOid.HasValue || !groupBookingDict.ContainsKey(item.GroupOid.Value)) + { + groupFilteredRecords.Add(item); + if (item.GroupOid.HasValue) + { + groupBookingDict.Add(item.GroupOid.Value, true); + } + } + } + + empDetail.AllServiceRecords = groupFilteredRecords; + } + + while (berichtsAnfang < berichtsEnde) + { + + + + StarteBerechnung(berichtsAnfang, berichtsEnde, empDetail); + + empDetail.ArbeitstageImBerichtszeitraum = GetArbeitstageImBereich(berichtsAnfang, berichtsEnde, emp); + empDetail.ArbeitstageProWoche = ANZ_WOCHENTAGE; + + + workdaysTotal += empDetail.ArbeitstageImBerichtszeitraum; + weeklyTotalHours = 0; + + if (c != null) + { + if (c.WeeklyTotalHours.HasValue) + { + weeklyTotalHours = c.WeeklyTotalHours.Value; + bu = c.WeeklyTotalHours.Value / this.maxWochenstunden; + } + if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0) + { + empDetail.ArbeitstageProWoche = c.WeeklyDays.Value; + } + + empDetail.WeeklyFLS = c.WeeklyFLS; + empDetail.HourlyRate = c.HourlyRate; + empDetail.EmploymentType = c.EmploymentType; + + + } + empDetail.SollProTag = (weeklyTotalHours / empDetail.ArbeitstageProWoche); + empDetail.SollProWoche = BerechneSollProWoche(c, empDetail); + if (empDetail.SollProTag == 0 && empDetail.SollProWoche > 0 && empDetail.ArbeitstageProWoche > 0) + { + empDetail.SollProTag = empDetail.SollProWoche / empDetail.ArbeitstageProWoche; + + } + HandleServiceRecords(empDetail, groupFilteredRecords, c); + + if (CheckIst) + { + + var summary = BerechneStunden(empDetail, groupFilteredRecords, berichtsAnfang, berichtsEnde); + summaryTotal.IstAbrechenbar += summary.IstAbrechenbar; + summaryTotal.IstNichtAbrechenbar += summary.IstNichtAbrechenbar; + summaryTotal.RecordsImBerichtszeitraum.AddRange(summary.RecordsImBerichtszeitraum); + + if (SollPauseBerechnen()) + { + pause1 += BerechnePauseMinuten(summary.RecordsImBerichtszeitraum); + pause2 += BerechneAutoPausen(summary.RecordsImBerichtszeitraum); + + } + } + + + decimal? stundenSollProMonat = null; + decimal? stundenSollImVertragsUndBerichtzeitraum = null; + if (empDetail.HasContract) + { + stundenSollImVertragsUndBerichtzeitraum = GetSollNachArbeitszeiten(empDetail.Employee, berichtsAnfang, berichtsEnde); + } + + if (!stundenSollImVertragsUndBerichtzeitraum.HasValue) + { + stundenSollProMonat = BerechneSollProMonat(emp, berichtsAnfang, berichtsEnde); + stundenSollImVertragsUndBerichtzeitraum = stundenSollProMonat; + } + + if (!stundenSollImVertragsUndBerichtzeitraum.HasValue) + { + if (!stundenSollImVertragsUndBerichtzeitraum.HasValue) + { + if (empDetail.ArbeitstageProWoche <= 5) + { + stundenSollImVertragsUndBerichtzeitraum = + empDetail.ArbeitstageImBerichtszeitraum * (weeklyTotalHours / 5); + } + else + { + stundenSollImVertragsUndBerichtzeitraum = + empDetail.ArbeitstageImBerichtszeitraum * (weeklyTotalHours / empDetail.ArbeitstageProWoche); + } + } + } + + + stundenSollGesamt += stundenSollImVertragsUndBerichtzeitraum ?? 0; + + + empDetail.StundenSollMax = stundenSollGesamt; + + + var tatSollStdImVertragsUndBerichtzeitraum = stundenSollImVertragsUndBerichtzeitraum ?? 0; + + + decimal? fehlZeiten = GetFehlzeiten(groupFilteredRecords, berichtsAnfang, berichtsEnde); + if (fehlZeiten.HasValue) + { + if (!fehlZeitenGesamt.HasValue) + fehlZeitenGesamt = 0; + fehlZeitenGesamt += fehlZeiten; + + tatSollStdImVertragsUndBerichtzeitraum -= fehlZeiten.Value; + } + + AktuellBearbeitetesEmployeeDetail = empDetail; + var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, berichtsAnfang, berichtsEnde, empDetail.ArbeitstageProWoche); + empDetail.AbsenceTimeDays = ath; + + if (ath.TageUrlaub.HasValue) + { + if (!tageUrlaubGesamt.HasValue) + tageUrlaubGesamt = 0; + tageUrlaubGesamt += ath.TageUrlaub; + } + if (ath.TageKrankheit.HasValue) + { + if (!tageKrankheitGesamt.HasValue) + tageKrankheitGesamt = 0; + tageKrankheitGesamt += ath.TageKrankheit; + } + + + if (ath.TageGesamt.HasValue) + { + if (!abwesenheitTageGesamt.HasValue) + abwesenheitTageGesamt = 0; + abwesenheitTageGesamt += ath.TageGesamt; + + var minusStunden = BerechneStundenDurchAbwesenheiten(ath, berichtsAnfang, berichtsEnde, c, empDetail); + tatSollStdImVertragsUndBerichtzeitraum -= minusStunden; + if (tatSollStdImVertragsUndBerichtzeitraum < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein()) + { + + tatSollStdImVertragsUndBerichtzeitraum = 0; + + } + + } + + + var ath2 = GetAbwesenheitenDurchServiceRecords(empDetail, c, groupFilteredRecords, berichtsAnfang, berichtsEnde); + + if (ath2 != null) + { + + var stunden = ath2.StundenKrankheit + ath2.StundenUrlaub + ath2.StundenSonstiges; + + tatSollStdImVertragsUndBerichtzeitraum -= stunden; + + if (tatSollStdImVertragsUndBerichtzeitraum < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein()) + { + tatSollStdImVertragsUndBerichtzeitraum = 0; + } + + if (empDetail.SollProTag > 0) + { + if (!abwesenheitTageGesamt.HasValue) + abwesenheitTageGesamt = 0; + abwesenheitTageGesamt += stunden / empDetail.SollProTag; + + if (ath2.StundenKrankheit > 0) + { + if (!tageKrankheitGesamt.HasValue) + { + tageKrankheitGesamt = 0; + } + tageKrankheitGesamt += ath2.StundenKrankheit / empDetail.SollProTag; + } + + if (ath2.StundenUrlaub > 0) + { + if (!tageUrlaubGesamt.HasValue) + { + tageUrlaubGesamt = 0; + } + tageUrlaubGesamt += ath2.StundenUrlaub / empDetail.SollProTag; + } + } + + + } + + tatSollStdGesamt += tatSollStdImVertragsUndBerichtzeitraum; + + + flsSollGesamt += BerechneFlsSoll(emp, c, berichtsAnfang, berichtsEnde, tatSollStdImVertragsUndBerichtzeitraum, stundenSollProMonat); + + if (ath.StundenAlsIst > 0) + { + tatSollStdGesamt += ath.StundenAlsIst; + summaryTotal.IstNichtAbrechenbar += ath.StundenAlsIst * 60; + } + + azDurchFeiertage += BerechneGesamtArbeitszeitDurchFeiertage(empDetail, berichtsAnfang, berichtsEnde); + + + berichtsAnfang = berichtsEnde; + berichtsEnde = monatsEnde; + c = GetNextValidContractForDate(berichtsAnfang, emp); + + if (c != null) + { + if (c.EntryDate.HasValue && c.EntryDate >= berichtsAnfang) + berichtsAnfang = c.EntryDate.Value; + if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde) + { + berichtsEnde = c.ContractEndDate.Value.Date.AddDays(1); + } + } + } + + if (AddRateFactorToIst() != 1) + { + summaryTotal.IstAbrechenbar *= AddRateFactorToIst(); + } + summaryTotal.IstAbrechenbar /= 60m; + summaryTotal.IstNichtAbrechenbar /= 60m; + + empDetail.WorkDaysThisMonth = workdaysTotal; + //decimal stundenSollMax = arbeitstage * (maxWochenstunden / xxx) * bu; + + + + empDetail.Fehlzeiten = fehlZeitenGesamt; + empDetail.TageUrlaub = tageUrlaubGesamt; + empDetail.TageKrankheit = tageKrankheitGesamt; + empDetail.UrlaubUndKrankheit = abwesenheitTageGesamt; + + empDetail.StundenSollOhneUrlaubKrankheit = tatSollStdGesamt; + + empDetail.FlsSoll = flsSollGesamt; + + + + empDetail.MittelbarIst = summaryTotal.IstNichtAbrechenbar; + empDetail.Bu = bu; + empDetail.FlsIst = summaryTotal.IstAbrechenbar; + empDetail.FlsDiff = summaryTotal.IstAbrechenbar - empDetail.FlsSoll; + empDetail.StundenMittelbarIst = summaryTotal.IstNichtAbrechenbar; + empDetail.StundenGesamtIst = summaryTotal.IstAbrechenbar + summaryTotal.IstNichtAbrechenbar; + + empDetail.StundenGesamtIst -= pause1 / 60; + empDetail.StundenGesamtIst -= pause2 / 60; + + empDetail.StundenGesamtIst += azDurchFeiertage; + + if (empDetail.FlsSoll != 0) + empDetail.RelationIstSoll = (empDetail.FlsIst / empDetail.FlsSoll) * 100m; + + + decimal? ueberstunden = empDetail.StundenGesamtIst - empDetail.StundenSollOhneUrlaubKrankheit; + decimal? ueberstundenVormonat; + + + decimal alteExtraUeberstunden = 0; + BerechneUeberstundenBis(empDetail, groupFilteredRecords, ro.ReportStartDate, out ueberstundenVormonat, + out alteExtraUeberstunden); + + // Claude: Globaler Cache für DienstEintraege/Dienstvertretungen, nur einmal pro Report-Lauf aus dem DAO holen statt pro Mitarbeiter + //EnsureDienstplanDataLoaded(); + //var empOidNullable = empDetail.Employee.Oid; + //var alleDiensteintraege = new List(); + //var alleDienstvertretungen = new List(); + //if (empOidNullable.HasValue) + //{ + // List deList; + // if (_allDienstEintraegeByEmpCache.TryGetValue(empOidNullable.Value, out deList)) + // { + // alleDiensteintraege.AddRange(deList); + // } + // List dvList; + // if (_allDienstvertretungenByEmpCache.TryGetValue(empOidNullable.Value, out dvList)) + // { + // alleDienstvertretungen.AddRange(dvList); + // } + //} + + //decimal? ueberstundenVormonatFuerDienstplanung; + //decimal? alteExtraUeberstundenDienstplanung; + //empDetail.DienstplanIst += BerechneDienstplanIst(empDetail, alleDiensteintraege, alleDienstvertretungen, ro.ReportStartDate, + //out ueberstundenVormonatFuerDienstplanung, out alteExtraUeberstundenDienstplanung); // ABCDEF + + //var dienstplanIstAlsStunden = empDetail.DienstplanIst / 60; + + decimal extraStunden = 0; + + BerechneExtraUeberstunden(emp, monatsAnfang, monatsEnde, out extraStunden); // OverTimes + + if (ShouldCreateDayDetails()) + { + + empDetail.Days = CreateDayDetails(monatsAnfang, monatsEnde, empDetail, summaryTotal.RecordsImBerichtszeitraum); + var ah = GetAdditionalHours(empDetail.Days, monatsAnfang, monatsEnde, empDetail, summaryTotal.RecordsImBerichtszeitraum); + ueberstunden += ah; + empDetail.StundenGesamtIst += ah; + } + + if (empDetail.StundenGesamtIst != 0) + empDetail.RelationFlsZuMittelbar = (summaryTotal.IstAbrechenbar / empDetail.StundenGesamtIst) * 100; + + empDetail.Ueberstunden = ueberstunden; + empDetail.UeberstundenVormonat = ueberstundenVormonat; + if (!empDetail.UeberstundenGesamt.HasValue) + empDetail.UeberstundenGesamt = ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden; + else + empDetail.UeberstundenGesamt += ((ueberstundenVormonat ?? 0) + (ueberstunden ?? 0)) + extraStunden; + empDetail.UeberstundenAusbezahlt = extraStunden; + + //Urlaubskonto (todo) + empDetail.UrlaubGesamtImAktuellenJahr = + GetAbsenceTimeDaysWithWorkWeeks(emp, new DateTime(monatsAnfang.Year, 1, 1), monatsEnde, empDetail.ArbeitstageProWoche).TageUrlaub; + + // Aktuellen Resturlaub berechnen + var vs = PluginLoader.FindClass(); + // Darstellungsvariante, da das normale Urlaubskonto nur den Resturlaubsstand vom Ende des Jahres anzeigt. + var konto = vs.BerechneUrlaubskontoZurDarstellung(MapperFactory.EmployeeDC_Employee.MapToNewDC(emp), ro.ReportStartDate.Year, berichtsEnde.AddDays(-1)); + if (konto != null && konto.Count > 0) + { + var alleVertraglichenBudgets = konto + .Where(k => k.DatumZurSortierung == null && ZaehltZuUrlaubsbudget(k.Erlaeuterung)) + .ToList(); + + empDetail.Resturlaub = alleVertraglichenBudgets.Sum(b => Convert.ToDecimal(b.AnzahlTage)); + + // Jeder Entry enthält den Zeitraum und die abzuziehenden Tage vom Gesamtbudget. + foreach (var entry in konto) + { + // Hat entry kein Datum, dann ist es nur eine Überschrift in der Darstellung und hat hier keinen Nutzen + if (entry.DatumZurSortierung != null || entry.Erlaeuterung.Contains("Resturlaub aus")) + { + // Ist das Datum nach dem Enddatum des Stundenkontos braucht nicht weitergerechnet werden + if (entry.DatumZurSortierung > ro.ReportEndDate.AddTicks(-1)) + break; + + empDetail.Resturlaub += Convert.ToDecimal(entry.AnzahlTage); // += weil Tage immer als Minus dargestellt sind + } + } + } + + CalculateEmployeeSalary(empDetail, berichtsAnfang, berichtsEnde); + } + + ro.EmployeeDetailList = FilterEmployeeList(ro.EmployeeDetailList); + } + + public override bool ZaehltZuUrlaubsbudget(string erlaueterung) + { + if (erlaueterung.Contains("Urlaubstage laut Vertrag") || + erlaueterung.Contains("Sonderurlaub") || + erlaueterung.Contains("Regeneration")) + return true; + else + return false; + } + + #region Ist-Berechnung für Dienstplanung + public decimal BerechneDienstplanIst(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, List eintraege, List vertretungen, DateTime monat, out decimal? ueberstunden, out decimal? alteExtraUeberstunden) // ABCDEF + { + ueberstunden = null; + alteExtraUeberstunden = 0; + + decimal dpIst = 0; + + + MitarbeiterstundenkontoRO.EmployeeDetail edTotal = new MitarbeiterstundenkontoRO.EmployeeDetail(); + Employee emp = empDetail.Employee; + Contract c = emp.FirstContract; + + // Der Einfachheit halber: Vertretungen in Einträge umwandeln + eintraege.AddRange(VertretungenUmwandeln(vertretungen.ToList())); + + if (c != null) + { + Dictionary> eintragDict = CreateDiensteintragDictByDate(eintraege); // Anzahl der Einträge korrekt + + DateTime start = c.EntryDate.HasValue ? c.EntryDate.Value : new DateTime(2011, 1, 1); + while (c != null && start < monat) + { + DateTime contractEnd = c.ContractEndDate.HasValue ? c.ContractEndDate.Value : DateTime.MaxValue; + decimal bu = 0; + decimal weeklyTotalHours = this.maxWochenstunden; + int anzWochentage = ANZ_WOCHENTAGE; + + if (c.WeeklyTotalHours.HasValue) + { + weeklyTotalHours = c.WeeklyTotalHours.Value; + bu = c.WeeklyTotalHours.Value / maxWochenstunden; + } + + while (start < monat && start < contractEnd) + { + decimal flsIst = 0; + decimal mittelbarIst = 0; + decimal minutenMonat = 0; + DateTime ende = new DateTime(start.Year, start.Month, 1).AddMonths(1); + if (contractEnd < DateTime.MaxValue && contractEnd.AddDays(1) < ende) + ende = contractEnd.AddDays(1); + + List entriesThisMonth = new List(); + string key = string.Format("{0:yyyyMM}", start); + + if (eintragDict.ContainsKey(key)) + { + foreach (var item in eintragDict[key]) + { + minutenMonat += Convert.ToDecimal(item.DauerInStunden * 60); + entriesThisMonth.Add(item); + } + } + dpIst += minutenMonat; + + decimal arbeitstage = GetArbeitstageImBereich(start, ende, emp); + + var arbeitstageProWoche = anzWochentage; + if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0) + arbeitstageProWoche = c.WeeklyDays.Value; + + var soll = GetSollNachArbeitszeiten(emp, start, ende); + if (!soll.HasValue) + { + var stundenSollProMonat = BerechneSollProMonat(emp, start, ende); + soll = stundenSollProMonat; + } + + if (!soll.HasValue) + { + + if (!soll.HasValue) + { + decimal maxSollMonat = arbeitstage * (maxWochenstunden / arbeitstageProWoche); + if (arbeitstageProWoche <= 5) + { + maxSollMonat = arbeitstage * (maxWochenstunden / 5); + } + + soll = maxSollMonat * bu; + } + } + + + + decimal sollMitarbeiter = soll ?? 0; + + MitarbeiterstundenkontoRO.EmployeeDetail ed = new MitarbeiterstundenkontoRO.EmployeeDetail(); + ed.SollProTag = weeklyTotalHours / arbeitstageProWoche; + ed.ArbeitstageProWoche = arbeitstageProWoche; + ed.ArbeitstageImBerichtszeitraum = arbeitstage; + ed.SollProWoche = BerechneSollProWoche(c, ed); + ed.Employee = emp; + ed.StundenSollMax = sollMitarbeiter; + + AktuellBearbeitetesEmployeeDetail = ed; + var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, start, ende, arbeitstageProWoche); + + + + ed.AbsenceTimeDays = ath; + + + if (ath.TageGesamt.HasValue) + { + var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, ed); + sollMitarbeiter -= minusStunden; + if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein()) + { + sollMitarbeiter = 0; + } + } + + decimal stundenMonat = minutenMonat / 60m; + stundenMonat = ((flsIst * AddRateFactorToIst()) + mittelbarIst) / 60m; + + + if (!ueberstunden.HasValue) + ueberstunden = 0; + ueberstunden += stundenMonat - sollMitarbeiter; + + decimal extraUeberstunden = 0; + BerechneExtraUeberstunden(emp, start, ende, out extraUeberstunden); + ueberstunden += extraUeberstunden; + + alteExtraUeberstunden = extraUeberstunden; + + ed.StundenSollOhneUrlaubKrankheit = sollMitarbeiter; + ed.StundenGesamtIst = stundenMonat; + ed.Ueberstunden = ueberstunden; + ed.UeberstundenVormonat = edTotal.Ueberstunden; + + edTotal.StundenSollOhneUrlaubKrankheit += sollMitarbeiter; + edTotal.StundenSollMax += soll ?? 0; + edTotal.FlsIst += flsIst / 60; + edTotal.MittelbarIst += mittelbarIst / 60; + edTotal.StundenGesamtIst += stundenMonat; + edTotal.Ueberstunden = ueberstunden; + + start = ende; + } + + if (start >= contractEnd) + { + var ctemp = GetNextValidContractForDate(start, emp); + if (ctemp != null && ctemp.Oid != c.Oid) + { + c = ctemp; + } + else + { + c = null; + } + + if (c != null && c.EntryDate.HasValue && c.EntryDate >= start) + start = c.EntryDate.Value; + else + { + start = start.AddMonths(1); + c = GetNextValidContractForDate(start, emp); + if (c != null && c.EntryDate.HasValue && c.EntryDate >= start) + start = c.EntryDate.Value; + } + } + } + } + return dpIst; + } + + // Claude: Lädt DienstEintraege und Dienstvertretungen einmalig per DAO und gruppiert nach EmployeeOid (statt pro Mitarbeiter den DAO erneut zu fragen) + private void EnsureDienstplanDataLoaded() + { + if (_allDienstEintraegeByEmpCache == null) + { + _allDienstEintraegeByEmpCache = new Dictionary>(); + foreach (var d in DAOFactory.GenericDAO.GetAllActive()) + { + List list; + if (!_allDienstEintraegeByEmpCache.TryGetValue(d.EmployeeOid, out list)) + { + list = new List(); + _allDienstEintraegeByEmpCache[d.EmployeeOid] = list; + } + list.Add(d); + } + } + if (_allDienstvertretungenByEmpCache == null) + { + _allDienstvertretungenByEmpCache = new Dictionary>(); + foreach (var d in DAOFactory.GenericDAO.GetAllActive()) + { + List list; + if (!_allDienstvertretungenByEmpCache.TryGetValue(d.EmployeeOid, out list)) + { + list = new List(); + _allDienstvertretungenByEmpCache[d.EmployeeOid] = list; + } + list.Add(d); + } + } + } + + private Dictionary> CreateDiensteintragDictByDate(List eintraege) + { + var dict = new Dictionary>(); + + foreach (var item in eintraege) + { + //if (!IsServiceRecordFehlzeit(item)) + //{ + + string key = string.Format("{0:yyyyMM}", item.Datum); + + List list; + if (!dict.ContainsKey(key)) + { + list = new List(); + dict.Add(key, list); + } + else + { + list = dict[key]; + } + list.Add(item); + + //} + } + return dict; + } + + private List VertretungenUmwandeln(List vertretungen) + { + List eintraege = new List(); + + foreach (var v in vertretungen) + { + eintraege.Add(new DienstEintrag() + { + Datum = v.Datum, + DauerInStunden = v.DauerInStunden, + DienstOid = v.DienstOid, + EmployeeOid = v.EmployeeOid, + Endzeit = v.Endzeit, + Notice = v.Notice, + Startzeit = v.Startzeit, + WohnheimOid = v.WohnheimOid, + Zeile = v.Zeile + }); + } + + return eintraege; + } + #endregion + + + protected override void StarteBerechnung(DateTime berichtsAnfang, DateTime berichtsEnde, MitarbeiterstundenkontoRO.EmployeeDetail empDetail) + { + //do nothing + } + + + protected override AbsenceTimeHours GetAbwesenheitenDurchServiceRecords(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, Contract c, IList groupFilteredRecords, DateTime berichtsAnfang, DateTime berichtsEnde) + { + return null; + } + + protected override decimal BerechneFlsSoll(Employee emp, Contract contract, DateTime berichtsAnfang, DateTime berichtsEnde, decimal tatSollStdImVertragsUndBerichtzeitraum, decimal? stundenSollProMonat) + { + var festeflsSollProMonat = BerechneFlsSollProMonat(emp, berichtsAnfang, berichtsEnde); + decimal flsSollImVertragsUndBerichtzeitraum = 0; + + if (festeflsSollProMonat.HasValue) + { + return festeflsSollProMonat.Value; + //flsSollImVertragsUndBerichtzeitraum = tatSollStdImVertragsUndBerichtzeitraum; + + //if (stundenSollProMonat.HasValue) + //{ + // flsSollImVertragsUndBerichtzeitraum *= (festeflsSollProMonat.Value / stundenSollProMonat.Value); + //} + } + else + { + if (contract != null && contract.WeeklyFLS.HasValue) + { + flsSollImVertragsUndBerichtzeitraum = tatSollStdImVertragsUndBerichtzeitraum; + + if (HatArbeitszeitImZeitraum(emp.Arbeitszeiten, berichtsAnfang, berichtsEnde)) + { + var wochenArbeitszeit = GetWochenArbeitszeit(emp.Arbeitszeiten, berichtsAnfang, berichtsEnde); + + if (wochenArbeitszeit > 0) + { + flsSollImVertragsUndBerichtzeitraum *= contract.WeeklyFLS.Value / wochenArbeitszeit; + } + + } + else if (contract.WeeklyTotalHours.HasValue && contract.WeeklyTotalHours.Value != 0) + { + flsSollImVertragsUndBerichtzeitraum *= contract.WeeklyFLS.Value / contract.WeeklyTotalHours.Value; + } + + } + } + + return flsSollImVertragsUndBerichtzeitraum; + } + + private decimal GetWochenArbeitszeit(IList arbeitszeiten, DateTime start, DateTime ende) + { + if (arbeitszeiten != null) + { + foreach (var az in arbeitszeiten) + { + if ((!az.GueltigVon.HasValue || az.GueltigVon.Value < ende) && + (!az.GueltigBis.HasValue || az.GueltigBis.Value >= start)) + { + + DateTime wochenStart = start; + while (wochenStart.DayOfWeek != DayOfWeek.Monday) + { + wochenStart = wochenStart.AddDays(1); + } + + decimal stunden = 0; + for (int i = 0; i < 7; i++) + { + stunden += GetArbeitszeitStundenFuerTag(az, wochenStart); + + wochenStart = wochenStart.AddDays(1); + } + + return stunden; + } + } + } + + return 0; + } + + protected override bool StundenDurchAbwesenheitenDuerfenNegativSein() + { + return false; + } + + protected override bool SchneideZuvieleAbwesenheitsstundenAb() + { + // sorgt dafür dass bei Überschreitung des Tagessolls nicht die Abwesenheitszeit verwendet wird + // wenn auch Überschreitung Wochensoll möglich sein soll muss zusätzlich GetSollStundenForAbsenceTime angepasst werden + return true; + } + + protected override decimal? GetSollNachArbeitszeiten(Employee emp, DateTime start, DateTime ende) + { + if (BerechneSollNurBisZumAktuellenTag()) + { + if (ende > DateTime.Now.Date.AddDays(1)) + { + ende = DateTime.Now.Date.AddDays(1); + } + } + + if (emp.Arbeitszeiten != null && emp.Arbeitszeiten.Count > 0) + { + if (HatArbeitszeitImZeitraum(emp.Arbeitszeiten, start, ende)) + { + decimal soll = 0; + + DateTime startTemp = start; + + while (startTemp < ende) + { + soll += GetSollNachArbeitszeitenFuerTag(emp, startTemp); + startTemp = startTemp.AddDays(1); + } + + return soll; + } + } + + return null; + } + + protected override bool HatArbeitszeitImZeitraum(IList arbeitszeiten, DateTime start, DateTime ende) + { + // Claude: Cache für den häufigen (date == date)-Aufruf aus CalculateAbsenceTimesForDay/BerechneSollStundenFuerTag + if (arbeitszeiten != null && start == ende) + { + if (_hatArbeitszeitImZeitraumCache == null) + { + _hatArbeitszeitImZeitraumCache = new Dictionary, Dictionary>(ReferenceEqualityComparer>.Default); + } + Dictionary innerCache; + if (!_hatArbeitszeitImZeitraumCache.TryGetValue(arbeitszeiten, out innerCache)) + { + innerCache = new Dictionary(); + _hatArbeitszeitImZeitraumCache[arbeitszeiten] = innerCache; + } + bool cachedResult; + if (innerCache.TryGetValue(start, out cachedResult)) + { + return cachedResult; + } + cachedResult = HatArbeitszeitImZeitraumIntern(arbeitszeiten, start, ende); + innerCache[start] = cachedResult; + return cachedResult; + } + return HatArbeitszeitImZeitraumIntern(arbeitszeiten, start, ende); + } + + // Claude: Ursprüngliche Logik, ohne Cache - aus HatArbeitszeitImZeitraum extrahiert + private static bool HatArbeitszeitImZeitraumIntern(IList arbeitszeiten, DateTime start, DateTime ende) + { + if (arbeitszeiten != null) + { + foreach (var az in arbeitszeiten) + { + if ((!az.GueltigVon.HasValue || az.GueltigVon.Value < ende) && (!az.GueltigBis.HasValue || az.GueltigBis.Value >= start) && az.ArbeitszeitEintraege != null && az.ArbeitszeitEintraege.Count > 0) + { + return true; + } + } + } + + return false; + } + + protected override decimal GetSollNachArbeitszeitenFuerTag(Employee emp, DateTime tag) + { + decimal soll = 0; + var fak = GetFeiertagsFaktor(tag); + + if (BerechneFeiertageAlsArbeitszeit()) + { + if (fak == 1) + { + fak = 0; + } + } + + if (fak < 1) + { + if (emp.Arbeitszeiten != null) + { + foreach (var az in emp.Arbeitszeiten) + { + if ((!az.GueltigVon.HasValue || az.GueltigVon.Value <= tag) && + (!az.GueltigBis.HasValue || az.GueltigBis.Value >= tag)) + { + var std = GetArbeitszeitStundenFuerTag(az, tag); + soll += (std * (1 - fak)); + } + } + } + } + + return soll; + } + + // Claude: Zurückgesetzt auf Original-Implementierung. Cache hat in Einzelfällen abweichende Werte erzeugt. + protected decimal GetArbeitszeitStundenFuerTag(Arbeitszeit az, DateTime tag) + { + decimal stunden = 0; + foreach (var ae in az.ArbeitszeitEintraege) + { + if (ae.IstAmGleichenWochentag(tag)) + { + String dateStr1 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitVon); + String dateStr2 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitBis); + + DateTime date1; + DateTime date2; + + if (DateTime.TryParse(dateStr1, out date1) && DateTime.TryParse(dateStr2, out date2)) + { + + stunden += (decimal)date2.Subtract(date1).TotalHours; + } + } + } + + return stunden; + } + + protected override void CalculateEmployeeSalary(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, DateTime start, DateTime ende) + { + decimal maxpayment = 0; + + Contract cstart = empDetail.Employee.GetValidContractForDate(start); + Contract cend = empDetail.Employee.GetValidContractForDate(ende); + if (cstart != null && cstart.HourlyRate.HasValue && cstart.WeeklyTotalHours.HasValue && cend != null) + { + if (cstart.Oid.Value == cend.Oid.Value || !cend.EntryDate.HasValue) + { + maxpayment = cstart.HourlyRate.Value * cstart.WeeklyTotalHours.Value * 4; + } + else + { + decimal totalHoursPerDay = cstart.WeeklyTotalHours.Value / 7m; + DateTime end = cend.EntryDate.Value; + TimeSpan ts = end.Subtract(start); + int days = (int)ts.TotalDays; + + maxpayment = totalHoursPerDay * cstart.HourlyRate.Value * days; + + Contract c2 = empDetail.Employee.GetValidContractForDate(end.AddDays(1)); + if (c2 != null && c2.HourlyRate.HasValue && c2.WeeklyTotalHours.HasValue) + { + decimal totalHoursPerDay2 = c2.WeeklyTotalHours.Value / 7m; + TimeSpan ts2 = ende.Subtract(end); + int days2 = (int)ts2.TotalDays + 1; + + maxpayment += totalHoursPerDay2 * c2.HourlyRate.Value * days2; + } + } + + + } + empDetail.EmployeeSalaryByContract = maxpayment; + + decimal salary = 0; + + if (empDetail.ServiceRecordsInSelectedTimeSpan != null) + { + foreach (var sr in empDetail.ServiceRecordsInSelectedTimeSpan) + { + if (sr.ServiceDescription.Name != "Freizeit" || sr.ServiceDescription.ServiceCategory.Name != "Selbstzahler Freizeit") + { + Contract con = empDetail.Employee.GetValidContractForDate(sr.Start); + if (con != null && con.HourlyRate.HasValue) + { + salary += con.HourlyRate.Value * (sr.RoundedDuration / 60); + } + + } + } + } + + empDetail.EmployeeSalaryByHours = salary; + } + + protected override ServiceRecordSummary BerechneStunden(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList groupFilteredRecords, DateTime berichtsAnfang, DateTime berichtsEnde) + { + decimal flsGesamt = 0; + decimal arbeitszeit = 0; + + var sum = new ServiceRecordSummary(); + sum.RecordsImBerichtszeitraum = new List(); + + empDetail.ServiceRecordsInSelectedTimeSpan = new List(); + + foreach (var item in groupFilteredRecords) + { + if (!IsServiceRecordFehlzeit(item)) + { + if (item.Start.Value >= berichtsAnfang && item.Start.Value < berichtsEnde) + { + //String test = String.Format("{0:dd.MM.yyyy HH:mm}, {1:0} Minuten, {2}, {3}", item.Start, item.RoundedDuration, item.ServiceDescription.Name, item.Notice); + + HandleServiceRecord(empDetail, item); + + if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung) + { + empDetail.ServiceRecordsInSelectedTimeSpan.Add(item); + + decimal duration = GetDuration(item, empDetail); + + if (IstArbeitszeit(item)) + { + arbeitszeit += duration; + } + else if (item.ServiceDescription != null && item.ServiceDescription.ServiceCategory != null && item.ServiceDescription.ServiceCategory.IsBillable) + { + flsGesamt += duration; + } + } + else if (AddServiceRecordToDuration(item)) + { + empDetail.ServiceRecordsInSelectedTimeSpan.Add(item); + + decimal duration = GetDuration(item, empDetail); + + if (item.ServiceDescription != null && + item.ServiceDescription.ServiceCategory != null && + item.ServiceDescription.ServiceCategory.IsBillable) + sum.IstAbrechenbar += duration; + else + sum.IstNichtAbrechenbar += duration; + + + //Prüfe ProzentStundenkonto, wenn der Wert != 100 ist, dann packe die prozentuale Differenz von abbrechenbar auf nicht abbrechenbar + decimal prozent = 100; + if (item.ServiceDescription.ProzentStundenkonto.HasValue) + { + prozent = item.ServiceDescription.ProzentStundenkonto.Value; + } + else if (item.ServiceDescription.ServiceCategory.ProzentStundenkonto.HasValue) + { + prozent = item.ServiceDescription.ServiceCategory.ProzentStundenkonto.Value; + } + if (prozent == 0) + { + int test = 0; + } + if (prozent != 100 && prozent > 0) + { + var ohneProzent = duration / prozent * 100; + + var diff = duration - ohneProzent; + + sum.IstAbrechenbar -= diff; + sum.IstNichtAbrechenbar += diff; + } + + + + } + sum.RecordsImBerichtszeitraum.Add(item); + } + } + } + if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung) + { + sum.IstAbrechenbar = flsGesamt; + sum.IstNichtAbrechenbar = arbeitszeit - flsGesamt; + } + return sum; + } + + public override bool UseServiceDescriptionPercentage() + { + return false; + } + + public override decimal BerechneAutoPausen(IList recordsThisMonth) + { + return 0; + } + + public override decimal BerechnePauseMinuten(IList srList) + { + decimal pausenZeit = 0; + + List pausen = new List(); + List nichtpausen = new List(); + + foreach (var sr in srList) + { + if (sr.ServiceDescription.Name == "Pause") + { + pausen.Add(sr); + } + else + { + nichtpausen.Add(sr); + } + } + + foreach (var pause in pausen) + { + foreach (var arbeitszeit in nichtpausen) + { + var pauseSpan = new DateTimeSpan + { + StartDateTime = pause.Start.Value, + EndDateTime = pause.Start.Value.AddMinutes((double)pause.RoundedDuration) + }; + + var arbeitszeitSpan = new DateTimeSpan + { + StartDateTime = arbeitszeit.Start.Value, + EndDateTime = arbeitszeit.Start.Value.AddMinutes((double)arbeitszeit.RoundedDuration) + }; + + if (arbeitszeit.GroupOid.HasValue) + { + arbeitszeitSpan.EndDateTime = + arbeitszeit.Start.Value.AddMinutes((double)(arbeitszeit.GroupRoundedDuration ?? 0)); + } + + + if (pauseSpan.Overlaps(arbeitszeitSpan)) + { + DateTime startUeberlappung = pauseSpan.StartDateTime; + if (arbeitszeitSpan.StartDateTime > startUeberlappung) + startUeberlappung = arbeitszeitSpan.StartDateTime; + + DateTime endUeberlappung = pauseSpan.EndDateTime; + + if (arbeitszeitSpan.EndDateTime < endUeberlappung) + endUeberlappung = arbeitszeitSpan.EndDateTime; + + var overlapping = (decimal)endUeberlappung.Subtract(startUeberlappung).TotalMinutes; + + pausenZeit += overlapping; + } + } + } + return pausenZeit; + } + + public override bool SollPauseBerechnen() + { + return false; + } + + public override bool BerechneSollNurBisZumAktuellenTag() + { + return false; + } + + public override List FilterEmployeeList(List employeeDetailList) + { + List newEmpList = new List(); + + foreach (var ed in employeeDetailList) + { + if (ed.Employee.IsActive == ActivationTypeId.Active) + { + newEmpList.Add(ed); + } + else if (ed.Employee.IsActive == ActivationTypeId.Archived) + { + if (ed.StundenGesamtIst != 0) + { + newEmpList.Add(ed); + } + } + } + + return newEmpList; + } + + public override void HandleServiceRecords(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList groupFilteredRecords, Contract contract) + { + //do nothing + } + + public override void CorrectOverlappingTimes(IList serviceRecords) + { + List oldRecords = new List(); + + foreach (var newRecord in serviceRecords) + { + if (!CorrectOverlappingTime(newRecord)) + { + continue; + } + if (newRecord.Start.HasValue && newRecord.Start.Value.Second > 0) + { + continue; + } + + bool isGroup = false; + var minuten = newRecord.RoundedDuration; + if (newRecord.GroupRoundedDuration.HasValue) + { + minuten = newRecord.GroupRoundedDuration.Value; + isGroup = true; + } + var newSpan = new DateTimeSpan + { + StartDateTime = newRecord.Start.Value, + EndDateTime = newRecord.Start.Value.AddMinutes((double)minuten) + }; + + foreach (var oldRecord in oldRecords) + { + if (!CorrectOverlappingTime(oldRecord, newRecord)) + { + continue; + } + var oldSpan = new DateTimeSpan + { + StartDateTime = oldRecord.Start.Value, + EndDateTime = oldRecord.Start.Value.AddMinutes((double)oldRecord.RoundedDuration) + }; + if (oldRecord.GroupRoundedDuration.HasValue) + { + oldSpan.EndDateTime = oldRecord.Start.Value.AddMinutes((double)oldRecord.GroupRoundedDuration); + } + + if (oldSpan.Overlaps(newSpan)) + { + DateTime start = newSpan.StartDateTime; + if (oldSpan.StartDateTime > start) + start = oldSpan.StartDateTime; + + DateTime end = newSpan.EndDateTime; + + if (oldSpan.EndDateTime < end) + end = oldSpan.EndDateTime; + + + if (newSpan.StartDateTime >= oldSpan.StartDateTime) + { + newSpan.StartDateTime = oldSpan.EndDateTime; + if (newSpan.StartDateTime > newSpan.EndDateTime) + { + newSpan.StartDateTime = newSpan.EndDateTime; + } + newRecord.Start = newSpan.StartDateTime; + newRecord.End = newSpan.EndDateTime; + + if (!isGroup) + { + newRecord.RoundedDuration = + (decimal)newRecord.End.Value.Subtract(newRecord.Start.Value).TotalMinutes; + } + else + { + newRecord.GroupRoundedDuration = (decimal)newRecord.End.Value.Subtract(newRecord.Start.Value).TotalMinutes; + } + } + else + { + oldSpan.StartDateTime = newSpan.EndDateTime; + if (oldSpan.StartDateTime > oldSpan.EndDateTime) + { + oldSpan.StartDateTime = oldSpan.EndDateTime; + } + oldRecord.Start = oldSpan.StartDateTime; + oldRecord.End = oldSpan.EndDateTime; + + + if (!oldRecord.GroupRoundedDuration.HasValue) + //if (!isGroup) + { + oldRecord.RoundedDuration = (decimal)oldRecord.End.Value.Subtract(oldRecord.Start.Value).TotalMinutes; + } + else + { + oldRecord.GroupRoundedDuration = (decimal)oldRecord.End.Value.Subtract(oldRecord.Start.Value).TotalMinutes; + } + + } + + } + } + oldRecords.Add(newRecord); + + } + } + + public override bool CorrectOverlappingTime(ServiceRecord newRecord) + { + return true; + } + + public override bool CorrectOverlappingTime(ServiceRecord record1, ServiceRecord record2) + { + return true; + } + + public override decimal? BerechneSollProMonat(Employee emp, DateTime start, DateTime ende) + { + Contract c = GetContractForDate(emp, start, ende); + + + if (c != null && c.MonthlyTotalHours.HasValue && c.MonthlyTotalHours.Value > 0) + { + return c.MonthlyTotalHours.Value; + + DateTime monatsAnfang = start; + DateTime monatsEnde = ende; + + if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang) + { + monatsAnfang = c.EntryDate.Value; + } + if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde) + { + monatsEnde = c.ContractEndDate.Value.Date.AddDays(1); + } + + if (monatsAnfang < monatsEnde) + { + //if (monatsAnfang.Day == 1 && monatsEnde.Day == 1) + //{ + return c.MonthlyTotalHours.Value; + //} + } + } + + return null; + } + + public override decimal? BerechneFlsSollProMonat(Employee emp, DateTime start, DateTime ende) + { + Contract c = GetContractForDate(emp, start, ende); + + + if (c != null && c.MonthlyFLS.HasValue && c.MonthlyFLS.Value > 0) + { + return c.MonthlyFLS.Value; + + DateTime monatsAnfang = start; + DateTime monatsEnde = ende; + + if (c.EntryDate.HasValue && c.EntryDate > monatsAnfang) + { + monatsAnfang = c.EntryDate.Value; + } + if (c.ContractEndDate.HasValue && c.ContractEndDate < monatsEnde) + { + monatsEnde = c.ContractEndDate.Value.Date.AddDays(1); + } + + if (monatsAnfang < monatsEnde) + { + //if (monatsAnfang.Day == 1 && monatsEnde.Day == 1) + //{ + return c.MonthlyFLS.Value; + //} + } + } + + return null; + } + + public override void HandleServiceRecord(MitarbeiterstundenkontoRO.EmployeeDetail emp, ServiceRecord item) + { + //do nothing + } + + public override bool ShouldCreateDayDetails() + { + //if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung) + //{ + // return true; + //} + //CB ab 04.09.2024 immer true + return true; + } + + public override bool ShouldCreateDayDetailsForEachServiceRecord() + { + return false; + } + + public override IList CreateDayDetails(DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList recordsAll) + { + if (ShouldCreateDayDetailsForEachServiceRecord()) + { + return CreateDayDetailsForEachServiceRecord(berichtStart, berichtEnde, ed, recordsAll); + } + + Dictionary dayToDetail = new Dictionary(); + + for (int i = berichtStart.Day; i <= berichtEnde.AddDays(-1).Day; i++) + { + MitarbeiterstundenkontoRO.DayDetail dd = CreateDayDetail(new DateTime(berichtStart.Year, berichtStart.Month, i), ed); + dayToDetail.Add(i, dd); + } + + foreach (var sr in recordsAll) + { + if (sr.Start.HasValue && sr.Start >= berichtStart && sr.Start < berichtEnde && sr.End.HasValue) + { + if (sr.Start.Value.Year == 2023 && sr.Start.Value.Month == 10 && sr.Start.Value.Day == 3) + { + int test = 0; + } + var dd = dayToDetail[sr.Start.Value.Day]; + + if (!IsServiceRecordFehlzeit(sr) && AddServiceRecordToDuration(sr)) + { + decimal duration = GetDuration(sr, ed); + dd.MinutesTotal += duration; + TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal); + dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + if (dd.ServiceRecords == null) + dd.ServiceRecords = new List(); + dd.ServiceRecords.Add(sr); + + CalculateSpecialHours(dd, sr, duration); + } + } + } + + //Berechne von bis Zeiten + foreach (var ddKeyValue in dayToDetail) + { + var dd = ddKeyValue.Value; + if (dd.ZusammenhaengendeZeitraeume == null) + { + dd.ZusammenhaengendeZeitraeume = new List(); + } + + if (dd.ServiceRecords != null) + { + var orderedList = dd.ServiceRecords.OrderBy(i => i.Start); + + DateTime lastEnd = DateTime.MinValue; + var gapCount = -1; + + foreach (var sr in orderedList) + { + if (sr.Start.HasValue && sr.Start.Value.Hour == 0 && sr.Start.Value.Second > 0) + { + continue; + } + decimal duration = GetDuration(sr, ed); + + + DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration); + if (sr.Start > lastEnd) + { + gapCount++; + + } + if (dtEnd > lastEnd) + { + lastEnd = dtEnd; + } + + DateTimeSpan currentSpan = null; + if (gapCount + 1 > dd.ZusammenhaengendeZeitraeume.Count) + { + currentSpan = new DateTimeSpan(); + currentSpan.StartDateTime = DateTime.MaxValue; + currentSpan.EndDateTime = DateTime.MinValue; + dd.ZusammenhaengendeZeitraeume.Add(currentSpan); + } + else + { + currentSpan = dd.ZusammenhaengendeZeitraeume[gapCount]; + } + + if (sr.Start < currentSpan.StartDateTime) + { + currentSpan.StartDateTime = sr.Start.Value; + } + if (dtEnd > currentSpan.EndDateTime) + { + currentSpan.EndDateTime = dtEnd; + } + + if (gapCount == 0) + { + if (!dd.StartDateTime1.HasValue || sr.Start < dd.StartDateTime1) + { + dd.StartDateTime1 = sr.Start; + } + if (!dd.EndDateTime1.HasValue || dtEnd > dd.EndDateTime1) + { + dd.EndDateTime1 = dtEnd; + } + } + else if (gapCount == 1) + { + if (!dd.StartDateTime2.HasValue || sr.Start < dd.StartDateTime2) + { + dd.StartDateTime2 = sr.Start; + } + if (!dd.EndDateTime2.HasValue || dtEnd > dd.EndDateTime2) + { + dd.EndDateTime2 = dtEnd; + } + } + else if (gapCount == 2) + { + if (!dd.StartDateTime3.HasValue || sr.Start < dd.StartDateTime3) + { + dd.StartDateTime3 = sr.Start; + } + if (!dd.EndDateTime3.HasValue || dtEnd > dd.EndDateTime3) + { + dd.EndDateTime3 = dtEnd; + } + } + else + { + if (!dd.StartDateTime4.HasValue || sr.Start < dd.StartDateTime4) + { + dd.StartDateTime4 = sr.Start; + } + if (!dd.EndDateTime4.HasValue || dtEnd > dd.EndDateTime4) + { + dd.EndDateTime4 = dtEnd; + } + } + } + } + } + + + + + return dayToDetail.Values.OrderBy(dd => dd.Date).ToList(); + } + + public override IList CreateDayDetailsForEachServiceRecord(DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList recordsAll) + { + var details = new List(); + + foreach (var sr in recordsAll) + { + if (sr.Start.HasValue && sr.Start >= berichtStart && sr.Start < berichtEnde && sr.End.HasValue) + { + if (sr.Start.Value.Day == 8) + { + int test = 0; + } + if (!IsServiceRecordFehlzeit(sr) && AddServiceRecordToDuration(sr)) + { + var dd = CreateDayDetail(sr.Start.Value, ed); + details.Add(dd); + + decimal duration = GetDuration(sr, ed); + + + dd.MinutesTotal += duration; + TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal); + dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + if (dd.ServiceRecords == null) + dd.ServiceRecords = new List(); + dd.ServiceRecords.Add(sr); + + dd.Date = sr.Start.Value; + + if (sr.Start.Value.Second == 0) + { + DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration); + dd.StartDateTime1 = sr.Start; + + dd.EndDateTime1 = dtEnd; + + } + + CalculateSpecialHours(dd, sr, duration); + + } + } + } + + return details.OrderBy(dd => dd.Date).ToList(); + } + + protected override decimal GetDuration(ServiceRecord sr, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + + foreach (var at in ed.Employee.AbsenceTimes) + { + if (!StundenBeiAbwesenheitBerechnen(at)) + { + DateTime start = DateTime.MinValue; + DateTime end = DateTime.MaxValue; + + if (at.Start.HasValue) + { + start = at.Start.Value; + } + if (at.End.HasValue) + { + end = at.End.Value; + if (end.Hour == 0) + { + end = end.AddDays(1); + } + } + + if (start <= sr.Start && sr.Start < end) + { + return 0; + } + } + + } + + decimal duration = 0; + + if (sr.GroupRoundedDuration.HasValue) + { + duration = sr.GroupRoundedDuration.Value; + } + else + { + if (appSettings.ShowRoundedDurationInEmployeeAnalysis) + { + duration = sr.RoundedDuration; + } + else + { + duration = (decimal)sr.End.Value.Subtract(sr.Start.Value).TotalMinutes; + } + } + + if (UseServiceDescriptionPercentage()) + { + decimal prozent = sr.ServiceDescription.ServiceCategory.ProzentStundenkonto ?? sr.ServiceDescription.ServiceCategory.ProzentAbrechnung; + + if (sr.ServiceDescription.ProzentStundenkonto.HasValue) + { + prozent = sr.ServiceDescription.ProzentStundenkonto.Value; + } + duration = (duration * prozent) / 100; + } + else + { + decimal prozent = 100; + if (sr.ServiceDescription.ProzentStundenkonto.HasValue) + { + prozent = sr.ServiceDescription.ProzentStundenkonto.Value; + } + else if (sr.ServiceDescription.ServiceCategory.ProzentStundenkonto.HasValue) + { + prozent = sr.ServiceDescription.ServiceCategory.ProzentStundenkonto.Value; + } + + + duration *= (prozent / 100); + } + + + return duration; + } + + //Mit "SpecialHours" sind Sonderzeiten wie Nachtzeiten oder Arbeitszeiten sonn- und feiertags gemeint. Da diese gesondert abgerechnet werden können. + public override void CalculateSpecialHours(MitarbeiterstundenkontoRO.DayDetail dd, ServiceRecord sr, decimal duration) + { + //Prüfe, ob Start oder Ende des Eintrags an einem Sonntag sind, dann die am Sonntag geleisteten Minuten berechnen + //Todo: Bei mehrtägigen Einträgen prüfen, ob sie vor einem Sonntag anfangen und nach einem Sonntag enden. Unklar, ob das vorkommen kann und ob dann Sonntagszuschläge berechnet werden müssen + if (sr.Start.Value.DayOfWeek == DayOfWeek.Sunday || sr.End.Value.DayOfWeek == DayOfWeek.Sunday) + { + DateTime sonntag = sr.Start.Value.Date; + if (sr.Start.Value.DayOfWeek != DayOfWeek.Sunday && sr.End.Value.DayOfWeek == DayOfWeek.Sunday) + { + sonntag = sr.End.Value.Date; + } + + var sonnatgsMinuten = GetOverlappingMinutes(sr.Start.Value, sr.End.Value, sonntag, sonntag.AddDays(1)); + dd.HoursSunday += (decimal)sonnatgsMinuten / 60; + } + //Prüfe, ob Start oder Ende des Eintrags an einem Feiertag sind, dann die am Feiertag geleisteten Minuten berechnen + //Todo: Bei mehrtägigen Einträgen prüfen, ob sie vor einem Feiertag anfangen und nach einem Feiertag enden. Unklar, ob das vorkommen kann + else if (IstFeiertag(sr.Start.Value) || IstFeiertag(sr.End.Value)) + { + var feiertagsMinuten = duration; + DateTime feiertag = sr.Start.Value.Date; + if (IstFeiertag(sr.Start.Value) && !IstFeiertag(sr.End.Value)) + { + feiertagsMinuten = (decimal)GetOverlappingMinutes(sr.Start.Value, sr.End.Value, feiertag, feiertag.AddDays(1)); + } + else if (!IstFeiertag(sr.Start.Value) && IstFeiertag(sr.End.Value)) + { + feiertag = sr.End.Value.Date; + feiertagsMinuten = (decimal)GetOverlappingMinutes(sr.Start.Value, sr.End.Value, feiertag, feiertag.AddDays(1)); + } + dd.HoursHoliday += feiertagsMinuten / 60; + } + + if (sr.Start.Value.Second == 0 && dd.NightEnd.HasValue && dd.NightStart.HasValue) + { + var minuten = CheckNachtzeiten(sr.Start.Value, dd.NightStart.Value, dd.NightEnd.Value, (double)duration); + + if (minuten > 0) + { + dd.SpecialHours += (decimal)(Math.Round(minuten, 0) / 60); + } + + //DateTime dtEnd = sr.Start.Value.AddMinutes((double)duration); + + //if (dtEnd.Hour >= dd.NightStart.Value) + //{ + // DateTime dtStart = dtEnd.Date.AddHours(dd.NightStart.Value); + // if (sr.Start.Value > dtStart) + // dtStart = sr.Start.Value; + + // TimeSpan ts = dtEnd.Subtract(dtStart); + // dd.SpecialHours += (decimal)ts.TotalMinutes / 60; + //} + + //if (sr.Start.Value.Hour < dd.NightEnd) + //{ + // DateTime dtEnd2 = sr.Start.Value.Date.AddHours(dd.NightEnd.Value); + // if (dtEnd < dtEnd2) + // dtEnd2 = dtEnd; + // TimeSpan ts = dtEnd2.Subtract(sr.Start.Value); + // dd.SpecialHours += (decimal)(Math.Round(ts.TotalMinutes, 0) / 60); + //} + } + } + + private double CheckNachtzeiten(DateTime start, int nightStart, int nightEnd, double minuten) + { + DateTime nightStartDt = start.Date.AddHours(nightStart); + DateTime nightEndDt = start.Date.AddDays(1).AddHours(nightEnd); + + + DateTime dtEnd = start.AddMinutes(minuten); + + double minutes = GetOverlappingMinutes(start, dtEnd, nightStartDt, nightEndDt); + minutes += GetOverlappingMinutes(start, dtEnd, nightStartDt.AddDays(-1), nightEndDt.AddDays(-1)); + minutes += GetOverlappingMinutes(start, dtEnd, nightStartDt.AddDays(1), nightEndDt.AddDays(1)); + + return minutes; + + } + + public double GetOverlappingMinutes(DateTime start1, DateTime end1, DateTime start2, DateTime end2) + { + if (end1 > start2 && start1 < end2) + { + DateTime dtStart = start1; + + if (dtStart < start2) + dtStart = start2; + + DateTime dtEnd = end1; + + if (dtEnd > end2) + dtEnd = end2; + TimeSpan ts = dtEnd.Subtract(dtStart); + + return ts.TotalMinutes; + + } + + return 0; + } + + public override MitarbeiterstundenkontoRO.DayDetail CreateDayDetail(DateTime date, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + MitarbeiterstundenkontoRO.DayDetail dd = new MitarbeiterstundenkontoRO.DayDetail(); + dd.ZusammenhaengendeZeitraeume = new List(); + dd.Date = date.Date; + dd.NightStart = 20; + dd.NightEnd = 6; + dd.TimeTotal = "00:00"; + dd.TimeInVacation = "00:00"; + dd.TimeSick = "00:00"; + dd.TimeOtherAbsenceTimes = "00:00"; + ConfigurePauseSettings(dd); + CalculateAbsenceTimesForDay(dd, ed); + // Claude: vacationService.GetFeiertag pro Datum cachen, da dieselbe Datum-Abfrage über viele Mitarbeiter wiederholt wird + // Claude: Vollständiges DateTime als Key (nicht .Date), damit das Verhalten bei evtl. nicht-mitternächtigen Aufrufen identisch bleibt + if (_feiertagDCByDateCache == null) + { + _feiertagDCByDateCache = new Dictionary(); + } + FeiertagDC feiertagDC; + if (!_feiertagDCByDateCache.TryGetValue(date, out feiertagDC)) + { + feiertagDC = vacationService.GetFeiertag(date); + _feiertagDCByDateCache[date] = feiertagDC; + } + dd.Feiertag = feiertagDC; + BerechneSollStundenFuerDayDetail(ed, dd); + + if (BerechneFeiertageAlsArbeitszeit()) + { + if (dd.Feiertag != null) + { + dd.MinutesTotal = dd.SollStunden * 60;// * GetFeiertagsFaktor(dd.Date); + TimeSpan tspan = TimeSpan.FromMinutes((double)dd.MinutesTotal); + dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + } + } + + return dd; + } + + public override void ConfigurePauseSettings(MitarbeiterstundenkontoRO.DayDetail dd) + { + if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung) + { + if (Settings.EnableAutomaticPauses) + { + dd.MaxHoursUntilBreak = 6; + dd.BreakMinutes = 30; + dd.MaxHoursUntilBreak2 = 9; + dd.BreakMinutes2 = 15; + } + } + //nicht mehr nötig. Im Default werden keine Pausen mehr berechnet + //else + // { + // //Alter Default + // dd.MaxHoursUntilBreak = null; + // dd.BreakMinutes = 30; + //} + } + + public override void BerechneSollStundenFuerDayDetail(MitarbeiterstundenkontoRO.EmployeeDetail ed, MitarbeiterstundenkontoRO.DayDetail dd) + { + var sollMax = BerechneSollStundenFuerTag(ed, dd.Date); + + if (BerechneSollNurBisZumAktuellenTag()) + { + if (dd.Date > DateTime.Now.Date) + { + sollMax = 0; + } + } + + + dd.SollStunden = sollMax; + + } + + public override decimal BerechneSollStundenFuerTag(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date) + { + decimal sollDiesenTag = 0; + var faktor = GetFeiertagsFaktor(date); + if (BerechneFeiertageAlsArbeitszeit()) + { + if (faktor == 1) + { + faktor = 0; + } + } + if (faktor < 1) + { + //Berechne Soll, entweder aus Vertrag oder Arbeitszeiten + if (HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, date, date)) + { + sollDiesenTag = GetSollNachArbeitszeitenFuerTag(ed.Employee, date); + + } + else + { + //sollDiesenTag = ed.SollProTag; + + var c = ed.Employee.GetValidContractForDate(date); + if (c != null) + { + decimal wth = 0; + int apw = ANZ_WOCHENTAGE; + if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0) + { + apw = c.WeeklyDays.Value; + } + if (c.WeeklyTotalHours.HasValue) + { + wth = c.WeeklyTotalHours.Value; + } + else if (c.MonthlyTotalHours.HasValue && ed.ArbeitstageImBerichtszeitraum > 0) + { + wth = c.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum; + wth *= apw; + } + + + sollDiesenTag = wth / apw; + + bool istWochenende = date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday; + if (apw == 6) + { + istWochenende = date.DayOfWeek == DayOfWeek.Sunday; + } + else if (apw > 6) + { + istWochenende = false; + } + if (istWochenende) + { + sollDiesenTag = 0; + } + } + if (faktor != 0) + { + sollDiesenTag *= faktor; //halbe Feiertage etc. berücksichtigen + } + } + } + + return sollDiesenTag; + + } + + public override void CalculateAbsenceTimesForDay(MitarbeiterstundenkontoRO.DayDetail dd, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + var absenceTimes = ed.Employee.AbsenceTimes; + + if (absenceTimes != null) + { + foreach (var absenceTime in absenceTimes) + { + + //if (IsUrlaubAbsenceTime(absenceTime) || IsKrankheitAbsenceTime(absenceTime)) + //{ + DateTimeSpan span = new DateTimeSpan(); + + span.StartDate = dd.Date; + + if (absenceTime.Start.HasValue && absenceTime.Start.Value > span.StartDate) + { + span.StartDate = absenceTime.Start.Value; + } + + if (absenceTime.End.HasValue && absenceTime.End.Value.Date == span.StartDate.Date && + absenceTime.End.Value.Hour > 0) + { + span.EndDate = absenceTime.End.Value; + } + else + { + span.EndDate = dd.Date.AddDays(1); + if (absenceTime.End.HasValue && absenceTime.End.Value.Date.AddDays(1) < span.EndDate) + { + span.EndDate = absenceTime.End.Value.Date.AddDays(1); + } + } + + if (span.EndDate > dd.Date && span.StartDate < dd.Date.AddDays(1)) + { + DateTime day = span.StartDate; + var faktor = GetFeiertagsFaktor(day); + + + if (faktor < 1 && (day.DayOfWeek != DayOfWeek.Saturday || ed.ArbeitstageProWoche == 6) && (day.DayOfWeek != DayOfWeek.Sunday || ed.ArbeitstageProWoche > 6)) // Prüfung auf 6 + bzw 7 Tage Woche + { + //Berechne Soll, entweder aus Vertrag oder Arbeitszeiten + var sollDiesenTag = ed.SollProTag; + + bool checkMaxWochenSoll = true; + + if (!IstAnrechenbareAbsenceTime(absenceTime)) + { + sollDiesenTag = 0; + } + else if (HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, dd.Date, dd.Date)) + { + sollDiesenTag = BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(ed.Employee, dd.Date); // Hier wird der Feiertagsfaktor schon applied + + } + else + { + var ssa = GetSollStundenForAbsenceTime(ed, absenceTime, dd.Date); + if (ssa.HasValue) + { + sollDiesenTag = ssa.Value; + checkMaxWochenSoll = false; + + } + else + { + var c = ed.Employee.GetValidContractForDate(dd.Date); + if (c != null) + { + decimal wth = 0; + int apw = ANZ_WOCHENTAGE; + if (c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0) + { + apw = c.WeeklyDays.Value; + } + + if (c.WeeklyTotalHours.HasValue) + { + wth = c.WeeklyTotalHours.Value; + } + else if (c.MonthlyTotalHours.HasValue) + { + + sollDiesenTag = c.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum; + wth = sollDiesenTag * apw; + } + + + sollDiesenTag = wth / apw; + } + } + } + + + //Dauer der Abwesenheit = Soll an diesem Tag + bool istGanztaegig = true; + var abwesenheitsDauer = sollDiesenTag; + + if (span.StartDate.Hour > 0 && span.EndDate.Hour > 0) + { + istGanztaegig = false; + + var ts = span.EndDate.Subtract(span.StartDate); + abwesenheitsDauer = (decimal)ts.TotalHours; + if (SchneideZuvieleAbwesenheitsstundenAb() && abwesenheitsDauer > sollDiesenTag) + { + abwesenheitsDauer = sollDiesenTag; + } + + sollDiesenTag -= abwesenheitsDauer; + if (sollDiesenTag < 0) + { + sollDiesenTag = 0; + } + + faktor = 1; // Keine halben Feiertag berücksichtigen, wenn die Abwesenheit in Stunden angegeben wurde + } + + if (faktor != 0 && !HatArbeitszeitImZeitraum(ed.Employee.Arbeitszeiten, dd.Date, dd.Date)) + { + abwesenheitsDauer *= faktor; //halbe Feiertage etc. berücksichtigen + } + + if (ed.AbsenceTimeDays != null && ed.AbsenceTimeDays.TageProWocheDict != null) + { + if (ed.AbsenceTimeDays.RestStundenProWocheDict == null) + { + ed.AbsenceTimeDays.RestStundenProWocheDict = new Dictionary(); + } + foreach (var week in ed.AbsenceTimeDays.TageProWocheDict.Keys) + { + if (!ed.AbsenceTimeDays.RestStundenProWocheDict.ContainsKey(week)) + { + ed.AbsenceTimeDays.RestStundenProWocheDict.Add(week, ed.SollProWoche); + } + + if (day.Date >= week.StartDate && day.Date <= week.EndDate && checkMaxWochenSoll) + { + + var rest = ed.AbsenceTimeDays.RestStundenProWocheDict[week]; + if (rest < abwesenheitsDauer) + { + abwesenheitsDauer = rest; + } + rest -= abwesenheitsDauer; + if (rest < 0) + { + rest = 0; + } + + ed.AbsenceTimeDays.RestStundenProWocheDict[week] = rest; + } + } + } + + + if (!AbsenceTimeAufIstStundenAddieren(absenceTime) && IsUrlaubAbsenceTime(absenceTime)) + { // Vermutlich diese Methode in CustomMitarbeiterStundenKontoBerechnung überschreiben und hier Prüfung machen, ob absenceReason ohne Fortzahlung ist + //und wenn ja, HoursSick oder Soll oder so für den Tag auf 0 setzen + + dd.HoursInVacation = abwesenheitsDauer; + var tspan = TimeSpan.FromHours((double)abwesenheitsDauer); + dd.TimeInVacation = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + } + else if (!AbsenceTimeAufIstStundenAddieren(absenceTime) && IsKrankheitAbsenceTime(absenceTime)) + { + dd.HoursSick = abwesenheitsDauer; + var tspan = TimeSpan.FromHours((double)abwesenheitsDauer); + dd.TimeSick = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + } + else + { + if (!AbsenceTimeAufIstStundenAddieren(absenceTime)) + { + dd.HoursOtherAbsenceTimes = abwesenheitsDauer; + var tspan = TimeSpan.FromHours((double)abwesenheitsDauer); + dd.TimeOtherAbsenceTimes = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + } + if (istGanztaegig) + { + //Bei ganztägigen Abwesenheiten darf nicht noch zusätzlich gearbeitet werden + dd.MinutesTotal = 0; + dd.TimeTotal = "00:00"; + } + dd.MinutesTotal += abwesenheitsDauer * 60; + TimeSpan tspan2 = TimeSpan.FromMinutes((double)dd.MinutesTotal); + dd.TimeTotal = String.Format("{0:00}:{1:00}", tspan2.Hours, tspan2.Minutes); + } + + if (dd.AbsenceTimes == null) + { + dd.AbsenceTimes = new List(); + } + dd.AbsenceTimes.Add(absenceTime); + //dd.MinutesTotal += sollDiesenTag * 60; + } + } + + } + } + + } + + public override bool IstAnrechenbareAbsenceTime(AbsenceTime item) + { + //CB Erst ab 2026 Überstundenfrei berücksichtigen + if (item.Start.HasValue && item.Start.Value.Date >= new DateTime(2026, 1, 1) && item.AbsenceReason != null && (item.AbsenceReason.Description.ToLower().Contains("überstundenfrei") || item.AbsenceReason.Description.ToLower().Contains("überstundenabbau") || + item.AbsenceReason.Description.ToLower().Contains("ü-frei"))) + return false; + else + return true; + } + + public override bool IsKrankheitAbsenceTime(AbsenceTime absenceTime) + { + return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) && + (absenceTime.AbsenceReason.Description.ToLower().IndexOf("arbeitsunfähig") >= 0 + || absenceTime.AbsenceReason.Description.ToLower().IndexOf("krank") >= 0 || absenceTime.AbsenceReason.Description.ToLower() == "au"); + } + + public override bool IsUrlaubAbsenceTime(AbsenceTime absenceTime) + { + return !String.IsNullOrEmpty(absenceTime.AbsenceReason.Description) && + absenceTime.AbsenceReason.Description.ToLower().IndexOf("urlaub") >= 0; + } + + public override decimal? GetSollStundenForAbsenceTime(MitarbeiterstundenkontoRO.EmployeeDetail ed, AbsenceTime at, DateTime datum) + { + return null; + + } + + public override bool AddAbsenceTimeToIst() + { + return false; + } + + + public override bool AddServiceRecordToDuration(ServiceRecord item) + { + if (Settings.ShowWorktime && Settings.EnableArbeitszeiterfassung) + { + return IstArbeitszeit(item); + + } + + return true; + } + + public override bool IstArbeitszeit(ServiceRecord item) + { + if (item.ServiceDescription != null && item.ServiceDescription.ServiceCategory != null) + { + if (item.ServiceDescription.Name.StartsWith("Arbeitszeit") || item.ServiceDescription.ServiceCategory.Name.StartsWith("Arbeitszeit")) //Arbeitszeit und Arbeitszeiterfassung etc. + { + return true; + } + } + return false; + } + + public override decimal AddRateFactorToIst() + { + return 1; + } + + public override MitarbeiterstundenkontoRO.EmployeeDetail BerechneUeberstundenBis(MitarbeiterstundenkontoRO.EmployeeDetail edOrig, IList records, DateTime monat, out decimal? ueberstunden, out decimal alteExtraUeberstunden) + { + MitarbeiterstundenkontoRO.EmployeeDetail edTotal = new MitarbeiterstundenkontoRO.EmployeeDetail(); + + ueberstunden = null; + alteExtraUeberstunden = 0; + + var emp = edOrig.Employee; + + Contract c = emp.FirstContract; + + + + if (c != null) + { + + Dictionary> srDict = CreateServiceRecordDictByDate(records); + + DateTime start = c.EntryDate.HasValue ? c.EntryDate.Value : new DateTime(2011, 1, 1); + if (BerechnungsStartdatum > start) + { + start = BerechnungsStartdatum; + c = GetNextValidContractForDate(start, emp); + } + while (c != null && start < monat) + { + DateTime contractEnd = c.ContractEndDate.HasValue ? c.ContractEndDate.Value : DateTime.MaxValue; + if (contractEnd < DateTime.MaxValue) + { + contractEnd = contractEnd.Date.AddDays(1).AddTicks(-1); + } + decimal bu = 0; + decimal weeklyTotalHours = this.maxWochenstunden; + int anzWochentage = ANZ_WOCHENTAGE; + + + if (c.WeeklyTotalHours.HasValue) + { + weeklyTotalHours = c.WeeklyTotalHours.Value; + bu = c.WeeklyTotalHours.Value / maxWochenstunden; + } + + + //while (start < monat && start <= contractEnd) + while (start < monat && start < contractEnd) + { + if (start >= new DateTime(2024, 8, 1)) + { + int test = 0; + } + + decimal flsIst = 0; + decimal mittelbarIst = 0; + decimal minutenMonat = 0; + DateTime ende = new DateTime(start.Year, start.Month, 1).AddMonths(1); + if (contractEnd < DateTime.MaxValue && contractEnd.Date.AddDays(1) < ende) + ende = contractEnd.Date.AddDays(1); + + IList recordsThisMonth = new List(); + String key = String.Format("{0:yyyyMM}", start); + + if (srDict.ContainsKey(key)) + { + foreach (var item in srDict[key]) + { + if (item.Start.HasValue && item.Start >= start && item.Start < ende) + { + if (AddServiceRecordToDuration(item)) + { + + decimal duration = GetDuration(item, edOrig); + + + minutenMonat += duration; + + if (item.ServiceDescription != null && + item.ServiceDescription.ServiceCategory != null && + item.ServiceDescription.ServiceCategory.IsBillable) + flsIst += duration; + else + mittelbarIst += duration; + + } + recordsThisMonth.Add(item); + } + } + } + + + + decimal arbeitstage = GetArbeitstageImBereich(start, ende, emp); + var arbeitstageProWoche = anzWochentage; + if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays.Value > 0) + { + arbeitstageProWoche = c.WeeklyDays.Value; + } + + //if (anzWochentage != 5) + //{ + // arbeitstage *= anzWochentage / 5m; + //} + var soll = GetSollNachArbeitszeiten(emp, start, ende); + + if (!soll.HasValue) + { + var stundenSollProMonat = BerechneSollProMonat(emp, start, ende); + soll = stundenSollProMonat; + } + + if (!soll.HasValue) + { + + if (!soll.HasValue) + { + decimal maxSollMonat = arbeitstage * (maxWochenstunden / arbeitstageProWoche); + if (arbeitstageProWoche <= 5) + { + maxSollMonat = arbeitstage * (maxWochenstunden / 5); + } + + soll = maxSollMonat * bu; + } + } + + decimal sollMitarbeiter = soll ?? 0; + + MitarbeiterstundenkontoRO.EmployeeDetail ed = new MitarbeiterstundenkontoRO.EmployeeDetail(); + ed.SollProTag = weeklyTotalHours / arbeitstageProWoche; + ed.ArbeitstageProWoche = arbeitstageProWoche; + ed.ArbeitstageImBerichtszeitraum = arbeitstage; + ed.SollProWoche = BerechneSollProWoche(c, ed); + + ed.Employee = emp; + + AktuellBearbeitetesEmployeeDetail = ed; + var ath = GetAbsenceTimeDaysWithWorkWeeks(emp, start, ende, arbeitstageProWoche); + ed.AbsenceTimeDays = ath; + + decimal? fehlzeit = GetFehlzeiten(records, start, ende); + + if (fehlzeit.HasValue) + { + sollMitarbeiter -= fehlzeit.Value; + + } + + ed.StundenSollMax = sollMitarbeiter; + + + if (ath.TageGesamt.HasValue) + { + var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, ed); + sollMitarbeiter -= minusStunden; + if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein()) + { + sollMitarbeiter = 0; + } + //if (stundenSollProMonat.HasValue) + //{ + // decimal stundenProTag = stundenSollProMonat.Value; + // if (arbeitstage != 0) + // { + // stundenProTag = stundenSollProMonat.Value/arbeitstage; + // } + // sollMitarbeiter -= (ath.TageGesamt.Value * stundenProTag); + //} + //else + //{ + // decimal sollProWoche = maxWochenstunden*bu; + + + // var minusStunden = BerechneStundenDurchAbwesenheiten(ath, start, ende, c, sollProWoche); + + + //} + } + + var ath2 = GetAbwesenheitenDurchServiceRecords(ed, c, records, start, ende); + + if (ath2 != null) + { + + var stunden = ath2.StundenKrankheit + ath2.StundenUrlaub + ath2.StundenSonstiges; + + sollMitarbeiter -= stunden; + + if (sollMitarbeiter < 0 && !StundenDurchAbwesenheitenDuerfenNegativSein()) + { + sollMitarbeiter = 0; + } + + + } + + var azDurchFeiertage = BerechneGesamtArbeitszeitDurchFeiertage(ed, start, ende); + sollMitarbeiter -= azDurchFeiertage; + + decimal stundenMonat = minutenMonat / 60m; + stundenMonat = ((flsIst * AddRateFactorToIst()) + mittelbarIst) / 60m; + + + if (!ueberstunden.HasValue) + ueberstunden = 0; + ueberstunden += stundenMonat - sollMitarbeiter; + + if (SollPauseBerechnen()) + { + var pause1 = BerechnePauseMinuten(recordsThisMonth); + var pause2 = BerechneAutoPausen(recordsThisMonth); + + ueberstunden -= pause1 / 60; + ueberstunden -= pause2 / 60; + } + + decimal extraUeberstunden = 0; + + BerechneExtraUeberstunden(emp, start, ende, out extraUeberstunden); + ueberstunden += extraUeberstunden; + + alteExtraUeberstunden += extraUeberstunden; + + if (ShouldCreateDayDetails()) + { + var days = CreateDayDetails(start, ende, ed, records); + var ah = GetAdditionalHours(days, start, ende, ed, records); + ueberstunden += ah; + } + + + ed.StundenSollOhneUrlaubKrankheit = sollMitarbeiter; + ed.FlsIst = flsIst / 60; + ed.MittelbarIst = mittelbarIst / 60; + ed.StundenGesamtIst = stundenMonat; + ed.Ueberstunden = ueberstunden; + ed.UeberstundenVormonat = edTotal.Ueberstunden; + + ueberstunden = BerechneUeberstunden(ed, ueberstunden, c, start, ende); + + edTotal.StundenSollOhneUrlaubKrankheit += sollMitarbeiter; + edTotal.StundenSollMax += soll ?? 0; + edTotal.FlsIst += flsIst / 60; + edTotal.MittelbarIst += mittelbarIst / 60; + edTotal.StundenGesamtIst += stundenMonat; + edTotal.Ueberstunden = ueberstunden; + + start = ende; + } + + //if (start > contractEnd) + if (start >= contractEnd) + { + var ctemp = GetNextValidContractForDate(start, emp); + if (ctemp != null && ctemp.Oid != c.Oid) + { + c = ctemp; + } + else + { + c = null; + } + + if (c != null && c.EntryDate.HasValue && c.EntryDate >= start) + start = c.EntryDate.Value; + else + { + start = start.AddMonths(1); + c = GetNextValidContractForDate(start, emp); + if (c != null && c.EntryDate.HasValue && c.EntryDate >= start) + start = c.EntryDate.Value; + } + } + } + + } + + return edTotal; + } + + protected override decimal? BerechneUeberstunden(MitarbeiterstundenkontoRO.EmployeeDetail ed, decimal? ueberstundenStd, Contract contract, DateTime start, DateTime ende) + { + return ueberstundenStd; + } + + public override decimal BerechneSollAmTag(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date) + { + decimal soll = 0; + var c = ed.Employee.GetValidContractForDate(date); + + if (c != null) + { + int arbeitstageProWoche = ANZ_WOCHENTAGE; + + if (c.WeeklyDays.HasValue && c.WeeklyDays > 0) + { + arbeitstageProWoche = c.WeeklyDays.Value; + } + if (c.WeeklyTotalHours.HasValue) + { + return c.WeeklyTotalHours.Value / arbeitstageProWoche; + } + } + return soll; + } + public override decimal BerechneSollProWoche(Contract c, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + if (c != null) + { + if (c.MonthlyTotalHours.HasValue && c.MonthlyTotalHours.Value > 0) + { + if (ed.ArbeitstageImBerichtszeitraum > 0) + { + + decimal stundenProTag = GetSollArbeitszeitProArbeitstag(c, ed); + + return stundenProTag * 5; + //return stundenProTag * ed.ArbeitstageProWoche; + //sollStundenProWoche = stundenProTag * ed.ArbeitstageProWoche; + + //return (ath.TageGesamt.Value * stundenProTag); + } + } + else if (c.WeeklyTotalHours.HasValue) + { + return c.WeeklyTotalHours.Value; + } + } + return 0; + } + + + + + public override decimal BerechneAbwesenheitsStundenDurchArbeitszeiten(Employee emp, DateTime start, DateTime ende) + { + decimal stunden = 0; + + DateTime startTemp = start; + + while (startTemp < ende) + { + stunden += BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(emp, startTemp); + + startTemp = startTemp.AddDays(1); + } + + return stunden; + } + + // Claude: Zurückgesetzt auf Original-Implementierung. Die TimeSpan-Optimierung hat in Einzelfällen abweichende Werte erzeugt. + public override decimal BerechneAlleAbwesenheitsStundenFuerArbeitszeiten(Employee emp, DateTime tag) + { + decimal stunden = 0; + + foreach (var az in emp.Arbeitszeiten) + { + if ((!az.GueltigVon.HasValue || az.GueltigVon.Value <= tag) && + (!az.GueltigBis.HasValue || az.GueltigBis.Value >= tag)) + { + foreach (var ae in az.ArbeitszeitEintraege) + { + if (ae.IstAmGleichenWochentag(tag)) + { + + String dateStr1 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitVon); + String dateStr2 = String.Format("{0:dd.MM.yyyy} {1}", tag, ae.UhrzeitBis); + + DateTime date1; + DateTime date2; + + if (DateTime.TryParse(dateStr1, out date1) && DateTime.TryParse(dateStr2, out date2)) + { + stunden += BerechneAbwesenheitsStundenFuerArbeitstag(emp, date1, date2); + + } + } + } + } + } + + return stunden; + } + + public override decimal BerechneAbwesenheitsStundenFuerArbeitstag(Employee emp, DateTime arbeitstagStart, DateTime arbeitstagEnde) + { + foreach (var at in emp.AbsenceTimes) + { + if (IstAnrechenbareAbsenceTime(at)) + { + if ((!at.Start.HasValue || at.Start.Value <= arbeitstagEnde) && (!at.End.HasValue || at.End.Value >= arbeitstagStart.Date)) + { + var faktor = GetFeiertagsFaktor(arbeitstagStart); + + if (faktor < 1) + { + var hours = (decimal)arbeitstagEnde.Subtract(arbeitstagStart).TotalHours; + + // Prüfen, ob halber Tag + if ((at.Start.HasValue && at.Start.Value.Date != at.Start.Value) || (at.End.HasValue && at.End.Value.Date != at.End.Value)) + { + var start = at.Start; + var end = at.End; + if (!at.End.HasValue || at.End.HasValue && at.End.Value > arbeitstagEnde) + end = arbeitstagEnde; + if (!at.Start.HasValue || at.Start.HasValue && at.Start.Value < arbeitstagStart) + start = arbeitstagStart; + + hours = Convert.ToDecimal((end.Value - start.Value).TotalHours); + } + + if (faktor != 0) + { + hours *= faktor; + } + + return hours; + } + + + } + } + } + + return 0; + } + + //CB 12.4.18: Die Abwesenheitsstunden wurden bei Arbeitstagen < 5 früher falsch berechnet. Damit bereits erstellte Stundenkonto nicht zu veränderten Werten führen, habe + //ich einen Schalter eingebaut, mit dem bestimmt werden kann, ob die alte Berechnung angewendet werden soll oder nicht (Standard ist ja) + public override bool BerechneAlteAbwesenheitsStunden(DateTime monatAnfang) + { + if (monatAnfang < new DateTime(2018, 4, 1)) + { + return true; + } + + return false; + } + + private decimal BerechneStundenDurchAbwesenheitenAlt(AbsenceTimeDays ath, DateTime start, DateTime ende, Contract c, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + + decimal? sollProMonat = BerechneSollProMonat(ed.Employee, start, ende); + + if (sollProMonat.HasValue) + { + decimal stundenProTag = sollProMonat.Value / ed.ArbeitstageImBerichtszeitraum; + return (ath.TageGesamt.Value * stundenProTag); + } + else + { + decimal sollProWoche = BerechneSollProWoche(c, ed); + decimal stunden = 0; + + if (ath.TageGesamt.HasValue) + { + if (ed.ArbeitstageProWoche == 5) + { + stunden = ath.TageGesamt.Value * (sollProWoche / ed.ArbeitstageProWoche); + } + else if (ath.TageProWocheDict != null && ed.ArbeitstageProWoche > 0) + { + foreach (var week in ath.TageProWocheDict.Keys) + { + var tage = ath.TageProWocheDict[week]; + + if (tage > 0) + { + if (tage > ed.ArbeitstageProWoche) + { + tage = ed.ArbeitstageProWoche; + + } + stunden += tage * (sollProWoche / ed.ArbeitstageProWoche); + } + } + } + } + + return stunden; + } + } + + public override decimal GetSollArbeitszeitProArbeitstag(Contract contract, MitarbeiterstundenkontoRO.EmployeeDetail ed) + { + if (contract.MonthlyTotalHours.HasValue && contract.MonthlyTotalHours.Value > 0 && ed.ArbeitstageImBerichtszeitraum > 0) + { + return contract.MonthlyTotalHours.Value / ed.ArbeitstageImBerichtszeitraum; + } + else if (contract.WeeklyTotalHours.HasValue && ed.ArbeitstageProWoche > 0) + { + return contract.WeeklyTotalHours.Value / ed.ArbeitstageProWoche; + } + + return 0; + } + + + public override Dictionary> CreateServiceRecordDictByDate(IList records) + { + var dict = new Dictionary>(); + foreach (var item in records) + { + if (!IsServiceRecordFehlzeit(item)) + { + if (item.Start.HasValue) + { + String key = String.Format("{0:yyyyMM}", item.Start.Value); + + IList list; + if (!dict.ContainsKey(key)) + { + list = new List(); + dict.Add(key, list); + } + else + { + list = dict[key]; + } + list.Add(item); + } + } + } + return dict; + } + + public override decimal GetAdditionalHours(IList days, DateTime berichtStart, DateTime berichtEnde, MitarbeiterstundenkontoRO.EmployeeDetail ed, IList recordsAll) + { + decimal addHours = 0; + + foreach (var dayDetail in days) + { + if (dayDetail.Date >= new DateTime(2023, 1, 1)) + { + addHours += CalculateBreakMinutesNeu(dayDetail); + } + else + { + if (dayDetail.BreakMinutes2.HasValue && dayDetail.MaxHoursUntilBreak2.HasValue) + { + addHours += CalculateBreakMinutes(dayDetail, dayDetail.MaxHoursUntilBreak2.Value, dayDetail.BreakMinutes2.Value); + } + if (dayDetail.BreakMinutes.HasValue && dayDetail.MaxHoursUntilBreak.HasValue) + { + addHours += CalculateBreakMinutes(dayDetail, dayDetail.MaxHoursUntilBreak.Value, dayDetail.BreakMinutes.Value); + } + } + } + + return addHours; + } + + public override decimal CalculateBreakMinutesNeu(MitarbeiterstundenkontoRO.DayDetail dayDetail) + { + decimal addHours = 0; + + if (dayDetail.ZusammenhaengendeZeitraeume != null && dayDetail.ZusammenhaengendeZeitraeume.Count > 0) + { + foreach (var span in dayDetail.ZusammenhaengendeZeitraeume) + { + addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, span.StartDateTime, span.EndDateTime) / 60; + } + + return addHours; + } + + if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue) + { + addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime1.Value, dayDetail.EndDateTime1.Value) / 60; + } + if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue) + { + addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime2.Value, dayDetail.EndDateTime2.Value) / 60; + } + if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue) + { + addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime3.Value, dayDetail.EndDateTime3.Value) / 60; + } + if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue) + { + addHours -= BerechneGesetzlichePauseInMinuten(dayDetail, dayDetail.StartDateTime4.Value, dayDetail.EndDateTime4.Value) / 60; + } + + return addHours; + } + + public override decimal BerechneGesetzlichePauseInMinuten(MitarbeiterstundenkontoRO.DayDetail dayDetail, DateTime startDateTime, DateTime endDateTime) + { + decimal arbeitsstunden = (decimal)endDateTime.Subtract(startDateTime).TotalHours; + decimal gesetzlichePauseMinuten = 0; + if (dayDetail.MaxHoursUntilBreak.HasValue && arbeitsstunden > dayDetail.MaxHoursUntilBreak) + { + gesetzlichePauseMinuten += dayDetail.BreakMinutes ?? 0; + } + arbeitsstunden -= gesetzlichePauseMinuten / 60; + + if (dayDetail.MaxHoursUntilBreak2.HasValue && arbeitsstunden > dayDetail.MaxHoursUntilBreak2) + { + gesetzlichePauseMinuten += dayDetail.BreakMinutes2 ?? 0; + } + + dayDetail.MinutesTotal -= gesetzlichePauseMinuten; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += gesetzlichePauseMinuten; + + return gesetzlichePauseMinuten; + } + + public override decimal CalculateBreakMinutes(MitarbeiterstundenkontoRO.DayDetail dayDetail, int maxHoursUntilBreak, int breakMinutes) + { + decimal addHours = 0; + + if (dayDetail.ZusammenhaengendeZeitraeume != null && dayDetail.ZusammenhaengendeZeitraeume.Count > 0) + { + foreach (var span in dayDetail.ZusammenhaengendeZeitraeume) + { + if (span.EndDateTime.Subtract(span.StartDateTime).TotalHours > maxHoursUntilBreak) + { + addHours -= (breakMinutes / 60m); + dayDetail.MinutesTotal -= breakMinutes; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += breakMinutes; + } + } + + return addHours; + } + + if (dayDetail.EndDateTime1.HasValue && dayDetail.StartDateTime1.HasValue) + { + if (dayDetail.EndDateTime1.Value.Subtract(dayDetail.StartDateTime1.Value).TotalHours > maxHoursUntilBreak) + { + addHours -= (breakMinutes / 60m); + dayDetail.MinutesTotal -= breakMinutes; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += breakMinutes; + } + } + if (dayDetail.EndDateTime2.HasValue && dayDetail.StartDateTime2.HasValue) + { + if (dayDetail.EndDateTime2.Value.Subtract(dayDetail.StartDateTime2.Value).TotalHours > maxHoursUntilBreak) + { + addHours -= (breakMinutes / 60m); + dayDetail.MinutesTotal -= breakMinutes; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += breakMinutes; + } + } + if (dayDetail.EndDateTime3.HasValue && dayDetail.StartDateTime3.HasValue) + { + if (dayDetail.EndDateTime3.Value.Subtract(dayDetail.StartDateTime3.Value).TotalHours > maxHoursUntilBreak) + { + addHours -= (breakMinutes / 60m); + dayDetail.MinutesTotal -= breakMinutes; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += breakMinutes; + } + } + if (dayDetail.EndDateTime4.HasValue && dayDetail.StartDateTime4.HasValue) + { + if (dayDetail.EndDateTime4.Value.Subtract(dayDetail.StartDateTime4.Value).TotalHours > maxHoursUntilBreak) + { + addHours -= (breakMinutes / 60m); + dayDetail.MinutesTotal -= breakMinutes; + TimeSpan tspan = TimeSpan.FromMinutes((double)dayDetail.MinutesTotal); + dayDetail.TimeTotal = String.Format("{0:00}:{1:00}", tspan.Hours, tspan.Minutes); + dayDetail.PauseInMinuten += breakMinutes; + } + } + + return addHours; + } + + public override void BerechneExtraUeberstunden(Employee emp, DateTime start, DateTime ende, out decimal stunden) + { + stunden = 0; + + if (emp.Overtimes != null) + { + foreach (var ot in emp.Overtimes) + { + if (ot.Date >= start && ot.Date < ende) + { + stunden += ot.Betrag; + } + } + } + } + + public override Contract GetNextValidContractForDate(DateTime dt, Employee emp) + { + Contract contractNextToStartDate = emp.GetValidContractForDate(dt); + + if (contractNextToStartDate == null) + { + + foreach (var item in emp.Contracts) + { + if (item.EntryDate.HasValue && item.EntryDate.Value >= dt) + { + if (contractNextToStartDate == null || item.EntryDate.Value < contractNextToStartDate.EntryDate.Value) + { + contractNextToStartDate = item; + } + } + + + } + } + return contractNextToStartDate; + } + + public override decimal? GetFehlzeiten(IList records, DateTime monat) + { + + return GetFehlzeiten(records, monat, monat.AddMonths(1)); + } + + public override decimal? GetFehlzeiten(IList records, DateTime start, DateTime ende) + { + decimal? stunden = null; + decimal minuten = 0; + if (records != null) + { + foreach (var item in records) + { + if (IsServiceRecordFehlzeit(item)) + { + if (item.Start.Value >= start && item.Start.Value < ende) + { + if (item.GroupRoundedDuration.HasValue) + { + minuten += item.GroupRoundedDuration.Value; + } + else + { + if (appSettings.ShowRoundedDurationInEmployeeAnalysis) + { + minuten += item.RoundedDuration; + } + else + { + minuten += (decimal)item.End.Value.Subtract(item.Start.Value).TotalMinutes; + } + } + } + } + } + } + if (minuten != 0) + stunden = minuten / 60; + return stunden; + } + + + public override decimal GetArbeitstageImMonat(DateTime datum, Employee emp) + { + Contract c = GetNextValidContractForDate(datum, emp); + if (c == null) + return 0; + + DateTime start = new DateTime(datum.Year, datum.Month, 1); + + DateTime end = start.AddMonths(1); + + if (c.EntryDate.HasValue) + { + if (c.EntryDate >= end) + return 0; + + if (c.EntryDate > start) + start = c.EntryDate.Value; + } + if (c.ContractEndDate.HasValue && c.ContractEndDate.Value.AddDays(1) < end) + end = c.ContractEndDate.Value.AddDays(1); + + return GetArbeitstageImBereich(start, end, emp); + + } + + public override decimal GetArbeitstageImBereich(DateTime von, DateTime bis, Employee emp) + { + return GetArbeitstageImBereich(von, bis, emp, false); + + } + + // Wenn das Flag berechneAnhandArbeitszeiten gesetzt ist, werden nur die Wochentage als Arbeitstage berechent, für die auch im Reiter Arbeitszeiten ein entsprechender Arbeitstag hinterlegt wurde. + // Nur relevant bei < 5 Wochenarbeitstagen + // Aus Kompatibilitätsgründen ist das Flag standardmäßig auf false + public override decimal GetArbeitstageImBereich(DateTime von, DateTime bis, Employee emp, bool berechneAnhandArbeitszeiten) + { + DateTime start = von; + DateTime end = bis; + + if (BerechneSollNurBisZumAktuellenTag()) + { + if (end > DateTime.Now.Date.AddDays(1)) + { + end = DateTime.Now.Date.AddDays(1); + } + } + + TimeSpan ts = end.Subtract(start); + + var maxDays = (decimal)ts.TotalDays; + + while (start < end) + { + int arbeitstageProWoche = ANZ_WOCHENTAGE; + + var c = GetContractForDate(emp, start, start); + + if (c != null && c.WeeklyDays.HasValue && c.WeeklyDays > 0) + { + arbeitstageProWoche = c.WeeklyDays.Value; + } + + + if (arbeitstageProWoche <= 5 && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday)) + { + maxDays -= 1; + } + else if (arbeitstageProWoche == 6 && start.DayOfWeek == DayOfWeek.Sunday) + { + maxDays -= 1; + } + else + { + if (berechneAnhandArbeitszeiten && arbeitstageProWoche < 5) + { + if (GetSollNachArbeitszeitenFuerTag(emp, start) == 0) + { + maxDays--; // Kein Arbeitstag, also Anzahl um eins verringern + } + } + else if (!BerechneFeiertageAlsArbeitszeit()) + { + maxDays -= GetFeiertagsFaktor(start); + } + } + start = start.AddDays(1); + } + return maxDays; + + } + + public override Contract GetContractForDate(Employee emp, DateTime start, DateTime ende) + { + Contract c = emp.GetValidContractForDate(start); + if (c == null) // Kein Vertrag vom 1. gefunden, gucken, ob es einen gibt, der mitten im Monat beginnt. + { + var c2 = GetNextValidContractForDate(start, emp); + if (c2 != null && c2.EntryDate.HasValue && c2.EntryDate.Value < ende) + c = c2; + } + return c; + } + + //gibt 1 zurück wenn ganzer Feiertag und z.B. 0.5 bei halben etc. + public override decimal GetFeiertagsFaktor(DateTime start) + { + //CB 26.02.2024: Darf nicht über den VacationService gehen, sonst werden die im CustomMitarbeiterstundenkontoBerechnung hinterlegten Feiertage ignoriert + //return vacationService.GetFeiertagsFaktor(start); + if (IstFeiertag(start)) + { + return 1; + } + return 0; + } + + public override bool IstFeiertag(DateTime start) + { + // Claude: HashSet-Lookup statt linearer Suche; pro Jahr nur einmal aufgebaut + if (_feiertageSetCache == null) + { + _feiertageSetCache = new Dictionary>(); + } + HashSet set; + if (!_feiertageSetCache.TryGetValue(start.Year, out set)) + { + set = new HashSet(GetFeiertage(start.Year)); + _feiertageSetCache[start.Year] = set; + } + return set.Contains(start.Date); + } + + public override List GetFeiertageDCs(int year) + { + // Hier muss das Bundesland überprüft werden und für z.B. Hessen Allerheiligen raus + return vacationService.GetFeiertage(year, vacationService.Bundesland); + } + + public override List GetFeiertage(int year) + { + // Claude: Feiertagsliste pro Jahr cachen, damit vacationService.GetFeiertage nicht wiederholt aufgerufen wird + if (_feiertageListeCache == null) + { + _feiertageListeCache = new Dictionary>(); + } + List list; + if (!_feiertageListeCache.TryGetValue(year, out list)) + { + list = vacationService.GetFeiertage(year); + _feiertageListeCache[year] = list; + } + return list; + } + + + protected DateTime GetOsterSonntag(int year) + { + return VacationService.GetOsterSonntag(year); + } + + public override bool IsServiceRecordFehlzeit(ServiceRecord sr) + { + if (sr.ServiceDescription != null && sr.ServiceDescription.ServiceCategory != null && !String.IsNullOrEmpty(sr.ServiceDescription.ServiceCategory.Name) && sr.ServiceDescription.ServiceCategory.Name.ToLower().IndexOf("fehlzeit") >= 0) + return true; + return false; + } + + public override bool BerechneFeiertageAlsArbeitszeit() + { + return false; + } + + public override bool AbsenceTimeAufIstStundenAddieren(AbsenceTime at) + { + return false; + } + + public override bool StundenBeiAbwesenheitBerechnen(AbsenceTime at) + { + return true; + } + + public override decimal BerechneGesamtArbeitszeitDurchFeiertage(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime start, DateTime ende) + { + decimal az = 0; + if (BerechneFeiertageAlsArbeitszeit() && ed.Employee.IsActive == ActivationTypeId.Active) + { + DateTime dt = start; + while (dt < ende) + { + if (GetFeiertagsFaktor(dt) > 0) + { + az += BerechneSollStundenFuerTag(ed, dt); + } + dt = dt.AddDays(1); + } + } + return az; + } + + public AbsenceTime GetAbsenceTimeForDate(MitarbeiterstundenkontoRO.EmployeeDetail ed, DateTime date) + { + var absenceTimes = ed.Employee.AbsenceTimes; + + if (absenceTimes != null) + { + foreach (var absenceTime in absenceTimes) + { + if (absenceTime.Start < date.AddDays(1) && (!absenceTime.End.HasValue || absenceTime.End >= date)) + { + return absenceTime; + } + } + } + + return null; + } + } + + public class AbsenceTimeDays + { + public decimal? TageUrlaub; + public decimal? TageKrankheit; + public decimal? TageGesamt; + public decimal TageAlsIst; + public decimal? StundenUrlaub; + public decimal? StundenKrankheit; + public decimal? StundenGesamt; + public decimal StundenAlsIst; + + public Dictionary TageProWocheDict; + public Dictionary StundenProTagDict; + public Dictionary RestStundenProWocheDict; + } + +} + diff --git a/ReportImp/ClaudeStundenkontoTest/Properties/AssemblyInfo.cs b/ReportImp/ClaudeStundenkontoTest/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..930888dd6 --- /dev/null +++ b/ReportImp/ClaudeStundenkontoTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("KundeXyz")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("KundeXyz")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly +// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von +// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("c1fed668-adb9-47e0-b589-1389618e1b6e")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, +// indem Sie "*" wie unten gezeigt eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]