1 {-# LANGUAGE FlexibleInstances #-}
2 {-# LANGUAGE MultiParamTypeClasses #-}
3 {-# LANGUAGE NamedFieldPuns #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
6 {-# LANGUAGE TupleSections #-}
7 module Hcompta.CLI.Command.GL where
9 import Control.Monad (liftM, forM_)
10 import Control.Monad.IO.Class (liftIO)
11 import Control.Monad.Trans.Except (runExceptT)
12 import qualified Data.Either
13 import qualified Data.Foldable
14 import qualified Data.Map.Strict as Data.Map
15 import Data.Monoid ((<>))
16 import qualified Data.Sequence
17 import qualified Data.Strict.Maybe as Strict
18 import qualified Data.Text as Text
19 import qualified Data.Text.Lazy as TL
20 import Prelude hiding (foldr)
21 import System.Console.GetOpt
26 import System.Environment as Env (getProgName)
27 import System.Exit (exitSuccess)
28 import qualified System.IO as IO
30 import Hcompta.Account (Account)
31 import Hcompta.Amount (Amount)
32 import qualified Hcompta.Amount as Amount
33 import qualified Hcompta.Amount.Write as Amount.Write
34 import qualified Hcompta.CLI.Args as Args
35 import qualified Hcompta.CLI.Context as Context
36 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
37 import qualified Hcompta.CLI.Lang as Lang
38 import qualified Hcompta.CLI.Lib.Leijen.Table as Table
39 import qualified Hcompta.CLI.Write as Write
40 import Hcompta.Date (Date)
41 import qualified Hcompta.Date.Write as Date.Write
42 import qualified Hcompta.Filter as Filter
43 import qualified Hcompta.Filter.Read as Filter.Read
44 import qualified Hcompta.Format.Ledger as Ledger
45 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
46 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
47 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
48 import Hcompta.Lib.Leijen (toDoc, ToDoc(..))
49 import qualified Hcompta.Lib.Leijen as W
50 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
51 import Hcompta.GL (GL(..))
52 import qualified Hcompta.GL as GL
53 import qualified Hcompta.Posting as Posting
57 { ctx_input :: [FilePath]
58 , ctx_filter_transaction :: Filter.Simplified
60 (Filter.Filter_Transaction
62 , ctx_filter_posting :: Filter.Simplified
64 (Filter.Filter_Posting
66 , ctx_filter_gl :: Filter.Simplified
72 , Amount.Sum Amount )))
73 , ctx_reduce_date :: Bool
79 { ctx_filter_gl = mempty
80 , ctx_filter_posting = mempty
81 , ctx_filter_transaction = mempty
83 , ctx_reduce_date = True
88 bin <- Env.getProgName
89 let pad = replicate (length bin) ' '
92 , " "++bin++" gl [-i JOURNAL_FILE]"
93 , " "++pad++" [-g GL_FILTER]"
94 , " "++pad++" [-p POSTING_FILTER]"
95 , " "++pad++" [-t TRANSACTION_FILTER]"
96 , " "++pad++" [JOURNAL_FILE] [...]"
98 , usageInfo "OPTIONS" options
101 options :: Args.Options Ctx
103 [ Option "g" ["filter-gl"]
104 (ReqArg (\s context ctx -> do
106 liftM ((ctx_filter_gl ctx <>) . Filter.simplify) $
107 liftIO $ Filter.Read.read Filter.Read.filter_gl s
109 Left ko -> Write.fatal context $ ko
110 Right ok -> return ok
111 return $ ctx{ctx_filter_gl}) "FILTER")
112 "filter at general ledger level, multiple uses are merged with a logical AND"
113 , Option "p" ["filter-posting"]
114 (ReqArg (\s context ctx -> do
115 ctx_filter_posting <-
116 liftM ((ctx_filter_posting ctx <>) . Filter.simplify) $
117 liftIO $ Filter.Read.read Filter.Read.filter_posting s
119 Left ko -> Write.fatal context $ ko
120 Right ok -> return ok
121 return $ ctx{ctx_filter_posting}) "FILTER")
122 "filter at posting level, multiple uses are merged with a logical AND"
123 , Option "t" ["filter-transaction"]
124 (ReqArg (\s context ctx -> do
125 ctx_filter_transaction <-
126 liftM ((ctx_filter_transaction ctx <>) . Filter.simplify) $
127 liftIO $ Filter.Read.read Filter.Read.filter_transaction s
129 Left ko -> Write.fatal context $ ko
130 Right ok -> return ok
131 return $ ctx{ctx_filter_transaction}) "FILTER")
132 "filter at transaction level, multiple uses are merged with a logical AND"
133 , Option "h" ["help"]
134 (NoArg (\_context _ctx -> do
135 usage >>= IO.hPutStr IO.stderr
138 , Option "i" ["input"]
139 (ReqArg (\s _context ctx -> do
140 return $ ctx{ctx_input=s:ctx_input ctx}) "JOURNAL_FILE")
141 "read data from given file, multiple uses merge the data as would a concatenation do"
142 {- NOTE: not used so far.
143 , Option "" ["reduce-date"]
144 (OptArg (\arg context ctx -> do
145 ctx_reduce_date <- case arg of
146 Nothing -> return $ True
147 Just "yes" -> return $ True
148 Just "no" -> return $ False
149 Just _ -> Write.fatal context $
150 W.text "--reduce-date option expects \"yes\", or \"no\" as value"
151 return $ ctx{ctx_reduce_date})
153 "use advanced date reducer to speed up filtering"
157 run :: Context.Context -> [String] -> IO ()
158 run context args = do
159 (ctx, inputs) <- Args.parse context usage options (nil, args)
161 liftM Data.Either.partitionEithers $ do
162 CLI.Ledger.paths context $ ctx_input ctx ++ inputs
165 liftIO $ runExceptT $ Ledger.Read.file
166 (Ledger.Read.context ( ctx_filter_transaction ctx
167 , ctx_filter_posting ctx )
171 Left ko -> return $ Left (path, ko)
172 Right ok -> return $ Right ok
173 case read_journals of
174 (errs@(_:_), _journals) ->
175 forM_ errs $ \(_path, err) -> do
176 Write.fatal context $ err
178 Write.debug context $ "filter: transaction: " ++ show (ctx_filter_transaction ctx)
179 Write.debug context $ "filter: posting: " ++ show (ctx_filter_posting ctx)
180 Write.debug context $ "filter: gl: " ++ show (ctx_filter_gl ctx)
181 let gl = ledger_gl ctx journals
182 style_color <- Write.with_color context IO.stdout
183 W.displayIO IO.stdout $
184 W.renderPretty style_color 1.0 maxBound $ do
187 TL.toStrict . W.displayT .
188 W.renderCompact False .
189 toDoc (Context.lang context) in
191 [ Table.column (title Lang.Message_Account) Table.Align_Left
192 , Table.column (title Lang.Message_Date) Table.Align_Left
193 , Table.column (title Lang.Message_Debit) Table.Align_Right
194 , Table.column (title Lang.Message_Credit) Table.Align_Right
195 , Table.column (title Lang.Message_Running_debit) Table.Align_Right
196 , Table.column (title Lang.Message_Running_credit) Table.Align_Right
197 , Table.column (title Lang.Message_Running_balance) Table.Align_Right
198 , Table.column (title Lang.Message_Description) Table.Align_Left
200 write_gl gl (repeat [])
204 -> [ Ledger.Journal (GL.GL Ledger.Transaction) ]
205 -> GL Ledger.Transaction
206 ledger_gl ctx journals =
209 (flip $ Ledger.Journal.fold
210 (\Ledger.Journal{Ledger.journal_transactions=g} ->
214 Lib.TreeMap.map_Maybe_with_Path
215 (\acct expanded_lines ->
216 case Data.Map.mapMaybeWithKey
218 case Data.Foldable.foldMap
220 { GL.gl_line_transaction = _t
221 , GL.gl_line_posting = p
224 if Filter.test (ctx_filter_gl ctx)
227 , Amount.sum $ snd $ Data.Map.elemAt 0 $ Ledger.posting_amounts p
228 , snd . Data.Map.elemAt 0 <$> s
230 then Data.Sequence.singleton line
231 else Data.Sequence.empty
233 m | Data.Sequence.null m -> Nothing
236 (GL.inclusive expanded_lines) of
237 m | Data.Map.null m -> Strict.Nothing
243 :: GL Ledger.Transaction
247 flip (Lib.TreeMap.foldr_with_Path
249 flip $ Data.Map.foldrWithKey
251 flip (Data.Foldable.foldr
253 { GL.gl_line_transaction = t
254 , GL.gl_line_posting = p
257 flip (Data.Map.foldrWithKey
259 let ptype = Posting.Posting_Type_Regular
260 let descr = Ledger.transaction_description t
263 { Table.cell_content = Ledger.Write.account ptype acct
264 , Table.cell_width = Ledger.Write.account_length ptype acct
267 { Table.cell_content = Date.Write.date date
268 , Table.cell_width = Date.Write.date_length date
271 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_positive amt)
272 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_positive amt)
275 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_negative amt)
276 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_negative amt)
279 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
280 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
283 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
284 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
287 { Table.cell_content = maybe W.empty Amount.Write.amount (Data.Map.lookup unit $ Amount.sum_balance s)
288 , Table.cell_width = maybe 0 Amount.Write.amount_length (Data.Map.lookup unit $ Amount.sum_balance s)
291 { Table.cell_content = toDoc () descr
292 , Table.cell_width = Text.length descr
296 (Ledger.posting_amounts p)