From cfe753fb0057f757570a092b928d280930870618 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Wed, 28 Oct 2015 10:25:19 -0400 Subject: [PATCH] Email Reminders --- Web/Controllers/VehicleServiceController.cs | 11 +++ Web/Email/EmailNotification.cs | 7 ++ Web/Email/ServiceReminderEmail.cs | 78 +++++++++++++++++++ Web/Email/ServiceReminderEmailService.cs | 70 +++++++++++++++++ .../201510150220550_ServiceReminder.cs | 22 +++++- Web/Startup.cs | 30 +++++++ Web/Views/ServiceReminder/Create.cshtml | 1 + Web/Views/Shared/_Layout.cshtml | 8 +- Web/Views/VehicleService/Create.cshtml | 1 + Web/Views/VehicleService/Index.cshtml | 11 ++- Web/Web.config | 2 + Web/Web.csproj | 34 +++++++- Web/packages.config | 7 ++ 13 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 Web/Email/ServiceReminderEmail.cs create mode 100644 Web/Email/ServiceReminderEmailService.cs create mode 100644 Web/Startup.cs diff --git a/Web/Controllers/VehicleServiceController.cs b/Web/Controllers/VehicleServiceController.cs index 41eb9fa..d43ffa9 100644 --- a/Web/Controllers/VehicleServiceController.cs +++ b/Web/Controllers/VehicleServiceController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Web.Mvc; using MileageTraker.Web.Attributes; using MileageTraker.Web.DAL; +using MileageTraker.Web.Email; using MileageTraker.Web.Models; using MileageTraker.Web.Utility; using MileageTraker.Web.ViewModels; @@ -213,5 +214,15 @@ namespace MileageTraker.Web.Controllers var names = DataService.GetServiceCenterNamesAutocomplete(term); return Json(names, JsonRequestBehavior.AllowGet); } + + [ActionLog] + [Authorize(Roles = "Developer")] + public ActionResult SendVehicleServiceReminderEmails() + { + var emailService = new ServiceReminderEmailService(); + emailService.SendAllNotificationEmails(); + SetStatusMessage("Vehicle Service Reminders Sent"); + return Redirect(Request.UrlReferrer.ToString()); + } } } \ No newline at end of file diff --git a/Web/Email/EmailNotification.cs b/Web/Email/EmailNotification.cs index 1b3f042..ce76180 100644 --- a/Web/Email/EmailNotification.cs +++ b/Web/Email/EmailNotification.cs @@ -1,6 +1,7 @@ using System.Configuration; using System.Net.Mail; using System.Reflection; +using System.Web.Security; using MileageTraker.Web.Models; using log4net; @@ -56,10 +57,16 @@ namespace MileageTraker.Web.Email SendMessage(new MailMessage(_emaialFromAddress, user.Email, _initializePasswordSubject, body)); } + public void SendServiceReminder(ServiceReminderEmail email) + { + SendMessage(new MailMessage(_emaialFromAddress, email.Recipient.Email, email.Subject, email.Body)); + } + private void SendMessage(MailMessage mailMessage) { try { + Logger.Debug("Email sending to " + mailMessage.To + ", subject: " + mailMessage.Subject); _smtpClient.Send(mailMessage); } catch (SmtpException ex) diff --git a/Web/Email/ServiceReminderEmail.cs b/Web/Email/ServiceReminderEmail.cs new file mode 100644 index 0000000..342436f --- /dev/null +++ b/Web/Email/ServiceReminderEmail.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using MileageTraker.Web.Models; + +namespace MileageTraker.Web.Email +{ + public class ServiceReminderEmail + { + private readonly string _emailSubject; + private readonly string _emailBody; + public User Recipient { get; set; } + public IList ServiceReminders { get; set; } + + public string Subject + { + get + { + if (!ServiceReminders.Any()) + return "No upcoming vehicle services currently scheduled"; + var vehicles = ServiceReminders.Select(sr => sr.Vehicle.VehicleId).Distinct().ToList(); + var vehicleDesc = vehicles.Count > 1 ? vehicles.Count.ToString() + " vehicles" : "Vehicle Id " + vehicles.First(); + var subject = string.Format(_emailSubject, vehicleDesc); + return subject; + } + } + + public string Body + { + get + { + var reminderTexts = GetReminderTexts(); + var body = + string.Format(_emailBody, + Recipient.FullName, + Environment.NewLine + Environment.NewLine + + string.Join(Environment.NewLine, reminderTexts) + ); + + return body; + } + } + + public ServiceReminderEmail(User recipient, IList serviceReminders) + { + _emailSubject = ConfigurationManager.AppSettings["ServiceReminderSubject"]; + _emailBody = ConfigurationManager.AppSettings["ServiceReminderBody"]; + Recipient = recipient; + ServiceReminders = serviceReminders; + } + + private IEnumerable GetReminderTexts() + { + if (!ServiceReminders.Any()) + return new[] {"No upcoming services currently scheduled"}; + + return + from sr in ServiceReminders + let diff = + sr.TargetOdometer - (sr.Vehicle.CurrentOdometer.HasValue ? sr.Vehicle.CurrentOdometer.Value : 0) + orderby diff + let description = + !string.IsNullOrEmpty(sr.Description) + ? String.Format(", description is '{0}'", sr.Description) + : string.Empty + let assignedTo = + !string.IsNullOrEmpty(sr.Vehicle.Assigned) + ? String.Format("assigned to {0}", sr.Vehicle.Assigned) + : "unassigned" + select + string.Format("Vehicle with Id {0}, which is {3}, has a reminder for service {1} miles{2}.", + sr.Vehicle.VehicleId, diff < 0 ? " overdue " + (-diff).ToString() : " in " + diff.ToString(), + description, + assignedTo); + } + } +} \ No newline at end of file diff --git a/Web/Email/ServiceReminderEmailService.cs b/Web/Email/ServiceReminderEmailService.cs new file mode 100644 index 0000000..bdd789b --- /dev/null +++ b/Web/Email/ServiceReminderEmailService.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MileageTraker.Web.DAL; +using MileageTraker.Web.Models; +using log4net; + +namespace MileageTraker.Web.Email +{ + public class ServiceReminderEmailService : IDisposable + { + private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected readonly DataService DataService = new DataService(); + protected readonly EmailNotificationService EmailService = new EmailNotificationService(); + + public void SendAllNotificationEmails() + { + Logger.Info("Starting vehicle service notifications"); + var serviceReminders = DataService.GetUpcomingServiceReminders().ToList(); + + Logger.Debug("Got " + serviceReminders.Count + " service reminders."); + + SendAdminEmails(serviceReminders); + + SendUserEmails(serviceReminders); + } + + private void SendAdminEmails(IList serviceReminders) + { + var admins = + (from u in DataService.GetUsers() + where u.Roles.Any(r => r.RoleName == "Vehicle Admin") + select u).Distinct().ToList(); + + var adminEmails = + from admin in admins + select new ServiceReminderEmail(admin, serviceReminders); + + foreach (var email in adminEmails) + { + EmailService.SendServiceReminder(email); + } + } + + private void SendUserEmails(IList serviceReminders) + { + // send email to user + var userEmails = + from sr in serviceReminders + group sr by sr.Vehicle.Assigned + into g + let driver = DataService.FindUserByFullName(g.Key) + where driver != null + let userReminders = g.ToList() + select new ServiceReminderEmail(driver, userReminders); + + foreach (var email in userEmails) + { + EmailService.SendServiceReminder(email); + } + } + + public void Dispose() + { + DataService.Dispose(); + } + } +} \ No newline at end of file diff --git a/Web/Migrations/201510150220550_ServiceReminder.cs b/Web/Migrations/201510150220550_ServiceReminder.cs index d30152c..f8ca00f 100644 --- a/Web/Migrations/201510150220550_ServiceReminder.cs +++ b/Web/Migrations/201510150220550_ServiceReminder.cs @@ -19,14 +19,32 @@ namespace MileageTraker.Web.Migrations .PrimaryKey(t => t.ServiceReminderId) .ForeignKey("dbo.Vehicle", t => t.Vehicle_VehicleId, cascadeDelete: true) .Index(t => t.Vehicle_VehicleId); - + + Sql(@"INSERT INTO [Role] + ([RoleId] + ,[RoleName] + ,[Description]) + VALUES + ('dd28bb32-afb6-4d54-bce6-04457bcf79d9' + ,'Vehicle Admin' + ,'Vehicle Administrator')"); + Sql(@"INSERT INTO [RoleUser] + ([User_UserId], [Role_RoleId]) + VALUES + ('A1720B63-5970-4313-9AC3-90B844FABD65', + 'dd28bb32-afb6-4d54-bce6-04457bcf79d9')"); } - + public override void Down() { DropForeignKey("dbo.ServiceReminder", "Vehicle_VehicleId", "dbo.Vehicle"); DropIndex("dbo.ServiceReminder", new[] { "Vehicle_VehicleId" }); DropTable("dbo.ServiceReminder"); + + Sql(@"DELETE FROM [Role] + WHERE [RoleId] = 'dd28bb32-afb6-4d54-bce6-04457bcf79d9'"); + Sql(@"DELETE FROM [RoleUser] + WHERE [Role_RoleId] = 'dd28bb32-afb6-4d54-bce6-04457bcf79d9'"); } } } diff --git a/Web/Startup.cs b/Web/Startup.cs new file mode 100644 index 0000000..a3c1242 --- /dev/null +++ b/Web/Startup.cs @@ -0,0 +1,30 @@ +using System; +using Hangfire; +using Microsoft.Owin; +using MileageTraker.Web.Email; +using Owin; + +[assembly: OwinStartup(typeof(MileageTraker.Web.Startup))] + +namespace MileageTraker.Web +{ + public class Startup + { + public void Configuration(IAppBuilder app) + { + GlobalConfiguration.Configuration + .UseSqlServerStorage("MileageTrakerContext"); + + app.UseHangfireDashboard(); + app.UseHangfireServer(); + + SetupRecurringJobs(); + } + + private void SetupRecurringJobs() + { + RecurringJob.AddOrUpdate( + "serviceReminderJob", s => s.SendAllNotificationEmails(), Cron.Weekly(DayOfWeek.Monday, 6)); + } + } +} \ No newline at end of file diff --git a/Web/Views/ServiceReminder/Create.cshtml b/Web/Views/ServiceReminder/Create.cshtml index 97a23b9..c4039d8 100644 --- a/Web/Views/ServiceReminder/Create.cshtml +++ b/Web/Views/ServiceReminder/Create.cshtml @@ -7,6 +7,7 @@ @Html.Partial("_StatusMessage")

@ViewBag.Title

+

Reminder for future vehicle service

@using (Html.BeginForm("Create", "ServiceReminder", FormMethod.Post, new { @class = "form-horizontal well center-content" })) { diff --git a/Web/Views/Shared/_Layout.cshtml b/Web/Views/Shared/_Layout.cshtml index c174bd4..a1b6f6a 100644 --- a/Web/Views/Shared/_Layout.cshtml +++ b/Web/Views/Shared/_Layout.cshtml @@ -42,7 +42,11 @@
  • @Html.ActionLink("Vehicles", "Index", "Vehicle")
  • @Html.ActionLink("Vehicle Service", "Index", "VehicleService")
  • @Html.ActionLink("Fuel Logs", "Index", "FuelLog")
  • -
  • @Html.ActionLink("Cost Report", "VehicleCostIndex", "Vehicle")
  • +
  • @Html.ActionLink("Cost Report", "VehicleCostIndex", "Vehicle")
  • + @if (User.IsInRole("Developer")) + { +
  • @Html.ActionLink("Send Service Reminder Emails", "SendVehicleServiceReminderEmails", "VehicleService")
  • + }
  • @Html.ActionLink("Users", "Index", "User")
  • @@ -50,7 +54,7 @@ } diff --git a/Web/Views/VehicleService/Create.cshtml b/Web/Views/VehicleService/Create.cshtml index a384c8d..61b8488 100644 --- a/Web/Views/VehicleService/Create.cshtml +++ b/Web/Views/VehicleService/Create.cshtml @@ -5,6 +5,7 @@ }

    @ViewBag.Title

    +

    Track completed service for a vehicle

    @using (Html.BeginForm("Create", "VehicleService", FormMethod.Post, new { @class = "form-horizontal well center-content" })) { diff --git a/Web/Views/VehicleService/Index.cshtml b/Web/Views/VehicleService/Index.cshtml index 82e1e91..75dfc0e 100644 --- a/Web/Views/VehicleService/Index.cshtml +++ b/Web/Views/VehicleService/Index.cshtml @@ -4,6 +4,7 @@ ViewBag.Title = "Vehicle Service"; var grid = new WebGrid(Model.ServiceItems, rowsPerPage: 45); var queryParams = new { Model.Year, Model.Month, Model.MonthRange, Model.VehicleId }; + var serviceOverdueBadge = Model.UpcomingServiceReminders.Any(sr => sr.IsServiceOverdue) ? "badge-warning" : ""; } @section Scripts { @@ -31,9 +32,15 @@
    @Html.ActionLink("Add Service", "Create", null, new { @class = "btn" }) @Html.ActionLink("Export", "Export", queryParams, new { @class = "btn" }) - Upcoming Services @Model.UpcomingServiceReminders.Count + Upcoming Services @Model.UpcomingServiceReminders.Count
    diff --git a/Web/Web.config b/Web/Web.config index 88937dd..af99432 100644 --- a/Web/Web.config +++ b/Web/Web.config @@ -19,6 +19,8 @@ + + diff --git a/Web/Web.csproj b/Web/Web.csproj index 7264a02..24e94f1 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -15,7 +15,7 @@ MileageTraker v4.5 false - false + true @@ -71,11 +71,27 @@ ..\packages\ExcelLibrary.1.2011.7.30\lib\ExcelLibrary.dll + + ..\packages\Hangfire.Core.1.5.2\lib\net45\Hangfire.Core.dll + True + + + ..\packages\Hangfire.SqlServer.1.5.2\lib\net45\Hangfire.SqlServer.dll + True + False ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll + + ..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True + True ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -87,6 +103,14 @@ False ..\packages\MvcCheckBoxList.1.4.4.5\lib\net40\MvcCheckBoxList.dll + + ..\packages\Newtonsoft.Json.5.0.1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + @@ -146,6 +170,9 @@ + + + @@ -636,12 +663,11 @@ - False + True True 2977 / - - + http://localhost:2977/ False False diff --git a/Web/packages.config b/Web/packages.config index 614399f..39e372e 100644 --- a/Web/packages.config +++ b/Web/packages.config @@ -5,6 +5,9 @@ + + + @@ -16,7 +19,11 @@ + + + + \ No newline at end of file