using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; using InABox.Core; namespace Comal.Classes { public enum SchedulePeriod { Minute, Hour, Day, Week, Month, Year } public enum ScheduleTrigger { Distance, Usage, Counter1, Counter2, Counter3, Counter4, Counter5 } public enum ScheduleRollover { FromDueDate, FromActualDate } public enum ScheduleType { None, Job, Task } public class ScheduleOffset : BaseObject, IPackable { public ScheduleOffset() { } public ScheduleOffset(int offset, SchedulePeriod period) { Period = period; Offset = offset; } [EnumLookupEditor(typeof(SchedulePeriod), Visible = Visible.Default)] [EditorSequence(1)] public SchedulePeriod Period { get; set; } [IntegerEditor(Visible = Visible.Default)] [EditorSequence(2)] public int Offset { get; set; } public void Pack(BinaryWriter writer) { writer.Write((int)Period); writer.Write(Offset); } public void Unpack(BinaryReader reader) { Period = (SchedulePeriod)reader.ReadInt32(); Offset = reader.ReadInt32(); } } public class ScheduleOffsetList : PackableList { } public class ScheduleLink : EntityLink { [NullEditor] public override Guid ID { get; set; } [TextBoxEditor(Editable = Editable.Hidden)] public string Title { get; set; } [EnumLookupEditor(typeof(ScheduleRollover), Editable = Editable.Hidden)] public ScheduleRollover Rollover { get; set; } } public class NextScheduleDue : CoreAggregate { public override Expression> Aggregate => x => x.DueDate; public override AggregateCalculation Calculation => AggregateCalculation.Minimum; public override Filter Filter => new Filter(x => x.IncludeInAggregate).IsEqualTo(true); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { Schedule => Schedule.DocumentID, Entity => Entity.ID } }; } public class ActiveSchedules : CoreAggregate { public override Expression> Aggregate => x => x.ID; public override AggregateCalculation Calculation => AggregateCalculation.Count; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { Schedule => Schedule.DocumentID, Entity => Entity.ID } }; } [UserTracking("Task Scheduler")] public class Schedule : Entity, IPersistent, IRemotable, IOneToMany, ILicense { #region Public Properties [NullEditor] public string DocumentClass { get; set; } //[NullEditor] //[DoNotSerialize] public Type? DocumentType() { try { return CoreUtils.GetEntityOrNull(DocumentClass); } catch { return null; } } [NullEditor] public Guid DocumentID { get; set; } [TextBoxEditor] [EditorSequence(0)] public string Title { get; set; } [MemoEditor] [EditorSequence(1)] public string Description { get; set; } // Date Based Scheduling [IntegerEditor] [EditorSequence(2)] [Caption("Schedule Frequency")] public int Frequency { get; set; } = 1; [EnumLookupEditor(typeof(SchedulePeriod))] [EditorSequence(3)] [Caption("Schedule Type")] public SchedulePeriod Period { get; set; } = SchedulePeriod.Month; [DateTimeEditor] [EditorSequence(4)] [Caption("Schedule Due")] public DateTime DueDate { get; set; } = DateTime.Today.AddDays(1); // Trigger-Based Scheduling [IntegerEditor] [EditorSequence(5)] [Caption("Trigger Frequency")] public int Threshold { get; set; } [EnumLookupEditor(typeof(ScheduleTrigger))] [Caption("Trigger Type")] [EditorSequence(6)] public ScheduleTrigger Trigger { get; set; } [IntegerEditor] [EditorSequence(7)] [Caption("Trigger Level")] public int DueThreshold { get; set; } [EnumLookupEditor(typeof(ScheduleType))] [EditorSequence(8)] [Caption("Scheduled Action")] public ScheduleType ScheduleType { get; set; } = ScheduleType.Task; //[LookupEditor(typeof(KanbanType))] [EditorSequence(9)] [Caption("Task Type")] public KanbanTypeLink KanbanType { get; set; } [IntegerEditor] [EditorSequence(10)] [Caption("Lead Time")] public int LeadTime { get; set; } public ScheduleOffsetList Offsets { get; set; } = new ScheduleOffsetList(); [Caption("Assigned To")] [EditorSequence(11)] public EmployeeLink EmployeeLink { get; set; } [Caption("Managed By")] [EditorSequence(12)] public EmployeeLink ManagerLink { get; set; } [Caption("Report Type")] [EditorSequence(13)] public DocumentLink Report { get; set; } [EnumLookupEditor(typeof(ScheduleRollover))] [EditorSequence(14)] public ScheduleRollover Rollover { get; set; } [TimestampEditor(Editable = Editable.Hidden)] public DateTime Completed { get; set; } [NullEditor] public bool Active { get; set; } [CheckBoxEditor] [EditorSequence(15)] public bool IncludeInAggregate { get; set; } = true; #endregion #region Public Methods private DateTime ApplyPeriod(DateTime date) { var result = date; switch (Period) { case SchedulePeriod.Year: return date.AddYears(Frequency); case SchedulePeriod.Month: return date.AddMonths(Frequency); case SchedulePeriod.Week: return date.AddDays(7 * Frequency); case SchedulePeriod.Day: return date.AddDays(Frequency); case SchedulePeriod.Hour: return date.AddHours(Frequency); case SchedulePeriod.Minute: return date.AddMinutes(Frequency); } return result; } private DateTime ApplyOffsets(DateTime date, int index) { if (index >= Offsets.Count) return date; var offset = Offsets[index]; var result = date; var skip = 1; switch (offset.Period) { case SchedulePeriod.Year: result = date.AddYears(offset.Offset); break; case SchedulePeriod.Month: result = date.AddMonths(offset.Offset); break; case SchedulePeriod.Week: // First Lets get to the start of the month var startOfMonth = date.Date.AddDays(1 - date.Day); // Calculate the DOW for the start of the month var dow = (int)startOfMonth.DayOfWeek; // If the next offset is a day-type var tgtdow = 0; if (index < Offsets.Count - 1) { skip++; if (Offsets[index + 1].Period == SchedulePeriod.Day) tgtdow = Offsets[index + 1].Offset; else if (Offsets[index + 1].Period == SchedulePeriod.Hour) tgtdow = Offsets[index + 1].Offset / 24; else if (Offsets[index + 1].Period == SchedulePeriod.Minute) tgtdow = Offsets[index + 1].Offset / (24 * 60); else skip--; } result = startOfMonth.AddDays(0 - dow).AddDays(tgtdow); if (dow > tgtdow) result = result.AddDays(7); result = result.AddDays(Offsets[index].Offset * 7); break; case SchedulePeriod.Day: result = date.AddDays(offset.Offset); break; case SchedulePeriod.Hour: result = date.AddHours(offset.Offset); break; case SchedulePeriod.Minute: result = date.AddMinutes(offset.Offset); break; } return ApplyOffsets(result, index + skip); } public DateTime GetNextDate(DateTime last) { // if this is the first time run, then // just return the due date if (last.IsEmpty()) return DueDate; // Get the base date for the next scheduled run var result = Rollover.Equals(ScheduleRollover.FromDueDate) ? DueDate : last; // Roll it forward to the next due date result = ApplyPeriod(result); // Apply any offsets we need if (Offsets != null && Offsets.Any()) result = ApplyOffsets(result, 0); return result; } #endregion } public interface ISchedulable { //bool ScheduleEnabled { get; set; } [NullEditor] int ActiveSchedules { get; set; } } public interface IScheduleAction { DateTime Completed { get; set; } ScheduleLink ScheduleLink { get; set; } } }