1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE DeriveDataTypeable #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE FlexibleInstances #-}
5 {-# LANGUAGE MultiParamTypeClasses #-}
6 {-# LANGUAGE NamedFieldPuns #-}
7 {-# LANGUAGE StandaloneDeriving #-}
8 {-# LANGUAGE TypeFamilies #-}
9 {-# OPTIONS_GHC -fno-warn-deprecations #-} -- FIXME: to be removed when dropping GHC-7.6 support
11 module Hcompta.GL where
13 -- import Control.Applicative (Const(..))
14 import Control.Exception (assert)
17 import Data.Eq (Eq(..))
18 import qualified Data.Foldable
19 import Data.Foldable (Foldable(..))
20 import Data.Functor (Functor(..))
21 import Data.Functor.Compose (Compose(..))
22 import Data.List (filter)
23 import Data.Map.Strict (Map)
24 import qualified Data.Map.Strict as Data.Map
25 import Data.Maybe (Maybe(..))
26 import Data.Monoid (Monoid(..))
27 import Data.Ord (Ord(..))
28 import qualified Data.Sequence
29 import Data.Sequence (Seq, (><), (|>), ViewR(..))
30 import qualified Data.Strict.Maybe as Strict
31 import qualified Data.Traversable
32 import Data.Tuple (fst, snd)
33 import Data.Typeable ()
34 import Prelude (($), (.), flip, undefined)
35 import Text.Show (Show(..))
37 import qualified Hcompta.Account as Account
38 import Hcompta.Account (Account)
39 import Hcompta.Date (Date)
40 -- import Hcompta.Lib.Consable
41 import qualified Hcompta.Lib.TreeMap as TreeMap
42 import Hcompta.Lib.TreeMap (TreeMap)
44 -- * Requirements' interface
48 ( Data (Amount_Unit a)
52 , Show (Amount_Unit a)
54 , Typeable (Amount_Unit a)
57 amount_add :: a -> a -> a
61 -- | A 'posting' used to produce a 'GL'
62 -- must be an instance of this class.
63 class Amount (Posting_Amount p)
66 posting_account :: p -> Account
67 posting_amount :: p -> Posting_Amount p
69 instance (Amount amount)
70 => Posting (Account, amount)
72 type Posting_Amount (Account, amount) = amount
73 posting_account (x, _) = x
74 posting_amount (_, x) = x
76 -- ** Class 'Transaction'
79 ( Posting (Transaction_Posting t)
80 , Data (Transaction_Posting t)
81 , Eq (Transaction_Posting t)
82 , Show (Transaction_Posting t)
83 , Foldable (Transaction_Postings t)
84 ) => Transaction t where
85 type Transaction_Posting t
86 type Transaction_Postings t :: * -> *
87 transaction_date :: t -> Date
88 transaction_postings :: t -> Transaction_Postings t (Transaction_Posting t)
89 transaction_postings_filter :: (Transaction_Posting t -> Bool) -> t -> t
91 {- NOTE: not needed so far.
97 ) => Transaction (Date, Map Account ([] posting))
99 type Transaction_Posting (Date, Map Account ([] posting)) = posting
100 type Transaction_Postings (Date, Map Account ([] posting)) = Compose (Map Account) []
101 transaction_date = fst
102 transaction_postings = Compose . snd
103 transaction_postings_filter f =
106 (\p -> case filter f p of
115 ) => Transaction (Date, [posting])
117 type Transaction_Posting (Date, [posting]) = posting
118 type Transaction_Postings (Date, [posting]) = []
119 transaction_date = fst
120 transaction_postings = snd
121 transaction_postings_filter = fmap . filter
125 newtype Transaction transaction
127 = GL (TreeMap Account.Name (Map Date (Seq (GL_Line transaction))))
128 deriving instance ( Transaction transaction
130 , Typeable transaction
132 ) => Data (GL transaction)
133 deriving instance ( Transaction transaction
135 ) => Eq (GL transaction)
136 deriving instance ( Transaction transaction
138 ) => Show (GL transaction)
139 deriving instance Typeable1 GL
140 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
141 instance Transaction transaction
142 => Monoid (GL transaction) where
147 Transaction transaction
148 => GL_Line transaction
150 { gl_line_transaction :: transaction
151 , gl_line_posting :: Transaction_Posting transaction
152 , gl_line_sum :: Posting_Amount (Transaction_Posting transaction)
154 deriving instance ( Transaction transaction
156 , Typeable transaction
158 ) => Data (GL_Line transaction)
159 deriving instance ( Transaction transaction
161 ) => Eq (GL_Line transaction)
162 deriving instance ( Transaction transaction
164 ) => Show (GL_Line transaction)
165 deriving instance Typeable1 GL_Line
166 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
171 :: Transaction transaction
175 -- | Return the given 'GL'
176 -- updated by the given 'Transaction'.
178 -- NOTE: to reduce memory consumption when 'cons'ing iteratively,
179 -- the given 'GL' is matched strictly.
181 :: Transaction transaction
191 { gl_line_transaction = t
192 , gl_line_posting = p
193 , gl_line_sum = posting_amount p
196 Data.Map.singleton (transaction_date t) $
197 Data.Sequence.singleton first_line in
200 let (nlt, leq, neq, ngt) =
201 case Data.Map.splitLookup (transaction_date t) old of
202 (olt, Nothing, ogt) | Data.Map.null olt ->
203 (olt, first_line, Data.Sequence.singleton first_line, ogt)
204 (olt, Nothing, ogt) ->
206 case Data.Sequence.viewr $ snd $ Data.Map.findMax olt of
207 (_:>GL_Line{gl_line_sum = s}) ->
208 first_line{gl_line_sum = amount_add s $ posting_amount p}
210 in (olt, line, Data.Sequence.singleton line, ogt)
211 (olt, Just oeq, ogt) ->
212 case Data.Sequence.viewr oeq of
213 (_:>GL_Line{gl_line_sum = s}) ->
214 let line = first_line{gl_line_sum = amount_add s $ posting_amount p} in
215 (olt, line, oeq |> line, ogt)
216 _ -> (olt, first_line, Data.Sequence.singleton first_line, ogt)
219 Data.Map.insert (transaction_date t) neq $
221 (fmap (\l -> l{gl_line_sum =
222 amount_add (gl_line_sum leq) $ gl_line_sum l}))
229 (transaction_postings t)
232 :: Transaction transaction
236 union (GL gl0) (GL gl1) =
239 (Data.Map.unionWith mappend)
244 -- | Descending propagation of 'Amount's accross 'Account's.
245 type Expanded transaction
246 = TreeMap Account.Name (GL_Line_Expanded transaction)
247 data Transaction transaction
248 => GL_Line_Expanded transaction
250 { exclusive :: !(Map Date (Seq (GL_Line transaction)))
251 , inclusive :: !(Map Date (Seq (GL_Line transaction))) -- ^ 'amount_add' folded over 'exclusive' and 'inclusive' of 'Lib.TreeMap.node_descendants'
253 deriving instance ( Transaction transaction
255 ) => Data (GL_Line_Expanded transaction)
256 deriving instance ( Transaction transaction
258 ) => Eq (GL_Line_Expanded transaction)
259 deriving instance ( Transaction transaction
261 ) => Show (GL_Line_Expanded transaction)
262 deriving instance Typeable1 GL_Line_Expanded
263 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
265 -- | Return the given 'GL' with:
267 -- * all missing 'Account.ascending' 'Account's inserted,
269 -- * and every mapped 'GL_Line'
270 -- added with any 'GL_Line'
271 -- of the 'Account's for which it is 'Account.ascending'.
273 :: Transaction transaction
275 -> Expanded transaction
277 let from_value = Strict.fromMaybe (assert False undefined) . TreeMap.node_value in
278 TreeMap.map_by_depth_first
279 (\descendants value ->
280 let nodes = TreeMap.nodes descendants in
281 let exclusive = Strict.fromMaybe Data.Map.empty value in
287 Data.Traversable.mapAccumL
289 let pamt = posting_amount $ gl_line_posting line in
291 Nothing -> (Just pamt, line)
293 let ls = amount_add s pamt in
295 , line{gl_line_sum=ls} )
299 (Data.Map.unionWith (flip (><)) . inclusive . from_value)