Skip to content

Commit

Permalink
Merge pull request #1494 from DFE-Digital/add-creation-channel-model
Browse files Browse the repository at this point in the history
Add creation channel model and TTA endppoint update
  • Loading branch information
martyn-w authored Jan 28, 2025
2 parents 3337237 + 72789e3 commit f650c04
Show file tree
Hide file tree
Showing 28 changed files with 744 additions and 95 deletions.
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());
}

[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>();

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,
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

0 comments on commit f650c04

Please sign in to comment.