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.Applicative ((<$>))
10 import Control.Arrow (first)
11 import Control.Monad (Monad(..), forM_, liftM, mapM)
12 import Control.Monad.IO.Class (liftIO)
13 import Control.Monad.Trans.Except (runExceptT)
15 import Data.Either (Either(..), partitionEithers)
16 import Data.Foldable (Foldable(..))
17 import Data.List ((++), repeat)
18 import qualified Data.Map.Strict as Data.Map
19 import Data.Maybe (Maybe(..), maybe)
20 import Data.Monoid (Monoid(..), (<>))
21 import qualified Data.Sequence
22 import qualified Data.Strict.Maybe as Strict
23 import Data.String (String)
24 import qualified Data.Text as Text
25 import Data.Tuple (snd)
26 import Prelude (($), (.), FilePath, IO, id, flip, unlines, zipWith)
27 import Text.Show (Show(..))
28 import System.Console.GetOpt
33 import System.Environment as Env (getProgName)
34 import System.Exit (exitSuccess)
35 import qualified System.IO as IO
37 import Hcompta.Account (Account)
38 import Hcompta.Amount (Amount)
39 import qualified Hcompta.Amount as Amount
40 import qualified Hcompta.Amount.Write as Amount.Write
41 import Hcompta.Chart (Chart)
42 import qualified Hcompta.Chart as Chart
43 import qualified Hcompta.CLI.Args as Args
44 import qualified Hcompta.CLI.Context as C
45 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
46 import qualified Hcompta.CLI.Lang as Lang
47 import qualified Hcompta.CLI.Lib.Leijen.Table as Table
48 import qualified Hcompta.CLI.Write as Write
49 import Hcompta.Date (Date)
50 import qualified Hcompta.Date.Write as Date.Write
51 import qualified Hcompta.Filter as Filter
52 import qualified Hcompta.Filter.Read as Filter.Read
53 import qualified Hcompta.Format.Ledger as Ledger
54 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
55 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
56 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
57 import Hcompta.Lib.Leijen (toDoc, ToDoc(..))
58 import qualified Hcompta.Lib.Leijen as W
59 import qualified Hcompta.Lib.TreeMap as TreeMap
60 import Hcompta.GL (GL(..))
61 import qualified Hcompta.GL as GL
62 import qualified Hcompta.Posting as Posting
63 import qualified Hcompta.Tag as Tag
67 { ctx_input :: [FilePath]
68 , ctx_output :: [(Write.Mode, FilePath)]
69 , ctx_filter_transaction :: Filter.Simplified
71 (Filter.Filter_Transaction
72 (Chart, Ledger.Transaction)))
73 , ctx_filter_posting :: Filter.Simplified
75 (Filter.Filter_Posting
76 (Chart, Ledger.Posting)))
77 , ctx_filter_gl :: Filter.Simplified
83 , Amount.Sum Amount )))
84 , ctx_reduce_date :: Bool
90 { ctx_filter_gl = mempty
91 , ctx_filter_posting = mempty
92 , ctx_filter_transaction = mempty
95 , ctx_reduce_date = True
98 usage :: C.Context -> IO String
100 bin <- Env.getProgName
102 [ C.translate c Lang.Section_Description
103 , " "++C.translate c Lang.Help_Command_General_Ledger
105 , C.translate c Lang.Section_Syntax
106 , " "++bin++" gl ["++C.translate c Lang.Type_Option++"] [...]"++
107 " ["++C.translate c Lang.Type_File_Journal++"] [...]"
109 , usageInfo (C.translate c Lang.Section_Options) (options c)
112 options :: C.Context -> Args.Options Ctx
114 [ Option "g" ["filter-gl"]
115 (ReqArg (\s ctx -> do
117 liftM ((ctx_filter_gl ctx <>) . Filter.simplify) $
118 liftIO $ Filter.Read.read Filter.Read.filter_gl s
120 Left ko -> Write.fatal c $ ko
121 Right ok -> return ok
122 return $ ctx{ctx_filter_gl}) $
123 C.translate c Lang.Type_Filter_General_Ledger) $
124 C.translate c Lang.Help_Option_Filter_General_Ledger
125 , Option "p" ["filter-posting"]
126 (ReqArg (\s ctx -> do
127 ctx_filter_posting <-
128 liftM ((ctx_filter_posting ctx <>) . Filter.simplify) $
129 liftIO $ Filter.Read.read Filter.Read.filter_posting s
131 Left ko -> Write.fatal c $ ko
132 Right ok -> return ok
133 return $ ctx{ctx_filter_posting}) $
134 C.translate c Lang.Type_Filter_Posting) $
135 C.translate c Lang.Help_Option_Filter_Posting
136 , Option "t" ["filter-transaction"]
137 (ReqArg (\s ctx -> do
138 ctx_filter_transaction <-
139 liftM ((ctx_filter_transaction ctx <>) . Filter.simplify) $
140 liftIO $ Filter.Read.read Filter.Read.filter_transaction s
142 Left ko -> Write.fatal c $ ko
143 Right ok -> return ok
144 return $ ctx{ctx_filter_transaction}) $
145 C.translate c Lang.Type_Filter_Transaction) $
146 C.translate c Lang.Help_Option_Filter_Transaction
147 , Option "h" ["help"]
149 usage c >>= IO.hPutStr IO.stderr
151 C.translate c Lang.Help_Option_Help
152 , Option "i" ["input"]
153 (ReqArg (\s ctx -> do
154 return $ ctx{ctx_input=s:ctx_input ctx}) $
155 C.translate c Lang.Type_File_Journal) $
156 C.translate c Lang.Help_Option_Input
157 , Option "o" ["output"]
158 (ReqArg (\s ctx -> do
159 return $ ctx{ctx_output=(Write.Mode_Append, s):ctx_output ctx}) $
160 C.translate c Lang.Type_File) $
161 C.translate c Lang.Help_Option_Output
162 , Option "O" ["overwrite"]
163 (ReqArg (\s ctx -> do
164 return $ ctx{ctx_output=(Write.Mode_Over, s):ctx_output ctx}) $
165 C.translate c Lang.Type_File) $
166 C.translate c Lang.Help_Option_Overwrite
167 {- NOTE: not used so far.
168 , Option "" ["reduce-date"]
169 (OptArg (\arg c ctx -> do
170 ctx_reduce_date <- case arg of
171 Nothing -> return $ True
172 Just "yes" -> return $ True
173 Just "no" -> return $ False
174 Just _ -> Write.fatal c $
175 W.text "--reduce-date option expects \"yes\", or \"no\" as value"
176 return $ ctx{ctx_reduce_date})
178 "use advanced date reducer to speed up filtering"
182 run :: C.Context -> [String] -> IO ()
187 [] -> x{ctx_output=[(Write.Mode_Append, "-")]}
189 Args.parse c usage options (nil, args)
191 liftM Data.Either.partitionEithers $ do
192 CLI.Ledger.paths c $ ctx_input ctx ++ inputs
195 liftIO $ runExceptT $ Ledger.Read.file
196 (Ledger.Read.context ( ctx_filter_transaction ctx
197 , ctx_filter_posting ctx )
201 Left ko -> return $ Left (path, ko)
202 Right ok -> return $ Right ok
203 case read_journals of
204 (errs@(_:_), _journals) ->
205 forM_ errs $ \(_path, err) -> do
208 Write.debug c $ "filter: transaction: " ++ show (ctx_filter_transaction ctx)
209 Write.debug c $ "filter: posting: " ++ show (ctx_filter_posting ctx)
210 Write.debug c $ "filter: gl: " ++ show (ctx_filter_gl ctx)
211 let gl = ledger_gl ctx journals
213 Write.write c Write.style (ctx_output ctx) $ do
216 [ Table.column (Lang.translate lang Lang.Title_Account) Table.Align_Left
217 , Table.column (Lang.translate lang Lang.Title_Date) Table.Align_Left
218 , Table.column (Lang.translate lang Lang.Title_Debit) Table.Align_Right
219 , Table.column (Lang.translate lang Lang.Title_Credit) Table.Align_Right
220 , Table.column (Lang.translate lang Lang.Title_Running_debit) Table.Align_Right
221 , Table.column (Lang.translate lang Lang.Title_Running_credit) Table.Align_Right
222 , Table.column (Lang.translate lang Lang.Title_Running_balance) Table.Align_Right
223 , Table.column (Lang.translate lang Lang.Title_Description) Table.Align_Left
225 write_gl gl (repeat [])
229 -> [ Ledger.Journal (GL.GL (Chart, Ledger.Transaction)) ]
230 -> GL (Chart, Ledger.Transaction)
231 ledger_gl ctx journals =
236 (Ledger.journal_chart j,) $
239 { Ledger.journal_sections=g
245 TreeMap.map_Maybe_with_Path
246 (\acct expanded_lines ->
247 case Data.Map.mapMaybeWithKey
249 case Data.Foldable.foldMap
251 { GL.gl_line_transaction = _t
252 , GL.gl_line_posting = p
255 if Filter.test (ctx_filter_gl ctx)
256 ( (acct, Chart.account_tags acct chart)
258 , Amount.sum $ snd $ Data.Map.elemAt 0 $ Ledger.posting_amounts $ snd p
259 , snd . Data.Map.elemAt 0 <$> s
261 then Data.Sequence.singleton line
262 else Data.Sequence.empty
264 m | Data.Sequence.null m -> Nothing
267 (GL.inclusive expanded_lines) of
268 m | Data.Map.null m -> Strict.Nothing
274 :: GL (Chart, Ledger.Transaction)
278 flip (TreeMap.foldr_with_Path
280 flip $ Data.Map.foldrWithKey
282 flip (Data.Foldable.foldr
284 { GL.gl_line_transaction = t
285 , GL.gl_line_posting = p
288 flip (Data.Map.foldrWithKey
290 let ptype = Posting.Posting_Type_Regular
291 let descr = Ledger.transaction_description $ snd t
294 { Table.cell_content = Ledger.Write.account ptype acct
295 , Table.cell_width = Ledger.Write.account_length ptype acct
298 { Table.cell_content = Date.Write.date date
299 , Table.cell_width = Date.Write.date_length date
302 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_positive amt)
303 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_positive amt)
306 { Table.cell_content = maybe W.empty Amount.Write.amount (Amount.sumable_negative amt)
307 , Table.cell_width = maybe 0 Amount.Write.amount_length (Amount.sumable_negative amt)
310 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
311 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_positive s)
314 { Table.cell_content = maybe W.empty Amount.Write.amount (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
315 , Table.cell_width = maybe 0 Amount.Write.amount_length (maybe Nothing (Data.Map.lookup unit) $ Amount.sum_negative s)
318 { Table.cell_content = maybe W.empty Amount.Write.amount (Data.Map.lookup unit $ Amount.sum_balance s)
319 , Table.cell_width = maybe 0 Amount.Write.amount_length (Data.Map.lookup unit $ Amount.sum_balance s)
322 { Table.cell_content = toDoc () descr
323 , Table.cell_width = Text.length descr
327 (Ledger.posting_amounts $ snd p)