diff --git a/Report/ReportObjects/MitarbeiterstundenkontoBerechnung.cs b/Report/ReportObjects/MitarbeiterstundenkontoBerechnung.cs
index 1402fa746..3bb2cd049 100644
--- a/Report/ReportObjects/MitarbeiterstundenkontoBerechnung.cs
+++ b/Report/ReportObjects/MitarbeiterstundenkontoBerechnung.cs
@@ -1236,9 +1236,38 @@ namespace BeWo.Report.ReportObjects
//do nothing
}
+ ///
+ /// Verknüpft einen bereits verarbeiteten ("alten") Record mit seinem Zeit-Span und
+ /// seiner Einfüge-Reihenfolge. Der Span wird gecacht (statt pro Paar neu berechnet) und
+ /// bei In-Place-Korrekturen des Records aktualisiert; der Index stellt die exakte
+ /// Verarbeitungsreihenfolge wieder her, wenn Kandidaten aus mehreren Tages-Buckets stammen.
+ ///
+ private sealed class OverlapEntry
+ {
+ public readonly StundenkontoServiceRecord Record;
+ public readonly DateTimeSpan Span;
+ public readonly int Index;
+
+ public OverlapEntry(StundenkontoServiceRecord record, DateTimeSpan span, int index)
+ {
+ Record = record;
+ Span = span;
+ Index = index;
+ }
+ }
+
public virtual void CorrectOverlappingTimes(IList serviceRecords)
{
- List oldRecords = new List();
+ // Tages-Index über bereits verarbeitete Records. Zwei Records können sich nur
+ // überlappen, wenn sich ihre Zeitintervalle schneiden - und damit teilen sie
+ // mindestens einen Kalendertag. Statt jeden neuen Record gegen ALLE alten zu
+ // prüfen (O(n²)), vergleichen wir ihn nur gegen die Records der Tage, die sein
+ // Span berührt. Da Korrekturen Intervalle ausschließlich verkleinern (Start wandert
+ // nach vorne, Ende bleibt), ist die Ausdehnung bei der Registrierung eine sichere
+ // Obermenge - es kann nie eine neue Überlappung entstehen. Durch Iteration in
+ // Einfüge-Reihenfolge (Index) bleibt das Ergebnis bit-identisch zur alten Version.
+ var oldRecordsByDay = new Dictionary>();
+ int insertionIndex = 0;
foreach (var newRecord in serviceRecords)
{
@@ -1264,34 +1293,19 @@ namespace BeWo.Report.ReportObjects
EndDateTime = newRecord.Start.AddMinutes((double)minuten)
};
- foreach (var oldRecord in oldRecords)
+ foreach (var oldEntry in CollectOverlapCandidates(oldRecordsByDay, newSpan))
{
+ var oldRecord = oldEntry.Record;
if (!CorrectOverlappingTime(oldRecord, newRecord))
{
continue;
}
- var oldSpan = new DateTimeSpan
- {
- StartDateTime = oldRecord.Start,
- EndDateTime = oldRecord.Start.AddMinutes((double)oldRecord.RoundedDuration)
- };
- if (oldRecord.GroupRoundedDuration.HasValue)
- {
- oldSpan.EndDateTime = oldRecord.Start.AddMinutes((double)oldRecord.GroupRoundedDuration);
- }
+ // Gecachter Span; wird bei der Korrektur unten in-place mit oldRecord synchron gehalten.
+ var oldSpan = oldEntry.Span;
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;
@@ -1337,8 +1351,84 @@ namespace BeWo.Report.ReportObjects
}
}
- oldRecords.Add(newRecord);
+ // newRecord wird zum "alten" Record: in alle Tages-Buckets seiner (ggf. bereits
+ // gekürzten) Ausdehnung einsortieren. newSpan ist zugleich der gecachte Span.
+ RegisterOldRecord(oldRecordsByDay, new OverlapEntry(newRecord, newSpan, insertionIndex++));
+ }
+ }
+
+ ///
+ /// Sammelt alle bereits verarbeiteten Records, deren Tag der übergebene Span berührt, in
+ /// exakter Einfüge-Reihenfolge. Der häufigste Fall (Record innerhalb eines Tages) wird ohne
+ /// Zwischenliste/Sortierung bedient; nur bei tagesübergreifenden Spans werden mehrere Buckets
+ /// zusammengeführt, dedupliziert und nach Einfüge-Index sortiert.
+ ///
+ private static IEnumerable CollectOverlapCandidates(
+ Dictionary> oldRecordsByDay, DateTimeSpan newSpan)
+ {
+ DateTime firstDay = newSpan.StartDateTime.Date;
+ DateTime lastDay = newSpan.EndDateTime.Date;
+ if (lastDay < firstDay)
+ {
+ lastDay = firstDay;
+ }
+
+ if (firstDay == lastDay)
+ {
+ // Ein Bucket ist bereits in Einfüge-Reihenfolge -> direkt verwenden.
+ List bucket;
+ if (oldRecordsByDay.TryGetValue(firstDay, out bucket))
+ {
+ return bucket;
+ }
+ return System.Linq.Enumerable.Empty();
+ }
+
+ var candidates = new List();
+ var seen = new HashSet();
+ for (DateTime day = firstDay; day <= lastDay; day = day.AddDays(1))
+ {
+ List bucket;
+ if (!oldRecordsByDay.TryGetValue(day, out bucket))
+ {
+ continue;
+ }
+ foreach (var entry in bucket)
+ {
+ // Ein tagesübergreifender Record steht in mehreren Buckets -> nur einmal prüfen.
+ if (seen.Add(entry.Index))
+ {
+ candidates.Add(entry);
+ }
+ }
+ }
+ candidates.Sort((a, b) => a.Index.CompareTo(b.Index));
+ return candidates;
+ }
+
+ ///
+ /// Registriert einen Record in allen Tages-Buckets, die seine Ausdehnung berührt.
+ ///
+ private static void RegisterOldRecord(
+ Dictionary> oldRecordsByDay, OverlapEntry entry)
+ {
+ DateTime firstDay = entry.Span.StartDateTime.Date;
+ DateTime lastDay = entry.Span.EndDateTime.Date;
+ if (lastDay < firstDay)
+ {
+ lastDay = firstDay;
+ }
+
+ for (DateTime day = firstDay; day <= lastDay; day = day.AddDays(1))
+ {
+ List bucket;
+ if (!oldRecordsByDay.TryGetValue(day, out bucket))
+ {
+ bucket = new List();
+ oldRecordsByDay[day] = bucket;
+ }
+ bucket.Add(entry);
}
}
diff --git a/ReportImp/HwHilfswerkInklusionUndTeilhabe/CustomMitarbeiterstundenkontoBerechnung.cs b/ReportImp/HwHilfswerkInklusionUndTeilhabe/CustomMitarbeiterstundenkontoBerechnung.cs
index ddd4ea5fa..8fb26e006 100644
--- a/ReportImp/HwHilfswerkInklusionUndTeilhabe/CustomMitarbeiterstundenkontoBerechnung.cs
+++ b/ReportImp/HwHilfswerkInklusionUndTeilhabe/CustomMitarbeiterstundenkontoBerechnung.cs
@@ -56,16 +56,16 @@ namespace HwHilfswerkInklusionUndTeilhabe
return dd;
}
- public override bool AddServiceRecordToDuration(ServiceRecord item)
+ public override bool AddServiceRecordToDuration(StundenkontoServiceRecord item)
{
- if (HatEmployeeAmbulantContract(item.Employee.GetValidContractForDate(item.Start.Value)))
+ if (HatEmployeeAmbulantContract(item.Employee.GetValidContractForDate(item.Start)))
{
return true;
}
- if (item.ServiceDescription != null && item.ServiceDescription.ServiceCategory != null)
+ if (item.ServiceDescription != null && item.ServiceDescription.Category != null)
{
- if (item.ServiceDescription.ServiceCategory.Name == "Arbeitszeit")
+ if (item.ServiceDescription.Category.Name == "Arbeitszeit")
{
return true;
}
@@ -74,21 +74,21 @@ namespace HwHilfswerkInklusionUndTeilhabe
return false;
}
- protected override ServiceRecordSummary BerechneStunden(MitarbeiterstundenkontoRO.EmployeeDetail empDetail, IList groupFilteredRecords, DateTime berichtsAnfang,
+ 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();
+ sum.RecordsImBerichtszeitraum = new List();
//base.CorrectOverlappingTimes(groupFilteredRecords);
foreach (var item in groupFilteredRecords)
{
if (!IsServiceRecordFehlzeit(item))
{
- if (item.Start.Value >= berichtsAnfang && item.Start.Value < berichtsEnde)
+ if (item.Start >= berichtsAnfang && item.Start < berichtsEnde)
{
//if (AddServiceRecordToDuration(item))
//{
@@ -104,23 +104,23 @@ namespace HwHilfswerkInklusionUndTeilhabe
}
if (item.ServiceDescription != null &&
- item.ServiceDescription.ServiceCategory != null)
+ item.ServiceDescription.Category != null)
{
- if (item.Employee != null && HatEmployeeAmbulantContract(item.Employee.GetValidContractForDate(item.Start.Value)))
+ if (item.Employee != null && HatEmployeeAmbulantContract(item.Employee.GetValidContractForDate(item.Start)))
{
arbeitszeit += duration;
- if (item.ServiceDescription.ServiceCategory.IsBillable)
+ if (item.ServiceDescription.Category.IsBillable)
{
flsGesamt += duration;
}
}
else
{
- if (item.ServiceDescription.ServiceCategory.Name.ToLower().Contains("arbeitszeit"))
+ if (item.ServiceDescription.Category.Name.ToLower().Contains("arbeitszeit"))
{
arbeitszeit += duration;
}
- else if (item.ServiceDescription.ServiceCategory.IsBillable)
+ else if (item.ServiceDescription.Category.IsBillable)
{
flsGesamt += duration;
}