1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE FlexibleInstances #-}
5 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
6 {-# LANGUAGE MultiParamTypeClasses #-}
7 {-# LANGUAGE NamedFieldPuns #-}
8 {-# LANGUAGE RecordWildCards #-}
9 {-# LANGUAGE ScopedTypeVariables #-}
10 {-# LANGUAGE TypeFamilies #-}
11 {-# LANGUAGE TypeOperators #-}
14 module Hcompta.GL where
16 import Control.DeepSeq (NFData(..))
17 import Control.Exception (assert)
20 import Data.Eq (Eq(..))
21 import Data.Function (($), (.), flip)
22 import Data.Functor (Functor(..))
23 import Data.Functor.Compose (Compose(..))
24 import Data.Map.Strict (Map)
25 import qualified Data.Map.Strict as Map
26 import Data.Maybe (Maybe(..))
27 import Data.Monoid (Monoid(..))
28 import qualified Data.MonoTraversable as MT
29 import Data.Ord (Ord(..))
30 import Data.Sequence (Seq, (><), (|>), ViewR(..))
31 import qualified Data.Sequence as Seq
32 import qualified Data.Strict.Maybe as Strict
33 import qualified Data.Traversable as Traversable
34 import Data.TreeMap.Strict (TreeMap(..))
35 import qualified Data.TreeMap.Strict as TreeMap
36 import Data.Tuple (snd)
37 import Data.Typeable ()
38 import Prelude (seq, undefined)
39 import Text.Show (Show(..))
41 import Hcompta.Account
44 import qualified Hcompta.Lib.Strict as Strict
45 import Hcompta.Posting
46 import Hcompta.Quantity
51 newtype GL_ tran date acct_sect post amt
52 = GL (TreeMap acct_sect
53 (Map date (Seq (GL_Line tran post amt))))
54 deriving (Data, Eq, NFData, Show, Typeable)
58 (MT.Element (Account :@ (MT.Element (Postings :@ tran))))
59 (MT.Element (Postings :@ tran))
60 (Amount :@ (MT.Element (Postings :@ tran)))
63 gl_empty = GL TreeMap.empty
65 -- * Type 'GL_Account'
66 -- | 'GL' operations works on this type of 'Account'.
67 type GL_Account = TreeMap.Path
69 -- | Return the given 'GL'
70 -- updated by the given 'GL_Transaction'.
72 -- NOTE: to reduce memory consumption when applying 'gl_cons' iteratively,
73 -- the given 'GL' is matched strictly.
75 ( post ~ MT.Element (Postings :@ tran)
76 , acct_sect ~ MT.Element (Account :@ post)
80 , Get (GL_Account acct_sect) post
81 , MT.MonoFoldable (Postings :@ tran)
82 , Addable (Amount :@ post)
85 ) => tran -> GL tran -> GL tran
86 gl_cons tran (GL !gl) =
92 { gl_line_transaction = tran
93 , gl_line_posting = post
94 , gl_line_sum = getI _Amount post
97 Map.singleton (getI _Date tran) $
98 Seq.singleton first_line in
101 let (nlt, leq, neq, ngt) =
102 case Map.splitLookup (getI _Date tran) old of
103 (olt, Nothing, ogt) | Map.null olt ->
104 (olt, first_line, Seq.singleton first_line, ogt)
105 (olt, Nothing, ogt) ->
107 case Seq.viewr $ snd $ Map.findMax olt of
108 (_:>GL_Line{gl_line_sum = s}) ->
109 first_line{gl_line_sum = quantity_add s $ getI _Amount post}
111 in (olt, line, Seq.singleton line, ogt)
112 (olt, Just oeq, ogt) ->
113 case Seq.viewr oeq of
114 (_:>GL_Line{gl_line_sum = s}) ->
115 let line = first_line{gl_line_sum = quantity_add s $ getI _Amount post} in
116 (olt, line, oeq |> line, ogt)
117 _ -> (olt, first_line, Seq.singleton first_line, ogt)
120 Map.insert (getI _Date tran) neq $
122 (fmap (\l -> l{gl_line_sum =
123 quantity_add (gl_line_sum leq) $ gl_line_sum l}))
130 (getI _Postings tran)
133 ( post ~ MT.Element (Postings :@ tran)
134 , Ord (MT.Element (Account :@ post))
136 ) => GL tran -> GL tran -> GL tran
137 gl_union (GL x) (GL y) =
138 GL $ TreeMap.union (Map.unionWith (flip mappend)) x y
141 data GL_Line tran post amt
143 { gl_line_transaction :: tran
144 , gl_line_posting :: post
147 deriving (Data, Eq, Show, Typeable)
152 ) => NFData (GL_Line tran post amt) where
154 rnf gl_line_transaction `seq`
155 rnf gl_line_posting `seq`
159 -- * Type 'ExpandedGL'
161 -- | Descending propagation of 'Amount's accross 'Account's.
162 newtype ExpandedGL_ tran date acct_sect post amt
163 = ExpandedGL (TreeMap acct_sect (ExpandedGL_Line tran date post amt))
164 deriving (Data, Eq, NFData, Show, Typeable)
168 (MT.Element (Account :@ (MT.Element (Postings :@ tran))))
169 (MT.Element (Postings :@ tran))
170 (Amount :@ (MT.Element (Postings :@ tran)))
172 -- ** Type 'ExpandedGL_Line'
174 -- * 'Strict.exclusive': contains the original 'GL_Line's.
175 -- * 'Strict.inclusive': contains 'quantity_add' folded
176 -- over 'Strict.exclusive' and 'Strict.inclusive'
177 -- of 'TreeMap.node_descendants'
178 type ExpandedGL_Line tran date post amt
179 = Strict.Clusive (Map date (Seq (GL_Line tran post amt)))
181 -- | Return the given 'GL' with:
183 -- * all missing 'Account.parent' 'Account's inserted;
184 -- * and every mapped 'GL_Line'
185 -- added with any 'GL_Line'
186 -- of the 'Account's for which it is 'Account.parent'.
188 ( post ~ MT.Element (Postings :@ tran)
190 , Addable (Amount :@ post)
191 , MT.MonoFoldable (Postings :@ tran)
192 , Ord (MT.Element (Account :@ post))
194 ) => GL tran -> ExpandedGL tran
195 expanded_gl (GL gl) =
197 Strict.fromMaybe (assert False undefined) .
198 TreeMap.node_value in
200 TreeMap.map_by_depth_first
201 (\(TreeMap nodes) value ->
202 let exclusive = Strict.fromMaybe Map.empty value in
208 Traversable.mapAccumL -- NOTE: recalc 'gl_line_sum's
210 let amt = getI _Amount $ gl_line_posting line in
212 Nothing -> (Just amt, line)
214 let new_sum = quantity_add last_sum amt in
216 , line{gl_line_sum=new_sum} )
220 (Map.unionWith (flip (><)) . Strict.inclusive . from_value)