1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE FlexibleInstances #-}
5 {-# LANGUAGE MultiParamTypeClasses #-}
6 {-# LANGUAGE NamedFieldPuns #-}
7 {-# LANGUAGE RecordWildCards #-}
8 {-# LANGUAGE StandaloneDeriving #-}
9 {-# LANGUAGE TypeFamilies #-}
10 {-# OPTIONS_GHC -fno-warn-deprecations #-}
11 -- FIXME: to be removed when dropping GHC-7.6 support
14 module Hcompta.GL where
16 -- import Control.Applicative (Const(..))
17 import Control.DeepSeq (NFData(..))
18 import Control.Exception (assert)
21 -- import Data.Eq (Eq(..))
22 -- import Data.Foldable (Foldable(..))
23 -- import qualified Data.Foldable as Foldable
24 import Data.Function (($), (.), flip, id)
25 import Data.Functor (Functor(..))
26 import Data.Functor.Compose (Compose(..))
27 -- import qualified Data.List as List
28 import Data.Map.Strict (Map)
29 import qualified Data.Map.Strict as Map
30 import Data.Maybe (Maybe(..))
31 import Data.Monoid (Monoid(..))
32 import qualified Data.MonoTraversable as MT
33 import Data.Sequence (Seq, (><), (|>), ViewR(..))
34 import qualified Data.Sequence as Seq
35 import qualified Data.Strict.Maybe as Strict
36 import qualified Data.Traversable as Traversable
37 import Data.TreeMap.Strict (TreeMap(..))
38 import qualified Data.TreeMap.Strict as TreeMap
39 import Data.Tuple (snd)
40 import Data.Typeable ()
41 import Prelude (seq, undefined)
42 import Text.Show (Show(..))
44 import Hcompta.Account
46 import qualified Hcompta.Lib.Strict as Strict
47 import Hcompta.Posting
48 import Hcompta.Quantity
49 import Hcompta.Transaction
51 -- * Class 'GL_Posting'
55 , Addable (GL_Posting_Quantity p)
56 , Data (GL_Posting_Quantity p)
57 , NFData (GL_Posting_Quantity p)
58 , Show (GL_Posting_Quantity p)
59 ) => GL_Posting p where
60 type GL_Posting_Quantity p
61 gl_posting_quantity :: p -> GL_Posting_Quantity p
67 , Posting (account, quantity)
70 ) => GL_Posting (account, quantity)
72 -- type Posting_Account (account, quantity) = account
73 type Posting_Quantity (account, quantity) = quantity
74 -- posting_account (x, _) = x
75 posting_quantity (_, x) = x
78 -- ** Class 'GL_Transaction'
82 , GL_Posting (Transaction_Posting t)
83 , Data (Transaction_Posting t)
84 , NFData (Transaction_Posting t)
85 , Show (Transaction_Posting t)
86 , MT.Element (Transaction_Postings t) ~ Transaction_Posting t
87 , MT.MonoFoldable (Transaction_Postings t)
88 , Data (GL_Transaction_Line t)
89 , Show (GL_Transaction_Line t)
90 , NFData (GL_Transaction_Line t)
91 ) => GL_Transaction t where
92 type GL_Transaction_Line t
93 gl_transaction_line :: t -> GL_Transaction_Line t
94 -- gl_transaction_postings_filter :: (Transaction_Posting t -> Bool) -> t -> t
96 {- NOTE: not needed so far.
102 , account ~ Posting_Account posting
103 ) => GL_Transaction (Date, Map account ([] posting))
105 type Transaction_Posting (Date, Map account ([] posting)) = posting
106 type Transaction_Postings (Date, Map account ([] posting)) = Compose (Map account) []
107 transaction_date = fst
108 transaction_postings = Compose . snd
109 gl_transaction_postings_filter f =
112 (\p -> case filter f p of
122 ) => GL_Transaction (Date, [posting])
124 type GL_Transaction_Line (Date, [posting]) = (Date, [posting])
125 gl_transaction_line = id
126 -- gl_transaction_postings_filter = fmap . List.filter
130 newtype GL_Transaction t
132 = GL (TreeMap (Account_Section (Posting_Account (Transaction_Posting t)))
133 (Map Date (Seq (GL_Line t))))
134 deriving instance -- Data
140 => Monoid (GL t) where
146 ) => NFData (GL t) where
148 deriving instance -- Show
152 deriving instance -- Typeable
154 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
158 data GL_Line transaction
160 { gl_line_transaction :: GL_Transaction_Line transaction
161 , gl_line_posting :: Transaction_Posting transaction
162 , gl_line_sum :: GL_Posting_Quantity (Transaction_Posting transaction)
164 deriving instance -- Data
169 ) => Data (GL_Line t)
173 ) => NFData (GL_Line t) where
175 rnf gl_line_transaction `seq`
176 rnf gl_line_posting `seq`
178 deriving instance -- Show
180 ) => Show (GL_Line t)
181 deriving instance -- Typeable
183 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
188 :: GL_Transaction transaction
192 -- | Return the given 'GL'
193 -- updated by the given 'GL_Transaction'.
195 -- NOTE: to reduce memory consumption when applying 'gl_cons' iteratively,
196 -- the given 'GL' is matched strictly.
198 :: GL_Transaction transaction
208 { gl_line_transaction = gl_transaction_line t
209 , gl_line_posting = p
210 , gl_line_sum = gl_posting_quantity p
213 Map.singleton (transaction_date t) $
214 Seq.singleton first_line in
217 let (nlt, leq, neq, ngt) =
218 case Map.splitLookup (transaction_date t) old of
219 (olt, Nothing, ogt) | Map.null olt ->
220 (olt, first_line, Seq.singleton first_line, ogt)
221 (olt, Nothing, ogt) ->
223 case Seq.viewr $ snd $ Map.findMax olt of
224 (_:>GL_Line{gl_line_sum = s}) ->
225 first_line{gl_line_sum = quantity_add s $ gl_posting_quantity p}
227 in (olt, line, Seq.singleton line, ogt)
228 (olt, Just oeq, ogt) ->
229 case Seq.viewr oeq of
230 (_:>GL_Line{gl_line_sum = s}) ->
231 let line = first_line{gl_line_sum = quantity_add s $ gl_posting_quantity p} in
232 (olt, line, oeq |> line, ogt)
233 _ -> (olt, first_line, Seq.singleton first_line, ogt)
236 Map.insert (transaction_date t) neq $
238 (fmap (\l -> l{gl_line_sum =
239 quantity_add (gl_line_sum leq) $ gl_line_sum l}))
242 (account_path $ posting_account p)
246 (transaction_postings t)
249 :: GL_Transaction transaction
253 gl_union (GL x) (GL y) =
254 GL $ TreeMap.union (Map.unionWith mappend) x y
256 -- * Type 'GL_Expanded'
258 -- | Descending propagation of 'Amount's accross 'Account's.
259 newtype GL_Transaction transaction
260 => GL_Expanded transaction
261 = GL_Expanded (TreeMap (Account_Section (Posting_Account (Transaction_Posting transaction)))
262 (GL_Expanded_Line transaction))
264 -- ** Type 'GL_Expanded_Line'
267 -- * 'Strict.exclusive': contains the original 'GL_Line's.
268 -- * 'Strict.inclusive': contains 'quantity_add' folded
269 -- over 'Strict.exclusive' and 'Strict.inclusive'
270 -- of 'TreeMap.node_descendants'
271 type GL_Expanded_Line transaction
272 = Strict.Clusive (Map Date (Seq (GL_Line transaction)))
274 -- | Return the given 'GL' with:
276 -- * all missing 'Account.parent' 'Account's inserted;
277 -- * and every mapped 'GL_Line'
278 -- added with any 'GL_Line'
279 -- of the 'Account's for which it is 'Account.parent'.
281 :: GL_Transaction transaction
283 -> GL_Expanded transaction
284 gl_expanded (GL gl) =
286 Strict.fromMaybe (assert False undefined) .
287 TreeMap.node_value in
289 TreeMap.map_by_depth_first
290 (\(TreeMap nodes) value ->
291 let exclusive = Strict.fromMaybe Map.empty value in
297 Traversable.mapAccumL -- NOTE: recalc 'gl_line_sum's
299 let amts = gl_posting_quantity $ gl_line_posting line in
301 Nothing -> (Just amts, line)
303 let new_sum = quantity_add last_sum amts in
305 , line{gl_line_sum=new_sum} )
309 (Map.unionWith (flip (><)) . Strict.inclusive . from_value)