Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add creation channel model and TTA endppoint update #1494

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8da6967
add new models and picklists fo contact_channel_creation
martyn-w Dec 5, 2024
0cc378f
add tests for creation channel
martyn-w Dec 9, 2024
a819647
work in progress
martyn-w Dec 17, 2024
c40d416
test fixes
martyn-w Jan 7, 2025
ce4e9c1
remove extra logging
martyn-w Jan 7, 2025
770af94
Update TeacherTrainingAdviserSignUp.cs
martyn-w Jan 9, 2025
2b9e03e
rollback whitespace changes
martyn-w Jan 9, 2025
4de45ac
rollback whitespace changes
martyn-w Jan 9, 2025
eccb8dc
rollback whitespace changes
martyn-w Jan 9, 2025
9de1bbd
Create ContactChannelCreationValidatorTests.cs
martyn-w Jan 9, 2025
1ded50e
add tests
martyn-w Jan 9, 2025
3a44630
add more tests
martyn-w Jan 9, 2025
15f191c
add broken test for ContactChannelCreation
martyn-w Jan 9, 2025
95275ce
Corrected verification on Upsert_WithContactChannelCreation_SavesCont…
spencerohegartyDfE Jan 13, 2025
5c2c969
tweak logic for CreationChannel field
martyn-w Jan 13, 2025
c1a9d1d
Small refactor to add AddCandidateCreationChannel() method
martyn-w Jan 14, 2025
e1256dc
add creation channel to mailing list endpoint
martyn-w Jan 14, 2025
8de1d5a
Merge pull request #1495 from DFE-Digital/add-mailing-list-creation-c…
martyn-w Jan 16, 2025
8ab448e
add creation channels to remaining endpoints
martyn-w Jan 17, 2025
2702912
Made changes to force code reuse and keep SonarQube happy
spencerohegartyDfE Jan 20, 2025
2ae66ef
Removed ContactChannelCreations object which encapsulated list of Con…
spencerohegartyDfE Jan 22, 2025
72789e3
Merge pull request #1496 from DFE-Digital/add-remaining-creation-chan…
martyn-w Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions GetIntoTeachingApi/Controllers/PickListItemsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,41 @@ public async Task<IActionResult> 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<PickListItem>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetContactCreationChannelSources()
{
return Ok(await _store.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelsource").ToListAsync());
spanersoraferty marked this conversation as resolved.
Show resolved Hide resolved
}

[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<PickListItem>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<PickListItem>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetContactCreationChannelActivities()
{
return Ok(await _store.GetPickListItems("dfe_contactchannelcreation", "dfe_creationchannelactivities").ToListAsync());
}
}
}
71 changes: 62 additions & 9 deletions GetIntoTeachingApi/Models/Crm/Candidate.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -297,15 +297,14 @@ public enum RegistrationStatus
public CandidatePrivacyPolicy PrivacyPolicy { get; set; }
[EntityRelationship("dfe_contact_dfe_candidateschoolexperience_ContactId", typeof(CandidateSchoolExperience))]
public List<CandidateSchoolExperience> SchoolExperiences { get; set; } = new List<CandidateSchoolExperience>();
[EntityRelationship("dfe_contact_dfe_contactchannelcreation_ContactId", typeof(ContactChannelCreation))]
public List<ContactChannelCreation> ContactChannelCreations { get; set; } = new List<ContactChannelCreation>();
spanersoraferty marked this conversation as resolved.
Show resolved Hide resolved

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()
Expand Down Expand Up @@ -333,6 +332,60 @@ public bool IsPlanningToRetakeGcseMathsAndEnglish()
return new[] { PlanningToRetakeGcseMathsId, PlanningToRetakeGcseEnglishId }.All(g => g == (int)GcseStatus.HasOrIsPlanningOnRetaking);
}

/// <summary>
/// Allows the configuration of a contact channel for a
/// candidate based on the provided contactChannelCreator.
/// </summary>
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);
}
}
}

/// <summary>
/// Allows automatic creation of an <see cref="ContactChannelCreation"/>
/// and assigns to the underlying collection of contact channel creations.
/// </summary>
/// <param name="channelCreation">
/// Predicates the context of the contact channel creation based on the candidate status.
/// </param>
/// <param name="contactChannelCreator">
/// The source of the contact channel creation request, a type
/// which implements the <see cref="ICreateContactChannel"/> interface.
/// </param>
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;
}
Expand Down
121 changes: 121 additions & 0 deletions GetIntoTeachingApi/Models/Crm/ContactChannelCreation.cs
Original file line number Diff line number Diff line change
@@ -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,
spanersoraferty marked this conversation as resolved.
Show resolved Hide resolved
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){
}

/// <summary>
/// Factory method for creating a ContactChannelCreation instance.
/// </summary>
/// <returns>
/// A configured <see cref="ContactChannelCreation"/> instance.
/// </returns>
public static ContactChannelCreation Create(
bool creationChannel,
int? sourceId,
int? serviceId,
int? activityId) => new()
{
CreationChannel = creationChannel,
CreationChannelSourceId = sourceId,
CreationChannelServiceId = serviceId,
CreationChannelActivityId = activityId,
};
}
}
29 changes: 29 additions & 0 deletions GetIntoTeachingApi/Models/Crm/ICreateContactChannel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace GetIntoTeachingApi.Models.Crm
{
/// <summary>
/// Interface that defines the contract on which objects
/// that wish to invoke contact channel creation behaviour must adhere.
/// </summary>
public interface ICreateContactChannel
{
/// <summary>
/// Provides the default read-only contact creation channel integer value.
/// </summary>
int? DefaultContactCreationChannel { get; }

/// <summary>
/// Provides the ability to assign and retrieve the channel source creation identifier.
/// </summary>
int? CreationChannelSourceId { get; set; }

/// <summary>
/// Provides the ability to assign and retrieve the channel service creation identifier.
/// </summary>
int? CreationChannelServiceId { get; set; }

/// <summary>
/// Provides the ability to assign and retrieve the channel activity creation identifier.
/// </summary>
int? CreationChannelActivityId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Candidate>("contact", "dfe_channelcreation", store));
RuleFor(candidate => candidate.HasGcseEnglishId)
.SetValidator(new PickListItemIdValidator<Candidate>("contact", "dfe_websitehasgcseenglish", store))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using FluentValidation;
using GetIntoTeachingApi.Services;
using GetIntoTeachingApi.Validators;

namespace GetIntoTeachingApi.Models.Crm.Validators
{
public class ContactChannelCreationValidator : AbstractValidator<ContactChannelCreation>
{
public ContactChannelCreationValidator(IStore store)
{
RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelSourceId)
.SetValidator(new PickListItemIdValidator<ContactChannelCreation>("dfe_contactchannelcreation", "dfe_creationchannelsource", store));
RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelServiceId)
.SetValidator(new PickListItemIdValidator<ContactChannelCreation>("dfe_contactchannelcreation", "dfe_creationchannelservice", store));
RuleFor(contactChannelCreation => contactChannelCreation.CreationChannelActivityId)
.SetValidator(new PickListItemIdValidator<ContactChannelCreation>("dfe_contactchannelcreation", "dfe_creationchannelactivities", store));
}
}
}
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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)
Expand Down Expand Up @@ -55,8 +63,7 @@ private Candidate CreateCandidate()
LastName = LastName,
AddressTelephone = AddressTelephone,
};

ConfigureChannel(candidate);
candidate.ConfigureChannel(contactChannelCreator: this, candidateId: CandidateId);
SchedulePhoneCall(candidate);
AcceptPrivacyPolicy(candidate);

Expand All @@ -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)
Expand Down
Loading
Loading