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,46 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses.Enums;
namespace Landmark.OLA.Models.Addresses
{
public class Address : IAddress
{
[Required]
[StringLength(40)]
public string Address1 { get; set; }
[StringLength(40)]
public string Address2 { get; set; }
[Required]
[StringLength(100)]
public string City { get; set; }
[Required]
//Nullable so that there is no default value
public StateOption? State { get; set; }
[Required]
[StringLength(5, MinimumLength = 5)]
public string PostalCode { get; set; }
[StringLength(4, MinimumLength = 4)]
public string PostalCodeExt { get; set; }
[Required]
//Nullable so that there is no default value.
public CountryOption? Country { get; set; }
public Guid Id { get; private set; }
public Address()
{
Id = Guid.NewGuid();
}
public Address(Guid id)
{
Id = id;
}
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models.Addresses.Enums
{
public enum CountryOption
{
//only value allowed by CRIF
UnitedStates,
}
}
@@ -0,0 +1,9 @@
namespace Landmark.OLA.Models.Addresses.Enums
{
public enum ResidenceTypeOption
{
Own,
Rent,
Other,
}
}
@@ -0,0 +1,71 @@
namespace Landmark.OLA.Models.Addresses.Enums
{
/// <summary>
/// StateEnum values from CRIF's commonSchema.xsd
/// </summary>
public enum StateOption
{
AA,
AE,
AK,
AL,
AP,
AR,
AS,
AZ,
CA,
CO,
CT,
DC,
DE,
FL,
//FM, //Present in XSD but not available in CRIF's v_RptGeoCode_v001 table
GA,
GU,
HI,
IA,
ID,
IL,
IN,
KS,
KY,
LA,
MA,
MD,
ME,
//MH, //Present in XSD but not available in CRIF's v_RptGeoCode_v001 table
MI,
MN,
MO,
MP,
MS,
MT,
NC,
ND,
NE,
NH,
NJ,
NM,
NV,
NY,
OH,
OK,
OR,
PA,
PR,
PW,
RI,
SC,
SD,
TN,
TX,
UT,
VA,
VI,
VT,
WA,
WI,
WV,
WY,
}
}
@@ -0,0 +1,21 @@
using Landmark.OLA.Models.Addresses.Enums;
namespace Landmark.OLA.Models.Addresses
{
public interface IAddress : IReferenceable
{
string Address1 { get; set; }
string Address2 { get; set; }
string City { get; set; }
StateOption? State { get; set; }
string PostalCode { get; set; }
string PostalCodeExt { get; set; }
CountryOption? Country { get; set; }
}
}
@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models.Addresses
{
public class PropertyLot : Address
{
[Required]
public bool? IsRented { get; set; }
[Range(minimum: 0, maximum: 1000000000)]
public decimal? RentalAmt { get; set; }
[Range(minimum: 0, maximum: 1000000000)]
public decimal? AnnualPropertyTax { get; set; }
//TODO #399
// public LienPositionEnum lienPositionID { get; set; }
public PropertyLot()
: base()
{
}
public PropertyLot(Guid id)
: base(id)
{
}
}
}
@@ -0,0 +1,16 @@
using FluentValidation;
namespace Landmark.OLA.Models.Addresses.Validators
{
public class AddressValidator : AbstractValidator<Address>
{
public AddressValidator()
{
RuleFor(x => x.Address1).NotNull();
RuleFor(x => x.City).NotNull();
RuleFor(x => x.State).NotNull();
RuleFor(x => x.PostalCode).NotNull();
}
}
}
@@ -0,0 +1,10 @@
namespace Landmark.OLA.Models
{
public class ApplicationControlConfiguration
{
public bool NotifyLogging { get; set; }
public string HomePageUrl { get; set; }
public string ImportantTermsPdfFilePath { get; set; }
public string WhatYouShouldKnowPdfFilePath { get; set; }
}
}
@@ -0,0 +1,11 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum LienPositionType
{
First,
Second,
Third,
Fourth,
Fifth
}
}
@@ -0,0 +1,10 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum ManufacturedHomeType
{
Single,
Double,
Triple,
Quad,
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum NewUsed
{
New,
Used,
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Assets.Enums
{
/// <summary>
/// OwnerOccupiedTypeEnum in CRIF's commonSchema.xsd
/// </summary>
public enum PropertyUse
{
OwnerOccupied,
SecondHome,
Rental,
VacationHome,
Vacant
}
}
@@ -0,0 +1,19 @@
namespace Landmark.OLA.Models.Assets.Enums
{
/// <summary>
/// Derived from PropertySubTypeEnum in CRIF's commonSchema.xsd
/// </summary>
public enum RealEstateType
{
Family2_4, //1-4 Family
OneUnit,
TwoUnit,
ThreeUnit,
FourUnit,
Condo,
CoOp,
Other,
SingleFamily,
Townhouse
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum UsageType
{
Personal,
Business,
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum VehicleSeller
{
Dealer,
Private_Party,
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Assets.Enums
{
public enum VehicleType
{
Automobile,
Motorcycle,
Boat,
ATV,
PWC,
RecreationalVehicle, //This is *not* a motorhome/camper, but is instead things like golf carts.
Snowmobile,
TrailerMotorHome, //This *does* include a traditional RV motorohome.
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Assets.Enums
{
//CRIF's ValueTypeEnum from commonSchema.xsd
public enum VehicleValuationType
{
Invoice,
Loan,
MSRP,
Retail,
TradeIn,
Wholesale,
Manual,
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Assets
{
public class LienPosition : OlaModel
{
public bool HasFirstMortgage { get; set; }
public bool HasSecondMortgage { get; set; }
public bool PayoffFirstMortgage { get; set; }
public bool PayoffSecondMortgage { get; set; }
}
}
@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Assets.Enums;
namespace Landmark.OLA.Models.Assets
{
public class ManufacturedHome : RealEstateBase
{
[Required]
public ManufacturedHomeType? Type { get; set; }
[Required]
public PropertyLot Lot { get; set; }
//Slightly arbitrary values that should be safe
[Range(0, 1000000000)]
public decimal? EstimatedLandValuation { get; set; }
[Required]
[Range(minimum: 0, maximum: 1000000000)]
public decimal EstimatedHomeValuation { get; set; }
public ManufacturedHome(Guid? manufacturedHomeId = null)
:base(manufacturedHomeId)
{
}
}
}
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Assets.Enums;
namespace Landmark.OLA.Models.Assets
{
public class RealEstate : RealEstateBase
{
public Address RealEstateAddress { get; set; }
[Required]
public RealEstateType? Type { get; set; }
public RealEstate(Guid? realEstateId = null)
: base(realEstateBaseId: realEstateId)
{
}
}
}
@@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Assets.Enums;
namespace Landmark.OLA.Models.Assets
{
public class RealEstateBase : OlaModel
{
//Slightly arbitrary values that should be safe
[Range(0, 1000000000)]
public decimal InitialEstimatedValue { get; set; }
[Required]
public PropertyUse? Use { get; set; }
public bool? HasSpa { get; set; }
public bool? HasPool { get; set; }
public bool? HasBasement { get; set; }
public bool? HasCentralAir { get; set; }
public bool? HasGarage { get; set; }
public bool? InFloodZone { get; set; }
public bool? InEquity { get; set; }
public bool? IsPropertyHeldInTrust { get; set; }
public bool IsCollateral { get; set; } = true;
public bool IsTradeIn { get; set; }
public LienPosition LienPosition { get; set; }
[Required]
public bool? CurrentlyListedForSale { get; set; }
[Required]
public bool? CurrentlyBeingRemodeledOrImproved { get; set; }
public RealEstateBase(Guid? realEstateBaseId = null)
{
Id = realEstateBaseId.HasValue ? realEstateBaseId.Value.ToString() : null;
}
}
}
@@ -0,0 +1,123 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Assets.Enums;
namespace Landmark.OLA.Models.Assets
{
public class Vehicle : OlaModel
{
//This will either hold an ToString'ed int or a 36-char long GUID
[StringLength(36)]
public string ValuationId { get; private set; }
[Required]
public VehicleValuationType? ValuationType { get; set; }
[Required]
public NewUsed? Condition { get; set; }
public VehicleSeller? Seller { get; set; }
[Required]
public VehicleType? Type { get; set; }
public UsageType? Usage { get; set; }
public bool? HasClearTitle { get; set; }
[Required]
[Range(minimum: 0, maximum: 1000000000)]
public decimal? InitialEstimatedValue { get; set; }
public bool? IsForBusinessUse { get; set; }
public bool? IsOwnerOperator { get; set; }
[Required]
//Slightly arbitrary values that should be safe
[Range(1900, 3000)]
public int? Year { get; set; }
//Slightly arbitrary values that should be safe
[Range(0, 1000000000)]
public int? Mileage { get; set; }
[Required]
[StringLength(100)]
public string Make { get; set; }
[Required]
[StringLength(100)]
public string Model { get; set; }
[StringLength(20)]
public string Color { get; set; }
[StringLength(400)]
public string Body { get; set; }
[StringLength(100)]
public string Series { get; set; }
[StringLength(100)]
public string Style { get; set; }
[StringLength(400)]
public string Trim { get; set; }
[StringLength(60)]
public string VIN { get; set; }
[StringLength(8)]
public string LicensePlateNumber { get; set; }
public string StateTitled { get; set; }
[StringLength(20)]
public string CountyTitled { get; set; }
[StringLength(120)]
public string MunicipalityType { get; set; }
[StringLength(120)]
public string MunicipalityWhereKept { get; set; }
public string Lien { get; set; }
[StringLength(120)]
public string DealerName { get; set; }
[StringLength(180)]
public string DealerPhone { get; set; }
public Address BoatAddress { get; set; }
[Range(0, 1000000000000000)]
public decimal? BoatLength { get; set; }
[Range(1900, 3000)]
public int? TrailerYear { get; set; }
[StringLength(60)]
public string TrailerMake { get; set; }
public string TrailerType { get; set; }
[Range(1900, 3000)]
public int? MotorYear { get; set; }
[StringLength(60)]
public string MotorMake { get; set; }
[StringLength(200)]
public string MotorModel { get; set; }
public int? MotorType { get; set; }
public Vehicle(Guid? valuationId = null, Guid? vehicleId = null, Guid? sellerId = null)
{
ValuationId = valuationId.HasValue ? valuationId.ToString() : Guid.NewGuid().ToString();
Id = vehicleId.HasValue ? vehicleId.ToString() : null;
}
}
}
@@ -0,0 +1,10 @@
namespace Landmark.OLA.Models
{
public class ClientError
{
public string Message { get; set; }
public string Name { get; set; }
public string StackTrace { get; set; }
}
}
@@ -0,0 +1,24 @@
namespace Landmark.OLA.Models.Core
{
public class CoreAddress
{
public int AddressId { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
public string CountyName { get; set; }
public string AddressType { get; set; }
public bool IsPrimary { get; set; }
public bool IsInternational { get; set; }
public int AddressVerification { get; set; }
public string Occupancy { get; set; }
public System.DateTime StartDate { get; set; }
public System.DateTime EndDate { get; set; }
public bool UspsVerified { get; set; }
}
}
@@ -0,0 +1,40 @@
namespace Landmark.OLA.Models.Core
{
public class CoreIndividual
{
public int IndividualId { get; set; }
public string IndividualType { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string NameSuffix { get; set; }
public string TaxId { get; set; }
public string Gender { get; set; }
public System.DateTime BirthDate { get; set; }
public int Branch { get; set; }
public string DriversLicense { get; set; }
public string DriversLicenseState { get; set; }
public string Ident { get; set; }
public string PreferredContact { get; set; }
public string MaritalStatus { get; set; }
public bool IsUSCitizen { get; set; }
public int PrimaryAddressId { get; set; }
public bool IsEmployee { get; set; }
public int EmployeeNumber { get; set; }
public string CompanyName { get; set; }
public string EmployerName { get; set; }
public int ApplicationCountLast90Days { get; set; }
public int ApplicationsDeclinedLast90Days { get; set; }
public int ApplicationsApprovedLast90Days { get; set; }
public List<CoreAddress> Address { get; set; }
public string Email1 { get; set; }
public string Email2 { get; set; }
public List<CorePhone> Phone { get; set; }
public string Status { get; set; }
public bool IsHomeOwner { get; set; }
public bool IsHeadOfHouseHold { get; set; }
public string Language { get; set; }
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Core
{
public class CoreMemberOwner
{
public int IndividualId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Address { get; set; }
public string TaxId { get; set; }
public string DriverLicense { get; set; }
public string BirthDate { get; set; }
}
}
@@ -0,0 +1,14 @@
namespace Landmark.OLA.Models.Core
{
public class CorePhone
{
public string PhoneKey { get; set; }
public bool IsPrimaryForType { get; set; }
public bool IsInternational { get; set; }
public string CountryCode { get; set; }
public string AreaCode { get; set; }
public string PhoneNumber { get; set; }
public string Extension { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace Landmark.OLA.Models.Core
{
public class MemberMeta
{
public bool IsMember { get; set; }
public bool IsIndividual { get; set; }
public bool IsTwoFactor { get; set; }
}
}
@@ -0,0 +1,36 @@
using Landmark.OLA.Models.Decisioning.Enums;
namespace Landmark.OLA.Models.Decisioning
{
//TODO: At some point the LoanApplication class should probably have this class as a property. Might need to wait for parsing.
public class DecisioningDetails
{
public DecisioningState DecisioningState { get; set; }
public DecisioningResult? DecisioningResult { get; set; }
public ICollection<int> Stipulations { get; private set; }
public DecisioningDetails(ICollection<int> stipulations = null)
{
Stipulations = stipulations ?? new List<int>();
}
public override bool Equals(object obj)
{
if (obj.GetType() != this.GetType())
return false;
var compare = (DecisioningDetails)obj;
return DecisioningState.Equals(compare.DecisioningState)
&& DecisioningResult.Equals(compare.DecisioningResult)
&& Stipulations.SequenceEqual(compare.Stipulations);
}
public override int GetHashCode()
{
return HashCode.Combine(DecisioningState, DecisioningResult, Stipulations);
}
}
}
@@ -0,0 +1,163 @@
namespace Landmark.OLA.Models.Decisioning.Enums
{
/// <summary>
/// StatusEnum values from CRIF's commonSchema.xsd
/// </summary>
public enum DecisioningResult
{
NoneSelected,
DecisionStatus_Approved,
DecisionStatus_Counteroffer,
DecisionStatus_Review,
DecisionStatus_Declined,
DecisionStatus_Cancelled,
DecisionStatus_Withdrawn,
DecisionStatus_Aprvd_RejectedbyApplicant,
DecisionStatus_CounterofferRejectedbyApplicant,
WorkflowStatus_DataAcquisition,
WorkflowStatus_LoanProcessing,
WorkflowStatus_BookingFulfillment,
ProcessStatus_Incomplete,
ProcessStatus_Complete,
ProcessStatus_MissingInformation,
ResourceStatus_MissingInformation,
ResourceStatus_RequiredInformation,
WorkflowStatus_Decisioning,
RiskManagerServiceRequestStatus_PendingHistory,
RiskManagerServiceRequestStatus_HistoryComplete,
RiskManagerServiceRequestStatus_PendingEvaluation,
RiskManagerServiceRequestStatus_EvaluationComplete,
RiskManagerServiceRequestStatus_ReplacedbyRiskManager_HistoryRequest,
StipulationStatus_Open,
StipulationStatus_Waive,
TitleExceptionStatus_Open,
TitleExceptionStatus_Satisfy,
TitleExceptionStatus_Waive,
StipulationStatus_Satisfied,
DisbursementStatus_Pending,
DisbursementStatus_Completed,
DisbursementStatus_Voided,
DisbursementStatus_Deleted,
ApplicationStatus_Pending,
ApplicationStatus_Complete,
MasterApplicationStatus_Signed,
MasterApplicationStatus_NotSigned,
LoanDocumentStatus_PendingGeneration,
LoanDocumentStatus_Generated,
DecisionStatus_Pending,
MasterApplicationStatus_Expired,
MasterApplicationStatus_Cancelled,
ApplicationStatus_Cancelled,
ApplicationExtractStatus_Pending,
ApplicationExtractStatus_Completed,
RiskManagerServiceRequestStatus_PendingDecisioning,
RiskManagerServiceRequestStatus_DecisioningComplete,
LoanDocumentStatus_Viewed,
LoanDocumentStatus_Error,
ContractStatus_Contracted,
ContractStatus_Funded,
ContractStatus_Unfunded,
ContractStatus_ReturntoChannel,
RiskManagerServiceRequestStatus_CompletewithError,
CollateralStatus_Active,
CollateralStatus_Withdrawn,
RecommendedStatus_Approved,
RecommendedStatus_Counteroffer,
RecommendedStatus_Declined,
RiskManagerServiceRequestStatus_NoDiscreteMatch,
RiskManagerServiceRequestStatus_ProcesswithoutMatch,
FileStatus_Deleted,
StipulationStatus_Ignore,
RuleStatus_Pass,
RuleStatus_Fail,
RuleStatus_Overriden,
ContractReviewStatus_Accept,
ContractReviewStatus_Counteroffer,
ContractReviewStatus_ReturntoChannel,
DecisionStatus_Incomplete,
IncomeStatus_Included,
IncomeStatus_Excluded,
DisclosureLetterStatus_Pending,
DisclosureLetterStatus_Printed,
DisclosureLetterStatus_Suppressed,
DisclosureLetterStatus_Failed,
NotificationStatus_Pending,
NotificationStatus_Completed,
NotificationStatus_Failed,
NotificationStatus_Previewed,
AcceptanceStatus_Provisional,
AcceptanceStatus_Current,
AcceptanceStatus_Rejected,
AcceptanceStatus_Cancelled,
AcceptanceStatus_Withdrawn,
SettlementServiceRequestStatus_Submitted,
SettlementServiceRequestStatus_CompletedReceived,
SettlementServiceRequestStatus_Cancelled,
SettlementServiceRequestStatus_Reviewed,
SettlementServiceRequestStatus_Failed,
SettlementServiceRequestStatus_CancellationRequest,
SettlementServiceRequestStatus_Accepted,
SettlementServiceRequestStatus_Incomplete,
SettlementServiceRequestStatus_Pending,
SettlementServiceRequestStatus_NoHit,
SettlementServiceRequestStatus_NoHitPendingResearch,
DisbursTrans_Printed,
DisbursTrans_Reprinted,
Contract_Unfunded,
History_Pending,
History_NoneRequested,
History_Error,
History_NoRecord,
History_Success,
LoanDoc_Processing,
LoanDoc_Cancelled,
HistoryStatus_Pending,
HistoryStatus_NoneRequested,
HistoryStatus_Error,
HistoryStatus_NoRecord,
HistoryStatus_Success,
LoanDocumentStatus_Processing,
LoanDocumentStatus_Cancelled,
LoanDocumentStatus_NonApplicable,
NotificationStatus_NonApplicable,
ChannelStatisticStatus_Entered,
ChannelStatisticStatus_Approved,
ChannelStatisticStatus_Other,
ChannelStatisticStatus_Counteroffer,
ChannelStatisticStatus_Declined,
ChannelStatisticStatus_Review,
ChannelStatisticStatus_Booked,
PrescreenStatus_PrescreenPass,
PrescreenStatus_PrescreenFail,
RiskManagerServiceRequestStatus_PrescreenCancel,
RiskManagerServiceRequestStatus_ChexSystemsCancel,
ACHExported,
ApplicantAcceptance,
ProcessStatus_Failed,
DocInterfaceStatus_Pending,
DocInterfaceStatus_Completed,
DocInterfaceStatus_Failed,
AFPApplicationReceived,
AFPApplicationDecisioned,
AFPApplicationFunded,
AFPApplicationWithdrawn,
IncompleteApplicationStatus_Pending,
IncompleteApplicationStatus_Cancelled,
eDocsRescanRequested,
eDocsReturnToDealerRequested,
eDocsImported,
eDocsPendingImport,
eDocsArchived,
eDocsReturnedToDealer,
eDocsRescanRequestFailed,
eDocsReturnToDealerRequestFailed,
eDocsPurged,
EContractingStatus_Verified,
EContractingStatus_VerificationError,
EContractingStatus_ContractImported,
EContractingStatus_EContractingError,
EContractingStatus_EContractingCancelled,
MultiOffer,
MultiOfferAccepted,
}
}
@@ -0,0 +1,11 @@
namespace Landmark.OLA.Models.Decisioning.Enums
{
public enum DecisioningState
{
NotStarted,
Unknown,
Requested,
Ready,
MissingRequiredInformation
}
}
@@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.HomeMortgageDisclosureAct
{
public class Hmda
{
public ICollection<Race> Races { get; set; }
[StringLength(100)]
public string AmericanIndianAlaskaNativeTribe { get; set; }
[StringLength(100)]
public string OtherAsianRace { get; set; }
[StringLength(100)]
public string OtherPacificIslanderRace { get; set; }
public ICollection<Ethnicity> Ethnicities { get; set; }
[StringLength(100)]
public string OtherHispanicLatinoEthnicity { get; set; }
public List<Sex> Sexes { get; set; }
public bool? HmdaNoticeDisclosed { get; set; }
public Hmda()
{
Races = [];
Ethnicities = [];
Sexes = [];
}
}
}
@@ -0,0 +1,32 @@
using FluentValidation;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.HomeMortgageDisclosureAct.Validators
{
public class HmdaValidator : AbstractValidator<Hmda>
{
public HmdaValidator()
{
RuleFor(x => x.Races).NotEmpty()
.WithMessage($"No race information was provided. Set {Race.NotProvided} if none will be provided.");
RuleFor(x => x.Races).Must(races => races.Contains(Race.AmericanIndianAlaskaNative))
.When(x => !string.IsNullOrWhiteSpace(x.AmericanIndianAlaskaNativeTribe))
.WithMessage($"Include {Race.AmericanIndianAlaskaNative} in Races when a value is set in AmericanIndianAlaskaNativeTribe.");
RuleFor(x => x.Races).Must(races => races.Contains(Race.OtherAsian))
.When(x => !string.IsNullOrWhiteSpace(x.OtherAsianRace))
.WithMessage($"Include {Race.OtherAsian} in Races when a value is set in OtherAsianRace.");
RuleFor(x => x.Races).Must(races => races.Contains(Race.OtherPacificIslander))
.When(x => !string.IsNullOrWhiteSpace(x.OtherPacificIslanderRace))
.WithMessage($"Include {Race.OtherPacificIslander} in Races when a value is set in OtherPacificIslanderRace.");
RuleFor(x => x.Ethnicities).NotEmpty()
.WithMessage($"No ethnicity information was provided. Set {Ethnicity.NotProvided} if none will be provided.");
RuleFor(x => x.Ethnicities).Must(ethnicities => ethnicities.Contains(Ethnicity.OtherHispanicLatino))
.When(x => !string.IsNullOrWhiteSpace(x.OtherHispanicLatinoEthnicity))
.WithMessage($"Include {Ethnicity.OtherHispanicLatino} in Ethnicities when a value is set in OtherHispanicLatinoEthnicity.");
RuleFor(x => x.Sexes).NotEmpty()
.WithMessage($"No sex information was provided. Set {Sex.NotProvided} if none will be provided.");
}
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models
{
public interface IReferenceable
{
Guid Id { get; }
}
}
@@ -0,0 +1,20 @@
namespace Landmark.OLA.Models.Individuals
{
public class CoApplicant : Individual
{
public bool IsMarriedToPrimaryBorrower { get; set; }
public CoApplicant()
: base()
{
}
public CoApplicant(Guid id)
: base(id)
{
}
}
}
@@ -0,0 +1,54 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals
{
public class Employer : OlaModel
{
[Required]
[StringLength(60, MinimumLength = 1)]
//Due to downstream CRIF issues, applications with employer names containing special characters will be denied
[RegularExpression(@"[a-zA-Z0-9 ]*")]
public string Name { get; set; }
[Required]
//Nullable so that there is no default value
public bool? CurrentEmployer { get; set; } // "isCurrentFL"
[Required]
//Nullable so that there is no default value
public EmploymentTypeOption EmploymentType { get; set; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string Title { get; set; }
[Required]
public Income Income { get; set; }
//[Required]
//The current front end offers an upper limit of 10 years, 11 months.
[Range(0, 130)]
public int MonthsEmployed { get; set; }
//[Required]
//Nullable so that there is no default value
public DateTime? StartDate { get; set; }
// Required if CurrentEmployer == false
//Nullable so that there is no default value
public DateTime? EndDate { get; set; }
[MaxLength(180)]
public string WorkPhone { get; set; }
[Required]
//Nullable so that there is no default value
public LanguageOption? Language;
public Employer(Guid? employerId = null)
{
Id = employerId.HasValue ? employerId.Value.ToString() : null;
}
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public enum EmploymentTypeOption
{
NoneSelected,
Contract,
FullTime,
PartTime,
SelfEmployed,
Retired,
Temporary
}
}
@@ -0,0 +1,16 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
/// <summary>
/// EthnicityEnum from CRIF's commonSchema.xsd trimmed down to HMDA form options
/// </summary>
public enum Ethnicity
{
HispanicOrLatino,
NotHispanicOrLatino,
Mexican,
PuertoRican,
Cuban,
OtherHispanicLatino,
NotProvided
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public enum Frequency
{
Annually,
SemiAnnually,
Monthly,
SemiMonthly,
TwoWeeks,
Weekly,
Hourly
}
}
@@ -0,0 +1,9 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public enum LanguageOption
{
//A language value is required by the CRIF API
//TODO #229: Accept other values here?
English,
}
}
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public enum PhoneType
{
Mobile,
Home,
}
}
@@ -0,0 +1,26 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
/// <summary>
/// RaceEnum from CRIF's commonSchema.xsd trimmed down to HMDA form options
/// </summary>
public enum Race
{
AmericanIndianAlaskaNative,
Asian,
NativeHawaiian,
BlackOrAfricanAmerican,
White,
AsianIndian,
Chinese,
Filipino,
Japanese,
Korean,
Vietnamese,
OtherAsian,
Hawaiian,
GuamanianChamorro,
Samoan,
OtherPacificIslander,
NotProvided
}
}
@@ -0,0 +1,12 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
/// <summary>
/// Values from HMDA form
/// </summary>
public enum Sex
{
Female,
Male,
NotProvided,
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public enum Suffix
{
II,
III,
IV,
V,
Jr,
Sr
}
}
@@ -0,0 +1,45 @@
namespace Landmark.OLA.Models.Individuals.Enums
{
public class SuffixType
{
public string CD { get; set; }
public string Name { get; set; }
public Suffix SuffixEnum { get; set; }
public SuffixType(string cd, string name, Suffix suffix)
{
SuffixEnum = suffix;
CD = cd;
Name = name;
}
}
public sealed class SuffixMap
{
public SuffixMap() {}
public static readonly SuffixType II = new("2", "II", Suffix.II);
public static readonly SuffixType III = new("3", "III", Suffix.III);
public static readonly SuffixType IV = new("4", "IV", Suffix.IV);
public static readonly SuffixType V = new("5", "V", Suffix.V);
public static readonly SuffixType Jr = new("J", "Jr.", Suffix.Jr);
public static readonly SuffixType Sr = new("S", "Sr.", Suffix.Sr);
public static readonly SuffixType[] map = [II, III, IV, V, Jr, Sr];
public static SuffixType GetSuffixType(Suffix? suffix)
{
SuffixType match = null;
foreach (SuffixType suff in map)
{
if (suff.SuffixEnum == suffix)
{
match = suff;
}
}
return match;
}
}
}
@@ -0,0 +1,101 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals.Enums;
using Landmark.OLA.Models.Residences;
namespace Landmark.OLA.Models.Individuals
{
public class HistoryIndividual : IIndividualHistory
{
public Guid Id { get; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string FirstName { get; set; }
[StringLength(1, MinimumLength = 0)]
[RegularExpression(@"[a-zA-Z]")]
public string MiddleInitial { get; set; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string LastName { get; set; }
public Suffix? Suffix { get; set; }
[Required]
public CurrentResidenceAddress Address { get; set; }
public Address MailingAddress { get; set; }
[Required]
//Nullable so that there is no default value
public LanguageOption? Language { get; set; }
//A value is required for decisioning. May require input from user in the future.
[StringLength(60)]
public string CustomerNumber { get; set; } = "0";
[Required]
//Nullable so that there is no default value
public DateTime? DOB { get; set; }
[Required]
[StringLength(9, MinimumLength = 9)]
[RegularExpression(@"[0-9]+")]
public string SSN { get; set; }
[Required]
[StringLength(180, MinimumLength = 10)]
//CRIF accepts up to 180 characters.
//Dashes, periods, and parentheses are accepted but ignored.
//Numbers, letters, pound signs, and spaces are kept.
public string Phone { get; set; }
[Required]
//Nullable so that there is no default value
public PhoneType? PhoneType { get; set; }
[StringLength(180, MinimumLength = 10)]
public string SecondaryPhone { get; set; }
public PhoneType? SecondaryPhoneType { get; set; }
[Required] //Required by Landmark
[StringLength(180, MinimumLength = 3)]
[RegularExpression(@".+@.+")]
//Given that a user could enter a fake address,
//this is not exhaustive check for a valid address form
public string Email { get; set; }
[StringLength(180)]
[RegularExpression(@".+@.+")]
public string PrimaryApplicantEmail { get; set; }
[StringLength(180)]
[RegularExpression(@".+@.+")]
public string CoApplicantEmail { get; set; }
//TODO #228: Enforce time requirement of total MonthsResided across Address (on IndividualBase) and PreviousAddresses
public ICollection<ResidenceAddress> PreviousAddresses { get; set; }
public ICollection<Employer> Employers { get; set; }
public ICollection<Income> OtherIncomes { get; set; }
public bool? IsLandmarkEmployee { get; set; }
public HistoryIndividual()
{
Id = Guid.NewGuid();
}
public HistoryIndividual(Guid id)
{
Id = id;
}
[Required]
public bool HasOptedOutTCPA { get; set; }
}
}
@@ -0,0 +1,17 @@
using Landmark.OLA.Models.HomeMortgageDisclosureAct;
namespace Landmark.OLA.Models.Individuals
{
public interface IApplicant : IIndividualHistory
{
bool? IsMarried { get; set; }
MailableSpouse NonApplicantSpouse { get; set; }
HistoryIndividual DetailedNonApplicantSpouse { get; set; }
Hmda HmdaValues { get; set; }
bool HasAnyNonApplicantSpouse();
}
}
@@ -0,0 +1,7 @@
namespace Landmark.OLA.Models.Individuals
{
public interface ICoApplicant : IApplicant
{
bool IsMarriedToPrimaryBorrower { get; set; }
}
}
@@ -0,0 +1,49 @@
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals.Enums;
using Landmark.OLA.Models.Residences;
namespace Landmark.OLA.Models.Individuals
{
public interface IIndividualHistory : IReferenceable
{
string FirstName { get; set; }
string MiddleInitial { get; set; }
string LastName { get; set; }
Suffix? Suffix { get; set; }
string SSN { get; set; }
DateTime? DOB { get; set; }
string Phone { get; set; }
PhoneType? PhoneType { get; set; }
string SecondaryPhone { get; set; }
PhoneType? SecondaryPhoneType { get; set; }
string Email { get; set; }
LanguageOption? Language { get; set; }
string CustomerNumber { get; set; }
CurrentResidenceAddress Address { get; set; }
ICollection<ResidenceAddress> PreviousAddresses { get; set; }
Address MailingAddress { get; set; }
ICollection<Employer> Employers { get; set; }
ICollection<Income> OtherIncomes { get; set; }
bool? IsLandmarkEmployee { get; set; }
bool HasOptedOutTCPA { get; set; }
}
}
@@ -0,0 +1,24 @@
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals
{
public interface INonApplicant : IReferenceable
{
string FirstName { get; set; }
string MiddleInitial { get; set; }
string LastName { get; set; }
Address MailingAddress { get; set; }
string SSN { get; set; }
string Email { get; set; }
LanguageOption? Language { get; set; }
bool HasOptedOutTCPA { get; set; }
}
}
@@ -0,0 +1,68 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals
{
public class Income
{
[Required]
//Nullable so that there is no default value
public IncomeType? IncomeTypeCD { get; set; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string SourceName { get; set; }
// required based on type?
//Nullable so that there is no default value
public AlimonyStatus? AlimonyStatus { get; set; }
[Required]
[Range(minimum: 0, maximum: 1000000000)]
public decimal? AmountPerPeriod { get; set; }
[Required]
//Nullable so that there is no default value
public Frequency? Frequency { get; set; }
[Range(0, 168)]
public int? HoursPerWeek { get; set; }
[Required]
// Must be Excluded when part of a previous employer
//Nullable so that there is no default value
public IncomeStatus? IncomeStatus { get; set; }
}
public enum IncomeStatus
{
IncomeStatus_Excluded,
IncomeStatus_Included
}
public enum IncomeType
{
Alimony,
Bonuses,
ChildSupport,
RetirementDisability,
Dividends,
Commissions,
Other,
Spouse,
RentalProperty,
Interest,
SocialSecurity,
IndividualAccount,
Salary
}
public enum AlimonyStatus
{
CourtOrdered,
OralUnderstanding,
Written
}
}
@@ -0,0 +1,16 @@
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals
{
public class IncomeLimits
{
private static Limit HourLimit => new() { min = 0, max = 999 };
private static Limit AnnualLimit => new() { min = 0, max = 1000000000 };
public static Dictionary<Frequency, Limit> Limits => new()
{
{ Frequency.Hourly, HourLimit },
{ Frequency.Annually, AnnualLimit },
};
}
}
@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.HomeMortgageDisclosureAct;
namespace Landmark.OLA.Models.Individuals
{
public class Individual : HistoryIndividual, IApplicant
{
[Required]
//Nullable so that there is no default value
public bool? IsMarried { get; set; }
public MailableSpouse NonApplicantSpouse { get; set; }
public HistoryIndividual DetailedNonApplicantSpouse { get; set; }
public Hmda HmdaValues { get; set; }
public Individual()
: base()
{
HmdaValues = new Hmda();
}
public Individual(Guid id)
: base(id)
{
HmdaValues = new Hmda();
}
public Individual( Hmda hmda)
: base()
{
HmdaValues = hmda;
}
public bool HasAnyNonApplicantSpouse()
{
var hasNonApplicantSpouse = NonApplicantSpouse is not null;
var hasDetailedNonApplicantSpouse = DetailedNonApplicantSpouse is not null;
return hasNonApplicantSpouse || hasDetailedNonApplicantSpouse;
}
}
}
@@ -0,0 +1,51 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals
{
public class MailableSpouse : INonApplicant
{
public Guid Id { get; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string FirstName { get; set; }
[StringLength(1, MinimumLength = 0)]
[RegularExpression(@"[a-zA-Z]")]
public string MiddleInitial { get; set; }
[Required]
[StringLength(60, MinimumLength = 1)]
public string LastName { get; set; }
[Required]
public Address MailingAddress { get; set; }
[StringLength(9, MinimumLength = 9)]
[RegularExpression(@"[0-9]+")]
public string SSN { get; set; }
[StringLength(180, MinimumLength = 3)]
[RegularExpression(@".+@.+")]
public string Email { get; set; }
[Required]
//Nullable so that there is no default value
public LanguageOption? Language { get; set; }
public MailableSpouse()
{
Id = Guid.NewGuid();
}
public MailableSpouse(Guid id)
{
Id = id;
}
[Required]
public bool HasOptedOutTCPA { get; set; }
}
}
@@ -0,0 +1,26 @@
using FluentValidation;
namespace Landmark.OLA.Models.Individuals.Validators
{
class ApplicantValidator : AbstractValidator<IApplicant>
{
public ApplicantValidator()
{
RuleForEach(x => x.Employers).SetValidator(new EmployerValidator());
When(x => x.DetailedNonApplicantSpouse is not null, () =>
{
RuleForEach(x => x.DetailedNonApplicantSpouse.Employers).SetValidator(new EmployerValidator());
});
When(x => x.NonApplicantSpouse is not null, () =>
{
//An individual can only have a single spouse entry
RuleFor(x => x.DetailedNonApplicantSpouse).Null();
});
RuleFor(x => x.SecondaryPhoneType).NotNull().When(x => x.SecondaryPhone?.Length > 0)
.WithMessage("SecondaryPhoneType must be set when a SecondaryPhone value is present.");
}
}
}
@@ -0,0 +1,16 @@
using FluentValidation;
using Landmark.OLA.Models.Individuals.Enums;
namespace Landmark.OLA.Models.Individuals.Validators
{
class EmployerValidator : AbstractValidator<Employer>
{
public EmployerValidator()
{
When(x => x.Income.Frequency == Frequency.Hourly, () =>
{
RuleFor(x => x.Income.HoursPerWeek).NotNull().WithMessage("Hourly employment must state hours per week.");
});
}
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.3.6" />
<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="MongoDB.Driver" Version="2.9.0" />
<PackageReference Include="SharpCompress" Version="0.29.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>
</Project>
@@ -0,0 +1,8 @@
namespace Landmark.OLA.Models
{
public struct Limit
{
public decimal min;
public decimal max;
}
}
@@ -0,0 +1,32 @@
using MongoDB.Bson.Serialization.Attributes;
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models.Loan
{
public class BranchData
{
[Required]
[StringLength(5, MinimumLength = 5, ErrorMessage = "Zipcodes must be 5 digits")]
public string Zipcode { get; set; }
[Required]
[Range(minimum: 1, maximum: 99999999)]
public int ChannelId { get; set; }
public BranchData(string zip, int channel)
{
this.Zipcode = zip;
this.ChannelId = channel;
}
}
[BsonIgnoreExtraElements]
public class BranchConfig : OlaModel
{
public DateTime Date { get; set; }
[Required]
public List<BranchData> BranchCodesByZip { get; set; }
}
}
@@ -0,0 +1,485 @@
namespace Landmark.OLA.Models.Loan
{
public static class BranchMapping
{
public static Dictionary<string, int> BranchCodesByZip = new Dictionary<string, int>
{
{"53001", 1561599},
{"53002", 1561599},
{"53003", 1561245},
{"53004", 1561591},
{"53005", 1561255},
{"53006", 1561657},
{"53007", 1561255},
{"53008", 1561255},
{"53010", 1561599},
{"53011", 1561599},
{"53012", 1561591},
{"53013", 1561591},
{"53014", 1561657},
{"53015", 1561241},
{"53016", 1561665},
{"53017", 1561664},
{"53018", 1561267},
{"53019", 1561599},
{"53020", 1561599},
{"53021", 1561604},
{"53022", 1561288},
{"53023", 1561288},
{"53024", 1561591},
{"53026", 1561591},
{"53027", 1561662},
{"53029", 1561267},
{"53031", 1561599},
{"53032", 1561657},
{"53033", 1561664},
{"53034", 1561665},
{"53035", 1561657},
{"53036", 1561284},
{"53037", 1561604},
{"53038", 1561284},
{"53039", 1561665},
{"53040", 1561599},
{"53042", 1561599},
{"53044", 1561599},
{"53045", 1561588},
{"53046", 1561265},
{"53047", 1561284},
{"53048", 1561662},
{"53049", 1561657},
{"53050", 1561657},
{"53051", 1561288},
{"53052", 1561288},
{"53056", 1561267},
{"53057", 1561599},
{"53058", 1561267},
{"53059", 1561662},
{"53060", 1561599},
{"53061", 1561599},
{"53062", 1561599},
{"53063", 1561599},
{"53064", 1561267},
{"53065", 1561657},
{"53066", 1561245},
{"53069", 1561267},
{"53070", 1561591},
{"53072", 1561262},
{"53073", 1561599},
{"53074", 1561591},
{"53075", 1561599},
{"53076", 1561664},
{"53078", 1561662},
{"53079", 1561599},
{"53080", 1561591},
{"53081", 1561591},
{"53082", 1561604},
{"53083", 1561599},
{"53085", 1561599},
{"53086", 1561662},
{"53088", 1561657},
{"53089", 1561265},
{"53090", 1561599},
{"53091", 1561657},
{"53092", 1561591},
{"53093", 1561599},
{"53094", 1561284},
{"53095", 1561604},
{"53096", 1561599},
{"53097", 1561591},
{"53098", 1561284},
{"53099", 1561662},
{"53101", 1561631},
{"53102", 1561631},
{"53103", 2081492},
{"53104", 1561624},
{"53105", 1561631},
{"53108", 1561598},
{"53109", 1561631},
{"53110", 1561616},
{"53114", 1561631},
{"53115", 1561631},
{"53118", 1561245},
{"53119", 1561245},
{"53120", 1561631},
{"53121", 1561631},
{"53122", 1561588},
{"53125", 1561631},
{"53126", 1561624},
{"53127", 1561262},
{"53128", 1561631},
{"53129", 1561289},
{"53130", 1561289},
{"53132", 1561289},
{"53137", 1561284},
{"53138", 1561661},
{"53139", 1561631},
{"53140", 1561639},
{"53141", 1561639},
{"53142", 1561639},
{"53143", 1561639},
{"53144", 1561639},
{"53146", 1561243},
{"53147", 1561631},
{"53148", 1561631},
{"53149", 2081492},
{"53150", 2081492},
{"53151", 1561243},
{"53152", 1561631},
{"53153", 1561262},
{"53154", 1561598},
{"53156", 1561245},
{"53157", 1561631},
{"53158", 1561639},
{"53159", 1561631},
{"53167", 1561631},
{"53168", 1561631},
{"53170", 1561631},
{"53171", 1561639},
{"53172", 1561598},
{"53176", 1561631},
{"53177", 1561624},
{"53178", 1561245},
{"53179", 1561631},
{"53181", 1561631},
{"53182", 1561624},
{"53183", 1561267},
{"53184", 1561631},
{"53185", 1561631},
{"53186", 1561262},
{"53187", 1561262},
{"53188", 1561262},
{"53189", 1561262},
{"53190", 1561245},
{"53191", 1561631},
{"53192", 1561631},
{"53194", 1561624},
{"53195", 1561631},
{"53199", 1561631},
{"53201", 1561241},
{"53202", 1561615},
{"53203", 1561241},
{"53204", 1561241},
{"53205", 1561241},
{"53206", 1561241},
{"53207", 1561615},
{"53208", 1561241},
{"53209", 1561255},
{"53210", 1561241},
{"53211", 1561615},
{"53212", 1561241},
{"53213", 1561241},
{"53214", 1561587},
{"53215", 1561283},
{"53216", 1561241},
{"53217", 1561255},
{"53218", 1561255},
{"53219", 1561241},
{"53220", 1561283},
{"53221", 1561283},
{"53222", 1561255},
{"53223", 1561255},
{"53224", 1561255},
{"53225", 1561255},
{"53226", 1561255},
{"53227", 1561587},
{"53228", 1561587},
{"53233", 1561241},
{"53234", 1561241},
{"53235", 1561616},
{"53237", 1561283},
{"53259", 1561615},
{"53263", 1561255},
{"53267", 1561241},
{"53268", 1561615},
{"53274", 1561283},
{"53278", 1561241},
{"53288", 1561615},
{"53290", 1561241},
{"53293", 1561241},
{"53295", 1561241},
{"53401", 1561639},
{"53402", 1561285},
{"53403", 1561639},
{"53404", 1561639},
{"53405", 1561285},
{"53406", 1561624},
{"53407", 1561639},
{"53408", 1561624},
{"53490", 1561285},
{"53501", 1561661},
{"53502", 1561661},
{"53503", 1561661},
{"53505", 1561631},
{"53506", 1561661},
{"53507", 1561661},
{"53508", 1561661},
{"53511", 1561661},
{"53512", 1561661},
{"53515", 1561661},
{"53517", 1561661},
{"53520", 1561661},
{"53521", 1561661},
{"53522", 1561661},
{"53523", 1561284},
{"53525", 1561661},
{"53526", 1561661},
{"53527", 1561661},
{"53528", 1561661},
{"53529", 1561661},
{"53531", 1561661},
{"53532", 1561661},
{"53533", 1561661},
{"53534", 1561661},
{"53535", 1561661},
{"53536", 1561661},
{"53537", 1561661},
{"53538", 1561284},
{"53542", 1561661},
{"53543", 1561661},
{"53544", 1561661},
{"53545", 1561661},
{"53546", 1561631},
{"53547", 1561661},
{"53548", 1561661},
{"53549", 1561284},
{"53550", 1561661},
{"53551", 1561284},
{"53553", 1561661},
{"53555", 1561661},
{"53557", 1561665},
{"53558", 1561661},
{"53559", 1561660},
{"53560", 1561661},
{"53562", 1561661},
{"53563", 1561284},
{"53565", 1561661},
{"53566", 1561661},
{"53570", 1561661},
{"53571", 1561661},
{"53572", 1561661},
{"53574", 1561661},
{"53575", 1561661},
{"53576", 1561661},
{"53579", 1561660},
{"53580", 1561661},
{"53582", 1561661},
{"53585", 1561631},
{"53589", 1561661},
{"53590", 1561661},
{"53593", 1561661},
{"53594", 1561284},
{"53595", 1561661},
{"53596", 1561661},
{"53597", 1561661},
{"53598", 1561661},
{"53701", 1561661},
{"53702", 1561661},
{"53703", 1561661},
{"53704", 1561661},
{"53705", 1561661},
{"53706", 1561661},
{"53707", 1561661},
{"53708", 1561661},
{"53711", 1561661},
{"53713", 1561661},
{"53714", 1561661},
{"53715", 1561661},
{"53716", 1561661},
{"53717", 1561661},
{"53718", 1561661},
{"53719", 1561661},
{"53725", 1561661},
{"53726", 1561661},
{"53744", 1561661},
{"53774", 1561661},
{"53777", 1561661},
{"53778", 1561661},
{"53779", 1561661},
{"53782", 1561661},
{"53783", 1561661},
{"53784", 1561661},
{"53785", 1561661},
{"53786", 1561661},
{"53788", 1561661},
{"53789", 1561661},
{"53790", 1561661},
{"53791", 1561661},
{"53792", 1561661},
{"53793", 1561661},
{"53794", 1561661},
{"53901", 1561660},
{"53911", 1561661},
{"53916", 1561649},
{"53919", 1561649},
{"53920", 1561660},
{"53922", 1561657},
{"53923", 1561660},
{"53925", 1561660},
{"53926", 1561660},
{"53928", 1561660},
{"53930", 1561660},
{"53931", 1561649},
{"53932", 1561660},
{"53933", 1561649},
{"53935", 1561660},
{"53939", 1561649},
{"53946", 1561649},
{"53947", 1561649},
{"53949", 1561660},
{"53952", 1561660},
{"53953", 1561660},
{"53954", 1561660},
{"53955", 1561660},
{"53956", 1561660},
{"53957", 1561649},
{"53960", 1561660},
{"53963", 1561657},
{"53964", 1561660},
{"53965", 1561660},
{"53969", 1561661},
{"54106", 1561657},
{"54110", 1561599},
{"54113", 1561657},
{"54115", 1561657},
{"54123", 1561657},
{"54126", 1561599},
{"54129", 1561657},
{"54130", 1561657},
{"54131", 1561657},
{"54136", 1561657},
{"54140", 1561657},
{"54152", 1561657},
{"54155", 1561657},
{"54160", 1561599},
{"54162", 1561657},
{"54165", 1561657},
{"54169", 1561657},
{"54170", 1561657},
{"54173", 1561657},
{"54180", 1561657},
{"54207", 1561599},
{"54208", 1561599},
{"54214", 1561599},
{"54215", 1561599},
{"54220", 1561599},
{"54221", 1561599},
{"54227", 1561599},
{"54228", 1561599},
{"54229", 1561599},
{"54230", 1561599},
{"54232", 1561599},
{"54240", 1561599},
{"54241", 1561599},
{"54245", 1561599},
{"54247", 1561599},
{"54301", 1561657},
{"54302", 1561599},
{"54303", 1561657},
{"54304", 1561657},
{"54305", 1561657},
{"54306", 1561657},
{"54307", 1561657},
{"54308", 1561657},
{"54311", 1561599},
{"54313", 1561657},
{"54324", 1561657},
{"54344", 1561657},
{"54901", 1561657},
{"54902", 1561657},
{"54903", 1561657},
{"54904", 1561657},
{"54906", 1561657},
{"54911", 1561657},
{"54912", 1561657},
{"54913", 1561657},
{"54914", 1561657},
{"54915", 1561657},
{"54919", 1561657},
{"54922", 1561657},
{"54923", 1561615},
{"54927", 1561657},
{"54931", 1561657},
{"54932", 1561657},
{"54934", 1561649},
{"54935", 1561657},
{"54936", 1561657},
{"54937", 1561657},
{"54941", 1561649},
{"54942", 1561657},
{"54944", 1561657},
{"54947", 1561657},
{"54952", 1561657},
{"54956", 1561657},
{"54957", 1561657},
{"54960", 1561649},
{"54963", 1561657},
{"54964", 1561657},
{"54968", 1561649},
{"54971", 1561649},
{"54974", 1561657},
{"54979", 1561657},
{"54980", 1561649},
{"54985", 1561657},
{"54986", 1561657},
{"60001", 1561631},
{"60002", 1561624},
{"60010", 1561631},
{"60011", 1561631},
{"60012", 1561631},
{"60013", 1561631},
{"60014", 1561631},
{"60015", 1561639},
{"60020", 1561631},
{"60021", 1561631},
{"60030", 1561631},
{"60031", 1561639},
{"60033", 1561631},
{"60034", 1561631},
{"60035", 1561639},
{"60037", 1561639},
{"60039", 1561631},
{"60040", 1561639},
{"60041", 1561631},
{"60042", 1561631},
{"60044", 1561639},
{"60045", 1561639},
{"60046", 1561631},
{"60047", 1561631},
{"60048", 1561639},
{"60049", 1561631},
{"60050", 1561631},
{"60051", 1561631},
{"60060", 1561639},
{"60061", 1561639},
{"60064", 1561639},
{"60069", 1561639},
{"60071", 1561631},
{"60072", 1561631},
{"60073", 1561631},
{"60075", 1561639},
{"60079", 1561639},
{"60081", 1561631},
{"60083", 1561639},
{"60084", 1561631},
{"60085", 1561639},
{"60086", 1561631},
{"60087", 1561639},
{"60088", 1561639},
{"60089", 1561639},
{"60092", 1561631},
{"60096", 1561639},
{"60097", 1561631},
{"60098", 1561631},
{"60099", 1561639},
{"60102", 1561631},
{"60142", 1561631},
{"60152", 1561631},
{"60156", 1561631},
{"60180", 1561631}
};
}
}
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using MongoDB.Bson.Serialization.Attributes;
namespace Landmark.OLA.Models.Loan
{
public class BranchNameData
{
[Required]
[Range(minimum: 1, maximum: 99999999)]
public int ChannelId { get; set; }
[Required]
[RegularExpression("^[a-zA-Z ]*$")]
[StringLength(150, ErrorMessage = "Branch Names must be less than 150 characters")]
public string BranchName { get; set; }
}
[BsonIgnoreExtraElements]
public class BranchNameConfig : OlaModel
{
public DateTime Date { get; set; }
[Required]
public List<BranchNameData> BranchNamesByChannelId { get; set; }
}
}
@@ -0,0 +1,60 @@
using System.ComponentModel.DataAnnotations;
using MongoDB.Bson.Serialization.Attributes;
namespace Landmark.OLA.Models.Loan
{
[BsonIgnoreExtraElements]
public class DealershipAuthRequest
{
[Required]
public string DealershipCode { get; set; }
public string DealershipName { get; set; }
[Required]
public string DealershipPassword { get; set; }
[Required]
public string ApplicantLastName { get; set; }
[Required]
public string ApplicationId { get; set; }
public DealershipAuthRequest(string dealershipCode, string dealershipName, string dealershipPassword, string applicantLastName, string applicationId)
{
this.DealershipCode = dealershipCode;
this.DealershipName = dealershipName;
this.DealershipPassword = dealershipPassword;
this.ApplicantLastName = applicantLastName;
this.ApplicationId = applicationId;
}
public static bool ValidateAuthRequest(DealershipAuthRequest dealershipRequest, DealershipAuthRequest actionResponse)
{
return dealershipRequest?.Equals(actionResponse) ?? false;
}
public override bool Equals(Object authRequest)
{
if (authRequest is not DealershipAuthRequest request)
{
return false;
}
return string.Equals(DealershipCode, request.DealershipCode, StringComparison.Ordinal) &&
string.Equals(DealershipPassword, request.DealershipPassword, StringComparison.Ordinal) &&
string.Equals(ApplicantLastName, request.ApplicantLastName, StringComparison.OrdinalIgnoreCase) &&
string.Equals(ApplicationId, request.ApplicationId, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
{
return HashCode.Combine(
DealershipCode?.ToUpperInvariant(),
DealershipPassword,
ApplicantLastName?.ToUpperInvariant(),
ApplicationId?.ToUpperInvariant()
);
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Http;
namespace Landmark.OLA.Models.Loan
{
public class DocumentUploadFromLoanApplication
{
public IFormFile File { get; set; }
public string Section { get; set; }
public string SectionId { get; set; }
public string Description { get; set; }
public static bool IsNullOrEmpty(DocumentUploadFromLoanApplication file)
{
if(file == null || file.File == null || String.IsNullOrEmpty(file.SectionId) || String.IsNullOrEmpty(file.Description))
{
return true;
}
if(file.File.Length == 0 || String.IsNullOrEmpty(file.File.FileName))
{
return true;
}
return false;
}
}
}
@@ -0,0 +1,11 @@
namespace Landmark.OLA.Models.Loan.Enums
{
public enum LoanErrorCode
{
No_Error,
Error_Updating,
Error_Submitting,
Error_Deleting
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Loan.Enums
{
public enum LoanStatus
{
Application_Error,
Vendor_Error,
In_Progress,
Deleted,
Submitted,
Decisioned
}
}
@@ -0,0 +1,29 @@
namespace Landmark.OLA.Models.Loan.Enums
{
public enum LoanType
{
CC_CashBack,
CC_Rewards,
CC_Student,
CC_Secure,
EQ_HELOC_Flex,
EQ_Home_Equity_Fixed,
EQ_Mtg_Refi_Express,
EQ_Mfg_Home_With_or_Without_Land,
V_Vehicle,
V_VehiclePreapproval,
M_Boat,
P_Powersport,
T_TrailerMotorhome,
S_Signature,
S_Special
}
public enum HMDALoanType
{
EQ_HELOC_Flex,
EQ_Home_Equity_Fixed,
EQ_Mtg_Refi_Express,
EQ_Mfg_Home_With_or_Without_Land
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Loan
{
public class Filter
{
public int Count { get; set; }
public bool SortDesc { get; set; }
public string SortField { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public FilterField[] FilterField { get; set; }
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.Loan
{
public class FilterField
{
public string Field { get; set; }
public string FilterValue { get; set; }
public bool Equal { get; set; }
public bool NotEqual { get; set; }
public bool GreaterThan { get; set; }
public bool LessThan { get; set; }
}
}
@@ -0,0 +1,80 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.Loan.Enums;
using MongoDB.Bson.Serialization.Attributes;
namespace Landmark.OLA.Models.Loan
{
[BsonIgnoreExtraElements]
public class LoanApplication : OlaModel
{
[Required]
//Nullable so that there is no default value
public LoanType? LoanType { get; set; }
[Required]
//One Billion is an arbitrary limit but I'm pretty confidant that it will be an acceptable upper limit for a long time.
[Range(minimum: 0, maximum: 1000000000)]
//Nullable so that there is no default value
public decimal? RequestedAmount { get; set; }
//Required for Personal Signature loans.
[StringLength(100)]
public string PurposeOfLoanSignature { get; set; }
public DateTime Date { get; private set; }
[Range(minimum: 0, maximum: 1000000000)]
//Required for certain loan types. Checked by FluentValidation.
public int? TermLengthInMonths { get; set; }
//these could be ICollection if needed, would impact builders
public RealEstate RealEstate { get; set; }
public Vehicle Vehicle { get; set; }
public ManufacturedHome ManufacturedHome { get; set; }
public Guid SecuredCardCollateralId { get; }
[Required]
//Nullable so that there is no default value
//Values must match loan type. Checked by FluentValidation.
public LoanPurpose.Purpose? Purpose { get; set; }
[Required]
public Individual PrimaryBorrower { get; set; }
public CoApplicant CoApplicant { get; set; }
public string ModFeeAccount { get; set; }
//3500 is maximum crif note length
[StringLength(3500)]
[RegularExpression(@"[\w\d\s\.\,\&\n\r\@\-\;\'\!]*")]
public string Note { get; set; }
public string PrimaryContactMethod { get; set; }
public string PromoCode { get; set; }
public string LoanTitle { get; set; }
[StringLength(3450)]
public string ClientMessage { get; set; }
public DateTime DisclosureDate { get; set; }
public string IPAddress { get; set; }
public IList<LoanApplicationFile> Files { get; set; }
public LoanApplication()
{
Date = DateTime.Now;
SecuredCardCollateralId = Guid.NewGuid();
Files = [];
}
public LoanApplication(DateTime? loanDate = null, Guid? securedCardCollateralId = null, string loanId = null)
{
Date = loanDate ?? DateTime.Now;
SecuredCardCollateralId = securedCardCollateralId ?? Guid.NewGuid();
Id = loanId ?? null;
Files = [];
}
}
}
@@ -0,0 +1,42 @@
namespace Landmark.OLA.Models.Loan
{
public class LoanApplicationFile
{
public string CosmosLoanId { get; set; }
public Guid FileId { get; set; }
public string Name { get; set; }
public string Section { get; set; }
public string SectionId { get; set; }
public string Description { get; set; }
public LoanApplicationFile(string cosmosLoanId, Guid fileId, string name, string section, string sectionId, string description)
{
CosmosLoanId = cosmosLoanId;
FileId = fileId;
Name = name;
Section = section;
SectionId = sectionId;
Description = description;
}
public string GetBlobPath()
{
return $"{CosmosLoanId}/{FileId}";
}
public Dictionary<string, string> GetMetadata()
{
var metadata = new Dictionary<string, string>
{
{ "Name", Name },
{ "Section", Section },
{ "SectionId", SectionId.ToString() },
{ "Description", Description },
{ "CosmosLoanId", CosmosLoanId }
};
return metadata;
}
}
}
@@ -0,0 +1,34 @@
using Landmark.OLA.Models.Loan.Enums;
using MongoDB.Bson.Serialization.Attributes;
namespace Landmark.OLA.Models.Loan
{
[BsonIgnoreExtraElements]
public class LoanContainer : OlaModel
{
public LoanApplication LoanApplication { get; set; }
public LoanType LoanType { get; set; }
public LoanPage Page { get; set; }
public LoanStatus Status { get; set; }
public LoanError Error { get; set; }
public DateTime Date { get; private set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Branch { get; set; }
public string LoanId { get; set; }
public string MemberId { get; set; }
public string IndividualId { get; set; }
public LoanContainer()
{
Date = DateTime.UtcNow; //allows us to have control over date format
}
public LoanContainer(LoanApplication loanApplication, DateTime? loanDate = null, LoanStatus status = LoanStatus.In_Progress)
{
LoanApplication = loanApplication ?? null;
Date = loanDate ?? DateTime.UtcNow;
Status = status;
}
}
}
@@ -0,0 +1,15 @@
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models.Loan {
public class LoanError
{
public LoanErrorCode code;
public string message;
public LoanError(LoanErrorCode code, string message)
{
this.code = code;
this.message = message;
}
}
}
@@ -0,0 +1,36 @@
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models.Loan
{
public class LoanLimits
{
private static Limit EqLimit => new() { min = 10000, max = 250000 };
private static Limit EqHighLimit => new() { min = 10000, max = 500000 };
private static Limit VLimit => new() { min = 250, max = 250000 };
private static Limit MfgHome => new() { min = 250, max = 250000 };
private static Limit SLimit => new() { min = 250, max = 25000 };
private static Limit SpecLimit => new() { min = 250, max = 10000 };
public static Dictionary<LoanType, Limit> Limits => new()
{
{ LoanType.EQ_HELOC_Flex, EqHighLimit },
{ LoanType.EQ_Mtg_Refi_Express, EqLimit },
{ LoanType.EQ_Home_Equity_Fixed, EqHighLimit },
{ LoanType.EQ_Mfg_Home_With_or_Without_Land, MfgHome },
{ LoanType.V_Vehicle, VLimit },
{ LoanType.V_VehiclePreapproval, VLimit },
{ LoanType.M_Boat, VLimit },
{ LoanType.P_Powersport, VLimit },
{ LoanType.T_TrailerMotorhome, MfgHome }, //same limits as mfHome
{ LoanType.S_Signature, SLimit },
{ LoanType.S_Special, SpecLimit },
};
public static bool IsValidAmountForType(LoanType loanType, decimal requestedAmount)
{
var limit = Limits[loanType];
return requestedAmount >= limit.min && requestedAmount <= limit.max;
}
}
}
@@ -0,0 +1,18 @@
namespace Landmark.OLA.Models.Loan
{
public class LoanPage
{
public bool hasPersonal;
public bool hasAddress;
public bool hasEmployment;
public bool hasMemberInfo;
public bool hasSpouseInfo;
public bool hasTattleSpouseInfo;
public bool hasResidenceInfo;
public bool hasVehicleCollateral;
public bool hasHMDA;
public bool hasDisclosure;
public bool hasCollateralInfo;
public bool hasNote;
}
}
@@ -0,0 +1,109 @@
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models.Loan
{
public static class LoanPurpose
{
//Acceptable values taken from CRIF database
/*
select p.shortname, p.name, p.productid, lp.name, lp.[description],
lp.loanpurposeid, lp.isactivefl from v_RptProduct_v001 as p
inner join v_RptProductLoanPurpose_v001 as plp
on plp.productid = p.productid
inner join v_RptLoanPurpose_v001 as lp
on lp.loanpurposeid = plp.loanpurposeid
order by p.productid
*/
private static readonly List<Purpose> _ccPurposes = new List<Purpose>
{ Purpose.Other, Purpose.Consolidation, Purpose.Reestablish_Credit};
private static readonly List<Purpose> _eqPurposes = new List<Purpose>
{ Purpose.Home_Improvement, Purpose.Home_Purchase, Purpose.Home_Refinance,
Purpose.Other, Purpose.Cash_Out_Refinance };
private static readonly List<Purpose> _mfgHomePurposes = new List<Purpose>
{ Purpose.Home_Improvement, Purpose.Purchase_Manufactured_Home, Purpose.Home_Refinance,
Purpose.Other, Purpose.Cash_Out_Refinance };
private static readonly List<Purpose> _vPurposes = new List<Purpose>
{ Purpose.Purchase, Purpose.Cash_Out_Refinance, Purpose.Refinance_Vehicle };
private static readonly List<Purpose> _mPurposes = new List<Purpose>
{ Purpose.Purchase, Purpose.Refinance_Vehicle };
private static readonly List<Purpose> _pPurposes = new List<Purpose>
{ Purpose.Purchase, Purpose.Refinance_Vehicle };
private static readonly List<Purpose> _tPurposes = new List<Purpose>
{ Purpose.Purchase, Purpose.Refinance_Vehicle };
private static readonly List<Purpose> _sPurposes = new List<Purpose>
{ Purpose.Other, Purpose.Consolidation};
public static Dictionary<LoanType, List<Purpose>> TypePurposes => new Dictionary<LoanType, List<Purpose>>
{
{ LoanType.CC_CashBack, _ccPurposes },
{ LoanType.CC_Rewards, _ccPurposes },
{ LoanType.CC_Student, _ccPurposes },
{ LoanType.CC_Secure, _ccPurposes },
{ LoanType.EQ_HELOC_Flex, _eqPurposes },
{ LoanType.EQ_Mtg_Refi_Express, _eqPurposes },
{ LoanType.EQ_Home_Equity_Fixed, _eqPurposes },
{ LoanType.EQ_Mfg_Home_With_or_Without_Land, _mfgHomePurposes },
{ LoanType.V_Vehicle, _vPurposes },
{ LoanType.V_VehiclePreapproval, _vPurposes },
{ LoanType.M_Boat, _mPurposes },
{ LoanType.P_Powersport, _pPurposes },
{ LoanType.T_TrailerMotorhome, _tPurposes },
{ LoanType.S_Signature, _sPurposes },
{ LoanType.S_Special, _sPurposes },
};
public static bool IsValidPurposeForType(LoanType? type, Purpose? purpose)
{
if (!type.HasValue)
throw new ArgumentException("LoanType cannot be null");
if (!purpose.HasValue)
throw new ArgumentException("Purpose cannot be null");
return IsValidPurposeForType(type.Value, purpose.Value);
}
public static bool IsValidPurposeForType(LoanType type, Purpose purpose)
{
return TypePurposes[type].Contains(purpose);
}
//Enum names and values based on CRIF's v_RptProductLoanPurpose_v001 table
//joined with v_RptProductLoanPurpose_v001
//to find purposes that are active and in use
/*
select distinct lp.loanpurposeid, lp.name,lp.[description]
from v_RptLoanPurpose_v001 as lp
inner join v_RptProductLoanPurpose_v001 as plp
on lp.loanpurposeid = plp.loanpurposeid
where isactivefl = 1
*/
public enum Purpose
{
Home_Improvement = 3,
Home_Purchase = 4,
Home_Refinance = 5,
Purchase_Boat = 8,
Purchase = 12,
Other = 149,
Refinance = 141,
Dental_Expense = 142,
Medical_Expense = 143,
Cash_Out_Refinance = 144,
Refinance_Vehicle = 146,
Consolidation = 148,
Reestablish_Credit = 150,
Purchase_Manufactured_Home = 151,
}
}
}
@@ -0,0 +1,155 @@
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Addresses.Enums;
using Landmark.OLA.Models.Individuals;
using Landmark.OLA.Models.Individuals.Enums;
using Landmark.OLA.Models.Loan.Enums;
using Landmark.OLA.Models.Residences;
namespace Landmark.OLA.Models.Loan
{
public class MockApplication
{
public readonly LoanApplication LoanApplication =
new(DateTime.Parse("2018-02-21T16:52:47.62"), new Guid("9ed5a50f-279c-49b0-b5b4-d5b614e42501"))
{
LoanType = LoanType.CC_Secure,
RequestedAmount = 1320,
PrimaryContactMethod = "1234567890",
Purpose = LoanPurpose.Purpose.Other,
PrimaryBorrower = new Individual(new Guid("8066fa2a-16ba-4148-b28c-2926ba263c5f"))
{
FirstName = "Jane",
MiddleInitial = "A",
LastName = "Doe",
DOB = new DateTime(2018, 7, 9),
Phone = "1235555555",
PhoneType = PhoneType.Home,
SSN = "000090001",
Email = "[email protected]",
IsMarried = false,
Language = LanguageOption.English,
Address = new CurrentResidenceAddress(new Guid("bf7e502a-9830-43c6-b603-1a32294c7e1a"))
{
Address1 = "123 Main St",
City = "New Berlin",
State = StateOption.WI,
Country = CountryOption.UnitedStates,
PostalCode = "53151",
MonthsResided = 24,
ResidenceType = ResidenceTypeOption.Own,
MonthlyPayment = 100,
},
MailingAddress = new Address(new Guid("f84db562-36a0-4039-9ce5-346a2ba6899b"))
{
Address1 = "321 Main St",
City = "New Berlin",
State = StateOption.WI,
Country = CountryOption.UnitedStates,
PostalCode = "53151"
},
Employers =
[
new Employer(employerId: new Guid("eee4efa3-327f-4ce5-9e8b-8c3e7a865fff"))
{
Name = "College Pro Painters",
Title = "tester",
WorkPhone = "9992245555",
MonthsEmployed = 60,
StartDate = new DateTime(2017, 01, 01),
CurrentEmployer = true,
EmploymentType = EmploymentTypeOption.FullTime,
Language = LanguageOption.English,
Income = new Income
{
IncomeTypeCD = IncomeType.Salary,
SourceName = "Salary",
AmountPerPeriod = 5000,
Frequency = Frequency.Annually,
IncomeStatus = IncomeStatus.IncomeStatus_Included
}
}
]
},
CoApplicant = new CoApplicant(new Guid("ba99037f-df92-46d4-a477-ffecea1f4837"))
{
FirstName = "John",
LastName = "Smith",
DOB = new DateTime(2018, 7, 25),
SSN = "111221111",
Phone = "5555550983",
PhoneType = PhoneType.Mobile,
Email = "[email protected]",
IsMarried = true,
IsMarriedToPrimaryBorrower = false,
Language = LanguageOption.English,
Address = new CurrentResidenceAddress(new Guid("ae795a5f-1ea3-4186-b4b8-e9f9b81e3059"))
{
Address1 = "339 1st St",
Address2 = "#76",
City = "Milwaukee",
State = StateOption.IL,
Country = CountryOption.UnitedStates,
PostalCode = "53202",
ResidenceType = ResidenceTypeOption.Rent,
MonthsResided = 12,
MonthlyPayment = 100,
},
PreviousAddresses =
[
new ResidenceAddress(new Guid("e7e881f0-d371-4371-8022-cce4f700e6d6"))
{
Address1 = "339 1st St",
Address2 = "#80",
City = "Milwaukee",
State = StateOption.WI,
Country = CountryOption.UnitedStates,
PostalCode = "53202",
ResidenceType = ResidenceTypeOption.Rent,
MonthsResided = 28,
},
],
Employers =
[
new Employer(employerId: new Guid("e8436884-0e6c-4144-8de5-1f44c7936a23"))
{
StartDate = new DateTime(2015, 01, 01),
Name = "Chocolate Teapot Manfuacturing Inc",
EmploymentType = EmploymentTypeOption.PartTime,
Title = "Lead Teapot Assembler",
WorkPhone = "5556783241",
CurrentEmployer = true,
Language = LanguageOption.English,
Income = new Income
{
IncomeTypeCD = IncomeType.Salary,
SourceName = "Work",
HoursPerWeek = 24,
AmountPerPeriod = 10,
Frequency = Frequency.Hourly,
IncomeStatus = IncomeStatus.IncomeStatus_Included
},
},
new Employer(employerId: new Guid("58d25f44-1260-428d-ac2c-d8804b06ed66"))
{
StartDate = new DateTime(2015, 01, 01),
Name = "Vanilla Teapot Manfuacturing Coop",
EmploymentType = EmploymentTypeOption.PartTime,
Title = "Junior Teapot Assembler",
WorkPhone = "5559572318",
CurrentEmployer = true,
Language = LanguageOption.English,
Income = new Income
{
IncomeTypeCD = IncomeType.Salary,
SourceName = "Work",
HoursPerWeek = 32,
AmountPerPeriod = 50,
Frequency = Frequency.Hourly,
IncomeStatus = IncomeStatus.IncomeStatus_Included
},
},
],
},
};
}
}
@@ -0,0 +1,223 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models.Loan
{
public static class TermLengths
{
//All lengths are in months.
public class UiTermOption
{
[Required]
public int value;
[Required]
public string desc;
}
public static List<UiTermOption> AvailableTermOptions =
[
new UiTermOption {value = 12, desc = "12 months / 1 year"},
new UiTermOption {value = 24, desc = "24 months / 2 years"},
new UiTermOption {value = 36, desc = "36 months / 3 years"},
new UiTermOption {value = 48, desc = "48 months / 4 years"},
new UiTermOption {value = 60, desc = "60 months / 5 years"},
new UiTermOption {value = 63, desc = "63 months"},
new UiTermOption {value = 72, desc = "72 months / 6 years"},
new UiTermOption {value = 75, desc = "75 months"},
new UiTermOption {value = 84, desc = "84 months / 7 years"},
new UiTermOption {value = 96, desc = "96 months / 8 years"},
new UiTermOption {value = 120, desc = "120 months / 10 years"},
new UiTermOption {value = 144, desc = "144 months / 12 years"},
new UiTermOption {value = 180, desc = "180 months / 15 years"},
new UiTermOption {value = 240, desc = "240 months / 20 years"}
];
public static List<UiTermOption> AvailableTermOptionsWithoutYears =
[
new UiTermOption {value = 12, desc = "12 months"},
new UiTermOption {value = 24, desc = "24 months"},
new UiTermOption {value = 36, desc = "36 months"},
new UiTermOption {value = 48, desc = "48 months"},
new UiTermOption {value = 60, desc = "60 months"},
new UiTermOption {value = 63, desc = "63 months"},
new UiTermOption {value = 72, desc = "72 months"},
new UiTermOption {value = 75, desc = "75 months"},
new UiTermOption {value = 84, desc = "84 months"},
new UiTermOption {value = 96, desc = "96 months"},
new UiTermOption {value = 120, desc = "120 months"},
new UiTermOption {value = 144, desc = "144 months"},
new UiTermOption {value = 180, desc = "180 months"},
new UiTermOption {value = 240, desc = "240 months"}
];
public static Dictionary<decimal, List<int>> BoatLoansByAmount = new()
{
//Loan amount can be greater than or equal to the amount listed here.
{0,
new List<int>
{
48
}
},
{10000,
new List<int>
{
48,
63,
72,
84
}
},
{20000,
new List<int>
{
48,
63,
72,
84,
120,
180,
}
},
};
public static Dictionary<decimal, List<int>> MfdHomeAndRvByAmount = new()
{
//Loan amount can be greater than or equal to the amount listed here.
{0,
new List<int>
{
48
}
},
{10000,
new List<int>
{
48,
60,
72,
84
}
},
{20000,
new List<int>
{
48,
60,
72,
84,
120,
180
}
},
};
public static List<int> Special =
[
36
];
public static List<int> HomeEquity =
[
60,
84,
120,
180,
240,
];
public static List<int> MortgageRefiExpress =
[
96,
120,
144,
];
public static List<int> VehiclePreApproval =
[
36,
48,
63,
75,
];
public static Dictionary<int, List<int>> TermsByYear = new()
{
{2023,
new List<int>
{
36,
48,
63,
75,
84
}
},
{2022,
new List<int>
{
36,
48,
63,
75
}
},
{2021,
new List<int>
{
36,
48,
63
}
},
{2020,
new List<int>
{
36,
48
}
}
};
public static Dictionary<decimal, List<int>> PowersportByAmount = new()
{
//Loan amount can be greater than or equal to the amount listed here.
{0,
new List<int>
{
36,
48,
63
}
},
{10000,
new List<int>
{
36,
48,
63,
72
}
}
};
public static Dictionary<decimal, List<int>> SignatureByAmount = new()
{
//Loan amount can be greater than or equal to the amount listed here.
{0,
new List<int>
{
12,
24,
36
}
},
{5000,
new List<int>
{
48
}
},
};
}
}
@@ -0,0 +1,33 @@
using FluentValidation;
namespace Landmark.OLA.Models.Loan.Validators
{
public class BranchConfigValidator : AbstractValidator<BranchConfig>
{
public BranchConfigValidator() {
RuleFor(x => x.BranchCodesByZip).Must((x, branchCodes) => NoZipcodeDuplicates(branchCodes))
.WithMessage("You cannot have duplicate zipcodes.");
}
private static bool NoZipcodeDuplicates(List<BranchData> branchData)
{
var matchFound = false;
foreach (var eachMapping in branchData)
{
int matchCount = 0;
foreach (var otherMapping in branchData)
{
if( otherMapping.Zipcode == eachMapping.Zipcode)
{
matchCount++;
}
}
if( matchCount > 1 )
{
matchFound = true;
}
}
return !matchFound;
}
}
}
@@ -0,0 +1,283 @@
using FluentValidation;
using Landmark.OLA.Models.Assets;
using Landmark.OLA.Models.HomeMortgageDisclosureAct.Validators;
using Landmark.OLA.Models.Individuals.Validators;
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models.Loan.Validators
{
public class LoanApplicationValidator : AbstractValidator<LoanApplication>
{
public LoanApplicationValidator()
{
RuleFor(x => x.PrimaryBorrower).SetValidator(new ApplicantValidator());
//when primary is married and in a state requiring spouse information
When(x =>
(x.PrimaryBorrower.IsMarried ?? false) &&
(x.PrimaryBorrower.Address?.State.HasValue ?? false) &&
ValidationConstants.StatesRequiringSpouseInformation.Contains(x.PrimaryBorrower.Address.State.Value),
() =>
{
//Primary must have spouse data if not married to coapplicant
RuleFor(x => x.PrimaryBorrower.HasAnyNonApplicantSpouse()).Equal(true)
.Unless(x => x.CoApplicant?.IsMarriedToPrimaryBorrower ?? false)
.WithMessage("PrimaryBorrower must include spouse information due to marriage in a community property state.");
});
//when coapp is married and in a state requiring spouse information
When(x =>
(x.CoApplicant?.IsMarried ?? false) &&
(x.CoApplicant?.Address?.State.HasValue ?? false) &&
ValidationConstants.StatesRequiringSpouseInformation.Contains(x.CoApplicant.Address.State.Value),
() =>
{
//coapp must have spouse data if not married to coapplicant
RuleFor(x => x.CoApplicant.HasAnyNonApplicantSpouse()).Equal(true)
.Unless(x => x.CoApplicant?.IsMarriedToPrimaryBorrower ?? false)
.WithMessage("CoApplicant must include spouse information due to marriage in a community property state.");
});
Unless(x => x.CoApplicant is null, () =>
{
RuleFor(x => x.CoApplicant).SetValidator(new ApplicantValidator());
When(x => x.CoApplicant.IsMarriedToPrimaryBorrower, () =>
{
//If the two applicants are married there should be no other spouse information
RuleFor(x => x.PrimaryBorrower.NonApplicantSpouse).Null();
RuleFor(x => x.PrimaryBorrower.DetailedNonApplicantSpouse).Null();
RuleFor(x => x.CoApplicant.NonApplicantSpouse).Null();
RuleFor(x => x.CoApplicant.DetailedNonApplicantSpouse).Null();
});
});
When(x => (x.PrimaryBorrower?.IsMarried ?? false) == false, () =>
{
RuleFor(x => x.PrimaryBorrower.HasAnyNonApplicantSpouse()).Equal(false)
.WithMessage("PrimaryBorrower is set as not married but includes spouse information.");
});
When(x => IsHmdaLoan(x.LoanType), () =>
{
RuleFor(x => x.PrimaryBorrower.HmdaValues).SetValidator(new HmdaValidator());
RuleFor(x => x.CoApplicant.HmdaValues).SetValidator(new HmdaValidator())
.Unless(x => x.CoApplicant is null);
});
When(x => IsVehicleRequired(x.LoanType), () =>
{
RuleFor(x => x.Vehicle).NotNull();
RuleFor(x => x).SetValidator(new VehicleValidator());
});
When(x => IsRealEstateRequired(x.LoanType), () =>
{
RuleFor(x => x.RealEstate).NotNull();
});
When(x => IsTermRequired(x.LoanType), () =>
{
RuleFor(x => x.TermLengthInMonths).NotNull();
RuleFor(x => x.TermLengthInMonths).Must((x, termLength) => IsValidTermLength(x) == true)
.WithMessage("TermLengthInMonths is not set to a valid value for this loan type.");
});
RuleFor(x => x.Purpose).Must((x, purpose) => LoanPurpose.IsValidPurposeForType(x.LoanType, purpose));
Unless(x => x.LoanType.Value == LoanType.CC_CashBack || x.LoanType.Value == LoanType.CC_Rewards || x.LoanType.Value == LoanType.CC_Secure
|| x.LoanType.Value == LoanType.CC_Student, () => {
RuleFor(x => x.RequestedAmount).Must((x, requestedAmount) => LoanLimits.IsValidAmountForType(x.LoanType.Value, requestedAmount.Value))
.WithMessage("The requested loan amount is outside the limits for this product type.");
});
//They actually can, but for now that means something went wrong on the frontend.
//To allow vehicle details on preapprovals, simply remove this validation and everything should likely work.
RuleFor(x => x.Vehicle).Null().When(x => x.LoanType == LoanType.V_VehiclePreapproval)
.WithMessage("Vehicle PreApproval loans cannot include vehicle details");
When(x => IsClientMessageRequired(x.LoanType), () =>
{
RuleFor(x => x.ClientMessage).NotNull();
RuleFor(x => x.ClientMessage.Trim()).Must((x, clientMessage) => clientMessage.Length >= 5)
.WithMessage("Applications for this type of loan must include a client message");
});
}
private bool IsValidTermLength(LoanApplication loanApplication)
{
if ((!loanApplication.LoanType.HasValue) || (!loanApplication.TermLengthInMonths.HasValue))
return false;
List<int> validTermLengths = this.GetValidTermLengthsForLoanType(loanApplication);
return validTermLengths.Contains(loanApplication.TermLengthInMonths.Value);
}
private List<int> GetValidTermLengthsForLoanType(LoanApplication loanApplication)
{
return loanApplication.LoanType.Value switch
{
LoanType.EQ_Mtg_Refi_Express => TermLengths.MortgageRefiExpress,
LoanType.EQ_Home_Equity_Fixed => TermLengths.HomeEquity,
LoanType.V_VehiclePreapproval => TermLengths.VehiclePreApproval,
LoanType.V_Vehicle => GetVehicleTermList(loanApplication.Vehicle),
LoanType.EQ_Mfg_Home_With_or_Without_Land or LoanType.T_TrailerMotorhome => GetMfdHomeAndRvTermList(loanApplication.RequestedAmount.Value),
LoanType.M_Boat => GetBoatTermList(loanApplication.RequestedAmount.Value),
LoanType.P_Powersport => GetPowersportTermList(loanApplication.Vehicle.InitialEstimatedValue.Value),
LoanType.S_Signature => GetSignatureTermList(loanApplication.RequestedAmount.Value),
LoanType.S_Special => GetSpecialTermList(loanApplication.RequestedAmount.Value),
LoanType.CC_CashBack => [],
LoanType.CC_Rewards => [],
LoanType.CC_Student => [],
LoanType.CC_Secure => [],
LoanType.EQ_HELOC_Flex => [],
_ => [],
};
}
private static List<int> GetSignatureTermList(decimal loanAmount)
{
var applicableKeys = TermLengths.SignatureByAmount.Keys.Where(x => loanAmount >= x);
var allowedTerms = new List<int>();
foreach (var key in applicableKeys)
{
allowedTerms.AddRange(TermLengths.SignatureByAmount[key]);
}
return allowedTerms;
}
private static List<int> GetSpecialTermList(decimal loanAmount)
{
var allowedTerms = new List<int>
{
36
};
return allowedTerms;
}
private static List<int> GetMfdHomeAndRvTermList(decimal loanAmount)
{
var availableTermLengths = new List<int>();
foreach (var amount in TermLengths.MfdHomeAndRvByAmount.Keys)
{
if (loanAmount >= amount)
{
availableTermLengths.AddRange(TermLengths.MfdHomeAndRvByAmount[amount]);
}
}
return availableTermLengths;
}
public static List<int> GetVehicleTermList(Vehicle vehicle)
{
if ((vehicle is null) || (!vehicle.Condition.HasValue) || (!vehicle.Year.HasValue))
return [];
var yearList = TermLengths.TermsByYear;
int lookupYear = vehicle.Year.Value;
if (yearList.TryGetValue(lookupYear, out List<int> value))
{
return value;
}
else if (lookupYear >= yearList.First().Key)
{
return yearList.First().Value;
}
else
{
return yearList.Last().Value;
}
}
private static List<int> GetBoatTermList(decimal loanAmount)
{
var applicableKeys = TermLengths.BoatLoansByAmount.Keys.Where(x => loanAmount >= x);
var allowedTerms = new List<int>();
foreach (var key in applicableKeys)
{
allowedTerms.AddRange(TermLengths.BoatLoansByAmount[key]);
}
return allowedTerms;
}
private static List<int> GetPowersportTermList(decimal loanAmount)
{
var applicableKeys = TermLengths.PowersportByAmount.Keys.Where(x => loanAmount >= x);
var allowedTerms = new List<int>();
foreach (var key in applicableKeys)
{
allowedTerms.AddRange(TermLengths.PowersportByAmount[key]);
}
return allowedTerms;
}
public static bool IsTermRequired(LoanType? loanType)
{
if (!loanType.HasValue)
{
return false;
}
return !ValidationConstants.LoansNotRequiringTerm.Contains(loanType.Value);
}
private static bool IsVehicleRequired(LoanType? loanType)
{
if (!loanType.HasValue)
{
return false;
}
return ValidationConstants.LoansRequiringVehicle.Contains(loanType.Value);
}
private static bool IsRealEstateRequired(LoanType? loanType)
{
if (!loanType.HasValue)
{
return false;
}
return ValidationConstants.LoansRequiringRealEstate.Contains(loanType.Value);
}
private static bool IsHmdaLoan(LoanType? loanType)
{
if (!loanType.HasValue)
{
return false;
}
return ValidationConstants.HmdaLoans.Contains(loanType.Value);
}
private static bool IsClientMessageRequired(LoanType? loanType)
{
if (!loanType.HasValue)
{
return false;
}
return loanType.Value == LoanType.S_Special;
}
}
}
@@ -0,0 +1,80 @@
using FluentValidation;
using Landmark.OLA.Models.Assets.Enums;
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models.Loan.Validators
{
class VehicleValidator : AbstractValidator<LoanApplication>
{
private readonly Dictionary<VehicleType, List<LoanType>> _VehiclesToLoans = new()
{
{ VehicleType.Automobile,
new List<LoanType>
{
LoanType.V_Vehicle,
LoanType.V_VehiclePreapproval,
}
},
{ VehicleType.Motorcycle,
new List<LoanType>
{
LoanType.V_Vehicle,
LoanType.V_VehiclePreapproval,
}
},
{ VehicleType.Boat,
new List<LoanType>
{
LoanType.M_Boat,
}
},
{ VehicleType.ATV,
new List<LoanType>
{
LoanType.P_Powersport,
}
},
{ VehicleType.PWC,
new List<LoanType>
{
LoanType.P_Powersport,
}
},
{ VehicleType.RecreationalVehicle,
new List<LoanType>
{
LoanType.P_Powersport,
}
},
{ VehicleType.Snowmobile,
new List<LoanType>
{
LoanType.P_Powersport,
}
},
{ VehicleType.TrailerMotorHome,
new List<LoanType>
{
LoanType.T_TrailerMotorhome,
}
},
};
public VehicleValidator()
{
RuleFor(x => x.Vehicle.Type).Must((x, vehicleType) => IsAllowedVehicleTypeForLoan(x.LoanType.Value, vehicleType.Value))
.WithMessage($"Vehicle type is not allowed for this loan type.");
}
private bool IsAllowedVehicleTypeForLoan(LoanType loanType, VehicleType vehicleType)
{
if (_VehiclesToLoans.TryGetValue(vehicleType, out var loanTypeList))
{
return loanTypeList.Contains(loanType);
}
return false;
}
}
}
@@ -0,0 +1,37 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models
{
public static class ModelValidator
{
public static bool TryValidate(Object data)
{
try
{
Validate(data);
return true;
}
catch(Exception)
{
return false;
}
}
//TODO #231: This will not validate properties of the object passed in - a valid Individual with an invalid Address will be deemed valid.
//Controllers do validate all properties.
public static void Validate(Object data)
{
//https://stackoverflow.com/a/3783328/266594
var validationResults = new List<ValidationResult>(); //Can use this to inform user of exact problems
var vc = new ValidationContext(data, null, null);
var isValid = Validator.TryValidateObject(data, vc, validationResults, true);
if (!isValid)
{
string validationProblems = String.Join('\n', validationResults);
throw new ArgumentException($"{data.GetType()} data is invalid: \n {validationProblems}");
}
}
}
}
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models
{
public class OlaModel
{
//This will either hold an ToString'ed int or a 36-char long GUID
[StringLength(36)]
public string Id { get; set; }
public Guid GetOrCreateGuid()
{
if (Guid.TryParse(Id, out Guid parsedGuid))
{
return parsedGuid;
}
else
{
var newGuid = Guid.NewGuid();
Id = newGuid.ToString();
return newGuid;
}
}
}
}
@@ -0,0 +1,33 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class AuthTokenResponse
{
public AuthTokenStatus Status { get; set; }
public AuthToken Token { get; set; }
}
public class AuthTokenStatus
{
public string StatusMessage { get; set; }
public AuthTokenError ErrorInfo { get; set; }
}
public class AuthTokenError
{
public string ErrorType { get; set; }
public string ErrorCode { get; set; }
public string ErrorMessage { get; set; }
}
public class AuthToken
{
public string DiFiid { get; set; }
public string DiFicustomer { get; set; }
public string DiMemberNumber { get; set; }
public string AccessToken { get; set; }
public string ExpiresIn { get; set; }
public string RefreshToken { get; set; }
public string RefreshTokenExpiresIn { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class AuthenticationStatement
{
public string SessionIndex { get; set; }
public string AuthContextClassRef { get; set; }
}
}
@@ -0,0 +1,17 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class Constants
{
public static readonly string TokenPayload = "TokenPayload";
public static readonly string TokenAuthorization = "TokenAuthorization";
public static readonly string TokenAuthorizationString = "Member";
public static readonly string TokenNonAuthorizationString = "None";
public static readonly string MemberID = "MemberID";
public static readonly string IndividualID = "IndividualID";
public static readonly string FI_ID = "GroupID";
public static readonly string IsTwoFactor = "IsTwoFactor";
public static readonly string InProgressLoanId = "InProgressLoanId";
public static readonly string TokenPrefix = "Bearer "; // space is intentional
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class DISAMLAssertion
{
public string NameID { get; set; }
public string Recipient { get; set; }
public SAMLConditions Conditions { get; set; }
public AuthenticationStatement AuthnStatement { get; set; }
public string MemberID { get; set; }
public string FI_ID { get; set; }
}
}
@@ -0,0 +1,13 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class OnlineBankingFormData
{
public string Username { get; set; }
public string Password { get; set; }
public string DI_TID { get; set; }
public string DI_FIID { get; set; }
public string GrantType = "password";
public string Authorization { get; set; }
}
}
@@ -0,0 +1,22 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class OnlineBankingToken
{
public string MemberID { get; set; }
public string IndividualID { get; set; }
public string FI_ID { get; set; }
public string LoanId { get; set; }
public bool IsTwoFactor { get; set; }
public OnlineBankingToken(DISAMLAssertion assertion, string loanId, bool isTwoFactor)
{
this.MemberID = assertion?.MemberID;
this.FI_ID = assertion.FI_ID;
this.LoanId = loanId;
this.IsTwoFactor = isTwoFactor;
}
public OnlineBankingToken() { }
}
}
@@ -0,0 +1,10 @@
namespace Landmark.OLA.Models.OnlineBanking
{
public class SAMLConditions
{
public DateTime NotBefore { get; set; }
public DateTime NotAfter { get; set; }
public string Audience { get; set; }
}
}
@@ -0,0 +1,23 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models.Residences
{
public class CurrentResidenceAddress : ResidenceAddress, ICurrentResidenceAddress
{
[Required]
//Slightly arbitrary values that should be safe
[Range(0, 1000000000)]
//Nullable so that there is no default value
public decimal? MonthlyPayment { get; set; }
public CurrentResidenceAddress()
: base()
{
}
public CurrentResidenceAddress(Guid id)
: base(id)
{
}
}
}
@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace Landmark.OLA.Models.Residences
{
public interface ICurrentResidenceAddress : IResidenceAddress
{
//Ten Billion is an arbitrary limit but I'm pretty confidant that it will be an acceptable upper limit for a long time.
[Range(minimum: 0, maximum: 10000000000)]
decimal? MonthlyPayment { get; set; }
}
}
@@ -0,0 +1,12 @@
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Addresses.Enums;
namespace Landmark.OLA.Models.Residences
{
public interface IResidenceAddress : IAddress
{
int? MonthsResided { get; set; }
ResidenceTypeOption? ResidenceType { get; set; }
}
}
@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Addresses.Enums;
namespace Landmark.OLA.Models.Residences
{
public class ResidenceAddress : Address, IResidenceAddress
{
[Required]
//TODO #227: Should MonthsResided be at least 1? What if you just moved yesterday?
//The current front end offers an upper limit of 10 years, 11 months.
[Range(0, 130)]
//Nullable so that there is no default value. Could change if a value of at least 1 is required
public int? MonthsResided { get; set; }
[Required]
//Nullable so that there is no default value
public ResidenceTypeOption? ResidenceType { get; set; }
public ResidenceAddress()
: base()
{
}
public ResidenceAddress(Guid id)
: base(id)
{
}
}
}
@@ -0,0 +1,43 @@
using Landmark.OLA.Models.Addresses.Enums;
using Landmark.OLA.Models.Loan.Enums;
namespace Landmark.OLA.Models
{
public static class ValidationConstants
{
public static readonly List<StateOption> StatesRequiringSpouseInformation = new List<StateOption>
{
//This is not an exhaustive list of community property states
StateOption.WI,
};
public static readonly List<LoanType> HmdaLoans = new List<LoanType>
{
LoanType.EQ_HELOC_Flex,
LoanType.EQ_Home_Equity_Fixed,
LoanType.EQ_Mtg_Refi_Express,
LoanType.EQ_Mfg_Home_With_or_Without_Land,
};
public static readonly List<LoanType> LoansNotRequiringTerm = new List<LoanType>
{
LoanType.CC_CashBack,
LoanType.CC_Rewards,
LoanType.CC_Student,
LoanType.CC_Secure,
LoanType.EQ_HELOC_Flex,
};
public static readonly List<LoanType> LoansRequiringVehicle = new List<LoanType>
{
LoanType.V_Vehicle,
};
public static readonly List<LoanType> LoansRequiringRealEstate = new List<LoanType>
{
LoanType.EQ_HELOC_Flex,
LoanType.EQ_Home_Equity_Fixed,
LoanType.EQ_Mtg_Refi_Express,
};
}
}
@@ -0,0 +1,9 @@
using Landmark.OLA.Models.Addresses;
namespace Landmark.OLA.Models.Vendor
{
public interface IUspsVendorClient
{
Task<Address> CompareAddress(Address address);
}
}