1 {-# LANGUAGE NamedFieldPuns #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE ScopedTypeVariables #-}
4 {-# LANGUAGE TupleSections #-}
5 module Hcompta.CLI.Command.GL where
7 import Control.Monad.IO.Class (liftIO)
8 import Control.Monad.Trans.Except (runExceptT)
9 import qualified Data.Either
10 import qualified Data.Foldable
11 import Data.Functor.Compose (Compose(..))
12 import qualified Data.List
13 import qualified Data.Map.Strict as Data.Map
14 import qualified Data.Sequence
15 import qualified Data.Text as Text
16 import qualified Data.Text.Lazy as TL
17 import Prelude hiding (foldr)
18 import System.Console.GetOpt
23 import System.Environment as Env (getProgName)
24 import System.Exit (exitWith, ExitCode(..))
25 import qualified System.IO as IO
27 import Hcompta.Account (Account)
28 import Hcompta.Amount (Amount)
29 import qualified Hcompta.Amount as Amount
30 import qualified Hcompta.Amount.Write as Amount.Write
31 import qualified Hcompta.CLI.Args as Args
32 import qualified Hcompta.CLI.Context as Context
33 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
34 import qualified Hcompta.CLI.Lang as Lang
35 import qualified Hcompta.CLI.Lib.Leijen.Table as Table
36 import qualified Hcompta.CLI.Write as Write
37 import Hcompta.Date (Date)
38 import qualified Hcompta.Date.Write as Date.Write
39 import qualified Hcompta.Filter as Filter
40 import qualified Hcompta.Filter.Read as Filter.Read
41 import qualified Hcompta.Format.Ledger as Ledger
42 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
43 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
44 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
45 import Hcompta.Lib.Leijen (toDoc, ToDoc(..))
46 import qualified Hcompta.Lib.Leijen as W
47 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
48 import Hcompta.GL (GL(..))
49 import qualified Hcompta.GL as GL
53 { ctx_input :: [FilePath]
54 , ctx_transaction_filter :: Filter.Test_Bool (Filter.Test_Transaction Ledger.Transaction)
55 , ctx_posting_filter :: Filter.Test_Bool (Filter.Test_Posting Ledger.Posting)
56 , ctx_gl_filter :: Filter.Test_Bool (Filter.Test_GL ( Account
59 , Amount.Sum Amount ))
66 , ctx_transaction_filter = Filter.Any
67 , ctx_posting_filter = Filter.Any
68 , ctx_gl_filter = Filter.Any
73 bin <- Env.getProgName
77 , " [-t TRANSACTION_FILTER]"
78 , " [-p POSTING_FILTER]"
80 , " JOURNAL_FILE [...]"
82 , usageInfo "OPTIONS" options
85 options :: Args.Options Ctx
87 [ Option "g" ["gl-filter"]
88 (ReqArg (\s context ctx -> do
90 fmap (Filter.And $ ctx_gl_filter ctx) $
91 liftIO $ Filter.Read.read Filter.Read.test_gl s
93 Left ko -> Write.fatal context $ ko
95 return $ ctx{ctx_gl_filter}) "FILTER")
96 "filter at general ledger level, multiple uses are merged with a logical AND"
98 (NoArg (\_context _ctx -> do
99 usage >>= IO.hPutStr IO.stderr
100 exitWith ExitSuccess))
102 , Option "i" ["input"]
103 (ReqArg (\s _context ctx -> do
104 return $ ctx{ctx_input=s:ctx_input ctx}) "JOURNAL_FILE")
105 "read data from given file, multiple uses merge the data as would a concatenation do"
106 , Option "p" ["posting-filter"]
107 (ReqArg (\s context ctx -> do
108 ctx_posting_filter <-
109 fmap (Filter.And $ ctx_posting_filter ctx) $
110 liftIO $ Filter.Read.read Filter.Read.test_posting s
112 Left ko -> Write.fatal context $ ko
113 Right ok -> return ok
114 return $ ctx{ctx_posting_filter}) "FILTER")
115 "filter at posting level, multiple uses are merged with a logical AND"
116 , Option "t" ["transaction-filter"]
117 (ReqArg (\s context ctx -> do
118 ctx_transaction_filter <-
119 fmap (Filter.And $ ctx_transaction_filter ctx) $
120 liftIO $ Filter.Read.read Filter.Read.test_transaction s
122 Left ko -> Write.fatal context $ ko
123 Right ok -> return ok
124 return $ ctx{ctx_transaction_filter}) "FILTER")
125 "filter at transaction level, multiple uses are merged with a logical AND"
128 run :: Context.Context -> [String] -> IO ()
129 run context args = do
130 (ctx, inputs) <- Args.parse context usage options (nil, args)
132 CLI.Ledger.paths context $ ctx_input ctx ++ inputs
135 liftIO $ runExceptT $ Ledger.Read.file path
137 Left ko -> return $ Left (path, ko)
138 Right ok -> return $ Right ok
139 >>= return . Data.Either.partitionEithers
140 case read_journals of
141 (errs@(_:_), _journals) ->
142 (flip mapM_) errs $ \(_path, err) -> do
143 Write.fatal context $ err
145 Write.debug context $ "transaction_filter: " ++ show (ctx_transaction_filter ctx)
146 Write.debug context $ "posting_filter: " ++ show (ctx_posting_filter ctx)
147 Write.debug context $ "gl_filter: " ++ show (ctx_gl_filter ctx)
148 let gl = ledger_gl ctx journals
149 style_color <- Write.with_color context IO.stdout
150 W.displayIO IO.stdout $
151 W.renderPretty style_color 1.0 maxBound $ do
154 TL.toStrict . W.displayT .
155 W.renderCompact False .
156 toDoc (Context.lang context) in
158 [ Table.column (title Lang.Message_Account) Table.Align_Left
159 , Table.column (title Lang.Message_Date) Table.Align_Left
160 , Table.column (title Lang.Message_Debit) Table.Align_Right
161 , Table.column (title Lang.Message_Credit) Table.Align_Right
162 , Table.column (title Lang.Message_Running_debit) Table.Align_Right
163 , Table.column (title Lang.Message_Running_credit) Table.Align_Right
164 , Table.column (title Lang.Message_Running_balance) Table.Align_Right
165 , Table.column (title Lang.Message_Description) Table.Align_Left
167 write_gl gl (repeat [])
172 -> GL Ledger.Transaction
173 ledger_gl ctx journals =
176 (flip $ Ledger.Journal.fold
178 flip (Data.Foldable.foldl'
180 case Filter.test (ctx_transaction_filter ctx) t of
184 t{ Ledger.transaction_postings =
186 (Data.Foldable.foldMap
188 Data.Map.foldrWithKey
189 (\u a -> (:) p{Ledger.posting_amounts=Data.Map.singleton u a})
191 (Ledger.posting_amounts p)
195 (\ps -> case Data.List.filter
196 (Filter.test $ ctx_posting_filter ctx) ps of
199 (Ledger.transaction_postings t)
203 (Compose $ Ledger.journal_transactions j)
209 Lib.TreeMap.map_Maybe_with_Path
210 (\acct expanded_lines ->
211 case Data.Map.mapMaybeWithKey
213 case Data.Foldable.foldMap
215 { GL.gl_line_transaction = _t
216 , GL.gl_line_posting = p
219 if Filter.test (ctx_gl_filter ctx)
222 , Amount.sum $ snd $ Data.Map.elemAt 0 $ Ledger.posting_amounts p
223 , snd . Data.Map.elemAt 0 <$> s
225 then Data.Sequence.singleton line
226 else Data.Sequence.empty
228 m | Data.Sequence.null m -> Nothing
231 (GL.inclusive expanded_lines) of
232 m | Data.Map.null m -> Nothing
238 :: GL Ledger.Transaction
242 flip (Lib.TreeMap.foldr_with_Path
244 flip $ Data.Map.foldrWithKey
246 flip (Data.Foldable.foldr
248 { GL.gl_line_transaction = t
249 , GL.gl_line_posting = p
252 flip (Data.Map.foldrWithKey
254 let ptype = Ledger.Posting_Type_Regular
255 let descr = Ledger.transaction_description t
258 { Table.cell_content = Ledger.Write.account ptype acct
259 , Table.cell_width = Ledger.Write.account_length ptype acct
262 { Table.cell_content = Date.Write.date date
263 , Table.cell_width = Date.Write.date_length date
266 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_positive amt)
267 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_positive amt)
270 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_negative amt)
271 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_negative amt)
274 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
275 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
278 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
279 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
282 { Table.cell_content = maybe W.empty Amount.Write.amount (Data.Map.lookup unit $ Amount.sum_balance s)
283 , Table.cell_width = maybe 0 Amount.Write.amount_length (Data.Map.lookup unit $ Amount.sum_balance s)
286 { Table.cell_content = toDoc () descr
287 , Table.cell_width = Text.length descr
291 (Ledger.posting_amounts p)