diff --git a/crates/immo-core/src/loan_offer.rs b/crates/immo-core/src/loan_offer.rs index 5e5345d..d5847c0 100644 --- a/crates/immo-core/src/loan_offer.rs +++ b/crates/immo-core/src/loan_offer.rs @@ -18,6 +18,8 @@ pub enum LoanOfferError { MaxAnnualNominalRateExceeded(Decimal), #[error("loan duration in months should be lower than {MAX_LOAN_DURATION_IN_MONTHS}: {0}")] MaxLoanDurationExceeded(u64), + #[error("loan cannot amortise")] + LoanCannotAmortise, } /// 1b€ is a pretty expensive house... @@ -59,11 +61,21 @@ impl LoanOffer { } else if borrowed_capital > MAX_BORROWED_CAPITAL { Err(LoanOfferError::MaxBorrowedCapitalExceeded(borrowed_capital)) } else { - Ok(LoanOffer { + let loan_offer = LoanOffer { borrowed_capital, annual_nominal_rate, loan_duration_in_months, - }) + }; + // Check that the first month is not "only interests", otherwise the loan cannot be reimbursed + // /!\ Must be kept after all the previous verifications otherwise monthly_repayment() can panic + if loan_offer.monthly_repayment().as_decimal() + > (borrowed_capital.as_decimal() * loan_offer.proportional_monthly_interest_rate()) + .round_dp(2) + { + Ok(loan_offer) + } else { + Err(LoanOfferError::LoanCannotAmortise) + } } } @@ -98,7 +110,7 @@ mod tests { #[test] fn creating_loan_offer_with_negative_interest_rate_returns_negative_interest_rate_error() { - let ret = LoanOffer::new(Euros::from_cents_as_i64(123), Decimal::new(-45, 2), 5); + let ret = LoanOffer::new(Euros::from_cents_as_i64(123), Decimal::new(-45, 2), 60); assert_eq!( ret, Err(LoanOfferError::NegativeInterestRate(Decimal::new(-45, 2))) @@ -177,6 +189,16 @@ mod tests { ); } + #[test] + fn creating_loan_offer_that_cannot_amortise_returns_loan_cannot_amortise_error() { + let ret = LoanOffer::new( + Euros::from_cents_as_i64(439_000_00), + Decimal::new(99, 2), + 480, + ); + assert_eq!(ret, Err(LoanOfferError::LoanCannotAmortise)); + } + #[test] fn creating_loan_offer_with_correct_inputs_works() { let ret = LoanOffer::new(Euros::from_cents_as_i64(10), Decimal::new(45, 2), 15); @@ -191,12 +213,8 @@ mod tests { } #[test] - fn creating_loan_offer_with_limit_inputs_works() { - let ret = LoanOffer::new( - MAX_BORROWED_CAPITAL, - MAX_ANNUAL_NOMINAL_RATE, - MAX_LOAN_DURATION_IN_MONTHS, - ); + fn creating_loan_offer_with_max_repayment_works() { + let ret = LoanOffer::new(MAX_BORROWED_CAPITAL, MAX_ANNUAL_NOMINAL_RATE, 1); assert!(ret.is_ok()); // Should not panic - checks that expect assertion is valid ret.unwrap().monthly_repayment(); @@ -219,11 +237,11 @@ mod tests { fn conversion_to_annual_nominal_rate_in_percent_works() { let offer = LoanOffer::new( Euros::from_cents_as_i64(150_000_00), - Decimal::new(4, 4), + Decimal::new(4, 2), 300, ) .unwrap(); - assert_eq!(offer.annual_nominal_rate_in_percent(), Decimal::new(4, 2)); + assert_eq!(offer.annual_nominal_rate_in_percent(), Decimal::new(400, 2)); } #[test]