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 jobRun() - 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.slnManual Verification
- Verify compilation of
ErpCrystal_MFG.sln. - Test
/api/jobs/task-flow-weekly-planned-schedulerendpoint. - Test Task Flow task creation with backdated dates (within 30 days past and older than 30 days past).