Saturday, 21 November 2020

Upload excel file as multipart

 Upload excel file as multipart


Client:

===========================================================


using System;

using System.Collections.Generic;

using System.Linq;

using System.Net.Http;

using System.Text;

using System.Threading.Tasks;


namespace QTC.OMS_Intake.UtilityLayer.HttpClientBase

{

    public  class UploadFileClientLocator: IHttpClientLocator

    {

        private static volatile object SyncRoot = new object();

        private static volatile IHttpClient _internalClient;


        private void InitializeClient(HttpClientHandler handler)

        {

            var client = new BasicHttpClient(handler)

            {

                Timeout = TimeSpan.FromSeconds(60)

            };


            lock (SyncRoot)

            {

                _internalClient = client;


            }

        }

        public IHttpClient Locate(HttpClientHandler handler)

        {

            if (_internalClient == null)

            {

                InitializeClient(handler);

            }


            return _internalClient;

        }

    }

}




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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

namespace QTC.OMS_Intake.UtilityLayer.HttpClientBase
{
    public  class BasicHttpClient : IHttpClient
    {
        private readonly HttpClient _internalClient;
        private readonly HashSet<Uri> _endpointCalled;
        public BasicHttpClient()
        {
            _internalClient = new HttpClient();
            _endpointCalled = new HashSet<Uri>();
        }

        public BasicHttpClient(HttpClientHandler handler)
        {
            _internalClient = new HttpClient(handler);
            _endpointCalled = new HashSet<Uri>();
        }

        public HttpRequestHeaders RequestHeaders { get => _internalClient.DefaultRequestHeaders; }
        public TimeSpan Timeout { set => _internalClient.Timeout = value; }
        public TimeSpan TimeOut { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
        public Uri BaseAddress { get => _internalClient.BaseAddress; set => _internalClient.BaseAddress = value; }
        public HttpRequestHeader RequestHeader { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        public void Dispose()
        {
            lock (_endpointCalled)
            {
                _endpointCalled.Clear();
            }
            _internalClient.Dispose();
        }

        public async Task<HttpResponseMessage> GetAsync(Uri requestUri)
        {
            var httprequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
            var stopwatch = Stopwatch.StartNew();
            var getAsync = await _internalClient.SendAsync(httprequestMessage)
                .ConfigureAwait(false);
            stopwatch.Stop();
            var time = stopwatch.ElapsedMilliseconds;
            return getAsync;
        }

        public async Task<HttpResponseMessage> PostAsync(Uri requestUri, HttpContent content)
        {
            var payload = content.ReadAsStringAsync().Result;
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, requestUri)
            {
                Content = content
            };
            var stopwatch = Stopwatch.StartNew();
            try
            {
                var postAsync =await _internalClient.SendAsync(httpRequestMessage)
                    .ConfigureAwait(false);
                stopwatch.Stop();
                return postAsync;
            }
            catch (Exception)
            {
                stopwatch.Stop();
                throw;
            }
        }

        public async Task<HttpResponseMessage> PuttAsync(Uri requestUri, HttpContent content)
        {
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Put, requestUri)
            {
                Content = content
            };
            var stopwatch = Stopwatch.StartNew();
            try
            {
                var putAsync = await _internalClient.SendAsync(httpRequestMessage)
                    .ConfigureAwait(false);
                stopwatch.Stop();
                return putAsync;
            }
            catch (Exception)
            {
                stopwatch.Stop();
                throw;
            }
        }
    }
}

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

using System;

using System.Collections.Generic;

using System.Configuration;

using System.IO;

using System.Linq;

using System.Net.Http;

using System.Net.Http.Headers;

using System.Text;

using System.Threading.Tasks;

using System.Web;

using Newtonsoft.Json;

using QTC.OMS_Intake.BusinessLayer.ManagerClasses;

using QTC.OMS_Intake.UtilityLayer;

using QTC.OMS_Intake.UtilityLayer.HttpClientBase;


namespace QTC.OMS_Intake.ViewModelLayer

{

    public class RequestMetaDataViewModel : AppViewModelBase

    {

        private readonly IHttpClientLocator _httpClientLocator;

        private readonly UploadRequestManager _uploadRequestManager;


        public RequestMetaDataViewModel()

        {

             _httpClientLocator = new UploadFileClientLocator();

            _uploadRequestManager = new UploadRequestManager();

        }


        public HttpPostedFileBase MetaData { get; set; }

        public bool IsExist { get; set; }

        public bool IsSuccess { get; set; }

        public bool UploadFileRequest(UploadFileRequestModel request)

        {

            var serialzedRequest = JsonConvert.SerializeObject(request);

            var content = new MultipartFormDataContent();


            content.Add(new StreamContent(new MemoryStream(request.FileData)), request.FileName, request.FileName,

                new

                {

                    user = request.UserName,

                    appid = request.AppId

                }

            );

            var baseApiUrl = ConfigurationManager.AppSettings["UploadFileAPI"];

            var uploadUrl = $"{baseApiUrl}/api/Upload/InclinicUpload";

            var handler = new HttpClientHandler();

            handler.Credentials = System.Net.CredentialCache.DefaultCredentials;


            var client = _httpClientLocator.Locate(handler);

            

            var result = client.PostAsync(new Uri(uploadUrl), content).Result;

            var response = result.Content.ReadAsStringAsync().Result;

            var responseModel = JsonConvert.DeserializeObject<UploadFileResponseModel>(response);

            //result.EnsureSuccessStatusCode();

            

            //for future

            //var response = result.Content.ReadAsStringAsync().Result;

            //var responseModel = JsonConvert.DeserializeObject<UploadFileResponseModel>(response);

            return IsSuccess;

        }


        public bool CheckFileExist(string fileName)

        {

            var uploadHistory = _uploadRequestManager.GetUploadHistory();

            var isExist = uploadHistory.FirstOrDefault(uh => uh.FileName == fileName);

            if (isExist == null)

                IsExist = false;

            else

                IsExist = true;

            return IsExist;

        }

    }

}



Server

==========================================================

using Newtonsoft.Json;

using Qtc.RhrpApi.BusinessLayer.DTO.OMSUpload;

using Qtc.RhrpApi.BusinessLayer.Extensions;

using Qtc.RhrpApi.BusinessLayer.ManagerClasses;

using QTC.Web.WebApi;

using System;

using System.Collections.Generic;

using System.Data.SqlClient;

using System.Linq;

using System.Net;

using System.Net.Http;

using System.Text;

using System.Web;

using System.Web.Http;

using static Qtc.RhrpApi.BusinessLayer.ManagerClasses.LogExceptionManager;


namespace Qtc.RhrpApi.WebApi.Controllers.OMSUpload

{

    public class UploadController : QTCWebApiControllerBase

    {


        [HttpPost]

        public HttpResponseMessage InclinicUpload()

        {

            UploadResponse uploadResp = new UploadResponse();

            HttpResponseMessage response = new HttpResponseMessage();



            if (!Request.Content.IsMimeMultipartContent())

                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);


            var provider = new MultipartMemoryStreamProvider();

            Request.Content.ReadAsMultipartAsync(provider);


            // Loop through all uploaded files

            foreach (var file in provider.Contents)

            {

                var fn = file.Headers.ContentDisposition.FileName;

                var user = file.Headers.ContentDisposition.Parameters.Where(u => u.Name == "user").Select(u => u.Value).FirstOrDefault();

                var app = file.Headers.ContentDisposition.Parameters.Where(u => u.Name == "appid").Select(u => u.Value).FirstOrDefault();

                FileResponse fileResponse = new FileResponse();


                try

                {

                    if (fn == null || user == null || app == null)

                    {

                        uploadResp.FileResponses.Add(new FileResponse { FileError = "filename, user and appid are required in the request header!" });

                        break;

                    }


                    var filename = fn.Trim('\"');

                    string userName = user.Trim('\"');

                    string AppId = app.Trim('\"');

                    fileResponse.FileName = filename;


                    var buffer = file.ReadAsByteArrayAsync().Result;


                    // Parse uploaded XLSM file

                    XlsmFileProcessor xlsmFileProcessor = new XlsmFileProcessor(buffer, userName, AppId);

                    bool bSuccess = xlsmFileProcessor.ProcessInclinicData(filename, out fileResponse);

                    if (!bSuccess)

                    {

                        uploadResp.FileResponses.Add(fileResponse);

                    }

                }

                catch (SqlException sqlException)

                {

                    fileResponse.FileError = sqlException.Message + System.Environment.NewLine + sqlException.StackTrace;

                    LogExceptionManager.Instance.Publish(Guid.Empty, Guid.Empty, "UploadController.InclinicUpload() HttpPost", SEVERITY.Fatal, "", sqlException.GetAllMessages(), "", user);

                    uploadResp.FileResponses.Add(fileResponse);

                }

                catch (Exception ex)

                {

                    fileResponse.FileError = ex.Message + System.Environment.NewLine + ex.StackTrace;

                    // re-throw cause the error page to display

                    LogExceptionManager.Instance.Publish(Guid.Empty, Guid.Empty, "UploadController.InclinicUpload() HttpPost", SEVERITY.Fatal, "", ex.GetAllMessages(), "", user);

                    uploadResp.FileResponses.Add(fileResponse);

                }

            }

            if (uploadResp.FileResponses.Count > 0)

            {

                HttpError err = new HttpError(JsonConvert.SerializeObject(uploadResp));

                response = Request.CreateResponse(HttpStatusCode.BadRequest, err);

            }

            else

            {

                response = Request.CreateResponse(HttpStatusCode.OK);

            }

            return response;

        }

    }

}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Qtc.RhrpApi.BusinessLayer.DTO.OMSUpload
{
    public class UploadResponse
    {
        public List<FileResponse> FileResponses { get; set; }

        public UploadResponse()
        {
            FileResponses = new List<FileResponse>();
        }        
    }

    public class FileResponse
    {

        public string FileName { get; set; }
        public List<Dictionary<string, string>> LineError;
        public string FileError { get; set; }

        public FileResponse()
        {
            LineError = new List<Dictionary<string, string>>();
        }
    }
}


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

using Newtonsoft.Json;
using NPOI.SS.UserModel;
using Qtc.RhrpApi.BusinessLayer.DTO.OMSUpload;
using Qtc.RhrpApi.BusinessLayer.EntityClasses;
using Qtc.RhrpApi.BusinessLayer.ManagerClasses.OMSUpload;
using Qtc.RhrpApi.BusinessLayer.Validation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;

namespace Qtc.RhrpApi.WebApi.Controllers.OMSUpload
{
    public class XlsmFileProcessor
    {
        const string nsXsd = "http://schemas.datacontract.org/2004/07/Qtc.RhrpApi.BusinessLayer.EntityClasses";
        public object WorkbookFactory { get; private set; }
        public byte[] FileContent { get; set; }
        public Guid ApplicationId { get; set; }
        public string User { get; set; }

        public XlsmFileProcessor(byte[] fileContent, string userName, string appID) 
        {
            FileContent = fileContent;
            User = userName;
            ApplicationId = string.IsNullOrEmpty(appID) ? Guid.NewGuid() : Guid.Parse(appID);
        }

        /// <summary>
        /// Parse service member(s) on "In-Clinic Request" spreadsheet
        /// Save sm data to the database if successful
        /// Return error if failed
        /// </summary>
        /// <param name="errMessage">List of errors</param>
        /// <returns>true: success; false: failed</returns>
        public bool ProcessInclinicData(string fileName, out FileResponse fileResponse) 
        {
            IWorkbook workbook;
            FileResponse rtnResp = new FileResponse();
            rtnResp.FileName = fileName; 
            bool result = true;

            try
            {               
                using (Stream sr = new MemoryStream(FileContent))
                {
                    workbook = NPOI.SS.UserModel.WorkbookFactory.Create(sr);
                }

                var importer = new Npoi.Mapper.Mapper(workbook);

                var excelInClinicList = importer.Take<InClinicData>("In-Clinic Request").ToList();

                InClinicList inClinicList = new InClinicList();
                inClinicList.ServiceMembers = excelInClinicList.Select(x => x.Value).ToList();

                if (inClinicList.ServiceMembers.Count == 0)
                {
                    rtnResp.FileError = "No service member found!";
                    fileResponse = rtnResp;
                    UploadManager.Instance.InsertUploadHistory(fileName, "Fail", JsonConvert.SerializeObject(rtnResp), User);
                    return false;
                }

                List<Dictionary<string, string>> lstErr = new List<Dictionary<string, string>>();
                int iLineNo = 0;
                // Validate every SM data
                foreach (var sm in inClinicList.ServiceMembers)
                {
                    iLineNo++;
                    Dictionary<string, string> dictError = new Dictionary<string, string>();
                    bool valid = ValidateData<InClinicData>(sm, out dictError);
                    if (!valid)
                    {
                        dictError.Add("LineNo", iLineNo.ToString());
                        rtnResp.LineError.Add(dictError);
                        result = false;
                    }
                }               

                if (rtnResp.LineError.Count == 0)
                {
                    UploadManager.Instance.CreateRequest(inClinicList.ServiceMembers, ApplicationId, User, fileName);
                    result = true;
                }
                else
                {
                    UploadManager.Instance.InsertUploadHistory(fileName, "Fail", JsonConvert.SerializeObject(rtnResp), User);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
                rtnResp.FileError = ex.Message + System.Environment.NewLine + ex.StackTrace;
                throw ex;
            }
            fileResponse = rtnResp;
            return result; 
        }

        /// <summary>
        /// Validate all properties in dataObject
        /// </summary>
        /// <typeparam name="T">Type for dataObject</typeparam>
        /// <param name="dataObject">class object to validate</param>
        /// <param name="dictError">Error list</param>
        /// <returns>true: valid; false: invalid</returns>
        private bool ValidateData<T>(object dataObject, out Dictionary<string, string> dictError)
        {
            Dictionary<string, string> errList = new Dictionary<string, string>();
         
            ValidationAttributeValidator<T> validator = new ValidationAttributeValidator<T>();
            PropertyInfo[] properties = typeof(T).GetProperties();

            foreach (PropertyInfo property in properties)
            {
                System.Diagnostics.Debug.WriteLine("Name: " + property.Name + ", Value: " + property.GetValue(dataObject, null));

                string errMsg = string.Empty;
                if (!validator.ValidateValidationAttribute(property.Name, property.GetValue(dataObject, null), out errMsg))
                {
                    errList.Add(property.Name, errMsg);
                }
            }
            dictError = errList;
            return (dictError.Count == 0);
        }

    }
}


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

using Qtc.RhrpApi.BusinessLayer.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Qtc.RhrpApi.BusinessLayer.EntityClasses
{
    public class InClinicList
    {
        [XmlElementAttribute("ServiceMembers")]
        public List<InClinicData> ServiceMembers { get; set; }
    }

    public class InClinicData : ServiceMember
    {
        [SingleChildServiceAttribute(ErrorMessage = "Single child rule violated")]
        public string PhaseIds { get; set; }
    }

}

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

using Qtc.RhrpApi.BusinessLayer.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace Qtc.RhrpApi.BusinessLayer.EntityClasses
{
    [Table("UploadInClinicData")]
    public abstract class ServiceMember
    {
        public ServiceMember()
        {
            TempId = Guid.NewGuid();
        }
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public Guid TempId { get; set; }
        public Guid UploadId { get; set; }

        [Required(ErrorMessage = "You must Request Type")]
        public string RequestType { get; set; }

        //[DataType(DataType.Date)]
        [DateRange("1/1/1753", "12/31/9999", ErrorMessage = "Invalid Request Date")]
        [Required(ErrorMessage = "You must provide Request Date")]
        public DateTime RequestDate { get; set; }

        [Required(ErrorMessage = "You must provide SSN")]
        [RegularExpression("^[0-9]{9}$", ErrorMessage = "Invalid SSN")]
        public string SSN { get; set; }

        [Required(ErrorMessage = "You must provide DOD ID")]
        [RegularExpression("^[0-9]*$", ErrorMessage = "Invalid DOD ID")]
        public string DODId { get; set; }

        [RegularExpression("^([a-zA-Z0-9]+)$", ErrorMessage = "Invalid UIC")]
        public string UIC { get; set; }

        [DataType(DataType.Date)]
        [DateRange("1/1/1753", "12/31/9999", ErrorMessage = "Invalid DOB")]
        [AgeMinimunLimit(17, ErrorMessage = "Age limit must be between 17-70 years")]
        [AgeMaximumLimit(70, ErrorMessage = "Age limit must be between 17-70 years")]
        public Nullable<DateTime> DOB { get; set; }
        public string Gender { get; set; }
        public string ServiceComponent { get; set; }

        [Required(ErrorMessage = "You must provide a First Name")]
        [RegularExpression("^([a-zA-Z- ~.'’ñ]+)$", ErrorMessage = "Invalid First Name")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "You must provide a Last Name")]
        [RegularExpression("^([a-zA-Z- ~.'’ñ]+)$", ErrorMessage = "Invalid Last Name")]
        public string LastName { get; set; }
        public string MiddleInitial { get; set; }

        //[RegularExpression("^[^@]+@[a-zA-Z0-9._-]+\\.+[a-z._-]+$", ErrorMessage = "Invalid Email Address")]
        [RegularExpression("^[_a-zA-Z0-9-]+(\\.[_a-zA-Z0-9-]+)*(\\+[a-zA-Z0-9-]+)?@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*$", ErrorMessage = "Invalid Email Address")]
        public string Email { get; set; }

        [RegularExpression(@"^(\([2-9]\d{2}\)|[2-9]\d{2})(?:\-?|\ ?|\.?)([0-9]\d{2})(?:\-?|\ ?|\.?)(\d{4})$", ErrorMessage = "Invalid Cell Phone.")]
        public string CellPhone { get; set; }

        [RegularExpression(@"^(\([2-9]\d{2}\)|[2-9]\d{2})(?:\-?|\ ?|\.?)([0-9]\d{2})(?:\-?|\ ?|\.?)(\d{4})$", ErrorMessage = "Invalid Daytime Phone.")]
        public string DayPhone { get; set; }

        [RegularExpression("^([a-zA-Z0-9- #./']+)$", ErrorMessage = "Invalid Address 1")]
        public string HomeAddressLine1 { get; set; }

        [RegularExpression("^([a-zA-Z0-9- #./']+)$", ErrorMessage = "Invalid Address 2")]
        public string HomeAddressLine2 { get; set; }

        [RegularExpression("^[a-zA-Z]+(?:[\\s-][a-zA-Z]+)*$", ErrorMessage = "Invalid city")]
        public string City { get; set; }

        public string State { get; set; }

        [RegularExpression("^[0-9]{5}(?:-[0-9]{4})?$", ErrorMessage = "Invalid zip code")]
        public string ZipCode { get; set; }

        public string CreatedUser { get; set; }
        public DateTime CreatedDate { get; set; }
    }
}

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

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Qtc.RhrpApi.BusinessLayer.Validation
{
    //T = class type containing the properties to be validated
    public class ValidationAttributeValidator<T>
    {
        public ValidationAttributeValidator() { }

        // Validate one property against all validation attributes
        public bool ValidateValidationAttribute(string property, object value, out string error)
        {
            var propertyInfo = typeof(T).GetProperty(property);
            var validationAttributes = propertyInfo.GetCustomAttributes(true);

            if (validationAttributes == null)
            {
                error = "No customer attribute found!";
                return true;
            }
            
            // Loop through all validation attributes
            foreach (object attribute in validationAttributes)
            {
                System.Diagnostics.Debug.WriteLine(" - Validation Attribute: " + (attribute.GetType()).Name);
                if ((attribute.GetType()).Name == "KeyAttribute" || (attribute.GetType()).Name == "DatabaseGeneratedAttribute"
                    || (attribute.GetType()).Name == "DisplayFormatAttribute"
                    || (attribute.GetType()).Name == "DisplayAttribute")
                    continue;

                if (!((ValidationAttribute)attribute).IsValid(value))
                {
                    error = ((ValidationAttribute)attribute).ErrorMessage + (value == null ? "" : "(" + value.ToString() + ")");
                    return false;
                }
            }
            error = string.Empty;
            return true; 
        }

    }


}

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


Thursday, 19 November 2020

Web API Async Programing

 controller

=================================


 [ApiMethod]

        [Route("BirthdayLoanAnniversaryAlerts")]

        public async Task<ScheduleBatchResponse> BirthdayLoanAnniversaryAlerts()

        {

            try

            {

                await birthdayLoanAnniversaryAlertsOrchestrator.ProcessBatch();


                return new ScheduleBatchResponse

                {

                    Status = true,

                    Message = "Success"

                };

            }

            catch (Exception ex)

            {

                logger.LogError($"[ERROR] BirthdayLoanAnniversaryAlerts - Message : {ex.Message}", ex);


                return new ScheduleBatchResponse

                {

                    Status = false,

                    Message = "Failed",

                    Error = ex

                };

            }

        }



Service

==================================================


 public async Task ProcessBatch()

        {

            try

            {

                var batchId = await marketingWatchesRepository.InsertBatchDetails(

                    DateTimeOffset.Now,

                    BatchStatus.Started,

                    BatchType.BirthdayLoanAnniversaryAlertsV2).ConfigureAwait(false);


                logger.LogInformation($"BirthdayLoanAnniversaryV2 Process - BatchId : {batchId} Started Birthday Loan Anniversary Alert process.", batchId);


                var userIds = await marketingProfileService.GetAllAcceptedTermsOfUseUsers().ConfigureAwait(false);

                logger.LogInformation($"BirthdayLoanAnniversaryV2 Process - marketingProfileService.GetAllAcceptedTermsOfUseUsers. UserIds Count : {userIds.Count}", batchId);


                var hasAcceptedTerms = userIds.Contains(16798);

                performanceLogger.Start(Processes.GetBirthdayLoanAnniversaryLeads, $"Request - [ BatchId : {batchId} ]");


                var leads = await clientConnectService.GetBirthdayLoanAnniversaryLeadsAsync().ConfigureAwait(false);


                var testLeads = leads.BirthdayLeads.Where(l => l.BrokerContactId.Equals("66486") && l.BrokerAccountId.Equals("38821")).ToList();

                var testLeadsCount = testLeads.Count;



                await performanceLogger.End(Processes.GetBirthdayLoanAnniversaryLeads, $"Response - [ BirthdayLeads : {leads?.BirthdayLeads?.Count}. LoanAnniversaryLeads : {leads?.LoanAnniversaryLeads?.Count} ]").ConfigureAwait(false);


                performanceLogger.Start(Processes.GetBirthdayLoanAnniversaryAlerts, $"Request - [ BatchId : {batchId} ]");


                var (birthdayAlerts, loanAnniversaryAlerts) = await GetBirthdayLoanAnniversaryAlertsAsDictionaryAsync().ConfigureAwait(false);

                logger.LogInformation($"BirthdayLoanAnniversaryV2 Process - GetBirthdayLoanAnniversaryAlertsAsDictionaryAsync. Birthday Alerts Count : {birthdayAlerts?.Count}, Loan Anniversary Alerts Count : {loanAnniversaryAlerts?.Count}");


                await performanceLogger.End(Processes.GetBirthdayLoanAnniversaryAlerts, $"Response - [ BirthdayAlerts : {birthdayAlerts?.Count}. LoanAnniversaryAlerts : {loanAnniversaryAlerts?.Count} ]").ConfigureAwait(false);


                (birthdayAlerts, loanAnniversaryAlerts) = await VerifyDataAsync(leads, birthdayAlerts, loanAnniversaryAlerts).ConfigureAwait(false);


                logger.LogInformation(

                    $"BirthdayLoanAnniversaryV2 Process - Retrieved data for Birthday : {leads.BirthdayLeads?.Count} (Existing: {birthdayAlerts?.Count}) - Loan Anniversary : {leads.LoanAnniversaryLeads?.Count} (Existing: {loanAnniversaryAlerts?.Count})",

                    batchId);


                await ProcessBirthdayAlerts(leads.BirthdayLeads, userIds, birthdayAlerts).ConfigureAwait(false);


                await ProcessLoanAnniversary(leads.LoanAnniversaryLeads, userIds, loanAnniversaryAlerts).ConfigureAwait(false);


                await clientConnectService.UpdateNewStatusesAsync().ConfigureAwait(false);


                marketingWatchesRepository.UpdateBatchDetails(

                    batchId,

                    DateTimeOffset.Now,

                    BatchStatus.Completed);


                logger.LogInformation($"BirthdayLoanAnniversaryV2 - BatchId : {batchId} Completed Birthday Loan Anniversary Alert process.", batchId);

            }

            catch (Exception ex)

            {

                logger.LogError($"[ERROR] BirthdayLoanAnniversaryV2 - ProcessBatch : {ex.Message}", ex);


                throw ex;

            }

        }


 private async Task<(IDictionary<string, Alert>, IDictionary<string, Alert>)> VerifyDataAsync(BirthdayLoanAnniversaryLead leads, IDictionary<string, Alert> birthdayAlerts, IDictionary<string, Alert> loanAnniversaryAlerts)

        {

            logger.LogInformation($"BirthdayLoanAnniversaryV2 - VerifyDataAsync. " +

                                  $"Birthday Leads Count : {leads.BirthdayLeads?.Count()}. " +

                                  $"Loan Anniversary Leads Count : {leads.BirthdayLeads?.Count()}. " +

                                  $"Birthday Alerts Count : {birthdayAlerts?.Count()}. " +

                                  $"Loan Anniversary Alerts Count : {loanAnniversaryAlerts?.Count()}.");


            if (birthdayAlerts?.Count > 0 || loanAnniversaryAlerts?.Count > 0)

            {

                return (birthdayAlerts, loanAnniversaryAlerts);

            }


            try

            {

                leads.BirthdayLeads = leads.BirthdayLeads?.Where(x => IsToday(x.BorrowerBirthDate))

                    .ToList();


                leads.LoanAnniversaryLeads = leads.LoanAnniversaryLeads?.Where(x => IsToday(x.LoanClosingDate))

                    .ToList();


                var (todayBirthdayAlerts, todayLoanAnniversaryAlerts) = await GetBirthdayLoanAnniversaryAlertsAsDictionaryAsync(false).ConfigureAwait(false);


                return (todayBirthdayAlerts, todayLoanAnniversaryAlerts);

            }

            catch (Exception ex)

            {

                logger.LogError($"BirthdayLoanAnniversaryV2 - VerifyDataAsync failed. Message : {ex.Message}", ex);


                throw ex;

            }

        }




 private async Task ProcessBirthdayAlerts(

            IReadOnlyCollection<BirthdayLead> birthdayLeads,

            IList<int> usersAcceptedTermsAndConditions,

            IDictionary<string, Alert> alerts)

        {

            logger.LogInformation("[START] BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts");


            if (!alerts?.Any() ?? true)

            {

                logger.LogInformation("BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts - There were no alerts to process.");


                return;

            }


            var alertsBatch = 0;

            var alertsSent = 0;

            var distinctContactEntityIds = new List<Tuple<int, int>>();


            foreach (var birthdayLead in birthdayLeads)

            {

                if (int.TryParse(birthdayLead.BrokerContactId, out var contactId) && int.TryParse(birthdayLead.BrokerAccountId, out var entityId))

                {

                    distinctContactEntityIds.Add(Tuple.Create(contactId, entityId));

                }

            }


            logger.LogInformation($"BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts - Birthday Leads Count : {birthdayLeads.Count}");


            var userPreferences = await marketingProfileService.GetUserPreferences(distinctContactEntityIds).ConfigureAwait(false);


            var userProfiles = await GetUserProfilesAsync(distinctContactEntityIds).ConfigureAwait(false);


            var contactEmailTemplates = await marketingProfileService.GetEmailTemplates(distinctContactEntityIds).ConfigureAwait(false);


            var batchedBirthdayItems = birthdayLeads.Split();


            performanceLogger.Start(Processes.SaveAlerts, $"Birthday Alerts Count : {birthdayLeads.Count}");

            //more time taken process this should be optimise using threading


            foreach (var batchAlertBatch in batchedBirthdayItems)

            {

                try

                {

                    var borrowerCrmIds = GetBorrowerCrmIds(batchAlertBatch);


                    var triggerTypePreferences = await preferenceService.GetTriggerTypePreferencesForWatches(borrowerCrmIds, new List<string>()).ConfigureAwait(false);


                    logger.LogInformation($"BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts - Batch {alertsBatch}");

                    var birthdayAlerts = await GetBirthdayAlert(

                        batchAlertBatch,

                        triggerTypePreferences?.Borrowers,

                        userPreferences,

                        contactEmailTemplates,

                        usersAcceptedTermsAndConditions,

                        alerts,

                        userProfiles).ConfigureAwait(false);


                    var validBirthdayAlerts = birthdayAlerts.Where(n => n.AlertType != null && n.BorrowerId != null);


                    var birthdayAlertsWithActiveUserProfile = await FilterOutBirthdayLoanAnniversaryAlertsWithInactiveProfile(validBirthdayAlerts).ConfigureAwait(false);


                    logger.LogInformation($"BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts - Batch {alertsBatch} - Call clientConnectService.SaveAlertsAsync. Count : {birthdayAlertsWithActiveUserProfile.ToList().Count}");

                    await clientConnectService.SaveAlertsAsync(birthdayAlertsWithActiveUserProfile).ConfigureAwait(false);


                    alertsSent += birthdayAlertsWithActiveUserProfile.ToList().Count;

                    alertsBatch += 1;

                    logger.LogInformation($"BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts - Completed Batch {alertsBatch} - Alerts Sent : {alertsSent}");

                }

                catch (Exception ex)

                {

                    logger.LogError($"[ERROR] BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts: {ex.Message}", ex);

                }

            }


            await performanceLogger.End(Processes.SaveAlerts, "End Birthday Alerts").ConfigureAwait(false);


            logger.LogInformation("[DONE] BirthdayLoanAnniversaryV2 - ProcessBirthdayAlerts");

        }