Ajout : Calc.Balance.infer_equilibre
authorJulien Moutinho <julm+hcompta@autogeree.net>
Tue, 19 May 2015 05:27:50 +0000 (07:27 +0200)
committerJulien Moutinho <julm+hcompta@autogeree.net>
Tue, 19 May 2015 05:58:35 +0000 (07:58 +0200)
cli/Hcompta/CLI/Command/Balance.hs
lib/Hcompta/Calc/Balance.hs
lib/Hcompta/Format/Ledger/Read.hs
lib/Hcompta/Lib/Foldable.hs
lib/Hcompta/Model/Journal.hs
lib/Hcompta/Model/Transaction/Posting.hs
lib/Test/Main.hs

index 912a9ea3ce80f6156bd2be880feed3e869e6b978..3a43c02eee0d86f2e10065879d2cb6132a874f1a 100644 (file)
@@ -18,7 +18,7 @@ import           System.Console.GetOpt
 import           System.Environment as Env (getProgName)
 import           System.Exit (exitWith, ExitCode(..))
 import qualified System.IO as IO
-import Text.Show.Pretty (ppShow)
+import           Text.Show.Pretty (ppShow) -- TODO: may be not necessary
 
 import qualified Hcompta.CLI.Args as Args
 import qualified Hcompta.CLI.Context as Context
@@ -99,7 +99,6 @@ run context args = do
                (flip mapM_) kos $ \(_path, ko) ->
                        Write.fatal context $ ko
         ([], journals) -> do
-               CLI.Ledger.equilibre context journals
                let balance =
                        Data.List.foldl
                         (\b j -> Balance.journal_with_virtual
index efc3d5eba47fc0d7348200f1fadcda5a16f70efc..3fd1c1406f93f6300aebc0282ff39030448ddaf2 100644 (file)
@@ -8,12 +8,14 @@ import qualified Data.Foldable
 import qualified Data.List
 import qualified Data.Map.Strict as Data.Map
 import           Data.Map.Strict (Map)
+import           Data.Foldable
 import           Data.Typeable ()
 import           Data.Maybe (fromMaybe)
 import qualified GHC.Num
 
 import qualified Hcompta.Model as Model ()
 import qualified Hcompta.Model.Account as Account
+import qualified Hcompta.Lib.Foldable as Lib.Foldable
 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
 import           Hcompta.Model.Account (Account)
 import qualified Hcompta.Model.Amount as Amount
@@ -224,6 +226,38 @@ equilibre balance = do
                 Data.Map.empty
                 (by_unit balance)
 
+
+-- | Return either:
+--
+--   * 'Left': the 'Posting.By_Account's (lazy list) that cannot be inferred
+--   * 'Right': the given 'Posting.By_Account's with inferred 'Amount's inserted.
+infer_equilibre
+ :: Posting.By_Account
+ -> Either [Unit_Sum]
+           Posting.By_Account
+infer_equilibre ps = do
+       let bal = postings ps nil
+       let Equilibre equ = equilibre bal
+       (\(l, r) -> case l of
+                _:_ -> Left l
+                _   -> Right r) $ do
+       Lib.Foldable.accumLeftsAndFoldrRights
+        (\p -> Data.Map.insertWith
+                (\pn -> (++) pn . Data.List.filter (not . Data.Map.null . Posting.amounts))
+                (Posting.account p) [p])
+        ps $ do
+       Data.Foldable.foldr
+        (\unit_sum@(Unit_Sum{ amount=amt, accounts }) acc ->
+               case Data.Map.size accounts of
+                0 -> acc
+                1 -> (Right $ (Posting.nil $ fst $ Data.Map.elemAt 0 accounts)
+                                { Posting.amounts = Amount.from_List [negate amt] }
+                        ):acc
+                _ -> Left [unit_sum]:acc
+        )
+        []
+        equ
+
 -- ** Tests
 
 -- | Return 'True' if and only if the given 'Equilibre' satisfies:
index 250d18d2b7dc17d9cb70271d7e29e50c2a1cd5ae..8f6bf725d3f048a5bcfc220a03967bea32694b51 100644 (file)
@@ -30,6 +30,7 @@ import qualified Data.Text.IO as Text.IO (readFile)
 import qualified Data.Text as Text
 import qualified System.FilePath.Posix as Path
 
+import qualified Hcompta.Calc.Balance as Calc.Balance
 import qualified Hcompta.Model.Account as Account
 import           Hcompta.Model.Account (Account)
 import qualified Hcompta.Model.Amount as Amount
@@ -695,17 +696,22 @@ transaction = (do
                 (tags_of_comments comments_before)
                 (tags_of_comments comments_after)
        R.new_line
-       postings_ <- R.many1_separated posting R.new_line
-       let (postings, postings__) =
-               (Posting.from_List . Data.List.map fst) *** id $
-               Data.List.partition
-                ((Posting.Type_Regular ==) . snd)
-                postings_
-       let (virtual_postings, balanced_virtual_postings) =
+       (postings_unchecked, postings_not_regular) <-
+               ((Posting.from_List . Data.List.map fst) *** id) .
+               Data.List.partition ((Posting.Type_Regular ==) . snd) <$>
+               R.many1_separated posting R.new_line
+       let (virtual_postings, balanced_virtual_postings_unchecked) =
                join (***) (Posting.from_List . Data.List.map fst) $
-               Data.List.partition
-                ((Posting.Type_Virtual ==) . snd)
-                postings__
+               Data.List.partition ((Posting.Type_Virtual ==) . snd)
+                postings_not_regular
+       postings <-
+               case Calc.Balance.infer_equilibre postings_unchecked of
+                Left _l -> fail $ "transaction not-equilibrated"
+                Right ps -> return ps
+       balanced_virtual_postings <-
+               case Calc.Balance.infer_equilibre balanced_virtual_postings_unchecked of
+                Left _l -> fail $ "virtual transaction not-equilibrated"
+                Right ps -> return ps
        return $
                Transaction.Transaction
                 { Transaction.code=code_
index 0bc978b0c84b569e8646745d360fa91e2d4b5c5d..ee0ef96bf9e2c734e078e1ab79b705cf5b94ab56 100644 (file)
@@ -1,9 +1,36 @@
 module Hcompta.Lib.Foldable where
 
+import Data.Monoid
 import Data.Maybe (listToMaybe, maybeToList)
-import Data.Foldable (Foldable, concatMap)
+import Data.Foldable (Foldable, foldMap, foldr)
 
 -- | Return the first non-'Nothing' returned by the given function
 --   applied on the elements of a 'Foldable'.
 find :: Foldable t => (a -> Maybe b) -> t a -> Maybe b
-find f = listToMaybe . Data.Foldable.concatMap (maybeToList . f)
+find f = listToMaybe . Data.Foldable.foldMap (maybeToList . f)
+
+-- | Like 'Data.Either.partitionEithers' but generalized
+--   to work on a 'Foldable' containing 'Monoid's.
+--
+-- NOTE: any lazyness on resulting 'Monoid's is preserved.
+partitionEithers
+ :: (Foldable t, Monoid r, Monoid l)
+ => t (Either l r) -> (l, r)
+partitionEithers m =
+       Data.Foldable.foldr (either left right) (mempty, mempty) m
+       where
+               left  a ~(l, r) = (a`mappend`l, r)
+               right a ~(l, r) = (l, a`mappend`r)
+
+-- | Return a tuple of accumulated 'Left's and folded 'Right's
+--   in the given 'Foldable'.
+--
+-- NOTE: any lazyness on resulting 'Left's’ 'Monoid' is preserved.
+accumLeftsAndFoldrRights
+ :: (Foldable t, Monoid l)
+ => (r -> ra -> ra) -> ra -> t (Either l r) -> (l, ra)
+accumLeftsAndFoldrRights f rempty m =
+       Data.Foldable.foldr (either left right) (mempty, rempty) m
+       where
+               left  a ~(l, r) = (a`mappend`l, r)
+               right a ~(l, r) = (l, f a r)
index 57706a3c63421405fb0fed96ca74759ee9fc9a13..d84ec457bce332a27708db13f942def2e123a3cd 100644 (file)
@@ -35,6 +35,6 @@ union
 
 unions :: Foldable t => t Journal -> Journal
 unions =
-       Data.Foldable.foldl
+       Data.Foldable.foldl'
         Hcompta.Model.Journal.union
         Hcompta.Model.Journal.nil
index 60590703b095f891ecbad8be4ca087bc1e2dfa54..8c010d67c90a2e6c27dd4654256d5b3bc16c1bdd 100644 (file)
@@ -112,7 +112,7 @@ units
  => m [Posting]
  -> [Amount.Unit]
 units =
-       Data.Foldable.foldl
+       Data.Foldable.foldl'
         (\acc ->
                Data.List.union acc .
                Data.List.concatMap
index 45b3f555d5e223fedc9759f851ebc74080450334..77e00cda0f4c0872ba89a7120cb82b7771aeb6a6 100644 (file)
@@ -41,7 +41,80 @@ main = defaultMain $ hUnitTestToTests test_Hcompta
 test_Hcompta :: Test
 test_Hcompta =
        TestList
-        [ "Model" ~: TestList
+        [ "Lib" ~: TestList
+                [ "TreeMap" ~: TestList
+                        [ "insert" ~: TestList
+                                [ "[] 0" ~:
+                                        (Lib.TreeMap.insert const ((0::Int):|[]) () Lib.TreeMap.empty)
+                                        ~?=
+                                        (Lib.TreeMap.TreeMap $
+                                               Data.Map.fromList
+                                                [ ((0::Int), Lib.TreeMap.leaf ())
+                                                ])
+                                , "[] 0/1" ~:
+                                        (Lib.TreeMap.insert const ((0::Int):|1:[]) () Lib.TreeMap.empty)
+                                        ~?=
+                                        (Lib.TreeMap.TreeMap $
+                                               Data.Map.fromList
+                                                [ ((0::Int), Lib.TreeMap.Node
+                                                        { Lib.TreeMap.node_value = Nothing
+                                                        , Lib.TreeMap.node_size = 1
+                                                        , Lib.TreeMap.node_descendants =
+                                                               Lib.TreeMap.singleton ((1::Int):|[]) ()
+                                                        })
+                                                ])
+                                ]
+                        , "union" ~: TestList
+                                [
+                                ]
+                        , "map_by_depth_first" ~: TestList
+                                [
+                                ]
+                        , "flatten" ~: TestList
+                                [ "[0, 0/1, 0/1/2]" ~:
+                                        (Lib.TreeMap.flatten id $
+                                               Lib.TreeMap.from_List const
+                                                [ ((0:|[]), ())
+                                                , ((0:|1:[]), ())
+                                                , ((0:|1:2:[]), ())
+                                                ]
+                                        )
+                                        ~?=
+                                        (Data.Map.fromList
+                                                [ ((0:|[]), ())
+                                                , ((0:|1:[]), ())
+                                                , ((0:|1:2:[]), ())
+                                                ])
+                                , "[1, 1/2, 1/22, 1/2/3, 1/2/33, 11, 11/2, 11/2/3, 11/2/33]" ~:
+                                        (Lib.TreeMap.flatten id $
+                                               Lib.TreeMap.from_List const
+                                                [ ((1:|[]), ())
+                                                , ((1:|2:[]), ())
+                                                , ((1:|22:[]), ())
+                                                , ((1:|2:3:[]), ())
+                                                , ((1:|2:33:[]), ())
+                                                , ((11:|[]), ())
+                                                , ((11:|2:[]), ())
+                                                , ((11:|2:3:[]), ())
+                                                , ((11:|2:33:[]), ())
+                                                ]
+                                        )
+                                        ~?=
+                                        (Data.Map.fromList
+                                                [ ((1:|[]), ())
+                                                , ((1:|2:[]), ())
+                                                , ((1:|22:[]), ())
+                                                , ((1:|2:3:[]), ())
+                                                , ((1:|2:33:[]), ())
+                                                , ((11:|[]), ())
+                                                , ((11:|2:[]), ())
+                                                , ((11:|2:3:[]), ())
+                                                , ((11:|2:33:[]), ())
+                                                ])
+                                ]
+                        ]
+                ]
+        , "Model" ~: TestList
                 [ "Account" ~: TestList
                         [ "foldr" ~: TestList
                                 [ "[A]" ~:
@@ -612,6 +685,36 @@ test_Hcompta =
                                                         })
                                                 ])
                                 ]
+                        , "equilibre" ~: TestList
+                                [ "{A+$1, $1}" ~:
+                                       (Calc.Balance.equilibre $
+                                               Calc.Balance.Balance
+                                                { Calc.Balance.by_account =
+                                                       Lib.TreeMap.from_List const
+                                                        [ ("A":|[], Amount.from_List [ Amount.usd $ 1 ])
+                                                        , ("B":|[], Amount.from_List [])
+                                                        ]
+                                                , Calc.Balance.by_unit =
+                                                       Data.Map.fromList $
+                                                       Data.List.map Calc.Balance.assoc_unit_sum $
+                                                       [ Calc.Balance.Unit_Sum
+                                                        { Calc.Balance.amount = Amount.usd $ 1
+                                                        , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,())
+                                                                ["A":|[]]
+                                                        }
+                                                       ]
+                                                })
+                                        ~?=
+                                        (Calc.Balance.Equilibre $
+                                               Data.Map.fromList $
+                                               Data.List.map Calc.Balance.assoc_unit_sum $
+                                               [ Calc.Balance.Unit_Sum
+                                                { Calc.Balance.amount = Amount.usd $ 1
+                                                , Calc.Balance.accounts = Data.Map.fromList $ Data.List.map (,())
+                                                        ["B":|[]]
+                                                }
+                                               ])
+                                ]
                         , "is_equilibrable" ~: TestList
                                 [ "nil" ~: TestCase $
                                                (@=?) True $
@@ -801,6 +904,44 @@ test_Hcompta =
                                                        ]
                                                 }
                                 ]
+                        , "infer_equilibre" ~: TestList
+                                [ "{A+$1 B}" ~:
+                                        (Calc.Balance.infer_equilibre $
+                                               Posting.from_List
+                                                [ (Posting.nil ("A":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] }
+                                                , (Posting.nil ("B":|[]))
+                                                        { Posting.amounts=Amount.from_List [] }
+                                                ])
+                                        ~?=
+                                        (Right $
+                                               Posting.from_List
+                                                [ (Posting.nil ("A":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] }
+                                                , (Posting.nil ("B":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ -1 ] }
+                                                ])
+                                , "{A+$1 B-1€}" ~:
+                                        (Calc.Balance.infer_equilibre $
+                                               Posting.from_List
+                                                [ (Posting.nil ("A":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ 1 ] }
+                                                , (Posting.nil ("B":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.eur $ -1 ] }
+                                                ])
+                                        ~?=
+                                        (Right $
+                                               Posting.from_List
+                                                [ (Posting.nil ("A":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.eur $ 1 ] }
+                                                , (Posting.nil ("A":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ 1] }
+                                                , (Posting.nil ("B":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.usd $ -1 ] }
+                                                , (Posting.nil ("B":|[]))
+                                                        { Posting.amounts=Amount.from_List [ Amount.eur $ -1 ] }
+                                                ])
+                                ]
                         ]
                 ]
         , "Format" ~: TestList