1 {-# LANGUAGE NamedFieldPuns #-}
 
   2 {-# LANGUAGE OverloadedStrings #-}
 
   3 module Hcompta.CLI.Command.Balance where
 
   5 import           Control.Monad.IO.Class (liftIO)
 
   6 import           Control.Monad.Trans.Except (runExceptT)
 
   7 import qualified Data.Either
 
   8 import qualified Data.Foldable
 
   9 import qualified Data.List
 
  10 import qualified Data.Map.Strict as Data.Map
 
  11 import qualified Data.Text.Lazy as TL
 
  12 import           System.Console.GetOpt
 
  17 import           System.Environment as Env (getProgName)
 
  18 import           System.Exit (exitWith, ExitCode(..))
 
  19 import qualified System.IO as IO
 
  20 import           Text.Show.Pretty (ppShow) -- TODO: may be not necessary
 
  22 import qualified Hcompta.Calc.Balance as Balance
 
  23 import qualified Hcompta.CLI.Args as Args
 
  24 import qualified Hcompta.CLI.Context as Context
 
  25 import qualified Hcompta.CLI.Format.Ledger as CLI.Ledger
 
  26 import qualified Hcompta.CLI.Lib.Shakespeare.Leijen as I18N
 
  27 import qualified Hcompta.CLI.Lib.Leijen.Table as Table
 
  28 import qualified Hcompta.CLI.Write as Write
 
  29 import qualified Hcompta.Format.Ledger as Ledger
 
  30 import qualified Hcompta.Format.Ledger.Journal as Ledger.Journal
 
  31 import qualified Hcompta.Format.Ledger.Read as Ledger.Read
 
  32 import qualified Hcompta.Format.Ledger.Write as Ledger.Write
 
  33 import qualified Hcompta.Lib.TreeMap as Lib.TreeMap
 
  34 import qualified Hcompta.Lib.Leijen as W
 
  35 import           Hcompta.Lib.Leijen (toDoc, ToDoc(..))
 
  36 import qualified Hcompta.Model.Amount as Amount
 
  37 import           Hcompta.Model.Amount (Amount, Unit)
 
  41  { ctx_input     :: [FilePath]
 
  42  , ctx_redundant :: Bool
 
  49          , ctx_redundant = False
 
  54         bin <- Env.getProgName
 
  57                 , "  "++bin++" balance [option..]"
 
  59                 , usageInfo "OPTIONS" options
 
  62 options :: Args.Options Ctx
 
  65          (NoArg (\_context _ctx -> do
 
  66                 usage >>= IO.hPutStr IO.stderr
 
  67                 exitWith ExitSuccess))
 
  69         , Option "i" ["input"]
 
  70          (ReqArg (\s _context ctx -> do
 
  71                 return $ ctx{ctx_input=s:ctx_input ctx}) "FILE")
 
  72          "read data from given file, can be use multiple times"
 
  73         , Option "" ["redundant"]
 
  74          (OptArg (\arg context ctx -> do
 
  75                 redundant <- case arg of
 
  76                  Nothing    -> return $ True
 
  77                  Just "yes" -> return $ True
 
  78                  Just "no"  -> return $ False
 
  79                  Just _     -> Write.fatal context $
 
  80                         W.text "--redundant option expects \"yes\", or \"no\" as value"
 
  81                 return $ ctx{ctx_redundant=redundant})
 
  83          "also print accounts with zero amount or the same amounts than its ascending account"
 
  86 run :: Context.Context -> [String] -> IO ()
 
  88         (ctx, _) <- Args.parse context usage options (nil, args)
 
  90                 CLI.Ledger.paths context $ ctx_input ctx
 
  91                 >>= do mapM $ \path -> do
 
  95                          Left  ko -> return $ Left (path, ko)
 
  96                          Right ok -> return $ Right ok
 
  97                 >>= return . Data.Either.partitionEithers
 
 100                 (flip mapM_) kos $ \(_path, ko) -> do
 
 101                         Write.debug context $ ppShow $ ko
 
 102                         Write.fatal context $ toDoc context ko
 
 107                                  (flip (Data.Foldable.foldr
 
 108                                          (flip (Data.Foldable.foldr
 
 110                                                  . Ledger.transaction_postings_balance))))
 
 111                                  . Ledger.journal_transactions))
 
 114                 let expanded = Balance.expanded $ Balance.balance_by_account balance
 
 115                 style_color <- Write.with_color context IO.stdout
 
 116                 Ledger.Write.put Ledger.Write.Style
 
 117                  { Ledger.Write.style_align = True
 
 118                  , Ledger.Write.style_color
 
 121                                 let title = TL.toStrict . W.displayT . W.renderCompact False .
 
 122                                         I18N.renderMessage Context.App (Context.langs context) in
 
 124                                 [ Table.column (title Write.I18N_Balance_debit)  Table.Align_Right
 
 125                                 , Table.column (title Write.I18N_Balance_credit) Table.Align_Right
 
 126                                 , Table.column (title Write.I18N_Balance_total)  Table.Align_Right
 
 127                                 , Table.column (title Write.I18N_Account)        Table.Align_Left
 
 129                                 flip (write_by_accounts ctx) expanded $
 
 131                                         [ Table.Cell_Line '=' 0
 
 132                                         , Table.Cell_Line '=' 0
 
 133                                         , Table.Cell_Line '=' 0
 
 134                                         , Table.Cell_Line ' ' 0
 
 136                                 write_by_amounts (repeat []) $
 
 138                                          Balance.unit_sum_amount
 
 139                                          (Balance.balance_by_unit balance)
 
 143  -> Balance.Expanded Amount Unit
 
 145 write_by_accounts ctx =
 
 146         let posting_type = Ledger.Posting_Type_Regular in
 
 147         Lib.TreeMap.foldr_with_Path_and_Node
 
 148          (\account node balance rows -> do
 
 149                 let descendants = Lib.TreeMap.nodes
 
 150                          (Lib.TreeMap.node_descendants node)
 
 155                                          (not . Amount.is_zero)
 
 156                                          (Balance.exclusive balance)) > 0
 
 159                                          ( maybe False (not . Amount.are_zero . Balance.amount_sum_balance . Balance.inclusive)
 
 160                                          . Lib.TreeMap.node_value )
 
 166                          (\(amount_positive, amount_negative, amount) ->
 
 169                                          { Table.cell_content = maybe W.empty Ledger.Write.amount  amount_positive
 
 170                                          , Table.cell_width   = maybe 0 Ledger.Write.amount_length amount_positive
 
 173                                          { Table.cell_content = maybe W.empty Ledger.Write.amount  amount_negative
 
 174                                          , Table.cell_width   = maybe 0 Ledger.Write.amount_length amount_negative
 
 177                                          { Table.cell_content = Ledger.Write.amount        $ amount
 
 178                                          , Table.cell_width   = Ledger.Write.amount_length $ amount
 
 181                                          { Table.cell_content = Ledger.Write.account        posting_type account
 
 182                                          , Table.cell_width   = Ledger.Write.account_length posting_type account
 
 187                         let bal = Balance.inclusive balance in
 
 188                         Data.Map.foldrWithKey
 
 190                                 ( Data.Map.lookup unit $ Balance.amount_sum_positive bal
 
 191                                 , Data.Map.lookup unit $ Balance.amount_sum_negative bal
 
 194                          ) [] $ Balance.amount_sum_balance bal
 
 199  -> Data.Map.Map Unit (Balance.Amount_Sum Amount ())
 
 205                         [ let amt = Data.Map.lookup () $ Balance.amount_sum_positive amount_sum in
 
 207                                  { Table.cell_content = maybe W.empty Ledger.Write.amount  amt
 
 208                                  , Table.cell_width   = maybe 0 Ledger.Write.amount_length amt
 
 210                         , let amt = Data.Map.lookup () $ Balance.amount_sum_negative amount_sum in
 
 212                                  { Table.cell_content = maybe W.empty Ledger.Write.amount  amt
 
 213                                  , Table.cell_width   = maybe 0 Ledger.Write.amount_length amt
 
 215                         , let amt = Data.Map.lookup () $ Balance.amount_sum_balance amount_sum in
 
 217                                  { Table.cell_content = maybe W.empty Ledger.Write.amount  amt
 
 218                                  , Table.cell_width   = maybe 0 Ledger.Write.amount_length amt
 
 221                                  { Table.cell_content = W.empty
 
 222                                  , Table.cell_width   = 0