Skip to content

Commit

Permalink
better fee settlement refund naming
Browse files Browse the repository at this point in the history
  • Loading branch information
simontreanor committed Apr 9, 2024
1 parent ec0328d commit 5507e07
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 158 deletions.
31 changes: 18 additions & 13 deletions src/Amortisation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ module Amortisation =
/// the settlement figure as of the current day
SettlementFigure: int64<Cent>
/// the pro-rated fees as of the current day
ProRatedFees: int64<Cent>
FeesDue: int64<Cent>
}
with
static member Default = {
Expand All @@ -97,7 +97,7 @@ module Amortisation =
InterestBalance = 0m<Cent>
ChargesBalance = 0L<Cent>
SettlementFigure = 0L<Cent>
ProRatedFees = 0L<Cent>
FeesDue = 0L<Cent>
}

[<Struct>]
Expand All @@ -108,6 +108,8 @@ module Amortisation =
CumulativeActualPayments: int64<Cent>
/// the total of generated payments made up to and including the current day
CumulativeGeneratedPayments: int64<Cent>
/// the total of fees paid up to and including the current day
CumulativeFees: int64<Cent>
/// the total of interest accrued up to and including the current day
CumulativeInterest: decimal<Cent>
/// the first missed payment
Expand Down Expand Up @@ -293,24 +295,26 @@ module Amortisation =
else
decimal feesTotal * (decimal originalFinalPaymentDay - decimal ap.AppliedPaymentDay) / decimal originalFinalPaymentDay |> Cent.round (ValueSome RoundUp)

let proRatedFees =
let feesDue =
match sp.FeesAndCharges.FeesSettlement with
| Fees.Settlement.ProRataRefund ->
| Fees.SettlementRefund.None ->
si.FeesBalance - feesPortion
| Fees.SettlementRefund.ProRata ->
match sp.ScheduleType with
| ScheduleType.Original ->
let originalFinalPaymentDay = sp.PaymentSchedule |> paymentDays sp.StartDate |> Array.vTryLastBut 0 |> ValueOption.defaultValue 0<OffsetDay>
calculateFees originalFinalPaymentDay
| ScheduleType.Rescheduled originalFinalPaymentDay ->
calculateFees originalFinalPaymentDay
| _ ->
si.FeesBalance - feesPortion
| Fees.SettlementRefund.Balance ->
a.CumulativeFees

let feesRefund =
match sp.FeesAndCharges.FeesSettlement with
| Fees.Settlement.DueInFull ->
| Fees.SettlementRefund.None ->
0L<Cent>
| Fees.Settlement.ProRataRefund ->
Cent.max 0L<Cent> proRatedFees
| _ ->
Cent.max 0L<Cent> feesDue

let generatedSettlementPayment = si.PrincipalBalance + si.FeesBalance - feesRefund + roundedInterestPortion + chargesPortion

Expand Down Expand Up @@ -383,7 +387,7 @@ module Amortisation =
InterestBalance = 0m<Cent>
ChargesBalance = 0L<Cent>
SettlementFigure = generatedSettlementPayment'
ProRatedFees = proRatedFees
FeesDue = feesDue
}, generatedSettlementPayment'

let scheduleItem, generatedPayment =
Expand Down Expand Up @@ -446,20 +450,21 @@ module Amortisation =
InterestBalance = if abs interestBalance < 1m<Cent> then Cent.toDecimalCent roundedInterestBalance else interestBalance
ChargesBalance = si.ChargesBalance + newChargesTotal - chargesPortion + carriedCharges
SettlementFigure = if paymentStatus = NoLongerRequired then 0L<Cent> else generatedSettlementPayment'
ProRatedFees = if paymentStatus = NoLongerRequired then 0L<Cent> else proRatedFees
FeesDue = if paymentStatus = NoLongerRequired then 0L<Cent> else feesDue
}, 0L<Cent>

let accumulator'' =
{ accumulator' with
CumulativeGeneratedPayments = a.CumulativeGeneratedPayments + generatedPayment
CumulativeFees = a.CumulativeFees + feesPortion'
CumulativeInterest = accumulator'.CumulativeInterest - interestRoundingDifference
}

scheduleItem, accumulator''

) (
{ ScheduleItem.Default with OffsetDate = sp.StartDate; Advances = [| sp.Principal |]; PrincipalBalance = sp.Principal; FeesBalance = feesTotal; SettlementFigure = sp.Principal + feesTotal; ProRatedFees = feesTotal },
{ CumulativeScheduledPayments = 0L<Cent>; CumulativeActualPayments = 0L<Cent>; CumulativeGeneratedPayments = 0L<Cent>; CumulativeInterest = 0m<Cent>; FirstMissedPayment = ValueNone; CumulativeMissedPayments = 0L<Cent> }
{ ScheduleItem.Default with OffsetDate = sp.StartDate; Advances = [| sp.Principal |]; PrincipalBalance = sp.Principal; FeesBalance = feesTotal; SettlementFigure = sp.Principal + feesTotal; FeesDue = feesTotal },
{ CumulativeScheduledPayments = 0L<Cent>; CumulativeActualPayments = 0L<Cent>; CumulativeGeneratedPayments = 0L<Cent>; CumulativeFees = 0L<Cent>; CumulativeInterest = 0m<Cent>; FirstMissedPayment = ValueNone; CumulativeMissedPayments = 0L<Cent> }
)
|> fun a -> if (a |> Array.filter(fun (si, _) -> si.OffsetDay = 0<OffsetDay>) |> Array.length = 2) then a |> Array.tail else a
|> Array.unzip
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Finance.Personal.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>FSharp.Finance.Personal</PackageId>
<Version>0.14.0</Version>
<Version>0.14.1</Version>
<Authors>Simon Treanor</Authors>
<PackageDescription>F# Personal Finance Library</PackageDescription>
<RepositoryUrl>https://github.com/simontreanor/FSharp.Finance.Personal</RepositoryUrl>
Expand Down
16 changes: 9 additions & 7 deletions src/FeesAndCharges.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ module FeesAndCharges =
)

/// how the fees are treated in the event of an early settlement
[<Struct>]
type Settlement =
/// the initial fees are due in full with no discount or refund
| DueInFull
/// the fees are refunded proportionately to the number of days elapsed in the current schedule
| ProRataRefund
[<RequireQualifiedAccess; Struct>]
type SettlementRefund =
/// fees are due in full with no discount or refund
| None
/// fees are refunded proportionately to the number of days elapsed in the current schedule
| ProRata
/// the current fee balance is refunded
| Balance

/// how to handle fees when rescheduling or rolling over
[<Struct>]
Expand Down Expand Up @@ -94,7 +96,7 @@ module FeesAndCharges =
/// a list of product fees applicable to a product
Fees: Fee array
/// how fees are treated when a product is repaid early
FeesSettlement: Fees.Settlement
FeesSettlement: Fees.SettlementRefund
/// a list of penalty charges applicable to a product
Charges: Charge array
/// any period during which charges are not payable
Expand Down
4 changes: 2 additions & 2 deletions src/Formatting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ module Formatting =
open Calculation
open Currency

let feesProperties hide = if hide then [| "FeesPortion"; "FeesRefund"; "FeesBalance"; "ProRatedFees" |] else [||]
let feesProperties hide = if hide then [| "FeesPortion"; "FeesRefund"; "FeesBalance"; "FeesDue" |] else [||]
let chargesProperties hide = if hide then [| "NewCharges"; "ChargesPortion"; "ChargesBalance" |] else [||]
let quoteProperties hide = if hide then [| "GeneratedPayment" |] else [||]
let extraProperties hide = if hide then [| "ProRatedFees"; "SettlementFigure" |] else [||]
let extraProperties hide = if hide then [| "FeesDue"; "SettlementFigure" |] else [||]

let filterColumns hideProperties =
match hideProperties with
Expand Down
8 changes: 4 additions & 4 deletions src/Quotes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,19 @@ module Quotes =
else
let principalPortion, feesPortion, interestPortion, chargesPortion, proRatedFees =
if si.GeneratedPayment.Value = 0L<Cent> then
0L<Cent>, 0L<Cent>, 0L<Cent>, 0L<Cent>, si.ProRatedFees
0L<Cent>, 0L<Cent>, 0L<Cent>, 0L<Cent>, si.FeesDue
else
if confirmedPayments <> 0L<Cent> then
if si.GeneratedPayment.Value > 0L<Cent> then
let chargesPortion = Cent.min si.ChargesPortion confirmedPayments
let interestPortion = Cent.min si.InterestPortion (Cent.max 0L<Cent> (confirmedPayments - chargesPortion))
let feesPortion = Cent.min si.FeesPortion (Cent.max 0L<Cent> (confirmedPayments - chargesPortion - interestPortion))
let principalPortion = Cent.max 0L<Cent> (confirmedPayments - feesPortion - chargesPortion - interestPortion)
si.PrincipalPortion - principalPortion, si.FeesPortion - feesPortion, si.InterestPortion - interestPortion, si.ChargesPortion - chargesPortion, si.ProRatedFees
si.PrincipalPortion - principalPortion, si.FeesPortion - feesPortion, si.InterestPortion - interestPortion, si.ChargesPortion - chargesPortion, si.FeesDue
else
si.GeneratedPayment.Value, 0L<Cent>, 0L<Cent>, 0L<Cent>, si.ProRatedFees
si.GeneratedPayment.Value, 0L<Cent>, 0L<Cent>, 0L<Cent>, si.FeesDue
else
si.PrincipalPortion, si.FeesPortion, si.InterestPortion, si.ChargesPortion, si.ProRatedFees
si.PrincipalPortion, si.FeesPortion, si.InterestPortion, si.ChargesPortion, si.FeesDue
PaymentQuote (si.GeneratedPayment.Value, principalPortion, feesPortion, interestPortion, chargesPortion, proRatedFees)
return {
QuoteType = quoteType
Expand Down
16 changes: 8 additions & 8 deletions src/Rescheduling.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Rescheduling =
/// the final payment day of the original schedule
OriginalFinalPaymentDay: int<OffsetDay>
/// whether the fees should be pro-rated or due in full in the new schedule
FeesSettlement: Fees.Settlement
FeesSettlement: Fees.SettlementRefund
/// whether the plan is autogenerated or a manual plan provided
PaymentSchedule: CustomerPaymentSchedule
/// how to handle interest on negative principal balances
Expand Down Expand Up @@ -100,13 +100,13 @@ module Rescheduling =
// get the settlement quote
let! quote = getQuote QuoteType.Settlement sp actualPayments
// process the quote and extract the portions if applicable
let! principalPortion, feesPortion, proRatedFees =
let! principalPortion, feesPortion, feesDue =
match quote.QuoteResult with
| PaymentQuote (paymentQuote, ofWhichPrincipal, ofWhichFees, ofWhichInterest, ofWhichCharges, proRatedFees) ->
| PaymentQuote (paymentQuote, ofWhichPrincipal, ofWhichFees, ofWhichInterest, ofWhichCharges, feesDue) ->
match rp.FeeHandling with
| Fees.FeeHandling.CapitaliseAsPrincipal -> ofWhichPrincipal + ofWhichFees + ofWhichInterest + ofWhichCharges, 0L<Cent>, proRatedFees
| Fees.FeeHandling.CarryOverAsIs -> ofWhichPrincipal + ofWhichInterest + ofWhichCharges, ofWhichFees, proRatedFees
| Fees.FeeHandling.WriteOffFeeBalance -> ofWhichPrincipal + ofWhichInterest + ofWhichCharges, ofWhichFees, proRatedFees
| Fees.FeeHandling.CapitaliseAsPrincipal -> ofWhichPrincipal + ofWhichFees + ofWhichInterest + ofWhichCharges, 0L<Cent>, feesDue
| Fees.FeeHandling.CarryOverAsIs -> ofWhichPrincipal + ofWhichInterest + ofWhichCharges, ofWhichFees, feesDue
| Fees.FeeHandling.WriteOffFeeBalance -> ofWhichPrincipal + ofWhichInterest + ofWhichCharges, ofWhichFees, feesDue
|> ValueSome
| AwaitPaymentConfirmation -> ValueNone
| UnableToGenerateQuote -> ValueNone
Expand All @@ -122,10 +122,10 @@ module Rescheduling =
| Fees.FeeHandling.CarryOverAsIs ->
{ sp.FeesAndCharges with
Fees = [| Fee.CustomFee ("Rolled-Over Fee", Amount.Simple feesPortion) |]
FeesSettlement = if proRatedFees = 0L<Cent> then Fees.Settlement.DueInFull else Fees.Settlement.ProRataRefund
FeesSettlement = if feesDue = 0L<Cent> then Fees.SettlementRefund.None else Fees.SettlementRefund.ProRata
}
| _ ->
{ sp.FeesAndCharges with Fees = [||]; FeesSettlement = Fees.Settlement.DueInFull }
{ sp.FeesAndCharges with Fees = [||]; FeesSettlement = Fees.SettlementRefund.None }
Interest = rp.Interest |> ValueOption.defaultValue sp.Interest
Calculation = rp.Calculation |> ValueOption.defaultValue sp.Calculation
}
Expand Down
Loading

0 comments on commit 5507e07

Please sign in to comment.