Repeating Tasks Technical Implementation

Repeating Tasks Technical Implementation

Repeating Tasks Logic and Flow Documentation

Overview

This document outlines the business logic and flow for the Repeating Tasks feature in the Task Management System (TMS). The repeating tasks functionality allows users to create recurring tasks that automatically generate new instances based on specified frequency and duration parameters.

Implementation Approach: SQL-Based Solution

Chosen Approach: SQL Stored Procedures

  • Date calculations and task generation will be handled in SQL stored procedures
  • Benefits: Better performance with large datasets, reduced application-database round trips
  • All frequency logic (Daily/Weekly/Monthly/Quarterly) implemented in T-SQL
  • Transaction safety ensured through SQL transaction management

Supported Frequencies

  1. Daily - Task repeats every day
  2. Weekly - Task repeats on selected days of the week (multiple days can be chosen)
  3. Monthly - Task repeats on selected dates of the month (multiple dates can be chosen)
  4. Quarterly - Task repeats on selected dates within each quarter (multiple dates can be chosen)

Core Logic Flow

1. User Input Collection

  • User selects frequency type (Daily/Weekly/Monthly/Quarterly)
  • User sets RepeatUntil date (end date for repetition)
  • Based on frequency:
    • Daily: No additional input needed
    • Weekly: User selects specific days (Monday, Tuesday, etc.)
    • Monthly: User selects specific dates (1st, 15th, 28th, etc.)
    • Quarterly: User selects specific dates within quarters

2. Validation Phase

  • On TaskFlowPlan Details Page we will have a “Make Repeating” button , that will be visible only if the task is in ‘T’ (To Do) status and not already repeating.
  • Validate RepeatUntil date constraints
  • Verify user permissions

3. Date Calculation Phase

  • Calculate all dates from StartDate to RepeatUntil based on selected frequency
  • Handle edge cases (month boundaries, leap years, etc.)

4. Task Generation Phase

  • Create individual task records for each calculated date
  • Link all instances via RepeatTaskPlanId
  • Update original task as repeating template

5. Business Rules Application

  • Auto-acceptance for repeating instances
  • Date synchronization (Start = Target = Committed)
  • Cascade deletion rules

Repeating Task Creation UI

  • “Make Repeating” button on TaskFlowPlan Details Page
  • Modal dialog for frequency and repeat until date selection will be displayed.
  • StartDate , Assignee details will be fetched from original task and displayed as read-only fields in the modal.

TaskFlowIndex Display

  • Shows regular tasks + repeating tasks due within 15 days
  • Repeating tasks sorted by TargetDate on TaskFlowIndex when ind is R

Detailed Frequency Logic

Daily Frequency Logic

  • Input: Start date and repeat until date
  • Process:
    • Start from the original task’s StartDate
    • Add 1 day repeatedly to generate subsequent dates
    • Include the start date as the first instance
    • Continue adding days until reaching or exceeding the repeat until date
    • Stop when current date exceeds repeat until date
  • Output: List of all dates from start date to repeat until date (inclusive)
  • Example: Start=2025-01-01, RepeatUntil=2025-01-05 → Dates: 2025-01-01, 2025-01-02, 2025-01-03, 2025-01-04, 2025-01-05

Weekly Frequency Logic

  • Input: Start date, repeat until date, selected days of week (multiple selection allowed)
  • Process:
    • Start from start date
    • Check each subsequent day if it matches any selected day of week
    • Add matching dates to result list
    • Continue until repeat until date reached
  • Output: List of dates that fall on selected days of week within the date range

Monthly Frequency Logic

  • Input: Start date, repeat until date, selected dates of month (multiple selection allowed, e.g., 1st, 15th, 28th)
  • Process:
    • For each month from start month to repeat until month
    • For each selected date in that month, create a date if it exists
    • Handle months with different number of days (e.g., if 31st selected but month has only 30 days, skip or adjust)
  • Output: List of dates matching selected dates within each month

Quarterly Frequency Logic

  • Input: Start date, repeat until date, selected dates within quarters (multiple selection allowed)
  • Process:
    • Divide year into quarters (Q1: Jan-Mar, Q2: Apr-Jun, Q3: Jul-Sep, Q4: Oct-Dec)
    • For each quarter within the date range
    • For each selected date in that quarter, create a date if it exists
    • Handle quarter boundaries and month transitions
  • Output: List of dates matching selected dates within each quarter

Task Instance Creation Logic

For Each Calculated Date:

  1. Generate Unique Task ID: Create new 8-digit sequential task ID
  2. Copy Task Data: Duplicate all task information from original task
  3. Set Linking: Set RepeatTaskPlanId to original task’s TaskPlanId
  4. Synchronize Dates: StartDate = TargetDate = CommittedDate = calculated_date
  5. Set Status: TaskPlanStatus = “T” (To Do)
  6. Mark as Instance: IsRepeating = “N” (individual instances are not repeating themselves)
  7. Insert Record: Create new TaskFlowPlan record in database

Update Original Task:

  1. Mark as Repeating: Set IsRepeating = “Y”
  2. Set Frequency: Set RepeatFrequency to selected frequency
  3. Set End Date: Set RepeatUntil to selected repeat until date
  4. Update Record: Save changes to original task record

Business Rules & Validations

Validation / Alert Rules

  1. Task Status: Only tasks with status ‘T’ (To Do) can be made repeating
  2. Existing Repeat: Tasks already marked as repeating cannot be converted again
  3. Date Validation: RepeatUntil must be ≤ 31 March next FY
  4. Assignee Validation: Only task assigners can create repeating tasks
  5. Reference Check: Tasks with existing RefTaskId cannot be made repeating

Business Rules for Repeating Tasks

  • Auto-Acceptance: Repeating task instances don’t require manual acceptance by assignee
  • Date Synchronization: StartDate = TargetDate = CommittedDate for all instances
  • Assignee Restrictions: Assignee cannot modify dates on repeating task instances
  • Task Linking: All instances linked via RepeatTaskPlanId to original task
  • Cascade Deletion: Deleting any instance deletes all related instances in the chain
  • Status Inheritance: New instances start with “T” (To Do) status
  • Template Protection: Original task becomes a template and cannot be modified once repeating

Data Flow Diagrams

Task Creation Flow

User Input → Validation → Date Calculation → Task Generation → Database Insert → Update Original Task

Daily Task Example Flow

Input: Start=2025-01-01, RepeatUntil=2025-01-05
Process: Add 1 day repeatedly from start date
Result: 2025-01-01, 2025-01-02, 2025-01-03, 2025-01-04, 2025-01-05
Output: 5 task instances created with synchronized dates

Weekly Task Example Flow

Input: Start=2025-01-01, RepeatUntil=2025-01-15, Days=[Monday, Wednesday]
Process: Check each day from 2025-01-01 to 2025-01-15
Result: 2025-01-01 (Wed), 2025-01-06 (Mon), 2025-01-08 (Wed), 2025-01-13 (Mon)
Output: 4 task instances created with synchronized dates

Monthly Task Example Flow

Input: Start=2025-01-01, RepeatUntil=2025-03-31, Dates=[1st, 15th, 28th]
Process: For each month, create tasks on selected dates if they exist
Result: Jan 1,15,28; Feb 1,15,28; Mar 1,15,28 (adjusted for shorter months)
Output: 9 task instances created

Quarterly Task Example Flow

Input: Start=2025-01-01, RepeatUntil=2025-12-31, Dates=[1st, 15th]
Process: Q1 (Jan-Mar): 1st,15th; Q2 (Apr-Jun): 1st,15th; etc.
Result: Tasks created on 1st and 15th of each quarter
Output: 8 task instances created

SQL Implementation - CTE-Based Queries with C# Variables

Approach: CTE Queries + C# Variables + Simple INSERT

This approach uses CTE to generate all task data at once, then uses C# variables for existing data and executes a simple INSERT query.

Daily Frequency - CTE Query

/* =========================================================
   INPUT CONFIGURATION
   ========================================================= */
/* =========================================================
   DAILY TASKS
   ========================================================= */
;WITH DailyCTE AS (
    SELECT @DailyStart AS StartDate
    UNION ALL
    SELECT DATEADD(DAY, @DailyInterval, StartDate)
    FROM DailyCTE
    WHERE DATEADD(DAY, @DailyInterval, StartDate) <= @RepeatUntil
)
SELECT
    'DAILY' AS TaskType,
    StartDate,
    DATEADD(DAY, @DailyGap, StartDate) AS TargetDate
FROM DailyCTE

UNION ALL

/* =========================================================
   WEEKLY TASKS (Calendar Anchored)
   ========================================================= */
SELECT
    'WEEKLY' AS TaskType,
    DATEADD(
        DAY,
        wd.DayOfWeek - DATEPART(WEEKDAY, wk.WeekStart),
        wk.WeekStart
    ) AS StartDate,
    DATEADD(
        DAY,
        @WeeklyGap,
        DATEADD(
            DAY,
            wd.DayOfWeek - DATEPART(WEEKDAY, wk.WeekStart),
            wk.WeekStart
        )
    ) AS TargetDate
FROM (
    SELECT DATEADD(WEEK, v.number, @WeeklyStart) AS WeekStart
    FROM master..spt_values v
    WHERE v.type = 'P'
      AND DATEADD(WEEK, v.number, @WeeklyStart) <= @RepeatUntil
) wk
CROSS JOIN @WeeklyDays wd
WHERE DATEADD(
        DAY,
        wd.DayOfWeek - DATEPART(WEEKDAY, wk.WeekStart),
        wk.WeekStart
      ) BETWEEN @WeeklyStart AND @RepeatUntil

UNION ALL

/* =========================================================
   MONTHLY TASKS (Calendar Anchored – FINAL RULE)
   ========================================================= */
SELECT
    'MONTHLY' AS TaskType,
    DATEFROMPARTS(
        YEAR(m.MonthDate),
        MONTH(m.MonthDate),
        CASE
            WHEN @MonthlyDay <= DAY(EOMONTH(m.MonthDate))
                THEN @MonthlyDay
            ELSE DAY(EOMONTH(m.MonthDate)) - 1  -- 🔒 FINAL RULE
        END
    ) AS StartDate,
    DATEADD(
        DAY,
        @MonthlyGap,
        DATEFROMPARTS(
            YEAR(m.MonthDate),
            MONTH(m.MonthDate),
            CASE
                WHEN @MonthlyDay <= DAY(EOMONTH(m.MonthDate))
                    THEN @MonthlyDay
                ELSE DAY(EOMONTH(m.MonthDate)) - 1
            END
        )
    ) AS TargetDate
FROM (
    SELECT DATEADD(MONTH, v.number, @MonthlyStart) AS MonthDate
    FROM master..spt_values v
    WHERE v.type = 'P'
      AND DATEADD(MONTH, v.number, @MonthlyStart) <= @RepeatUntil
) m
WHERE DATEFROMPARTS(
        YEAR(m.MonthDate),
        MONTH(m.MonthDate),
        CASE
            WHEN @MonthlyDay <= DAY(EOMONTH(m.MonthDate))
                THEN @MonthlyDay
            ELSE DAY(EOMONTH(m.MonthDate)) - 1
        END
      ) BETWEEN @MonthlyStart AND @RepeatUntil

OPTION (MAXRECURSION 0);

C# Implementation

using System;
using System.Collections.Generic;
using System.Linq;

namespace RepeatingTaskFinal
{
    class Program
    {
        static void Main()
        {
            // ===============================
            // DAILY EXAMPLE
            // ===============================
            Console.WriteLine("=================================");
            Console.WriteLine("DAILY EXAMPLE");
            Console.WriteLine("Every 6 days | Gap = 2 days");
            Console.WriteLine("=================================\n");

            var dailyTasks = RepeatEngine.GenerateDaily(
                startDate: new DateTime(2025, 1, 1),
                repeatUntil: new DateTime(2025, 2, 1),
                intervalDays: 6,
                gapDays: 2
            );

            Print(dailyTasks);

            // ===============================
            // WEEKLY EXAMPLE
            // ===============================
            Console.WriteLine("\n=================================");
            Console.WriteLine("WEEKLY EXAMPLE");
            Console.WriteLine("2 days per week (Mon & Thu) | Gap = 3 days");
            Console.WriteLine("=================================\n");

            var weeklyTasks = RepeatEngine.GenerateWeekly(
                startDate: new DateTime(2025, 1, 6), // Monday
                repeatUntil: new DateTime(2025, 2, 6),
                daysOfWeek: new List<DayOfWeek>
                {
                    DayOfWeek.Monday,
                    DayOfWeek.Thursday
                },
                gapDays: 3
            );

            Print(weeklyTasks);

            // ===============================
            // MONTHLY EXAMPLE
            // ===============================
            Console.WriteLine("\n=================================");
            Console.WriteLine("MONTHLY EXAMPLE");
            Console.WriteLine("30th of every month | Gap = 2 days");
            Console.WriteLine("=================================\n");

            var monthlyTasks = RepeatEngine.GenerateMonthly(
                startDate: new DateTime(2025, 1, 30),
                repeatUntil: new DateTime(2025, 6, 30),
                selectedDay: 30,
                gapDays: 2
            );

            Print(monthlyTasks);

            Console.WriteLine("\n=== DONE ===");
        }

        static void Print(List<TaskItem> tasks)
        {
            foreach (var t in tasks)
            {
                Console.WriteLine(
                    $"Start: {t.StartDate:dd-MMM-yyyy}  →  " +
                    $"Target: {t.TargetDate:dd-MMM-yyyy}"
                );
            }
        }
    }

    // =====================================================
    // TASK MODEL
    // =====================================================
    public class TaskItem
    {
        public DateTime StartDate { get; set; }
        public DateTime TargetDate { get; set; }
    }

    // =====================================================
    // REPEAT ENGINE (FINAL, BUSINESS-SAFE)
    // =====================================================
    public static class RepeatEngine
    {
        // ---------------- DAILY ----------------
        public static List<TaskItem> GenerateDaily(
            DateTime startDate,
            DateTime repeatUntil,
            int intervalDays,
            int gapDays
        )
        {
            var result = new List<TaskItem>();
            var cursor = startDate;

            while (cursor <= repeatUntil)
            {
                result.Add(new TaskItem
                {
                    StartDate = cursor,
                    TargetDate = cursor.AddDays(gapDays)
                });

                cursor = cursor.AddDays(intervalDays);
            }

            return result;
        }

        // ---------------- WEEKLY ----------------
        // Calendar-anchored (e.g. Mon & Thu)
        public static List<TaskItem> GenerateWeekly(
            DateTime startDate,
            DateTime repeatUntil,
            List<DayOfWeek> daysOfWeek,
            int gapDays
        )
        {
            var result = new List<TaskItem>();
            var weekCursor = startDate;

            while (weekCursor <= repeatUntil)
            {
                foreach (var day in daysOfWeek.OrderBy(d => d))
                {
                    var start =
                        weekCursor.AddDays((int)day - (int)weekCursor.DayOfWeek);

                    if (start >= startDate && start <= repeatUntil)
                    {
                        result.Add(new TaskItem
                        {
                            StartDate = start,
                            TargetDate = start.AddDays(gapDays)
                        });
                    }
                }

                weekCursor = weekCursor.AddDays(7);
            }

            return result
                .GroupBy(t => t.StartDate)
                .Select(g => g.First())
                .OrderBy(t => t.StartDate)
                .ToList();
        }

        // ---------------- MONTHLY ----------------
        // 🔒 FINAL BUSINESS RULE:
        // If selected day does not exist → (last day − 1)
        // Feb → 27, Apr → 29
        public static List<TaskItem> GenerateMonthly(
            DateTime startDate,
            DateTime repeatUntil,
            int selectedDay,
            int gapDays
        )
        {
            var result = new List<TaskItem>();
            var cursor = new DateTime(startDate.Year, startDate.Month, 1);

            while (cursor <= repeatUntil)
            {
                int daysInMonth =
                    DateTime.DaysInMonth(cursor.Year, cursor.Month);

                int adjustedDay =
                    selectedDay <= daysInMonth
                        ? selectedDay
                        : daysInMonth - 1; // 🔒 DO NOT CHANGE

                var start = new DateTime(
                    cursor.Year,
                    cursor.Month,
                    adjustedDay
                );

                if (start >= startDate && start <= repeatUntil)
                {
                    result.Add(new TaskItem
                    {
                        StartDate = start,
                        TargetDate = start.AddDays(gapDays)
                    });
                }

                cursor = cursor.AddMonths(1);
            }

            return result;
        }
    }
}