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.
439 lines
17 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|