diff --git a/GetIntoTeachingApi/Controllers/PickListItemsController.cs b/GetIntoTeachingApi/Controllers/PickListItemsController.cs index 909044525..27f0a83af 100644 --- a/GetIntoTeachingApi/Controllers/PickListItemsController.cs +++ b/GetIntoTeachingApi/Controllers/PickListItemsController.cs @@ -299,5 +299,41 @@ public async Task GetSubscriptionTypes() { return Ok(await _store.GetPickListItems("dfe_servicesubscription", "dfe_servicesubscriptiontype").ToListAsync()); } + + [HttpGet] + [Route("contact_creation_channel/sources")] + [SwaggerOperation( + Summary = "Retrieves the list of contact creation channel sources.", + OperationId = "GetContactCreationChannelSources", + Tags = new[] { "Pick List Items" })] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetContactCreationChannelSources() + { + return Ok(await _store.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelsource").ToListAsync()); + } + + [HttpGet] + [Route("contact_creation_channel/services")] + [SwaggerOperation( + Summary = "Retrieves the list of contact creation channel services.", + OperationId = "GetContactCreationChannelServices", + Tags = new[] { "Pick List Items" })] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetContactCreationChannelServices() + { + return Ok(await _store.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelservice").ToListAsync()); + } + + [HttpGet] + [Route("contact_creation_channel/activities")] + [SwaggerOperation( + Summary = "Retrieves the list of contact creation channel activities.", + OperationId = "GetContactCreationChannelActivities", + Tags = new[] { "Pick List Items" })] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetContactCreationChannelActivities() + { + return Ok(await _store.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelactivities").ToListAsync()); + } } } diff --git a/GetIntoTeachingApi/Models/Crm/Candidate.cs b/GetIntoTeachingApi/Models/Crm/Candidate.cs index b4ddab09e..1f62976b1 100644 --- a/GetIntoTeachingApi/Models/Crm/Candidate.cs +++ b/GetIntoTeachingApi/Models/Crm/Candidate.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using GetIntoTeachingApi.Attributes; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; using Microsoft.Xrm.Sdk; using Swashbuckle.AspNetCore.Annotations; +using System; +using System.Collections.Generic; +using System.Linq; namespace GetIntoTeachingApi.Models.Crm { @@ -297,15 +297,14 @@ public enum RegistrationStatus public CandidatePrivacyPolicy PrivacyPolicy { get; set; } [EntityRelationship("dfe_contact_dfe_candidateschoolexperience_ContactId", typeof(CandidateSchoolExperience))] public List SchoolExperiences { get; set; } = new List(); + [EntityRelationship("dfe_contact_dfe_contactchannelcreation_ContactId", typeof(ContactChannelCreation))] + public List ContactChannelCreations { get; set; } = new List(); - public Candidate() - : base() - { + public Candidate() : base(){ } public Candidate(Entity entity, ICrmService crm, IServiceProvider serviceProvider) - : base(entity, crm, serviceProvider) - { + : base(entity, crm, serviceProvider){ } public bool HasTeacherTrainingAdviser() @@ -333,6 +332,60 @@ public bool IsPlanningToRetakeGcseMathsAndEnglish() return new[] { PlanningToRetakeGcseMathsId, PlanningToRetakeGcseEnglishId }.All(g => g == (int)GcseStatus.HasOrIsPlanningOnRetaking); } + /// + /// Allows the configuration of a contact channel for a + /// candidate based on the provided contactChannelCreator. + /// + public void ConfigureChannel(ICreateContactChannel contactChannelCreator, Guid? candidateId) + { + if (candidateId == null) + { + if (contactChannelCreator.CreationChannelSourceId.HasValue) + { + ChannelId = null; + AddContactChannelCreation( + channelCreation: !ContactChannelCreations.Any(), + contactChannelCreator: contactChannelCreator); + } + else + { + ChannelId = contactChannelCreator.DefaultContactCreationChannel; + } + } + else // Candidate record already exists + { + // NB: we do not update a candidate's ChannelId for an existing record + // NB: CreationChannel should always be false for existing candidates + if (contactChannelCreator.CreationChannelSourceId.HasValue) + { + AddContactChannelCreation( + channelCreation: false, + contactChannelCreator: contactChannelCreator); + } + } + } + + /// + /// Allows automatic creation of an + /// and assigns to the underlying collection of contact channel creations. + /// + /// + /// Predicates the context of the contact channel creation based on the candidate status. + /// + /// + /// The source of the contact channel creation request, a type + /// which implements the interface. + /// + private void AddContactChannelCreation(bool channelCreation, ICreateContactChannel contactChannelCreator) + { + ContactChannelCreations.Add( + ContactChannelCreation.Create( + creationChannel: channelCreation, + sourceId: contactChannelCreator.CreationChannelSourceId, + serviceId: contactChannelCreator.CreationChannelServiceId, + activityId: contactChannelCreator.CreationChannelActivityId)); + } + public bool MagicLinkTokenExpired() => MagicLinkTokenExpiresAt == null || MagicLinkTokenExpiresAt < DateTime.UtcNow; public bool MagicLinkTokenAlreadyExchanged() => MagicLinkTokenStatusId == (int)MagicLinkTokenStatus.Exchanged; } diff --git a/GetIntoTeachingApi/Models/Crm/ContactChannelCreation.cs b/GetIntoTeachingApi/Models/Crm/ContactChannelCreation.cs new file mode 100644 index 000000000..1a0cfbc23 --- /dev/null +++ b/GetIntoTeachingApi/Models/Crm/ContactChannelCreation.cs @@ -0,0 +1,121 @@ +using GetIntoTeachingApi.Attributes; +using GetIntoTeachingApi.Services; +using Microsoft.Xrm.Sdk; +using System; +using System.Text.Json.Serialization; + +namespace GetIntoTeachingApi.Models.Crm +{ + [SwaggerIgnore] + [Entity("dfe_contactchannelcreation")] + public class ContactChannelCreation : BaseModel, IHasCandidateId + { + public enum CreationChannelSource + { + Apply = 222750000, + CheckinApp = 222750001, + ContactCentre = 222750002, + GITWebsite = 222750003, + Highfliers = 222750004, + HPITT = 222750005, + Internships = 222750006, + Legacy = 222750007, + OnCampus = 222750008, + PaidAdvertising = 222750009, + PaidSearch = 222750010, + PaidSocial = 222750011, + Pipeline = 222750012, + SchoolExperience = 222750013, + Scholarships = 222750014 + } + + public enum CreationChannelService + { + CreatedOnApply = 222750000, + CreatedOnSchoolExperience = 222750001, + CreatedOnScholarships = 222750002, + CreatedOnInternships = 222750003, + CreatedOnHPITT = 222750004, + CreatedOnHighfliers = 222750011, + ExploreTeachingAdviserService = 222750005, + Events = 222750006, + MailingList = 222750007, + PaidSearch = 222750008, + ReturnToTeachingAdviserService = 222750009, + TeacherTrainingAdviserService = 222750010 + } + + public enum CreationChannelActivity + { + BrandAmbassadorActivity = 222750000, + BritishCouncil = 222750001, + BRFS = 222750002, + BCS = 222750003, + CareersEvent = 222750004, + CTP = 222750005, + DebateMate = 222750006, + EngineersTeachPhysics = 222750007, + FreshersFairs = 222750008, + F2F = 222750009, + GradFairs = 222750010, + InstituteOfPhysics = 222750011, + IMECHE = 222750012, + IMA = 222750013, + NTP = 222750014, + OnsiteActivationDays = 222750015, + Over18CareersEvent = 222750016, + QuickfireSignUpOnApply = 222750017, + RefreshersFairs = 222750018, + RussellGroup6 = 222750019, + RCS = 222750020, + StudentUnionMedia = 222750021, + StudentRooms = 222750022, + ServiceLeaver = 222750023, + Webinar = 222750024 + } + + [JsonIgnore] + [EntityField("dfe_contactid", typeof(EntityReference), "contact")] + public Guid CandidateId { get; set; } + + [EntityField("createdby", typeof(EntityReference), "systemuser")] + public Guid? CreatedBy { get; set; } + + [EntityField("dfe_creationchannel")] + public bool? CreationChannel { get; set; } = false; + + [EntityField("dfe_creationchannelsource", typeof(OptionSetValue))] + public int? CreationChannelSourceId { get; set; } + + [EntityField("dfe_creationchannelservice", typeof(OptionSetValue))] + public int? CreationChannelServiceId { get; set; } + + [EntityField("dfe_creationchannelactivities", typeof(OptionSetValue))] + public int? CreationChannelActivityId { get; set; } + + public ContactChannelCreation() : base(){ + } + + public ContactChannelCreation(Entity entity, ICrmService crm, IServiceProvider serviceProvider) + : base(entity, crm, serviceProvider){ + } + + /// + /// Factory method for creating a ContactChannelCreation instance. + /// + /// + /// A configured instance. + /// + public static ContactChannelCreation Create( + bool creationChannel, + int? sourceId, + int? serviceId, + int? activityId) => new() + { + CreationChannel = creationChannel, + CreationChannelSourceId = sourceId, + CreationChannelServiceId = serviceId, + CreationChannelActivityId = activityId, + }; + } +} \ No newline at end of file diff --git a/GetIntoTeachingApi/Models/Crm/ICreateContactChannel.cs b/GetIntoTeachingApi/Models/Crm/ICreateContactChannel.cs new file mode 100644 index 000000000..bc1d12a8c --- /dev/null +++ b/GetIntoTeachingApi/Models/Crm/ICreateContactChannel.cs @@ -0,0 +1,29 @@ +namespace GetIntoTeachingApi.Models.Crm +{ + /// + /// Interface that defines the contract on which objects + /// that wish to invoke contact channel creation behaviour must adhere. + /// + public interface ICreateContactChannel + { + /// + /// Provides the default read-only contact creation channel integer value. + /// + int? DefaultContactCreationChannel { get; } + + /// + /// Provides the ability to assign and retrieve the channel source creation identifier. + /// + int? CreationChannelSourceId { get; set; } + + /// + /// Provides the ability to assign and retrieve the channel service creation identifier. + /// + int? CreationChannelServiceId { get; set; } + + /// + /// Provides the ability to assign and retrieve the channel activity creation identifier. + /// + int? CreationChannelActivityId { get; set; } + } +} diff --git a/GetIntoTeachingApi/Models/Crm/Validators/CandidateValidator.cs b/GetIntoTeachingApi/Models/Crm/Validators/CandidateValidator.cs index 9af495b9c..8f2e3b7fd 100644 --- a/GetIntoTeachingApi/Models/Crm/Validators/CandidateValidator.cs +++ b/GetIntoTeachingApi/Models/Crm/Validators/CandidateValidator.cs @@ -60,6 +60,7 @@ public CandidateValidator(IStore store, IDateTimeProvider dateTime) RuleFor(candidate => candidate.ChannelId) .NotNull() .When(candidate => candidate.Id == null) + .When(candidate => !candidate.ContactChannelCreations.Any()) .SetValidator(new PickListItemIdValidator("contact", "dfe_channelcreation", store)); RuleFor(candidate => candidate.HasGcseEnglishId) .SetValidator(new PickListItemIdValidator("contact", "dfe_websitehasgcseenglish", store)) diff --git a/GetIntoTeachingApi/Models/Crm/Validators/ContactChannelCreationValidator.cs b/GetIntoTeachingApi/Models/Crm/Validators/ContactChannelCreationValidator.cs new file mode 100644 index 000000000..80414a250 --- /dev/null +++ b/GetIntoTeachingApi/Models/Crm/Validators/ContactChannelCreationValidator.cs @@ -0,0 +1,19 @@ +using FluentValidation; +using GetIntoTeachingApi.Services; +using GetIntoTeachingApi.Validators; + +namespace GetIntoTeachingApi.Models.Crm.Validators +{ + public class ContactChannelCreationValidator : AbstractValidator + { + public ContactChannelCreationValidator(IStore store) + { + RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelSourceId) + .SetValidator(new PickListItemIdValidator("dfe_contactchannelcreation", "dfe_creationchannelsource", store)); + RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelServiceId) + .SetValidator(new PickListItemIdValidator("dfe_contactchannelcreation", "dfe_creationchannelservice", store)); + RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelActivityId) + .SetValidator(new PickListItemIdValidator("dfe_contactchannelcreation", "dfe_creationchannelactivities", store)); + } + } +} \ No newline at end of file diff --git a/GetIntoTeachingApi/Models/GetIntoTeaching/GetIntoTeachingCallback.cs b/GetIntoTeachingApi/Models/GetIntoTeaching/GetIntoTeachingCallback.cs index 445b1ad97..fbddfc8c8 100644 --- a/GetIntoTeachingApi/Models/GetIntoTeaching/GetIntoTeachingCallback.cs +++ b/GetIntoTeachingApi/Models/GetIntoTeaching/GetIntoTeachingCallback.cs @@ -1,13 +1,13 @@ -using System; -using System.Text.Json.Serialization; -using GetIntoTeachingApi.Models.Crm; +using GetIntoTeachingApi.Models.Crm; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; using Swashbuckle.AspNetCore.Annotations; +using System; +using System.Text.Json.Serialization; namespace GetIntoTeachingApi.Models.GetIntoTeaching { - public class GetIntoTeachingCallback + public class GetIntoTeachingCallback : ICreateContactChannel { public Guid? CandidateId { get; set; } [SwaggerSchema(WriteOnly = true)] @@ -20,14 +20,22 @@ public class GetIntoTeachingCallback [SwaggerSchema(WriteOnly = true)] public DateTime? PhoneCallScheduledAt { get; set; } public string TalkingPoints { get; set; } + + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelSourceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelServiceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelActivityId { get; set; } [JsonIgnore] public Candidate Candidate => CreateCandidate(); [JsonIgnore] public IDateTimeProvider DateTimeProvider { get; set; } = new DateTimeProvider(); - public GetIntoTeachingCallback() - { + public int? DefaultContactCreationChannel => (int?)Candidate.Channel.GetIntoTeachingCallback; + + public GetIntoTeachingCallback(){ } public GetIntoTeachingCallback(Candidate candidate) @@ -55,8 +63,7 @@ private Candidate CreateCandidate() LastName = LastName, AddressTelephone = AddressTelephone, }; - - ConfigureChannel(candidate); + candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId); SchedulePhoneCall(candidate); AcceptPrivacyPolicy(candidate); @@ -79,14 +86,6 @@ private void SchedulePhoneCall(Candidate candidate) } } - private void ConfigureChannel(Candidate candidate) - { - if (CandidateId == null) - { - candidate.ChannelId = (int?)Candidate.Channel.GetIntoTeachingCallback; - } - } - private void AcceptPrivacyPolicy(Candidate candidate) { if (AcceptedPolicyId != null) diff --git a/GetIntoTeachingApi/Models/GetIntoTeaching/MailingListAddMember.cs b/GetIntoTeachingApi/Models/GetIntoTeaching/MailingListAddMember.cs index 6aad83644..0d393ff3f 100644 --- a/GetIntoTeachingApi/Models/GetIntoTeaching/MailingListAddMember.cs +++ b/GetIntoTeachingApi/Models/GetIntoTeaching/MailingListAddMember.cs @@ -8,7 +8,7 @@ namespace GetIntoTeachingApi.Models.GetIntoTeaching { - public class MailingListAddMember + public class MailingListAddMember : ICreateContactChannel { public Guid? CandidateId { get; set; } public Guid? QualificationId { get; set; } @@ -20,6 +20,13 @@ public class MailingListAddMember public int? DegreeStatusId { get; set; } [SwaggerSchema(WriteOnly = true)] public int? ChannelId { get; set; } + + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelSourceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelServiceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelActivityId { get; set; } public string Email { get; set; } public string FirstName { get; set; } @@ -38,8 +45,10 @@ public class MailingListAddMember [JsonIgnore] public IDateTimeProvider DateTimeProvider { get; set; } = new DateTimeProvider(); - public MailingListAddMember() - { + public int? DefaultContactCreationChannel => + ChannelId ?? (int?)Candidate.Channel.MailingList; // Use the assigned channel ID if available, else assign default. + + public MailingListAddMember(){ } public MailingListAddMember(Candidate candidate) @@ -91,8 +100,7 @@ private Candidate CreateCandidate() GdprConsentId = (int)Candidate.GdprConsent.Consent, OptOutOfGdpr = false, }; - - ConfigureChannel(candidate); + candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId); ConfigureSubscriptions(candidate); AddQualification(candidate); AcceptPrivacyPolicy(candidate); @@ -100,14 +108,6 @@ private Candidate CreateCandidate() return candidate; } - private void ConfigureChannel(Candidate candidate) - { - if (CandidateId == null) - { - candidate.ChannelId = ChannelId ?? (int?)Candidate.Channel.MailingList; - } - } - private void AddQualification(Candidate candidate) { candidate.Qualifications.Add(new CandidateQualification() diff --git a/GetIntoTeachingApi/Models/GetIntoTeaching/TeachingEventAddAttendee.cs b/GetIntoTeachingApi/Models/GetIntoTeaching/TeachingEventAddAttendee.cs index cd4a2b69e..fc000f272 100644 --- a/GetIntoTeachingApi/Models/GetIntoTeaching/TeachingEventAddAttendee.cs +++ b/GetIntoTeachingApi/Models/GetIntoTeaching/TeachingEventAddAttendee.cs @@ -1,14 +1,14 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; -using GetIntoTeachingApi.Models.Crm; +using GetIntoTeachingApi.Models.Crm; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; using Swashbuckle.AspNetCore.Annotations; +using System; +using System.Linq; +using System.Text.Json.Serialization; namespace GetIntoTeachingApi.Models.GetIntoTeaching { - public class TeachingEventAddAttendee + public class TeachingEventAddAttendee : ICreateContactChannel { public Guid? CandidateId { get; set; } public Guid? QualificationId { get; set; } @@ -18,6 +18,13 @@ public class TeachingEventAddAttendee [SwaggerSchema(WriteOnly = true)] public int? ChannelId { get; set; } + + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelSourceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelServiceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelActivityId { get; set; } [SwaggerSchema(WriteOnly = true)] public Guid? AcceptedPolicyId { get; set; } @@ -48,8 +55,10 @@ public class TeachingEventAddAttendee [JsonIgnore] public IDateTimeProvider DateTimeProvider { get; set; } = new DateTimeProvider(); - public TeachingEventAddAttendee() - { + public int? DefaultContactCreationChannel => + ChannelId ?? (int?)Candidate.Channel.Event; // Use the assigned channel ID if available, else assign default. + + public TeachingEventAddAttendee(){ } public TeachingEventAddAttendee(Candidate candidate) @@ -126,8 +135,7 @@ private Candidate CreateCandidate() { candidate.PreferredTeachingSubjectId = PreferredTeachingSubjectId; } - - ConfigureChannel(candidate); + candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId); AddTeachingEventRegistration(candidate); AddQualification(candidate); AcceptPrivacyPolicy(candidate); @@ -136,14 +144,6 @@ private Candidate CreateCandidate() return candidate; } - private void ConfigureChannel(Candidate candidate) - { - if (CandidateId == null) - { - candidate.ChannelId = ChannelId ?? (int?)Candidate.Channel.Event; - } - } - private void AddQualification(Candidate candidate) { if (DegreeStatusId != null) diff --git a/GetIntoTeachingApi/Models/SchoolsExperience/SchoolsExperienceSignUp.cs b/GetIntoTeachingApi/Models/SchoolsExperience/SchoolsExperienceSignUp.cs index a5a0cee74..0092badd0 100644 --- a/GetIntoTeachingApi/Models/SchoolsExperience/SchoolsExperienceSignUp.cs +++ b/GetIntoTeachingApi/Models/SchoolsExperience/SchoolsExperienceSignUp.cs @@ -8,7 +8,7 @@ namespace GetIntoTeachingApi.Models.SchoolsExperience { - public class SchoolsExperienceSignUp + public class SchoolsExperienceSignUp : ICreateContactChannel { public Guid? CandidateId { get; set; } public Guid? PreferredTeachingSubjectId { get; set; } @@ -39,14 +39,22 @@ public class SchoolsExperienceSignUp public int? DegreeTypeId { get; set; } public string DegreeSubject { get; set; } public int? UkDegreeGradeId { get; set; } + + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelSourceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelServiceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelActivityId { get; set; } [JsonIgnore] public Candidate Candidate => CreateCandidate(); [JsonIgnore] public IDateTimeProvider DateTimeProvider { get; set; } = new DateTimeProvider(); - public SchoolsExperienceSignUp() - { + public int? DefaultContactCreationChannel => (int?)Candidate.Channel.SchoolsExperience; + + public SchoolsExperienceSignUp(){ } public SchoolsExperienceSignUp(Candidate candidate) @@ -123,22 +131,13 @@ private Candidate CreateCandidate() { candidate.SecondaryPreferredTeachingSubjectId = SecondaryPreferredTeachingSubjectId; } - - ConfigureChannel(candidate); + candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId); AcceptPrivacyPolicy(candidate); AddQualification(candidate); return candidate; } - private void ConfigureChannel(Candidate candidate) - { - if (CandidateId == null) - { - candidate.ChannelId = (int?)Candidate.Channel.SchoolsExperience; - } - } - private void AcceptPrivacyPolicy(Candidate candidate) { if (AcceptedPolicyId != null) diff --git a/GetIntoTeachingApi/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUp.cs b/GetIntoTeachingApi/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUp.cs index a08e67149..3a8b065e9 100644 --- a/GetIntoTeachingApi/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUp.cs +++ b/GetIntoTeachingApi/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUp.cs @@ -1,14 +1,14 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; -using GetIntoTeachingApi.Models.Crm; +using GetIntoTeachingApi.Models.Crm; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; using Swashbuckle.AspNetCore.Annotations; +using System; +using System.Linq; +using System.Text.Json.Serialization; namespace GetIntoTeachingApi.Models.TeacherTrainingAdviser { - public class TeacherTrainingAdviserSignUp + public class TeacherTrainingAdviserSignUp : ICreateContactChannel { public enum ResubscribableAdviserStatus { @@ -65,6 +65,14 @@ public enum ResubscribableAdviserStatus public int? AdviserStatusId { get; set; } [SwaggerSchema(WriteOnly = true)] public int? ChannelId { get; set; } + + + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelSourceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelServiceId { get; set; } + [SwaggerSchema(WriteOnly = true)] + public int? CreationChannelActivityId { get; set; } public string Email { get; set; } public string FirstName { get; set; } @@ -90,8 +98,10 @@ public enum ResubscribableAdviserStatus [JsonIgnore] private bool IsOverseas => CountryId != Country.UnitedKingdomCountryId; - public TeacherTrainingAdviserSignUp() - { + public int? DefaultContactCreationChannel => + ChannelId ?? (int?)Candidate.Channel.TeacherTrainingAdviser; // Use the assigned channel ID if available, else assign default. + + public TeacherTrainingAdviserSignUp(){ } public TeacherTrainingAdviserSignUp(Candidate candidate) @@ -217,7 +227,7 @@ private Candidate CreateCandidate() OptOutOfGdpr = false, }; - ConfigureChannel(candidate); + candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId); ConfigureGcseStatus(candidate); AcceptPrivacyPolicy(candidate); SchedulePhoneCall(candidate); @@ -246,14 +256,6 @@ private void UpdateClosedAdviserStatus(Candidate candidate) candidate.StatusIsWaitingToBeAssignedAt = DateTimeProvider.UtcNow; } - private void ConfigureChannel(Candidate candidate) - { - if (CandidateId == null) - { - candidate.ChannelId = ChannelId ?? (int?)Candidate.Channel.TeacherTrainingAdviser; - } - } - private void ConfigureGcseStatus(Candidate candidate) { if (HasGcseMathsAndEnglishId == null) diff --git a/GetIntoTeachingApi/Services/CandidateUpserter.cs b/GetIntoTeachingApi/Services/CandidateUpserter.cs index dafb0801f..7c2a18d77 100644 --- a/GetIntoTeachingApi/Services/CandidateUpserter.cs +++ b/GetIntoTeachingApi/Services/CandidateUpserter.cs @@ -28,6 +28,7 @@ public void Upsert(Candidate candidate) var pastTeachingPositions = ClearPastTeachingPositions(candidate); var applicationForms = ClearApplicationForms(candidate); var schoolExperiences = ClearSchoolExperiences(candidate); + var contactChannelCreations = ClearContactChannelCreations(candidate); PreventCandidateEmailFromBeingOverwritten(candidate); UpdateEventSubscriptionType(candidate); @@ -41,6 +42,7 @@ public void Upsert(Candidate candidate) SavePrivacyPolicy(privacyPolicy, candidate); SavePhoneCall(phoneCall, candidate); SaveSchoolExperiences(schoolExperiences, candidate); + SaveContactChannelCreation(contactChannelCreations, candidate); IncrementCallbackBookingQuotaNumberOfBookings(phoneCall); @@ -90,6 +92,15 @@ private static IEnumerable ClearSchoolExperiences(Can candidate.SchoolExperiences.Clear(); return schoolExperiences; } + + private static IEnumerable ClearContactChannelCreations(Candidate candidate) + { + List contactChannelCreations = + new(candidate.ContactChannelCreations); + candidate.ContactChannelCreations.Clear(); + + return contactChannelCreations; + } private static PhoneCall ClearPhoneCall(Candidate candidate) { @@ -251,5 +262,15 @@ private void SavePrivacyPolicy(CandidatePrivacyPolicy privacyPolicy, Candidate c string json = privacyPolicy.SerializeChangeTracked(); _jobClient.Enqueue>((x) => x.Run(json, null)); } + + private void SaveContactChannelCreation(IEnumerable contactChannelCreations, Candidate candidate) + { + foreach (var contactChannelCreation in contactChannelCreations) + { + contactChannelCreation.CandidateId = (Guid)candidate.Id; + string json = contactChannelCreation.SerializeChangeTracked(); + _jobClient.Enqueue>((x) => x.Run(json, null)); + } + } } } diff --git a/GetIntoTeachingApi/Services/Store.cs b/GetIntoTeachingApi/Services/Store.cs index ffc5352b6..626fe14ee 100644 --- a/GetIntoTeachingApi/Services/Store.cs +++ b/GetIntoTeachingApi/Services/Store.cs @@ -278,7 +278,12 @@ private async Task SyncPickListItems() await SyncPickListItem("dfe_applyapplicationform", "dfe_applystatus"); await SyncPickListItem("dfe_applyapplicationform", "dfe_recruitmentyear"); await SyncPickListItem("dfe_applyapplicationchoice", "dfe_applicationchoicestatus"); - await SyncPickListItem("dfe_applyreference", "dfe_referencefeedbackstatus"); + await SyncPickListItem("dfe_applyreference", "dfe_referencefeedbackstatus"); + + await SyncPickListItem("dfe_contactchannelcreation", "dfe_creationchannelsource"); + await SyncPickListItem("dfe_contactchannelcreation", "dfe_creationchannelservice"); + await SyncPickListItem("dfe_contactchannelcreation", "dfe_creationchannelactivities"); + } private async Task SyncModels(IEnumerable models, IQueryable dbSet) diff --git a/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs b/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs index c4d23dda6..dc8b1e0f0 100644 --- a/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs @@ -59,7 +59,7 @@ public void GenerateMappingInfo_RespondsWithMappingInfo() var ok = response.Should().BeOfType().Subject; var mappings = ok.Value.Should().BeOfType>().Subject; - mappings.Count.Should().Be(17); + mappings.Count.Should().Be(18); mappings.Any(m => m.LogicalName == "contact" && m.Class == "GetIntoTeachingApi.Models.Crm.Candidate" ).Should().BeTrue(); diff --git a/GetIntoTeachingApiTests/Controllers/PickListItemsControllerTests.cs b/GetIntoTeachingApiTests/Controllers/PickListItemsControllerTests.cs index 3fdc7e8fd..06890971b 100644 --- a/GetIntoTeachingApiTests/Controllers/PickListItemsControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/PickListItemsControllerTests.cs @@ -313,6 +313,45 @@ public async Task GetSubscriptionTypes_ReturnsAllTypes() var ok = response.Should().BeOfType().Subject; ok.Value.Should().BeEquivalentTo(mockItems); } + + [Fact] + public async Task GetContactCreationChannelSources_ReturnsAllSources() + { + var mockItems = MockPickListItems(); + _mockStore.Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelsource")) + .Returns(mockItems.AsAsyncQueryable()); + + var response = await _controller.GetContactCreationChannelSources(); + + var ok = response.Should().BeOfType().Subject; + ok.Value.Should().BeEquivalentTo(mockItems); + } + + [Fact] + public async Task GetContactCreationChannelServices_ReturnsAllServices() + { + var mockItems = MockPickListItems(); + _mockStore.Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelservice")) + .Returns(mockItems.AsAsyncQueryable()); + + var response = await _controller.GetContactCreationChannelServices(); + + var ok = response.Should().BeOfType().Subject; + ok.Value.Should().BeEquivalentTo(mockItems); + } + + [Fact] + public async Task GetContactCreationChannelActivities_ReturnsAllActivities() + { + var mockItems = MockPickListItems(); + _mockStore.Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelactivities")) + .Returns(mockItems.AsAsyncQueryable()); + + var response = await _controller.GetContactCreationChannelActivities(); + + var ok = response.Should().BeOfType().Subject; + ok.Value.Should().BeEquivalentTo(mockItems); + } private static PickListItem[] MockPickListItems() { diff --git a/GetIntoTeachingApiTests/Jobs/UpsertCandidateJobTests.cs b/GetIntoTeachingApiTests/Jobs/UpsertCandidateJobTests.cs index 0dbbaaa63..d3c39ca7c 100644 --- a/GetIntoTeachingApiTests/Jobs/UpsertCandidateJobTests.cs +++ b/GetIntoTeachingApiTests/Jobs/UpsertCandidateJobTests.cs @@ -135,7 +135,7 @@ private static bool IsMatch(object objectA, object objectB) private static string Signature(Candidate candidate) { - var changedProperties = "MergedIsNewRegistrantTeachingEventRegistrationsQualificationsPastTeachingPositionsApplicationFormsSchoolExperiencesIdEmail"; + var changedProperties = "MergedIsNewRegistrantTeachingEventRegistrationsQualificationsPastTeachingPositionsApplicationFormsSchoolExperiencesContactChannelCreationsIdEmail"; return $"{candidate.Id}-{candidate.Email}-{changedProperties}"; } } diff --git a/GetIntoTeachingApiTests/Models/Crm/ContactChannelCreationTests.cs b/GetIntoTeachingApiTests/Models/Crm/ContactChannelCreationTests.cs new file mode 100644 index 000000000..eabf6b9dc --- /dev/null +++ b/GetIntoTeachingApiTests/Models/Crm/ContactChannelCreationTests.cs @@ -0,0 +1,40 @@ +using FluentAssertions; +using GetIntoTeachingApi.Attributes; +using GetIntoTeachingApi.Models.Crm; +using Microsoft.Xrm.Sdk; +using Xunit; + +namespace GetIntoTeachingApiTests.Models.Crm +{ + public class ContactChannelCreationTests + { + [Fact] + public void EntityAttributes() + { + var type = typeof(ContactChannelCreation); + + type.Should().BeDecoratedWith(a => a.LogicalName == "dfe_contactchannelcreation"); + type.Should().BeDecoratedWith(); + + type.GetProperty("CreationChannelSourceId").Should().BeDecoratedWith( + a => a.Name == "dfe_creationchannelsource" && a.Type == typeof(OptionSetValue)); + type.GetProperty("CreationChannelServiceId").Should().BeDecoratedWith( + a => a.Name == "dfe_creationchannelservice" && a.Type == typeof(OptionSetValue)); + type.GetProperty("CreationChannelActivityId").Should().BeDecoratedWith( + a => a.Name == "dfe_creationchannelactivities" && a.Type == typeof(OptionSetValue)); + + type.GetProperty("CreatedBy").Should().BeDecoratedWith(a => a.Name == "createdby"); + type.GetProperty("CandidateId").Should().BeDecoratedWith(a => a.Name == "dfe_contactid"); + type.GetProperty("CreationChannel").Should().BeDecoratedWith(a => a.Name == "dfe_creationchannel"); + type.GetProperty("CreationChannelSourceId").Should().BeDecoratedWith(a => a.Name == "dfe_creationchannelsource"); + type.GetProperty("CreationChannelServiceId").Should().BeDecoratedWith(a => a.Name == "dfe_creationchannelservice"); + type.GetProperty("CreationChannelActivityId").Should().BeDecoratedWith(a => a.Name == "dfe_creationchannelactivities"); + } + + [Fact] + public void CreationChannel_DefaultValue_IsCorrect() + { + new ContactChannelCreation().CreationChannel.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/GetIntoTeachingApiTests/Models/Crm/Validators/ContactChannelCreationValidatorTests.cs b/GetIntoTeachingApiTests/Models/Crm/Validators/ContactChannelCreationValidatorTests.cs new file mode 100644 index 000000000..2deca61ff --- /dev/null +++ b/GetIntoTeachingApiTests/Models/Crm/Validators/ContactChannelCreationValidatorTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using FluentAssertions; +using FluentValidation.TestHelper; +using GetIntoTeachingApi.Models; +using GetIntoTeachingApi.Models.Crm; +using GetIntoTeachingApi.Models.Crm.Validators; +using GetIntoTeachingApi.Services; +using Moq; +using Xunit; + +namespace GetIntoTeachingApiTests.Models.Crm.Validators +{ + public class ContactChannelCreationValidatorTests + { + private readonly ContactChannelCreationValidator _validator; + private readonly Mock _mockStore; + + public ContactChannelCreationValidatorTests() + { + _mockStore = new Mock(); + _validator = new ContactChannelCreationValidator(_mockStore.Object); + } + + [Fact] + public void Validate_WhenValid_HasNoErrors() + { + var mockPickListItem = new PickListItem { Id = 123 }; + + _mockStore + .Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelsource")) + .Returns(new[] { mockPickListItem }.AsQueryable()); + + _mockStore + .Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelservice")) + .Returns(new[] { mockPickListItem }.AsQueryable()); + + _mockStore + .Setup(mock => mock.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelactivities")) + .Returns(new[] { mockPickListItem }.AsQueryable()); + + var contactChannelCreation = new ContactChannelCreation() + { + CreationChannelSourceId = mockPickListItem.Id, + CreationChannelServiceId = mockPickListItem.Id, + CreationChannelActivityId = mockPickListItem.Id, + }; + + var result = _validator.TestValidate(contactChannelCreation); + + result.IsValid.Should().BeTrue(); + } + + [Fact] + public void Validate_ChannelIdIsInvalid_HasError() + { + var result = _validator.TestValidate(new ContactChannelCreation() { CreationChannelSourceId = 123 }); + + result.ShouldHaveValidationErrorFor(r => r.CreationChannelSourceId); + } + } +} diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/GetIntoTeachingCallbackTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/GetIntoTeachingCallbackTests.cs index 3ce6596f0..e7bc7bca0 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/GetIntoTeachingCallbackTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/GetIntoTeachingCallbackTests.cs @@ -2,6 +2,7 @@ using GetIntoTeachingApi.Models.Crm; using GetIntoTeachingApi.Models.GetIntoTeaching; using System; +using System.Linq; using Xunit; namespace GetIntoTeachingApiTests.Models.GetIntoTeaching @@ -9,7 +10,7 @@ namespace GetIntoTeachingApiTests.Models.GetIntoTeaching public class GetIntoTeachingCallbackTests { [Fact] - public void Constructor_WithCandidate_MapsCorrectly() + public void Constructor_WithExistingCandidate_MapsCorrectly() { var candidate = new Candidate() { @@ -30,7 +31,7 @@ public void Constructor_WithCandidate_MapsCorrectly() } [Fact] - public void Candidate_MapsCorrectly() + public void ExistingCandidate_MapsCorrectly() { var request = new GetIntoTeachingCallback() { @@ -42,6 +43,9 @@ public void Candidate_MapsCorrectly() AddressTelephone = "123456789", PhoneCallScheduledAt = DateTime.UtcNow, TalkingPoints = "Talking points", + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var candidate = request.Candidate; @@ -61,6 +65,32 @@ public void Candidate_MapsCorrectly() candidate.PrivacyPolicy.AcceptedPolicyId.Should().Be((Guid)request.AcceptedPolicyId); candidate.PrivacyPolicy.AcceptedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(30)); + + var contactChannelCreation = candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(false); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + candidate.ChannelId.Should().Be(null); + } + + [Fact] + public void NewCandidate_MapsCorrectly() + { + var request = new GetIntoTeachingCallback() + { + CandidateId = null, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, + }; + + var contactChannelCreation = request.Candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(true); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + request.Candidate.ChannelId.Should().Be(null); } [Fact] diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/MailingListAddMemberTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/MailingListAddMemberTests.cs index 1f2e3fb64..1165c45b5 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/MailingListAddMemberTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/MailingListAddMemberTests.cs @@ -62,7 +62,7 @@ public void Constructor_WithCandidate_MapsCorrectly() } [Fact] - public void Candidate_MapsCorrectly() + public void ExistingCandidate_MapsCorrectly() { var request = new MailingListAddMember() { @@ -76,7 +76,10 @@ public void Candidate_MapsCorrectly() FirstName = "John", LastName = "Doe", AddressPostcode = "KY11 9YU", - WelcomeGuideVariant = "variant1" + WelcomeGuideVariant = "variant1", + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var candidate = request.Candidate; @@ -112,6 +115,32 @@ public void Candidate_MapsCorrectly() candidate.Qualifications.First().DegreeStatusId.Should().Be(request.DegreeStatusId); candidate.Qualifications.First().TypeId.Should().Be((int)CandidateQualification.DegreeType.Degree); candidate.Qualifications.First().Id.Should().Be(request.QualificationId); + + var contactChannelCreation = candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(false); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + candidate.ChannelId.Should().Be(null); + } + + [Fact] + public void NewCandidate_MapsCreationChannelCorrectly() + { + var request = new MailingListAddMember() + { + CandidateId = null, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, + }; + + var contactChannelCreation = request.Candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(true); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + request.Candidate.ChannelId.Should().Be(null); } [Fact] diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/TeachingEventAddAttendeeTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/TeachingEventAddAttendeeTests.cs index 53002b45d..1a6943ef9 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/TeachingEventAddAttendeeTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/TeachingEventAddAttendeeTests.cs @@ -11,7 +11,7 @@ namespace GetIntoTeachingApiTests.Models.GetIntoTeaching public class TeachingEventAddAttendeeTests { [Fact] - public void Constructor_WithCandidate_MapsCorrectly() + public void Constructor_WithExistingCandidate_MapsCorrectly() { var latestQualification = new CandidateQualification() { @@ -64,7 +64,7 @@ public void Constructor_WithCandidate_MapsCorrectly() } [Fact] - public void Candidate_MapsCorrectly() + public void ExistingCandidate_MapsCorrectly() { var request = new TeachingEventAddAttendee() { @@ -82,6 +82,9 @@ public void Candidate_MapsCorrectly() AddressPostcode = "KY11 9YU", SubscribeToMailingList = true, IsWalkIn = false, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var candidate = request.Candidate; @@ -121,6 +124,32 @@ public void Candidate_MapsCorrectly() candidate.Qualifications.First().DegreeStatusId.Should().Be(request.DegreeStatusId); candidate.Qualifications.First().TypeId.Should().Be((int)CandidateQualification.DegreeType.Degree); candidate.Qualifications.First().Id.Should().Be(request.QualificationId); + + var contactChannelCreation = candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(false); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + candidate.ChannelId.Should().Be(null); + } + + [Fact] + public void NewCandidate_MapsCorrectly() + { + var request = new TeachingEventAddAttendee() + { + CandidateId = null, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, + }; + + var contactChannelCreation = request.Candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(true); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + request.Candidate.ChannelId.Should().Be(null); } [Fact] diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/GetIntoTeachingCallbackValidatorTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/GetIntoTeachingCallbackValidatorTests.cs index 0c1256901..7498cae9a 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/GetIntoTeachingCallbackValidatorTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/GetIntoTeachingCallbackValidatorTests.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using FluentAssertions; using FluentValidation.TestHelper; using GetIntoTeachingApi.Models.GetIntoTeaching; using GetIntoTeachingApi.Models.GetIntoTeaching.Validators; @@ -20,6 +22,34 @@ public GetIntoTeachingCallbackValidatorTests() _validator = new GetIntoTeachingCallbackValidator(_mockStore.Object, new DateTimeProvider()); _callback = new GetIntoTeachingCallback(); } + + [Fact] + public void Validate_WhenValid_HasNoErrors() + { + // var mockPickListItem = new PickListItem { Id = 123 }; + // var mockSubject = new TeachingSubject { Id = Guid.NewGuid() }; + // var mockPrivacyPolicy = new PrivacyPolicy { Id = Guid.NewGuid() }; + + var request = new GetIntoTeachingCallback() + { + FirstName = "John", + LastName = "Doe", + Email = "email@address.com", + AcceptedPolicyId = Guid.NewGuid(), + AddressTelephone = "123456789", + PhoneCallScheduledAt = DateTime.UtcNow.AddDays(1), + TalkingPoints = "Talking points", + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, + }; + + var result = _validator.TestValidate(request); + // Ensure no validation errors on root object (we expect errors on the Candidate + // properties as we can't mock them). + var propertiesWithErrors = result.Errors.Select(e => e.PropertyName); + propertiesWithErrors.All(p => p.StartsWith("Candidate.")).Should().BeTrue(); + } [Fact] public void Validate_RequiredFieldsWhenNull_HasError() diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/MailingListAddMemberValidatorTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/MailingListAddMemberValidatorTests.cs index a83953e13..ab0d91865 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/MailingListAddMemberValidatorTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/MailingListAddMemberValidatorTests.cs @@ -41,6 +41,9 @@ public void Validate_WhenValid_HasNoErrors() FirstName = "John", LastName = "Doe", AddressPostcode = "KY11 9YU", + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var result = _validator.TestValidate(request); diff --git a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/TeachingEventAddAttendeeValidatorTests.cs b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/TeachingEventAddAttendeeValidatorTests.cs index 06c9157d0..0381e982b 100644 --- a/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/TeachingEventAddAttendeeValidatorTests.cs +++ b/GetIntoTeachingApiTests/Models/GetIntoTeaching/Validators/TeachingEventAddAttendeeValidatorTests.cs @@ -45,6 +45,9 @@ public void Validate_WhenValid_HasNoErrors() AddressPostcode = "KY11 9YU", IsWalkIn = true, IsVerified = false, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var result = _validator.TestValidate(request); diff --git a/GetIntoTeachingApiTests/Models/SchoolsExperience/SchoolsExperienceSignUpTests.cs b/GetIntoTeachingApiTests/Models/SchoolsExperience/SchoolsExperienceSignUpTests.cs index d85aee858..85fe1c1b0 100644 --- a/GetIntoTeachingApiTests/Models/SchoolsExperience/SchoolsExperienceSignUpTests.cs +++ b/GetIntoTeachingApiTests/Models/SchoolsExperience/SchoolsExperienceSignUpTests.cs @@ -3,6 +3,7 @@ using GetIntoTeachingApi.Models.Crm; using GetIntoTeachingApi.Models.SchoolsExperience; using System; +using System.Linq; using System.Reflection; using Xunit; @@ -11,7 +12,7 @@ namespace GetIntoTeachingApiTests.Models.SchoolsExperience public class SchoolsExperienceSignUpTests { [Fact] - public void Constructor_WithCandidate_MapsCorrectly() + public void Constructor_WithExistingCandidate_MapsCorrectly() { var candidate = new Candidate() { @@ -58,7 +59,7 @@ public void Constructor_WithCandidate_MapsCorrectly() } [Fact] - public void Candidate_MapsCorrectly() + public void ExistingCandidate_MapsCorrectly() { var request = new SchoolsExperienceSignUp() { @@ -78,6 +79,9 @@ public void Candidate_MapsCorrectly() Telephone = "234567890", HasDbsCertificate = true, DbsCertificateIssuedAt = DateTime.UtcNow, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var candidate = request.Candidate; @@ -101,6 +105,32 @@ public void Candidate_MapsCorrectly() candidate.PrivacyPolicy.AcceptedPolicyId.Should().Be((Guid)request.AcceptedPolicyId); candidate.PrivacyPolicy.AcceptedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(30)); + + var contactChannelCreation = candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(false); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + candidate.ChannelId.Should().Be(null); + } + + [Fact] + public void NewCandidate_MapsCorrectly() + { + var request = new SchoolsExperienceSignUp() + { + CandidateId = null, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, + }; + + var contactChannelCreation = request.Candidate.ContactChannelCreations.First(); + contactChannelCreation.CreationChannel.Should().Be(true); + contactChannelCreation.CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + contactChannelCreation.CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + contactChannelCreation.CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + request.Candidate.ChannelId.Should().Be(null); } [Theory] diff --git a/GetIntoTeachingApiTests/Models/SchoolsExperience/Validators/SchoolsExperienceSignUpValidatorTests.cs b/GetIntoTeachingApiTests/Models/SchoolsExperience/Validators/SchoolsExperienceSignUpValidatorTests.cs index 76f5b7c52..7c916dca8 100644 --- a/GetIntoTeachingApiTests/Models/SchoolsExperience/Validators/SchoolsExperienceSignUpValidatorTests.cs +++ b/GetIntoTeachingApiTests/Models/SchoolsExperience/Validators/SchoolsExperienceSignUpValidatorTests.cs @@ -45,6 +45,9 @@ public void Validate_WhenValid_HasNoErrors() AddressPostcode = "KY11 9YU", Telephone = "123456789", HasDbsCertificate = false, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var result = _validator.TestValidate(request); diff --git a/GetIntoTeachingApiTests/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUpTests.cs b/GetIntoTeachingApiTests/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUpTests.cs index 42c0a9c00..4694417cf 100644 --- a/GetIntoTeachingApiTests/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUpTests.cs +++ b/GetIntoTeachingApiTests/Models/TeacherTrainingAdviser/TeacherTrainingAdviserSignUpTests.cs @@ -138,6 +138,9 @@ public void Candidate_MapsCorrectly() DegreeSubject = "Maths", AddressPostcode = "KY11 9YU", PhoneCallScheduledAt = DateTime.UtcNow, + CreationChannelSourceId = 222750003, + CreationChannelServiceId = 222750002, + CreationChannelActivityId = 222750001, }; var candidate = request.Candidate; @@ -200,6 +203,10 @@ public void Candidate_MapsCorrectly() candidate.Qualifications.First().DegreeSubject.Should().Be(request.DegreeSubject); candidate.Qualifications.First().TypeId.Should().Be(request.DegreeTypeId); + candidate.ContactChannelCreations.First().CreationChannelSourceId.Should().Be(request.CreationChannelSourceId); + candidate.ContactChannelCreations.First().CreationChannelServiceId.Should().Be(request.CreationChannelServiceId); + candidate.ContactChannelCreations.First().CreationChannelActivityId.Should().Be(request.CreationChannelActivityId); + candidate.HasTeacherTrainingAdviserSubscription.Should().BeTrue(); } @@ -280,6 +287,43 @@ public void Candidate_ChannelIdWhenCandidateIdIsNotNull_IsNotChanged() request.Candidate.ChannelId.Should().BeNull(); request.Candidate.ChangedPropertyNames.Should().NotContain("ChannelId"); } + + [Fact] + public void Candidate_ContactChannelCreationWhenCandidateIdIsNull() + { + var request = new TeacherTrainingAdviserSignUp() + { + CandidateId = null, + CreationChannelSourceId = 222750000, + CreationChannelServiceId = 222750001, + CreationChannelActivityId = 222750002, + }; + + request.Candidate.ChannelId.Should().BeNull(); + request.Candidate.ChangedPropertyNames.Should().Contain("ContactChannelCreations"); + request.Candidate.ContactChannelCreations.First().CreationChannelSourceId.Should().Be(222750000); + request.Candidate.ContactChannelCreations.First().CreationChannelServiceId.Should().Be(222750001); + request.Candidate.ContactChannelCreations.First().CreationChannelActivityId.Should().Be(222750002); + } + + [Fact] + public void Candidate_ContactChannelCreationWhenCandidateIdIsNotNull() + { + var request = new TeacherTrainingAdviserSignUp() + { + CandidateId = Guid.NewGuid(), + CreationChannelSourceId = 222750000, + CreationChannelServiceId = 222750001, + CreationChannelActivityId = 222750002, + }; + + request.Candidate.ChannelId.Should().BeNull(); + request.Candidate.ChangedPropertyNames.Should().NotContain("ChannelId"); + request.Candidate.ChangedPropertyNames.Should().Contain("ContactChannelCreations"); + request.Candidate.ContactChannelCreations.First().CreationChannelSourceId.Should().Be(222750000); + request.Candidate.ContactChannelCreations.First().CreationChannelServiceId.Should().Be(222750001); + request.Candidate.ContactChannelCreations.First().CreationChannelActivityId.Should().Be(222750002); + } [Fact] public void Candidate_WhenChannelIsProvided_SetsOnAllModels() diff --git a/GetIntoTeachingApiTests/Services/CandidateUpserterTests.cs b/GetIntoTeachingApiTests/Services/CandidateUpserterTests.cs index 0f9154155..62183de1d 100644 --- a/GetIntoTeachingApiTests/Services/CandidateUpserterTests.cs +++ b/GetIntoTeachingApiTests/Services/CandidateUpserterTests.cs @@ -264,6 +264,28 @@ public void Upsert_WhenPrivacyPolicyIsNull_DoesNotUpsertPrivacyPolicy() It.Is(job => job.Type == typeof(UpsertModelWithCandidateIdJob)), It.IsAny()), Times.Never); } + + [Fact] + public void Upsert_WithContactChannelCreation_SavesContactChannelCreations() + { + var candidateId = Guid.NewGuid(); + var contactChannelCreation = new ContactChannelCreation { + CreationChannelActivityId = 222750000, + CreationChannelServiceId = 222750001, + CreationChannelSourceId = 222750002, + }; + + _candidate.ContactChannelCreations.Add(contactChannelCreation); + + _upserter.Upsert(_candidate); + + contactChannelCreation.CandidateId = candidateId; + + _mockJobClient.Verify(backgroundJobClient => + backgroundJobClient.Create( + It.Is(job => job.Type == typeof(UpsertModelWithCandidateIdJob)), + It.IsAny()), Times.Once); + } private static bool IsMatch(object objectA, object objectB) {