4 people like it.

Multi-currency report

Multi-currency report (generated as HTML) based on example given at the start of chapter one of Kent Beck's Test-Driven Development by Example book.

Multi-currency domain

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
type Money = private { Amount:decimal; Currency:Currency } 
   with   
   static member ( * ) (lhs:Money,rhs:decimal) = 
      { lhs with Amount=lhs.Amount * rhs }
   static member ( + ) (lhs:Money,rhs:Money) =
      if lhs.Currency <> rhs.Currency then invalidOp "Currency mismatch"
      { lhs with Amount=lhs.Amount + rhs.Amount}
   override money.ToString() = sprintf "%M%s" money.Amount money.Currency
and  Currency = string

type RateTable = { To:Currency; From:Rate list }
and  Rate = { From:Currency; Rate:decimal }

let exchangeRate (rates:RateTable) cy =   
   if rates.To = cy then 1M
   else rates.From |> Seq.find (fun rate -> rate.From = cy) |> fun rate -> rate.Rate

let convertCurrency (rates:RateTable) money =
   let rate = exchangeRate rates money.Currency
   { Amount=money.Amount / rate; Currency=rates.To }

Multi-currency report model

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
type Report = { Rows:Row list; Total:Money }
and  Row = { Position:Position; Total:Money }
and  Position = { Instrument:string; Shares:int; Price:Money }

let generateReport rates positions =
   let rows =
      [for position in positions ->        
         let total = position.Price * decimal position.Shares
         { Position=position; Total=total } ]
   let total =
      rows
      |> Seq.map (fun row -> convertCurrency rates row.Total)   
      |> Seq.reduce (+)
   { Rows=rows; Total=total }

Multi-currency report view

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
let toHtml (report:Report) =
   html [
      head [ title %"Multi-currency report" ]
      style %tableStyle
      body [
         table <|
            ("cellpadding"%="8") ::
            thead [
               tr [th %"Instrument"; th %"Shares"; th %"Price"; th %"Total"] 
            ] ::
            tbody [
               for row in report.Rows ->
                  let p = row.Position
                  tr [td %p.Instrument; td %p.Shares; td %p.Price; td %row.Total]
            ] :: 
            [ tfoot [
               tr [td ("colspan"%="3"::"align"%="right"::[strong %"Total"])
                   td %report.Total]
            ]]
         ]
      ]

Example

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
let USD amount = { Amount=amount; Currency="USD" }
let CHF amount = { Amount=amount; Currency="CHF" }

let positions =
   [{Instrument="IBM";      Shares=1000; Price=USD( 25M)}
    {Instrument="Novartis"; Shares= 400; Price=CHF(150M)}]

let inUSD = { To="USD"; From=[{From="CHF";Rate=1.5M}] }

let positionsInUSD = generateReport inUSD positions

positionsInUSD |> toHtml |> Html.toString
type Money =
  private {Amount: decimal;
           Currency: Currency;}
  override ToString : unit -> string
  static member ( + ) : lhs:Money * rhs:Money -> Money
  static member ( * ) : lhs:Money * rhs:decimal -> Money

Full name: Script.Money
Money.Amount: decimal
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
Multiple items
Money.Currency: Currency

--------------------
type Currency = string

Full name: Script.Currency
val lhs : Money
val rhs : decimal
val rhs : Money
Money.Currency: Currency
val invalidOp : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.invalidOp
val money : Money
override Money.ToString : unit -> string

Full name: Script.Money.ToString
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
type Currency = string

Full name: Script.Currency
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
type RateTable =
  {To: Currency;
   From: Rate list;}

Full name: Script.RateTable
RateTable.To: Currency
RateTable.From: Rate list
type Rate =
  {From: Currency;
   Rate: decimal;}

Full name: Script.Rate
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Rate.From: Currency
Multiple items
Rate.Rate: decimal

--------------------
type Rate =
  {From: Currency;
   Rate: decimal;}

Full name: Script.Rate
val exchangeRate : rates:RateTable -> cy:Currency -> decimal

Full name: Script.exchangeRate
val rates : RateTable
val cy : Currency
module Seq

from Microsoft.FSharp.Collections
val find : predicate:('T -> bool) -> source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.find
val rate : Rate
Rate.Rate: decimal
val convertCurrency : rates:RateTable -> money:Money -> Money

Full name: Script.convertCurrency
val rate : decimal
type Report =
  {Rows: Row list;
   Total: Money;}

Full name: Script.Report
Report.Rows: Row list
type Row =
  {Position: Position;
   Total: Money;}

Full name: Script.Row
Report.Total: Money
Multiple items
Row.Position: Position

--------------------
type Position =
  {Instrument: string;
   Shares: int;
   Price: Money;}

Full name: Script.Position
Row.Total: Money
type Position =
  {Instrument: string;
   Shares: int;
   Price: Money;}

Full name: Script.Position
Position.Instrument: string
Position.Shares: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Position.Price: Money
val generateReport : rates:RateTable -> positions:seq<Position> -> Report

Full name: Script.generateReport
val positions : seq<Position>
val rows : Row list
val position : Position
val total : Money
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val row : Row
val reduce : reduction:('T -> 'T -> 'T) -> source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.reduce
val toHtml : report:Report -> Html

Full name: Script.toHtml
val report : Report
val html : (Html list -> Html)

Full name: Script.html
val head : (Html list -> Html)

Full name: Script.head
val title : (Html list -> Html)

Full name: Script.title
val style : (Html list -> Html)

Full name: Script.style
val tableStyle : string

Full name: Script.tableStyle
val body : (Html list -> Html)

Full name: Script.body
val table : (Html list -> Html)

Full name: Script.table
val thead : (Html list -> Html)

Full name: Script.thead
val tr : (Html list -> Html)

Full name: Script.tr
val th : (Html list -> Html)

Full name: Script.th
val tbody : (Html list -> Html)

Full name: Script.tbody
val p : Position
Row.Position: Position
val td : (Html list -> Html)

Full name: Script.td
val tfoot : (Html list -> Html)

Full name: Script.tfoot
val strong : (Html list -> Html)

Full name: Script.strong
val USD : amount:decimal -> Money

Full name: Script.USD
val amount : decimal
val CHF : amount:decimal -> Money

Full name: Script.CHF
val positions : Position list

Full name: Script.positions
val inUSD : RateTable

Full name: Script.inUSD
val positionsInUSD : Report

Full name: Script.positionsInUSD
type Html =
  | Elem of string * Html list
  | Attr of string * string
  | Text of string
  override ToString : unit -> string
  static member toString : elem:Html -> string

Full name: Script.Html
static member Html.toString : elem:Html -> string
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/r5
Posted:9 years ago
Author:Phillip Trelford
Tags: html , dsl , money