{-# LANGUAGE TupleSections #-} import Prelude import Test.HUnit import Test.Framework.Providers.HUnit (hUnitTestToTests) import Test.Framework.Runners.Console (defaultMain) import Control.Applicative ((<*)) import qualified Data.List import qualified Data.Map import qualified Data.Either import qualified Text.Parsec import Data.Decimal (DecimalRaw(..)) import qualified Hcompta.Model.Account as Account import qualified Hcompta.Model.Amount as Amount import qualified Hcompta.Model.Amount.Style as Style import qualified Hcompta.Model.Transaction.Posting as Posting import qualified Hcompta.Calc.Balance as Calc.Balance import qualified Hcompta.Format.Ledger.Read as Format.Ledger.Read main :: IO () main = defaultMain $ hUnitTestToTests test_Hcompta test_Hcompta :: Test test_Hcompta = TestList [ "Model" ~: TestList [ "Account" ~: TestList [ "fold" ~: TestList [ "[] = []" ~: (reverse $ Account.fold [] (:) []) ~?= [] , "[A] = [[A]]" ~: (reverse $ Account.fold ["A"] (:) []) ~?= [["A"]] , "[A, B] = [[A], [A, B]]" ~: (reverse $ Account.fold ["A", "B"] (:) []) ~?= [["A"], ["A", "B"]] , "[A, B, C] = [[A], [A, B], [A, B, C]]" ~: (reverse $ Account.fold ["A", "B", "C"] (:) []) ~?= [["A"], ["A", "B"], ["A", "B", "C"]] ] , "ascending" ~: TestList [ "[] = []" ~: Account.ascending [] ~?= [] , "[A] = []" ~: Account.ascending ["A"] ~?= [] , "[A, B] = [A]" ~: Account.ascending ["A", "B"] ~?= ["A"] , "[A, B, C] = [A, B]" ~: Account.ascending ["A", "B", "C"] ~?= ["A", "B"] ] ] ] , "Calc" ~: TestList [ "Balance" ~: TestList [ "posting" ~: TestList [ "[A+$1] = A+$1 & $+1" ~: (Calc.Balance.posting Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] } Calc.Balance.nil) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "[A+$1, A-$1] = {A+$0, $+0}" ~: (Data.List.foldl (flip Calc.Balance.posting) Calc.Balance.nil [ Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] } , Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ -1 ] } ]) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 0 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "[A+$1, A-€1] = {A+$1-€1, $+1 €-1}" ~: (Data.List.foldl (flip Calc.Balance.posting) Calc.Balance.nil [ Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] } , Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.eur $ -1 ] } ]) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1, Amount.eur $ -1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ -1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "[A+$1, B-$1] = {A+$1 B-$1, $+0}" ~: (Data.List.foldl (flip Calc.Balance.posting) Calc.Balance.nil [ Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] } , Posting.nil { Posting.account=["B"] , Posting.amounts=Amount.from_List [ Amount.usd $ -1 ] } ]) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ -1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } ] } , "[A+$1+€2, A-$1-€2] = {A+$0+€0, $+0 €+0}" ~: (Data.List.foldl (flip Calc.Balance.posting) Calc.Balance.nil [ Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1, Amount.eur $ 2 ] } , Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ -1, Amount.eur $ -2 ] } ]) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 0, Amount.eur $ 0 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "[A+$1+€2+£3, B-$1-2€-£3] = {A+$1+€2+£3 B-$1-€2-£3, $+0 €+0 £+0}" ~: (Data.List.foldl (flip Calc.Balance.posting) Calc.Balance.nil [ Posting.nil { Posting.account=["A"] , Posting.amounts=Amount.from_List [ Amount.usd $ 1, Amount.eur $ 2, Amount.gbp $ 3 ] } , Posting.nil { Posting.account=["B"] , Posting.amounts=Amount.from_List [ Amount.usd $ -1, Amount.eur $ -2, Amount.gbp $ -3 ] } ]) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1, Amount.eur $ 2, Amount.gbp $ 3 ]) , (["B"], Amount.from_List [ Amount.usd $ -1, Amount.eur $ -2, Amount.gbp $ -3 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.gbp $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } ] } ] , "union" ~: TestList [ "nil nil = nil" ~: Calc.Balance.union Calc.Balance.nil Calc.Balance.nil ~?= Calc.Balance.nil , "{A+$1, $+1} {A+$1, $+1} = {A+$2, $+2}" ~: Calc.Balance.union (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] }) (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] }) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 2 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 2 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "{A+$1, $+1} {B+$1, $+1} = {A+$1 B+$1, $+2}" ~: Calc.Balance.union (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] }) (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["B"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["B"]] } ] }) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 2 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } ] } , "{A+$1, $+1} {B+€1, €+1} = {A+$1 B+€1, $+1 €+1}" ~: Calc.Balance.union (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] }) (Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["B"], Amount.from_List [ Amount.eur $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["B"]] } ] }) ~?= Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.eur $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["B"]] } ] } ] , "expand" ~: TestList [ "nil_By_Account = nil_By_Account" ~: Calc.Balance.expand Calc.Balance.nil_By_Account ~?= (Calc.Balance.Expanded $ Calc.Balance.nil_By_Account) , "A+$1 = A+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A/A+$1 = A+$1 A/A+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A/B+$1 = A+$1 A/B+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A/B/C+$1 = A+$1 A/B+$1 A/B/C+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A", "B", "C"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B", "C"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A+$1 A/B+$1 = A+$2 A/B+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 2 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A+$1 A/B+$1 A/B/C+$1 = A+$3 A/B+$2 A/B/C+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B", "C"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 3 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 2 ]) , (["A", "B", "C"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A+$1 A/B+$1 A/B/C+$1 A/B/C/D+$1 = A+$4 A/B+$3 A/B/C+$2 A/B/C/D+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B", "C"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B", "C", "D"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 4 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 3 ]) , (["A", "B", "C"], Amount.from_List [ Amount.usd $ 2 ]) , (["A", "B", "C", "D"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A+$1 A/B+$1 B/A+$1 = A+$2 A/B+$1 B/A+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 2 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) , "A+$1 A/B+$1 B/A+$1 = A+$2 A/B+$1 B/A+$1" ~: Calc.Balance.expand (Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) ~?= (Calc.Balance.Expanded $ Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 2 ]) , (["A", "B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ 1 ]) , (["B", "A"], Amount.from_List [ Amount.usd $ 1 ]) ]) ] , "is_equilibrated" ~: TestList [ "nil = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.nil , "{A+$0, $+0} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 0 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "{A+$1, $+1} = False" ~: TestCase $ (@=?) False $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "{A+$0+€0, $0 €+0} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 0, Amount.eur $ 0 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "{A+$1, B-$1, $+0} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ -1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } ] } , "{A+$1 B, $+1} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List []) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } ] } , "{A+$1 B+€1, $+1 €+1} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.eur $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["B"]] } ] } , "{A+$1 B-$1+€1, $+0 €+1} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1 ]) , (["B"], Amount.from_List [ Amount.usd $ -1, Amount.eur $ 1 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 1 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["B"]] } ] } , "{A+$1+€2+£3 B-$1-€2-£3, $+0 €+0 £+0} = True" ~: TestCase $ (@=?) True $ Calc.Balance.is_equilibrated $ Calc.Balance.Balance { Calc.Balance.by_account = Data.Map.fromList [ (["A"], Amount.from_List [ Amount.usd $ 1, Amount.eur $ 2, Amount.gbp $ 3 ]) , (["B"], Amount.from_List [ Amount.usd $ -1, Amount.eur $ -2, Amount.gbp $ -3 ]) ] , Calc.Balance.by_unit = Data.Map.fromList $ Data.List.map Calc.Balance.assoc_by_amount_unit $ [ Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.usd $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.eur $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } , Calc.Balance.Sum_by_Unit { Calc.Balance.amount = Amount.gbp $ 0 , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,()) [["A"], ["B"]] } ] } ] ] ] , "Format" ~: TestList [ "Ledger" ~: TestList [ "Read" ~: TestList [ "account_name" ~: TestList [ "\"\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" ""]) ~?= [] , "\"A\" = Right \"A\"" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A"]) ~?= ["A"] , "\"AA\" = Right \"AA\"" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "AA"]) ~?= ["AA"] , "\" \" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" " "]) ~?= [] , "\":\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" ":"]) ~?= [] , "\"A:\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A:"]) ~?= [] , "\":A\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" ":A"]) ~?= [] , "\"A \" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A "]) ~?= [] , "\"A A\" = Right \"A A\"" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A A"]) ~?= ["A A"] , "\"A \" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A "]) ~?= [] , "\"A \\n\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account_name <* Text.Parsec.eof) () "" "A \n"]) ~?= [] ] , "account" ~: TestList [ "\"\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" ""]) ~?= [] , "\"A\" = Right [\"A\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A"]) ~?= [["A"]] , "\"A:\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A:"]) ~?= [] , "\":A\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" ":A"]) ~?= [] , "\"A \" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A "]) ~?= [] , "\" A\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" " A"]) ~?= [] , "\"A:B\" = Right [\"A\", \"B\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A:B"]) ~?= [["A", "B"]] , "\"A:B:C\" = Right [\"A\", \"B\", \"C\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A:B:C"]) ~?= [["A", "B", "C"]] , "\"Aa:Bbb:Cccc\" = Right [\"Aa\", \"Bbb\", \":Cccc\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "Aa:Bbb:Cccc"]) ~?= [["Aa", "Bbb", "Cccc"]] , "\"A a : B b b : C c c c\" = Right [\"A a \", \" B b b \", \": C c c c\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A a : B b b : C c c c"]) ~?= [["A a ", " B b b ", " C c c c"]] , "\"A: :C\" = Right [\"A\", \" \", \"C\"]" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A: :C"]) ~?= [["A", " ", "C"]] , "\"A::C\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.account <* Text.Parsec.eof) () "" "A::C"]) ~?= [] ] , "amount" ~: TestList [ "\"\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" ""]) ~?= [] , "\"0\" = Right 0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 }] , "\"00\" = Right 0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 }] , "\"0.\" = Right 0." ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0."]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just '.' } }] , "\".0\" = Right 0.0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" ".0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.precision = 1 } }] , "\"0,\" = Right 0," ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0,"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just ',' } }] , "\",0\" = Right 0,0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" ",0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.precision = 1 } }] , "\"0_\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0_"]) ~?= [] , "\"_0\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "_0"]) ~?= [] , "\"0.0\" = Right 0.0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0.0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.precision = 1 } }] , "\"00.00\" = Right 0.00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "00.00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.precision = 2 } }] , "\"0,0\" = Right 0,0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0,0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.precision = 1 } }] , "\"00,00\" = Right 0,00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "00,00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.precision = 2 } }] , "\"0_0\" = Right 0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0_0"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Just $ Style.Grouping '_' [1] , Style.precision = 0 } }] , "\"00_00\" = Right 0" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "00_00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Just $ Style.Grouping '_' [2] , Style.precision = 0 } }] , "\"0,000.00\" = Right 0,000.00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "0,000.00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.grouping_integral = Just $ Style.Grouping ',' [3] , Style.precision = 2 } }] , "\"0.000,00\" = Right 0.000,00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount) () "" "0.000,00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 0 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.grouping_integral = Just $ Style.Grouping '.' [3] , Style.precision = 2 } }] , "\"1,000.00\" = Right 1,000.00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1,000.00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1000 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.grouping_integral = Just $ Style.Grouping ',' [3] , Style.precision = 2 } }] , "\"1.000,00\" = Right 1.000,00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount) () "" "1.000,00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1000 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.grouping_integral = Just $ Style.Grouping '.' [3] , Style.precision = 2 } }] , "\"1,000.00.\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount) () "" "1,000.00."]) ~?= [] , "\"1.000,00,\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount) () "" "1.000,00,"]) ~?= [] , "\"1,000.00_\" = Left" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount) () "" "1,000.00_"]) ~?= [] , "\"12\" = Right 12" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "123"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 123 }] , "\"1.2\" = Right 1.2" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1.2"]) ~?= [Amount.nil { Amount.quantity = Decimal 1 12 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.precision = 1 } }] , "\"1,2\" = Right 1,2" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1,2"]) ~?= [Amount.nil { Amount.quantity = Decimal 1 12 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.precision = 1 } }] , "\"12.23\" = Right 12.23" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "12.34"]) ~?= [Amount.nil { Amount.quantity = Decimal 2 1234 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.precision = 2 } }] , "\"12,23\" = Right 12,23" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "12,34"]) ~?= [Amount.nil { Amount.quantity = Decimal 2 1234 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.precision = 2 } }] , "\"1_2\" = Right 1_2" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1_2"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 12 , Amount.style = Style.nil { Style.grouping_integral = Just $ Style.Grouping '_' [1] , Style.precision = 0 } }] , "\"1_23\" = Right 1_23" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1_23"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 123 , Amount.style = Style.nil { Style.grouping_integral = Just $ Style.Grouping '_' [2] , Style.precision = 0 } }] , "\"1_23_456\" = Right 1_23_456" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1_23_456"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 123456 , Amount.style = Style.nil { Style.grouping_integral = Just $ Style.Grouping '_' [3, 2] , Style.precision = 0 } }] , "\"1_23_456.7890_12345_678901\" = Right 1_23_456.7890_12345_678901" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1_23_456.7890_12345_678901"]) ~?= [Amount.nil { Amount.quantity = Decimal 15 123456789012345678901 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.grouping_integral = Just $ Style.Grouping '_' [3, 2] , Style.grouping_fractional = Just $ Style.Grouping '_' [4, 5, 6] , Style.precision = 15 } }] , "\"123456_78901_2345.678_90_1\" = Right 123456_78901_2345.678_90_1" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "123456_78901_2345.678_90_1"]) ~?= [Amount.nil { Amount.quantity = Decimal 6 123456789012345678901 , Amount.style = Style.nil { Style.fractioning = Just '.' , Style.grouping_integral = Just $ Style.Grouping '_' [4, 5, 6] , Style.grouping_fractional = Just $ Style.Grouping '_' [3, 2] , Style.precision = 6 } }] , "\"$1\" = Right $1" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "$1"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Left , Style.unit_spaced = Just False } , Amount.unit = "$" }] , "\"1$\" = Right 1$" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1$"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Right , Style.unit_spaced = Just False } , Amount.unit = "$" }] , "\"$ 1\" = Right $ 1" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "$ 1"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Left , Style.unit_spaced = Just True } , Amount.unit = "$" }] , "\"1 $\" = Right 1 $" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1 $"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Right , Style.unit_spaced = Just True } , Amount.unit = "$" }] , "\"-$1\" = Right $-1" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "-$1"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 (-1) , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Left , Style.unit_spaced = Just False } , Amount.unit = "$" }] , "\"\\\"4 2\\\"1\" = Right \\\"4 2\\\"1" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "\"4 2\"1"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Left , Style.unit_spaced = Just False } , Amount.unit = "4 2" }] , "\"1\\\"4 2\\\"\" = Right 1\\\"4 2\\\"" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1\"4 2\""]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1 , Amount.style = Style.nil { Style.fractioning = Nothing , Style.grouping_integral = Nothing , Style.grouping_fractional = Nothing , Style.precision = 0 , Style.unit_side = Just Style.Side_Right , Style.unit_spaced = Just False } , Amount.unit = "4 2" }] , "\"$1.000,00\" = Right $1.000,00" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "$1.000,00"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1000 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.grouping_integral = Just $ Style.Grouping '.' [3] , Style.grouping_fractional = Nothing , Style.precision = 2 , Style.unit_side = Just Style.Side_Left , Style.unit_spaced = Just False } , Amount.unit = "$" }] , "\"1.000,00$\" = Right 1.000,00$" ~: (Data.Either.rights $ [Text.Parsec.runParser (Format.Ledger.Read.amount <* Text.Parsec.eof) () "" "1.000,00$"]) ~?= [Amount.nil { Amount.quantity = Decimal 0 1000 , Amount.style = Style.nil { Style.fractioning = Just ',' , Style.grouping_integral = Just $ Style.Grouping '.' [3] , Style.grouping_fractional = Nothing , Style.precision = 2 , Style.unit_side = Just Style.Side_Right , Style.unit_spaced = Just False } , Amount.unit = "$" }] ] ] ] ] ]