first commit

This commit is contained in:
Zeeshaun
2026-03-11 11:18:25 -05:00
commit f3cb730f3b
472 changed files with 53651 additions and 0 deletions
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<DotNetCliToolReference Include="dotnet-svcutil" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.3.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="SharpCompress" Version="0.29.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.ServiceModel.Http" Version="8.1.2" />
<PackageReference Include="System.ServiceModel.Primitives" Version="8.1.2" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Landmark.OLA.Models\Landmark.OLA.Models.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>
@@ -0,0 +1,33 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Common.Builders
{
public abstract class BuilderBase : IBuildable
{
public readonly XNamespace CrifRequestSectionNamespace;
private protected CrifSettings _settings;
public BuilderBase(XNamespace mainSectionNamespace, CrifSettings settings)
{
CrifRequestSectionNamespace = mainSectionNamespace;
_settings = settings;
}
public virtual XElement Build(params XElement[] content)
{
return new XElement(CrifNamespaces.Empty + "actionAPI",
new XAttribute("version", 1),
new XAttribute("xmlns", ""),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.Common.Prefix, CrifNamespaces.Common.ToString()),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.App.Prefix, CrifNamespaces.App.ToString()),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.Messages.Prefix, CrifNamespaces.Messages.ToString()),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.QO.Prefix, CrifNamespaces.QO.ToString()),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.Maint.Prefix, CrifNamespaces.Maint.ToString()),
new XAttribute(XNamespace.Xmlns + CrifNamespaces.XSI.Prefix, CrifNamespaces.XSI.ToString()),
content);
}
}
}
@@ -0,0 +1,10 @@
namespace CRIF.ACTion.Common.Builders
{
public class BuilderUtils
{
public static string FormatCrifData(string data)
{
return data?.ToUpper();
}
}
}
@@ -0,0 +1,44 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Common.Builders
{
public class CrifRequestSection : IBuildable
{
private readonly XNamespace SectionNamespace;
private readonly string SectionName;
private readonly List<XObject> Content;
private readonly List<XObject> Attributes;
private protected readonly CrifSettings _settings;
private protected CrifRequestSection(XNamespace sectionNamespace, string sectionName, CrifSettings settings)
{
SectionNamespace = sectionNamespace;
SectionName = sectionName;
Content = [];
Attributes = [];
_settings = settings;
}
public XElement Build(params XElement[] content)
{
var element = new XElement(SectionNamespace + SectionName, Attributes);
element.Add([.. Content]);
return element;
}
public void AddContent(XObject element)
{
Content.Add(element);
}
public void AddAttribute(XObject attribute)
{
Attributes.Add(attribute);
}
}
}
@@ -0,0 +1,9 @@
using System.Xml.Linq;
namespace CRIF.ACTion.Common.Builders
{
public interface IBuildable
{
XElement Build(params XElement[] content);
}
}
@@ -0,0 +1,107 @@
using Landmark.OLA.Models.Individuals.Enums;
namespace CRIF.ACTion.Common
{
public static class ContactMechanisms
{
public static CrifContactMechanism GetPhoneType(PhoneType type)
{
switch (type)
{
case PhoneType.Home:
return CrifContactMechanism.HomePhone;
case PhoneType.Mobile:
return CrifContactMechanism.CellPhone;
default:
throw new ArgumentOutOfRangeException(nameof(type), "Unexpected PhoneType value");
}
}
public static ContactMechanismCategory GetContactMechanismCategory(CrifContactMechanism mechanism)
{
switch (mechanism)
{
case CrifContactMechanism.HomePhone:
case CrifContactMechanism.WorkPhone:
case CrifContactMechanism.CellPhone:
case CrifContactMechanism.Fax:
return ContactMechanismCategory.Phone;
case CrifContactMechanism.EMail:
case CrifContactMechanism.URL:
return ContactMechanismCategory.EmailOrUrl;
case CrifContactMechanism.CurrentAddress:
case CrifContactMechanism.MailingAddress:
case CrifContactMechanism.PreviousAddress:
case CrifContactMechanism.RealEstateAddress:
case CrifContactMechanism.ParkContactAddress:
return ContactMechanismCategory.Address;
case CrifContactMechanism.NoneSelected:
default:
throw new ArgumentOutOfRangeException(nameof(mechanism), "ContactMechanismCategory not determined");
}
}
/// <summary>
/// ContactMechanismTypeEnum values from CRIF's commonSchema.xsd
/// </summary>
public enum CrifContactMechanism
{
NoneSelected,
HomePhone,
WorkPhone,
CellPhone,
Fax,
EMail,
CurrentAddress,
URL,
IM,
DriversLicenceState,
MailingAddress,
PreviousAddress,
ContractAddress,
LandlordPostalAddress,
LandlordPhone,
LandlordFax,
InventoryAddress,
AttorneyPostalAddress,
AttorneyPhone,
AttorneyFax,
AttorneyEMailAddress,
CPAPostalAddress,
CPAPhone,
CPAFax,
CPAEMailAddress,
VendorOFACEmail,
RealEstateAddress,
ParkContactAddress,
ParkPhone,
MarinaAddress,
MarinaStorageAddress,
InsurerPostalAddress,
InsurerPhone,
InsurerFax,
SellerPostalAddress,
SellerPhone,
SellerFax,
TitleClerkFax,
Pager,
AdHocPhone,
AdHocFax,
AdHocEMail,
AdHocAddress,
StatementPostalAddress,
BillingPostalAddress,
PropertyContactPhone,
}
/// <summary>
/// Per CRIF Data Mapping document
/// </summary>
public enum ContactMechanismCategory
{
Phone = 1,
Address = 2,
EmailOrUrl = 3,
}
}
}
@@ -0,0 +1,70 @@
using System.Xml;
using System.Xml.Linq;
namespace CRIF.ACTion.Common
{
public class CrifNamespaces
{
public static CrifNamespace App => new("App");
public static CrifNamespace Common => new("Common");
public static CrifNamespace Maint => new("Maint");
public static CrifNamespace Messages => new("msg", "Messages");
public static CrifNamespace QO => new("QO");
public static CrifNamespace Util => new("Util");
public static XNamespace Empty => "";
public static CrifNamespace XSI => new("xsi", "http://www.w3.org/2001/XMLSchema-instance");
public static XmlNamespaceManager GetNamespaceManager(XElement xElement)
{
var namespaceManager = new XmlNamespaceManager(xElement.CreateReader().NameTable);
namespaceManager.AddNamespace(Messages.Prefix, Messages.Namespace.ToString());
namespaceManager.AddNamespace(App.Prefix, App.Namespace.ToString());
namespaceManager.AddNamespace(Maint.Prefix, Maint.Namespace.ToString());
namespaceManager.AddNamespace(Util.Prefix, Util.Namespace.ToString());
namespaceManager.AddNamespace(Common.Prefix, Common.Namespace.ToString());
return namespaceManager;
}
}
public class CrifNamespace
{
public XNamespace Namespace { get; private set; }
public string Prefix { get; private set; }
public CrifNamespace(string prefix, XNamespace namespaceValue)
{
Prefix = prefix;
Namespace = namespaceValue;
}
public CrifNamespace(XNamespace namespaceValue)
{
Prefix = namespaceValue.ToString().ToLower();
Namespace = namespaceValue;
}
public override string ToString()
{
return Namespace.ToString();
}
public static implicit operator XNamespace(CrifNamespace crifNamespace)
{
return crifNamespace.Namespace;
}
public static XName operator +(CrifNamespace crifNamespace, string localName)
{
return crifNamespace.Namespace + localName;
}
}
}
@@ -0,0 +1,16 @@
namespace CRIF.ACTion.Common
{
public static class Dates
{
public static string GetFormattedDOB(DateTime dob)
{
return dob.ToString("yyyy-MM-dd");
}
public static string GetFormattedApplicationDate(DateTime applicationDate)
{
return applicationDate.ToUniversalTime().ToString("o");
}
}
}
@@ -0,0 +1,19 @@
using System.Xml;
using System.Xml.Linq;
namespace CRIF.ACTion.Common
{
public interface ICrifNamespaces
{
XNamespace App { get; }
XNamespace Common { get; }
XNamespace Maint { get; }
XNamespace Messages { get; }
XNamespace QO { get; }
XNamespace Util { get; }
XNamespace Empty { get; }
XmlNamespaceManager GetNamespaceManager(XElement xElement);
}
}
@@ -0,0 +1,9 @@
using System.Xml.Linq;
namespace CRIF.ACTion.Common.Parsers
{
public interface IResponseParser<T>
{
T Parse(XElement xElement);
}
}
@@ -0,0 +1,34 @@
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
namespace CRIF.ACTion.Common.Parsers
{
public static class XElementExtensions
{
/*
Using XPathSelectElement on an XElement starts with the XElement's parent XDocument,
unless you convert the XElement itself to an XDocument
*/
public static XElement XElementXPathSelectElement(this XElement node, string expression)
{
return (new XDocument(node)).XPathSelectElement(expression);
}
public static XElement XElementXPathSelectElement(this XNode node, string expression, IXmlNamespaceResolver resolver)
{
return (new XDocument(node)).XPathSelectElement(expression, resolver);
}
public static IEnumerable<XElement> XElementXPathSelectElements(this XElement node, string expression)
{
return (new XDocument(node)).XPathSelectElements(expression);
}
public static IEnumerable<XElement> XElementXPathSelectElements(this XElement node, string expression, IXmlNamespaceResolver resolver)
{
return (new XDocument(node)).XPathSelectElements(expression, resolver);
}
}
}
@@ -0,0 +1,24 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
namespace CRIF.ACTion.Configuration
{
public class ConfigurationRequestBuilder : BuilderBase
{
private readonly string SettingName;
public ConfigurationRequestBuilder(string settingName, CrifSettings settings)
: base(CrifNamespaces.Empty, settings)
{
SettingName = settingName;
}
public override XElement Build(params XElement[] content)
{
var retrieveCriffConfigurationElement = new XElement(CrifNamespaces.Empty + SettingName);
return base.Build(retrieveCriffConfigurationElement);
}
}
}
@@ -0,0 +1,78 @@
using System.Xml.Linq;
using Serilog;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Parsers;
namespace CRIF.ACTion.Configuration
{
public class ConfigurationRequestResponseParser : IResponseParser<SortedList<int, string>>
{
private const string _channelsPath = "/actionAPI/getChannels/msg:maintExportResponse/msg:maintenanceEntities/maint:parties/common:channel/common:channelClassifications/common:channelClassification/common:channelClassificationItem";
public ConfigurationRequestResponseParser()
{
}
public SortedList<int, string> Parse(XElement xElement)
{
SortedList<int, string> mappingList = [];
try
{
var namespaceManager = CrifNamespaces.GetNamespaceManager(xElement);
IEnumerable<XElement> channelNodeList = xElement.XElementXPathSelectElements(_channelsPath, namespaceManager);
foreach (XElement channelNode in channelNodeList)
{
try
{
string nodeAttributeValue = StandardizedStringValue(channelNode.Attribute(CrifNamespaces.Common + "name").Value);
if (ChannelIsActive(nodeAttributeValue))
{
string partyIdString = channelNode.Parent.Parent.Parent.Attribute(CrifNamespaces.Common + "partyID").Value;
string name = channelNode.Parent.Parent.Parent.Attribute(CrifNamespaces.Common + "name").Value;
_ = int.TryParse(partyIdString, out int partyId);
mappingList.Add(partyId, name);
}
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF BranchName Configuration Response", ex.Message, ex.StackTrace);
}
}
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF BranchName Configuration Response", ex.Message, ex.StackTrace);
}
return mappingList;
}
private static bool ChannelIsActive(string nodeAttributeValue)
{
bool channelIsActive = false;
if (!string.IsNullOrWhiteSpace(nodeAttributeValue))
{
channelIsActive =
(
nodeAttributeValue.Equals("BRANCH") ||
nodeAttributeValue.Equals("INTERNET") ||
nodeAttributeValue.Equals("ON-LINE")
);
}
return channelIsActive;
}
private static string StandardizedStringValue(string InputString)
{
string standardizedString = "";
if (!string.IsNullOrWhiteSpace(InputString))
{
standardizedString = InputString.Trim().ToUpper();
}
return standardizedString;
}
}
}
@@ -0,0 +1,86 @@
namespace CRIF.ACTion
{
/************
This file should be migrated to appsettings.json and the CrifSettings class
*************/
public class CrifConstants
{
public static TimeSpan SubmissionTimeoutInMinutes => new(0, 2, 0);
public static int EQ_ListedForSale_Field_Id = -110;
public static int EQ_ListedForSale_Field_True = 272;
public static int EQ_ListedForSale_Field_False = 271;
public static int EQ_CurrentlyBeingRemodeledOrImproved_Field_Id = -109;
public static int EQ_CurrentlyBeingRemodeledOrImproved_Field_True = 270;
public static int EQ_CurrentlyBeingRemodeledOrImproved_Field_False = 269;
public static int Borrower_IsApplicantNonSigningSpouse_Field_Id = -73;
public static int Borrower_IsApplicantNonSigningSpouse_Field_Borrower = 85;
public static int Borrower_IsApplicantNonSigningSpouse_Field_NonSigningMarriedToPrimary = 84;
public static int Borrower_IsApplicantNonSigningSpouse_Field_NonSigningMarriedToCo = 83;
public static int TCPA_Opt_Out_Field_Id = -112;
public static Dictionary<Collateral, int> CollateralIds = new Dictionary<Collateral, int>
{
//Take from CRIF database: select * from v_RptLuCollateralType_v001
{Collateral.Vehicle, 1},
{Collateral.ResidentialRealEstate, 2},
{Collateral.Marine, 3},
{Collateral.ManufacturedHome, 4},
{Collateral.RecreationalVehicle, 5},
{Collateral.Stocks, 6},
{Collateral.Bonds, 7},
{Collateral.Insurance, 8},
{Collateral.CDs, 9},
{Collateral.CDsOrSavings, 18},
{Collateral.Collateral401k, 10},
{Collateral.CommercialRealEstate, 11},
{Collateral.AccountsReceivable, 12},
{Collateral.SecuredOther, 13},
{Collateral.ManufacturedHomeAndLand, 14},
{Collateral.Motorcycle, 15},
{Collateral.ATV, 16},
{Collateral.Snowmobile, 17},
};
public static int SpecialHandlingCode_OneBorrower_with_NonapplicantSpouse = 8;
public static Dictionary<int, string> StipulationsToShow = new Dictionary<int, string>
{
//matches occur by int ID. Text is only used to easily identify stipulations.
{20, "A Personal Guaranty form is required for all business loans" },
{92, "Cards can only be issued to business owner and owner's spouse"},
{58, "Final Approval is dependent on Appraisal, Title, and Flood"},
{82, "Landmark Business savings account must be established."},
{85, "Must show proof borrower is a current student."},
{59, "Two years of income verification is required"},
};
public enum Collateral
{
//These enum int values have no relation to CRIF's int values. Use the dictionary above instead.
Vehicle,
ResidentialRealEstate,
Marine,
ManufacturedHome,
RecreationalVehicle,
Stocks,
Bonds,
Insurance,
CDs,
CDsOrSavings,
Collateral401k, //enums must start with a letter
CommercialRealEstate,
AccountsReceivable,
SecuredOther,
ManufacturedHomeAndLand,
Motorcycle,
ATV,
Snowmobile,
};
}
}
@@ -0,0 +1,106 @@
namespace CRIF.ACTion.Configuration
{
public class CrifSettings
{
public ApiConnectionSettings ApiSettings { get; set; }
public SourceChannelSettings SourceChannels { get; set; }
public ProductIdSettings ProductIds { get; set; }
public ServicingChannelSettings ServicingChannels { get; set; }
public CustomFieldMappings CustomFieldMappings { get; set; }
public CustomFieldConfigs CustomFields { get; set; }
public FormatCategorySettings FormatCategoryMappings { get; set; }
}
public class CustomJointCreditData
{
public string CustomFieldId { get; set; }
public string ProductCategoryId { get; set; }
public string SortOrder { get; set; }
public string IndividuallyId { get; set; }
public string JointlyId { get; set; }
}
public class CustomPropertyStatusData
{
public string CustomFieldId { get; set; }
public string ProductCategoryId { get; set; }
public string SortOrder { get; set; }
public string YesId { get; set; }
public string NoId { get; set; }
public string NaId { get; set; }
}
public class CustomFieldConfigs
{
public List<CustomPropertyStatusData> CustomPropertyRenovationFieldData { get; set; }
public List<CustomPropertyStatusData> CustomPropertySaleFieldData { get; set; }
public List<CustomJointCreditData> CustomJointCreditFieldData { get; set; }
public Dictionary<string, string> ProductIdToCategoryId { get; set; }
}
public class CustomFieldMappings
{
public Dictionary<string, string> LoanTypeToPromoId { get; set; }
public Dictionary<string, string> CustomFieldID { get; set; }
public Dictionary<string, string> MunicipalityTypeCode { get; set; }
}
public class CustomFieldIDMappings
{
public Dictionary<string, string> CustomFieldID { get; set; }
}
public class ApiConnectionSettings
{
public string EndpointUrl { get; set; }
public string Username { get; set; }
public int InterfaceId { get; set; }
public string OrgCode { get; set; }
public int OrgId { get; set; }
}
public class SourceChannelSettings
{
public int DefaultSourceChannelId { get; set; }
public int CreditCardSourceChannelId { get; set; }
}
public class ProductIdSettings
{
public int CC_CashBack { get; set; }
public int CC_Rewards { get; set; }
public int CC_Student { get; set; }
public int CC_Secure { get; set; }
public int EQ_HELOC_Flex { get; set; }
public int EQ_Home_Equity_Fixed { get; set; }
public int EQ_Mtg_Refi_Express { get; set; }
public int EQ_Mfg_Home_And_Land { get; set; }
public int V_Vehicle { get; set; }
public int V_VehiclePreapproval { get; set; }
public int M_Boat { get; set; }
public int P_Powersport { get; set; }
public int T_TrailerMotorhome { get; set; }
public int S_Signature { get; set; }
}
public class FormatCategorySettings
{
public Dictionary<string, int> Categories { get; set; }
public Dictionary<string, string> DocumentToCategory { get; set; }
}
public class ServicingChannelSettings
{
public int DefaultServicingChannel { get; set; }
}
}
@@ -0,0 +1,222 @@
using System.Xml.Linq;
using Serilog;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Parsers;
namespace CRIF.ACTion.Configuration
{
public class CustomFieldRequestResponseParser : IResponseParser<CustomFieldConfigs>
{
private const string _customFieldsPath = "/actionAPI/getCustomFields/msg:maintExportResponse/msg:maintenanceEntities/maint:maintenanceEntity/maint:fields/common:customLookupField";
public CustomFieldRequestResponseParser()
{
}
public CustomFieldConfigs Parse(XElement xElement)
{
CustomFieldConfigs customFieldConfig = new();
try
{
var namespaceManager = CrifNamespaces.GetNamespaceManager(xElement);
IEnumerable<XElement> customFieldNodeList = xElement.XElementXPathSelectElements(_customFieldsPath, namespaceManager);
List<string> RenovationMatchCriteria = ["IS THE PROPERTY CURRENTLY BEING REMODELED OR IMPROVED?"];
List<string> SaleMatchCriteria = ["IS THE PROPERTY CURRENTLY LISTED FOR SALE?"];
List<string> JointMatchCriteria = ["INTENT FOR JOINT CREDIT"];
customFieldConfig.CustomPropertyRenovationFieldData = ParseCustomPropertyStatusData(customFieldNodeList, RenovationMatchCriteria);
customFieldConfig.CustomPropertySaleFieldData = ParseCustomPropertyStatusData(customFieldNodeList, SaleMatchCriteria);
customFieldConfig.CustomJointCreditFieldData = ParseCustomJointCreditData(customFieldNodeList, JointMatchCriteria);
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF Custom Field Configuration Response", ex.Message, ex.StackTrace);
}
return customFieldConfig;
}
private List<CustomJointCreditData> ParseCustomJointCreditData(IEnumerable<XElement> customFieldNodeList, List<string> MatchCriteria)
{
List<CustomJointCreditData> customDataSet = [];
foreach (XElement node in customFieldNodeList)
{
try
{
string nodeAttributeValue = node.Attribute(CrifNamespaces.Common + "name").Value;
if (CustomFieldIsActive(nodeAttributeValue, MatchCriteria))
{
string individuallyId = "";
string jointlyId = "";
string productCategoryId = "";
IEnumerable<XElement> childNodes = node.Descendants().Elements();
foreach (XElement option in childNodes)
{
if (option.Name.LocalName.Equals("FIELDSELECTION", StringComparison.CurrentCultureIgnoreCase))
{
var attributes = option.Attributes();
foreach (var attribute in attributes)
{
string idValue = "";
string nameValue = "";
if (attribute.Name.LocalName.Equals("FIELDSELECTIONID", StringComparison.CurrentCultureIgnoreCase))
{
idValue = attribute.Value;
nameValue = attribute.NextAttribute.Value.ToUpper();
switch (nameValue)
{
case "INDIVIDUALLY":
individuallyId = idValue;
break;
case "JOINTLY":
jointlyId = idValue;
break;
default:
break;
}
}
}
}
else if (option.Name.LocalName.ToUpper() == "PRODUCTCATEGORY")
{
productCategoryId = option.LastAttribute.Value;
}
}
CustomJointCreditData data = new()
{
CustomFieldId = node.Attribute(CrifNamespaces.Common + "ID").Value,
ProductCategoryId = productCategoryId,
SortOrder = node.Attribute(CrifNamespaces.Common + "sortOrder").Value,
JointlyId = jointlyId,
IndividuallyId = individuallyId
};
customDataSet.Add(data);
}
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF Custom Field Configuration Response", ex.Message, ex.StackTrace);
}
}
return customDataSet;
}
private List<CustomPropertyStatusData> ParseCustomPropertyStatusData(IEnumerable<XElement> customFieldNodeList, List<string> MatchCriteria)
{
List<CustomPropertyStatusData> customDataSet = [];
foreach (XElement node in customFieldNodeList)
{
try
{
string nodeAttributeValue = node.Attribute(CrifNamespaces.Common + "name").Value;
if (CustomFieldIsActive(nodeAttributeValue, MatchCriteria))
{
string yesId = "";
string noId = "";
string naId = "";
string productCategoryId = "";
IEnumerable<XElement> childNodes = node.Descendants().Elements();
foreach (XElement option in childNodes)
{
if (option.Name.LocalName.Equals("FIELDSELECTION", StringComparison.CurrentCultureIgnoreCase))
{
var attributes = option.Attributes();
foreach (var attribute in attributes)
{
string idValue = "";
string nameValue = "";
if (attribute.Name.LocalName.ToUpper() == "FIELDSELECTIONID")
{
idValue = attribute.Value;
nameValue = attribute.NextAttribute.Value.ToUpper();
switch (nameValue)
{
case "YES":
yesId = idValue;
break;
case "NO":
noId = idValue;
break;
case "N/A (FLEX FIXED DRAWS ONLY)":
naId = idValue;
break;
default:
break;
}
}
}
}
else if (option.Name.LocalName.ToUpper() == "PRODUCTCATEGORY")
{
productCategoryId = option.LastAttribute.Value;
}
}
CustomPropertyStatusData data = new CustomPropertyStatusData
{
CustomFieldId = node.Attribute(CrifNamespaces.Common + "ID").Value,
ProductCategoryId = productCategoryId,
SortOrder = node.Attribute(CrifNamespaces.Common + "sortOrder").Value,
NoId = noId,
YesId = yesId,
NaId = naId
};
customDataSet.Add(data);
}
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF Custom Field Configuration Response", ex.Message, ex.StackTrace);
}
}
return customDataSet;
}
private bool CustomFieldIsActive(string NodeAttributeValue, List<string> ActiveMatchCriteria)
{
bool isActive = false;
foreach (string compare in ActiveMatchCriteria)
{
string standardizedNodeString = StandardizedStringValue(NodeAttributeValue);
string standardizedCompareString = StandardizedStringValue(compare);
bool validCompareInput = !string.IsNullOrWhiteSpace(standardizedNodeString) && !string.IsNullOrWhiteSpace(compare);
if (validCompareInput && !isActive)
{
isActive =
(
standardizedNodeString.Equals(standardizedCompareString)
);
}
}
return isActive;
}
private string StandardizedStringValue(string InputString)
{
string standardizedString = "";
if (!string.IsNullOrWhiteSpace(InputString))
{
standardizedString = InputString.Trim().ToUpper();
}
return standardizedString;
}
}
}
@@ -0,0 +1,47 @@
using System.Xml.Linq;
using Serilog;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Parsers;
namespace CRIF.ACTion.Configuration
{
public class ProductDetailsResponseParser : IResponseParser<Dictionary<string, string>>
{
private const string _productDetailsPath = "/actionAPI/getProducts/msg:maintExportResponse/msg:maintenanceEntities/maint:maintenanceEntity/maint:products/common:product/common:productCategory";
public ProductDetailsResponseParser()
{
}
public Dictionary<string, string> Parse(XElement xElement)
{
Dictionary<string, string> mappingList = [];
try
{
var namespaceManager = CrifNamespaces.GetNamespaceManager(xElement);
IEnumerable<XElement> channelNodeList = xElement.XElementXPathSelectElements(_productDetailsPath, namespaceManager);
foreach (XElement channelNode in channelNodeList)
{
try
{
string productId = channelNode.Parent.Attribute(CrifNamespaces.Common + "productID").Value;
string categoryId = channelNode.Attribute(CrifNamespaces.Common + "productCategoryID").Value;
mappingList.Add(productId, categoryId);
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF Product information from node in Configuration Response", ex.Message, ex.StackTrace);
}
}
}
catch (Exception ex)
{
Log.Error("Error trying to parse CRIF Products Configuration Response", ex.Message, ex.StackTrace);
}
return mappingList;
}
}
}
@@ -0,0 +1,29 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Decisioning
{
public class DecisionStatusBuilder : BuilderBase
{
private readonly string ApplicationId;
private readonly string AppDetailId;
public DecisionStatusBuilder(string applicationId, string appDetailId, CrifSettings settings)
: base(CrifNamespaces.Empty, settings)
{
ApplicationId = applicationId;
AppDetailId = appDetailId;
}
public override XElement Build(params XElement[] content)
{
var getDecisioningElement = new XElement(CrifNamespaces.Empty + "getDecisionStatus",
new XAttribute(CrifNamespaces.Empty + "applicationID", ApplicationId),
new XAttribute(CrifNamespaces.Empty + "appDetailID", AppDetailId));
return base.Build(getDecisioningElement);
}
}
}
@@ -0,0 +1,47 @@
using System.Xml.Linq;
using Serilog;
using CRIF.ACTion.Common.Parsers;
using Landmark.OLA.Models.Decisioning.Enums;
namespace CRIF.ACTion.Decisioning
{
public class DecisioningStateResponseParser : IResponseParser<DecisioningState>
{
private const string SuccessMessage = "Success";
private const string MissingRequredInformation = "WorkFlowBC::SendToWorkflow::(Decisioning) Process Instances not in correct state Please correct the Process Errors.";
private string ExpectedLoanId;
public DecisioningStateResponseParser()
{
}
public void SetLoanId(string expectedLoanId)
{
ExpectedLoanId = expectedLoanId;
}
public DecisioningState Parse(XElement xElement)
{
if (string.IsNullOrWhiteSpace(ExpectedLoanId))
{
throw new ArgumentException("Must set valid ExpectedLoanId with SetLoanId(expectedLoanId)");
}
if (xElement.Value.Equals(SuccessMessage + ExpectedLoanId))
{
return DecisioningState.Requested;
}
if (xElement.Value.Equals(MissingRequredInformation))
{
Log.Debug("Application {loanId} is likely missing information required for decisioning.", SuccessMessage, ExpectedLoanId);
return DecisioningState.MissingRequiredInformation;
}
else
{
Log.Debug("Did not receive {SuccessMessage} for a SendToDecisioning call for Application {loanId}.", SuccessMessage, ExpectedLoanId);
return DecisioningState.Unknown;
}
}
}
}
@@ -0,0 +1,26 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Decisioning
{
public class GetApplicationProductsRequestBuilder : BuilderBase
{
private readonly string ApplicationId;
public GetApplicationProductsRequestBuilder(string applicationId, CrifSettings settings)
: base(CrifNamespaces.Empty, settings)
{
ApplicationId = applicationId;
}
public override XElement Build(params XElement[] content)
{
var sendToDecisioningElement = new XElement(CrifNamespaces.Empty + "getApplicationProducts",
new XAttribute(CrifNamespaces.Empty + "applicationID", ApplicationId));
return base.Build(sendToDecisioningElement);
}
}
}
@@ -0,0 +1,165 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Parsers;
using Serilog;
using Landmark.OLA.Models.Decisioning;
using Landmark.OLA.Models.Decisioning.Enums;
namespace CRIF.ACTion.Decisioning
{
public class GetApplicationProductsResponseParser : IResponseParser<DecisioningDetails>
{
private string LoanApplicationId;
private const string ApplicationsPath = "/actionAPI/getApplicationProducts/msg:appExportResponse/msg:applications/app:application";
private const string AppDetailsPath = "/app:application/app:appDetails";
private readonly XName AppDetailStatusesName = CrifNamespaces.App + "appDetailStatuses";
private readonly XName AppDetailStatusName = CrifNamespaces.App + "appDetailStatus";
private readonly XName StatusAttribute = CrifNamespaces.App + "statusType";
private const string DecisionTypeValue = "DecisionStatus";
private readonly XName DecisionResultName = CrifNamespaces.App + "status";
private readonly XName AppDetailStipulationsName = CrifNamespaces.App + "appDetailStipulations";
private readonly XName StipulationName = CrifNamespaces.App + "stipulation";
private readonly XName StipulationIdName = CrifNamespaces.Common + "stipulationID";
public GetApplicationProductsResponseParser()
{
}
public void SetLoanId(string loanApplicationId)
{
LoanApplicationId = loanApplicationId;
}
public DecisioningDetails Parse(XElement xElement)
{
if (string.IsNullOrWhiteSpace(LoanApplicationId))
{
throw new ArgumentException("Must set valid LoanApplicationId with SetLoanId(loanApplicationId)");
}
var namespaceManager = CrifNamespaces.GetNamespaceManager(xElement);
var applicationElement = xElement.XElementXPathSelectElement(ApplicationsPath, namespaceManager);
if (applicationElement == null)
{
//CRIF didn't recognize this application ID
throw new ArgumentException("The Application Id was not recognized by the decisioning system.");
}
if (!applicationElement.Attribute(CrifNamespaces.App + "applicationID").Value.Equals(LoanApplicationId))
{
Log.Fatal("CRIF XML does not match expected loan ID {loanId}", LoanApplicationId);
throw new InvalidOperationException("Unexpected vendor response.");
}
var appDetailElements = applicationElement.XElementXPathSelectElement(AppDetailsPath, namespaceManager).Elements().ToList();
for (int detailIndex = appDetailElements.Count - 1; detailIndex >= 0; detailIndex--)
{
var detail = appDetailElements[detailIndex];
var statuses = GetElementFromElement(detail, AppDetailStatusesName);
var decision = GetElementFromElementWithAttributeIdentifier(statuses, AppDetailStatusName, StatusAttribute, DecisionTypeValue);
if (decision == null)
{
appDetailElements.Remove(detail);
continue;
}
var decisionResult = GetAttributeFromElement(decision, DecisionResultName);
var status = (DecisioningResult)Enum.Parse(typeof(DecisioningResult), decisionResult.Value);
if (status == DecisioningResult.DecisionStatus_Cancelled)
{
appDetailElements.Remove(detail);
}
}
if (appDetailElements.Count != 1)
{
if (appDetailElements.Count == 0)
{
return new DecisioningDetails()
{
DecisioningState = DecisioningState.Unknown
};
}
throw new ArgumentException($"Application contains {appDetailElements.Count} appDetail elements. An appDetailID is required.");
}
var appDetailStatuses = GetElementFromElement(appDetailElements.SingleOrDefault(), AppDetailStatusesName);
var decisionStatus = GetElementFromElementWithAttributeIdentifier(appDetailStatuses, AppDetailStatusName, StatusAttribute, DecisionTypeValue);
var stipulations = GetStipulations(appDetailElements.Single().Element(AppDetailStipulationsName));
if (decisionStatus == null)
{
return new DecisioningDetails(stipulations)
{
DecisioningState = DecisioningState.Unknown,
};
}
else
{
var decisioningResult = GetAttributeFromElement(decisionStatus, DecisionResultName);
var statusResult = (DecisioningResult)Enum.Parse(typeof(DecisioningResult), decisioningResult.Value);
return new DecisioningDetails(stipulations)
{
DecisioningState = DecisioningState.Ready,
DecisioningResult = statusResult,
};
}
}
private ICollection<int> GetStipulations(XElement appDetailStipulations)
{
var stipulations = new List<int>();
if (appDetailStipulations is null)
{
return stipulations;
}
foreach (var stipulationElement in appDetailStipulations.Elements())
{
var appDetailStipulation = stipulationElement.Element(StipulationName);
var stipulationIdString = appDetailStipulation.Attribute(StipulationIdName).Value;
if (int.TryParse(stipulationIdString, out int stipulationId))
{
if (CrifConstants.StipulationsToShow.ContainsKey(stipulationId))
{
stipulations.Add(stipulationId);
}
}
}
return stipulations;
}
private static XElement GetElementFromElement(XElement element, XName elementToFind)
{
return element.Element(elementToFind);
}
private static XElement GetElementFromElementWithAttributeIdentifier(XElement element, XName elementToSearch, XName attribute, string identifier)
{
return element.Elements(elementToSearch).SingleOrDefault(x => x.Attribute(attribute).Value.Equals(identifier));
}
private static XAttribute GetAttributeFromElement(XElement element, XName attribute)
{
return element.Attribute(attribute);
}
}
}
@@ -0,0 +1,26 @@
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using System.Xml.Linq;
namespace CRIF.ACTion.Decisioning
{
public class SendToDecisioningRequestBuilder : BuilderBase
{
private readonly string ApplicationId;
public SendToDecisioningRequestBuilder(string applicationId, CrifSettings settings)
: base(CrifNamespaces.Empty, settings)
{
ApplicationId = applicationId;
}
public override XElement Build(params XElement[] content)
{
var sendToDecisioningElement = new XElement(CrifNamespaces.Empty + "sendToDecisioning",
new XAttribute(CrifNamespaces.Empty + "applicationID", ApplicationId));
return base.Build(sendToDecisioningElement);
}
}
}
@@ -0,0 +1,32 @@
# CRIF API Risks and Issues
*Some of this content is copied from NVISIA's API Risk Analysis document.*
## Important Considerations
* The API WSDLs (description files) point to non-secure HTTP endpoint URLs. These URLs should not be used.
* CRIF can enable IP address whitelisting upon request. NVISIA recommends that this be used, and it is currently enabled in the production environment.
* The XML any-typed body fields mean that changes on CRIF's end cannot be automatically detected or updated via the API WSDL. Maintaining data models may require a large amount of manual effort as the vendor was unfamiliar with any code generation process.
* CRIF states that the “ACTion API only supports the Transactions, Elements, and Fields as described in [the documentation]”. Certain transactions or fields desired by Landmark Credit Union may not be available.
* Documentation is spread throughout a few places, including CRIF’s in-progress data mapping document. Documentation has been missing or wrong in some cases.
## Known Issues
* The XML formatting of the UAT system’s error responses is different than described in CRIF’s documentation.
* State abbreviations in addresses are validated against `StateEnum` in *commonSchema.xsd* and must also match a listing in the `v_RptGeoCode_v001` table. As of September 2018 `FM` and `MH` are listed in the XSD but are not listed in the database. Addresses submitted with either of those values results in a foreign key SQL error from CRIF. `FM` and `MH` are also available in the ACTion web interface and will prevent the loan from being saved.
* The CRIF update deployed to Landmark's UAT environment around July 23rd contains a bug that prevents most, if not all, applications from being saved due to an 'IX_PartyRelationship_ParentPartyID_BeginDate_ChildPartyID_ChildBeginDate_RelationshipTypeID' SQL error. CRIF has a fix that has now been applied to the UAT and Production environments.
* Not all custom field types can be written to, and the documentation is wrong in this regard. Product-level and borrower-level fields are supported. Application-level and collateral-level fields are not currently supported.
* The UAT environment does not accept all of the API calls defined in CRIF's reference XSDs (*addIndividualApplicant*, for example, is not supported).
* Many of the calls used to edit existing loans do not work as expected even when accepted by the server:
* *addApplicant* calls appear to prevent the updated loan from being opened in the ACTion web application as an IIS error screen appears instead.
* *addIndividual* calls do not save martial status or anything sent in the *trades* element.
* *updateApp* does not appear to be able to save relationship data (that is, indicating that two parties are married to each other), though the call is accepted by the server.
* *updateApp* calls adding individuals to a loan results in a SQL error response from the API server
## Other Risks
* The CRIF API server does respond over HTTP (port 80) but appears to only respond with system data over HTTPS.
* CRIF has informed us that the API layout and definition is generally stable from a development standpoint but there will be changes made as the underlying data models for the core applications change over time. These changes do not seem to correlate to ACTion version numbers.
* Mismatches between API-required fields, business-required fields, CRIF-required fields, and documented fields can complicate data submission.
* Custom fields, such as channels (that is, LCU branches), may have different IDs in each environment/version. The UAT environment may especially change over time when it is refreshed from the production environment.
* Submitting a loan that references a disabled custom field will result in a loan that cannot be view in the ACTion web application.
@@ -0,0 +1,82 @@
using System.Xml.Linq;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Files
{
public class AddAppFilesSection : CrifRequestSection
{
public AddAppFilesSection(CrifSettings settings) : base(CrifNamespaces.Empty, "addAppFiles", settings)
{}
public virtual void SetLoanId(string loanId)
{
AddAttribute(new XAttribute("applicationID", loanId));
}
public virtual void SetFile(LoanApplicationFile file, string fileBase64Value)
{
XAttribute description;
if (file.Section?.Length > 0) {
description = new XAttribute(CrifNamespaces.Common + "description", $"{file.Section} - {file.SectionId}: {file.Description}");
} else {
description = new XAttribute(CrifNamespaces.Common + "description", $"{file.SectionId}: {file.Description}");
}
var addAppFilesElement = new XElement(CrifNamespaces.Common + "file",
new XAttribute(CrifNamespaces.Common + "id", file.FileId),
new XAttribute(CrifNamespaces.Common + "name", file.Name),
description,
new XAttribute(CrifNamespaces.Common + "size", CalculateFileSize(fileBase64Value)));
addAppFilesElement.Add(new XElement(CrifNamespaces.Common + "formatType", new XAttribute(CrifNamespaces.Common + "CD", GetFormatType())));
addAppFilesElement.Add(new XElement(CrifNamespaces.Common + "formatCategory", new XAttribute(CrifNamespaces.Common + "CD", GetFormatCategory(file.Description))));
var fileElement = new XElement(CrifNamespaces.Common + "file");
fileElement.SetValue(fileBase64Value);
addAppFilesElement.Add(fileElement);
AddContent(addAppFilesElement);
}
public static string CalculateFileSize(string fileBase64Value)
{
int size = fileBase64Value.Length / 4 * 3;
if (fileBase64Value.EndsWith("=="))
{
size -= 2;
}
else if (fileBase64Value.EndsWith("="))
{
size -= 1;
}
return size.ToString();
}
public int GetFormatType()
{
// Always return 1 since it seems that there are no other types used. Creating this in case that ever changes.
return 1;
}
// Configurable in appsettings.json, defaults to 1
public int GetFormatCategory(string description)
{
try
{
var category = _settings.FormatCategoryMappings.DocumentToCategory.GetValueOrDefault(description, "Attachment");
return _settings.FormatCategoryMappings.Categories.GetValueOrDefault(category, 1);
} catch (Exception)
{
return 1;
}
}
}
}
@@ -0,0 +1,34 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Files
{
public class FileRequestBuilder : BuilderBase, IAddAppFiles
{
string _loanId;
LoanApplicationFile _file;
public AddAppFilesSection AddAppFiles { get; set; }
public string _fileBase64Value;
public FileRequestBuilder(string loanId, LoanApplicationFile file, string fileBase64Value, CrifSettings settings) : base(CrifNamespaces.Empty, settings)
{
AddAppFiles = new AddAppFilesSection(settings);
_loanId = loanId;
_file = file;
_fileBase64Value = fileBase64Value;
}
public override XElement Build(params XElement[] content)
{
AddAppFiles.SetLoanId(_loanId);
AddAppFiles.SetFile(_file, _fileBase64Value);
var baseElement = base.Build(AddAppFiles.Build());
return baseElement;
}
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Files
{
public interface IAddAppFiles
{
AddAppFilesSection AddAppFiles { get; }
}
}
@@ -0,0 +1,8 @@
namespace CRIF.ACTion.Files
{
public interface IFileRequestBuilder
{
void SetFile(FileRequestBuilder createApp);
}
}
@@ -0,0 +1,14 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class AppDetailBankcardSection : AppDetailsSection
{
public AppDetailBankcardSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "appDetailBankcard", settings)
{
}
}
}
@@ -0,0 +1,89 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Loan
{
public class AppDetailHelocSection : AppDetailsSection
{
private readonly List<XObject> _content;
private readonly XElement _fieldsElement;
public AppDetailHelocSection(XNamespace sectionNamespace, CrifSettings settings,
string interestRateType = null, string calculationType = null)
: base(sectionNamespace, "appDetailHELOC", settings, interestRateType, calculationType)
{
_content = [];
}
private protected AppDetailHelocSection(XNamespace sectionNamespace, CrifSettings settings,
string elementName, string interestRateType = null, string calculationType = null)
: base(sectionNamespace, elementName, settings, interestRateType, calculationType)
{
_content = [];
}
public virtual void AddPropertyDetails(RealEstateBase realEstateBase, bool? mfgHomeIncludesLand = null)
{
var appDetailPropertiesElement = new XElement(CrifNamespaces.App + "appDetailProperties");
var propertyType = "RealEstate";
var collateralType = CrifConstants.CollateralIds[CrifConstants.Collateral.ResidentialRealEstate];
string subType;
if (mfgHomeIncludesLand.HasValue)
{
propertyType = "MfgHomeAndLand";
collateralType = mfgHomeIncludesLand.Value ?
CrifConstants.CollateralIds[CrifConstants.Collateral.ManufacturedHomeAndLand] :
CrifConstants.CollateralIds[CrifConstants.Collateral.ManufacturedHome];
subType = ((ManufacturedHome)realEstateBase).Type.Value.ToString();
}
else
{
subType = ((RealEstate)realEstateBase).Type.Value.ToString();
}
var statusID = "Active";
var includeInLTVCalculations = true;
var appDetailPropertyElement = new XElement(CrifNamespaces.App + "appDetailProperty",
new XAttribute(CrifNamespaces.Common + "idRef", realEstateBase.GetOrCreateGuid()),
new XAttribute(CrifNamespaces.Common + "propertyType", propertyType),
new XAttribute(CrifNamespaces.Common + "propertySubType", subType),
new XAttribute(CrifNamespaces.App + "isCollateralFL", realEstateBase.IsCollateral),
new XAttribute(CrifNamespaces.App + "isTradeInFL", realEstateBase.IsTradeIn),
new XAttribute(CrifNamespaces.App + "statusID", statusID),
new XAttribute(CrifNamespaces.App + "includeInLTVCalculations", includeInLTVCalculations),
new XAttribute(CrifNamespaces.App + "collateralTypeID", collateralType)
);
appDetailPropertiesElement.Add(appDetailPropertyElement);
_content.Add(appDetailPropertiesElement);
}
public void AddHmdaDisclosed(bool? acceptedDisclosure)
{
if ((!acceptedDisclosure.HasValue) || !acceptedDisclosure.Value)
{
return;
}
var hmdaElement = new XElement(CrifNamespaces.App + "appDetailHMDA2018",
new XAttribute(CrifNamespaces.App + "isNoticeDisclosedFL", acceptedDisclosure.Value));
_content.Add(hmdaElement);
}
public override void SetDetails(LoanApplication loanApplication, Guid channelRef, object[] additionalContent = null, XElement fieldsElement = null, XElement vehicleValuationsElement = null)
{
base.SetDetails(loanApplication, channelRef, [.. _content], _fieldsElement);
}
}
}
@@ -0,0 +1,13 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class AppDetailSignatureSection : AppDetailsSection
{
public AppDetailSignatureSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "appDetail", settings, "Fixed", "Installment")
{
}
}
}
@@ -0,0 +1,237 @@
using System.Xml.Linq;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Services.Landmark;
using Landmark.OLA.Models.Loan;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Loan
{
public class AppDetailsSection : CrifRequestSection
{
private readonly string _elementName;
private readonly string _interestRateType;
private readonly string _calculationType;
private readonly ProductIdService _productIdTranslator;
protected AppDetailsSection(XNamespace sectionNamespace, string elementName, CrifSettings settings,
string interestRateType = null, string calculationType = null)
: base(sectionNamespace, "appDetails", settings)
{
_elementName = elementName;
_interestRateType = string.IsNullOrWhiteSpace(interestRateType) ? "Variable" : interestRateType;
_calculationType = string.IsNullOrWhiteSpace(calculationType) ? "OpenEnd" : calculationType;
_productIdTranslator = new ProductIdService(settings);
}
public static bool IsHMDA(LoanType? loanType)
{
HMDALoanType hmdaType;
try
{
hmdaType = (HMDALoanType)Enum.Parse(typeof(HMDALoanType), loanType.ToString());
}
catch
{
return false;
}
return true;
}
public virtual void SetDetails(LoanApplication loanApplication, Guid channelRef, object[] additionalContent = null, XElement fieldsElement = null, XElement vehicleValuationsElement = null)
{
var appDetailElement = new XElement(CrifNamespaces.App + _elementName,
new XAttribute(CrifNamespaces.App + "contractRate", 0m), //Per Ben V. at Landmark, we should send in 0 so that CRIF can set the correct rate
new XAttribute(CrifNamespaces.App + "financedAmt", loanApplication.RequestedAmount),
new XAttribute(CrifNamespaces.App + "termFrequencyCD", "Monthly"),
new XAttribute(CrifNamespaces.App + "calculationType", _calculationType),
new XAttribute(CrifNamespaces.App + "loanType", "Consumer"),
new XAttribute(CrifNamespaces.App + "interestRateType", _interestRateType),
new XAttribute(CrifNamespaces.App + "rankNum", 1),
new XAttribute(CrifNamespaces.App + "isApplicantRequestedInterestRate", false),
new XAttribute(CrifNamespaces.App + "isApplicantRequestedAmountRequested", true),
new XAttribute(CrifNamespaces.App + "principal", loanApplication.RequestedAmount), //TODO #234: Why is the loan amount listed twice?
new XAttribute(CrifNamespaces.App + "isApplicantRequestedTerm", true),
new XAttribute(CrifNamespaces.App + "isHMDAFL", IsHMDA(loanApplication.LoanType))
);
if (loanApplication.TermLengthInMonths.HasValue)
{
appDetailElement.Add(new XAttribute(CrifNamespaces.App + "termInMonths", loanApplication.TermLengthInMonths.Value));
}
var appDetailParties = new XElement(CrifNamespaces.App + "appDetailParties",
new XElement(CrifNamespaces.App + "appDetailParty",
new XAttribute(CrifNamespaces.Common + "idRef", channelRef),
new XAttribute(CrifNamespaces.Common + "partyType", "Channel"),
new XAttribute(CrifNamespaces.App + "appPartyType", "ServicingChannel")));
var appDetailRequestedAmts = new XElement(CrifNamespaces.App + "appDetailRequestedAmts",
new XElement(CrifNamespaces.App + "appDetailRequestedAmt",
new XAttribute(CrifNamespaces.App + "requestedAmount", loanApplication.RequestedAmount), //TODO #234: Financed amount is listed 3 times? Are the other 2 from the calculator?
new XAttribute(CrifNamespaces.App + "requestedAmountType", "Generic")));
var product = new XElement(CrifNamespaces.App + "product",
new XAttribute(CrifNamespaces.Common + "productID",
_productIdTranslator.GetProductId(loanApplication.LoanType.Value)));
var loanPurposes = new XElement(CrifNamespaces.App + "loanPurposes",
new XElement(CrifNamespaces.App + "appDetailLoanPurpose",
new XAttribute(CrifNamespaces.App + "rank", 1),
new XElement(CrifNamespaces.App + "loanPurpose",
new XAttribute(CrifNamespaces.Common + "CD", (int)loanApplication.Purpose.Value)
)));
appDetailElement.Add(appDetailParties);
appDetailElement.Add(additionalContent);
appDetailElement.Add(appDetailRequestedAmts);
appDetailElement.Add(product);
string productId = _productIdTranslator.GetProductId(loanApplication.LoanType.Value).ToString();
XElement appFieldsElement = null;
if (_settings.CustomFields != null && _settings.CustomFields.ProductIdToCategoryId.TryGetValue(productId, out string productCategoryId))
{
CustomPropertyStatusData customPropertyRenovationData = _settings.CustomFields.CustomPropertyRenovationFieldData.Find(x => x.ProductCategoryId == productCategoryId);
if (customPropertyRenovationData != null)
{
bool realEstateNotNullValue = loanApplication.RealEstate?.CurrentlyBeingRemodeledOrImproved ?? false;
string propertyRenovationValueCode = realEstateNotNullValue ? customPropertyRenovationData.YesId : customPropertyRenovationData.NoId;
appFieldsElement = GetLookupFieldLinkElement(customPropertyRenovationData.CustomFieldId, propertyRenovationValueCode, "AppDetail", appFieldsElement);
}
CustomPropertyStatusData customPropertySaleData = _settings.CustomFields.CustomPropertySaleFieldData.Find(x => x.ProductCategoryId == productCategoryId);
if (customPropertySaleData != null)
{
bool realEstateNotNullValue = loanApplication.RealEstate?.CurrentlyListedForSale ?? false;
string propertySaleValueCode = realEstateNotNullValue ? customPropertySaleData.YesId : customPropertySaleData.NoId;
appFieldsElement = GetLookupFieldLinkElement(customPropertySaleData.CustomFieldId, propertySaleValueCode, "AppDetail", appFieldsElement);
}
CustomJointCreditData customJointCreditData = _settings.CustomFields.CustomJointCreditFieldData.Find(x => x.ProductCategoryId == productCategoryId);
if (customJointCreditData != null)
{
string jointCreditValueCode = loanApplication.CoApplicant != null ? customJointCreditData.JointlyId : customJointCreditData.IndividuallyId;
appFieldsElement = GetLookupFieldLinkElement(customJointCreditData.CustomFieldId, jointCreditValueCode, "ProductCategory", appFieldsElement);
}
}
if (!string.IsNullOrWhiteSpace(loanApplication.PromoCode))
{
if (_settings.CustomFieldMappings.LoanTypeToPromoId.TryGetValue(productId, out string promoFieldId))
{
appFieldsElement = GetStringFieldLinkElement(promoFieldId, loanApplication.PromoCode, "ProductCategory", appFieldsElement);
}
}
if (loanApplication.Vehicle != null)
{
if (!string.IsNullOrWhiteSpace(loanApplication.Vehicle.LicensePlateNumber))
{
if (_settings.CustomFieldMappings.CustomFieldID.TryGetValue("LicensePlateNumber", out string customFieldId))
{
appFieldsElement = GetStringFieldLinkElement(customFieldId, loanApplication.Vehicle.LicensePlateNumber, "ProductCategory", appFieldsElement);
}
}
if (!string.IsNullOrWhiteSpace(loanApplication.Vehicle.CountyTitled))
{
if (_settings.CustomFieldMappings.CustomFieldID.TryGetValue("CountyTitled", out string customFieldId))
{
appFieldsElement = GetStringFieldLinkElement(customFieldId, loanApplication.Vehicle.CountyTitled, "ProductCategory", appFieldsElement);
}
}
if (!string.IsNullOrWhiteSpace(loanApplication.Vehicle.MunicipalityWhereKept))
{
if (_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityWhereKept", out string customFieldId))
{
appFieldsElement = GetStringFieldLinkElement(customFieldId, loanApplication.Vehicle.MunicipalityWhereKept, "ProductCategory", appFieldsElement);
}
}
var municipalityTypeCode = GetMunicipalityTypeCode(loanApplication.Vehicle.MunicipalityType);
if (!string.IsNullOrWhiteSpace(municipalityTypeCode))
{
if (_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityType", out string customFieldId))
{
appFieldsElement = GetLookupFieldLinkElement(customFieldId, municipalityTypeCode, "ProductCategory", appFieldsElement);
}
}
}
if (appFieldsElement != null)
{
appDetailElement.Add(appFieldsElement); //Order matters for this element.
}
appDetailElement.Add(loanPurposes);
appDetailElement.Add(vehicleValuationsElement); //Order matters for this element.
AddContent(appDetailElement);
}
private static XElement GetStringFieldLinkElement(string customFieldId, string value, string fieldLevel, XElement fieldsElement)
{
fieldsElement = CreateFieldsElementIfNull(fieldsElement);
var stringFieldLinkElement = new XElement(CrifNamespaces.Common + "stringFieldLink",
new XAttribute(CrifNamespaces.Common + "value", value));
var customFieldElement = new XElement(CrifNamespaces.Common + "customField",
new XAttribute(CrifNamespaces.Common + "ID", customFieldId),
new XAttribute(CrifNamespaces.Common + "fieldLevel", fieldLevel));
fieldsElement.Add(stringFieldLinkElement);
stringFieldLinkElement.Add(customFieldElement);
return fieldsElement;
}
private static XElement GetLookupFieldLinkElement(string Id, string value, string fieldLevel, XElement fieldsElement)
{
fieldsElement = CreateFieldsElementIfNull(fieldsElement);
var lookupFieldLinkElement = new XElement(CrifNamespaces.Common + "lookupFieldLink",
new XAttribute(CrifNamespaces.Common + "value", value),
new XElement(CrifNamespaces.Common + "customLookupField",
new XAttribute(CrifNamespaces.Common + "ID", Id),
new XAttribute(CrifNamespaces.Common + "fieldLevel", fieldLevel)));
fieldsElement.Add(lookupFieldLinkElement);
return fieldsElement;
}
private static XElement CreateFieldsElementIfNull(XElement fieldsElement)
{
if (fieldsElement == null)
{
fieldsElement = new XElement(CrifNamespaces.App + "fields");
}
return fieldsElement;
}
private string GetMunicipalityTypeCode(string municipalityType)
{
if (string.IsNullOrWhiteSpace(municipalityType))
{
return null;
}
if (_settings?.CustomFieldMappings?.MunicipalityTypeCode == null)
{
return null;
}
var key = municipalityType.Trim().ToUpperInvariant();
return _settings.CustomFieldMappings.MunicipalityTypeCode.TryGetValue(key, out string code)
? code
: null;
}
}
}
@@ -0,0 +1,35 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class AppNotesSectionBuilder : CrifRequestSection
{
public AppNotesSectionBuilder(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "appNotes", settings)
{
}
public void AddAppNote(string appNoteContent)
{
var appNote = new XElement(CrifNamespaces.App + "appNote",
new XAttribute(CrifNamespaces.App + "noteEntry", BuilderUtils.FormatCrifData(appNoteContent)),
new XAttribute(CrifNamespaces.App + "noteTypeDisplayName", "Application Note"),
new XAttribute(CrifNamespaces.App + "noteTypeCategoryDisplayName", "Application Note"));
var noteType = new XElement(CrifNamespaces.App + "noteType",
new XAttribute(CrifNamespaces.Common + "CD", 1));
var noteTypeCategory = new XElement(CrifNamespaces.App + "noteTypeCategory",
new XAttribute(CrifNamespaces.Common + "lookupType", "NoteTypeCategory"),
new XAttribute(CrifNamespaces.Common + "CD", 1));
appNote.Add(noteType, noteTypeCategory);
AddContent(appNote);
}
}
}
@@ -0,0 +1,23 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Assets;
namespace CRIF.ACTion.Loan.Assets
{
public class AppDetailMfgHomeAndLandSection : AppDetailHelocSection
{
//AppDetailMfgHomeAndLand is generally similar to AppDetailHeloc.
public AppDetailMfgHomeAndLandSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, settings, "appDetailMfgHousingAndLand", "Fixed", "Installment")
{
}
public override void AddPropertyDetails(RealEstateBase realEstateBase, bool? mfgHomeIncludesLand = null)
{
if (!mfgHomeIncludesLand.HasValue)
throw new ArgumentException("Manufactured Home Loans must state if the land is included.");
base.AddPropertyDetails(realEstateBase, mfgHomeIncludesLand);
}
}
}
@@ -0,0 +1,16 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan.Assets
{
public class AppDetailRealEstateSection : AppDetailHelocSection
{
//So far, AppDetailRealEstate is used exactly the same as AppDetailHeloc.
//The only difference is the element name and term types.
public AppDetailRealEstateSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, settings, "appDetailRealEstate", "Fixed", "Installment")
{
}
}
}
@@ -0,0 +1,69 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Loan.Assets
{
public class AppDetailVehicleSection : AppDetailsSection
{
private readonly List<XObject> _content;
private XElement _vehicleValuationsElement;
public AppDetailVehicleSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "appDetailVehicle", settings, "Fixed", "Installment")
{
_content = [];
}
public void AddPropertyDetails(Vehicle vehicle)
{
//If there's no vehicle type, it's likely a preapproval and doesn't need the appDetailProperties element
if (vehicle is null || vehicle.Type is null)
{
return;
}
var appDetailPropertiesElement = new XElement(CrifNamespaces.App + "appDetailProperties");
var propertyValues = PropertyValues.GetPropertyValues(vehicle.Type.Value);
var isCollateralFL = true;
var isTradeInFL = false;
var statusID = "Active";
var includeInLTVCalculations = true;
var appDetailPropertyElement = new XElement(CrifNamespaces.App + "appDetailProperty",
new XAttribute(CrifNamespaces.Common + "idRef", vehicle.GetOrCreateGuid()),
new XAttribute(CrifNamespaces.Common + "propertyType", propertyValues.PropertyType),
new XAttribute(CrifNamespaces.Common + "propertySubType", propertyValues.PropertySubType),
new XAttribute(CrifNamespaces.App + "isCollateralFL", isCollateralFL),
new XAttribute(CrifNamespaces.App + "isTradeInFL", isTradeInFL),
new XAttribute(CrifNamespaces.App + "statusID", statusID),
new XAttribute(CrifNamespaces.App + "includeInLTVCalculations", includeInLTVCalculations),
new XAttribute(CrifNamespaces.App + "collateralTypeID", propertyValues.CollateralId)
);
appDetailPropertiesElement.Add(appDetailPropertyElement);
_vehicleValuationsElement = new XElement(CrifNamespaces.App + "vehicleValuations",
new XElement(CrifNamespaces.App + "appDetailVehicleValuation",
new XAttribute(CrifNamespaces.Common + "idRef", vehicle.ValuationId),
new XAttribute(CrifNamespaces.App + "valueType", vehicle.ValuationType.Value.ToString())));
//TODO: What does valuationType mean?
_content.Add(appDetailPropertiesElement);
}
public void AddSalesPriceDetails(LoanApplication loanApplication)
{
_content.Add(new XAttribute(CrifNamespaces.App + "initialSalesPrice", loanApplication.RequestedAmount));
}
public override void SetDetails(LoanApplication loanApplication, Guid channelRef, object[] additionalContent = null, XElement fieldsElement = null, XElement vehicleValuationsElement = null)
{
base.SetDetails(loanApplication, channelRef, [.. _content], vehicleValuationsElement: _vehicleValuationsElement);
}
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan.Assets
{
public interface IVehicleValuations
{
VehicleValuationsSection VehicleValuations { get; }
}
}
@@ -0,0 +1,36 @@
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan.Assets
{
public class MfgHousingAndLandAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public MfgHousingAndLandAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailMfgHomeAndLandSection(createApp.CrifRequestSectionNamespace, _settings);
var includesLand = true;
if (createApp.LoanApplication.ManufacturedHome?.Lot?.IsRented ?? false)
{
includesLand = false;
}
((AppDetailMfgHomeAndLandSection)createApp.AppDetails).AddPropertyDetails(createApp.LoanApplication.ManufacturedHome, includesLand);
((AppDetailMfgHomeAndLandSection)createApp.AppDetails).AddHmdaDisclosed(createApp.LoanApplication.PrimaryBorrower.HmdaValues.HmdaNoticeDisclosed);
createApp.Properties.AddProperty(new MfgHousingProperty(createApp.LoanApplication.ManufacturedHome, includesLand));
createApp.Addresses.AddAddress(createApp.LoanApplication.ManufacturedHome.Lot, ContactMechanisms.CrifContactMechanism.RealEstateAddress);
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel);
}
}
}
@@ -0,0 +1,12 @@
using Landmark.OLA.Models.Assets;
namespace CRIF.ACTion.Loan.Assets
{
public class MfgHousingProperty : RealEstateProperty
{
public MfgHousingProperty(ManufacturedHome manufacturedHome, bool includesLand)
: base(manufacturedHome, "propertyMfgHousing", "MfgHomeAndLand")
{
}
}
}
@@ -0,0 +1,32 @@
using Landmark.OLA.Models.Assets.Enums;
namespace CRIF.ACTion.Loan.Assets
{
public static class PropertyValues
{
public static (int CollateralId, string PropertyType, string PropertySubType, string elementName) GetPropertyValues(VehicleType type)
{
switch (type)
{
case VehicleType.Automobile:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.Vehicle], "Auto", "Automobile", "propertyAuto");
case VehicleType.Motorcycle:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.Motorcycle], "Motorcycle", "NoneSelected", "propertyAuto");
case VehicleType.Boat:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.Marine], "Marine", "NoneSelected", "propertyMarine");
case VehicleType.ATV:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.ATV], "ATV", "NoneSelected", "propertyAuto");
case VehicleType.PWC:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.Marine], "Marine", "NoneSelected", "propertyMarine");
case VehicleType.RecreationalVehicle:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.RecreationalVehicle], "RV", "CampingTrailer", "propertyAuto"); //"CampingTrailer" is from CRIF example
case VehicleType.Snowmobile:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.Snowmobile], "Snowmobile", "NoneSelected", "propertyAuto");
case VehicleType.TrailerMotorHome:
return (CrifConstants.CollateralIds[CrifConstants.Collateral.RecreationalVehicle], "RV", "Other", "propertyAuto"); //"Other" is from sprint requirements doc
default:
throw new NotImplementedException("This vehicle type is not implemented.");
}
}
}
}
@@ -0,0 +1,27 @@
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan.Assets
{
public class RealEstateEquityAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public RealEstateEquityAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailRealEstateSection(createApp.CrifRequestSectionNamespace, _settings);
((AppDetailRealEstateSection)createApp.AppDetails).AddPropertyDetails(createApp.LoanApplication.RealEstate);
((AppDetailRealEstateSection)createApp.AppDetails).AddHmdaDisclosed(createApp.LoanApplication.PrimaryBorrower.HmdaValues.HmdaNoticeDisclosed);
createApp.Properties.AddProperty(new RealEstateProperty(createApp.LoanApplication.RealEstate));
createApp.Addresses.AddAddress(createApp.LoanApplication.RealEstate.RealEstateAddress, ContactMechanisms.CrifContactMechanism.RealEstateAddress);
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel);
}
}
}
@@ -0,0 +1,221 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Assets.Enums;
using static CRIF.ACTion.Common.ContactMechanisms;
namespace CRIF.ACTion.Loan.Assets
{
public class RealEstateProperty : IProperty
{
private protected readonly RealEstateBase _realEstateBase;
private protected readonly string _elementName;
private protected readonly string _propertyType;
public RealEstateProperty(RealEstateBase realEstateBase, string elementName = "propertyRealEstate",
string propertyType = "RealEstate")
{
_realEstateBase = realEstateBase;
_elementName = elementName;
_propertyType = propertyType;
}
public XElement Build(params XElement[] content)
{
var classType = _realEstateBase.GetType();
if (classType.Equals(typeof(ManufacturedHome)) || classType.IsSubclassOf(typeof(ManufacturedHome)))
{
var realEstateElement = GetRealEstateElement((ManufacturedHome)_realEstateBase);
realEstateElement.Add(
new XAttribute(CrifNamespaces.Common + "lienPositionID", LienPositionType.First)
);
return realEstateElement;
}
else if (classType.Equals(typeof(RealEstate)) || classType.IsSubclassOf(typeof(RealEstate)))
{
var realEstateElement = GetRealEstateElement((RealEstate)_realEstateBase);
realEstateElement.Add(
new XAttribute(CrifNamespaces.Common + "lienPositionID", GetLienPosition(_realEstateBase.LienPosition))
);
return realEstateElement;
}
else
{
throw new ArgumentException("Unexpected object given to constructor for RealEstateProperty");
}
}
private XElement GetRealEstateElement(RealEstate realEstate)
{
return GetRealEstateElement(realEstate, realEstate.Type.Value.ToString());
}
private XElement GetRealEstateElement(ManufacturedHome manufacturedHome)
{
var element = GetRealEstateElement(manufacturedHome, manufacturedHome.Type.Value.ToString(),
manufacturedHome.EstimatedLandValuation, manufacturedHome.EstimatedHomeValuation);
//The valuation fields above are suppose to go in totalValue and finalEstimatedValue as written.
//It's a little strange but that's how CRIF stores that data.
// This has to be before propertyLot, otherwise CRIF will complain!
if (manufacturedHome.Lot.AnnualPropertyTax.HasValue)
{
var propertyTaxesElement = new XElement(CrifNamespaces.Common + "propertyTaxes");
var propertyTaxElement = new XElement(
CrifNamespaces.Common + "propertyTax",
new XAttribute(CrifNamespaces.Common + "taxEntity", "IRS"),
new XAttribute(CrifNamespaces.Common + "amount", manufacturedHome.Lot.AnnualPropertyTax.Value.ToString()),
new XAttribute(CrifNamespaces.Common + "paymentFrequency", "Annually")
);
propertyTaxesElement.Add(propertyTaxElement);
element.Add(propertyTaxesElement);
}
var propertyLotElement = new XElement(CrifNamespaces.Common + "propertyLot");
if (manufacturedHome.Lot.RentalAmt.HasValue)
{
propertyLotElement.Add(new XAttribute(CrifNamespaces.Common + "rentalAmt", manufacturedHome.Lot.RentalAmt.Value));
}
element.Add(propertyLotElement);
return element;
}
private XElement GetRealEstateElement(RealEstateBase realEstateBase, string subtype, decimal? finalEstimatedValue = null, decimal? totalValue = null)
{
var propertyRealEstateElement = new XElement(CrifNamespaces.Common + _elementName,
new XAttribute(CrifNamespaces.Common + "id", realEstateBase.Id),
new XAttribute(CrifNamespaces.Common + "propertySubType", subtype),
// new XAttribute(CrifNamespaces.Common + "hasClearTitleFL", realEstate.HasClearTitle),
new XAttribute(CrifNamespaces.Common + "initialEstimatedValue", realEstateBase.InitialEstimatedValue),
new XAttribute(CrifNamespaces.Common + "propertyType", _propertyType),
new XAttribute(CrifNamespaces.Common + "ownerOccupiedTypeID", realEstateBase.Use.Value));
if(finalEstimatedValue.HasValue)
{
propertyRealEstateElement.Add(new XAttribute(CrifNamespaces.Common + "finalEstimatedValue", finalEstimatedValue));
}
if (totalValue.HasValue)
{
propertyRealEstateElement.Add(new XAttribute(CrifNamespaces.Common + "totalValue", totalValue));
}
var contactMechanismsElement = GetContactMechanisms(realEstateBase);
propertyRealEstateElement.Add(GetPropertyDetails(realEstateBase));
propertyRealEstateElement.Add(contactMechanismsElement);
return propertyRealEstateElement;
}
private static XElement GetContactMechanisms(RealEstateBase realEstateBase)
{
var contactMechanismType = CrifContactMechanism.RealEstateAddress;
var idRef = "";
var classType = realEstateBase.GetType();
if (classType.Equals(typeof(ManufacturedHome)) || classType.IsSubclassOf(typeof(ManufacturedHome)))
{
contactMechanismType = CrifContactMechanism.ParkContactAddress;
idRef = ((ManufacturedHome)realEstateBase).Lot.Id.ToString();
}
else if (classType.Equals(typeof(RealEstate)) || classType.IsSubclassOf(typeof(RealEstate)))
{
idRef = ((RealEstate)realEstateBase).RealEstateAddress.Id.ToString();
}
var contactMechanismsElement = new XElement(CrifNamespaces.Common + "contactMechanisms",
new XElement(CrifNamespaces.Common + "contactMechanism",
new XAttribute(CrifNamespaces.Common + "contactMechanismType", contactMechanismType.ToString()),
new XElement(CrifNamespaces.Common + "addressRef",
new XAttribute(CrifNamespaces.Common + "idRef", idRef)),
new XElement(CrifNamespaces.Common + "contactMechanismCategory",
new XAttribute(CrifNamespaces.Common + "CD", (int)GetContactMechanismCategory(contactMechanismType)))
));
return contactMechanismsElement;
}
private static XAttribute[] GetPropertyDetails(RealEstateBase realEstateBase)
{
var attributeList = new List<XAttribute>();
if (realEstateBase.HasSpa.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "hasSpaFL", realEstateBase.HasSpa.Value));
}
if (realEstateBase.HasPool.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "hasPoolFL", realEstateBase.HasPool.Value));
}
if (realEstateBase.HasBasement.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "hasBasementFL", realEstateBase.HasBasement.Value));
}
if (realEstateBase.HasCentralAir.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "hasCentralAirFL", realEstateBase.HasCentralAir.Value));
}
if (realEstateBase.HasGarage.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "hasGarageFL", realEstateBase.HasGarage.Value));
}
if (realEstateBase.InFloodZone.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "inFloodZoneFL", realEstateBase.InFloodZone.Value));
}
if (realEstateBase.InEquity.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "inEquityFL", realEstateBase.InEquity.Value));
}
if (realEstateBase.IsPropertyHeldInTrust.HasValue)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "isPropertyHeldInTrust", realEstateBase.IsPropertyHeldInTrust.Value));
}
return [.. attributeList];
}
public static LienPositionType GetLienPosition(LienPosition lienPositionData)
{
if (lienPositionData.HasFirstMortgage && lienPositionData.HasSecondMortgage)
{
if (lienPositionData.PayoffFirstMortgage && lienPositionData.PayoffSecondMortgage)
{
return LienPositionType.First;
}
else if (!lienPositionData.PayoffFirstMortgage && !lienPositionData.PayoffSecondMortgage)
{
return LienPositionType.Third;
}
else if (lienPositionData.PayoffFirstMortgage || lienPositionData.PayoffSecondMortgage)
{
return LienPositionType.Second;
}
}
else if (lienPositionData.HasFirstMortgage || lienPositionData.HasSecondMortgage)
{
if (lienPositionData.PayoffFirstMortgage || lienPositionData.PayoffSecondMortgage)
{
return LienPositionType.First;
}
else
{
return LienPositionType.Second;
}
}
return LienPositionType.First;
}
}
}
@@ -0,0 +1,45 @@
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Loan.Assets
{
public class VehicleAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public VehicleAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailVehicleSection(createApp.CrifRequestSectionNamespace, _settings);
if (createApp.LoanApplication.LoanType.Value == LoanType.V_VehiclePreapproval
&& createApp.LoanApplication.Vehicle is null)
{
createApp.LoanApplication.Vehicle = new Vehicle
{
InitialEstimatedValue = createApp.LoanApplication.RequestedAmount
};
}
((AppDetailVehicleSection)createApp.AppDetails).AddPropertyDetails(createApp.LoanApplication.Vehicle);
((AppDetailVehicleSection)createApp.AppDetails).AddSalesPriceDetails(createApp.LoanApplication);
createApp.Properties.AddProperty(new VehicleProperty(createApp.LoanApplication.Vehicle));
createApp.VehicleValuations.AddVehicleValuation(createApp.LoanApplication.Vehicle);
if (createApp.LoanApplication.LoanType == LoanType.M_Boat &&
!(createApp.LoanApplication.Vehicle.BoatAddress is null))
{
createApp.Addresses.AddAddress(createApp.LoanApplication.Vehicle.BoatAddress, ContactMechanisms.CrifContactMechanism.MarinaAddress);
}
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel);
}
}
}
@@ -0,0 +1,167 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Loan.Enums;
using static CRIF.ACTion.Common.ContactMechanisms;
namespace CRIF.ACTion.Loan.Assets
{
public class VehicleProperty : IProperty
{
private readonly Vehicle _vehicle;
public VehicleProperty(Vehicle vehicle)
{
_vehicle = vehicle;
}
public XElement Build(params XElement[] content)
{
//If there's no vehicle type, it's likely a preapproval and doesn't need this element
if (_vehicle is null || _vehicle.Type is null)
return null;
var propertyValues = PropertyValues.GetPropertyValues(_vehicle.Type.Value);
var propertyElement = new XElement(CrifNamespaces.Common + propertyValues.elementName,
new XAttribute(CrifNamespaces.Common + "id", _vehicle.GetOrCreateGuid()),
new XAttribute(CrifNamespaces.Common + "propertySubType", propertyValues.PropertySubType),
new XAttribute(CrifNamespaces.Common + "yearBuilt", _vehicle.Year.Value),
new XAttribute(CrifNamespaces.Common + "initialEstimatedValue", _vehicle.InitialEstimatedValue.Value),
new XAttribute(CrifNamespaces.Common + "propertyType", propertyValues.PropertyType));
if ( _vehicle.Type != Landmark.OLA.Models.Assets.Enums.VehicleType.Automobile ) {
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "totalValue",
_vehicle.InitialEstimatedValue.Value));
}
if (_vehicle.HasClearTitle.HasValue)
{
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "hasClearTitleFL",
_vehicle.HasClearTitle.Value));
}
if (_vehicle.IsForBusinessUse.HasValue)
{
var usageType = _vehicle.IsForBusinessUse.Value ? "Business" : "Personal";
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "propertyUsageTypeID",
usageType));
}
if (_vehicle.IsOwnerOperator.HasValue)
{
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "isOwnerOperatorFL",
_vehicle.IsOwnerOperator.Value));
}
if ((_vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.PWC || _vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.Boat)
&& !string.IsNullOrWhiteSpace(_vehicle.VIN))
{
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "hullNum",
BuilderUtils.FormatCrifData(_vehicle.VIN)));
}
if (_vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.Boat && (_vehicle.BoatLength ?? 0) != 0)
{
propertyElement.Add(new XAttribute(CrifNamespaces.Common + "lengthDesc",
_vehicle.BoatLength));
}
// Order matters for elements
if (_vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.Boat
&& _vehicle.BoatAddress is not null)
{
var contactMechanisms = new XElement(CrifNamespaces.Common + "contactMechanisms");
contactMechanisms.Add(GetAddressContactMechanismElement(_vehicle.BoatAddress.Id, CrifContactMechanism.MarinaAddress));
propertyElement.Add(contactMechanisms);
}
var valuationsLinkElement = new XElement(CrifNamespaces.Common + "propertyVehicleValuations",
new XElement(CrifNamespaces.Common + "propertyVehicleValuation",
new XAttribute(CrifNamespaces.Common + "idRef", _vehicle.ValuationId)));
propertyElement.Add(valuationsLinkElement);
if (_vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.Boat)
{
propertyElement.Add(CreateMotorElement(_vehicle));
propertyElement.Add(CreateTrailerElement(_vehicle));
}
return propertyElement;
}
public static XElement CreateMotorElement(Vehicle vehicle)
{
var motorElement = new XElement(CrifNamespaces.Common + "motors");
var propertyMotorElement = new XElement(CrifNamespaces.Common + "propertyMotor",
new XAttribute(CrifNamespaces.Common + "motorNum", 1));
if (!string.IsNullOrWhiteSpace(vehicle.MotorMake))
{
propertyMotorElement.Add(new XAttribute(CrifNamespaces.Common + "make", vehicle.MotorMake));
}
if (vehicle.MotorYear is not null)
{
propertyMotorElement.Add(new XAttribute(CrifNamespaces.Common + "yearBuilt", vehicle.MotorYear.Value));
}
if (!string.IsNullOrWhiteSpace(vehicle.MotorModel))
{
propertyMotorElement.Add(new XAttribute(CrifNamespaces.Common + "model", vehicle.MotorModel));
}
if (vehicle.MotorType is not null)
{
propertyMotorElement.Add(new XAttribute(CrifNamespaces.Common + "engineTypeID", vehicle.MotorType));
}
motorElement.Add(propertyMotorElement);
return motorElement;
}
public static XElement CreateTrailerElement(Vehicle vehicle)
{
var trailerElement = new XElement(CrifNamespaces.Common + "trailer",
new XAttribute(CrifNamespaces.Common + "registrationState", "WI"));
if (!string.IsNullOrWhiteSpace(vehicle.TrailerType))
{
trailerElement.Add(new XAttribute(CrifNamespaces.Common + "propertySubType", vehicle.TrailerType));
}
if (vehicle.TrailerYear is not null)
{
trailerElement.Add(new XAttribute(CrifNamespaces.Common + "yearBuild", vehicle.TrailerYear.Value));
}
if (!string.IsNullOrWhiteSpace(vehicle.TrailerMake))
{
trailerElement.Add(new XAttribute(CrifNamespaces.Common + "make", vehicle.TrailerMake));
}
return trailerElement;
}
public static XElement GetAddressContactMechanismElement(Guid addressRef, CrifContactMechanism contactMechanismType)
{
var contactMechanism = new XElement(CrifNamespaces.Common + "contactMechanism",
new XAttribute(CrifNamespaces.Common + "contactMechanismType", contactMechanismType),
new XElement(CrifNamespaces.Common + "addressRef",
new XAttribute(CrifNamespaces.Common + "idRef", addressRef)
),
new XElement(CrifNamespaces.Common + "contactMechanismCategory",
new XAttribute(CrifNamespaces.Common + "CD", 2)
)
);
return contactMechanism;
}
}
}
@@ -0,0 +1,79 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Assets;
namespace CRIF.ACTion.Loan.Assets
{
public class VehicleValuationsSection : CrifRequestSection
{
public VehicleValuationsSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "vehicleValuations", settings)
{
}
public void AddVehicleValuation(Vehicle vehicle)
{
//If there's no vehicle type, it's likely a preapproval and doesn't need this element
if (vehicle is null || vehicle.Type is null)
return;
var valuationElement = new XElement(CrifNamespaces.Common + "vehicleValuation",
new XAttribute(CrifNamespaces.Common + "id", vehicle.ValuationId),
new XAttribute(CrifNamespaces.Common + "valuationType", "Manual"),
new XAttribute(CrifNamespaces.Common + "make", BuilderUtils.FormatCrifData(vehicle.Make)),
new XAttribute(CrifNamespaces.Common + "model", BuilderUtils.FormatCrifData(vehicle.Model)),
new XAttribute(CrifNamespaces.Common + "year", vehicle.Year.Value),
new XAttribute(CrifNamespaces.Common + "newUsed", vehicle.Condition.Value.ToString()),
new XAttribute(CrifNamespaces.Common + "estimatedValue", vehicle.InitialEstimatedValue));
var valuationValueTypesElement = new XElement(CrifNamespaces.Common + "vehicleValuationValueTypes");
valuationValueTypesElement.Add(new XElement(CrifNamespaces.Common + "vehicleValuationValueType",
new XAttribute(CrifNamespaces.Common + "valueType", "Manual"),
new XAttribute(CrifNamespaces.Common + "baseValue", vehicle.InitialEstimatedValue)
));
valuationElement.Add(valuationValueTypesElement);
if (vehicle.Mileage.HasValue)
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "mileage", vehicle.Mileage.Value));
}
if (!string.IsNullOrWhiteSpace(vehicle.Color))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "color", BuilderUtils.FormatCrifData(vehicle.Color)));
}
if (!string.IsNullOrWhiteSpace(vehicle.Body))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "body", BuilderUtils.FormatCrifData(vehicle.Body)));
}
if (!string.IsNullOrWhiteSpace(vehicle.Series))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "series", BuilderUtils.FormatCrifData(vehicle.Series)));
}
if (!string.IsNullOrWhiteSpace(vehicle.Style))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "style", BuilderUtils.FormatCrifData(vehicle.Style)));
}
if (!string.IsNullOrWhiteSpace(vehicle.Trim))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "trim", BuilderUtils.FormatCrifData(vehicle.Trim)));
}
if (!string.IsNullOrWhiteSpace(vehicle.VIN))
{
if (vehicle.Type != Landmark.OLA.Models.Assets.Enums.VehicleType.PWC)
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "vin", BuilderUtils.FormatCrifData(vehicle.VIN)));
}
}
if (!string.IsNullOrWhiteSpace(vehicle.StateTitled))
{
valuationElement.Add(new XAttribute(CrifNamespaces.Common + "state", BuilderUtils.FormatCrifData(vehicle.StateTitled)));
}
AddContent(valuationElement);
}
}
}
@@ -0,0 +1,45 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Loan
{
public class CreditCardAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public CreditCardAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailBankcardSection(createApp.CrifRequestSectionNamespace, _settings);
var additionalContent = new List<XElement>();
if (createApp.LoanApplication.LoanType == LoanType.CC_Secure)
{
var collateralId = createApp.LoanApplication.SecuredCardCollateralId;
//per Ben V., all Secure CC loans should have a savings account collateral equal to the CC limit
createApp.Properties.AddProperty(new SavingsAccountProperty(collateralId, createApp.LoanApplication.RequestedAmount.Value));
var appDetailProperties = new XElement(CrifNamespaces.App + "appDetailProperties",
new XElement(CrifNamespaces.App + "appDetailProperty",
new XAttribute(CrifNamespaces.Common + "idRef", collateralId),
new XAttribute(CrifNamespaces.Common + "propertyType", "CD"), //from CRIF example
new XAttribute(CrifNamespaces.Common + "propertySubType", "NoneSelected"), //from CRIF example
new XAttribute(CrifNamespaces.App + "statusID", "Active"), //from CRIF example
new XAttribute(CrifNamespaces.App + "collateralTypeID", CrifConstants.CollateralIds[CrifConstants.Collateral.CDsOrSavings])));
additionalContent.Add(appDetailProperties);
}
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel, additionalContent: additionalContent.ToArray());
}
}
}
@@ -0,0 +1,35 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class DealershipRequestBuilder : BuilderBase
{
public readonly string applicationId;
public DealershipRequestBuilder(string applicationId,
CrifSettings settings)
: base(CrifNamespaces.App.Namespace, settings)
{
this.applicationId = applicationId;
}
public override XElement Build(params XElement[] content)
{
var createAppElement = GetAppElement(applicationId);
return base.Build(createAppElement);
}
public static XElement GetAppElement(string applicationId)
{
var element = new XElement(CrifNamespaces.Empty + "getApplication");
element.SetAttributeValue("applicationID", applicationId);
return element;
}
}
}
@@ -0,0 +1,96 @@
using System.Xml.Linq;
using CRIF.ACTion.Common.Parsers;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Loan
{
public class DealershipResponseParser : IResponseParser<DealershipAuthRequest>
{
public DealershipAuthRequest Parse(XElement xdoc)
{
try
{
var application = xdoc.Descendants(Common.CrifNamespaces.App + "application").GetEnumerator();
application.MoveNext();
if(application.Current == null)
{
return null;
}
var applicationId = application.Current.Attribute(Common.CrifNamespaces.App + "applicationID")?.Value;
var primaryBorrower = xdoc.Descendants(Common.CrifNamespaces.App + "primaryBorrower").GetEnumerator();
primaryBorrower.MoveNext();
var primaryBorrowerRef = primaryBorrower.Current.Attribute(Common.CrifNamespaces.Common + "idRef").Value;
string applicantLastName = null;
var individual = xdoc.Descendants(Common.CrifNamespaces.Common + "individual").GetEnumerator();
while(individual.MoveNext())
{
if(individual.Current.Attribute(Common.CrifNamespaces.Common + "id").Value.Equals(primaryBorrowerRef))
{
applicantLastName = individual.Current.Attribute(Common.CrifNamespaces.Common + "lastName")?.Value;
break;
}
}
if (applicantLastName == null)
{
// Check for Company as primary applicant
var company = xdoc.Descendants(Common.CrifNamespaces.Common + "company").GetEnumerator();
while (company.MoveNext())
{
if (company.Current.Attribute(Common.CrifNamespaces.Common + "id").Value.Equals(primaryBorrowerRef))
{
applicantLastName = company.Current.Attribute(Common.CrifNamespaces.Common + "tradeName").Value;
break;
}
}
}
/*if (applicantLastName == null)
{
// Check for DBA/Trust Name as primary applicant
var company = xdoc.Descendants(Common.CrifNamespaces.Common + "company").GetEnumerator();
while (company.MoveNext())
{
if (company.Current.Attribute(Common.CrifNamespaces.Common + "id").Value.Equals(primaryBorrowerRef))
{
applicantLastName = company.Current.Attribute(Common.CrifNamespaces.Common + "code").Value;
break;
}
}
}*/
var channel = xdoc.Descendants(Common.CrifNamespaces.Common + "channel");
var channelEnum = channel.GetEnumerator();
channelEnum.MoveNext();
var dealershipCode = channelEnum.Current.Attribute(Common.CrifNamespaces.Common + "code")?.Value;
var dealershipName = channelEnum.Current.Attribute(Common.CrifNamespaces.Common + "dbaName")?.Value;
var dealershipPassword = channelEnum.Current.Attribute(Common.CrifNamespaces.Common + "costCenterCode")?.Value;
var isActiveFlag = channelEnum.Current.Attribute(Common.CrifNamespaces.Common + "isActiveFL")?.Value;
if(isActiveFlag == "false")
{
return null;
}
var classificationItems = channel.Descendants(Common.CrifNamespaces.Common + "channelClassificationItem").GetEnumerator();
while (classificationItems.MoveNext())
{
if(string.Equals(classificationItems.Current.Attribute(Common.CrifNamespaces.Common + "name").Value, "Dealer", StringComparison.OrdinalIgnoreCase))
{
return new DealershipAuthRequest(dealershipCode, dealershipName, dealershipPassword, applicantLastName, applicationId);
}
}
return null;
}
catch
{
throw new FormatException("CRIF XML is in an unexpected layout.");
}
}
}
}
@@ -0,0 +1,28 @@
using CRIF.ACTion.Common;
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Loan.Assets;
namespace CRIF.ACTion.Loan
{
public class HelocFlexAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public HelocFlexAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailHelocSection(createApp.CrifRequestSectionNamespace, _settings);
((AppDetailHelocSection)createApp.AppDetails).AddPropertyDetails(createApp.LoanApplication.RealEstate);
((AppDetailHelocSection)createApp.AppDetails).AddHmdaDisclosed(createApp.LoanApplication.PrimaryBorrower.HmdaValues.HmdaNoticeDisclosed);
createApp.Properties.AddProperty(new RealEstateProperty(createApp.LoanApplication.RealEstate));
createApp.Addresses.AddAddress(createApp.LoanApplication.RealEstate.RealEstateAddress, ContactMechanisms.CrifContactMechanism.RealEstateAddress);
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel);
}
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan
{
public interface IAppDetails
{
AppDetailsSection AppDetails { get; }
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan
{
public interface IAppNotes
{
AppNotesSectionBuilder AppNotes { get; }
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan
{
public interface ILoanApplicationRequestBuilder
{
void SetDetails(LoanApplicationRequestBuilder createApp);
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan
{
public interface IOrg
{
OrgSection Org { get; }
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan
{
public interface IProperties
{
PropertiesSection Properties { get; }
}
}
@@ -0,0 +1,8 @@
using CRIF.ACTion.Common.Builders;
namespace CRIF.ACTion.Loan
{
public interface IProperty : IBuildable
{
}
}
@@ -0,0 +1,65 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals;
namespace CRIF.ACTion.Loan.Individuals
{
public class AddIndividualRequestBuilder : BuilderBase, IAppParties, IParties, IAddresses
{
private readonly IApplicant Individual;
private readonly string ApplicationId;
public AppPartiesSection AppParties { get; set; }
public PartiesSection Parties { get; set; }
public AddressesSection Addresses { get; set; }
public AddIndividualRequestBuilder(string applicationId, IApplicant individual, CrifSettings settings)
: base(CrifNamespaces.Empty, settings)
{
Individual = individual;
ApplicationId = applicationId;
AppParties = new AppPartiesSection(CrifRequestSectionNamespace, _settings);
Parties = new PartiesSection(CrifRequestSectionNamespace, _settings);
Addresses = new AddressesSection(CrifRequestSectionNamespace, _settings);
}
public override XElement Build(params XElement[] content)
{
Parties.AddApplicant(Individual);
AppParties.AddIndividualParty(Individual, AppPartiesSection.PartyType.borrower, Individual.HasOptedOutTCPA);
Addresses.AddAddress(Individual.Address, ContactMechanisms.CrifContactMechanism.CurrentAddress);
if (Individual.MailingAddress is not null)
{
Addresses.AddAddress(Individual.MailingAddress, ContactMechanisms.CrifContactMechanism.MailingAddress);
}
if (Individual.PreviousAddresses?.Count > 0)
{
foreach (Address previousAddress in Individual.PreviousAddresses)
{
Addresses.AddAddress(previousAddress, ContactMechanisms.CrifContactMechanism.PreviousAddress);
}
}
var addIndividualElement = new XElement(CrifNamespaces.Empty + "addIndividual",
new XAttribute("applicationID", ApplicationId),
AppParties.Build(),
Parties.Build(),
Addresses.Build());
var baseElement = base.Build(addIndividualElement);
return baseElement;
}
}
}
@@ -0,0 +1,42 @@
using System.Xml.Linq;
using System.Xml.XPath;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Parsers;
using Landmark.OLA.Models.Individuals;
namespace CRIF.ACTion.Loan.Individuals
{
public class AddIndividualResponseParser: IResponseParser<Individual>
{
public Individual Parse(XElement xElement)
{
CheckErrors(xElement);
return ParseIndividual(xElement);
}
private static void CheckErrors(XElement xElement)
{
var error = xElement.XPathSelectElement("/actionAPI/addIndividual/errors/error",
CrifNamespaces.GetNamespaceManager(xElement));
if (error != null)
{
throw new Exception(error.Value);
}
}
public static Individual ParseIndividual(XElement xElement)
{
var individual = xElement.XPathSelectElement("/actionAPI/addIndividual/msg:appImportResponse/msg:applications/app:application/app:parties/common:individual",
CrifNamespaces.GetNamespaceManager(xElement));
var parsedIndividual = new Individual
{
FirstName = individual.Attribute(CrifNamespaces.Common + "firstName").Value
};
return parsedIndividual;
}
}
}
@@ -0,0 +1,68 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Residences;
using static CRIF.ACTion.Common.ContactMechanisms;
namespace CRIF.ACTion.Loan.Individuals
{
public class AddressesSection : CrifRequestSection
{
public AddressesSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "addresses", settings)
{
}
public void AddAddress(IAddress address, CrifContactMechanism contactMechanismType)
{
ModelValidator.Validate(address);
var isCurrentAddress = contactMechanismType == CrifContactMechanism.CurrentAddress
? true : false;
var addressElement = new XElement(CrifNamespaces.Common + "address",
new XAttribute(CrifNamespaces.Common + "id", address.Id),
new XAttribute(CrifNamespaces.Common + "street1", BuilderUtils.FormatCrifData(address.Address1)),
new XAttribute(CrifNamespaces.Common + "cityName", BuilderUtils.FormatCrifData(address.City)),
new XAttribute(CrifNamespaces.Common + "state", address.State),
new XAttribute(CrifNamespaces.Common + "postalCode", address.PostalCode),
new XAttribute(CrifNamespaces.Common + "country", address.Country));
var addressType = address.GetType();
if (addressType.Equals(typeof(ResidenceAddress)) || addressType.IsSubclassOf(typeof(ResidenceAddress)))
{
var residence = (ResidenceAddress)address;
addressElement.Add(
new XAttribute(CrifNamespaces.Common + "isCurrentResFL", isCurrentAddress),
new XAttribute(CrifNamespaces.Common + "monthsResided", residence.MonthsResided));
var typeCode = CrifResidenceType.GetCrifResidenceTypeCode(residence.ResidenceType.Value);
if (typeCode != null)
{
var residenceTypeElement = new XElement(CrifNamespaces.Common + "residenceType",
new XAttribute(CrifNamespaces.Common + "CD", typeCode));
addressElement.Add(residenceTypeElement);
}
}
if (!string.IsNullOrWhiteSpace(address.Address2))
{
addressElement.Add(new XAttribute(CrifNamespaces.Common + "street2", BuilderUtils.FormatCrifData(address.Address2)));
}
if (!string.IsNullOrWhiteSpace(address.PostalCodeExt))
{
addressElement.Add(new XAttribute(CrifNamespaces.Common + "zipExt", address.PostalCodeExt));
}
AddContent(addressElement);
}
}
}
@@ -0,0 +1,111 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models;
namespace CRIF.ACTion.Loan.Individuals
{
public class AppPartiesSection : CrifRequestSection
{
public AppPartiesSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "appParties", settings)
{
}
public void AddIndividualParty(IReferenceable individual, PartyType partyType, bool hasOptedOutTCPA = false)
{
var borrowerTypeString = partyType == PartyType.primaryBorrower ? "primaryBorrower" : "borrower";
var individualParty = new XElement(CrifNamespaces.App + borrowerTypeString,
new XAttribute(CrifNamespaces.Common + "idRef", individual.Id),
new XAttribute(CrifNamespaces.Common + "partyType", "Individual"),
new XAttribute(CrifNamespaces.App + "isCollateralGrantorFL", false)
);
individualParty.Add(GetCustomField(partyType, hasOptedOutTCPA));
AddContent(individualParty);
}
private XElement GetTCPACustomField()
{
var customField = GetBoolFieldLinkElement(CrifConstants.TCPA_Opt_Out_Field_Id,
true, "IndividualApplicant");
return customField;
}
private XElement GetCustomField(PartyType type, bool includeTCPAOptOut)
{
var fieldValue = type switch
{
PartyType.NonSigningSpouseMarriedToCoBorrower => CrifConstants.Borrower_IsApplicantNonSigningSpouse_Field_NonSigningMarriedToCo,
PartyType.NonSigningSpouseMarriedToPrimary => CrifConstants.Borrower_IsApplicantNonSigningSpouse_Field_NonSigningMarriedToPrimary,
_ => CrifConstants.Borrower_IsApplicantNonSigningSpouse_Field_Borrower,
};
var customField = GetLookupFieldLinkElement(CrifConstants.Borrower_IsApplicantNonSigningSpouse_Field_Id,
fieldValue, "IndividualApplicant");
if (includeTCPAOptOut)
{
var tcpaField = GetTCPACustomField();
var fieldsElementWithTCPA = new XElement(CrifNamespaces.App + "fields", customField, tcpaField);
return fieldsElementWithTCPA;
}
var fieldsElement = new XElement(CrifNamespaces.App + "fields", customField);
return fieldsElement;
}
public void SetSourceChannel(Guid channelId)
{
var sourceChannel = new XElement(CrifNamespaces.App + "sourceChannel",
new XAttribute(CrifNamespaces.Common + "idRef", channelId),
new XAttribute(CrifNamespaces.Common + "partyType", "Channel"));
AddContent(sourceChannel);
}
public void SetServicingChannel(Guid channelId)
{
var servicingChannel = new XElement(CrifNamespaces.App + "servicingChannel",
new XAttribute(CrifNamespaces.Common + "idRef", channelId),
new XAttribute(CrifNamespaces.Common + "partyType", "Channel"));
AddContent(servicingChannel);
}
private static XElement GetLookupFieldLinkElement(int Id, int value, string fieldLevel)
{
var lookupFieldLinkElement = new XElement(CrifNamespaces.Common + "lookupFieldLink",
new XAttribute(CrifNamespaces.Common + "value", value),
new XElement(CrifNamespaces.Common + "customLookupField",
new XAttribute(CrifNamespaces.Common + "ID", Id),
new XAttribute(CrifNamespaces.Common + "fieldLevel", fieldLevel)));
return lookupFieldLinkElement;
}
private static XElement GetBoolFieldLinkElement(int Id, bool value, string fieldLevel)
{
var lookupFieldLinkElement = new XElement(CrifNamespaces.Common + "boolFieldLink",
new XAttribute(CrifNamespaces.Common + "value", value),
new XElement(CrifNamespaces.Common + "customLookupField",
new XAttribute(CrifNamespaces.Common + "ID", Id),
new XAttribute(CrifNamespaces.Common + "fieldLevel", fieldLevel)));
return lookupFieldLinkElement;
}
public enum PartyType
{
primaryBorrower,
borrower,
NonSigningSpouseMarriedToPrimary,
NonSigningSpouseMarriedToCoBorrower,
}
}
}
@@ -0,0 +1,29 @@
using Landmark.OLA.Models.Addresses.Enums;
namespace CRIF.ACTion.Loan.Individuals
{
public static class CrifResidenceType
{
public static string GetCrifResidenceTypeCode(ResidenceTypeOption residenceTypeOption)
{
return residenceTypeOption switch
{
ResidenceTypeOption.Own => "N",
ResidenceTypeOption.Rent => "R",
ResidenceTypeOption.Other => "O",
_ => throw new ArgumentOutOfRangeException(nameof(residenceTypeOption), "Unexpected ResidenceTypeOptions value"),
};
}
public static string GetCrifResidenceTypeName(ResidenceTypeOption residenceTypeOption)
{
return residenceTypeOption switch
{
ResidenceTypeOption.Own => "Own",
ResidenceTypeOption.Rent => "Rent",
ResidenceTypeOption.Other => "Other",
_ => throw new ArgumentOutOfRangeException(nameof(residenceTypeOption), "Unexpected ResidenceTypeOptions value"),
};
}
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan.Individuals
{
public interface IAddresses
{
AddressesSection Addresses { get; }
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan.Individuals
{
public interface IAppParties
{
AppPartiesSection AppParties { get; }
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Loan.Individuals
{
public interface IParties
{
PartiesSection Parties { get; }
}
}
@@ -0,0 +1,36 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using Landmark.OLA.Models.Individuals;
namespace CRIF.ACTion.Loan.Individuals
{
public class IncomeSectionBuilder
{
private readonly Income _income;
public IncomeSectionBuilder(Income income)
{
_income = income;
}
public XElement Build()
{
var incomeElement = new XElement(CrifNamespaces.Common + "income",
new XAttribute(CrifNamespaces.Common + "IncomeTypeCD", _income.IncomeTypeCD),
new XAttribute(CrifNamespaces.Common + "frequency", _income.Frequency),
new XAttribute(CrifNamespaces.Common + "amountPerPeriod", _income.AmountPerPeriod),
new XAttribute(CrifNamespaces.Common + "incomeStatus", _income.IncomeStatus),
new XAttribute(CrifNamespaces.Common + "incomeAdjustmentPercentage", 1m),
new XAttribute(CrifNamespaces.Common + "adjustedIncome", _income.AmountPerPeriod),
new XAttribute(CrifNamespaces.Common + "sourceName", BuilderUtils.FormatCrifData(_income.SourceName)));
if (_income.HoursPerWeek.HasValue)
{
incomeElement.Add(new XAttribute(CrifNamespaces.Common + "hoursPerWeek", _income.HoursPerWeek.Value));
}
return incomeElement;
}
}
}
@@ -0,0 +1,29 @@
namespace CRIF.ACTion.Loan.Individuals
{
public static class MaritalStatus
{
public static string GetMaritalStatusCode(bool isMarried)
{
if (isMarried)
{
return "M";
}
else
{
return "U";
}
}
public static string GetMaritalStatusName(bool isMarried)
{
if (isMarried)
{
return "Married";
}
else
{
return "Unmarried";
}
}
}
}
@@ -0,0 +1,788 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Services.Landmark.DistributionChannel;
using CRIF.ACTion.Services.Landmark.SourceChannel;
using Landmark.OLA.Models;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Individuals.Enums;
using Landmark.OLA.Models.Loan.Enums;
using static CRIF.ACTion.Common.ContactMechanisms;
namespace CRIF.ACTion.Loan.Individuals
{
public class PartiesSection : CrifRequestSection
{
private enum CrifGender
{
NoneSelected,
Female,
NotProvided,
Male,
NotApplicable,
MaleFemale,
}
private readonly IDistributionChannelLookupService _distributionChannelLookupService;
public PartiesSection(XNamespace sectionNamespace,
CrifSettings settings,
IDistributionChannelLookupService distributionChannelLookupService = null)
: base(sectionNamespace, "parties", settings)
{
_distributionChannelLookupService = distributionChannelLookupService ??
new LandmarkDistributionChannelLookupService(settings);
}
public void AddNonApplicant(INonApplicant individual, bool isMarried = false)
{
var individualElement = GetNonApplicantElement(individual, isMarried);
AddContent(individualElement);
}
public void AddApplicant(IApplicant individual,
Guid? externalSpouseId = null,
PropertyLot mfgHomeLot = null)
{
XElement partyRelationships = null;
if (externalSpouseId.HasValue ||
individual.HasAnyNonApplicantSpouse() ||
individual.Employers?.Count > 0)
{
partyRelationships = GetApplicantPartyRelationshipsSectionElement(individual, externalSpouseId);
}
var individualElement = GetInitialIndividualElement(individual, partyRelationships, mfgHomeLot);
SetHmdaData(individual, individualElement);
//this must occur last within the individual element
var maritalStatusElement = GetMaritalStatusElement(individual.IsMarried ?? false);
individualElement.Add(maritalStatusElement);
AddContent(individualElement);
}
public void AddHistory(IIndividualHistory individual, bool isMarried = false, bool requiresHMDA = false)
{
XElement partyRelationships = null;
if (individual.Employers?.Count > 0)
{
partyRelationships = GetHistoryPartyRelationshipsSectionElement(individual);
}
var individualElement = GetInitialIndividualElement(individual, partyRelationships);
if (requiresHMDA)
{
AddNonApplicantHmdaDetermination(individualElement);
}
//this must occur last within the individual element
var maritalStatusElement = GetMaritalStatusElement(isMarried);
individualElement.Add(maritalStatusElement);
AddContent(individualElement);
}
public static void AddNonApplicantHmdaDetermination(XElement individualElement)
{
var ethnicityDeterminationElement = new XElement(CrifNamespaces.Common + "ethnicityDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "T"),
new XAttribute(CrifNamespaces.Common + "name", "No Co-Applicant"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(ethnicityDeterminationElement);
var raceDeterminationElement = new XElement(CrifNamespaces.Common + "raceDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "T"),
new XAttribute(CrifNamespaces.Common + "name", "No Co-Applicant"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(raceDeterminationElement);
var sexDeterminationElement = new XElement(CrifNamespaces.Common + "sexDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "T"),
new XAttribute(CrifNamespaces.Common + "name", "No Co-Applicant"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(sexDeterminationElement);
var applicationTakenMethodElement = new XElement(CrifNamespaces.Common + "applicationTakenMethod",
new XAttribute(CrifNamespaces.Common + "lookupType", "ApplicationTakenMethod"),
new XAttribute(CrifNamespaces.Common + "CD", "2"),
new XAttribute(CrifNamespaces.Common + "name", "Internet"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "description", "Internet"));
individualElement.Add(applicationTakenMethodElement);
}
private XElement GetNonApplicantElement(INonApplicant individual, bool isMarried = false)
{
var individualElement = new XElement(CrifNamespaces.Common + "individual",
new XAttribute(CrifNamespaces.Common + "id", individual.Id),
new XAttribute(CrifNamespaces.Common + "languageID", individual.Language.Value),
new XAttribute(CrifNamespaces.Common + "firstName", BuilderUtils.FormatCrifData(individual.FirstName)),
new XAttribute(CrifNamespaces.Common + "lastName", BuilderUtils.FormatCrifData(individual.LastName)),
new XAttribute(CrifNamespaces.Common + "ssn", individual.SSN ?? "")
);
if (!string.IsNullOrWhiteSpace(individual.MiddleInitial))
{
individualElement.Add(new XAttribute(CrifNamespaces.Common + "middleName", BuilderUtils.FormatCrifData(individual.MiddleInitial)));
}
var contact = new XElement(CrifNamespaces.Common + "contactMechanisms");
var currentAddress = GetAddressContactMechanismElement(individual.MailingAddress.Id, CrifContactMechanism.CurrentAddress);
contact.Add(currentAddress);
individualElement.Add(contact);
//this must occur last within the individual element
var maritalStatusElement = GetMaritalStatusElement(isMarried);
individualElement.Add(maritalStatusElement);
return individualElement;
}
private XElement GetInitialIndividualElement(IIndividualHistory individual,
XElement partyRelationshipElement = null,
PropertyLot mfgHomeLot = null)
{
ModelValidator.Validate(individual);
var individualElement = new XElement(CrifNamespaces.Common + "individual",
new XAttribute(CrifNamespaces.Common + "id", individual.Id),
new XAttribute(CrifNamespaces.Common + "languageID", individual.Language.Value),
new XAttribute(CrifNamespaces.Common + "firstName", BuilderUtils.FormatCrifData(individual.FirstName)),
new XAttribute(CrifNamespaces.Common + "lastName", BuilderUtils.FormatCrifData(individual.LastName)),
new XAttribute(CrifNamespaces.Common + "dob", Dates.GetFormattedDOB(individual.DOB.Value)),
new XAttribute(CrifNamespaces.Common + "ssn", individual.SSN),
new XAttribute(CrifNamespaces.Common + "customerNum", individual.CustomerNumber)
);
if (individual.IsLandmarkEmployee.HasValue)
{
individualElement.Add(new XAttribute(CrifNamespaces.Common + "isEmployeeFL", individual.IsLandmarkEmployee.Value));
}
var contact = GetContactMechanismsSectionElement(individual);
individualElement.Add(contact);
individualElement.Add(partyRelationshipElement); //order matters for this element
if (individual.Employers?.Count > 0 || individual.OtherIncomes?.Count > 0)
{
var incomes = GetIncomesSectionElement(individual);
individualElement.Add(incomes);;
}
var tradesElement = GetTradesElement(individual, mfgHomeLot);
individualElement.Add(tradesElement);
if (individual.Suffix != null)
{
var suffixElement = GetSuffixElement(individual);
individualElement.Add(suffixElement);
}
if (!string.IsNullOrWhiteSpace(individual.MiddleInitial))
{
individualElement.Add(new XAttribute(CrifNamespaces.Common + "middleName", BuilderUtils.FormatCrifData(individual.MiddleInitial)));
}
return individualElement;
}
private void SetHmdaData(IApplicant individual, XElement individualElement)
{
var racesDetermination = false;
if (individual.HmdaValues.Races != null && individual.HmdaValues.Races.Count > 0)
{
var racesElement = GetRacesElement(individual);
individualElement.Add(racesElement);
racesDetermination = true;
}
var ethnicitiesDetermination = false;
if (individual.HmdaValues.Ethnicities != null && individual.HmdaValues.Ethnicities.Count > 0)
{
var ethnicitiesElement = GetEthnicitiesElement(individual);
individualElement.Add(ethnicitiesElement);
ethnicitiesDetermination = true;
}
if (ethnicitiesDetermination)
{
if (!(individual.HmdaValues.Ethnicities.Count == 1 && individual.HmdaValues.Ethnicities.Contains(Ethnicity.NotProvided)))
{
var ethnicityDeterminationElement = new XElement(CrifNamespaces.Common + "ethnicityDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "N"),
new XAttribute(CrifNamespaces.Common + "name", "Not Collected on the Basis of Visual Observation or Surname"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(ethnicityDeterminationElement);
}
}
if (racesDetermination)
{
if (!(individual.HmdaValues.Races.Count == 1 && individual.HmdaValues.Races.Contains(Race.NotProvided)))
{
var raceDeterminationElement = new XElement(CrifNamespaces.Common + "raceDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "N"),
new XAttribute(CrifNamespaces.Common + "name", "Not Collected on the Basis of Visual Observation or Surname"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(raceDeterminationElement);
}
}
if (individual.HmdaValues.Sexes != null && individual.HmdaValues.Sexes.Count > 0)
{
var sexElements = GetSexAttributes(individual);
individualElement.Add(sexElements);
if (!(individual.HmdaValues.Sexes.Count == 1 && individual.HmdaValues.Sexes.Contains(Sex.NotProvided)))
{
var sexDeterminationElement = new XElement(CrifNamespaces.Common + "sexDetermination",
new XAttribute(CrifNamespaces.Common + "lookupType", "GMIDetermination"),
new XAttribute(CrifNamespaces.Common + "CD", "N"),
new XAttribute(CrifNamespaces.Common + "name", "Not Collected on the Basis of Visual Observation or Surname"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "externalSystemCode", "3"));
individualElement.Add(sexDeterminationElement);
}
}
var applicationTakenMethodElement = GetApplicationTakenMethodElement(individual);
individualElement.Add(applicationTakenMethodElement);
}
public XElement GetApplicationTakenMethodElement(IApplicant individual)
{
if (!GetApplicationTakenMethodElementRequired(individual))
{
return null;
}
var applicationTakenMethodElement = new XElement(CrifNamespaces.Common + "applicationTakenMethod",
new XAttribute(CrifNamespaces.Common + "lookupType", "ApplicationTakenMethod"),
new XAttribute(CrifNamespaces.Common + "CD", "2"),
new XAttribute(CrifNamespaces.Common + "name", "Internet"),
new XAttribute(CrifNamespaces.Common + "isActive", true),
new XAttribute(CrifNamespaces.Common + "description", "Internet"));
return applicationTakenMethodElement;
}
public static XElement GetSuffixElement(IIndividualHistory individual)
{
var suffixType = SuffixMap.GetSuffixType(individual.Suffix);
var suffixElement = new XElement(CrifNamespaces.Common + "suffix",
new XAttribute(CrifNamespaces.Common + "lookupType", "Suffix"),
new XAttribute(CrifNamespaces.Common + "CD", suffixType.CD),
new XAttribute(CrifNamespaces.Common + "name", suffixType.Name),
new XAttribute(CrifNamespaces.Common + "isActive", true));
return suffixElement;
}
public static XElement GetTradesElement(IIndividualHistory individual, PropertyLot mfgHomeLot)
{
var tradesElement = new XElement(CrifNamespaces.Common + "trades");
var mortgageOrRentTradeElement = new XElement(CrifNamespaces.Common + "trade",
new XAttribute(CrifNamespaces.Common + "accountTypeCD", "RealEstateMortgage"),
new XAttribute(CrifNamespaces.Common + "tradeTypeID", "Mortgage"),
new XAttribute(CrifNamespaces.Common + "creditorName", "Rent - Mortgage"),
new XAttribute(CrifNamespaces.Common + "debtStatusCD", "Include"),
new XAttribute(CrifNamespaces.Common + "ecoaCD", "Individual"),
new XAttribute(CrifNamespaces.Common + "isCurrResidenceRentMortgage", true),
new XAttribute(CrifNamespaces.Common + "monthlyPayment", individual.Address.MonthlyPayment),
new XElement(CrifNamespaces.Common + "kindOfBusiness",
new XAttribute(CrifNamespaces.Common + "lookupType", "KOB"),
new XAttribute(CrifNamespaces.Common + "CD", "FM")));
tradesElement.Add(mortgageOrRentTradeElement);
if ((!(mfgHomeLot is null)) && mfgHomeLot.RentalAmt.HasValue)
{
var mfgHomeLotRentalElement = new XElement(CrifNamespaces.Common + "trade",
new XAttribute(CrifNamespaces.Common + "tradeTypeID", "Mortgage"),
new XAttribute(CrifNamespaces.Common + "creditorName", "Lot Rent"),
new XAttribute(CrifNamespaces.Common + "debtStatusCD", "Include"),
new XAttribute(CrifNamespaces.Common + "ecoaCD", "Individual"),
new XAttribute(CrifNamespaces.Common + "monthlyPayment", mfgHomeLot.RentalAmt.Value),
new XElement(CrifNamespaces.Common + "kindOfBusiness",
new XAttribute(CrifNamespaces.Common + "lookupType", "KOB"),
new XAttribute(CrifNamespaces.Common + "CD", "FM")));
tradesElement.Add(mfgHomeLotRentalElement);
}
if (individual.OtherIncomes?.Count > 0)
{
foreach (var income in individual.OtherIncomes)
{
if (income.IncomeTypeCD == IncomeType.Alimony)
{
double monthlyAlimony = (double)income.AmountPerPeriod / 12;
var alimonyElement = new XElement(CrifNamespaces.Common + "trade",
new XAttribute(CrifNamespaces.Common + "tradeTypeID", "Open"),
new XAttribute(CrifNamespaces.Common + "creditorName", "Alimony/Child Support - Payment"),
new XAttribute(CrifNamespaces.Common + "debtStatusCD", "Include"),
new XAttribute(CrifNamespaces.Common + "ecoaCD", "Individual"),
new XAttribute(CrifNamespaces.Common + "monthlyPayment", monthlyAlimony),
new XElement(CrifNamespaces.Common + "kindOfBusiness",
new XAttribute(CrifNamespaces.Common + "lookupType", "KOB"),
new XAttribute(CrifNamespaces.Common + "CD", "VK")));
tradesElement.Add(alimonyElement);
}
}
}
return tradesElement;
}
private static bool GetApplicationTakenMethodElementRequired(IApplicant individual)
{
var hasRaces = individual.HmdaValues.Races != null && individual.HmdaValues.Races.Count > 0;
var hasEthnicities = individual.HmdaValues.Ethnicities != null && individual.HmdaValues.Ethnicities.Count > 0;
var hasSexes = individual.HmdaValues.Sexes != null && individual.HmdaValues.Sexes.Count > 0;
return hasRaces || hasEthnicities || hasSexes;
}
public static XAttribute[] GetSexAttributes(IApplicant individual)
{
var attributeList = new List<XAttribute>();
var hasMale = individual.HmdaValues.Sexes.Contains(Sex.Male);
var hasFemale = individual.HmdaValues.Sexes.Contains(Sex.Female);
var isNotProvided = individual.HmdaValues.Sexes.Contains(Sex.NotProvided);
var genderCDValue = CrifGender.NoneSelected;
if (isNotProvided && !hasMale && !hasFemale)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "genderCD", Sex.NotProvided));
return [.. attributeList];
}
if (hasMale)
{
if (hasFemale)
{
genderCDValue = CrifGender.MaleFemale;
}
else
{
genderCDValue = CrifGender.Male;
}
}
else if (hasFemale)
{
genderCDValue = CrifGender.Female;
}
if (genderCDValue != CrifGender.NoneSelected)
{
attributeList.Add(new XAttribute(CrifNamespaces.Common + "genderCD", genderCDValue.ToString()));
}
//Yes, you both could provide an answer and say that you don't wish to provide an answer
//Validation will likely restrict that
//Ignore Not Provided if Other Values are Selected
attributeList.Add(new XAttribute(CrifNamespaces.Common + "refusedToFurnishGenderFL", false));
return [.. attributeList];
}
public static XElement GetRacesElement(IApplicant individual)
{
var racesElement = new XElement(CrifNamespaces.Common + "races");
if (individual.HmdaValues.Races.Count == 1 && individual.HmdaValues.Races.Contains(Race.NotProvided)){
var raceElement = new XElement(CrifNamespaces.Common + "race",
new XAttribute(CrifNamespaces.Common + "isCheckedFL", true),
new XAttribute(CrifNamespaces.Common + "race", Race.NotProvided.ToString()));
racesElement.RemoveNodes();
racesElement.Add(raceElement);
return racesElement;
}
foreach (Race raceOption in (Race[])Enum.GetValues(typeof(Race)))
{
var isChecked = individual.HmdaValues.Races.Contains(raceOption);
bool isNotProvided = (raceOption == Race.NotProvided);
string otherRaceInfoValue = null;
switch (raceOption)
{
case Race.AmericanIndianAlaskaNative:
otherRaceInfoValue = individual.HmdaValues.AmericanIndianAlaskaNativeTribe;
break;
case Race.OtherAsian:
otherRaceInfoValue = individual.HmdaValues.OtherAsianRace;
break;
case Race.OtherPacificIslander:
otherRaceInfoValue = individual.HmdaValues.OtherPacificIslanderRace;
break;
}
var hasOtherValue = !string.IsNullOrWhiteSpace(otherRaceInfoValue);
//Ignore not provided if it is not the only ethnicity selected
if ( (isChecked || hasOtherValue) && !isNotProvided)
{
var raceElement = new XElement(CrifNamespaces.Common + "race",
new XAttribute(CrifNamespaces.Common + "isCheckedFL", isChecked),
new XAttribute(CrifNamespaces.Common + "race", raceOption.ToString()));
if (hasOtherValue)
{
raceElement.Add(new XAttribute(CrifNamespaces.Common + "otherRaceInfo", BuilderUtils.FormatCrifData(otherRaceInfoValue)));
}
racesElement.Add(raceElement);
}
}
return racesElement;
}
public static XElement GetEthnicitiesElement(IApplicant individual)
{
var ethnicitiesElement = new XElement(CrifNamespaces.Common + "ethnicities");
if (individual.HmdaValues.Ethnicities.Count == 1 && individual.HmdaValues.Ethnicities.Contains(Ethnicity.NotProvided)){
var ethnicityElement = new XElement(CrifNamespaces.Common + "ethnicity",
new XAttribute(CrifNamespaces.Common + "isCheckedFL", true),
new XAttribute(CrifNamespaces.Common + "ethnicity", Ethnicity.NotProvided.ToString()));
ethnicitiesElement.RemoveNodes();
ethnicitiesElement.Add(ethnicityElement);
return ethnicitiesElement;
}
foreach (Ethnicity ethnicityOption in (Ethnicity[])Enum.GetValues(typeof(Ethnicity)))
{
var isChecked = individual.HmdaValues.Ethnicities.Contains(ethnicityOption);
bool isNotProvided = (ethnicityOption == Ethnicity.NotProvided);
string otherEthnicityInfo = null;
switch (ethnicityOption)
{
case Ethnicity.OtherHispanicLatino:
otherEthnicityInfo = individual.HmdaValues.OtherHispanicLatinoEthnicity;
break;
}
var hasOtherValue = !string.IsNullOrWhiteSpace(otherEthnicityInfo);
//Ignore not provided if it is not the only ethnicity selected
if ( (isChecked || hasOtherValue) && !isNotProvided)
{
var ethnicityElement = new XElement(CrifNamespaces.Common + "ethnicity",
new XAttribute(CrifNamespaces.Common + "isCheckedFL", isChecked),
new XAttribute(CrifNamespaces.Common + "ethnicity", ethnicityOption.ToString()));
if (hasOtherValue)
{
ethnicityElement.Add(new XAttribute(CrifNamespaces.Common + "otherEthnicityInfo", BuilderUtils.FormatCrifData(otherEthnicityInfo)));
}
ethnicitiesElement.Add(ethnicityElement);
}
}
return ethnicitiesElement;
}
public static XElement GetIncomesSectionElement(IIndividualHistory individual)
{
var incomesElement = new XElement(CrifNamespaces.Common + "incomes");
if (individual.Employers?.Count > 0)
{
foreach (var employer in individual.Employers)
{
var income = employer.Income;
ModelValidator.Validate(income);
var incomeElement = new IncomeSectionBuilder(income).Build();
var employerRefElement = new XElement(CrifNamespaces.Common + "employerRef",
new XAttribute(CrifNamespaces.Common + "idRef", employer.GetOrCreateGuid()));
incomeElement.Add(employerRefElement);
incomesElement.Add(incomeElement);
}
}
if (individual.OtherIncomes?.Count > 0)
{
foreach (var income in individual.OtherIncomes)
{
if (income.IncomeTypeCD != IncomeType.Alimony)
{
var incomeElement = new IncomeSectionBuilder(income).Build();
incomesElement.Add(incomeElement);
}
}
}
return incomesElement;
}
public static XElement GetHistoryPartyRelationshipsSectionElement(IIndividualHistory individual)
{
var prElement = new XElement(CrifNamespaces.Common + "partyRelationships");
if (individual.Employers?.Count > 0)
{
foreach (var employer in individual.Employers)
{
ModelValidator.Validate(employer);
var employmentType = employer.EmploymentType;
var employmentElement = new XElement(CrifNamespaces.Common + "employment",
new XAttribute(CrifNamespaces.Common + "relationshipType", "Employment"));
var partyRefElement = new XElement(CrifNamespaces.Common + "partyRef",
new XAttribute(CrifNamespaces.XSI + "type", $"{CrifNamespaces.Common.Prefix}:EmployerRef"),
new XAttribute(CrifNamespaces.Common + "idRef", employer.GetOrCreateGuid()),
new XAttribute(CrifNamespaces.Common + "partyType", "Employer"));
var employmentSubRelElement = new XElement(CrifNamespaces.Common + "employmentSubRel",
new XAttribute(CrifNamespaces.Common + "isCurrentFL", employer.CurrentEmployer),
new XAttribute(CrifNamespaces.Common + "title", BuilderUtils.FormatCrifData(employer.Title)),
new XAttribute(CrifNamespaces.Common + "monthsEmployed", employer.MonthsEmployed),
new XAttribute(CrifNamespaces.Common + "isSelfEmployed", false), //TODO #239: is this always false?
new XAttribute(CrifNamespaces.Common + "employmentType", employmentType));
var userEmploymentTypeElement = new XElement(CrifNamespaces.Common + "userEmploymentType",
new XAttribute(CrifNamespaces.Common + "lookupType", "UserEmploymentType"), //TODO #240: Does this have other values?
new XAttribute(CrifNamespaces.Common + "CD", UserEmploymentType.GetCrifUserEmploymentTypeIndex(employmentType)), //TODO #240
new XAttribute(CrifNamespaces.Common + "name", UserEmploymentType.GetCrifUserEmploymentTypeName(employmentType)));
employmentSubRelElement.Add(userEmploymentTypeElement);
employmentElement.Add(partyRefElement);
employmentElement.Add(employmentSubRelElement);
prElement.Add(employmentElement);
}
}
return prElement;
}
public static XElement GetApplicantPartyRelationshipsSectionElement(IApplicant individual,
Guid? externalSpouseId = null)
{
var prElement = GetHistoryPartyRelationshipsSectionElement(individual);
prElement.Add(GetMarriageElement(individual, externalSpouseId));
return prElement;
}
private static XElement GetMarriageElement(IApplicant individual, Guid? externalSpouseId = null)
{
if (!(individual.HasAnyNonApplicantSpouse() || externalSpouseId.HasValue))
{
return null;
}
Guid spouseId;
if (externalSpouseId.HasValue)
{
spouseId = externalSpouseId.Value;
}
else
{
if (individual.NonApplicantSpouse is not null)
{
spouseId = individual.NonApplicantSpouse.Id;
}
else
{
spouseId = individual.DetailedNonApplicantSpouse.Id;
}
}
var partyRef = new XElement(CrifNamespaces.Common + "partyRef",
new XAttribute(CrifNamespaces.XSI + "type", $"{CrifNamespaces.Common.Prefix}:IndividualRef"),
new XAttribute(CrifNamespaces.Common + "idRef", spouseId),
new XAttribute(CrifNamespaces.Common + "partyType", "Individual"));
var marriageRelationshipElement = new XElement(CrifNamespaces.Common + "partyRelationship",
new XAttribute(CrifNamespaces.Common + "relationshipType", "Marriage"),
partyRef);
return marriageRelationshipElement;
}
public static XElement GetMaritalStatusElement(bool isMarried)
{
var maritalStatusElement = new XElement(CrifNamespaces.Common + "maritalStatus",
new XAttribute(CrifNamespaces.Common + "lookupType", "MaritalStatus"),
new XAttribute(CrifNamespaces.Common + "CD", MaritalStatus.GetMaritalStatusCode(isMarried)),
new XAttribute(CrifNamespaces.Common + "name", MaritalStatus.GetMaritalStatusName(isMarried)));
return maritalStatusElement;
}
public static XElement GetContactMechanismsSectionElement(IIndividualHistory individual)
{
var contact = new XElement(CrifNamespaces.Common + "contactMechanisms");
var mechanismType = ContactMechanisms.GetPhoneType(individual.PhoneType.Value);
var primaryPhone = GetContactMechanismElement(individual.Phone, mechanismType);
var email = GetContactMechanismElement(individual.Email, ContactMechanisms.CrifContactMechanism.EMail);
var currentAddress = GetAddressContactMechanismElement(individual.Address.Id, ContactMechanisms.CrifContactMechanism.CurrentAddress);
contact.Add(primaryPhone);
contact.Add(email);
contact.Add(currentAddress);
if (individual.SecondaryPhone?.Length > 0)
{
mechanismType = ContactMechanisms.GetPhoneType(individual.SecondaryPhoneType.Value);
var secondaryPhone = GetContactMechanismElement(individual.SecondaryPhone, mechanismType);
contact.Add(secondaryPhone);
}
if (individual.MailingAddress is not null)
{
var mailingAddressElement = GetAddressContactMechanismElement(individual.MailingAddress.Id, ContactMechanisms.CrifContactMechanism.MailingAddress);
contact.Add(mailingAddressElement);
}
else
{
var mailingAddressElement = GetAddressContactMechanismElement(individual.Address.Id,ContactMechanisms.CrifContactMechanism.MailingAddress);
contact.Add(mailingAddressElement);
}
if (individual.PreviousAddresses?.Count > 0)
{
foreach (var previousAddress in individual.PreviousAddresses)
{
var previousAddressElement = GetAddressContactMechanismElement(previousAddress.Id, ContactMechanisms.CrifContactMechanism.PreviousAddress);
contact.Add(previousAddressElement);
}
}
return contact;
}
public static XElement GetAddressContactMechanismElement(Guid addressRef, ContactMechanisms.CrifContactMechanism contactMechanismType)
{
var contactMechanismCategory = ContactMechanisms.GetContactMechanismCategory(contactMechanismType);
var contactMechanism = new XElement(CrifNamespaces.Common + "contactMechanism",
new XAttribute(CrifNamespaces.Common + "contactMechanismType", contactMechanismType),
new XElement(CrifNamespaces.Common + "addressRef",
new XAttribute(CrifNamespaces.Common + "idRef", addressRef)
),
new XElement(CrifNamespaces.Common + "contactMechanismCategory",
new XAttribute(CrifNamespaces.Common + "CD", (int)contactMechanismCategory)
)
);
return contactMechanism;
}
public static XElement GetEmployerContactMechanismsSectionElement(Employer employer)
{
var contact = new XElement(CrifNamespaces.Common + "contactMechanisms");
if (!string.IsNullOrWhiteSpace(employer.WorkPhone))
{
contact.Add(GetContactMechanismElement(employer.WorkPhone, CrifContactMechanism.WorkPhone));
}
return contact;
}
public static XElement GetContactMechanismElement(string contactString, CrifContactMechanism contactMechanismType)
{
var contactMechanismCategory = GetContactMechanismCategory(contactMechanismType);
var contactMechanism = new XElement(CrifNamespaces.Common + "contactMechanism",
new XAttribute(CrifNamespaces.Common + "contactMechanismType", contactMechanismType),
new XAttribute(CrifNamespaces.Common + "contactString", BuilderUtils.FormatCrifData(contactString)),
new XElement(CrifNamespaces.Common + "contactMechanismCategory",
new XAttribute(CrifNamespaces.Common + "CD", (int)contactMechanismCategory)
)
);
return contactMechanism;
}
public void AddOrganizationParty(Guid orgId)
{
var organization = new XElement(CrifNamespaces.Common + "organization",
new XAttribute(CrifNamespaces.Common + "id", orgId),
new XAttribute(CrifNamespaces.Common + "partyID", 4)); //Per CRIF Documentation
AddContent(organization);
}
public void AddSourceChannelParty(Guid channelId, LoanType loanType)
{
var channelLookup = new LandmarkSourceChannelLookupService(_settings);
int partyId = channelLookup.GetSourceChannelByLoanType(loanType);
AddChannelParty(channelId, partyId);
}
public void AddServicingChannelParty(Guid channelId, string zipCode)
{
var partyId = _distributionChannelLookupService.GetDistributionChannelByZipCode(zipCode);
AddChannelParty(channelId, partyId);
}
private void AddChannelParty(Guid channelId, int partyId)
{
var channel = new XElement(CrifNamespaces.Common + "channel",
new XAttribute(CrifNamespaces.Common + "id", channelId),
new XAttribute(CrifNamespaces.Common + "partyID", partyId));
AddContent(channel);
}
public void AddEmployer(Employer employer)
{
ModelValidator.Validate(employer);
var employerElement = new XElement(CrifNamespaces.Common + "employer",
new XAttribute(CrifNamespaces.Common + "id", employer.GetOrCreateGuid()),
new XAttribute(CrifNamespaces.Common + "name", BuilderUtils.FormatCrifData(employer.Name)),
new XAttribute(CrifNamespaces.Common + "languageID", employer.Language));
var contact = GetEmployerContactMechanismsSectionElement(employer);
employerElement.Add(contact);
AddContent(employerElement);
}
}
}
@@ -0,0 +1,38 @@
using Landmark.OLA.Models.Individuals.Enums;
namespace CRIF.ACTion.Loan.Individuals
{
public static class UserEmploymentType
{
public static string GetCrifUserEmploymentTypeIndex(EmploymentTypeOption userEmploymentTypeOption)
{
return userEmploymentTypeOption switch
{
EmploymentTypeOption.NoneSelected => "0",
EmploymentTypeOption.Contract => "1",
EmploymentTypeOption.FullTime => "2",
EmploymentTypeOption.PartTime => "3",
EmploymentTypeOption.SelfEmployed => "4",
EmploymentTypeOption.Retired => "5",
EmploymentTypeOption.Temporary => "6",
_ => throw new ArgumentOutOfRangeException(nameof(userEmploymentTypeOption), "Unexpected UserEmploymentType value"),
};
}
public static string GetCrifUserEmploymentTypeName(EmploymentTypeOption userEmploymentTypeOption)
{
return userEmploymentTypeOption switch
{
EmploymentTypeOption.NoneSelected => "None Selected",
EmploymentTypeOption.Contract => "Contract",
EmploymentTypeOption.FullTime => "Full Time",
EmploymentTypeOption.PartTime => "Part Time",
EmploymentTypeOption.SelfEmployed => "Self Employed",
EmploymentTypeOption.Retired => "Retired",
EmploymentTypeOption.Temporary => "Temporary",
_ => throw new ArgumentOutOfRangeException(nameof(userEmploymentTypeOption), "Unexpected UserEmploymentType value"),
};
}
}
}
@@ -0,0 +1,384 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Services.Landmark.DistributionChannel;
using CRIF.ACTion.Loan.Assets;
using CRIF.ACTion.Loan.Individuals;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Loan;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Loan
{
public class LoanApplicationRequestBuilder : BuilderBase, IParties, IAppDetails, IAppParties,
IAddresses, IOrg, IProperties, IVehicleValuations, IAppNotes
{
public readonly LoanApplication LoanApplication;
private readonly Guid OrgRef;
public readonly Guid _sourceChannel;
public readonly Guid _servicingChannel;
public AppPartiesSection AppParties { get; set; }
public PartiesSection Parties { get; set; }
public AppDetailsSection AppDetails { get; set; }
public AddressesSection Addresses { get; set; }
public AppNotesSectionBuilder AppNotes { get; set; }
public OrgSection Org { get; set; }
public PropertiesSection Properties { get; set; }
public VehicleValuationsSection VehicleValuations { get; set; }
public LoanApplicationRequestBuilder(LoanApplication loanApplication,
CrifSettings settings,
Guid? orgRef = null,
Guid? sourceChannel = null,
Guid? servicingChannel = null,
IDistributionChannelLookupService distributionChannelLookupService = null)
: base(CrifNamespaces.App.Namespace, settings)
{
LoanApplication = loanApplication;
OrgRef = orgRef ?? Guid.NewGuid();
_sourceChannel = sourceChannel ?? Guid.NewGuid();
_servicingChannel = servicingChannel ?? Guid.NewGuid();
AppParties = new AppPartiesSection(CrifRequestSectionNamespace, settings);
Parties = new PartiesSection(CrifRequestSectionNamespace, settings, distributionChannelLookupService);
Addresses = new AddressesSection(CrifRequestSectionNamespace, settings);
AppNotes = new AppNotesSectionBuilder(CrifRequestSectionNamespace, settings);
Org = new OrgSection(CrifRequestSectionNamespace, settings);
Properties = new PropertiesSection(CrifRequestSectionNamespace, settings);
VehicleValuations = new VehicleValuationsSection(CrifRequestSectionNamespace, settings);
}
public override XElement Build(params XElement[] content)
{
Org.SetOrgId(OrgRef);
Parties.AddOrganizationParty(OrgRef);
Parties.AddSourceChannelParty(_sourceChannel, LoanApplication.LoanType.Value);
Parties.AddServicingChannelParty(_servicingChannel, LoanApplication?.PrimaryBorrower?.Address?.PostalCode ?? "");
AppParties.SetSourceChannel(_sourceChannel);
AppParties.SetServicingChannel(_servicingChannel);
AddApplicationNotes();
var productDetails = CreateProductDetails(LoanApplication.LoanType, _settings);
productDetails.SetDetails(this);
Guid? externalSpouseId = null;
if (LoanApplication.CoApplicant is not null && LoanApplication.CoApplicant.IsMarriedToPrimaryBorrower)
{
externalSpouseId = LoanApplication.CoApplicant.Id;
}
PropertyLot mfgHomeLot = null;
if (!(LoanApplication.ManufacturedHome?.Lot?.RentalAmt is null))
{
//needed to pass long possible Lot rent amount
mfgHomeLot = LoanApplication.ManufacturedHome.Lot;
}
ProcessIndividual(LoanApplication.PrimaryBorrower,
AppPartiesSection.PartyType.primaryBorrower,
externalSpouseId,
mfgHomeLot);
if (LoanApplication.CoApplicant is not null)
{
ProcessIndividual(LoanApplication.CoApplicant, AppPartiesSection.PartyType.borrower);
CheckForAndProcessSpouses(LoanApplication.CoApplicant);
}
CheckForAndProcessSpouses(LoanApplication.PrimaryBorrower, true);
var createAppElement = GetCreateAppElement(LoanApplication.Date,
AppParties.Build(),
Parties.Build(),
AppDetails.Build(),
Properties.Build(),
Addresses.Build(),
AppNotes.Build(),
Org.Build(),
VehicleValuations.Build());
var baseElement = base.Build(createAppElement);
return baseElement;
}
private void AddApplicationNotes()
{
AppNotes.AddAppNote("IP: " + LoanApplication.IPAddress + ", DISCLOSURE DATE: " + ConvertUtcToCentral(LoanApplication.DisclosureDate));
if (!string.IsNullOrWhiteSpace(LoanApplication.PrimaryContactMethod))
{
AppNotes.AddAppNote("PRIMARY CONTACT METHOD: " + LoanApplication.PrimaryContactMethod);
}
if (!string.IsNullOrWhiteSpace(LoanApplication.PromoCode))
{
AppNotes.AddAppNote("PROMO CODE: " + LoanApplication.PromoCode);
}
if (!string.IsNullOrWhiteSpace(LoanApplication.Note))
{
AppNotes.AddAppNote(LoanApplication.Note);
}
if (LoanApplication.RealEstate is not null && LoanApplication.RealEstate.LienPosition is not null &&
LoanApplication.LoanTitle != "Heloc Rate Modification")
{
if (LoanApplication.RealEstate.LienPosition.PayoffFirstMortgage)
{
AppNotes.AddAppNote("I INTEND TO PAYOFF MY FIRST MORTGAGE WITH THIS LOAN.");
}
if (LoanApplication.RealEstate.LienPosition.PayoffSecondMortgage)
{
AppNotes.AddAppNote("I INTEND TO PAYOFF MY SECOND MORTGAGE WITH THIS LOAN.");
}
}
if (LoanApplication.LoanType == LoanType.S_Special)
{
AppNotes.AddAppNote("Special Loan: " + LoanApplication.LoanTitle);
AppNotes.AddAppNote("Client Message: " + LoanApplication.ClientMessage);
}
if (LoanApplication.LoanType == LoanType.S_Signature && !string.IsNullOrWhiteSpace(LoanApplication.PurposeOfLoanSignature))
{
AppNotes.AddAppNote("PURPOSE OF LOAN: " + LoanApplication.PurposeOfLoanSignature);
}
if (LoanApplication.LoanType == LoanType.EQ_HELOC_Flex) {
if (!string.IsNullOrWhiteSpace(LoanApplication.ModFeeAccount))
{
AppNotes.AddAppNote("MOD FEE ACCOUNT: " + LoanApplication.ModFeeAccount);
}
if (!string.IsNullOrWhiteSpace(LoanApplication.PrimaryBorrower.PrimaryApplicantEmail))
{
AppNotes.AddAppNote("PRIMARY APPLICANT EMAIL (E-DOC SIGNATURE): " + LoanApplication.PrimaryBorrower.PrimaryApplicantEmail);
}
if (!string.IsNullOrWhiteSpace(LoanApplication.PrimaryBorrower.CoApplicantEmail))
{
AppNotes.AddAppNote("CO-APPLICANT EMAIL (E-DOC SIGNATURE): " + LoanApplication.PrimaryBorrower.CoApplicantEmail);
}
}
if (LoanApplication.Vehicle != null)
{
if (LoanApplication.Vehicle.Type == Landmark.OLA.Models.Assets.Enums.VehicleType.Motorcycle
&& !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.StateTitled))
{
AppNotes.AddAppNote("State of Registration: " + LoanApplication.Vehicle.StateTitled);
}
if (LoanApplication.Vehicle.Lien == "yes")
{
AppNotes.AddAppNote("Vehicle has a lien");
}
var seller = LoanApplication.Vehicle?.Seller;
if (seller != null)
{
var sellerStr = "INTERESTED IN BUYING FROM A " + seller.Value;
sellerStr = sellerStr.Replace('_', ' ');
if (!string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerName) || !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerPhone))
{
bool bothSeller = !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerName) && !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerPhone);
sellerStr += ":";
sellerStr += !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerName) ? " " + LoanApplication.Vehicle.DealerName : "";
sellerStr += bothSeller ? " - " : "";
sellerStr += !string.IsNullOrWhiteSpace(LoanApplication.Vehicle.DealerPhone) ? " " + String.Format("{0:(###) ###-####}", LoanApplication.Vehicle.DealerPhone) : "";
}
AppNotes.AddAppNote(sellerStr);
}
}
}
private void CheckForAndProcessSpouses(IApplicant individual, bool isPrimaryBorrowerSpouse = false)
{
var partyType = isPrimaryBorrowerSpouse ? AppPartiesSection.PartyType.NonSigningSpouseMarriedToPrimary
: AppPartiesSection.PartyType.NonSigningSpouseMarriedToCoBorrower;
//Validation can esnure that only one of these holds data
if (individual.NonApplicantSpouse is not null)
{
ProcessIndividual(individual.NonApplicantSpouse, partyType);
}
if (individual.DetailedNonApplicantSpouse is not null)
{
ProcessIndividual(individual.DetailedNonApplicantSpouse, partyType);
}
}
private void ProcessIndividual(INonApplicant individual, AppPartiesSection.PartyType partyType)
{
var isMarried = partyType == AppPartiesSection.PartyType.NonSigningSpouseMarriedToCoBorrower
|| partyType == AppPartiesSection.PartyType.NonSigningSpouseMarriedToPrimary;
Parties.AddNonApplicant(individual, isMarried);
Addresses.AddAddress(individual.MailingAddress,
ContactMechanisms.CrifContactMechanism.CurrentAddress);
}
private void ProcessIndividual(IApplicant individual,
AppPartiesSection.PartyType partyType,
Guid? externalSpouseId = null,
PropertyLot mfgHomeLot = null)
{
Parties.AddApplicant(individual, externalSpouseId, mfgHomeLot);
ProcessHistory(individual, partyType);
}
private void ProcessIndividual(IIndividualHistory individual, AppPartiesSection.PartyType partyType)
{
var isMarried = partyType == AppPartiesSection.PartyType.NonSigningSpouseMarriedToCoBorrower
|| partyType == AppPartiesSection.PartyType.NonSigningSpouseMarriedToPrimary;
bool requiresHMDA = IsHMDARequired(LoanApplication.LoanType.ToString());
Parties.AddHistory(individual, isMarried, requiresHMDA);
ProcessHistory(individual, partyType);
}
public static bool IsHMDARequired(string loanType)
{
HMDALoanType parsedLoanType;
if (Enum.TryParse(loanType, out parsedLoanType))
{
return true;
}
else
{
return false;
}
}
private void ProcessHistory(IIndividualHistory individual, AppPartiesSection.PartyType partyType)
{
AddHistory(individual, partyType);
if (individual.Employers?.Count > 0)
{
foreach (var employer in individual.Employers)
{
Parties.AddEmployer(employer);
}
}
}
public static XElement GetCreateAppElement(DateTime date, params XElement[] content)
{
var element = new XElement(CrifNamespaces.Empty + "createApp",
new XElement(CrifNamespaces.App + "application",
new XAttribute(CrifNamespaces.App + "appReceivedDate",
Dates.GetFormattedApplicationDate(date)),
new XAttribute(CrifNamespaces.App + "appInitiatedDate",
Dates.GetFormattedApplicationDate(date)),
new XAttribute(CrifNamespaces.App + "priorityCD", "High"), //Per vendor examples
content));
return element;
}
private void AddHistory(IIndividualHistory individual, AppPartiesSection.PartyType partyType)
{
AppParties.AddIndividualParty(individual, partyType, individual.HasOptedOutTCPA);
Addresses.AddAddress(individual.Address,
ContactMechanisms.CrifContactMechanism.CurrentAddress);
if (!(individual.MailingAddress is null))
{
Addresses.AddAddress(individual.MailingAddress, ContactMechanisms.CrifContactMechanism.MailingAddress);
}
if (individual.PreviousAddresses?.Count > 0)
{
foreach (var previousAddress in individual.PreviousAddresses)
{
Addresses.AddAddress(previousAddress, ContactMechanisms.CrifContactMechanism.PreviousAddress);
}
}
}
private ILoanApplicationRequestBuilder CreateProductDetails(LoanType? loanType, CrifSettings settings)
{
if (!loanType.HasValue)
throw new ArgumentException("LoanType must have a value.");
if (settings == null)
throw new ArgumentException("Product Details requires CrifSettings");
ILoanApplicationRequestBuilder appBuilder = null;
switch (loanType.Value)
{
case LoanType.CC_CashBack:
case LoanType.CC_Rewards:
case LoanType.CC_Student:
case LoanType.CC_Secure:
appBuilder = new CreditCardAppBuilder(settings);
break;
case LoanType.EQ_HELOC_Flex:
appBuilder = new HelocFlexAppBuilder(settings);
break;
case LoanType.EQ_Mtg_Refi_Express:
case LoanType.EQ_Home_Equity_Fixed:
appBuilder = new RealEstateEquityAppBuilder(settings);
break;
case LoanType.EQ_Mfg_Home_With_or_Without_Land:
appBuilder = new MfgHousingAndLandAppBuilder(settings);
break;
case LoanType.V_VehiclePreapproval:
case LoanType.V_Vehicle:
case LoanType.M_Boat:
case LoanType.P_Powersport:
case LoanType.T_TrailerMotorhome:
appBuilder = new VehicleAppBuilder(settings);
break;
case LoanType.S_Special:
case LoanType.S_Signature:
appBuilder = new SignatureAppBuilder(settings);
break;
default:
throw new ArgumentOutOfRangeException(nameof(loanType), $"The requested loan type {loanType.Value} is not implemented.");
}
return appBuilder;
}
private static string ConvertUtcToCentral(DateTime date)
{
TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateTime centralDate = TimeZoneInfo.ConvertTimeFromUtc(date.ToUniversalTime(), timeZone);
return centralDate.ToString("g");
}
}
}
@@ -0,0 +1,25 @@
using System.Xml.Linq;
using CRIF.ACTion.Common.Parsers;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Loan
{
public class LoanApplicationResponseParser : IResponseParser<LoanApplication>
{
public LoanApplication Parse(XElement xdoc)
{
try
{
//TODO: This entire parser should be rewritten to use proper XPath-style lookups
var idElement = (XElement)((XElement)((XElement)((XElement)xdoc.FirstNode).FirstNode).FirstNode).NextNode;
return new LoanApplication(loanId: idElement.Value);
}
catch
{
throw new FormatException("CRIF XML is in an unexpected layout.");
}
}
}
}
@@ -0,0 +1,24 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class OrgSection : CrifRequestSection
{
public OrgSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "org", settings)
{
}
public void SetOrgId(Guid orgId)
{
var orgAttribute = new XAttribute(CrifNamespaces.Common + "idRef", orgId);
AddContent(orgAttribute);
}
}
}
@@ -0,0 +1,19 @@
using System.Xml.Linq;
using CRIF.ACTion.Common.Builders;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class PropertiesSection : CrifRequestSection
{
public PropertiesSection(XNamespace sectionNamespace, CrifSettings settings)
: base(sectionNamespace, "properties", settings)
{
}
public void AddProperty(IProperty property)
{
AddContent(property.Build());
}
}
}
@@ -0,0 +1,29 @@
using System.Xml.Linq;
using CRIF.ACTion.Common;
namespace CRIF.ACTion.Loan
{
public class SavingsAccountProperty : IProperty
{
private readonly Guid _Id;
private readonly decimal _Value;
public SavingsAccountProperty(Guid id, decimal value)
{
_Id = id;
_Value = value;
}
public XElement Build(params XElement[] content)
{
var element = new XElement(CrifNamespaces.Common + "propertyStocksAndBonds",
new XAttribute(CrifNamespaces.Common + "id", _Id.ToString()),
new XAttribute(CrifNamespaces.Common + "finalEstimatedValue", _Value),
new XAttribute(CrifNamespaces.Common + "propertyType", "CD"), //from CRIF Example
new XAttribute(CrifNamespaces.Common + "description", "CDs or Savings")); //from CRIF Example
return element;
}
}
}
@@ -0,0 +1,21 @@
using System.Xml.Linq;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Loan
{
public class SignatureAppBuilder : ILoanApplicationRequestBuilder
{
private readonly CrifSettings _settings;
public SignatureAppBuilder(CrifSettings settings)
{
_settings = settings;
}
public void SetDetails(LoanApplicationRequestBuilder createApp)
{
createApp.AppDetails = new AppDetailSignatureSection(createApp.CrifRequestSectionNamespace, _settings);
var additionalContent = new List<XElement>();
createApp.AppDetails.SetDetails(createApp.LoanApplication,
createApp._sourceChannel, additionalContent: [.. additionalContent]);
}
}
}
@@ -0,0 +1,9 @@
# Generating Service Client
1. Install dotnet-svcutil (https://docs.microsoft.com/en-us/dotnet/core/additional-tools/dotnet-svcutil-guide)
2. run `dotnet svcutil https://www-lc3cst.myappro.com/ACS/ConnectorService.svc -n "*,CRIF.ACTion"`
3. move Reference.cs into desired directory structure
* This process can also be accomplished within Visual Studio directly: https://docs.microsoft.com/en-us/visualstudio/data-tools/how-to-add-update-or-remove-a-wcf-data-service-reference
Note that the generated Service Model Binding will reject HTTPS URLs due to the use of `BasicHttpBinding`, which results from the WSDL's listed (but unusable) HTTP endpoints. A `BasicHttpsBinding` object should be used instead.
@@ -0,0 +1,778 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// //
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CRIF.ACTion
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="CRIF.ACTion.IConnectorService")]
public interface IConnectorService
{
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IConnectorService/SubmitEx", ReplyAction="http://tempuri.org/IConnectorService/SubmitExResponse")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
System.Threading.Tasks.Task<CRIF.ACTion.btMessage> SubmitExAsync(CRIF.ACTion.btMessage theXML);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IConnectorService/Submit", ReplyAction="http://tempuri.org/IConnectorService/SubmitResponse")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
System.Threading.Tasks.Task<CRIF.ACTion.btMessage> SubmitAsync(CRIF.ACTion.btMessage theXML);
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IConnectorService/SubmitX", ReplyAction="http://tempuri.org/IConnectorService/SubmitXResponse")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
System.Threading.Tasks.Task<System.Xml.Linq.XElement> SubmitXAsync(System.Xml.Linq.XElement part);
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class btMessage
{
private Routing routingField;
private System.Xml.Linq.XElement bodyField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public Routing routing
{
get
{
return this.routingField;
}
set
{
this.routingField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public System.Xml.Linq.XElement body
{
get
{
return this.bodyField;
}
set
{
this.bodyField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class Routing
{
private Org orgField;
private Action actionField;
private User userField;
private Interface interfaceField;
private ApproSystem systemField;
private ApproResult approResultField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public Org org
{
get
{
return this.orgField;
}
set
{
this.orgField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public Action action
{
get
{
return this.actionField;
}
set
{
this.actionField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public User user
{
get
{
return this.userField;
}
set
{
this.userField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public Interface @interface
{
get
{
return this.interfaceField;
}
set
{
this.interfaceField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=4)]
public ApproSystem system
{
get
{
return this.systemField;
}
set
{
this.systemField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=5)]
public ApproResult approResult
{
get
{
return this.approResultField;
}
set
{
this.approResultField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class Org
{
private int orgIDField;
private bool orgIDFieldSpecified;
private string orgCodeField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public int orgID
{
get
{
return this.orgIDField;
}
set
{
this.orgIDField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool orgIDSpecified
{
get
{
return this.orgIDFieldSpecified;
}
set
{
this.orgIDFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string orgCode
{
get
{
return this.orgCodeField;
}
set
{
this.orgCodeField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class ApproResult
{
private ApproResultEnum resultField;
private bool resultFieldSpecified;
private string errorInfoField;
private int exceptionCodeField;
private bool exceptionCodeFieldSpecified;
private string exceptionTextField;
private string stackTraceField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=0)]
public ApproResultEnum result
{
get
{
return this.resultField;
}
set
{
this.resultField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool resultSpecified
{
get
{
return this.resultFieldSpecified;
}
set
{
this.resultFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=1)]
public string errorInfo
{
get
{
return this.errorInfoField;
}
set
{
this.errorInfoField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=2)]
public int exceptionCode
{
get
{
return this.exceptionCodeField;
}
set
{
this.exceptionCodeField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool exceptionCodeSpecified
{
get
{
return this.exceptionCodeFieldSpecified;
}
set
{
this.exceptionCodeFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=3)]
public string exceptionText
{
get
{
return this.exceptionTextField;
}
set
{
this.exceptionTextField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Order=4)]
public string stackTrace
{
get
{
return this.stackTraceField;
}
set
{
this.stackTraceField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public enum ApproResultEnum
{
/// <remarks/>
Success,
/// <remarks/>
Exception,
/// <remarks/>
SqlError,
/// <remarks/>
NotImplemented,
/// <remarks/>
InvalidArg,
/// <remarks/>
UnexpectedType,
/// <remarks/>
InvalidXML,
/// <remarks/>
NoDataFound,
/// <remarks/>
InvalidOperation,
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class ApproSystem
{
private string systemVersionField;
private string systemVirtualDirectoryField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string systemVersion
{
get
{
return this.systemVersionField;
}
set
{
this.systemVersionField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string systemVirtualDirectory
{
get
{
return this.systemVirtualDirectoryField;
}
set
{
this.systemVirtualDirectoryField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class InterfaceConfig
{
private string settingNumField;
private string nameField;
private string valueField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string settingNum
{
get
{
return this.settingNumField;
}
set
{
this.settingNumField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class Interface
{
private InterfaceConfig[] interfaceConfigField;
private int interfaceIDField;
private bool interfaceIDFieldSpecified;
private string interfaceNameField;
private int productIDField;
private bool productIDFieldSpecified;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("interfaceConfig", Order=0)]
public InterfaceConfig[] interfaceConfig
{
get
{
return this.interfaceConfigField;
}
set
{
this.interfaceConfigField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public int interfaceID
{
get
{
return this.interfaceIDField;
}
set
{
this.interfaceIDField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool interfaceIDSpecified
{
get
{
return this.interfaceIDFieldSpecified;
}
set
{
this.interfaceIDFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string interfaceName
{
get
{
return this.interfaceNameField;
}
set
{
this.interfaceNameField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public int productID
{
get
{
return this.productIDField;
}
set
{
this.productIDField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool productIDSpecified
{
get
{
return this.productIDFieldSpecified;
}
set
{
this.productIDFieldSpecified = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class User
{
private int userIDField;
private bool userIDFieldSpecified;
private string userNameField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public int userID
{
get
{
return this.userIDField;
}
set
{
this.userIDField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool userIDSpecified
{
get
{
return this.userIDFieldSpecified;
}
set
{
this.userIDFieldSpecified = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string userName
{
get
{
return this.userNameField;
}
set
{
this.userNameField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="BizTalkMessage")]
public partial class Action
{
private string transactionTypeField;
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string transactionType
{
get
{
return this.transactionTypeField;
}
set
{
this.transactionTypeField = value;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
public interface IConnectorServiceChannel : CRIF.ACTion.IConnectorService, System.ServiceModel.IClientChannel
{
}
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("dotnet-svcutil", "1.0.0.0")]
public partial class ConnectorServiceClient : System.ServiceModel.ClientBase<CRIF.ACTion.IConnectorService>, CRIF.ACTion.IConnectorService
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
public ConnectorServiceClient(EndpointConfiguration endpointConfiguration) :
base(ConnectorServiceClient.GetBindingForEndpoint(endpointConfiguration), ConnectorServiceClient.GetEndpointAddress(endpointConfiguration))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public ConnectorServiceClient(EndpointConfiguration endpointConfiguration, string remoteAddress) :
base(ConnectorServiceClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress))
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public ConnectorServiceClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) :
base(ConnectorServiceClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
public ConnectorServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
base(binding, remoteAddress)
{
}
public System.Threading.Tasks.Task<CRIF.ACTion.btMessage> SubmitExAsync(CRIF.ACTion.btMessage theXML)
{
return base.Channel.SubmitExAsync(theXML);
}
public System.Threading.Tasks.Task<CRIF.ACTion.btMessage> SubmitAsync(CRIF.ACTion.btMessage theXML)
{
return base.Channel.SubmitAsync(theXML);
}
public System.Threading.Tasks.Task<System.Xml.Linq.XElement> SubmitXAsync(System.Xml.Linq.XElement part)
{
return base.Channel.SubmitXAsync(part);
}
public virtual System.Threading.Tasks.Task OpenAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginOpen(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndOpen));
}
public virtual System.Threading.Tasks.Task CloseAsync()
{
return System.Threading.Tasks.Task.Factory.FromAsync(((System.ServiceModel.ICommunicationObject)(this)).BeginClose(null, null), new System.Action<System.IAsyncResult>(((System.ServiceModel.ICommunicationObject)(this)).EndClose));
}
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.ExternalEndpoint))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
if ((endpointConfiguration == EndpointConfiguration.InternalEndpoint))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.ExternalEndpoint))
{
return new System.ServiceModel.EndpointAddress("http://www-lc3cst.myappro.com/ACS/ConnectorService.svc/ConnectorService");
}
if ((endpointConfiguration == EndpointConfiguration.InternalEndpoint))
{
return new System.ServiceModel.EndpointAddress("http://www-lc3cst.myappro.com/ACS/ConnectorService.svc/InternalService");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
public enum EndpointConfiguration
{
ExternalEndpoint,
InternalEndpoint,
}
}
}
@@ -0,0 +1,139 @@
using System.Reflection;
using System.ServiceModel;
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using Serilog;
using Serilog.Events;
using CRIF.ACTion.Configuration;
namespace CRIF.ACTion.Services.Crif
{
public class CrifService : ICrifService
{
private ConnectorServiceClient serviceClient;
private readonly IWcfInspectorBehavior WcfInspectorBehavior;
private readonly CrifSettings _settings;
public CrifService(IWcfInspectorBehavior wcfInspectorBehavior,
IOptions<CrifSettings> settings)
{
WcfInspectorBehavior = wcfInspectorBehavior;
_settings = settings.Value;
IsAnyNullOrEmpty(_settings.ApiSettings);
CreateServiceClient();
}
private void CreateServiceClient()
{
Log.Debug("Creating client binding and object");
var binding = new BasicHttpsBinding
{
MaxBufferSize = int.MaxValue,
ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max,
MaxReceivedMessageSize = int.MaxValue,
AllowCookies = true,
SendTimeout = CrifConstants.SubmissionTimeoutInMinutes
};
var endpoint = new EndpointAddress(_settings.ApiSettings.EndpointUrl);
serviceClient = new ConnectorServiceClient(binding, endpoint);
if (Log.IsEnabled(LogEventLevel.Debug))
{
serviceClient.Endpoint.EndpointBehaviors.Add(WcfInspectorBehavior);
}
}
public btMessage BuildMessage(XElement actionApi)
{
Log.Information("Building btMessage");
btMessage message = new btMessage
{
body = actionApi,
routing = new Routing
{
org = new Org
{
orgCode = _settings.ApiSettings.OrgCode,
orgID = _settings.ApiSettings.OrgId,
orgIDSpecified = true,
},
user = new User
{
userName = _settings.ApiSettings.Username,
},
@interface = new Interface
{
interfaceID = _settings.ApiSettings.InterfaceId,
interfaceIDSpecified = true,
},
action = new CRIF.ACTion.Action
{
transactionType = "API",
},
system = new ApproSystem(), // empty object required
approResult = new ApproResult(), // empty object required
}
};
return message;
}
public async Task<XElement> SendMessage(XElement request)
{
var message = BuildMessage(request);
XElement body;
try
{
Log.Information("Sending message to CRIF");
var response = await serviceClient.SubmitExAsync(message);
if (response.routing.approResult.exceptionCodeSpecified)
{
//Note that approResult.result will likely equal ApproResultEnum.Success
//even with a failed submission
var errorResult = response.routing.approResult;
var errorText = "CRIF responded with error " + errorResult.exceptionCode + " - " + errorResult.exceptionText;
if (errorResult.exceptionCode == 13)
{
// Go ahead and catch the error 13 that CRIF won't resolve.
throw new Error13Exception(errorText + "[KnownReoccuringError]");
} else {
throw new FormatException("CRIF responded with error " +
errorResult.exceptionCode + " - " + errorResult.exceptionText);
}
}
body = response.body;
}
catch (Exception e)
{
Log.Debug("{exception}", e.Message);
throw;
}
return body;
}
public static bool IsAnyNullOrEmpty(ApiConnectionSettings myObject)
{
foreach (PropertyInfo pi in myObject.GetType().GetProperties())
{
if (pi.PropertyType == typeof(string))
{
string value = (string)pi.GetValue(myObject);
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException(pi.ToString());
}
}
}
return false;
}
}
}
@@ -0,0 +1,265 @@
using Microsoft.Extensions.Options;
using Serilog;
using CRIF.ACTion.Common.Parsers;
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Decisioning;
using CRIF.ACTion.Files;
using CRIF.ACTion.Loan;
using CRIF.ACTion.Loan.Individuals;
using CRIF.ACTion.Services.Landmark.DistributionChannel;
using Landmark.OLA.Models;
using Landmark.OLA.Models.Decisioning;
using Landmark.OLA.Models.Decisioning.Enums;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Services.Crif
{
public class CrifVendorClient : ICrifVendorClient
{
private readonly string _getCustomFieldsString = "getCustomFields";
private readonly string _getProductDetailsString = "getProducts";
private readonly int retryLimit = 6;
private readonly ICrifService _crifService;
private readonly IResponseParser<Individual> _individualResponseParser;
private readonly IResponseParser<LoanApplication> _loanApplicationResponseParser;
private readonly IResponseParser<DecisioningState> _decisioningStateResponseParser;
private readonly IResponseParser<DecisioningDetails> _decisioningDetailsResponseParser;
private readonly IResponseParser<SortedList<int, string>> _configurationResponseParser;
private readonly IResponseParser<Dictionary<string, string>> _productDetailsResponseParser;
private readonly IResponseParser<CustomFieldConfigs> _customFieldResponseParser;
private readonly IResponseParser<DealershipAuthRequest> _dealershipResponseParser;
private readonly CrifSettings _settings;
public CrifVendorClient(ICrifService crifService,
IResponseParser<Individual> individualResponseParser,
IResponseParser<LoanApplication> loanApplicationResponseParser,
IResponseParser<DecisioningState> decisioningStateResponseParser,
IResponseParser<DecisioningDetails> decisioningDetailsResponseParser,
IResponseParser<SortedList<int, string>> crifConfigurationResponseParser,
IResponseParser<Dictionary<string, string>> productDetailsResponseParser,
IResponseParser<CustomFieldConfigs> customFieldResponseParser,
IResponseParser<DealershipAuthRequest> dealershipResponseParser,
IOptions<CrifSettings> settings)
{
_crifService = crifService;
_individualResponseParser = individualResponseParser;
_loanApplicationResponseParser = loanApplicationResponseParser;
_decisioningStateResponseParser = decisioningStateResponseParser;
_decisioningDetailsResponseParser = decisioningDetailsResponseParser;
_configurationResponseParser = crifConfigurationResponseParser;
_productDetailsResponseParser = productDetailsResponseParser;
_customFieldResponseParser = customFieldResponseParser;
_dealershipResponseParser = dealershipResponseParser;
_settings = settings.Value;
_settings.CustomFields = GetCustomFieldConfiguration().GetAwaiter().GetResult();
}
public async Task<DealershipAuthRequest> GetDealershipAuthDetails(string applicationId)
{
var getApp = new DealershipRequestBuilder(applicationId, _settings);
var xmlApp = getApp.Build();
try
{
var response = await _crifService.SendMessage(xmlApp);
return _dealershipResponseParser.Parse(response);
}
catch (Exception e)
{
Log.Fatal("Error parsing response: " + e.Message);
throw;
}
}
public async Task<LoanApplication> CreateApplication(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null)
{
Log.Debug("Building Loan Application");
ModelValidator.Validate(loanApplication);
var createApp = new LoanApplicationRequestBuilder(loanApplication, _settings, null, null, null, channelService);
var xmlApp = createApp.Build();
var response = await _crifService.SendMessage(xmlApp);
try
{
return _loanApplicationResponseParser.Parse(response);
}
catch (Exception)
{
Log.Fatal("Error parsing response.");
throw;
}
}
public async Task<string> CreateApplicationAndGetId(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null)
{
var parsedLoanApplication = await CreateApplication(loanApplication, channelService);
return parsedLoanApplication.Id;
}
public async Task<bool> AddFile(string applicationId, LoanApplicationFile file, string fileBase64Value)
{
try
{
var addFile = new FileRequestBuilder(applicationId, file, fileBase64Value, _settings);
var response = await _crifService.SendMessage(addFile.Build());
return true;
}
catch (Exception e)
{
Log.Fatal("Error uploading file to loan application: " + applicationId);
Log.Fatal(e.ToString());
}
return false;
}
public async Task<Individual> AddIndividual(string applicationId, Individual individual)
{
ModelValidator.Validate(individual);
var addIndividual = new AddIndividualRequestBuilder(applicationId, individual, _settings);
var response = await _crifService.SendMessage(addIndividual.Build());
try
{
return _individualResponseParser.Parse(response);
}
catch (Exception)
{
Log.Fatal("Error parsing response.");
throw;
}
}
public async Task<bool> SendToDecisioning(string applicationId)
{
var sendToDecisioning = new SendToDecisioningRequestBuilder(applicationId, _settings);
var response = await _crifService.SendMessage(sendToDecisioning.Build());
//TODO: This cast is not particularly Dependency Injection-y
//TODO: Requiring the Application Id is really just a double check, and may not matter all that much
((DecisioningStateResponseParser)_decisioningStateResponseParser).SetLoanId(applicationId);
var result = _decisioningStateResponseParser.Parse(response);
if (result == DecisioningState.MissingRequiredInformation)
{
throw new InvalidOperationException("Loan application cannot be sent to decisioning, and is likely missing required information.");
}
if (result == DecisioningState.Requested || result == DecisioningState.Ready)
{
return true;
}
else
{
return false;
}
}
public async Task<DecisioningDetails> GetDecisionStatus(string applicationId)
{
//TODO: This cast is not particularly Dependency Injection-y
//TODO: Requiring the Application Id is really just a double check, and may not matter all that much
((GetApplicationProductsResponseParser)_decisioningDetailsResponseParser).SetLoanId(applicationId);
var result = new DecisioningDetails()
{
DecisioningState = DecisioningState.Unknown
};
int retryAttempts = 0;
Thread.Sleep(2500);
do
{
Thread.Sleep((retryAttempts * 200) + 1500);
var getDecisionStatus = new GetApplicationProductsRequestBuilder(applicationId, _settings);
var response = await _crifService.SendMessage(getDecisionStatus.Build());
result = _decisioningDetailsResponseParser.Parse(response);
retryAttempts++;
} while (retryAttempts < retryLimit && result.DecisioningState == DecisioningState.Unknown);
if (retryAttempts >= retryLimit && result.DecisioningState == DecisioningState.Unknown)
{
result.DecisioningState = DecisioningState.Ready;
result.DecisioningResult = DecisioningResult.DecisionStatus_Review;
}
Log.Information($"Application ID: {applicationId}, Number of Attempts to Get DecisionStatus: {retryAttempts}");
return result;
}
public async Task<SortedList<int, string>> GetBranchNameConfiguration(string ConfigurationSectionName)
{
var configurationRequest = new ConfigurationRequestBuilder(ConfigurationSectionName, _settings);
var response = await _crifService.SendMessage(configurationRequest.Build());
var result = _configurationResponseParser.Parse(response);
return result;
}
public async Task<CustomFieldConfigs> GetCustomFieldConfiguration()
{
CustomFieldConfigs customFieldConfig = new CustomFieldConfigs();
string municipalityTypeId = null;
string municipalityWhereKeptId = null;
try
{
var configurationRequest = new ConfigurationRequestBuilder(_getCustomFieldsString, _settings);
var response = await _crifService.SendMessage(configurationRequest.Build());
customFieldConfig = _customFieldResponseParser.Parse(response);
if (_settings?.CustomFieldMappings?.CustomFieldID != null)
{
_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityType", out municipalityTypeId);
_settings.CustomFieldMappings.CustomFieldID.TryGetValue("MunicipalityWhereKept", out municipalityWhereKeptId);
}
Log.Information(
"CRIF custom field mappings loaded: MunicipalityType={MunicipalityTypeId}, MunicipalityWhereKept={MunicipalityWhereKeptId}",
municipalityTypeId ?? "<missing>",
municipalityWhereKeptId ?? "<missing>");
}
catch (Exception ex)
{
Log.Fatal("Error parsing CRIF custom fields configuration response." + ex.StackTrace);
}
customFieldConfig.ProductIdToCategoryId = await GetProductDetailsConfiguration();
return customFieldConfig;
}
public async Task<Dictionary<string, string>> GetProductDetailsConfiguration()
{
Dictionary<string, string> mappingList = [];
try
{
var configurationRequest = new ConfigurationRequestBuilder(_getProductDetailsString, _settings);
var response = await _crifService.SendMessage(configurationRequest.Build());
mappingList = _productDetailsResponseParser.Parse(response);
}
catch (Exception ex)
{
Log.Fatal("Error parsing CRIF custom fields configuration response." + ex.StackTrace);
}
return mappingList;
}
}
}
@@ -0,0 +1,10 @@
namespace CRIF.ACTion.Services.Crif
{
public class Error13Exception : Exception
{
public Error13Exception(string message) : base(message)
{
}
}
}
@@ -0,0 +1,11 @@
using System.Xml.Linq;
namespace CRIF.ACTion.Services.Crif
{
public interface ICrifService
{
btMessage BuildMessage(XElement actionApi);
Task<XElement> SendMessage(XElement request);
}
}
@@ -0,0 +1,29 @@
using CRIF.ACTion.Configuration;
using CRIF.ACTion.Services.Landmark.DistributionChannel;
using Landmark.OLA.Models.Decisioning;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Services.Crif
{
public interface ICrifVendorClient
{
Task<DealershipAuthRequest> GetDealershipAuthDetails(string applicationId);
Task<LoanApplication> CreateApplication(LoanApplication loanApplication, IDistributionChannelLookupService channelService);
Task<string> CreateApplicationAndGetId(LoanApplication loanApplication, IDistributionChannelLookupService channelService = null);
Task<bool> AddFile(string applicationId, LoanApplicationFile file, string fileBase64Value);
Task<Individual> AddIndividual(string applicationId, Individual individual);
Task<bool> SendToDecisioning(string applicationId);
Task<DecisioningDetails> GetDecisionStatus(string applicationId);
Task<SortedList<int, string>> GetBranchNameConfiguration(string ConfigurationSectionName);
Task<CustomFieldConfigs> GetCustomFieldConfiguration();
}
}
@@ -0,0 +1,9 @@
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace CRIF.ACTion.Services.Crif
{
public interface IWcfInspectorBehavior: IClientMessageInspector, IEndpointBehavior
{
}
}
@@ -0,0 +1,46 @@
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using Serilog;
namespace CRIF.ACTion.Services.Crif
{
public class WcfInspectorBehavior : IWcfInspectorBehavior
{
public WcfInspectorBehavior()
{
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
Log.Debug("Reply received: {reply}", reply);
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
Log.Debug("Sending request: {request}", request);
return null;
}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(this);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
}
@@ -0,0 +1,7 @@
namespace CRIF.ACTion.Services.Landmark.DistributionChannel
{
public interface IDistributionChannelLookupService
{
int GetDistributionChannelByZipCode(string zipCode);
}
}
@@ -0,0 +1,23 @@
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan;
namespace CRIF.ACTion.Services.Landmark.DistributionChannel
{
public class LandmarkDistributionChannelLookupService : IDistributionChannelLookupService
{
private readonly ServicingChannelSettings _settings;
public LandmarkDistributionChannelLookupService(CrifSettings settings)
{
_settings = settings.ServicingChannels;
}
public int GetDistributionChannelByZipCode(string zipCode)
{
var partyId = BranchMapping.BranchCodesByZip.TryGetValue(zipCode, out int foundValue)
? foundValue : _settings.DefaultServicingChannel;
return partyId;
}
}
}
@@ -0,0 +1,38 @@
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Services.Landmark
{
public class ProductIdService
{
private readonly CrifSettings _settings;
public ProductIdService(CrifSettings settings)
{
_settings = settings;
}
public int GetProductId(LoanType loanType)
{
return loanType switch
{
LoanType.CC_CashBack => _settings.ProductIds.CC_CashBack,
LoanType.CC_Rewards => _settings.ProductIds.CC_Rewards,
LoanType.CC_Student => _settings.ProductIds.CC_Student,
LoanType.CC_Secure => _settings.ProductIds.CC_Secure,
LoanType.EQ_HELOC_Flex => _settings.ProductIds.EQ_HELOC_Flex,
LoanType.EQ_Home_Equity_Fixed => _settings.ProductIds.EQ_Home_Equity_Fixed,
LoanType.EQ_Mtg_Refi_Express => _settings.ProductIds.EQ_Mtg_Refi_Express,
LoanType.EQ_Mfg_Home_With_or_Without_Land => _settings.ProductIds.EQ_Mfg_Home_And_Land,
LoanType.V_Vehicle => _settings.ProductIds.V_Vehicle,
LoanType.V_VehiclePreapproval => _settings.ProductIds.V_VehiclePreapproval,
LoanType.M_Boat => _settings.ProductIds.M_Boat,
LoanType.P_Powersport => _settings.ProductIds.P_Powersport,
LoanType.T_TrailerMotorhome => _settings.ProductIds.T_TrailerMotorhome,
LoanType.S_Signature or LoanType.S_Special => _settings.ProductIds.S_Signature,
_ => throw new ArgumentOutOfRangeException(nameof(LoanApplication.LoanType), $"The requested loan type {loanType} is not implemented."),
};
}
}
}
@@ -0,0 +1,9 @@
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Services.Landmark.SourceChannel
{
public interface ISourceChannelLookupService
{
int GetSourceChannelByLoanType(LoanType loanType);
}
}
@@ -0,0 +1,40 @@
using System.Collections.ObjectModel;
using CRIF.ACTion.Configuration;
using Landmark.OLA.Models.Loan.Enums;
namespace CRIF.ACTion.Services.Landmark.SourceChannel
{
public class LandmarkSourceChannelLookupService : ISourceChannelLookupService
{
private readonly CrifSettings _settings;
private ReadOnlyDictionary<LoanType, int> _loanTypeToSourceChannel;
public LandmarkSourceChannelLookupService(CrifSettings settings)
{
_settings = settings;
ConfigureMapping();
}
private void ConfigureMapping()
{
var mapping = new Dictionary<LoanType, int>
{
{LoanType.CC_CashBack, _settings.SourceChannels.CreditCardSourceChannelId},
{LoanType.CC_Rewards, _settings.SourceChannels.CreditCardSourceChannelId},
{LoanType.CC_Secure, _settings.SourceChannels.CreditCardSourceChannelId},
{LoanType.CC_Student, _settings.SourceChannels.CreditCardSourceChannelId},
};
_loanTypeToSourceChannel = new ReadOnlyDictionary<LoanType, int>(mapping);
}
public int GetSourceChannelByLoanType(LoanType loanType)
{
var channelId = _loanTypeToSourceChannel.TryGetValue(loanType, out int foundValue)
? foundValue : _settings.SourceChannels.DefaultSourceChannelId;
return channelId;
}
}
}