summaryrefslogtreecommitdiff
path: root/Hledger/RegisterCSV.hs
blob: 69fec7707f78c14667fffa3ff57b31a709926af4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
-- sflc-ledger-reports: web interface to hledger-based reports
--  Copyright (C) 2013  Clint Adams
--
--  This program is free software: you can redistribute it and/or modify
--  it under the terms of the GNU Affero General Public License as
--  published by the Free Software Foundation, either version 3 of the
--  License, or (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU Affero General Public License for more details.
--
--  You should have received a copy of the GNU Affero General Public License
--  along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE FlexibleContexts #-}

module Hledger.RegisterCSV (convertJournalToRegisterCSV) where

import Prelude (String, Double, Show, Eq, (||), Bool(..), const, (<),  (>), snd, (.), ($), maybe, show, id, length, (==), (/=), head, map, FilePath, IO, Either(..), Maybe(..), return, concatMap, Char, otherwise)

import Control.Applicative (many)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Trans.Control (MonadBaseControl)
import Control.Monad.Trans.Resource (MonadThrow)
import Control.Monad.Primitive (PrimMonad)
import Control.Monad.Base (MonadBase)

import Data.List (isPrefixOf)
import qualified Data.Text as T

import Hledger.Data.Amount (showMixedAmount)
import Hledger.Data.Journal (journalAccountNamesUsed)
import Hledger.Data.Types (Journal(..), Transaction(..), Posting(..), MixedAmount(..))
import Hledger.Query (Query(..))
import Hledger.Read (readJournalFile)
import Hledger.Reports (postingsReport, defreportopts, PostingsReportItem)

import Data.Conduit (($=),($$))
import Control.Monad.Trans.Resource (runResourceT)
import qualified Data.Conduit.List as CL
import Data.CSV.Conduit (fromCSV, defCSVSettings, CSV)

import Text.Parsec.Char (char, digit, satisfy, space)
import Text.Parsec.Combinator (many1)
import Text.Parsec.Prim (ParsecT, Stream, parse)

data AcctReg = AcctReg {
    _acct :: String
  , _openingBalance :: Double
  , _items :: [PostingsReportItem]
} deriving (Show, Eq)

cordGuess :: String -> MixedAmount -> T.Text
cordGuess acct
    | "Assets:" `isPrefixOf` acct || "Liabilities:" `isPrefixOf` acct || "Equity:" `isPrefixOf` acct = cordGuest' True
    | "Income:" `isPrefixOf` acct || "Expenses:" `isPrefixOf` acct = cordGuest' False
    | otherwise = const (T.pack "INVALID")
    where
        cordGuest' True amt | amt > 0 = T.pack "Deposit"
        cordGuest' True _ = T.pack "Check"
        cordGuest' False amt | amt > 0 = T.pack "Check"
        cordGuest' False _ = T.pack "Deposit"

acctReg :: String -> Journal -> AcctReg
acctReg a j = AcctReg a 0 (snd . postingsReport defreportopts (Acct a) $ j)

priToLine :: PostingsReportItem -> [T.Text]
priToLine (mday, mdesc, p, mixa) = [T.empty, cord, date, num, name, memo, split, amt, bal]
    where
      date = maybe T.empty (T.pack . show) mday
      desc' = maybe "" id mdesc
      (c', desc) = parseDesc desc'
      cord = cordGuess (paccount p) (pamount p)
      num = T.pack c'
      name = T.empty
      memo = T.pack desc
      split = maybe (T.pack "ERROR") (splitField . tpostings) (ptransaction p)
      amt = T.pack . showMixedAmount . pamount $ p
      bal = T.pack . showMixedAmount $ mixa

splitField :: [Posting] -> T.Text
splitField ps
    | length ps < 2 = T.pack "ERROR"
    | length ps == 2 = T.pack (paccount . head $ ps)
    | otherwise = T.pack "-SPLIT-"

arToLines :: AcctReg -> [[T.Text]]
arToLines ar = [T.pack . _acct $ ar,T.empty,T.empty,T.empty,T.empty,T.empty,T.empty,T.empty,T.pack "0.000000"]:map priToLine (_items ar)

convertJournalToRegisterCSV :: FilePath -> IO (T.Text)
convertJournalToRegisterCSV fp = do
    Right j <- readJournalFile Nothing Nothing fp
    csv <- journalToRegisterCSV j
    return $ T.concat csv

journalToRegisterCSV :: ( PrimMonad base, 
                          MonadBase base m,
                          MonadThrow m,
                          MonadIO m,
                          CSV a [T.Text],
                          MonadBaseControl IO m)
                        => Journal 
                        -> m [a]
journalToRegisterCSV j = do
    let accts = journalAccountNamesUsed j
        precsv' = concatMap (\x -> arToLines $ acctReg x j) accts
	precsv = [T.empty, T.pack "Type",T.pack "Date",T.pack "Num",T.pack "Name",T.pack "Memo",T.pack "Split",T.pack "Amount",T.pack "Balance"]:precsv'
    csv <- runResourceT $ CL.sourceList precsv $= fromCSV defCSVSettings $$ CL.consume
    return csv

parseDesc :: String -> (String, String)
parseDesc d = case parse descParser' "desc" d of
    Left _ -> ("", d)
    Right x -> x

descParser' :: Stream s m Char => ParsecT s u m (String, String)
descParser' = do
    desc' <- many1 (satisfy (/='('))
    _ <- char '('
    checknum <- many1 digit
    _ <- char ')'
    _ <- many space
    return (checknum, desc')