Files
BeWoPlaner/Data/Utils/IntervalFinderHelper.cs
Lyndon Jetten e0447d0a05 MoK:
Intervallfinder überarbeitet & Bug gefixt, der zu einem Fehler beim Suchen nach freien Intervallen über mehrere Tage geführt hatte, wenn die Enduhrzeit vor der Startuhrzeit lag.

Berichte mit Parametern.

Verbesserte Mitarbeiter-, Klienten-, Team-, und Organisationsdropdowns mit Suche.

Quittierungsbelegresultate (zum Unterschreiben) wird wie die Zeiterfassung paginiert.

FaC:

neuer Kalender noch nicht fertig.
2024-11-12 18:37:23 +01:00

439 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using BeWo.Data.Entities;
using BS.Shared.Core;
using BS.Shared.Extensions;
using DevExpress.XtraScheduler;
using DevExpress.XtraScheduler.Compatibility;
using NHibernate.Criterion;
using NHibernate.SqlCommand;
namespace BeWo.Data.Utils
{
public class IntervalFinderHelper
{
public static List<DateTimeSpan> GenerateIntervalsForCriteria(DateTime intervalStart, DateTime intervalEnd, bool skipWeekends)
{
var result = new List<DateTimeSpan>();
if (intervalEnd <= intervalStart)
{
return result;
}
var start = intervalStart;
var end = start.MergeDatesByDate(intervalEnd);
while(end <= intervalEnd)
{
end = start.Date.Equals(intervalEnd.Date) ? start.MergeDatesByDate(intervalEnd) : new DateTime(start.Year, start.Month, start.Day, 23, 59, 59);
if(!start.Date.Equals(intervalStart.Date))
{
start = new DateTime(start.Year, start.Month, start.Day);
}
if(skipWeekends && (start.Date.DayOfWeek == DayOfWeek.Saturday || start.Date.DayOfWeek == DayOfWeek.Sunday))
{
start = start.AddDays(1);
end = start.Date.Equals(intervalEnd.Date) ? start.MergeDatesByDate(intervalEnd) : new DateTime(start.Year, start.Month, start.Day, 23, 59, 59);
continue;
}
result.AddIfNotIn(new DateTimeSpan(start, end));
start = start.AddDays(1);
end = start.MergeDatesByDate(intervalEnd);
}
return result;
}
public static List<ICriterion> CreateIntervalRecurrenceCriterias(List<DateTimeSpan> intervals, bool skipWeekends)
{
var result = new List<ICriterion>();
foreach(var interval in intervals)
{
var start = interval.StartDate;
var end = interval.EndDate;
result.AddIfNotIn(CreateRecurrenceCriterion(start, end));
}
// TODO: KGW
//for (var i = 0; i < totalDays; i++)
//{
// start = start.AddDays(i).MergeDatesByDate(startDate);
// if (skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday))
// {
// continue;
// }
// DateTime e;
// var end = start.MergeDatesByDate(endDate);
// while ((e = start.AddMinutes(duration)) <= end && start <= end.AddMinutes(-1 * duration))
// {
// result.AddIfNotIn(CreateRecurrenceCriterion(start, e));
// start = start.AddMinutes(intervalBuffer);
// }
//}
return result;
}
private static ICriterion CreateRecurrenceCriterion(DateTime start, DateTime end)
{
var recurrenceBetween = $"'{start:yyyy-MM-dd HH:mm:ss}' BETWEEN STR_TO_DATE(SUBSTRING(RecurrenceInfo, 24, 19), '%m/%d/%Y %H:%i:%s') AND STR_TO_DATE(SUBSTRING(RecurrenceInfo, 50, 19), '%m/%d/%Y %H:%i:%s')";
var recurrenceAfter = $"'{start:yyyy-MM-dd HH:mm:ss}' > STR_TO_DATE(SUBSTRING(RecurrenceInfo, 24, 19), '%m/%d/%Y %H:%i:%s')";
var result = Restrictions.Or(
Restrictions.And(
Restrictions.Not(Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "Range", MatchMode.Anywhere)),
Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "OccurrenceCount=\"10\"", MatchMode.Anywhere)),
Restrictions.Or(
Restrictions.Or(
Restrictions.And(Restrictions.Lt(nameof(SchedulerAppointment.StartDate), end), Restrictions.Ge(nameof(SchedulerAppointment.EndDate), start)),
Restrictions.Or(Restrictions.And(
Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "End", MatchMode.Anywhere),
Restrictions.And(Restrictions.IsNotNull(nameof(SchedulerAppointment.RecurrenceInfo)), Expression.Sql(new SqlString(recurrenceBetween)))
), Restrictions.And(
Restrictions.And(
Restrictions.IsNotNull(nameof(SchedulerAppointment.RecurrenceInfo)),
Restrictions.Not(Restrictions.Like(nameof(SchedulerAppointment.RecurrenceInfo), "End", MatchMode.Anywhere))),
Expression.Sql(recurrenceAfter)))
),
Restrictions.And(Restrictions.Eq(nameof(SchedulerAppointment.Type), 3), Expression.Sql(new SqlString(recurrenceBetween)))));
return result;
}
public static Dictionary<DateTime, List<DateTimeSpan>> CreateFreeIntervalsDictionary(IEnumerable<DateTimeSpan> freeIntervals)
{
var result = new Dictionary<DateTime, List<DateTimeSpan>>();
var groupedIntervals = from dateTimeSpan in freeIntervals group dateTimeSpan by dateTimeSpan.StartDate.Date;
var unsortedDictionary = new Dictionary<DateTime, List<DateTimeSpan>>();
foreach(var grouping in groupedIntervals)
{
var orderedList = grouping.OrderBy(dateTimeSpan => dateTimeSpan.StartDate).ToList();
unsortedDictionary.AddOrUpdateValueInDictionary(grouping.Key, orderedList);
}
var keys = unsortedDictionary.Keys.OrderBy(key => key.Date).ToList();
foreach(var key in keys)
{
result.Add(key, unsortedDictionary[key]);
}
return result;
}
public static List<DateTimeSpan> CreateIntervals(int duration, DateTime intervalStart, DateTime intervalEnd, bool skipWeekends, int intervalBuffer)
{
var result = new List<DateTimeSpan>();
if(duration > 0 && intervalStart < intervalEnd && intervalBuffer > 10)
{
var timeSpan = intervalEnd - intervalStart;
var totalDays = Convert.ToInt32(Math.Ceiling(timeSpan.TotalDays));
for(var i = 0; i < totalDays; i++)
{
var start = intervalStart.AddDays(i).MergeDatesByDate(intervalStart);
if(skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday))
{
continue;
}
DateTime e;
var end = intervalEnd;
while((e = start.AddMinutes(duration)) <= end && start <= end.AddMinutes(-1 * duration))
{
result.Add(new DateTimeSpan(start, e, true));
start = start.AddMinutes(intervalBuffer);
}
}
}
return result;
}
/// <summary>
/// Liefert DateTimeSpan-Objekte für ganze Tage zwischen zwei Datumsangaben zurück.
/// Z.B. 04.12.2023 bis 06.12.2023 liefert drei DateTimeSpan für den 04.12., 05.12. und 06.12. zurück.
/// </summary>
/// <param name="intervalStart">Intervallstart</param>
/// <param name="intervalEnd">Intervallende</param>
/// <returns>Liste der DateTimeSpan-Objekte für die Anzahl der Tage im angegebenen Intervall.</returns>
public static List<DateTimeSpan> GenereateIntervalsForChecking(DateTime intervalStart, DateTime intervalEnd)
{
var start = intervalStart.Date;
var end = intervalEnd.Date;
var result = new List<DateTimeSpan>();
if (end < start)
{
return result;
}
var days = Convert.ToInt32((end - start).TotalDays);
if (days == 0)
{
days = 1;
}
var rest = days % 6;
var limit = (days - rest) / 6;
var s = start;
DateTime e;
for(var i = 0; i < limit; i++)
{
e = s.AddDays(5);
result.Add(new DateTimeSpan(s, e));
s = e.AddDays(1);
}
if(rest > 0)
{
e = s.AddDays(rest);
if (rest == 1)
{
e = e.AddDays(-1);
}
result.Add(new DateTimeSpan(s, e));
}
return result;
}
public static IEnumerable<SchedulerAppointment> CalculateRecurrences(IEnumerable<SchedulerAppointment> appointments, DateTime start, DateTime end, IReadOnlyCollection<RecurrenceInformation> changedOccurrences, IReadOnlyCollection<RecurrenceInformation> deletedOccurrences)
{
var result = new List<SchedulerAppointment>();
var interval = new TimeInterval(start, end);
foreach (var appointment in appointments.Where(app => app.RecurrenceInfo != null && app.Type == 1 && app.StartDate.HasValue && app.EndDate.HasValue))
{
var recurrenceInfo = new RecurrenceInfo();
recurrenceInfo.FromXml(appointment.RecurrenceInfo);
var occurrenceCalculator = OccurrenceCalculator.CreateInstance(recurrenceInfo);
var pattern = StaticAppointmentFactory.CreateAppointment(AppointmentType.Pattern);
pattern.RecurrenceInfo.FromXml(appointment.RecurrenceInfo);
pattern.Start = pattern.RecurrenceInfo.Start;
pattern.End = pattern.RecurrenceInfo.End;
var patternId = pattern.RecurrenceInfo.Id.ToString();
var occurrences = occurrenceCalculator.CalcOccurrences(interval, pattern);
if (occurrences.Count > 0)
{
foreach (var app in occurrences.GetAppointments(interval))
{
if(appointment.EndDate == null || appointment.StartDate == null)
{
continue;
}
var index = app.RecurrenceIndex;
var duration = (appointment.EndDate.Value - appointment.StartDate.Value).TotalMinutes;
var s = app.Start;
var e = app.Start.AddMinutes(duration);
var isOutOfInterval = s.AreInBetweenDates(e, start, end) == false;
var isChangedOccurrence = changedOccurrences.Any(a => a.PatternId.Equals(patternId) && a.Index == index);
var isDeletedOccurrence = deletedOccurrences.Any(a => a.PatternId.Equals(patternId) && a.Index == index);
if (isOutOfInterval || isChangedOccurrence || isDeletedOccurrence)
{
continue;
}
var recurringAppointment = new SchedulerAppointment
{
AllDay = appointment.AllDay,
CustomerList = appointment.CustomerList,
Notice = appointment.Notice,
EmployeeList = appointment.EmployeeList,
EndDate = app.Start.AddMinutes(duration),
FormerBookingSequenceOid = appointment.FormerBookingSequenceOid,
IsPrivate = appointment.IsPrivate,
Location = appointment.Location,
Originator = appointment.Originator,
RecurrenceInfo = app.RecurrenceInfo.ToXml(),
ReminderInfo = appointment.ReminderInfo,
ResourceList = appointment.ResourceList,
StartDate = app.Start,
Subject = appointment.Subject ?? string.Empty,
Type = appointment.Type
};
result.AddIfNotIn(recurringAppointment);
}
}
}
return result;
}
public static IEnumerable<SchedulerAppointment> GetRecurrencesForIntervalFinder(List<SchedulerAppointment> appointments, DateTime startDate, DateTime endDate, IReadOnlyCollection<RecurrenceInformation> changedOccurrences, IReadOnlyCollection<RecurrenceInformation> deletedOccurrences, bool skipWeekends)
{
var result = new List<SchedulerAppointment>();
var dayCount = Convert.ToInt32(Math.Ceiling((endDate - startDate).TotalDays));
if(dayCount <= 0)
{
return result;
}
var startTimeBeforeEndTime = startDate >= startDate.MergeDatesByDate(endDate);
if(startTimeBeforeEndTime || startDate.Date == endDate.Date)
{
return CalculateRecurrences(appointments, startDate, endDate, changedOccurrences, deletedOccurrences);
}
var start = startDate;
for(var i = 0; i < dayCount; i++)
{
if(skipWeekends && (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday))
{
start = start.AddDays(1);
continue;
}
var end = i == dayCount - 1
? start.MergeDatesByDate(endDate)
: start.AddDays(1).MergeDatesByDate(start.Date);
start = i == 0
? start
: start.MergeDatesByDate(start.Date);
var calculatedRecurrences = CalculateRecurrences(appointments, start, end, changedOccurrences, deletedOccurrences);
result.AddRange(calculatedRecurrences);
start = start.AddDays(1);
}
return result;
}
public static Dictionary<DateTimeSpan, Dictionary<DateTime, bool>> CreateFreeIntervals(List<DateTimeSpan> freeIntervals, DateTime intervalStart, DateTime intervalEnd, int buffer, int durationInMinutes)
{
var result = new Dictionary<DateTimeSpan, Dictionary<DateTime, bool>>();
if(intervalEnd < intervalStart)
{
(intervalEnd, intervalStart) = (intervalStart, intervalEnd);
}
if(durationInMinutes <= 0)
{
durationInMinutes = 60;
}
if(buffer < 30)
{
buffer = 30;
}
var possibleIntervals = CalculatePossibleIntervals(intervalStart, intervalEnd, buffer, durationInMinutes);
var days = freeIntervals.Select(dateTimeSpan => dateTimeSpan.StartDate.Date).Distinct().ToList();
foreach(var possibleInterval in possibleIntervals)
{
foreach(var day in days)
{
var hasFreeSlot = freeIntervals.Any(freeInterval =>
{
var isTimeEqual = freeInterval.EqualTimes(possibleInterval);
var isDayEqual = freeInterval.StartDate.Date.Equals(day);
return isTimeEqual && isDayEqual;
});
if(result.Keys.Any(key => key.EqualTimes(possibleInterval)))
{
result[possibleInterval].AddAndIgnoreDuplicates(day, hasFreeSlot);
}
else
{
result.Add(possibleInterval, new Dictionary<DateTime, bool> { { day, hasFreeSlot } });
}
}
}
return result;
}
private static List<DateTimeSpan> CalculatePossibleIntervals(DateTime intervalStart, DateTime intervalEnd, int buffer, int durationInMinutes)
{
var possibleIntervals = new List<DateTimeSpan>();
if(intervalEnd < intervalStart)
{
(intervalEnd, intervalStart) = (intervalStart, intervalEnd);
}
if(durationInMinutes <= 0)
{
durationInMinutes = 60;
}
if(buffer < 30)
{
buffer = 30;
}
var start = intervalStart;
var end = intervalStart.AddMinutes(durationInMinutes);
while(end < intervalEnd)
{
if(possibleIntervals.Any(timeSpan => timeSpan.StartDate.CompareTime(start)))
{
start = start.AddMinutes(buffer);
end = start.AddMinutes(durationInMinutes);
continue;
}
possibleIntervals.Add(new DateTimeSpan(start, end, true));
start = start.AddMinutes(buffer);
end = start.AddMinutes(durationInMinutes);
}
return possibleIntervals.OrderBy(dateTimeSpan => dateTimeSpan.StartDate.Hour).ThenBy(dateTimeSpan => dateTimeSpan.StartDate.Minute).ToList();
}
}
}