1 {-# LANGUAGE DeriveDataTypeable #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE FlexibleInstances #-}
4 {-# LANGUAGE MultiParamTypeClasses #-}
5 {-# LANGUAGE StandaloneDeriving #-}
6 {-# LANGUAGE TupleSections #-}
7 {-# LANGUAGE TypeFamilies #-}
8 module Hcompta.Filter where
10 import Prelude hiding (filter)
11 import Control.Applicative (pure, (<$>), (<*>))
13 import qualified Data.Fixed
14 import qualified Data.Foldable
15 import Data.Foldable (Foldable(..))
16 import qualified Data.Functor.Compose
17 import Data.Traversable (Traversable(..))
18 import qualified Data.Monoid
19 import Data.Monoid (Monoid(..))
20 import Data.Typeable ()
21 import Data.Text (Text)
22 -- import qualified Data.Text as Text
23 import qualified Data.Map.Strict as Data.Map
24 import Data.Map.Strict (Map)
25 import Text.Regex.TDFA ()
26 import Text.Regex.Base ()
27 import Text.Regex.TDFA.Text ()
29 import qualified Data.List.NonEmpty as NonEmpty
30 -- import Data.List.NonEmpty (NonEmpty(..))
31 import qualified Hcompta.Lib.Regex as Regex
32 import Hcompta.Lib.Regex (Regex)
33 import qualified Hcompta.Amount as Amount
34 import qualified Hcompta.Amount.Unit as Amount.Unit
35 import qualified Hcompta.Date as Date
36 import Hcompta.Date (Date)
37 import qualified Hcompta.Account as Account
38 import Hcompta.Account (Account)
39 -- import qualified Hcompta.Date as Date
40 import qualified Hcompta.Balance as Balance
41 import qualified Hcompta.GL as GL
43 -- * Requirements' interface
48 unit_text :: a -> Text
50 instance Unit Amount.Unit where
51 unit_text = Amount.Unit.text
53 instance Unit Text where
59 ( Ord (Amount_Quantity a)
60 , Show (Amount_Quantity a)
61 , Show (Amount_Unit a)
62 , Unit (Amount_Unit a)
66 type Amount_Quantity a
67 amount_unit :: a -> Amount_Unit a
68 amount_quantity :: a -> Amount_Quantity a
70 instance Amount Amount.Amount where
71 type Amount_Unit Amount.Amount = Amount.Unit
72 type Amount_Quantity Amount.Amount = Amount.Quantity
73 amount_quantity = Amount.quantity
74 amount_unit = Amount.unit
76 instance (Amount a, GL.Amount a)
77 => Amount (Amount.Sum a) where
78 type Amount_Unit (Amount.Sum a) = Amount_Unit a
79 type Amount_Quantity (Amount.Sum a) = Amount_Quantity a
80 amount_quantity = amount_quantity . Amount.sum_balance
81 amount_unit = amount_unit . Amount.sum_balance
85 class Amount (Posting_Amount p)
88 posting_account :: p -> Account
89 posting_amounts :: p -> Map (Amount_Unit (Posting_Amount p)) (Posting_Amount p)
91 -- ** Class 'Transaction'
93 class Posting (Transaction_Posting t)
94 => Transaction t where
95 type Transaction_Posting t
96 transaction_date :: t -> Date
97 transaction_description :: t -> Text
98 transaction_postings :: t -> Map Account [Transaction_Posting t]
99 transaction_tags :: t -> Map Text [Text]
101 -- ** Class 'Balance'
103 class Amount (Balance_Amount b)
105 type Balance_Amount b
106 balance_account :: b -> Account
107 balance_amount :: b -> Balance_Amount b
108 balance_positive :: b -> Maybe (Balance_Amount b)
109 balance_negative :: b -> Maybe (Balance_Amount b)
111 instance (Amount a, Balance.Amount a)
112 => Balance (Account, Amount.Sum a) where
113 type Balance_Amount (Account, Amount.Sum a) = a
114 balance_account = fst
115 balance_amount (_, amt) =
117 Amount.Sum_Negative n -> n
118 Amount.Sum_Positive p -> p
119 Amount.Sum_Both n p -> Balance.amount_add n p
120 balance_positive = Amount.sum_positive . snd
121 balance_negative = Amount.sum_negative . snd
125 class Amount (GL_Amount r)
128 gl_account :: r -> Account
130 gl_amount_positive :: r -> Maybe (GL_Amount r)
131 gl_amount_negative :: r -> Maybe (GL_Amount r)
132 gl_amount_balance :: r -> GL_Amount r
133 gl_sum_positive :: r -> Maybe (GL_Amount r)
134 gl_sum_negative :: r -> Maybe (GL_Amount r)
135 gl_sum_balance :: r -> GL_Amount r
137 instance (Amount a, GL.Amount a)
138 => GL (Account, Date, Amount.Sum a, Amount.Sum a) where
139 type GL_Amount (Account, Date, Amount.Sum a, Amount.Sum a) = a
140 gl_account (x, _, _, _) = x
141 gl_date (_, x, _, _) = x
142 gl_amount_positive (_, _, x, _) = Amount.sum_positive x
143 gl_amount_negative (_, _, x, _) = Amount.sum_negative x
144 gl_amount_balance (_, _, x, _) = Amount.sum_balance x
145 gl_sum_positive (_, _, _, x) = Amount.sum_positive x
146 gl_sum_negative (_, _, _, x) = Amount.sum_negative x
147 gl_sum_balance (_, _, _, x) = Amount.sum_balance x
152 test :: p -> x -> Bool
155 :: (Foldable t, Test p x, Monoid x)
158 Data.Foldable.foldMap
159 (\x -> if test p x then x else mempty)
161 -- ** Type 'Test_Text'
165 | Test_Text_Exact Text
166 | Test_Text_Regex Regex
167 deriving (Eq, Show, Typeable)
169 instance Test Test_Text Text where
172 Test_Text_Any -> True
173 Test_Text_Exact m -> (==) m x
174 Test_Text_Regex m -> Regex.match m x
176 -- ** Type 'Test_Ord'
186 deriving (Data, Eq, Show, Typeable)
188 instance (Ord o, o ~ x)
189 => Test (Test_Ord o) x where
192 Test_Ord_Lt o -> (<) x o
193 Test_Ord_Le o -> (<=) x o
194 Test_Ord_Gt o -> (>) x o
195 Test_Ord_Ge o -> (>=) x o
196 Test_Ord_Eq o -> (==) x o
199 -- ** Type 'Test_Range'
203 | Test_Range_In (Maybe a) (Maybe a)
206 test_range_all :: Test_Range a
208 Test_Range_In Nothing Nothing
210 instance (Ord o, o ~ x)
211 => Test (Test_Range o) x where
214 Test_Range_Eq o -> (==) x o
215 Test_Range_In (Just a0) (Just a1) -> (<=) a0 x && (<=) x a1
216 Test_Range_In Nothing (Just a1) -> (<=) x a1
217 Test_Range_In (Just a0) Nothing -> (<=) a0 x
218 Test_Range_In Nothing Nothing -> True
219 instance Functor Test_Range where
220 fmap f (Test_Range_Eq a) = Test_Range_Eq (f a)
221 fmap f (Test_Range_In a0 a1) = Test_Range_In (fmap f a0) (fmap f a1)
223 -- ** Type 'Test_Num_Abs'
227 = Test_Num_Abs (Test_Ord n)
228 deriving (Data, Eq, Show, Typeable)
230 instance (Num n, Ord x, n ~ x)
231 => Test (Test_Num_Abs n) x where
232 test (Test_Num_Abs f) x = test f (abs x)
234 -- ** Type 'Test_Bool'
240 | And (Test_Bool p) (Test_Bool p)
241 | Or (Test_Bool p) (Test_Bool p)
243 deriving instance Eq p => Eq (Test_Bool p)
244 instance Functor Test_Bool where
246 fmap f (Bool x) = Bool (f x)
247 fmap f (Not t) = Not (fmap f t)
248 fmap f (And t0 t1) = And (fmap f t0) (fmap f t1)
249 fmap f (Or t0 t1) = Or (fmap f t0) (fmap f t1)
250 instance Foldable Test_Bool where
251 foldr _ acc Any = acc
252 foldr f acc (Bool p) = f p acc
253 foldr f acc (Not t) = Data.Foldable.foldr f acc t
254 foldr f acc (And t0 t1) = Data.Foldable.foldr f (Data.Foldable.foldr f acc t0) t1
255 foldr f acc (Or t0 t1) = Data.Foldable.foldr f (Data.Foldable.foldr f acc t0) t1
256 instance Traversable Test_Bool where
257 traverse _ Any = pure Any
258 traverse f (Bool x) = Bool <$> f x
259 traverse f (Not t) = Not <$> traverse f t
260 traverse f (And t0 t1) = And <$> traverse f t0 <*> traverse f t1
261 traverse f (Or t0 t1) = Or <$> traverse f t0 <*> traverse f t1
262 instance Test p x => Test (Test_Bool p) x where
264 test (Bool p) x = test p x
265 test (Not t) x = not $ test t x
266 test (And t0 t1) x = test t0 x && test t1 x
267 test (Or t0 t1) x = test t0 x || test t1 x
269 bool :: Test p x => Test_Bool p -> x -> Bool
271 bool (Bool p) x = test p x
272 bool (Not t) x = not $ test t x
273 bool (And t0 t1) x = test t0 x && test t1 x
274 bool (Or t0 t1) x = test t0 x || test t1 x
276 -- ** Type 'Test_Unit'
279 = Test_Unit Test_Text
280 deriving (Eq, Show, Typeable)
282 instance Unit u => Test Test_Unit u where
283 test (Test_Unit f) = test f . unit_text
285 -- ** Type 'Test_Account'
288 = [Test_Account_Section]
290 data Test_Account_Section
291 = Test_Account_Section_Any
292 | Test_Account_Section_Many
293 | Test_Account_Section_Text Test_Text
294 deriving (Eq, Show, Typeable)
296 instance Test Test_Account Account where
298 comp f (NonEmpty.toList acct)
300 comp :: [Test_Account_Section] -> [Account.Name] -> Bool
302 comp [Test_Account_Section_Many] _ = True
307 Test_Account_Section_Any -> True
308 Test_Account_Section_Many -> True
309 Test_Account_Section_Text m -> test m n
311 comp so@(s:ss) no@(n:ns) =
313 Test_Account_Section_Any -> comp ss ns
314 Test_Account_Section_Many -> comp ss no || comp so ns
315 Test_Account_Section_Text m -> test m n && comp ss ns
318 -- ** Type 'Test_Amount'
326 { test_amount_quantity :: Test_Quantity (Amount_Quantity a)
327 , test_amount_unit :: Test_Unit
328 } deriving (Typeable)
329 deriving instance Amount a => Eq (Test_Amount a)
330 deriving instance Amount a => Show (Test_Amount a)
333 => Test (Test_Amount a) a where
334 test (Test_Amount fq fu) amt =
335 test fu (amount_unit amt) &&
336 test fq (amount_quantity amt)
338 -- ** Type 'Test_Date'
341 = Test_Date_UTC (Test_Ord Date)
342 | Test_Date_Year (Test_Range Integer)
343 | Test_Date_Month (Test_Range Int)
344 | Test_Date_DoM (Test_Range Int)
345 | Test_Date_Hour (Test_Range Int)
346 | Test_Date_Minute (Test_Range Int)
347 | Test_Date_Second (Test_Range Data.Fixed.Pico)
349 deriving instance Show (Test_Date)
351 instance Test Test_Date Date where
352 test (Test_Date_UTC f) d = test f d
353 test (Test_Date_Year f) d = test f $ Date.year d
354 test (Test_Date_Month f) d = test f $ Date.month d
355 test (Test_Date_DoM f) d = test f $ Date.dom d
356 test (Test_Date_Hour f) d = test f $ Date.hour d
357 test (Test_Date_Minute f) d = test f $ Date.minute d
358 test (Test_Date_Second f) d = test f $ Date.second d
360 -- ** Type 'Test_Tag'
363 = Test_Tag_Name Test_Text
364 | Test_Tag_Value Test_Text
366 deriving instance Show (Test_Tag)
368 instance Test Test_Tag (Text, Text) where
369 test (Test_Tag_Name f) (x, _) = test f x
370 test (Test_Tag_Value f) (_, x) = test f x
372 -- ** Type 'Test_Posting'
375 => Test_Posting posting
376 = Test_Posting_Account Test_Account
377 | Test_Posting_Amount (Test_Amount (Posting_Amount posting))
378 | Test_Posting_Unit Test_Unit
381 -- Description Comp_String String
383 -- Account_Tag Comp_String String (Maybe (Comp_String, String))
384 -- Account_Balance Comp_Num Comp_Num_Absolute Amount
385 -- Depth Comp_Num Int
389 -- Tag Comp_String Tag.Name (Maybe (Comp_String, Tag.Value))
390 deriving instance Posting p => Eq (Test_Posting p)
391 deriving instance Posting p => Show (Test_Posting p)
394 => Test (Test_Posting p) p where
395 test (Test_Posting_Account f) p =
396 test f $ posting_account p
397 test (Test_Posting_Amount f) p =
398 Data.Foldable.any (test f) $ posting_amounts p
399 test (Test_Posting_Unit f) p =
400 Data.Foldable.any (test f . amount_unit) $ posting_amounts p
402 newtype Cross t = Cross t
403 instance (Transaction t, Transaction_Posting t ~ p, Posting p)
404 => Test (Test_Transaction t) (Cross p) where
407 (Test_Transaction_Description _) -> True
408 (Test_Transaction_Posting f) -> test f p
409 (Test_Transaction_Date _) -> True -- TODO: use posting_date
410 (Test_Transaction_Tag _) -> False -- TODO: use posting_tags
412 -- ** Type 'Test_Transaction'
415 => Test_Transaction t
416 = Test_Transaction_Description Test_Text
417 | Test_Transaction_Posting (Test_Posting (Transaction_Posting t))
418 | Test_Transaction_Date (Test_Bool Test_Date)
419 | Test_Transaction_Tag (Test_Bool Test_Tag)
421 deriving instance Transaction t => Show (Test_Transaction t)
423 instance Transaction t
424 => Test (Test_Transaction t) t where
425 test (Test_Transaction_Description f) t =
426 test f $ transaction_description t
427 test (Test_Transaction_Posting f) t =
428 Data.Foldable.any (test f) $
429 Data.Functor.Compose.Compose $
430 transaction_postings t
431 test (Test_Transaction_Date f) t =
432 test f $ transaction_date t
433 test (Test_Transaction_Tag f) t =
435 Data.Map.foldrWithKey
436 (\n -> mappend . Data.Monoid.Any .
437 Data.Foldable.any (test f . (n,)))
438 (Data.Monoid.Any False) $
441 -- ** Type 'Test_Balance'
445 = Test_Balance_Account Test_Account
446 | Test_Balance_Amount (Test_Amount (Balance_Amount b))
447 | Test_Balance_Positive (Test_Amount (Balance_Amount b))
448 | Test_Balance_Negative (Test_Amount (Balance_Amount b))
450 deriving instance Balance b => Eq (Test_Balance b)
451 deriving instance Balance b => Show (Test_Balance b)
454 => Test (Test_Balance b) b where
455 test (Test_Balance_Account f) b =
456 test f $ balance_account b
457 test (Test_Balance_Amount f) b =
458 test f $ balance_amount b
459 test (Test_Balance_Positive f) b =
460 Data.Foldable.any (test f) $
462 test (Test_Balance_Negative f) b =
463 Data.Foldable.any (test f) $
470 = Test_GL_Account Test_Account
471 | Test_GL_Amount_Positive (Test_Amount (GL_Amount r))
472 | Test_GL_Amount_Negative (Test_Amount (GL_Amount r))
473 | Test_GL_Amount_Balance (Test_Amount (GL_Amount r))
474 | Test_GL_Sum_Positive (Test_Amount (GL_Amount r))
475 | Test_GL_Sum_Negative (Test_Amount (GL_Amount r))
476 | Test_GL_Sum_Balance (Test_Amount (GL_Amount r))
478 deriving instance GL r => Eq (Test_GL r)
479 deriving instance GL r => Show (Test_GL r)
482 => Test (Test_GL r) r where
483 test (Test_GL_Account f) r =
484 test f $ gl_account r
485 test (Test_GL_Amount_Positive f) r =
486 Data.Foldable.any (test f) $
488 test (Test_GL_Amount_Negative f) r =
489 Data.Foldable.any (test f) $
491 test (Test_GL_Amount_Balance f) r =
492 test f $ gl_amount_balance r
493 test (Test_GL_Sum_Positive f) r =
494 Data.Foldable.any (test f) $
496 test (Test_GL_Sum_Negative f) r =
497 Data.Foldable.any (test f) $
499 test (Test_GL_Sum_Balance f) r =
500 test f $ gl_sum_balance r