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 -- General Ledger
13 -- import Control.Applicative (Const(..))
14 import Control.Exception (assert)
16 import qualified Data.Foldable
17 -- import Data.Foldable (Foldable)
18 import Data.Functor.Compose (Compose(..))
19 import qualified Data.Sequence
20 import Data.Sequence (Seq, (><), (|>), ViewR(..))
21 import qualified Data.Strict.Maybe as Strict
22 import qualified Data.Traversable
23 import qualified Data.Map.Strict as Data.Map
24 import Data.Map.Strict (Map)
25 import Data.Typeable ()
27 import qualified Hcompta.Account as Account
28 import Hcompta.Account (Account)
29 import Hcompta.Date (Date)
30 -- import Hcompta.Lib.Consable
31 import qualified Hcompta.Lib.TreeMap as TreeMap
32 import Hcompta.Lib.TreeMap (TreeMap)
34 -- * Requirements' interface
38 ( Data (Amount_Unit a)
42 , Show (Amount_Unit a)
44 , Typeable (Amount_Unit a)
47 amount_add :: a -> a -> a
51 -- | A 'posting' used to produce a 'GL'
52 -- must be an instance of this class.
53 class Amount (Posting_Amount p)
56 posting_account :: p -> Account
57 posting_amount :: p -> Posting_Amount p
59 instance (Amount amount)
60 => Posting (Account, amount)
62 type Posting_Amount (Account, amount) = amount
63 posting_account (x, _) = x
64 posting_amount (_, x) = x
66 -- ** Class 'Transaction'
69 ( Posting (Transaction_Posting t)
70 , Data (Transaction_Posting t)
71 , Eq (Transaction_Posting t)
72 , Show (Transaction_Posting t)
73 , Foldable (Transaction_Postings t)
74 ) => Transaction t where
75 type Transaction_Posting t
76 type Transaction_Postings t :: * -> *
77 transaction_date :: t -> Date
78 transaction_postings :: t -> Transaction_Postings t (Transaction_Posting t)
79 transaction_postings_filter :: (Transaction_Posting t -> Bool) -> t -> t
81 {- NOTE: not needed so far.
87 ) => Transaction (Date, Map Account ([] posting))
89 type Transaction_Posting (Date, Map Account ([] posting)) = posting
90 type Transaction_Postings (Date, Map Account ([] posting)) = Compose (Map Account) []
91 transaction_date = fst
92 transaction_postings = Compose . snd
93 transaction_postings_filter f =
96 (\p -> case filter f p of
105 ) => Transaction (Date, [posting])
107 type Transaction_Posting (Date, [posting]) = posting
108 type Transaction_Postings (Date, [posting]) = []
109 transaction_date = fst
110 transaction_postings = snd
111 transaction_postings_filter = fmap . filter
115 newtype Transaction transaction
117 = GL (TreeMap Account.Name (Map Date (Seq (GL_Line transaction))))
118 deriving instance ( Transaction transaction
120 , Typeable transaction
122 ) => Data (GL transaction)
123 deriving instance ( Transaction transaction
125 ) => Eq (GL transaction)
126 deriving instance ( Transaction transaction
128 ) => Show (GL transaction)
129 deriving instance Typeable1 GL
130 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
131 instance Transaction transaction
132 => Monoid (GL transaction) where
137 Transaction transaction
138 => GL_Line transaction
140 { gl_line_transaction :: transaction
141 , gl_line_posting :: Transaction_Posting transaction
142 , gl_line_sum :: Posting_Amount (Transaction_Posting transaction)
144 deriving instance ( Transaction transaction
146 , Typeable transaction
148 ) => Data (GL_Line transaction)
149 deriving instance ( Transaction transaction
151 ) => Eq (GL_Line transaction)
152 deriving instance ( Transaction transaction
154 ) => Show (GL_Line transaction)
155 deriving instance Typeable1 GL_Line
156 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
161 :: Transaction transaction
165 -- | Return the given 'GL'
166 -- updated by the given 'Transaction'.
168 -- NOTE: to reduce memory consumption when 'cons'ing iteratively,
169 -- the given 'GL' is matched strictly.
171 :: Transaction transaction
181 { gl_line_transaction = t
182 , gl_line_posting = p
183 , gl_line_sum = posting_amount p
186 Data.Map.singleton (transaction_date t) $
187 Data.Sequence.singleton first_line in
190 let (nlt, leq, neq, ngt) =
191 case Data.Map.splitLookup (transaction_date t) old of
192 (olt, Nothing, ogt) | Data.Map.null olt ->
193 (olt, first_line, Data.Sequence.singleton first_line, ogt)
194 (olt, Nothing, ogt) ->
196 case Data.Sequence.viewr $ snd $ Data.Map.findMax olt of
197 (_:>GL_Line{gl_line_sum = s}) ->
198 first_line{gl_line_sum = amount_add s $ posting_amount p}
200 in (olt, line, Data.Sequence.singleton line, ogt)
201 (olt, Just oeq, ogt) ->
202 case Data.Sequence.viewr oeq of
203 (_:>GL_Line{gl_line_sum = s}) ->
204 let line = first_line{gl_line_sum = amount_add s $ posting_amount p} in
205 (olt, line, oeq |> line, ogt)
206 _ -> (olt, first_line, Data.Sequence.singleton first_line, ogt)
209 Data.Map.insert (transaction_date t) neq $
211 (fmap (\l -> l{gl_line_sum =
212 amount_add (gl_line_sum leq) $ gl_line_sum l}))
219 (transaction_postings t)
222 :: Transaction transaction
226 union (GL gl0) (GL gl1) =
229 (Data.Map.unionWith mappend)
234 -- | Descending propagation of 'Amount's accross 'Account's.
235 type Expanded transaction
236 = TreeMap Account.Name (GL_Line_Expanded transaction)
237 data Transaction transaction
238 => GL_Line_Expanded transaction
240 { exclusive :: !(Map Date (Seq (GL_Line transaction)))
241 , inclusive :: !(Map Date (Seq (GL_Line transaction))) -- ^ 'amount_add' folded over 'exclusive' and 'inclusive' of 'Lib.TreeMap.node_descendants'
243 deriving instance ( Transaction transaction
245 ) => Data (GL_Line_Expanded transaction)
246 deriving instance ( Transaction transaction
248 ) => Eq (GL_Line_Expanded transaction)
249 deriving instance ( Transaction transaction
251 ) => Show (GL_Line_Expanded transaction)
252 deriving instance Typeable1 GL_Line_Expanded
253 -- FIXME: use 'Typeable' when dropping GHC-7.6 support
255 -- | Return the given 'GL' with:
257 -- * all missing 'Account.ascending' 'Account's inserted,
259 -- * and every mapped 'GL_Line'
260 -- added with any 'GL_Line'
261 -- of the 'Account's for which it is 'Account.ascending'.
263 :: Transaction transaction
265 -> Expanded transaction
267 let from_value = Strict.fromMaybe (assert False undefined) . TreeMap.node_value in
268 TreeMap.map_by_depth_first
269 (\descendants value ->
270 let nodes = TreeMap.nodes descendants in
271 let exclusive = Strict.fromMaybe Data.Map.empty value in
277 Data.Traversable.mapAccumL
279 let pamt = posting_amount $ gl_line_posting line in
281 Nothing -> (Just pamt, line)
283 let ls = amount_add s pamt in
285 , line{gl_line_sum=ls} )
289 (Data.Map.unionWith (flip (><)) . inclusive . from_value)