{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
module Hledger.Data.Transaction
(
nulltransaction
, transaction
, txnTieKnot
, txnUntieKnot
, hasRealPostings
, realPostings
, assignmentPostings
, virtualPostings
, balancedVirtualPostings
, transactionsPostings
, transactionTransformPostings
, transactionApplyValuation
, transactionToCost
, transactionAddInferredEquityPostings
, transactionAddPricesFromEquity
, transactionApplyAliases
, transactionMapPostings
, transactionMapPostingAmounts
, transactionDate2
, transactionDateOrDate2
, transactionPayee
, transactionNote
, showTransaction
, showTransactionOneLineAmounts
, showTransactionLineFirstPart
, transactionFile
, annotateErrorWithTransaction
, tests_Transaction
) where
import Control.Monad.Trans.State (StateT(..), evalStateT)
import Data.Bifunctor (first)
import Data.Foldable (foldrM)
import Data.Maybe (fromMaybe, isJust, mapMaybe)
import Data.Semigroup (Endo(..))
import Data.Text (Text)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TB
import Data.Time.Calendar (Day, fromGregorian)
import Hledger.Utils
import Hledger.Data.Types
import Hledger.Data.Dates
import Hledger.Data.Posting
import Hledger.Data.Amount
import Hledger.Data.Valuation
nulltransaction :: Transaction
nulltransaction :: Transaction
nulltransaction = Transaction :: Integer
-> Text
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> Text
-> Text
-> Text
-> [Tag]
-> [Posting]
-> Transaction
Transaction {
tindex :: Integer
tindex=Integer
0,
tsourcepos :: (SourcePos, SourcePos)
tsourcepos=(SourcePos, SourcePos)
nullsourcepos,
tdate :: Day
tdate=Day
nulldate,
tdate2 :: Maybe Day
tdate2=Maybe Day
forall a. Maybe a
Nothing,
tstatus :: Status
tstatus=Status
Unmarked,
tcode :: Text
tcode=Text
"",
tdescription :: Text
tdescription=Text
"",
tcomment :: Text
tcomment=Text
"",
ttags :: [Tag]
ttags=[],
tpostings :: [Posting]
tpostings=[],
tprecedingcomment :: Text
tprecedingcomment=Text
""
}
transaction :: Day -> [Posting] -> Transaction
transaction :: Day -> [Posting] -> Transaction
transaction Day
day [Posting]
ps = Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$ Transaction
nulltransaction{tdate :: Day
tdate=Day
day, tpostings :: [Posting]
tpostings=[Posting]
ps}
transactionPayee :: Transaction -> Text
transactionPayee :: Transaction -> Text
transactionPayee = Tag -> Text
forall a b. (a, b) -> a
fst (Tag -> Text) -> (Transaction -> Tag) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Tag
payeeAndNoteFromDescription (Text -> Tag) -> (Transaction -> Text) -> Transaction -> Tag
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tdescription
transactionNote :: Transaction -> Text
transactionNote :: Transaction -> Text
transactionNote = Tag -> Text
forall a b. (a, b) -> b
snd (Tag -> Text) -> (Transaction -> Tag) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Tag
payeeAndNoteFromDescription (Text -> Tag) -> (Transaction -> Text) -> Transaction -> Tag
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> Text
tdescription
payeeAndNoteFromDescription :: Text -> (Text,Text)
payeeAndNoteFromDescription :: Text -> Tag
payeeAndNoteFromDescription Text
t
| Text -> Bool
T.null Text
n = (Text
t, Text
t)
| Bool
otherwise = (Text -> Text
T.strip Text
p, Text -> Text
T.strip (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.drop Int
1 Text
n)
where
(Text
p, Text
n) = (Char -> Bool) -> Text -> Tag
T.span (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'|') Text
t
showTransaction :: Transaction -> Text
showTransaction :: Transaction -> Text
showTransaction = Text -> Text
TL.toStrict (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
TB.toLazyText (Builder -> Text)
-> (Transaction -> Builder) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Transaction -> Builder
showTransactionHelper Bool
False
showTransactionOneLineAmounts :: Transaction -> Text
showTransactionOneLineAmounts :: Transaction -> Text
showTransactionOneLineAmounts = Text -> Text
TL.toStrict (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
TB.toLazyText (Builder -> Text)
-> (Transaction -> Builder) -> Transaction -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Transaction -> Builder
showTransactionHelper Bool
True
showTransactionHelper :: Bool -> Transaction -> TB.Builder
showTransactionHelper :: Bool -> Transaction -> Builder
showTransactionHelper Bool
onelineamounts Transaction
t =
Text -> Builder
TB.fromText Text
descriptionline Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
newline
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (Text -> Builder) -> [Text] -> Builder
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap ((Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
newline) (Builder -> Builder) -> (Text -> Builder) -> Text -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Builder
TB.fromText) [Text]
newlinecomments
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (Text -> Builder) -> [Text] -> Builder
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap ((Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
newline) (Builder -> Builder) -> (Text -> Builder) -> Text -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Builder
TB.fromText) (Bool -> [Posting] -> [Text]
postingsAsLines Bool
onelineamounts ([Posting] -> [Text]) -> [Posting] -> [Text]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t)
Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Builder
newline
where
descriptionline :: Text
descriptionline = Text -> Text
T.stripEnd (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
showTransactionLineFirstPart Transaction
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Text
T.concat [Text
desc, Text
samelinecomment]
desc :: Text
desc = if Text -> Bool
T.null Text
d then Text
"" else Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
d where d :: Text
d = Transaction -> Text
tdescription Transaction
t
(Text
samelinecomment, [Text]
newlinecomments) =
case Text -> [Text]
renderCommentLines (Transaction -> Text
tcomment Transaction
t) of [] -> (Text
"",[])
Text
c:[Text]
cs -> (Text
c,[Text]
cs)
newline :: Builder
newline = Char -> Builder
TB.singleton Char
'\n'
showTransactionLineFirstPart :: Transaction -> Text
showTransactionLineFirstPart Transaction
t = [Text] -> Text
T.concat [Text
date, Text
status, Text
code]
where
date :: Text
date = Day -> Text
showDate (Transaction -> Day
tdate Transaction
t) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> (Day -> Text) -> Maybe Day -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" ((Text
"="Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) (Text -> Text) -> (Day -> Text) -> Day -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Day -> Text
showDate) (Transaction -> Maybe Day
tdate2 Transaction
t)
status :: Text
status | Transaction -> Status
tstatus Transaction
t Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
Cleared = Text
" *"
| Transaction -> Status
tstatus Transaction
t Status -> Status -> Bool
forall a. Eq a => a -> a -> Bool
== Status
Pending = Text
" !"
| Bool
otherwise = Text
""
code :: Text
code = if Text -> Bool
T.null (Transaction -> Text
tcode Transaction
t) then Text
"" else Text -> Text -> Text -> Text
wrap Text
" (" Text
")" (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
tcode Transaction
t
hasRealPostings :: Transaction -> Bool
hasRealPostings :: Transaction -> Bool
hasRealPostings = Bool -> Bool
not (Bool -> Bool) -> (Transaction -> Bool) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Posting] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([Posting] -> Bool)
-> (Transaction -> [Posting]) -> Transaction -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
realPostings
realPostings :: Transaction -> [Posting]
realPostings :: Transaction -> [Posting]
realPostings = (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
filter Posting -> Bool
isReal ([Posting] -> [Posting])
-> (Transaction -> [Posting]) -> Transaction -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings
assignmentPostings :: Transaction -> [Posting]
assignmentPostings :: Transaction -> [Posting]
assignmentPostings = (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
filter Posting -> Bool
hasBalanceAssignment ([Posting] -> [Posting])
-> (Transaction -> [Posting]) -> Transaction -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings
virtualPostings :: Transaction -> [Posting]
virtualPostings :: Transaction -> [Posting]
virtualPostings = (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
filter Posting -> Bool
isVirtual ([Posting] -> [Posting])
-> (Transaction -> [Posting]) -> Transaction -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings
balancedVirtualPostings :: Transaction -> [Posting]
balancedVirtualPostings :: Transaction -> [Posting]
balancedVirtualPostings = (Posting -> Bool) -> [Posting] -> [Posting]
forall a. (a -> Bool) -> [a] -> [a]
filter Posting -> Bool
isBalancedVirtual ([Posting] -> [Posting])
-> (Transaction -> [Posting]) -> Transaction -> [Posting]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings
transactionsPostings :: [Transaction] -> [Posting]
transactionsPostings :: [Transaction] -> [Posting]
transactionsPostings = (Transaction -> [Posting]) -> [Transaction] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Transaction -> [Posting]
tpostings
transactionDate2 :: Transaction -> Day
transactionDate2 :: Transaction -> Day
transactionDate2 Transaction
t = Day -> Maybe Day -> Day
forall a. a -> Maybe a -> a
fromMaybe (Transaction -> Day
tdate Transaction
t) (Maybe Day -> Day) -> Maybe Day -> Day
forall a b. (a -> b) -> a -> b
$ Transaction -> Maybe Day
tdate2 Transaction
t
transactionDateOrDate2 :: WhichDate -> Transaction -> Day
transactionDateOrDate2 :: WhichDate -> Transaction -> Day
transactionDateOrDate2 WhichDate
PrimaryDate = Transaction -> Day
tdate
transactionDateOrDate2 WhichDate
SecondaryDate = Transaction -> Day
transactionDate2
txnTieKnot :: Transaction -> Transaction
txnTieKnot :: Transaction -> Transaction
txnTieKnot t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps} = Transaction
t' where
t' :: Transaction
t' = Transaction
t{tpostings :: [Posting]
tpostings=(Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map (Transaction -> Posting -> Posting
postingSetTransaction Transaction
t') [Posting]
ps}
txnUntieKnot :: Transaction -> Transaction
txnUntieKnot :: Transaction -> Transaction
txnUntieKnot t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps} = Transaction
t{tpostings :: [Posting]
tpostings=(Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map (\Posting
p -> Posting
p{ptransaction :: Maybe Transaction
ptransaction=Maybe Transaction
forall a. Maybe a
Nothing}) [Posting]
ps}
postingSetTransaction :: Transaction -> Posting -> Posting
postingSetTransaction :: Transaction -> Posting -> Posting
postingSetTransaction Transaction
t Posting
p = Posting
p{ptransaction :: Maybe Transaction
ptransaction=Transaction -> Maybe Transaction
forall a. a -> Maybe a
Just Transaction
t}
transactionTransformPostings :: (Posting -> Posting) -> Transaction -> Transaction
transactionTransformPostings :: (Posting -> Posting) -> Transaction -> Transaction
transactionTransformPostings Posting -> Posting
f t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps} = Transaction
t{tpostings :: [Posting]
tpostings=(Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Posting
f [Posting]
ps}
transactionApplyValuation :: PriceOracle -> M.Map CommoditySymbol AmountStyle -> Day -> Day -> ValuationType -> Transaction -> Transaction
transactionApplyValuation :: PriceOracle
-> Map Text AmountStyle
-> Day
-> Day
-> ValuationType
-> Transaction
-> Transaction
transactionApplyValuation PriceOracle
priceoracle Map Text AmountStyle
styles Day
periodlast Day
today ValuationType
v =
(Posting -> Posting) -> Transaction -> Transaction
transactionTransformPostings (PriceOracle
-> Map Text AmountStyle
-> Day
-> Day
-> ValuationType
-> Posting
-> Posting
postingApplyValuation PriceOracle
priceoracle Map Text AmountStyle
styles Day
periodlast Day
today ValuationType
v)
transactionToCost :: M.Map CommoditySymbol AmountStyle -> ConversionOp -> Transaction -> Transaction
transactionToCost :: Map Text AmountStyle -> ConversionOp -> Transaction -> Transaction
transactionToCost Map Text AmountStyle
styles ConversionOp
cost Transaction
t = Transaction
t{tpostings :: [Posting]
tpostings = (Posting -> Maybe Posting) -> [Posting] -> [Posting]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Map Text AmountStyle -> ConversionOp -> Posting -> Maybe Posting
postingToCost Map Text AmountStyle
styles ConversionOp
cost) ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t}
transactionAddInferredEquityPostings :: AccountName -> Transaction -> Transaction
transactionAddInferredEquityPostings :: Text -> Transaction -> Transaction
transactionAddInferredEquityPostings Text
equityAcct Transaction
t =
Transaction
t{tpostings :: [Posting]
tpostings=(Posting -> [Posting]) -> [Posting] -> [Posting]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Text -> Posting -> [Posting]
postingAddInferredEquityPostings Text
equityAcct) ([Posting] -> [Posting]) -> [Posting] -> [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t}
type IdxPosting = (Int, Posting)
transactionAddPricesFromEquity :: M.Map AccountName AccountType -> Transaction -> Either String Transaction
transactionAddPricesFromEquity :: Map Text AccountType -> Transaction -> Either String Transaction
transactionAddPricesFromEquity Map Text AccountType
acctTypes Transaction
t = (Text -> String)
-> Either Text Transaction -> Either String Transaction
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (Transaction -> String -> String
annotateErrorWithTransaction Transaction
t (String -> String) -> (Text -> String) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack) (Either Text Transaction -> Either String Transaction)
-> Either Text Transaction -> Either String Transaction
forall a b. (a -> b) -> a -> b
$ do
([((Int, Posting), (Int, Posting))]
conversionPairs, ([(Int, Posting)], [(Int, Posting)])
stateps) <- [(Int, Posting)]
-> Either
Text
([((Int, Posting), (Int, Posting))],
([(Int, Posting)], [(Int, Posting)]))
forall a.
[(a, Posting)]
-> Either
Text
([((a, Posting), (a, Posting))], ([(a, Posting)], [(a, Posting)]))
partitionPs [(Int, Posting)]
npostings
(Int, Posting) -> (Int, Posting)
f <- (((Int, Posting), (Int, Posting))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting)))
-> [((Int, Posting), (Int, Posting))]
-> ([(Int, Posting)], [(Int, Posting)])
-> Either Text ((Int, Posting) -> (Int, Posting))
forall (m :: * -> *) (t :: * -> *) a s a.
(Monad m, Traversable t) =>
(a -> StateT s m (a -> a)) -> t a -> s -> m (a -> a)
transformIndexedPostingsF ((Int, Posting), (Int, Posting))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting))
addPricesToPostings [((Int, Posting), (Int, Posting))]
conversionPairs ([(Int, Posting)], [(Int, Posting)])
stateps
Transaction -> Either Text Transaction
forall (m :: * -> *) a. Monad m => a -> m a
return Transaction
t{tpostings :: [Posting]
tpostings = ((Int, Posting) -> Posting) -> [(Int, Posting)] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map ((Int, Posting) -> Posting
forall a b. (a, b) -> b
snd ((Int, Posting) -> Posting)
-> ((Int, Posting) -> (Int, Posting)) -> (Int, Posting) -> Posting
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, Posting) -> (Int, Posting)
f) [(Int, Posting)]
npostings}
where
npostings :: [(Int, Posting)]
npostings = [Int] -> [Posting] -> [(Int, Posting)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0..] ([Posting] -> [(Int, Posting)]) -> [Posting] -> [(Int, Posting)]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
transformIndexedPostingsF :: (a -> StateT s m (a -> a)) -> t a -> s -> m (a -> a)
transformIndexedPostingsF a -> StateT s m (a -> a)
f = StateT s m (a -> a) -> s -> m (a -> a)
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT (StateT s m (a -> a) -> s -> m (a -> a))
-> (t a -> StateT s m (a -> a)) -> t a -> s -> m (a -> a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (t (a -> a) -> a -> a)
-> StateT s m (t (a -> a)) -> StateT s m (a -> a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Endo a -> a -> a
forall a. Endo a -> a -> a
appEndo (Endo a -> a -> a)
-> (t (a -> a) -> Endo a) -> t (a -> a) -> a -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((a -> a) -> Endo a) -> t (a -> a) -> Endo a
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap (a -> a) -> Endo a
forall a. (a -> a) -> Endo a
Endo) (StateT s m (t (a -> a)) -> StateT s m (a -> a))
-> (t a -> StateT s m (t (a -> a))) -> t a -> StateT s m (a -> a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> StateT s m (a -> a)) -> t a -> StateT s m (t (a -> a))
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
traverse a -> StateT s m (a -> a)
f
partitionPs :: [(a, Posting)]
-> Either
Text
([((a, Posting), (a, Posting))], ([(a, Posting)], [(a, Posting)]))
partitionPs = ((([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> ([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])))
-> Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
Text
([((a, Posting), (a, Posting))], ([(a, Posting)], [(a, Posting)]))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> ([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)]))
forall a b. (a, b) -> a
fst (Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
Text
([((a, Posting), (a, Posting))], ([(a, Posting)], [(a, Posting)])))
-> ([(a, Posting)]
-> Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting)))
-> [(a, Posting)]
-> Either
Text
([((a, Posting), (a, Posting))], ([(a, Posting)], [(a, Posting)]))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((a, Posting)
-> (([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting)))
-> (([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> [(a, Posting)]
-> Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> b -> m b) -> b -> t a -> m b
foldrM (a, Posting)
-> (([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
Text
(([((a, Posting), (a, Posting))],
([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a a a.
IsString a =>
(a, Posting)
-> (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe a)
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
select (([], ([], [])), Maybe (a, Posting)
forall a. Maybe a
Nothing)
select :: (a, Posting)
-> (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe a)
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
select np :: (a, Posting)
np@(a
_, Posting
p) (([(a, (a, Posting))]
cs, others :: ([(a, Posting)], [(a, Posting)])
others@([(a, Posting)]
ps, [(a, Posting)]
os)), Maybe a
Nothing)
| Posting -> Bool
isConversion Posting
p = (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a b. b -> Either a b
Right (([(a, (a, Posting))]
cs, ([(a, Posting)], [(a, Posting)])
others), (a, Posting) -> Maybe (a, Posting)
forall a. a -> Maybe a
Just (a, Posting)
np)
| Posting -> Bool
hasPrice Posting
p = (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a b. b -> Either a b
Right (([(a, (a, Posting))]
cs, ((a, Posting)
np(a, Posting) -> [(a, Posting)] -> [(a, Posting)]
forall a. a -> [a] -> [a]
:[(a, Posting)]
ps, [(a, Posting)]
os)), Maybe (a, Posting)
forall a. Maybe a
Nothing)
| Bool
otherwise = (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a b. b -> Either a b
Right (([(a, (a, Posting))]
cs, ([(a, Posting)]
ps, (a, Posting)
np(a, Posting) -> [(a, Posting)] -> [(a, Posting)]
forall a. a -> [a] -> [a]
:[(a, Posting)]
os)), Maybe (a, Posting)
forall a. Maybe a
Nothing)
select np :: (a, Posting)
np@(a
_, Posting
p) (([(a, (a, Posting))]
cs, ([(a, Posting)], [(a, Posting)])
others), Just a
lst)
| Posting -> Bool
isConversion Posting
p = (([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a b. b -> Either a b
Right (((a
lst, (a, Posting)
np)(a, (a, Posting)) -> [(a, (a, Posting))] -> [(a, (a, Posting))]
forall a. a -> [a] -> [a]
:[(a, (a, Posting))]
cs, ([(a, Posting)], [(a, Posting)])
others), Maybe (a, Posting)
forall a. Maybe a
Nothing)
| Bool
otherwise = a
-> Either
a
(([(a, (a, Posting))], ([(a, Posting)], [(a, Posting)])),
Maybe (a, Posting))
forall a b. a -> Either a b
Left a
"Conversion postings must occur in adjacent pairs"
addPricesToPostings :: (IdxPosting, IdxPosting)
-> StateT ([IdxPosting], [IdxPosting]) (Either Text) (IdxPosting -> IdxPosting)
addPricesToPostings :: ((Int, Posting), (Int, Posting))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting))
addPricesToPostings ((Int
n1, Posting
cp1), (Int
n2, Posting
cp2)) = (([(Int, Posting)], [(Int, Posting)])
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)])))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting))
forall s (m :: * -> *) a. (s -> m (a, s)) -> StateT s m a
StateT ((([(Int, Posting)], [(Int, Posting)])
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)])))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting)))
-> (([(Int, Posting)], [(Int, Posting)])
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)])))
-> StateT
([(Int, Posting)], [(Int, Posting)])
(Either Text)
((Int, Posting) -> (Int, Posting))
forall a b. (a -> b) -> a -> b
$ \([(Int, Posting)]
priceps, [(Int, Posting)]
otherps) -> do
Amount
ca1 <- Posting -> Either Text Amount
postingAmountNoPrice Posting
cp1
Amount
ca2 <- Posting -> Either Text Amount
postingAmountNoPrice Posting
cp2
let
transformPostingF :: Int -> Posting -> (Int, Posting) -> (Int, Posting)
transformPostingF Int
np Posting
pricep (Int
n,Posting
p) =
(Int
n, if | Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
np -> Posting
pricep Posting -> [Tag] -> Posting
`postingAddTags` [(Text
"_price-matched",Text
"")]
| Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
n1 Bool -> Bool -> Bool
|| Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
n2 -> Posting
p Posting -> [Tag] -> Posting
`postingAddTags` [(Text
"_conversion-matched",Text
"")]
| Bool
otherwise -> Posting
p)
matchingPricePs :: [(Int, (Posting, Amount))]
matchingPricePs = ((Int, Posting) -> Maybe (Int, (Posting, Amount)))
-> [(Int, Posting)] -> [(Int, (Posting, Amount))]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ((Posting -> Maybe (Posting, Amount))
-> (Int, Posting) -> Maybe (Int, (Posting, Amount))
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM ((Posting -> Maybe (Posting, Amount))
-> (Int, Posting) -> Maybe (Int, (Posting, Amount)))
-> (Posting -> Maybe (Posting, Amount))
-> (Int, Posting)
-> Maybe (Int, (Posting, Amount))
forall a b. (a -> b) -> a -> b
$ Amount -> Amount -> Posting -> Maybe (Posting, Amount)
pricedPostingIfMatchesBothAmounts Amount
ca1 Amount
ca2) [(Int, Posting)]
priceps
matchingOtherPs :: [(Int, (Posting, Amount))]
matchingOtherPs = ((Int, Posting) -> Maybe (Int, (Posting, Amount)))
-> [(Int, Posting)] -> [(Int, (Posting, Amount))]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ((Posting -> Maybe (Posting, Amount))
-> (Int, Posting) -> Maybe (Int, (Posting, Amount))
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM ((Posting -> Maybe (Posting, Amount))
-> (Int, Posting) -> Maybe (Int, (Posting, Amount)))
-> (Posting -> Maybe (Posting, Amount))
-> (Int, Posting)
-> Maybe (Int, (Posting, Amount))
forall a b. (a -> b) -> a -> b
$ Amount -> Amount -> Posting -> Maybe (Posting, Amount)
addPriceIfMatchesOneAmount Amount
ca1 Amount
ca2) [(Int, Posting)]
otherps
(Text -> Text)
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ([Posting] -> Text -> Text
annotateWithPostings [Posting
cp1, Posting
cp2]) (Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)])))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
forall a b. (a -> b) -> a -> b
$
if
| [(Int
np, (Posting
pricep, Amount
_))] <- [(Int, (Posting, Amount))]
matchingPricePs
, Just [(Int, Posting)]
newpriceps <- Int -> [(Int, Posting)] -> Maybe [(Int, Posting)]
forall a b. Eq a => a -> [(a, b)] -> Maybe [(a, b)]
deleteIdx Int
np [(Int, Posting)]
priceps
-> ((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
forall a b. b -> Either a b
Right (Int -> Posting -> (Int, Posting) -> (Int, Posting)
transformPostingF Int
np Posting
pricep, ([(Int, Posting)]
newpriceps, [(Int, Posting)]
otherps))
| [] <- [(Int, (Posting, Amount))]
matchingPricePs
, (Int
np, (Posting
pricep, Amount
amt)):[(Int, (Posting, Amount))]
nps <- [(Int, (Posting, Amount))]
matchingOtherPs
, Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ ((Int, (Posting, Amount)) -> Bool)
-> [(Int, (Posting, Amount))] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Amount -> Amount -> Bool
amountMatches Amount
amt (Amount -> Bool)
-> ((Int, (Posting, Amount)) -> Amount)
-> (Int, (Posting, Amount))
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Posting, Amount) -> Amount
forall a b. (a, b) -> b
snd ((Posting, Amount) -> Amount)
-> ((Int, (Posting, Amount)) -> (Posting, Amount))
-> (Int, (Posting, Amount))
-> Amount
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, (Posting, Amount)) -> (Posting, Amount)
forall a b. (a, b) -> b
snd) [(Int, (Posting, Amount))]
nps
, Just [(Int, Posting)]
newotherps <- Int -> [(Int, Posting)] -> Maybe [(Int, Posting)]
forall a b. Eq a => a -> [(a, b)] -> Maybe [(a, b)]
deleteIdx Int
np [(Int, Posting)]
otherps
-> ((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
forall a b. b -> Either a b
Right (Int -> Posting -> (Int, Posting) -> (Int, Posting)
transformPostingF Int
np Posting
pricep, ([(Int, Posting)]
priceps, [(Int, Posting)]
newotherps))
| Bool
otherwise -> Text
-> Either
Text
((Int, Posting) -> (Int, Posting),
([(Int, Posting)], [(Int, Posting)]))
forall a b. a -> Either a b
Left Text
"There is not a unique posting which matches the conversion posting pair:"
pricedPostingIfMatchesBothAmounts :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
pricedPostingIfMatchesBothAmounts :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
pricedPostingIfMatchesBothAmounts Amount
a1 Amount
a2 Posting
p = do
a :: Amount
a@Amount{aprice :: Amount -> Maybe AmountPrice
aprice=Just AmountPrice
_} <- Posting -> Maybe Amount
postingSingleAmount Posting
p
if | Amount -> Amount -> Bool
amountMatches (-Amount
a1) Amount
a Bool -> Bool -> Bool
&& Amount -> Amount -> Bool
amountMatches Amount
a2 (Amount -> Amount
amountCost Amount
a) -> (Posting, Amount) -> Maybe (Posting, Amount)
forall a. a -> Maybe a
Just (Posting
p, -Amount
a2)
| Amount -> Amount -> Bool
amountMatches (-Amount
a2) Amount
a Bool -> Bool -> Bool
&& Amount -> Amount -> Bool
amountMatches Amount
a1 (Amount -> Amount
amountCost Amount
a) -> (Posting, Amount) -> Maybe (Posting, Amount)
forall a. a -> Maybe a
Just (Posting
p, -Amount
a1)
| Bool
otherwise -> Maybe (Posting, Amount)
forall a. Maybe a
Nothing
addPriceIfMatchesOneAmount :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
addPriceIfMatchesOneAmount :: Amount -> Amount -> Posting -> Maybe (Posting, Amount)
addPriceIfMatchesOneAmount Amount
a1 Amount
a2 Posting
p = do
Amount
a <- Posting -> Maybe Amount
postingSingleAmount Posting
p
let newp :: Amount -> Posting
newp Amount
price = Posting
p{pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount Amount
a{aprice :: Maybe AmountPrice
aprice = AmountPrice -> Maybe AmountPrice
forall a. a -> Maybe a
Just (AmountPrice -> Maybe AmountPrice)
-> AmountPrice -> Maybe AmountPrice
forall a b. (a -> b) -> a -> b
$ Amount -> AmountPrice
TotalPrice Amount
price}}
if | Amount -> Amount -> Bool
amountMatches (-Amount
a1) Amount
a -> (Posting, Amount) -> Maybe (Posting, Amount)
forall a. a -> Maybe a
Just (Amount -> Posting
newp Amount
a2, Amount
a2)
| Amount -> Amount -> Bool
amountMatches (-Amount
a2) Amount
a -> (Posting, Amount) -> Maybe (Posting, Amount)
forall a. a -> Maybe a
Just (Amount -> Posting
newp Amount
a1, Amount
a1)
| Bool
otherwise -> Maybe (Posting, Amount)
forall a. Maybe a
Nothing
hasPrice :: Posting -> Bool
hasPrice Posting
p = Maybe AmountPrice -> Bool
forall a. Maybe a -> Bool
isJust (Maybe AmountPrice -> Bool) -> Maybe AmountPrice -> Bool
forall a b. (a -> b) -> a -> b
$ Amount -> Maybe AmountPrice
aprice (Amount -> Maybe AmountPrice) -> Maybe Amount -> Maybe AmountPrice
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Posting -> Maybe Amount
postingSingleAmount Posting
p
postingAmountNoPrice :: Posting -> Either Text Amount
postingAmountNoPrice Posting
p = case Posting -> Maybe Amount
postingSingleAmount Posting
p of
Just a :: Amount
a@Amount{aprice :: Amount -> Maybe AmountPrice
aprice=Maybe AmountPrice
Nothing} -> Amount -> Either Text Amount
forall a b. b -> Either a b
Right Amount
a
Maybe Amount
_ -> Text -> Either Text Amount
forall a b. a -> Either a b
Left (Text -> Either Text Amount) -> Text -> Either Text Amount
forall a b. (a -> b) -> a -> b
$ [Posting] -> Text -> Text
annotateWithPostings [Posting
p] Text
"The posting must only have a single amount with no transaction price"
postingSingleAmount :: Posting -> Maybe Amount
postingSingleAmount Posting
p = case MixedAmount -> [Amount]
amountsRaw (Posting -> MixedAmount
pamount Posting
p) of
[Amount
a] -> Amount -> Maybe Amount
forall a. a -> Maybe a
Just Amount
a
[Amount]
_ -> Maybe Amount
forall a. Maybe a
Nothing
amountMatches :: Amount -> Amount -> Bool
amountMatches Amount
a Amount
b = Amount -> Text
acommodity Amount
a Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> Text
acommodity Amount
b Bool -> Bool -> Bool
&& Amount -> Quantity
aquantity Amount
a Quantity -> Quantity -> Bool
forall a. Eq a => a -> a -> Bool
== Amount -> Quantity
aquantity Amount
b
isConversion :: Posting -> Bool
isConversion Posting
p = Text -> Map Text AccountType -> Maybe AccountType
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (Posting -> Text
paccount Posting
p) Map Text AccountType
acctTypes Maybe AccountType -> Maybe AccountType -> Bool
forall a. Eq a => a -> a -> Bool
== AccountType -> Maybe AccountType
forall a. a -> Maybe a
Just AccountType
Conversion
deleteIdx :: a -> [(a, b)] -> Maybe [(a, b)]
deleteIdx a
n = ((a, b) -> Bool) -> [(a, b)] -> Maybe [(a, b)]
forall a. (a -> Bool) -> [a] -> Maybe [a]
deleteUniqueMatch ((a
na -> a -> Bool
forall a. Eq a => a -> a -> Bool
==) (a -> Bool) -> ((a, b) -> a) -> (a, b) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a, b) -> a
forall a b. (a, b) -> a
fst)
deleteUniqueMatch :: (a -> Bool) -> [a] -> Maybe [a]
deleteUniqueMatch a -> Bool
p (a
x:[a]
xs) | a -> Bool
p a
x = if (a -> Bool) -> [a] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any a -> Bool
p [a]
xs then Maybe [a]
forall a. Maybe a
Nothing else [a] -> Maybe [a]
forall a. a -> Maybe a
Just [a]
xs
| Bool
otherwise = (a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:) ([a] -> [a]) -> Maybe [a] -> Maybe [a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (a -> Bool) -> [a] -> Maybe [a]
deleteUniqueMatch a -> Bool
p [a]
xs
deleteUniqueMatch a -> Bool
_ [] = Maybe [a]
forall a. Maybe a
Nothing
annotateWithPostings :: [Posting] -> Text -> Text
annotateWithPostings [Posting]
xs Text
str = [Text] -> Text
T.unlines ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ Text
str Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Bool -> [Posting] -> [Text]
postingsAsLines Bool
False [Posting]
xs
transactionApplyAliases :: [AccountAlias] -> Transaction -> Either RegexError Transaction
transactionApplyAliases :: [AccountAlias] -> Transaction -> Either String Transaction
transactionApplyAliases [AccountAlias]
aliases Transaction
t =
case (Posting -> Either String Posting)
-> [Posting] -> Either String [Posting]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM ([AccountAlias] -> Posting -> Either String Posting
postingApplyAliases [AccountAlias]
aliases) ([Posting] -> Either String [Posting])
-> [Posting] -> Either String [Posting]
forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t of
Right [Posting]
ps -> Transaction -> Either String Transaction
forall a b. b -> Either a b
Right (Transaction -> Either String Transaction)
-> Transaction -> Either String Transaction
forall a b. (a -> b) -> a -> b
$ Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$ Transaction
t{tpostings :: [Posting]
tpostings=[Posting]
ps}
Left String
err -> String -> Either String Transaction
forall a b. a -> Either a b
Left String
err
transactionMapPostings :: (Posting -> Posting) -> Transaction -> Transaction
transactionMapPostings :: (Posting -> Posting) -> Transaction -> Transaction
transactionMapPostings Posting -> Posting
f t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps} = Transaction
t{tpostings :: [Posting]
tpostings=(Posting -> Posting) -> [Posting] -> [Posting]
forall a b. (a -> b) -> [a] -> [b]
map Posting -> Posting
f [Posting]
ps}
transactionMapPostingAmounts :: (MixedAmount -> MixedAmount) -> Transaction -> Transaction
transactionMapPostingAmounts :: (MixedAmount -> MixedAmount) -> Transaction -> Transaction
transactionMapPostingAmounts MixedAmount -> MixedAmount
f = (Posting -> Posting) -> Transaction -> Transaction
transactionMapPostings ((MixedAmount -> MixedAmount) -> Posting -> Posting
postingTransformAmount MixedAmount -> MixedAmount
f)
transactionFile :: Transaction -> FilePath
transactionFile :: Transaction -> String
transactionFile Transaction{(SourcePos, SourcePos)
tsourcepos :: (SourcePos, SourcePos)
tsourcepos :: Transaction -> (SourcePos, SourcePos)
tsourcepos} = SourcePos -> String
sourceName (SourcePos -> String) -> SourcePos -> String
forall a b. (a -> b) -> a -> b
$ (SourcePos, SourcePos) -> SourcePos
forall a b. (a, b) -> a
fst (SourcePos, SourcePos)
tsourcepos
annotateErrorWithTransaction :: Transaction -> String -> String
annotateErrorWithTransaction :: Transaction -> String -> String
annotateErrorWithTransaction Transaction
t String
s =
[String] -> String
unlines [ (SourcePos, SourcePos) -> String
sourcePosPairPretty ((SourcePos, SourcePos) -> String)
-> (SourcePos, SourcePos) -> String
forall a b. (a -> b) -> a -> b
$ Transaction -> (SourcePos, SourcePos)
tsourcepos Transaction
t, String
s
, Text -> String
T.unpack (Text -> String) -> (Text -> Text) -> Text -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
T.stripEnd (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
showTransaction Transaction
t
]
tests_Transaction :: TestTree
tests_Transaction :: TestTree
tests_Transaction =
String -> [TestTree] -> TestTree
testGroup String
"Transaction" [
String -> [TestTree] -> TestTree
testGroup String
"showPostingLines" [
String -> Assertion -> TestTree
testCase String
"null posting" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Posting -> [Text]
showPostingLines Posting
nullposting [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [Text
" 0"]
, String -> Assertion -> TestTree
testCase String
"non-null posting" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
let p :: Posting
p =
Posting
posting
{ pstatus :: Status
pstatus = Status
Cleared
, paccount :: Text
paccount = Text
"a"
, pamount :: MixedAmount
pamount = [Amount] -> MixedAmount
forall (t :: * -> *). Foldable t => t Amount -> MixedAmount
mixed [Quantity -> Amount
usd Quantity
1, Quantity -> Amount
hrs Quantity
2]
, pcomment :: Text
pcomment = Text
"pcomment1\npcomment2\n tag3: val3 \n"
, ptype :: PostingType
ptype = PostingType
RegularPosting
, ptags :: [Tag]
ptags = [(Text
"ptag1", Text
"val1"), (Text
"ptag2", Text
"val2")]
}
in Posting -> [Text]
showPostingLines Posting
p [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" * a $1.00 ; pcomment1"
, Text
" ; pcomment2"
, Text
" ; tag3: val3 "
, Text
" * a 2.00h ; pcomment1"
, Text
" ; pcomment2"
, Text
" ; tag3: val3 "
]
]
, let
timp :: Transaction
timp = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"a" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1, Text
"b" Text -> Amount -> Posting
`post` Amount
missingamt]}
texp :: Transaction
texp = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"a" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1, Text
"b" Text -> Amount -> Posting
`post` Quantity -> Amount
usd (-Quantity
1)]}
texp1 :: Transaction
texp1 = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"(a)" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1]}
texp2 :: Transaction
texp2 = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"a" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1, Text
"b" Text -> Amount -> Posting
`post` (Quantity -> Amount
hrs (-Quantity
1) Amount -> Amount -> Amount
`at` Quantity -> Amount
usd Quantity
1)]}
texp2b :: Transaction
texp2b = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"a" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1, Text
"b" Text -> Amount -> Posting
`post` Quantity -> Amount
hrs (-Quantity
1)]}
t3 :: Transaction
t3 = Transaction
nulltransaction {tpostings :: [Posting]
tpostings = [Text
"a" Text -> Amount -> Posting
`post` Quantity -> Amount
usd Quantity
1, Text
"b" Text -> Amount -> Posting
`post` Amount
missingamt, Text
"c" Text -> Amount -> Posting
`post` Quantity -> Amount
usd (-Quantity
1)]}
in String -> [TestTree] -> TestTree
testGroup String
"postingsAsLines" [
String -> Assertion -> TestTree
testCase String
"null-transaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
nulltransaction) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= []
, String -> Assertion -> TestTree
testCase String
"implicit-amount" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
timp) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" a $1.00"
, Text
" b"
]
, String -> Assertion -> TestTree
testCase String
"explicit-amounts" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
texp) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" a $1.00"
, Text
" b $-1.00"
]
, String -> Assertion -> TestTree
testCase String
"one-explicit-amount" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
texp1) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" (a) $1.00"
]
, String -> Assertion -> TestTree
testCase String
"explicit-amounts-two-commodities" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
texp2) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" a $1.00"
, Text
" b -1.00h @ $1.00"
]
, String -> Assertion -> TestTree
testCase String
"explicit-amounts-not-explicitly-balanced" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
texp2b) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[ Text
" a $1.00"
, Text
" b -1.00h"
]
, String -> Assertion -> TestTree
testCase String
"implicit-amount-not-last" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Bool -> [Posting] -> [Text]
postingsAsLines Bool
False (Transaction -> [Posting]
tpostings Transaction
t3) [Text] -> [Text] -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[Text
" a $1.00", Text
" b", Text
" c $-1.00"]
]
, String -> [TestTree] -> TestTree
testGroup String
"showTransaction" [
String -> Assertion -> TestTree
testCase String
"null transaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
showTransaction Transaction
nulltransaction Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= Text
"0000-01-01\n\n"
, String -> Assertion -> TestTree
testCase String
"non-null transaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$ Transaction -> Text
showTransaction
Transaction
nulltransaction
{ tdate :: Day
tdate = Integer -> Int -> Int -> Day
fromGregorian Integer
2012 Int
05 Int
14
, tdate2 :: Maybe Day
tdate2 = Day -> Maybe Day
forall a. a -> Maybe a
Just (Day -> Maybe Day) -> Day -> Maybe Day
forall a b. (a -> b) -> a -> b
$ Integer -> Int -> Int -> Day
fromGregorian Integer
2012 Int
05 Int
15
, tstatus :: Status
tstatus = Status
Unmarked
, tcode :: Text
tcode = Text
"code"
, tdescription :: Text
tdescription = Text
"desc"
, tcomment :: Text
tcomment = Text
"tcomment1\ntcomment2\n"
, ttags :: [Tag]
ttags = [(Text
"ttag1", Text
"val1")]
, tpostings :: [Posting]
tpostings =
[ Posting
nullposting
{ pstatus :: Status
pstatus = Status
Cleared
, paccount :: Text
paccount = Text
"a"
, pamount :: MixedAmount
pamount = [Amount] -> MixedAmount
forall (t :: * -> *). Foldable t => t Amount -> MixedAmount
mixed [Quantity -> Amount
usd Quantity
1, Quantity -> Amount
hrs Quantity
2]
, pcomment :: Text
pcomment = Text
"\npcomment2\n"
, ptype :: PostingType
ptype = PostingType
RegularPosting
, ptags :: [Tag]
ptags = [(Text
"ptag1", Text
"val1"), (Text
"ptag2", Text
"val2")]
}
]
} Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
[Text] -> Text
T.unlines
[ Text
"2012-05-14=2012-05-15 (code) desc ; tcomment1"
, Text
" ; tcomment2"
, Text
" * a $1.00"
, Text
" ; pcomment2"
, Text
" * a 2.00h"
, Text
" ; pcomment2"
, Text
""
]
, String -> Assertion -> TestTree
testCase String
"show a balanced transaction" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
(let t :: Transaction
t =
Integer
-> Text
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> Text
-> Text
-> Text
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
Text
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
Maybe Day
forall a. Maybe a
Nothing
Status
Unmarked
Text
""
Text
"coopportunity"
Text
""
[]
[ Posting
posting {paccount :: Text
paccount = Text
"expenses:food:groceries", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (Quantity -> Amount
usd Quantity
47.18), ptransaction :: Maybe Transaction
ptransaction = Transaction -> Maybe Transaction
forall a. a -> Maybe a
Just Transaction
t}
, Posting
posting {paccount :: Text
paccount = Text
"assets:checking", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (Quantity -> Amount
usd (-Quantity
47.18)), ptransaction :: Maybe Transaction
ptransaction = Transaction -> Maybe Transaction
forall a. a -> Maybe a
Just Transaction
t}
]
in Transaction -> Text
showTransaction Transaction
t) Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
([Text] -> Text
T.unlines
[ Text
"2007-01-28 coopportunity"
, Text
" expenses:food:groceries $47.18"
, Text
" assets:checking $-47.18"
, Text
""
])
, String -> Assertion -> TestTree
testCase String
"show an unbalanced transaction, should not elide" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
(Transaction -> Text
showTransaction
(Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$
Integer
-> Text
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> Text
-> Text
-> Text
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
Text
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
Maybe Day
forall a. Maybe a
Nothing
Status
Unmarked
Text
""
Text
"coopportunity"
Text
""
[]
[ Posting
posting {paccount :: Text
paccount = Text
"expenses:food:groceries", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (Quantity -> Amount
usd Quantity
47.18)}
, Posting
posting {paccount :: Text
paccount = Text
"assets:checking", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (Quantity -> Amount
usd (-Quantity
47.19))}
])) Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
([Text] -> Text
T.unlines
[ Text
"2007-01-28 coopportunity"
, Text
" expenses:food:groceries $47.18"
, Text
" assets:checking $-47.19"
, Text
""
])
, String -> Assertion -> TestTree
testCase String
"show a transaction with one posting and a missing amount" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
(Transaction -> Text
showTransaction
(Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$
Integer
-> Text
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> Text
-> Text
-> Text
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
Text
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
Maybe Day
forall a. Maybe a
Nothing
Status
Unmarked
Text
""
Text
"coopportunity"
Text
""
[]
[Posting
posting {paccount :: Text
paccount = Text
"expenses:food:groceries", pamount :: MixedAmount
pamount = MixedAmount
missingmixedamt}])) Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
([Text] -> Text
T.unlines [Text
"2007-01-28 coopportunity", Text
" expenses:food:groceries", Text
""])
, String -> Assertion -> TestTree
testCase String
"show a transaction with a priced commodityless amount" (Assertion -> TestTree) -> Assertion -> TestTree
forall a b. (a -> b) -> a -> b
$
(Transaction -> Text
showTransaction
(Transaction -> Transaction
txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
forall a b. (a -> b) -> a -> b
$
Integer
-> Text
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> Text
-> Text
-> Text
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
Text
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2010 Int
01 Int
01)
Maybe Day
forall a. Maybe a
Nothing
Status
Unmarked
Text
""
Text
"x"
Text
""
[]
[ Posting
posting {paccount :: Text
paccount = Text
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
forall a b. (a -> b) -> a -> b
$ Quantity -> Amount
num Quantity
1 Amount -> Amount -> Amount
`at` (Quantity -> Amount
usd Quantity
2 Amount -> AmountPrecision -> Amount
`withPrecision` Word8 -> AmountPrecision
Precision Word8
0)}
, Posting
posting {paccount :: Text
paccount = Text
"b", pamount :: MixedAmount
pamount = MixedAmount
missingmixedamt}
])) Text -> Text -> Assertion
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
([Text] -> Text
T.unlines [Text
"2010-01-01 x", Text
" a 1 @ $2", Text
" b", Text
""])
]
]