From 150734bb207fa99c4c74e8e07d79355abf5c7926 Mon Sep 17 00:00:00 2001 From: Guillermo Ramos Date: Sat, 1 Mar 2025 11:14:38 +0100 Subject: Additional data for initial payment (VAT + agency fee) --- front/src/Main.elm | 175 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 144 insertions(+), 31 deletions(-) diff --git a/front/src/Main.elm b/front/src/Main.elm index c6f63f6..1fd3839 100644 --- a/front/src/Main.elm +++ b/front/src/Main.elm @@ -11,6 +11,7 @@ import Html , div , hr , input + , pre , span , table , tbody @@ -48,6 +49,16 @@ import Url.Parser.Query as UQ +-- CONSTANTS + + +{-| TODO decide by locale +-} +amountSep = + { thousands = '.', decimal = ',' } + + + -- MAIN @@ -94,6 +105,23 @@ capitalDecoder = (field "interest" float) +capitalSumView : Capital -> Html Msg +capitalSumView { principal, interest } = + let + partsTitle = + String.concat + [ "Principal: " + , amountToString principal + , "\nInterest: " + , amountToString interest + , " (" + , Round.round 2 (100 * interest / (principal + interest)) + , "% from total)" + ] + in + span [ class "underline", title partsTitle ] [ amountView (principal + interest) ] + + type alias MortgageSim = { history : List Quota , topay : Capital @@ -118,6 +146,8 @@ type alias RawSpecs = , rate : String , i1 : String , years : String + , vat : String + , fee : String } @@ -129,22 +159,26 @@ defaultRawSpecs = , rate = "80" , i1 = "1.621" , years = "30" + , vat = "6" + , fee = "3" } rawSpecsParser : UQ.Parser RawSpecs rawSpecsParser = - UQ.map6 RawSpecs + UQ.map8 RawSpecs (UQ.map (Maybe.withDefault defaultRawSpecs.title) <| UQ.string "title") (UQ.map (Maybe.withDefault defaultRawSpecs.total) <| UQ.string "total") (UQ.map (Maybe.withDefault defaultRawSpecs.initial) <| UQ.string "initial") (UQ.map (Maybe.withDefault defaultRawSpecs.rate) <| UQ.string "rate") (UQ.map (Maybe.withDefault defaultRawSpecs.i1) <| UQ.string "i1") (UQ.map (Maybe.withDefault defaultRawSpecs.years) <| UQ.string "years") + (UQ.map (Maybe.withDefault defaultRawSpecs.vat) <| UQ.string "vat") + (UQ.map (Maybe.withDefault defaultRawSpecs.fee) <| UQ.string "fee") rawSpecsToURL : RawSpecs -> String -rawSpecsToURL { title, total, initial, rate, i1, years } = +rawSpecsToURL { title, total, initial, rate, i1, years, vat, fee } = UB.toQuery <| [ UB.string "title" title , UB.string "total" total @@ -152,6 +186,8 @@ rawSpecsToURL { title, total, initial, rate, i1, years } = , UB.string "rate" rate , UB.string "i1" i1 , UB.string "years" years + , UB.string "vat" vat + , UB.string "fee" fee ] @@ -297,6 +333,8 @@ type SpecField | Initial | I1 | Years + | VAT + | Fee type Msg @@ -382,6 +420,12 @@ update msg m = Years -> { rawSpecs | years = val } + + VAT -> + { rawSpecs | vat = val } + + Fee -> + { rawSpecs | fee = val } in ( { m | rawSpecs = newRawSpecs }, Cmd.none ) @@ -432,10 +476,48 @@ slider attributes onInputMsg valueTxt = -- VIEW +amountToString : Float -> String +amountToString amount = + let + amountStr = + Round.round 2 amount + + insertThousandsSep str = + let + l = + List.reverse <| String.toList str + + indexed = + List.map2 Tuple.pair (List.range 0 (String.length str)) l + + withCommas = + List.concatMap + (\( i, c ) -> + if i > 0 && modBy 3 i == 0 then + [ amountSep.thousands, c ] + + else + [ c ] + ) + indexed + in + String.fromList <| List.reverse withCommas + in + case String.split "." amountStr of + int :: float :: [] -> + String.join (String.fromChar amountSep.decimal) + [ insertThousandsSep int + , float + ] + + _ -> + amountStr + + specsView : RawSpecs -> Html Msg specsView rawSpecs = let - { title, total, rate, initial, i1, years } = + { title, total, rate, initial, i1, years, vat, fee } = rawSpecs simButAttrs = @@ -466,7 +548,7 @@ specsView rawSpecs = total ] , div [ class "flex my-1" ] - [ div [ class "" ] + [ div [] [ text "Initial contribution: " , txtInput [ class "w-[100px]", Html.Attributes.min "0", Html.Attributes.max total ] (UpdateSpecs Initial) @@ -487,14 +569,30 @@ specsView rawSpecs = i1 , text " % (nominal)" ] - , div [ class "flex my-1 mb-2" ] + , div [ class "flex my-1" ] [ text "Years: " , slider [ Html.Attributes.min "1", Html.Attributes.max "40", step "1" ] (UpdateSpecs Years) years , text years ] - , button (butAttrs ++ simButAttrs) [ text "Simulate" ] + , div [ class "flex my-1" ] + [ div [] + [ text "VAT: " + , txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "50" ] + (UpdateSpecs VAT) + vat + , text "%" + ] + , div [ class "ml-6" ] + [ text "Agent fee: " + , txtInput [ class "w-[55px] mx-1", Html.Attributes.min "0", Html.Attributes.max "10" ] + (UpdateSpecs Fee) + fee + , text "%" + ] + ] + , button (butAttrs ++ simButAttrs ++ [ class "mt-2" ]) [ text "Simulate" ] ] @@ -518,21 +616,22 @@ historyView m quotas = ] -capitalSumView : Capital -> Html Msg -capitalSumView { principal, interest } = +amountView : Float -> Html Msg +amountView amount = let - partsTitle = - String.concat - [ "Principal: " - , Round.round 2 principal - , "\nInterest: " - , Round.round 2 interest - , " (" - , Round.round 2 (100 * interest / (principal + interest)) - , "% from total)" - ] + amountStr = + amountToString amount in - span [ class "underline", title partsTitle ] [ text (Round.round 2 (principal + interest)) ] + case String.split (String.fromChar amountSep.decimal) amountStr of + int :: float :: [] -> + span [] + [ text int + , text <| String.fromChar amountSep.decimal + , span [ class "text-xs" ] [ text float ] + ] + + _ -> + text amountStr periodToYear : Int -> Int @@ -580,7 +679,7 @@ quotaView m { period, payed, pending_principal } = , text (String.fromInt period) , capitalSumView payed - , text (Round.round 2 pending_principal) + , amountView pending_principal ] ) @@ -591,7 +690,7 @@ quotaView m { period, payed, pending_principal } = , text (String.fromInt period) , capitalSumView payed - , text (Round.round 2 pending_principal) + , amountView pending_principal ] ) @@ -602,22 +701,36 @@ quotaView m { period, payed, pending_principal } = simView : Model -> ( RawSpecs, MortgageSim ) -> Html Msg simView m ( rawSpecs, { history, topay, payed } ) = let - initial = - Maybe.withDefault 0 <| String.toFloat rawSpecs.initial + parseFloat = + Maybe.withDefault 0 << String.toFloat total = - topay.principal + topay.interest + initial + parseFloat rawSpecs.total + + initial = + parseFloat rawSpecs.initial + + vat = + total * parseFloat rawSpecs.vat / 100 + + fee = + total * parseFloat rawSpecs.fee / 100 in div [] [ hr [ class "my-5" ] [] - , div [] - [ text "To pay: " - , text <| Round.round 2 total - , text " (" - , text <| Round.round 2 initial - , text " initial + " + , pre [ class "leading-none" ] + [ text "Total to pay: " + , amountView (topay.principal + topay.interest + initial + vat + fee) + , text "\n├ Initial payment: " + , amountView (initial + vat + fee) + , text "\n│ ├ Property: " + , amountView initial + , text "\n│ ├ Agent fee: " + , amountView fee + , text "\n│ └ VAT: " + , amountView vat + , text "\n└ Financed (mortgage): " , capitalSumView topay - , text " financed)" ] -- , div [] [ text "payed: ", capitalSumView payed ] -- cgit v1.2.3