first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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; }
|
||||
}
|
||||
}
|
||||
+26
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -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.");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user