From b5de18f8c4b0ddec3936a59f90912683322418fe Mon Sep 17 00:00:00 2001 From: Greg Finzer Date: Tue, 19 Sep 2023 15:59:46 -0400 Subject: [PATCH 1/3] Full bulk email in progress --- .../Pages/Administration/Admin/Email.razor | 118 +++++++----------- .../Pages/Administration/Admin/Email.razor.cs | 56 +++++++++ BedBrigade.Common/EmailRecipientOption.cs | 35 ++++++ BedBrigade.Data/Data/Seeding/Seed.cs | 1 + BedBrigade.Data/Models/BedRequest.cs | 2 +- BedBrigade.Data/Models/BulkEmailModel.cs | 25 ++++ BedBrigade.Data/Models/ContactUs.cs | 2 +- BedBrigade.Data/Models/IEmail.cs | 7 ++ BedBrigade.Data/Models/User.cs | 2 +- BedBrigade.Data/Models/Volunteer.cs | 2 +- BedBrigade.Data/Models/VolunteerEvent.cs | 3 +- .../Services/BedRequestDataService.cs | 10 ++ BedBrigade.Data/Services/CommonService.cs | 35 ++++++ .../Services/ContactUsDataService.cs | 10 ++ .../Services/EmailQueueDataService.cs | 58 ++++++++- .../Services/IBedRequestDataService.cs | 2 + BedBrigade.Data/Services/ICommonService.cs | 6 + .../Services/IContactUsDataService.cs | 2 + .../Services/IEmailQueueDataService.cs | 4 +- .../Services/IScheduleDataService.cs | 1 + BedBrigade.Data/Services/IUserDataService.cs | 2 + .../Services/IVolunteerDataService.cs | 4 + BedBrigade.Data/Services/Repository.cs | 2 +- .../Services/ScheduleDataService.cs | 41 +++++- BedBrigade.Data/Services/UserDataService.cs | 10 ++ .../Services/VolunteerDataService.cs | 59 ++++++++- 26 files changed, 418 insertions(+), 81 deletions(-) create mode 100644 BedBrigade.Client/Pages/Administration/Admin/Email.razor.cs create mode 100644 BedBrigade.Common/EmailRecipientOption.cs create mode 100644 BedBrigade.Data/Models/BulkEmailModel.cs create mode 100644 BedBrigade.Data/Models/IEmail.cs diff --git a/BedBrigade.Client/Pages/Administration/Admin/Email.razor b/BedBrigade.Client/Pages/Administration/Admin/Email.razor index 718e29cf..da678b15 100644 --- a/BedBrigade.Client/Pages/Administration/Admin/Email.razor +++ b/BedBrigade.Client/Pages/Administration/Admin/Email.razor @@ -2,78 +2,56 @@ @page "/administration/admin/email" @attribute [Authorize(Roles = RoleNames.CanSendBulkEmail)] -

Send Email

- - -
- - -
- -
- - -
- -
- - -
- -
- -

 

- -@if (isSuccess) +@if (Model == null || Model.Locations == null || Model.Locations.Count == 0 || Model.CurrentLocationId == 0) { - + + + Loading .... } -else if (isFailure) +else { - }
- + +
+ +
- + +
+ +
+ + @if (showPlan) + { + + } + + +

 

+ + @if (isSuccess) + { + + } + else if (isFailure) + { + + } } \ No newline at end of file diff --git a/BedBrigade.Client/Pages/Administration/Admin/Email.razor.cs b/BedBrigade.Client/Pages/Administration/Admin/Email.razor.cs index 91009231..787cb16a 100644 --- a/BedBrigade.Client/Pages/Administration/Admin/Email.razor.cs +++ b/BedBrigade.Client/Pages/Administration/Admin/Email.razor.cs @@ -11,23 +11,51 @@ public partial class Email : ComponentBase [Inject] private IUserDataService _svcUserDataService { get; set; } [Inject] private ILocationDataService _svcLocationDataService { get; set; } [Inject] private IScheduleDataService _svcScheduleDataService { get; set; } + [Inject] private IEmailQueueDataService _svcEmailQueueDataService { get; set; } + public BulkEmailModel Model { get; set; } = new(); - + private bool isSuccess; + private bool isFailure; + private bool showPlan; + private string message; + private bool isNationalAdmin; protected override async Task OnInitializedAsync() { Model.Locations = (await _svcLocationDataService.GetAllAsync()).Data; var user = (await _svcUserDataService.GetCurrentLoggedInUser()).Data; + Model.Body = (await _svcUserDataService.GetEmailSignature(user.UserName)).Data; Model.Schedules = (await _svcScheduleDataService.GetFutureSchedulesByLocationId(user.LocationId)).Data; Model.CurrentLocationId = user.LocationId; - Model.IsNationalAdmin = Model.CurrentLocationId == Constants.NationalLocationId; - Model.EmailRecipientOptions = EnumHelper.GetEnumNameValues(); - Model.CurrentEmailRecipientOption = EmailRecipientOption.AllBedBrigadeLeadersForMyLocation; - Model.ShowEventDropdown = true; + isNationalAdmin = user.LocationId == Constants.NationalLocationId; + + if (isNationalAdmin) + { + Model.EmailRecipientOptions = EnumHelper.GetEnumNameValues(); + } + else + { + Model.EmailRecipientOptions = EnumHelper.GetEnumNameValues().Where(x => x.Value != EmailRecipientOption.Everyone).ToList(); + } + + Model.CurrentEmailRecipientOption = EmailRecipientOption.BedBrigadeLeadersForLocation; + Model.ShowLocationDropdown = isNationalAdmin; + Model.ShowEventDropdown = false; + await BuildPlan(); } private async Task HandleValidSubmit() { + var emails = await _svcEmailQueueDataService.GetEmailsToSend(Model.CurrentLocationId, Model.CurrentEmailRecipientOption, Model.CurrentScheduleId); + var result = await EmailQueueLogic.QueueBulkEmail(emails.Data, Model.Subject, Model.Body); + if (result.Success) + { + ShowSuccess("Email successfully queued."); + } + else + { + ShowFailure("Email failed to queue. " + result.Message); + } } private async void LocationChangeEvent(ChangeEventArgs args) @@ -35,22 +63,47 @@ private async void LocationChangeEvent(ChangeEventArgs args) Model.CurrentLocationId = args.Value; Model.Schedules = (await _svcScheduleDataService.GetFutureSchedulesByLocationId(Model.CurrentLocationId)).Data; Model.CurrentScheduleId = 0; + await BuildPlan(); StateHasChanged(); } + private async Task BuildPlan() + { + message = (await _svcEmailQueueDataService.GetSendPlanMessage(Model.CurrentLocationId, Model.CurrentEmailRecipientOption, Model.CurrentScheduleId)).Data; + showPlan = true; + } + private async void ScheduleChangeEvent(ChangeEventArgs args) { Model.CurrentScheduleId = args.Value; + await BuildPlan(); StateHasChanged(); } - private async void EmailRecipientChangeEvent(ChangeEventArgs args) + private async void EmailRecipientChangeEvent(ChangeEventArgs> args) { Model.CurrentEmailRecipientOption = args.Value; - Model.ShowEventDropdown = Model.CurrentEmailRecipientOption == EmailRecipientOption.VolunteersForAnEvent - || Model.CurrentEmailRecipientOption == - EmailRecipientOption.BedRequestorsForAnEvent; + Model.ShowEventDropdown = Model.CurrentEmailRecipientOption.ToString().Contains("Event"); + Model.ShowLocationDropdown = isNationalAdmin && (Model.CurrentEmailRecipientOption.ToString().Contains("Location") + || Model.CurrentEmailRecipientOption.ToString().Contains("Event")); + await BuildPlan(); StateHasChanged(); } + + public void ShowSuccess(string successMessage) + { + isSuccess = true; + isFailure = false; + showPlan = false; + message = successMessage; + } + + public void ShowFailure(string failureMessage) + { + isFailure = true; + isSuccess = false; + showPlan = false; + message = failureMessage; + } } } diff --git a/BedBrigade.Common/DateUtil.cs b/BedBrigade.Common/DateUtil.cs new file mode 100644 index 00000000..644d7675 --- /dev/null +++ b/BedBrigade.Common/DateUtil.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BedBrigade.Common +{ + public static class DateUtil + { + public static String MillisecondsToTimeLapse(long milliseconds) + { + TimeSpan ts = TimeSpan.FromMilliseconds(milliseconds); + + string result; + + if ((long)ts.TotalDays == 1) + result = string.Format("{0:n0} day, {1:n0} hours, {2:n0} minutes, {3:n0} seconds, {4:n0} milliseconds", ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); + else if (ts.TotalDays >= 1) + result = string.Format("{0:n0} days, {1:n0} hours, {2:n0} minutes, {3:n0} seconds, {4:n0} milliseconds", ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); + else if ((long)ts.TotalHours == 1) + result = string.Format("{0:n0} hr, {1:n0} minutes, {2:n0} seconds, {3:n0} milliseconds", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); + else if (ts.TotalHours >= 1) + result = string.Format("{0:n0} hours, {1:n0} minutes, {2:n0} seconds, {3:n0} milliseconds", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); + else if ((long)ts.TotalMinutes == 1) + result = string.Format("{0:n0} min, {1:n0} seconds, {2:n0} milliseconds", ts.Minutes, ts.Seconds, ts.Milliseconds); + else if (ts.TotalMinutes >= 1) + result = string.Format("{0:n0} minutes, {1:n0} seconds, {2:n0} milliseconds", ts.Minutes, ts.Seconds, ts.Milliseconds); + else if ((long)ts.TotalSeconds == 1) + result = string.Format("{0:n0} second, {1:n0} milliseconds", ts.Seconds, ts.Milliseconds); + else if (ts.TotalSeconds >= 1) + result = string.Format("{0:n0} seconds, {1:n0} milliseconds", ts.Seconds, ts.Milliseconds); + else + result = string.Format("{0:n0} milliseconds", ts.Milliseconds); + + result = result.Replace(" 1 hours", " 1 hour"); + result = result.Replace(" 1 minutes", " 1 minute"); + result = result.Replace(" 1 seconds", " 1 second"); + + return result; + } + } +} diff --git a/BedBrigade.Common/EmailRecipientOption.cs b/BedBrigade.Common/EmailRecipientOption.cs index 0e8b9031..cd2fae26 100644 --- a/BedBrigade.Common/EmailRecipientOption.cs +++ b/BedBrigade.Common/EmailRecipientOption.cs @@ -9,27 +9,27 @@ namespace BedBrigade.Common { public enum EmailRecipientOption { + [Description("Bed Brigade Leaders Nationwide")] + BedBrigadeLeadersNationwide, + [Description("Bed Brigade Leaders for Location")] + BedBrigadeLeadersForLocation, + [Description("Bed Requestors for Location")] + BedRequestorsForLocation, + [Description("Bed Requestors Who Have NOT Received A Bed for Location")] + BedRequestorsWhoHaveNotRecievedABed, + [Description("Bed Requestors Who Have Received A Bed for Location")] + BedRequestorsWhoHaveRecievedABed, + [Description("Bed Requestors For An Event")] + BedRequestorsForAnEvent, Everyone, - [Description("All Volunteers")] - AllVolunteers, - [Description("All Bed Requestors")] - AllBedRequestors, - [Description("All Contacts Us Requests")] - AllContactUs, - [Description("All Bed Brigade Leaders Nationwide")] - AllBedBrigadeLeadersNationwide, - [Description("All Bed Brigade Leaders For My Location")] - AllBedBrigadeLeadersForMyLocation, - [Description("Volunteers With Delivery Vehicles")] + [Description("Contact Us Requests for Location")] + ContactUsForLocation, + [Description("Volunteers for Location")] + VolunteersForLocation, + [Description("Volunteers With Delivery Vehicles for Location")] VolunteersWithDeliveryVehicles, [Description("Volunteers For An Event")] VolunteersForAnEvent, - [Description("Bed Requestors Who Have NOT Received A Bed")] - BedRequestorsWhoHaveNotRecievedABed, - [Description("Bed Requestors Who Have Received A Bed")] - BedRequestorsWhoHaveRecievedABed, - [Description("Bed Requestors For An Event")] - BedRequestorsForAnEvent } } diff --git a/BedBrigade.Common/TypeHelper.cs b/BedBrigade.Common/TypeHelper.cs deleted file mode 100644 index 2a499ee3..00000000 --- a/BedBrigade.Common/TypeHelper.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Reflection; - -namespace BedBrigade.Common -{ - public static class TypeHelper - { - /// - /// Return true if the type is an enum - /// - /// - /// - public static bool IsEnum(Type type) - { - if (type == null) - return false; - - if (type.GetTypeInfo().IsGenericType && type.GetTypeInfo().GetGenericTypeDefinition() == typeof(Nullable<>)) - { - type = Nullable.GetUnderlyingType(type); - } - - return type.GetTypeInfo().IsEnum; - } - - /// - /// Return true if the type is a Double - /// - /// - /// - public static bool IsDouble(Type type) - { - if (type == null) - return false; - - return type == typeof(Double); - } - - /// - /// Return true if the type is a Decimal - /// - /// - /// - public static bool IsDecimal(Type type) - { - if (type == null) - return false; - - return type == typeof(Decimal); - } - - /// - /// Return true if the type is a DateTime - /// - /// - /// - public static bool IsDateTime(Type type) - { - if (type == null) - return false; - - return type == typeof(DateTime); - } - - /// - /// Return true if the type is a string - /// - /// - /// - public static bool IsString(Type type) - { - if (type == null) - return false; - - return type == typeof(string); - } - - /// - /// Return true if the type is a guid - /// - /// - /// - public static bool IsGuid(Type type) - { - if (type == null) - return false; - - return type == typeof(Guid); - } - - } -} diff --git a/BedBrigade.Data/Data/DataContext.cs b/BedBrigade.Data/Data/DataContext.cs index dbd0948a..39896fbb 100644 --- a/BedBrigade.Data/Data/DataContext.cs +++ b/BedBrigade.Data/Data/DataContext.cs @@ -34,11 +34,6 @@ public DataContext(DbContextOptions options) : base(options) protected override void OnModelCreating(ModelBuilder modelBuilder) { CreateIndexes(modelBuilder); - SetSeedForLocation(modelBuilder); - } - - private void SetSeedForLocation(ModelBuilder modelBuilder) - { } /// @@ -60,6 +55,9 @@ private static void CreateIndexes(ModelBuilder modelBuilder) modelBuilder.Entity() .HasIndex(o => o.VolunteeringForId); + + modelBuilder.Entity() + .HasIndex(o => o.ScheduleId); } } } diff --git a/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.Designer.cs b/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.Designer.cs new file mode 100644 index 00000000..fd7369c8 --- /dev/null +++ b/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.Designer.cs @@ -0,0 +1,1079 @@ +// +using System; +using BedBrigade.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace BedBrigade.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230920132435_AddBedRequestScheduleId")] + partial class AddBedRequestScheduleId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("BedBrigade.Data.Models.BedRequest", b => + { + b.Property("BedRequestId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("BedRequestId")); + + b.Property("AgesGender") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DeliveryDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("NumberOfBeds") + .HasColumnType("int"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(14) + .HasColumnType("nvarchar(14)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("nvarchar(5)"); + + b.Property("ScheduleId") + .HasColumnType("int"); + + b.Property("SpecialInstructions") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Street") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("nvarchar(40)"); + + b.Property("TeamNumber") + .HasColumnType("int"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("BedRequestId"); + + b.HasIndex("LocationId"); + + b.HasIndex("ScheduleId"); + + b.ToTable("BedRequests"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Configuration", b => + { + b.Property("ConfigurationKey") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ConfigurationValue") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Section") + .HasColumnType("int"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("ConfigurationKey"); + + b.ToTable("Configurations"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.ContactUs", b => + { + b.Property("ContactUsId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ContactUsId")); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(14) + .HasColumnType("nvarchar(14)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("ContactUsId"); + + b.HasIndex("LocationId"); + + b.HasIndex("Status"); + + b.ToTable("ContactUs"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Content", b => + { + b.Property("ContentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ContentId")); + + b.Property("ContentHtml") + .HasColumnType("nvarchar(max)"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("ContentId"); + + b.HasIndex("ContentType"); + + b.HasIndex("LocationId"); + + b.ToTable("Content"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Donation", b => + { + b.Property("DonationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("DonationId")); + + b.Property("Amount") + .HasColumnType("decimal(18,4)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("LastName") + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TaxFormSent") + .HasColumnType("bit"); + + b.Property("TransactionId") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("DonationId"); + + b.HasIndex("LocationId"); + + b.ToTable("Donations"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.EmailQueue", b => + { + b.Property("EmailQueueId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("EmailQueueId")); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FailureMessage") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FromAddress") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FromDisplayName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("HtmlBody") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("LockDate") + .HasColumnType("datetime2"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("QueueDate") + .HasColumnType("datetime2"); + + b.Property("SentDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ToAddress") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("EmailQueueId"); + + b.ToTable("EmailQueue"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Location", b => + { + b.Property("LocationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("LocationId")); + + b.Property("Address1") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Address2") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("City") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Latitude") + .HasColumnType("decimal(18,10)"); + + b.Property("Longitude") + .HasColumnType("decimal(18,10)"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Route") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("State") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("LocationId"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Media", b => + { + b.Property("MediaId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("MediaId")); + + b.Property("AltText") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("FileName") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FilePath") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("FileSize") + .HasColumnType("int"); + + b.Property("FileStatus") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("FileUse") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MediaType") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("MediaId"); + + b.HasIndex("LocationId"); + + b.ToTable("Media"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Role", b => + { + b.Property("RoleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("RoleId")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.HasKey("RoleId"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Schedule", b => + { + b.Property("ScheduleId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ScheduleId")); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("EventDateScheduled") + .HasColumnType("datetime2"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EventNote") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("EventStatus") + .HasColumnType("int"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("GroupName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("VehiclesDeliveryMax") + .HasColumnType("int"); + + b.Property("VehiclesDeliveryRegistered") + .HasColumnType("int"); + + b.Property("VehiclesNormalMax") + .HasColumnType("int"); + + b.Property("VolunteersMax") + .HasColumnType("int"); + + b.Property("VolunteersRegistered") + .HasColumnType("int"); + + b.HasKey("ScheduleId"); + + b.HasIndex("EventType"); + + b.HasIndex("LocationId"); + + b.ToTable("Schedules"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Template", b => + { + b.Property("TemplateId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("TemplateId")); + + b.Property("ContentHtml") + .HasColumnType("nvarchar(max)"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("TemplateId"); + + b.ToTable("Templates"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.User", b => + { + b.Property("UserName") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("FkRole") + .HasColumnType("int"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PasswordHash") + .HasMaxLength(255) + .HasColumnType("varbinary(255)"); + + b.Property("PasswordSalt") + .HasMaxLength(255) + .HasColumnType("varbinary(255)"); + + b.Property("PersistBedRequest") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistConfig") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistDonation") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistLocation") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistMedia") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistPages") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistUser") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("PersistVolunteers") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(14) + .HasColumnType("nvarchar(14)"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("UserName"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Volunteer", b => + { + b.Property("VolunteerId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("VolunteerId")); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("IHaveAMinivan") + .HasColumnType("bit"); + + b.Property("IHaveAPickupTruck") + .HasColumnType("bit"); + + b.Property("IHaveAnSUV") + .HasColumnType("bit"); + + b.Property("IHaveVolunteeredBefore") + .HasColumnType("bit"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(25) + .HasColumnType("nvarchar(25)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Message") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("OrganizationOrGroup") + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(14) + .HasColumnType("nvarchar(14)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("VehicleType") + .HasColumnType("int"); + + b.Property("VolunteeringForDate") + .HasColumnType("datetime2"); + + b.Property("VolunteeringForId") + .HasColumnType("int"); + + b.HasKey("VolunteerId"); + + b.HasIndex("LocationId"); + + b.HasIndex("VolunteeringForId"); + + b.ToTable("Volunteers"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.VolunteerEvent", b => + { + b.Property("RegistrationId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("RegistrationId")); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ScheduleId") + .HasColumnType("int"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("VolunteerEventNote") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("VolunteerId") + .HasColumnType("int"); + + b.HasKey("RegistrationId"); + + b.HasIndex("VolunteerId"); + + b.ToTable("VolunteerEvents"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.VolunteerFor", b => + { + b.Property("VolunteerForId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("VolunteerForId")); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("CreateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("MachineName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UpdateDate") + .HasColumnType("datetime2"); + + b.Property("UpdateUser") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("VolunteerForId"); + + b.ToTable("VolunteersFor"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.BedRequest", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("BedRequests") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.ContactUs", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("ContactUs") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Content", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Contents") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Donation", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Donations") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Media", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Media") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Schedule", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Schedules") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.User", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Users") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Volunteer", b => + { + b.HasOne("BedBrigade.Data.Models.Location", null) + .WithMany("Volunteers") + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.VolunteerEvent", b => + { + b.HasOne("BedBrigade.Data.Models.Volunteer", "Volunteer") + .WithMany() + .HasForeignKey("VolunteerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volunteer"); + }); + + modelBuilder.Entity("BedBrigade.Data.Models.Location", b => + { + b.Navigation("BedRequests"); + + b.Navigation("ContactUs"); + + b.Navigation("Contents"); + + b.Navigation("Donations"); + + b.Navigation("Media"); + + b.Navigation("Schedules"); + + b.Navigation("Users"); + + b.Navigation("Volunteers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.cs b/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.cs new file mode 100644 index 00000000..4e59b962 --- /dev/null +++ b/BedBrigade.Data/Migrations/20230920132435_AddBedRequestScheduleId.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BedBrigade.Data.Migrations +{ + /// + public partial class AddBedRequestScheduleId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ScheduleId", + table: "BedRequests", + type: "int", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_BedRequests_ScheduleId", + table: "BedRequests", + column: "ScheduleId"); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BedRequests_ScheduleId", + table: "BedRequests"); + + migrationBuilder.DropColumn( + name: "ScheduleId", + table: "BedRequests"); + } + } +} diff --git a/BedBrigade.Data/Migrations/DataContextModelSnapshot.cs b/BedBrigade.Data/Migrations/DataContextModelSnapshot.cs index 64c7481f..cd5281eb 100644 --- a/BedBrigade.Data/Migrations/DataContextModelSnapshot.cs +++ b/BedBrigade.Data/Migrations/DataContextModelSnapshot.cs @@ -89,6 +89,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(5) .HasColumnType("nvarchar(5)"); + b.Property("ScheduleId") + .HasColumnType("int"); + b.Property("SpecialInstructions") .HasMaxLength(4000) .HasColumnType("nvarchar(4000)"); @@ -119,6 +122,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("LocationId"); + b.HasIndex("ScheduleId"); + b.ToTable("BedRequests"); }); @@ -923,6 +928,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("RegistrationId"); + b.HasIndex("VolunteerId"); + b.ToTable("VolunteerEvents"); }); @@ -1034,6 +1041,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("BedBrigade.Data.Models.VolunteerEvent", b => + { + b.HasOne("BedBrigade.Data.Models.Volunteer", "Volunteer") + .WithMany() + .HasForeignKey("VolunteerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volunteer"); + }); + modelBuilder.Entity("BedBrigade.Data.Models.Location", b => { b.Navigation("BedRequests"); diff --git a/BedBrigade.Data/Models/BedRequest.cs b/BedBrigade.Data/Models/BedRequest.cs index beff1fb4..dffa736d 100644 --- a/BedBrigade.Data/Models/BedRequest.cs +++ b/BedBrigade.Data/Models/BedRequest.cs @@ -12,9 +12,12 @@ public class BedRequest : BaseEntity, ILocationId, IEmail public Int32 BedRequestId { get; set; } [ForeignKey("LocationId"), Required] - public Int32 LocationId { get; set; } + public Int32 LocationId { get; set; } - [Required(ErrorMessage = "First Name is required.")] + [ForeignKey("ScheduleId")] + public Int32? ScheduleId { get; set; } + + [Required(ErrorMessage = "First Name is required.")] [MaxLength(20)] public String? FirstName { get; set; } = string.Empty; diff --git a/BedBrigade.Data/Models/BulkEmailModel.cs b/BedBrigade.Data/Models/BulkEmailModel.cs index b7922628..94731bf2 100644 --- a/BedBrigade.Data/Models/BulkEmailModel.cs +++ b/BedBrigade.Data/Models/BulkEmailModel.cs @@ -8,18 +8,21 @@ public class BulkEmailModel [Required] public List? Locations { get; set; } public int CurrentLocationId { get; set; } - public bool IsNationalAdmin { get; set; } public List> EmailRecipientOptions { get; set; } public EmailRecipientOption CurrentEmailRecipientOption { get; set; } public List? Schedules { get; set; } public int CurrentScheduleId { get; set; } - [Required] + [Required(ErrorMessage = "Subject is required.")] + [MaxLength(100)] public string? Subject { get; set; } - [Required] + [Required(ErrorMessage = "Body is required.")] + [MaxLength(4000)] public string? Body { get; set; } public bool ShowEventDropdown { get; set; } + + public bool ShowLocationDropdown { get; set; } } } diff --git a/BedBrigade.Data/Services/BedRequestDataService.cs b/BedBrigade.Data/Services/BedRequestDataService.cs index eb36b999..d65f923b 100644 --- a/BedBrigade.Data/Services/BedRequestDataService.cs +++ b/BedBrigade.Data/Services/BedRequestDataService.cs @@ -6,12 +6,16 @@ namespace BedBrigade.Data.Services; public class BedRequestDataService : Repository, IBedRequestDataService { + private readonly IDbContextFactory _contextFactory; + private readonly ICachingService _cachingService; private readonly ICommonService _commonService; public BedRequestDataService(IDbContextFactory contextFactory, ICachingService cachingService, AuthenticationStateProvider authProvider, ICommonService commonService) : base(contextFactory, cachingService, authProvider) { + _contextFactory = contextFactory; + _cachingService = cachingService; _commonService = commonService; } @@ -29,6 +33,57 @@ public async Task>> GetDistinctEmailByLocation(int { return await _commonService.GetDistinctEmailByLocation(this, locationId); } + + public async Task>> EmailsForNotReceivedABed(int locationId) + { + string cacheKey = _cachingService.BuildCacheKey(GetEntityName(), $"EmailsForNotReceivedABed"); + var cachedContent = _cachingService.Get>(cacheKey); + + if (cachedContent != null) + return new ServiceResponse>($"Found {cachedContent.Count} {GetEntityName()} records in cache", true, cachedContent); ; + + using (var ctx = _contextFactory.CreateDbContext()) + { + var dbSet = ctx.Set(); + var result = await dbSet.Where(o => o.LocationId == locationId && o.Status != Common.Common.BedRequestStatus.Delivered).Select(b => b.Email).Distinct().ToListAsync(); + _cachingService.Set(cacheKey, result); + return new ServiceResponse>($"Found {result.Count()} {GetEntityName()} records", true, result); + } + } + + public async Task>> EmailsForReceivedABed(int locationId) + { + string cacheKey = _cachingService.BuildCacheKey(GetEntityName(), $"RecievedABed"); + var cachedContent = _cachingService.Get>(cacheKey); + + if (cachedContent != null) + return new ServiceResponse>($"Found {cachedContent.Count} {GetEntityName()} records in cache", true, cachedContent); ; + + using (var ctx = _contextFactory.CreateDbContext()) + { + var dbSet = ctx.Set(); + var result = await dbSet.Where(o => o.LocationId == locationId && o.Status == Common.Common.BedRequestStatus.Delivered).Select(b => b.Email).Distinct().ToListAsync(); + _cachingService.Set(cacheKey, result); + return new ServiceResponse>($"Found {result.Count()} {GetEntityName()} records", true, result); + } + } + + public async Task>> EmailsForSchedule(int scheduleId) + { + string cacheKey = _cachingService.BuildCacheKey(GetEntityName(), $"EmailsForSchedule({scheduleId})"); + var cachedContent = _cachingService.Get>(cacheKey); + + if (cachedContent != null) + return new ServiceResponse>($"Found {cachedContent.Count} {GetEntityName()} records in cache", true, cachedContent); ; + + using (var ctx = _contextFactory.CreateDbContext()) + { + var dbSet = ctx.Set(); + var result = await dbSet.Where(o => o.ScheduleId == scheduleId).Select(b => b.Email).Distinct().ToListAsync(); + _cachingService.Set(cacheKey, result); + return new ServiceResponse>($"Found {result.Count()} {GetEntityName()} records", true, result); + } + } } diff --git a/BedBrigade.Data/Services/EmailQueueDataService.cs b/BedBrigade.Data/Services/EmailQueueDataService.cs index 705b5685..cd3fa9bf 100644 --- a/BedBrigade.Data/Services/EmailQueueDataService.cs +++ b/BedBrigade.Data/Services/EmailQueueDataService.cs @@ -15,6 +15,9 @@ public class EmailQueueDataService : Repository, IEmailQueueDataServ private readonly IBedRequestDataService _bedRequestDataService; private readonly IContactUsDataService _contactUsDataService; private readonly IUserDataService _userDataService; + private readonly IConfigurationDataService _configurationDataService; + private readonly ILocationDataService _locationDataService; + private readonly IScheduleDataService _scheduleDataService; public EmailQueueDataService(IDbContextFactory contextFactory, ICachingService cachingService, @@ -22,7 +25,10 @@ public EmailQueueDataService(IDbContextFactory contextFactory, IVolunteerDataService volunteerDataService, IBedRequestDataService bedRequestDataService, IContactUsDataService contactUsDataService, - IUserDataService userDataService) : base(contextFactory, cachingService, authProvider) + IUserDataService userDataService, + IConfigurationDataService configurationDataService, + ILocationDataService locationDataService, + IScheduleDataService scheduleDataService) : base(contextFactory, cachingService, authProvider) { _contextFactory = contextFactory; _cachingService = cachingService; @@ -30,6 +36,9 @@ public EmailQueueDataService(IDbContextFactory contextFactory, _bedRequestDataService = bedRequestDataService; _contactUsDataService = contactUsDataService; _userDataService = userDataService; + _configurationDataService = configurationDataService; + _locationDataService = locationDataService; + _scheduleDataService = scheduleDataService; } public async Task> GetLockedEmails() @@ -193,13 +202,27 @@ public async Task> GetSendPlanMessage(int locationId, Em { try { - using (var ctx = _contextFactory.CreateDbContext()) + int queueCount = await GetQueueCount(); + int emailCount = (await GetEmailsToSend(locationId, option, scheduleId)).Data.Count; + string estimatedTime = await GetEstimatedTime(queueCount, emailCount); + string liveEmailMessage = await IsLiveEmail() ? "Email is LIVE." : "Email is NOT live, it is logged."; + string locationName = await GetLocationName(locationId); + string eventName = await GetEventName(scheduleId); + string message; + if (option == EmailRecipientOption.Everyone || option == EmailRecipientOption.BedBrigadeLeadersNationwide) { - var dbSet = ctx.Set(); - bool isNational = locationId == Constants.NationalLocationId; - int queueCount = await dbSet.CountAsync(o => o.Status == EmailQueueStatus.Queued.ToString()); - + message = $"{emailCount} emails will be sent to {EnumHelper.GetEnumDescription(option)}. There are currently {queueCount} other emails in the queue. It will take an estimated {estimatedTime} to send. {liveEmailMessage}"; + } + else if (option.ToString().Contains("Event")) + { + message = $"{emailCount} emails will be sent to {EnumHelper.GetEnumDescription(option)} {locationName} for the event {eventName}. There are currently {queueCount} other emails in the queue. It will take an estimated {estimatedTime} to send. {liveEmailMessage}"; + } + else + { + message = $"{emailCount} emails will be sent to {EnumHelper.GetEnumDescription(option)} {locationName}. There are currently {queueCount} other emails in the queue. It will take an estimated {estimatedTime} to send. {liveEmailMessage}"; } + + return new ServiceResponse("Created plan message", true, message); } catch (DbException ex) { @@ -207,28 +230,113 @@ public async Task> GetSendPlanMessage(int locationId, Em } } - private async int GetEmailCount(int locationId, EmailRecipientOption option, int scheduleId) + private async Task GetEventName(int scheduleId) + { + if (scheduleId == 0) + { + return string.Empty; + } + + return (await _scheduleDataService.GetByIdAsync(scheduleId)).Data.EventSelect; + } + + private async Task GetLocationName(int locationId) + { + return (await _locationDataService.GetByIdAsync(locationId)).Data.Name; + } + + private async Task IsLiveEmail() + { + string configValue = (await _configurationDataService.GetByIdAsync(ConfigNames.EmailUseFileMock)).Data.ConfigurationValue; + return configValue != "true"; + } + + public async Task GetQueueCount() { - int emailCount; - bool isNational = locationId == Constants.NationalLocationId; + string cacheKey = _cachingService.BuildCacheKey(GetEntityName(), $"GetQueueCount()"); + int? cachedContent = _cachingService.Get(cacheKey); + + if (cachedContent.HasValue) + { + return cachedContent.Value; + } + + using (var ctx = _contextFactory.CreateDbContext()) + { + var dbSet = ctx.Set(); + var result = await dbSet.CountAsync(o => o.Status == EmailQueueStatus.Queued.ToString()); + _cachingService.Set(cacheKey, result); + return result; + } + } + public async Task>> GetEmailsToSend(int locationId, EmailRecipientOption option, int scheduleId) + { switch (option) { - case EmailRecipientOption.AllVolunteers: - return (await _volunteerDataService.GetDistinctEmailByLocation(locationId)).Data.Count; - case EmailRecipientOption.AllBedRequestors: - return (await _bedRequestDataService.GetDistinctEmailByLocation(locationId)).Data.Count; - case EmailRecipientOption.AllContactUs: - return (await _contactUsDataService.GetDistinctEmailByLocation(locationId)).Data.Count; - case EmailRecipientOption.AllBedBrigadeLeadersNationwide: - return (await _userDataService.GetDistinctEmail()).Data.Count; - case EmailRecipientOption.AllBedBrigadeLeadersForMyLocation: - return (await _userDataService.GetDistinctEmailByLocation(locationId)).Data.Count; + case EmailRecipientOption.Everyone: + return new ServiceResponse>("Built Email List", true, (await GetEveryone())); + case EmailRecipientOption.VolunteersForLocation: + return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetDistinctEmailByLocation(locationId)).Data); + case EmailRecipientOption.BedRequestorsForLocation: + return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.GetDistinctEmailByLocation(locationId)).Data); + case EmailRecipientOption.ContactUsForLocation: + return new ServiceResponse>("Built Email List", true, (await _contactUsDataService.GetDistinctEmailByLocation(locationId)).Data); + case EmailRecipientOption.BedBrigadeLeadersNationwide: + return new ServiceResponse>("Built Email List", true, (await _userDataService.GetDistinctEmail()).Data); + case EmailRecipientOption.BedBrigadeLeadersForLocation: + return new ServiceResponse>("Built Email List", true, (await _userDataService.GetDistinctEmailByLocation(locationId)).Data); case EmailRecipientOption.VolunteersWithDeliveryVehicles: - return (await _volunteerDataService.GetVolunteerEmailsWithDeliveryVehicles(locationId)).Data.Count; + return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetVolunteerEmailsWithDeliveryVehicles(locationId)).Data); case EmailRecipientOption.VolunteersForAnEvent: - return (await _volunteerDataService.GetVolunteerEmailsForASchedule(scheduleId)).Data.Count; + return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetVolunteerEmailsForASchedule(scheduleId)).Data); + case EmailRecipientOption.BedRequestorsWhoHaveNotRecievedABed: + return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForNotReceivedABed(locationId)).Data); + case EmailRecipientOption.BedRequestorsWhoHaveRecievedABed: + return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForReceivedABed(locationId)).Data); + case EmailRecipientOption.BedRequestorsForAnEvent: + return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForSchedule(scheduleId)).Data); + default: + throw new ArgumentOutOfRangeException(nameof(option), option, $"Unsupported Option: {option}"); + } + } + + public async Task GetEstimatedTime(int queueCount, int emailCount) + { + int totalCount = queueCount + emailCount; + int maxEmailsPerMinute = Convert.ToInt32((await _configurationDataService.GetByIdAsync(ConfigNames.EmailMaxSendPerMinute)).Data.ConfigurationValue); + int maxEmailsPerHour = Convert.ToInt32((await _configurationDataService.GetByIdAsync(ConfigNames.EmailMaxSendPerHour)).Data.ConfigurationValue); + + if (totalCount <= maxEmailsPerMinute) + { + return "1 minute"; + } + + if (totalCount <= maxEmailsPerHour) + { + return "2 minutes"; } + + double hours = Convert.ToDouble(totalCount) / Convert.ToDouble(maxEmailsPerHour); + long milliseconds = Convert.ToInt64(hours * (double) 60 * (double) 60 * (double)1000); + return DateUtil.MillisecondsToTimeLapse(milliseconds); + } + + + + private async Task> GetEveryone() + { + var volunteers = await _volunteerDataService.GetDistinctEmail(); + var bedRequestors = await _bedRequestDataService.GetDistinctEmail(); + var contactUs = await _contactUsDataService.GetDistinctEmail(); + var users = await _userDataService.GetDistinctEmail(); + + var everyone = new List(); + everyone.AddRange(volunteers.Data); + everyone.AddRange(bedRequestors.Data); + everyone.AddRange(contactUs.Data); + everyone.AddRange(users.Data); + return everyone.Distinct().ToList(); } } } diff --git a/BedBrigade.Data/Services/EmailQueueLogic.cs b/BedBrigade.Data/Services/EmailQueueLogic.cs index b1ba17aa..9f2d63ab 100644 --- a/BedBrigade.Data/Services/EmailQueueLogic.cs +++ b/BedBrigade.Data/Services/EmailQueueLogic.cs @@ -47,6 +47,36 @@ public static void Start(IEmailQueueDataService emailQueueDataService, IConfigur _timer = new Timer(ProcessQueue, null, oneMinute, oneMinute); } + public static async Task> QueueBulkEmail(List emaiList, string subject, + string body) + { + if (emaiList.Count == 0) + return new ServiceResponse("No emails to send", false); + + try + { + foreach (string email in emaiList) + { + EmailQueue emailQueue = new EmailQueue + { + ToAddress = email, + Subject = subject, + Body = body, + Status = EmailQueueStatus.Queued.ToString(), + QueueDate = DateTime.UtcNow, + FailureMessage = string.Empty + }; + await _emailQueueDataService.CreateAsync(emailQueue); + } + } + catch (Exception ex) + { + return new ServiceResponse(ex.Message, false); + } + + return new ServiceResponse(EmailQueueStatus.Queued.ToString(), true); + } + public static async Task> QueueEmail(EmailQueue email) { try diff --git a/BedBrigade.Data/Services/IBedRequestDataService.cs b/BedBrigade.Data/Services/IBedRequestDataService.cs index 32082fa6..709b5858 100644 --- a/BedBrigade.Data/Services/IBedRequestDataService.cs +++ b/BedBrigade.Data/Services/IBedRequestDataService.cs @@ -7,5 +7,8 @@ public interface IBedRequestDataService : IRepository Task>> GetAllForLocationAsync(); Task>> GetDistinctEmail(); Task>> GetDistinctEmailByLocation(int locationId); + Task>> EmailsForNotReceivedABed(int locationId); + Task>> EmailsForReceivedABed(int locationId); + Task>> EmailsForSchedule(int scheduleId); } } \ No newline at end of file diff --git a/BedBrigade.Data/Services/IEmailQueueDataService.cs b/BedBrigade.Data/Services/IEmailQueueDataService.cs index e0d8d906..de3fa45d 100644 --- a/BedBrigade.Data/Services/IEmailQueueDataService.cs +++ b/BedBrigade.Data/Services/IEmailQueueDataService.cs @@ -12,5 +12,6 @@ public interface IEmailQueueDataService : IRepository Task DeleteOldEmailQueue(int daysOld); Task LockEmailsToProcess(List emailsToProcess); Task> GetSendPlanMessage(int locationId, EmailRecipientOption option, int scheduleId); + Task>> GetEmailsToSend(int locationId, EmailRecipientOption option, int scheduleId); } } diff --git a/BedBrigade.Data/Services/IUserDataService.cs b/BedBrigade.Data/Services/IUserDataService.cs index e5745267..eff9a9aa 100644 --- a/BedBrigade.Data/Services/IUserDataService.cs +++ b/BedBrigade.Data/Services/IUserDataService.cs @@ -12,5 +12,6 @@ public interface IUserDataService : IRepository Task>> GetAllForLocationAsync(); Task>> GetDistinctEmail(); Task>> GetDistinctEmailByLocation(int locationId); + Task> GetEmailSignature(string userName); } } \ No newline at end of file diff --git a/BedBrigade.Data/Services/UserDataService.cs b/BedBrigade.Data/Services/UserDataService.cs index 4bf52e34..2d5b8435 100644 --- a/BedBrigade.Data/Services/UserDataService.cs +++ b/BedBrigade.Data/Services/UserDataService.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.EntityFrameworkCore; using System.Data.Common; +using System.Text; using static BedBrigade.Common.Common; namespace BedBrigade.Data.Services @@ -12,15 +13,18 @@ public class UserDataService : Repository, IUserDataService private readonly IDbContextFactory _contextFactory; private readonly AuthenticationStateProvider _auth; private readonly ICommonService _commonService; + private readonly ILocationDataService _locationDataService; public UserDataService(IDbContextFactory contextFactory, ICachingService cachingService, AuthenticationStateProvider authProvider, - ICommonService commonService) : base(contextFactory, cachingService, authProvider) + ICommonService commonService, + ILocationDataService locationDataService) : base(contextFactory, cachingService, authProvider) { _contextFactory = contextFactory; _cachingService = cachingService; _auth = authProvider; _commonService = commonService; + _locationDataService = locationDataService; } public async Task> GetCurrentLoggedInUser() @@ -202,5 +206,28 @@ public async Task>> GetDistinctEmailByLocation(int { return await _commonService.GetDistinctEmailByLocation(this, locationId); } + + public async Task> GetEmailSignature(string userName) + { + string cacheKey = _cachingService.BuildCacheKey(GetEntityName(), $"GetEmailSignature({userName})"); + var cachedContent = _cachingService.Get(cacheKey); + + if (cachedContent != null) + return new ServiceResponse($"Found Email Signature for id {userName} in cache", true, cachedContent); + + var user = await GetByIdAsync(userName); + var location = await _locationDataService.GetByIdAsync(user.Data.LocationId); + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine(); + sb.AppendLine("Thank you,"); + sb.AppendLine($"{user.Data.FirstName} {user.Data.LastName}, {user.Data.Role} {location.Data.Name}"); + sb.AppendLine($"{user.Data.Email}"); + sb.AppendLine($"{user.Data.Phone}"); + var result = sb.ToString(); + _cachingService.Set(cacheKey, result); + return new ServiceResponse($"Email Signature for {user.Data.FirstName} {user.Data.LastName}", true, result); + } } } From d846cb8176ed890343ffe70a482d4b1a752f7558 Mon Sep 17 00:00:00 2001 From: Greg Finzer Date: Wed, 20 Sep 2023 11:34:29 -0400 Subject: [PATCH 3/3] Quality fix --- .../Services/EmailQueueDataService.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/BedBrigade.Data/Services/EmailQueueDataService.cs b/BedBrigade.Data/Services/EmailQueueDataService.cs index cd3fa9bf..3116c6d2 100644 --- a/BedBrigade.Data/Services/EmailQueueDataService.cs +++ b/BedBrigade.Data/Services/EmailQueueDataService.cs @@ -272,30 +272,31 @@ public async Task GetQueueCount() public async Task>> GetEmailsToSend(int locationId, EmailRecipientOption option, int scheduleId) { + const string message = "Built Email List"; switch (option) { case EmailRecipientOption.Everyone: - return new ServiceResponse>("Built Email List", true, (await GetEveryone())); + return new ServiceResponse>(message, true, (await GetEveryone())); case EmailRecipientOption.VolunteersForLocation: - return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetDistinctEmailByLocation(locationId)).Data); + return new ServiceResponse>(message, true, (await _volunteerDataService.GetDistinctEmailByLocation(locationId)).Data); case EmailRecipientOption.BedRequestorsForLocation: - return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.GetDistinctEmailByLocation(locationId)).Data); + return new ServiceResponse>(message, true, (await _bedRequestDataService.GetDistinctEmailByLocation(locationId)).Data); case EmailRecipientOption.ContactUsForLocation: - return new ServiceResponse>("Built Email List", true, (await _contactUsDataService.GetDistinctEmailByLocation(locationId)).Data); + return new ServiceResponse>(message, true, (await _contactUsDataService.GetDistinctEmailByLocation(locationId)).Data); case EmailRecipientOption.BedBrigadeLeadersNationwide: - return new ServiceResponse>("Built Email List", true, (await _userDataService.GetDistinctEmail()).Data); + return new ServiceResponse>(message, true, (await _userDataService.GetDistinctEmail()).Data); case EmailRecipientOption.BedBrigadeLeadersForLocation: - return new ServiceResponse>("Built Email List", true, (await _userDataService.GetDistinctEmailByLocation(locationId)).Data); + return new ServiceResponse>(message, true, (await _userDataService.GetDistinctEmailByLocation(locationId)).Data); case EmailRecipientOption.VolunteersWithDeliveryVehicles: - return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetVolunteerEmailsWithDeliveryVehicles(locationId)).Data); + return new ServiceResponse>(message, true, (await _volunteerDataService.GetVolunteerEmailsWithDeliveryVehicles(locationId)).Data); case EmailRecipientOption.VolunteersForAnEvent: - return new ServiceResponse>("Built Email List", true, (await _volunteerDataService.GetVolunteerEmailsForASchedule(scheduleId)).Data); + return new ServiceResponse>(message, true, (await _volunteerDataService.GetVolunteerEmailsForASchedule(scheduleId)).Data); case EmailRecipientOption.BedRequestorsWhoHaveNotRecievedABed: - return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForNotReceivedABed(locationId)).Data); + return new ServiceResponse>(message, true, (await _bedRequestDataService.EmailsForNotReceivedABed(locationId)).Data); case EmailRecipientOption.BedRequestorsWhoHaveRecievedABed: - return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForReceivedABed(locationId)).Data); + return new ServiceResponse>(message, true, (await _bedRequestDataService.EmailsForReceivedABed(locationId)).Data); case EmailRecipientOption.BedRequestorsForAnEvent: - return new ServiceResponse>("Built Email List", true, (await _bedRequestDataService.EmailsForSchedule(scheduleId)).Data); + return new ServiceResponse>(message, true, (await _bedRequestDataService.EmailsForSchedule(scheduleId)).Data); default: throw new ArgumentOutOfRangeException(nameof(option), option, $"Unsupported Option: {option}"); }