Task Flow Weekly Planned task

Task Flow Weekly Planned task

This plan incorporates the requested naming standards and channel filtering:

  • DTO Class Name: TaskFlowWeeklyPlannedTasks
  • Scheduler Job Class & File Name: TaskFlowWeeklyPlannedSchedulerJob (TaskFlowWeeklyPlannedSchedulerJob.cs)
  • Repository Method Name: GetWeeklyPlannedTasksDetails
  • Channel Check: if (channelType == "E") in job Run()
  • Email Message Greeting: "Hello <strong>{userTasks.UserName}</strong>, Here is your this week assigned tasks."
  • Backdated dates allowed up to 30 days prior to today with warning message "Please note, you have entered a backdated date.".

Proposed Changes

Component 1: ErpCrystal_MFG.Models

[MODIFY] TaskFlowReport.cs

Add TaskFlowWeeklyPlannedTasks DTO class:

public class TaskFlowWeeklyPlannedTasks
{
    public string UserId { get; set; } = string.Empty;
    public string UserName { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
    public List<TaskFlowPlan> Tasks { get; set; } = new();
}

Component 2: ErpCrystal_MFG.Api

[MODIFY] ITaskFlowReportRepository.cs

Add interface method signature:

public Task<List<TaskFlowWeeklyPlannedTasks>> GetWeeklyPlannedTasksDetails(string dbname);

[MODIFY] TaskFlowReportRepository.cs

Implement GetWeeklyPlannedTasksDetails:

public async Task<List<TaskFlowWeeklyPlannedTasks>> GetWeeklyPlannedTasksDetails(string dbname)
{
    var query = @"
    SELECT 
        TFU.TaskUserId AS UserId,
        TFU.TaskUserName AS UserName,
        TFU.Email,
        TP.TaskPlanId,
        TP.TaskPlanName,
        TFT.TaskTypeName,
        IIF(TP.Priority = 'L', 'Low', IIF(TP.Priority = 'M', 'Medium', 'High')) AS PriorityName,
        TFU1.TaskUserName AS CreatedByName,
        TP.StartDate,
        TP.TargetDate,
        TP.Description
    FROM TaskFlowUsers TFU
    INNER JOIN TaskFlowPlan TP ON TFU.TaskUserId = TP.AssignedTo
    LEFT JOIN TaskFlowType TFT ON TP.TaskTypeCode = TFT.TaskTypeCode
    LEFT JOIN TaskFlowUsers TFU1 ON TP.CreatedBy = TFU1.TaskUserId
    WHERE TFU.IsActive = 'Y'
      AND CAST(TP.StartDate AS DATE) BETWEEN DATEADD(DAY, -6, CAST(GETDATE() AS DATE)) AND CAST(GETDATE() AS DATE)
    ORDER BY TFU.TaskUserId, TP.Id DESC";

    using var connection = _DapperContext.SetClientConnection(dbname);
    var data = await connection.QueryAsync<dynamic>(query);

    var grouped = data.GroupBy(x => new { UserId = (string)x.UserId, UserName = (string)x.UserName, Email = (string)x.Email })
        .Select(g => new TaskFlowWeeklyPlannedTasks
        {
            UserId = g.Key.UserId,
            UserName = g.Key.UserName,
            Email = g.Key.Email ?? "",
            Tasks = g.Select(x => new TaskFlowPlan
            {
                TaskPlanId = x.TaskPlanId,
                TaskPlanName = x.TaskPlanName,
                TaskTypeName = x.TaskTypeName,
                PriorityName = x.PriorityName,
                CreatedByName = x.CreatedByName,
                StartDate = x.StartDate,
                TargetDate = x.TargetDate,
                Description = x.Description
            }).ToList()
        }).ToList();

    return grouped;
}

[NEW] TaskFlowWeeklyPlannedSchedulerJob.cs

Create new job class TaskFlowWeeklyPlannedSchedulerJob (scheduledTaskCode: "015"):

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using ErpCrystal_MFG.Models;
using ErpCrystal_MFG.Api.Repositories;
using ErpCrystal_MFG.Api.Controllers;
using ErpCrystal_MFG.Api.Services;

namespace ErpCrystal_MFG.Api.Jobs;

public class TaskFlowWeeklyPlannedSchedulerJob(
    IConfiguration configuration,
    ITaskSchedulerRepository itaskschedulerrepository,
    ITaskFlowReportRepository itaskflowreportrepository,
    IFilePathService ifilepathservice,
    IEmailRepository iemailrepository,
    IUtilityMethodsRepository iutilitymethodsrepository)
{
    private readonly IConfiguration _IConfiguration = configuration;
    private readonly ITaskSchedulerRepository _ITaskSchedulerRepository = itaskschedulerrepository;
    private readonly ITaskFlowReportRepository _ITaskFlowReportRepository = itaskflowreportrepository;
    private readonly IFilePathService _IFilePathService = ifilepathservice;
    private readonly IEmailRepository _IEmailRepository = iemailrepository;
    private readonly IUtilityMethodsRepository _IUtilityMethodsRepository = iutilitymethodsrepository;

    public async Task Run(string dbname, string channelType)
    {
        TaskSchedulerConfig _TaskScheduler = new();
        try
        {
            _TaskScheduler.ActivityName = "Weekly Planned Tasks";

            var userTaskList = await _ITaskFlowReportRepository.GetWeeklyPlannedTasksDetails(dbname);

            if (userTaskList == null || userTaskList.Count == 0)
            {
                _TaskScheduler.ActivityDate = DateTime.Now;
                _TaskScheduler.Notes = "No records found";
                _TaskScheduler.Status = "F";
                await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
                return;
            }

            foreach (var userTasks in userTaskList)
            {
                if (userTasks.Tasks == null || userTasks.Tasks.Count == 0)
                {
                    _TaskScheduler.ActivityDate = DateTime.Now;
                    _TaskScheduler.Status = "F";
                    _TaskScheduler.Notes = $"No records found for User - {userTasks.UserName}";
                    await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
                    continue;
                }

                try
                {
                    if (channelType == "E")
                    {
                        if (string.IsNullOrEmpty(userTasks.Email))
                        {
                            _TaskScheduler.ActivityDate = DateTime.Now;
                            _TaskScheduler.Notes = $"No Email found for user {userTasks.UserName}";
                            _TaskScheduler.Status = "F";
                            await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
                            continue;
                        }

                        var tableRows = string.Join("", userTasks.Tasks.Select(t => $@"
                            <tr>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.TaskPlanId}</td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;""><strong>{t.TaskPlanName}</strong></td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.TaskTypeName}</td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.PriorityName}</td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.CreatedByName}</td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.StartDate?.ToString("dd-MMM-yy")}</td>
                                <td style=""padding: 10px; border-bottom: 1px solid #eeeeee;"">{t.TargetDate?.ToString("dd-MMM-yy")}</td>
                            </tr>"));

                        var emailBody = $@"
                        <div style=""font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 700px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden;"">
                            <div style=""background-color: #2c3e50; color: #ffffff; padding: 20px; text-align: center;"">
                                <h2 style=""margin: 0; font-size: 24px;"">Weekly Planned Tasks</h2>
                            </div>
                            <div style=""padding: 30px; background-color: #f9f9f9;"">
                                <p style=""font-size: 16px; color: #333; margin-top: 0;"">Hello <strong>{userTasks.UserName}</strong>,</p>
                                <p style=""font-size: 14px; color: #666;"">Here is your this week assigned tasks.</p>
                                
                                <table style=""width: 100%; border-collapse: collapse; background-color: #ffffff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-top: 20px; font-size: 13px;"">
                                    <thead>
                                        <tr style=""background-color: #34495e; color: #ffffff;"">
                                            <th style=""padding: 10px; text-align: left;"">Task ID</th>
                                            <th style=""padding: 10px; text-align: left;"">Task Name</th>
                                            <th style=""padding: 10px; text-align: left;"">Type</th>
                                            <th style=""padding: 10px; text-align: left;"">Priority</th>
                                            <th style=""padding: 10px; text-align: left;"">Created By</th>
                                            <th style=""padding: 10px; text-align: left;"">Start Date</th>
                                            <th style=""padding: 10px; text-align: left;"">Target Date</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {tableRows}
                                    </tbody>
                                </table>
                                
                                <div style=""margin-top: 25px; font-size: 12px; color: #7f8c8d; text-align: center;"">
                                    <p>This is an automated report generated by ERP Crystal Task Management.</p>
                                </div>
                            </div>
                        </div>";

                        var emailController = new EmailController(_IEmailRepository, _IUtilityMethodsRepository, _IFilePathService);
                        ReceiverEmail _ReceiverEmail = new()
                        {
                            email1 = userTasks.Email,
                            emailsubject = "Weekly Planned Tasks List",
                            emailmessage = emailBody,
                            emailfilename = ""
                        };

                        var emaildata = await emailController.SendEmail(dbname, _ReceiverEmail, "C");
                        var emailResult = (emaildata as OkObjectResult)?.Value as ReceiverEmail;
                        
                        var status = emailResult?.msgcode != 1 ? "F" : "S";
                        var note = emailResult?.msgcode != 1 ? "could not succeed" : "succeeded";
                        
                        _TaskScheduler.ActivityDate = DateTime.Now;
                        _TaskScheduler.Status = status;
                        _TaskScheduler.Notes = $"Email {note} for User - {userTasks.UserName}";
                        await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
                    }
                }
                catch (Exception ex)
                {
                    var emailController = new EmailController(_IEmailRepository, _IUtilityMethodsRepository, _IFilePathService);
                    ReceiverEmail _ReceiverEmail = new()
                    {
                        email1 = "support@erpcrystal.in",
                        emailsubject = "Weekly Planned Tasks Scheduler Error",
                        emailmessage = $"An error occurred in Weekly Planned Tasks Email Scheduler for database {dbname}: {ex.Message}"
                    };
                    await emailController.SendEmail(dbname, _ReceiverEmail, "C");

                    _TaskScheduler.ActivityDate = DateTime.Now;
                    _TaskScheduler.Status = "F";
                    _TaskScheduler.Notes = $"Exception during Email processing for User - {userTasks.UserName}";
                    await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
                }
            }
        }
        catch (Exception ex)
        {
            var emailController = new EmailController(_IEmailRepository, _IUtilityMethodsRepository, _IFilePathService);
            ReceiverEmail _ReceiverEmail = new()
            {
                email1 = "support@erpcrystal.in",
                emailsubject = "Weekly Planned Tasks Scheduler Error",
                emailmessage = $"An error occurred in Weekly Planned Tasks Scheduler for database {dbname}: {ex.Message}"
            };
            await emailController.SendEmail(dbname, _ReceiverEmail, "C");

            _TaskScheduler.ActivityDate = DateTime.Now;
            _TaskScheduler.Status = "F";
            _TaskScheduler.Notes = "Exception during processing";
            await _ITaskSchedulerRepository.InsertSchedulerLog(_TaskScheduler, dbname);
        }
    }

    public async Task RunAll()
    {
        var allConfigs = await _ITaskSchedulerRepository.GetAllTaskSchedulerDetails(scheduledTaskCode: "015");
        var configList = allConfigs
            .Select(x => (x.DbName, x.ChannelType))
            .Distinct()
            .ToList();

        foreach (var config in configList)
        {
            await Run(config.DbName, config.ChannelType);
        }
    }
}

[MODIFY] ScheduledJobsController.cs

Inject TaskFlowWeeklyPlannedSchedulerJob and add endpoint:

private readonly TaskFlowWeeklyPlannedSchedulerJob _taskFlowWeeklyPlannedSchedulerJob;

// constructor injection:
TaskFlowWeeklyPlannedSchedulerJob taskFlowWeeklyPlannedSchedulerJob,
...
_taskFlowWeeklyPlannedSchedulerJob = taskFlowWeeklyPlannedSchedulerJob;

// Endpoint:
[HttpPost("task-flow-weekly-planned-scheduler")]
public async Task<IActionResult> TaskFlowWeeklyPlannedScheduler()
{
    await _taskFlowWeeklyPlannedSchedulerJob.RunAll();
    return Ok(new { message = "Task Flow Weekly Planned Scheduler Job Started for All Databases" });
}

[MODIFY] Program.cs

Register job:

builder.Services.AddScoped<TaskFlowWeeklyPlannedSchedulerJob>();

[MODIFY] TaskFlowPlanController.cs

Update TaskFlowPlanValidateCreateModify:

if (_TaskFlowPlan.Id == 0)
{
    if (_TaskFlowPlan.StartDate?.Date < DateTime.Now.Date.AddDays(-30))
    {
        ModelState.AddModelError("StartDate", "Start date cannot be earlier than 30 days in the past.");
    }

    if (_TaskFlowPlan.TargetDate?.Date < DateTime.Now.Date.AddDays(-30))
    {
        ModelState.AddModelError("TargetDate", "Target date cannot be earlier than 30 days in the past.");
    }

    if (_TaskFlowPlan.TargetDate?.Date < _TaskFlowPlan.StartDate?.Date)
    {
        ModelState.AddModelError("TargetDate", "Target date must be after start date.");
    }
}

Component 3: ErpCrystal_MFG.Web

[MODIFY] TaskFlowPlanCreate.razor

Add backdated dates warning message:

<MudGrid>
    <MudItem xs="6">
        <DateComponent label="Start Date" SetDateValue="@AssignStartDateValue" DisableFutureDates="N"/>
        <ValidationMessage For="@(() => _TaskFlowPlan.StartDate)" class="custom-validation-message"/>
    </MudItem>
    <MudItem xs="6">
        <DateComponent label="Target Date" SetDateValue="@AssignTargetDateValue" DisableFutureDates="N"/>
        <ValidationMessage For="@(() => _TaskFlowPlan.TargetDate)" class="custom-validation-message"/>
    </MudItem>
</MudGrid>

@if ((_TaskFlowPlan.StartDate.HasValue && _TaskFlowPlan.StartDate.Value.Date < DateTime.Now.Date) || 
    (_TaskFlowPlan.TargetDate.HasValue && _TaskFlowPlan.TargetDate.Value.Date < DateTime.Now.Date))
{
    <MudAlert Severity="Severity.Warning" Class="mt-2 mb-2">Please note, you have entered a backdated date.</MudAlert>
}

Verification Plan

Automated Build Verification

dotnet build ErpCrystal_MFG.sln

Manual Verification

  1. Verify compilation of ErpCrystal_MFG.sln.
  2. Test /api/jobs/task-flow-weekly-planned-scheduler endpoint.
  3. Test Task Flow task creation with backdated dates (within 30 days past and older than 30 days past).