{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module Hledger.Data.Balancing
(
BalancingOpts(..)
, HasBalancingOpts(..)
, defbalancingopts
, isTransactionBalanced
, balanceTransaction
, balanceTransactionHelper
, journalBalanceTransactions
, journalCheckBalanceAssertions
, tests_Balancing
)
where
import Control.Monad.Except (ExceptT(..), runExceptT, throwError)
import "extra" Control.Monad.Extra (whenM)
import Control.Monad.Reader as R
import Control.Monad.ST (ST, runST)
import Data.Array.ST (STArray, getElems, newListArray, writeArray)
import Data.Foldable (asum)
import Data.Function ((&))
import qualified Data.HashTable.Class as H (toList)
import qualified Data.HashTable.ST.Cuckoo as H
import Data.List (partition, sortOn)
import Data.List.Extra (nubSort)
import Data.Maybe (fromJust, fromMaybe, isJust, isNothing, mapMaybe)
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Time.Calendar (fromGregorian)
import qualified Data.Map as M
import Safe (headDef)
import Text.Printf (printf)
import Hledger.Utils
import Hledger.Data.Types
import Hledger.Data.AccountName (isAccountNamePrefixOf)
import Hledger.Data.Amount
import Hledger.Data.Journal
import Hledger.Data.Posting
import Hledger.Data.Transaction
import Hledger.Data.Errors
data BalancingOpts = BalancingOpts
{ BalancingOpts -> Bool
ignore_assertions_ :: Bool
, BalancingOpts -> Bool
infer_transaction_prices_ :: Bool
, BalancingOpts -> Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ :: Maybe (M.Map CommoditySymbol AmountStyle)
} deriving (Int -> BalancingOpts -> String -> String
[BalancingOpts] -> String -> String
BalancingOpts -> String
forall a.
(Int -> a -> String -> String)
-> (a -> String) -> ([a] -> String -> String) -> Show a
showList :: [BalancingOpts] -> String -> String
$cshowList :: [BalancingOpts] -> String -> String
show :: BalancingOpts -> String
$cshow :: BalancingOpts -> String
showsPrec :: Int -> BalancingOpts -> String -> String
$cshowsPrec :: Int -> BalancingOpts -> String -> String
Show)
defbalancingopts :: BalancingOpts
defbalancingopts :: BalancingOpts
defbalancingopts = BalancingOpts
{ ignore_assertions_ :: Bool
ignore_assertions_ = Bool
False
, infer_transaction_prices_ :: Bool
infer_transaction_prices_ = Bool
True
, commodity_styles_ :: Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ = forall a. Maybe a
Nothing
}
transactionCheckBalanced :: BalancingOpts -> Transaction -> [String]
transactionCheckBalanced :: BalancingOpts -> Transaction -> [String]
transactionCheckBalanced BalancingOpts{Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ :: Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ :: BalancingOpts -> Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_} Transaction
t = [String]
errs
where
([Posting]
rps, [Posting]
bvps) = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Posting -> ([Posting], [Posting]) -> ([Posting], [Posting])
partitionPosting ([], []) forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
where
partitionPosting :: Posting -> ([Posting], [Posting]) -> ([Posting], [Posting])
partitionPosting Posting
p ~([Posting]
l, [Posting]
r) = case Posting -> PostingType
ptype Posting
p of
PostingType
RegularPosting -> (Posting
pforall a. a -> [a] -> [a]
:[Posting]
l, [Posting]
r)
PostingType
BalancedVirtualPosting -> ([Posting]
l, Posting
pforall a. a -> [a] -> [a]
:[Posting]
r)
PostingType
VirtualPosting -> ([Posting]
l, [Posting]
r)
canonicalise :: MixedAmount -> MixedAmount
canonicalise = forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. a -> a
id Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
canonicaliseMixedAmount Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_
postingBalancingAmount :: Posting -> MixedAmount
postingBalancingAmount Posting
p
| CommoditySymbol
"_price-matched" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst (Posting -> [Tag]
ptags Posting
p) = MixedAmount -> MixedAmount
mixedAmountStripPrices forall a b. (a -> b) -> a -> b
$ Posting -> MixedAmount
pamount Posting
p
| Bool
otherwise = MixedAmount -> MixedAmount
mixedAmountCost forall a b. (a -> b) -> a -> b
$ Posting -> MixedAmount
pamount Posting
p
signsOk :: [Posting] -> Bool
signsOk [Posting]
ps =
case forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
notforall b c a. (b -> c) -> (a -> b) -> a -> c
.MixedAmount -> Bool
mixedAmountLooksZero) forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (MixedAmount -> MixedAmount
canonicaliseforall b c a. (b -> c) -> (a -> b) -> a -> c
.Posting -> MixedAmount
postingBalancingAmount) [Posting]
ps of
[MixedAmount]
nonzeros | forall (t :: * -> *) a. Foldable t => t a -> Int
length [MixedAmount]
nonzeros forall a. Ord a => a -> a -> Bool
>= Int
2
-> forall (t :: * -> *) a. Foldable t => t a -> Int
length (forall a. Ord a => [a] -> [a]
nubSort forall a b. (a -> b) -> a -> b
$ forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe MixedAmount -> Maybe Bool
isNegativeMixedAmount [MixedAmount]
nonzeros) forall a. Ord a => a -> a -> Bool
> Int
1
[MixedAmount]
_ -> Bool
True
(Bool
rsignsok, Bool
bvsignsok) = ([Posting] -> Bool
signsOk [Posting]
rps, [Posting] -> Bool
signsOk [Posting]
bvps)
(MixedAmount
rsumcost, MixedAmount
bvsumcost) = (forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap Posting -> MixedAmount
postingBalancingAmount [Posting]
rps, forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap Posting -> MixedAmount
postingBalancingAmount [Posting]
bvps)
(MixedAmount
rsumdisplay, MixedAmount
bvsumdisplay) = (MixedAmount -> MixedAmount
canonicalise MixedAmount
rsumcost, MixedAmount -> MixedAmount
canonicalise MixedAmount
bvsumcost)
(Bool
rsumok, Bool
bvsumok) = (MixedAmount -> Bool
mixedAmountLooksZero MixedAmount
rsumdisplay, MixedAmount -> Bool
mixedAmountLooksZero MixedAmount
bvsumdisplay)
errs :: [String]
errs = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
notforall b c a. (b -> c) -> (a -> b) -> a -> c
.forall (t :: * -> *) a. Foldable t => t a -> Bool
null) [String
rmsg, String
bvmsg]
where
rmsg :: String
rmsg
| Bool
rsumok = String
""
| Bool -> Bool
not Bool
rsignsok = String
"The real postings all have the same sign. Consider negating some of them."
| Bool
otherwise = String
"The real postings' sum should be 0 but is: " forall a. [a] -> [a] -> [a]
++ Bool -> MixedAmount -> String
showMixedAmountOneLineWithoutPrice Bool
False MixedAmount
rsumcost
bvmsg :: String
bvmsg
| Bool
bvsumok = String
""
| Bool -> Bool
not Bool
bvsignsok = String
"The balanced virtual postings all have the same sign. Consider negating some of them."
| Bool
otherwise = String
"The balanced virtual postings' sum should be 0 but is: " forall a. [a] -> [a] -> [a]
++ Bool -> MixedAmount -> String
showMixedAmountOneLineWithoutPrice Bool
False MixedAmount
bvsumcost
isTransactionBalanced :: BalancingOpts -> Transaction -> Bool
isTransactionBalanced :: BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
bopts = forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall b c a. (b -> c) -> (a -> b) -> a -> c
. BalancingOpts -> Transaction -> [String]
transactionCheckBalanced BalancingOpts
bopts
balanceTransaction ::
BalancingOpts
-> Transaction
-> Either String Transaction
balanceTransaction :: BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
bopts = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> a
fst forall b c a. (b -> c) -> (a -> b) -> a -> c
. BalancingOpts
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
balanceTransactionHelper BalancingOpts
bopts
balanceTransactionHelper ::
BalancingOpts
-> Transaction
-> Either String (Transaction, [(AccountName, MixedAmount)])
balanceTransactionHelper :: BalancingOpts
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
balanceTransactionHelper BalancingOpts
bopts Transaction
t = do
(Transaction
t', [(CommoditySymbol, MixedAmount)]
inferredamtsandaccts) <- Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
inferBalancingAmount (forall a. a -> Maybe a -> a
fromMaybe forall k a. Map k a
M.empty forall a b. (a -> b) -> a -> b
$ BalancingOpts -> Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_ BalancingOpts
bopts) forall a b. (a -> b) -> a -> b
$
if BalancingOpts -> Bool
infer_transaction_prices_ BalancingOpts
bopts then Transaction -> Transaction
inferBalancingPrices Transaction
t else Transaction
t
case BalancingOpts -> Transaction -> [String]
transactionCheckBalanced BalancingOpts
bopts Transaction
t' of
[] -> forall a b. b -> Either a b
Right (Transaction -> Transaction
txnTieKnot Transaction
t', [(CommoditySymbol, MixedAmount)]
inferredamtsandaccts)
[String]
errs -> forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ Transaction -> [String] -> String
transactionBalanceError Transaction
t' [String]
errs'
where
ismulticommodity :: Bool
ismulticommodity = (forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Transaction -> Set CommoditySymbol
transactionCommodities Transaction
t') forall a. Ord a => a -> a -> Bool
> Int
1
errs' :: [String]
errs' =
[ String
"Automatic commodity conversion is not enabled."
| Bool
ismulticommodity Bool -> Bool -> Bool
&& Bool -> Bool
not (BalancingOpts -> Bool
infer_transaction_prices_ BalancingOpts
bopts)
] forall a. [a] -> [a] -> [a]
++
[String]
errs forall a. [a] -> [a] -> [a]
++
if Bool
ismulticommodity
then
[ String
"Consider adjusting this entry's amounts, adding missing postings,"
, String
"or recording conversion price(s) with @, @@ or equity postings."
]
else
[ String
"Consider adjusting this entry's amounts, or adding missing postings."
]
transactionCommodities :: Transaction -> S.Set CommoditySymbol
transactionCommodities :: Transaction -> Set CommoditySymbol
transactionCommodities Transaction
t = forall a. Monoid a => [a] -> a
mconcat forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (MixedAmount -> Set CommoditySymbol
maCommodities forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> MixedAmount
pamount) forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
transactionBalanceError :: Transaction -> [String] -> String
transactionBalanceError :: Transaction -> [String] -> String
transactionBalanceError Transaction
t [String]
errs = forall r. PrintfType r => String -> r
printf String
"%s:\n%s\n\nThis %stransaction is unbalanced.\n%s"
((SourcePos, SourcePos) -> String
sourcePosPairPretty forall a b. (a -> b) -> a -> b
$ Transaction -> (SourcePos, SourcePos)
tsourcepos Transaction
t)
(CommoditySymbol -> CommoditySymbol
textChomp CommoditySymbol
ex)
(if Bool
ismulticommodity then String
"multi-commodity " else String
"" :: String)
(String -> String
chomp forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines [String]
errs)
where
ismulticommodity :: Bool
ismulticommodity = (forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Transaction -> Set CommoditySymbol
transactionCommodities Transaction
t) forall a. Ord a => a -> a -> Bool
> Int
1
(String
_f,Int
_l,Maybe (Int, Maybe Int)
_mcols,CommoditySymbol
ex) = Transaction
-> (Transaction -> Maybe (Int, Maybe Int))
-> (String, Int, Maybe (Int, Maybe Int), CommoditySymbol)
makeTransactionErrorExcerpt Transaction
t forall {p} {a}. p -> Maybe a
finderrcols
where
finderrcols :: p -> Maybe a
finderrcols p
_ = forall a. Maybe a
Nothing
inferBalancingAmount ::
M.Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(AccountName, MixedAmount)])
inferBalancingAmount :: Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
inferBalancingAmount Map CommoditySymbol AmountStyle
styles t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps}
| forall (t :: * -> *) a. Foldable t => t a -> Int
length [Posting]
amountlessrealps forall a. Ord a => a -> a -> Bool
> Int
1
= forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ Transaction -> [String] -> String
transactionBalanceError Transaction
t
[String
"There can't be more than one real posting with no amount."
,String
"(Remember to put two or more spaces between account and amount.)"]
| forall (t :: * -> *) a. Foldable t => t a -> Int
length [Posting]
amountlessbvps forall a. Ord a => a -> a -> Bool
> Int
1
= forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ Transaction -> [String] -> String
transactionBalanceError Transaction
t
[String
"There can't be more than one balanced virtual posting with no amount."
,String
"(Remember to put two or more spaces between account and amount.)"]
| Bool
otherwise
= let psandinferredamts :: [(Posting, Maybe MixedAmount)]
psandinferredamts = forall a b. (a -> b) -> [a] -> [b]
map Posting -> (Posting, Maybe MixedAmount)
inferamount [Posting]
ps
inferredacctsandamts :: [(CommoditySymbol, MixedAmount)]
inferredacctsandamts = [(Posting -> CommoditySymbol
paccount Posting
p, MixedAmount
amt) | (Posting
p, Just MixedAmount
amt) <- [(Posting, Maybe MixedAmount)]
psandinferredamts]
in forall a b. b -> Either a b
Right (Transaction
t{tpostings :: [Posting]
tpostings=forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst [(Posting, Maybe MixedAmount)]
psandinferredamts}, [(CommoditySymbol, MixedAmount)]
inferredacctsandamts)
where
([Posting]
amountfulrealps, [Posting]
amountlessrealps) = forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Posting -> Bool
hasAmount (Transaction -> [Posting]
realPostings Transaction
t)
realsum :: MixedAmount
realsum = [Posting] -> MixedAmount
sumPostings [Posting]
amountfulrealps
([Posting]
amountfulbvps, [Posting]
amountlessbvps) = forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Posting -> Bool
hasAmount (Transaction -> [Posting]
balancedVirtualPostings Transaction
t)
bvsum :: MixedAmount
bvsum = [Posting] -> MixedAmount
sumPostings [Posting]
amountfulbvps
inferamount :: Posting -> (Posting, Maybe MixedAmount)
inferamount :: Posting -> (Posting, Maybe MixedAmount)
inferamount Posting
p =
let
minferredamt :: Maybe MixedAmount
minferredamt = case Posting -> PostingType
ptype Posting
p of
PostingType
RegularPosting | Bool -> Bool
not (Posting -> Bool
hasAmount Posting
p) -> forall a. a -> Maybe a
Just MixedAmount
realsum
PostingType
BalancedVirtualPosting | Bool -> Bool
not (Posting -> Bool
hasAmount Posting
p) -> forall a. a -> Maybe a
Just MixedAmount
bvsum
PostingType
_ -> forall a. Maybe a
Nothing
in
case Maybe MixedAmount
minferredamt of
Maybe MixedAmount
Nothing -> (Posting
p, forall a. Maybe a
Nothing)
Just MixedAmount
a -> (Posting
p{pamount :: MixedAmount
pamount=MixedAmount
a', poriginal :: Maybe Posting
poriginal=forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Posting -> Posting
originalPosting Posting
p}, forall a. a -> Maybe a
Just MixedAmount
a')
where
a' :: MixedAmount
a' = Map CommoditySymbol AmountStyle -> MixedAmount -> MixedAmount
styleMixedAmount Map CommoditySymbol AmountStyle
styles forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> MixedAmount
mixedAmountCost forall a b. (a -> b) -> a -> b
$ MixedAmount -> MixedAmount
maNegate MixedAmount
a
inferBalancingPrices :: Transaction -> Transaction
inferBalancingPrices :: Transaction -> Transaction
inferBalancingPrices t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps} = Transaction
t{tpostings :: [Posting]
tpostings=[Posting]
ps'}
where
ps' :: [Posting]
ps' = forall a b. (a -> b) -> [a] -> [b]
map (Transaction -> PostingType -> Posting -> Posting
priceInferrerFor Transaction
t PostingType
BalancedVirtualPosting forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> PostingType -> Posting -> Posting
priceInferrerFor Transaction
t PostingType
RegularPosting) [Posting]
ps
priceInferrerFor :: Transaction -> PostingType -> (Posting -> Posting)
priceInferrerFor :: Transaction -> PostingType -> Posting -> Posting
priceInferrerFor Transaction
t PostingType
pt = forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. a -> a
id (Amount, Amount) -> Posting -> Posting
inferprice Maybe (Amount, Amount)
inferFromAndTo
where
postings :: [Posting]
postings = forall a. (a -> Bool) -> [a] -> [a]
filter ((forall a. Eq a => a -> a -> Bool
==PostingType
pt)forall b c a. (b -> c) -> (a -> b) -> a -> c
.Posting -> PostingType
ptype) forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t
pcommodities :: [CommoditySymbol]
pcommodities = forall a b. (a -> b) -> [a] -> [b]
map Amount -> CommoditySymbol
acommodity forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (MixedAmount -> [Amount]
amounts forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> MixedAmount
pamount) [Posting]
postings
sumamounts :: [Amount]
sumamounts = MixedAmount -> [Amount]
amounts forall a b. (a -> b) -> a -> b
$ [Posting] -> MixedAmount
sumPostings [Posting]
postings
inferFromAndTo :: Maybe (Amount, Amount)
inferFromAndTo = case [Amount]
sumamounts of
[Amount
a,Amount
b] | Bool
noprices, Bool
oppositesigns -> forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map CommoditySymbol -> Maybe (Amount, Amount)
orderIfMatches [CommoditySymbol]
pcommodities
where
noprices :: Bool
noprices = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall a. Maybe a -> Bool
isNothing forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> Maybe AmountPrice
aprice) [Amount]
sumamounts
oppositesigns :: Bool
oppositesigns = forall a. Num a => a -> a
signum (Amount -> DecimalRaw Integer
aquantity Amount
a) forall a. Eq a => a -> a -> Bool
/= forall a. Num a => a -> a
signum (Amount -> DecimalRaw Integer
aquantity Amount
b)
orderIfMatches :: CommoditySymbol -> Maybe (Amount, Amount)
orderIfMatches CommoditySymbol
x | CommoditySymbol
x forall a. Eq a => a -> a -> Bool
== Amount -> CommoditySymbol
acommodity Amount
a = forall a. a -> Maybe a
Just (Amount
a,Amount
b)
| CommoditySymbol
x forall a. Eq a => a -> a -> Bool
== Amount -> CommoditySymbol
acommodity Amount
b = forall a. a -> Maybe a
Just (Amount
b,Amount
a)
| Bool
otherwise = forall a. Maybe a
Nothing
[Amount]
_ -> forall a. Maybe a
Nothing
inferprice :: (Amount, Amount) -> Posting -> Posting
inferprice (Amount
fromamount, Amount
toamount) Posting
p
| [Amount
a] <- MixedAmount -> [Amount]
amounts (Posting -> MixedAmount
pamount Posting
p), Posting -> PostingType
ptype Posting
p forall a. Eq a => a -> a -> Bool
== PostingType
pt, Amount -> CommoditySymbol
acommodity Amount
a forall a. Eq a => a -> a -> Bool
== Amount -> CommoditySymbol
acommodity Amount
fromamount
= Posting
p{ pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount Amount
a{aprice :: Maybe AmountPrice
aprice=forall a. a -> Maybe a
Just AmountPrice
conversionprice}
, poriginal :: Maybe Posting
poriginal = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Posting -> Posting
originalPosting Posting
p }
| Bool
otherwise = Posting
p
where
conversionprice :: AmountPrice
conversionprice = case forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
== Amount -> CommoditySymbol
acommodity Amount
fromamount) [CommoditySymbol]
pcommodities of
[CommoditySymbol
_] -> Amount -> AmountPrice
TotalPrice forall a b. (a -> b) -> a -> b
$ forall a. Num a => a -> a
negate Amount
toamount
[CommoditySymbol]
_ -> Amount -> AmountPrice
UnitPrice forall a b. (a -> b) -> a -> b
$ forall a. Num a => a -> a
negate Amount
unitprice Amount -> AmountPrecision -> Amount
`withPrecision` AmountPrecision
unitprecision
unitprice :: Amount
unitprice = Amount -> DecimalRaw Integer
aquantity Amount
fromamount DecimalRaw Integer -> Amount -> Amount
`divideAmount` Amount
toamount
unitprecision :: AmountPrecision
unitprecision = case (AmountStyle -> AmountPrecision
asprecision forall a b. (a -> b) -> a -> b
$ Amount -> AmountStyle
astyle Amount
fromamount, AmountStyle -> AmountPrecision
asprecision forall a b. (a -> b) -> a -> b
$ Amount -> AmountStyle
astyle Amount
toamount) of
(Precision Word8
a, Precision Word8
b) -> Word8 -> AmountPrecision
Precision forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Ord a => a -> a -> a
max Word8
2 forall a b. (a -> b) -> a -> b
$ forall {a}. (Ord a, Num a, Bounded a) => a -> a -> a
saturatedAdd Word8
a Word8
b
(AmountPrecision, AmountPrecision)
_ -> AmountPrecision
NaturalPrecision
saturatedAdd :: a -> a -> a
saturatedAdd a
a a
b = if forall a. Bounded a => a
maxBound forall a. Num a => a -> a -> a
- a
a forall a. Ord a => a -> a -> Bool
< a
b then forall a. Bounded a => a
maxBound else a
a forall a. Num a => a -> a -> a
+ a
b
journalCheckBalanceAssertions :: Journal -> Maybe String
journalCheckBalanceAssertions :: Journal -> Maybe String
journalCheckBalanceAssertions = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. a -> Maybe a
Just (forall a b. a -> b -> a
const forall a. Maybe a
Nothing) forall b c a. (b -> c) -> (a -> b) -> a -> c
. BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts
type Balancing s = ReaderT (BalancingState s) (ExceptT String (ST s))
data BalancingState s = BalancingState {
forall s.
BalancingState s -> Maybe (Map CommoditySymbol AmountStyle)
bsStyles :: Maybe (M.Map CommoditySymbol AmountStyle)
,forall s. BalancingState s -> Set CommoditySymbol
bsUnassignable :: S.Set AccountName
,forall s. BalancingState s -> Bool
bsAssrt :: Bool
,forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances :: H.HashTable s AccountName MixedAmount
,forall s. BalancingState s -> STArray s Integer Transaction
bsTransactions :: STArray s Integer Transaction
}
withRunningBalance :: (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance :: forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance BalancingState s -> ST s a
f = forall r (m :: * -> *). MonadReader r m => m r
ask forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall b c a. (b -> c) -> (a -> b) -> a -> c
. BalancingState s -> ST s a
f
getRunningBalanceB :: AccountName -> Balancing s MixedAmount
getRunningBalanceB :: forall s. CommoditySymbol -> Balancing s MixedAmount
getRunningBalanceB CommoditySymbol
acc = forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{HashTable s CommoditySymbol MixedAmount
bsBalances :: HashTable s CommoditySymbol MixedAmount
bsBalances :: forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances} -> do
forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> ST s (Maybe v)
H.lookup HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc
addToRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
addToRunningBalanceB :: forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
addToRunningBalanceB CommoditySymbol
acc MixedAmount
amt = forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{HashTable s CommoditySymbol MixedAmount
bsBalances :: HashTable s CommoditySymbol MixedAmount
bsBalances :: forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances} -> do
MixedAmount
old <- forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> ST s (Maybe v)
H.lookup HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc
let new :: MixedAmount
new = MixedAmount -> MixedAmount -> MixedAmount
maPlus MixedAmount
old MixedAmount
amt
forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> v -> ST s ()
H.insert HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc MixedAmount
new
forall (m :: * -> *) a. Monad m => a -> m a
return MixedAmount
new
setRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
setRunningBalanceB :: forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
setRunningBalanceB CommoditySymbol
acc MixedAmount
amt = forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{HashTable s CommoditySymbol MixedAmount
bsBalances :: HashTable s CommoditySymbol MixedAmount
bsBalances :: forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances} -> do
MixedAmount
old <- forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> ST s (Maybe v)
H.lookup HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc
forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> v -> ST s ()
H.insert HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc MixedAmount
amt
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ MixedAmount -> MixedAmount -> MixedAmount
maMinus MixedAmount
amt MixedAmount
old
setInclusiveRunningBalanceB :: AccountName -> MixedAmount -> Balancing s MixedAmount
setInclusiveRunningBalanceB :: forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
setInclusiveRunningBalanceB CommoditySymbol
acc MixedAmount
newibal = forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{HashTable s CommoditySymbol MixedAmount
bsBalances :: HashTable s CommoditySymbol MixedAmount
bsBalances :: forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances} -> do
MixedAmount
oldebal <- forall a. a -> Maybe a -> a
fromMaybe MixedAmount
nullmixedamt forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> ST s (Maybe v)
H.lookup HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc
[(CommoditySymbol, MixedAmount)]
allebals <- forall (h :: * -> * -> * -> *) s k v.
HashTable h =>
h s k v -> ST s [(k, v)]
H.toList HashTable s CommoditySymbol MixedAmount
bsBalances
let subsibal :: MixedAmount
subsibal =
forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
maSum forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter ((CommoditySymbol
acc CommoditySymbol -> CommoditySymbol -> Bool
`isAccountNamePrefixOf`)forall b c a. (b -> c) -> (a -> b) -> a -> c
.forall a b. (a, b) -> a
fst) [(CommoditySymbol, MixedAmount)]
allebals
let newebal :: MixedAmount
newebal = MixedAmount -> MixedAmount -> MixedAmount
maMinus MixedAmount
newibal MixedAmount
subsibal
forall k s v.
(Eq k, Hashable k) =>
HashTable s k v -> k -> v -> ST s ()
H.insert HashTable s CommoditySymbol MixedAmount
bsBalances CommoditySymbol
acc MixedAmount
newebal
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ MixedAmount -> MixedAmount -> MixedAmount
maMinus MixedAmount
newebal MixedAmount
oldebal
updateTransactionB :: Transaction -> Balancing s ()
updateTransactionB :: forall s. Transaction -> Balancing s ()
updateTransactionB Transaction
t = forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{STArray s Integer Transaction
bsTransactions :: STArray s Integer Transaction
bsTransactions :: forall s. BalancingState s -> STArray s Integer Transaction
bsTransactions} ->
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ forall (a :: * -> * -> *) e (m :: * -> *) i.
(MArray a e m, Ix i) =>
a i e -> i -> e -> m ()
writeArray STArray s Integer Transaction
bsTransactions (Transaction -> Integer
tindex Transaction
t) Transaction
t
journalBalanceTransactions :: BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions :: BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
bopts' Journal
j' =
let
j :: Journal
j@Journal{jtxns :: Journal -> [Transaction]
jtxns=[Transaction]
ts} = Journal -> Journal
journalNumberTransactions Journal
j'
styles :: Maybe (Map CommoditySymbol AmountStyle)
styles = forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Journal -> Map CommoditySymbol AmountStyle
journalCommodityStyles Journal
j
bopts :: BalancingOpts
bopts = BalancingOpts
bopts'{commodity_styles_ :: Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_=Maybe (Map CommoditySymbol AmountStyle)
styles}
autopostingaccts :: Set CommoditySymbol
autopostingaccts = forall a. Ord a => [a] -> Set a
S.fromList forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (Posting -> CommoditySymbol
paccount forall b c a. (b -> c) -> (a -> b) -> a -> c
. TMPostingRule -> Posting
tmprPosting) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap TransactionModifier -> [TMPostingRule]
tmpostingrules forall a b. (a -> b) -> a -> b
$ Journal -> [TransactionModifier]
jtxnmodifiers Journal
j
in
forall a. (forall s. ST s a) -> a
runST forall a b. (a -> b) -> a -> b
$ do
STArray s Integer Transaction
balancedtxns <- forall (a :: * -> * -> *) e (m :: * -> *) i.
(MArray a e m, Ix i) =>
(i, i) -> [e] -> m (a i e)
newListArray (Integer
1, forall a. Integral a => a -> Integer
toInteger forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t a -> Int
length [Transaction]
ts) [Transaction]
ts
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT forall a b. (a -> b) -> a -> b
$ do
[Either Posting Transaction]
psandts :: [Either Posting Transaction] <- forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [Transaction]
ts forall a b. (a -> b) -> a -> b
$ \case
Transaction
t | forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
assignmentPostings Transaction
t -> case BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
bopts Transaction
t of
Left String
e -> forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError String
e
Right Transaction
t' -> do
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall (a :: * -> * -> *) e (m :: * -> *) i.
(MArray a e m, Ix i) =>
a i e -> i -> e -> m ()
writeArray STArray s Integer Transaction
balancedtxns (Transaction -> Integer
tindex Transaction
t') Transaction
t'
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ Transaction -> [Posting]
tpostings Transaction
t'
Transaction
t -> forall (m :: * -> *) a. Monad m => a -> m a
return [forall a b. b -> Either a b
Right Transaction
t]
HashTable s CommoditySymbol MixedAmount
runningbals <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall s k v. Int -> ST s (HashTable s k v)
H.newSized (forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Journal -> [CommoditySymbol]
journalAccountNamesUsed Journal
j)
forall a b c. (a -> b -> c) -> b -> a -> c
flip forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (forall s.
Maybe (Map CommoditySymbol AmountStyle)
-> Set CommoditySymbol
-> Bool
-> HashTable s CommoditySymbol MixedAmount
-> STArray s Integer Transaction
-> BalancingState s
BalancingState Maybe (Map CommoditySymbol AmountStyle)
styles Set CommoditySymbol
autopostingaccts (Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ BalancingOpts -> Bool
ignore_assertions_ BalancingOpts
bopts) HashTable s CommoditySymbol MixedAmount
runningbals STArray s Integer Transaction
balancedtxns) forall a b. (a -> b) -> a -> b
$ do
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a b. Monad f => (a -> f b) -> [a] -> f [b]
mapM' forall s. Either Posting Transaction -> Balancing s ()
balanceTransactionAndCheckAssertionsB forall a b. (a -> b) -> a -> b
$ forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either Posting -> Day
postingDate Transaction -> Day
tdate) [Either Posting Transaction]
psandts
[Transaction]
ts' <- forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift forall a b. (a -> b) -> a -> b
$ forall (a :: * -> * -> *) e (m :: * -> *) i.
(MArray a e m, Ix i) =>
a i e -> m [e]
getElems STArray s Integer Transaction
balancedtxns
forall (m :: * -> *) a. Monad m => a -> m a
return Journal
j{jtxns :: [Transaction]
jtxns=[Transaction]
ts'}
balanceTransactionAndCheckAssertionsB :: Either Posting Transaction -> Balancing s ()
balanceTransactionAndCheckAssertionsB :: forall s. Either Posting Transaction -> Balancing s ()
balanceTransactionAndCheckAssertionsB (Left p :: Posting
p@Posting{}) =
forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall s. Posting -> Balancing s Posting
addAmountAndCheckAssertionB forall a b. (a -> b) -> a -> b
$ Posting -> Posting
postingStripPrices Posting
p
balanceTransactionAndCheckAssertionsB (Right t :: Transaction
t@Transaction{tpostings :: Transaction -> [Posting]
tpostings=[Posting]
ps}) = do
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall s. Posting -> Balancing s ()
checkIllegalBalanceAssignmentB [Posting]
ps
[Posting]
ps' <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (forall s. Posting -> Balancing s Posting
addOrAssignAmountAndCheckAssertionB forall b c a. (b -> c) -> (a -> b) -> a -> c
. Posting -> Posting
postingStripPrices) [Posting]
ps
Maybe (Map CommoditySymbol AmountStyle)
styles <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
R.reader forall s.
BalancingState s -> Maybe (Map CommoditySymbol AmountStyle)
bsStyles
case BalancingOpts
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
balanceTransactionHelper BalancingOpts
defbalancingopts{commodity_styles_ :: Maybe (Map CommoditySymbol AmountStyle)
commodity_styles_=Maybe (Map CommoditySymbol AmountStyle)
styles} Transaction
t{tpostings :: [Posting]
tpostings=[Posting]
ps'} of
Left String
err -> forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError String
err
Right (Transaction
t', [(CommoditySymbol, MixedAmount)]
inferredacctsandamts) -> do
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
addToRunningBalanceB) [(CommoditySymbol, MixedAmount)]
inferredacctsandamts
forall s. Transaction -> Balancing s ()
updateTransactionB Transaction
t'
addOrAssignAmountAndCheckAssertionB :: Posting -> Balancing s Posting
addOrAssignAmountAndCheckAssertionB :: forall s. Posting -> Balancing s Posting
addOrAssignAmountAndCheckAssertionB p :: Posting
p@Posting{paccount :: Posting -> CommoditySymbol
paccount=CommoditySymbol
acc, pamount :: Posting -> MixedAmount
pamount=MixedAmount
amt, pbalanceassertion :: Posting -> Maybe BalanceAssertion
pbalanceassertion=Maybe BalanceAssertion
mba}
| Posting -> Bool
hasAmount Posting
p = do
MixedAmount
newbal <- forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
addToRunningBalanceB CommoditySymbol
acc MixedAmount
amt
forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
whenM (forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
R.reader forall s. BalancingState s -> Bool
bsAssrt) forall a b. (a -> b) -> a -> b
$ forall s. Posting -> MixedAmount -> Balancing s ()
checkBalanceAssertionB Posting
p MixedAmount
newbal
forall (m :: * -> *) a. Monad m => a -> m a
return Posting
p
| Just BalanceAssertion{Amount
baamount :: BalanceAssertion -> Amount
baamount :: Amount
baamount,Bool
batotal :: BalanceAssertion -> Bool
batotal :: Bool
batotal,Bool
bainclusive :: BalanceAssertion -> Bool
bainclusive :: Bool
bainclusive} <- Maybe BalanceAssertion
mba = do
MixedAmount
newbal <- if Bool
batotal
then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Amount -> MixedAmount
mixedAmount Amount
baamount
else do
MixedAmount
oldbalothercommodities <- (Amount -> Bool) -> MixedAmount -> MixedAmount
filterMixedAmount ((Amount -> CommoditySymbol
acommodity Amount
baamount forall a. Eq a => a -> a -> Bool
/=) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> CommoditySymbol
acommodity) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s. CommoditySymbol -> Balancing s MixedAmount
getRunningBalanceB CommoditySymbol
acc
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ MixedAmount -> Amount -> MixedAmount
maAddAmount MixedAmount
oldbalothercommodities Amount
baamount
MixedAmount
diff <- (if Bool
bainclusive then forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
setInclusiveRunningBalanceB else forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
setRunningBalanceB) CommoditySymbol
acc MixedAmount
newbal
let p' :: Posting
p' = Posting
p{pamount :: MixedAmount
pamount=(Amount -> Bool) -> MixedAmount -> MixedAmount
filterMixedAmount (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Amount -> Bool
amountIsZero) MixedAmount
diff, poriginal :: Maybe Posting
poriginal=forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Posting -> Posting
originalPosting Posting
p}
forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
whenM (forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
R.reader forall s. BalancingState s -> Bool
bsAssrt) forall a b. (a -> b) -> a -> b
$ forall s. Posting -> MixedAmount -> Balancing s ()
checkBalanceAssertionB Posting
p' MixedAmount
newbal
forall (m :: * -> *) a. Monad m => a -> m a
return Posting
p'
| Bool
otherwise = forall (m :: * -> *) a. Monad m => a -> m a
return Posting
p
addAmountAndCheckAssertionB :: Posting -> Balancing s Posting
addAmountAndCheckAssertionB :: forall s. Posting -> Balancing s Posting
addAmountAndCheckAssertionB Posting
p | Posting -> Bool
hasAmount Posting
p = do
MixedAmount
newbal <- forall s. CommoditySymbol -> MixedAmount -> Balancing s MixedAmount
addToRunningBalanceB (Posting -> CommoditySymbol
paccount Posting
p) forall a b. (a -> b) -> a -> b
$ Posting -> MixedAmount
pamount Posting
p
forall (m :: * -> *). Monad m => m Bool -> m () -> m ()
whenM (forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
R.reader forall s. BalancingState s -> Bool
bsAssrt) forall a b. (a -> b) -> a -> b
$ forall s. Posting -> MixedAmount -> Balancing s ()
checkBalanceAssertionB Posting
p MixedAmount
newbal
forall (m :: * -> *) a. Monad m => a -> m a
return Posting
p
addAmountAndCheckAssertionB Posting
p = forall (m :: * -> *) a. Monad m => a -> m a
return Posting
p
checkBalanceAssertionB :: Posting -> MixedAmount -> Balancing s ()
checkBalanceAssertionB :: forall s. Posting -> MixedAmount -> Balancing s ()
checkBalanceAssertionB p :: Posting
p@Posting{pbalanceassertion :: Posting -> Maybe BalanceAssertion
pbalanceassertion=Just (BalanceAssertion{Amount
baamount :: Amount
baamount :: BalanceAssertion -> Amount
baamount,Bool
batotal :: Bool
batotal :: BalanceAssertion -> Bool
batotal})} MixedAmount
actualbal =
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Amount
baamount forall a. a -> [a] -> [a]
: [Amount]
otheramts) forall a b. (a -> b) -> a -> b
$ \Amount
amt -> forall s. Posting -> Amount -> MixedAmount -> Balancing s ()
checkBalanceAssertionOneCommodityB Posting
p Amount
amt MixedAmount
actualbal
where
assertedcomm :: CommoditySymbol
assertedcomm = Amount -> CommoditySymbol
acommodity Amount
baamount
otheramts :: [Amount]
otheramts | Bool
batotal = forall a b. (a -> b) -> [a] -> [b]
map (\Amount
a -> Amount
a{aquantity :: DecimalRaw Integer
aquantity=DecimalRaw Integer
0}) forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> [Amount]
amountsRaw
forall a b. (a -> b) -> a -> b
$ (Amount -> Bool) -> MixedAmount -> MixedAmount
filterMixedAmount ((forall a. Eq a => a -> a -> Bool
/=CommoditySymbol
assertedcomm)forall b c a. (b -> c) -> (a -> b) -> a -> c
.Amount -> CommoditySymbol
acommodity) MixedAmount
actualbal
| Bool
otherwise = []
checkBalanceAssertionB Posting
_ MixedAmount
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkBalanceAssertionOneCommodityB :: Posting -> Amount -> MixedAmount -> Balancing s ()
checkBalanceAssertionOneCommodityB :: forall s. Posting -> Amount -> MixedAmount -> Balancing s ()
checkBalanceAssertionOneCommodityB p :: Posting
p@Posting{paccount :: Posting -> CommoditySymbol
paccount=CommoditySymbol
assertedacct} Amount
assertedamt MixedAmount
actualbal = do
let isinclusive :: Bool
isinclusive = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False BalanceAssertion -> Bool
bainclusive forall a b. (a -> b) -> a -> b
$ Posting -> Maybe BalanceAssertion
pbalanceassertion Posting
p
let istotal :: Bool
istotal = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False BalanceAssertion -> Bool
batotal forall a b. (a -> b) -> a -> b
$ Posting -> Maybe BalanceAssertion
pbalanceassertion Posting
p
MixedAmount
actualbal' <-
if Bool
isinclusive
then
forall s a. (BalancingState s -> ST s a) -> Balancing s a
withRunningBalance forall a b. (a -> b) -> a -> b
$ \BalancingState{HashTable s CommoditySymbol MixedAmount
bsBalances :: HashTable s CommoditySymbol MixedAmount
bsBalances :: forall s.
BalancingState s -> HashTable s CommoditySymbol MixedAmount
bsBalances} ->
forall a k v s.
(a -> (k, v) -> ST s a) -> a -> HashTable s k v -> ST s a
H.foldM
(\MixedAmount
ibal (CommoditySymbol
acc, MixedAmount
amt) -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
if CommoditySymbol
assertedacctforall a. Eq a => a -> a -> Bool
==CommoditySymbol
acc Bool -> Bool -> Bool
|| CommoditySymbol
assertedacct CommoditySymbol -> CommoditySymbol -> Bool
`isAccountNamePrefixOf` CommoditySymbol
acc then MixedAmount -> MixedAmount -> MixedAmount
maPlus MixedAmount
ibal MixedAmount
amt else MixedAmount
ibal)
MixedAmount
nullmixedamt
HashTable s CommoditySymbol MixedAmount
bsBalances
else forall (m :: * -> *) a. Monad m => a -> m a
return MixedAmount
actualbal
let
assertedcomm :: CommoditySymbol
assertedcomm = Amount -> CommoditySymbol
acommodity Amount
assertedamt
actualbalincomm :: Amount
actualbalincomm = forall a. a -> [a] -> a
headDef Amount
nullamt forall b c a. (b -> c) -> (a -> b) -> a -> c
. MixedAmount -> [Amount]
amountsRaw forall b c a. (b -> c) -> (a -> b) -> a -> c
. CommoditySymbol -> MixedAmount -> MixedAmount
filterMixedAmountByCommodity CommoditySymbol
assertedcomm forall a b. (a -> b) -> a -> b
$ MixedAmount
actualbal'
pass :: Bool
pass =
Amount -> DecimalRaw Integer
aquantity
Amount
assertedamt forall a. Eq a => a -> a -> Bool
==
Amount -> DecimalRaw Integer
aquantity
Amount
actualbalincomm
errmsg :: String
errmsg = String -> String
chomp forall a b. (a -> b) -> a -> b
$ forall r. PrintfType r => String -> r
printf ([String] -> String
unlines
[ String
"%s:",
String
"%s\n",
String
"This balance assertion failed.",
String
"In account: %s",
String
"and commodity: %s",
String
"this balance was asserted: %s",
String
"but the calculated balance is: %s",
String
"a difference of: %s",
String
"",
String
"Consider viewing this account's calculated balances to troubleshoot. Eg:",
String
"",
String
"hledger reg '%s'%s -I # -f FILE"
])
(SourcePos -> String
sourcePosPretty SourcePos
pos)
(CommoditySymbol -> CommoditySymbol
textChomp CommoditySymbol
ex)
(if Bool
isinclusive then forall r. PrintfType r => String -> r
printf String
"%-30s (including subaccounts)" String
acct else String
acct)
(if Bool
istotal then forall r. PrintfType r => String -> r
printf String
"%-30s (no other commodities allowed)" (CommoditySymbol -> String
T.unpack CommoditySymbol
assertedcomm) else (CommoditySymbol -> String
T.unpack CommoditySymbol
assertedcomm))
(forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Amount -> DecimalRaw Integer
aquantity Amount
assertedamt)
(forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Amount -> DecimalRaw Integer
aquantity Amount
actualbalincomm)
(forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ Amount -> DecimalRaw Integer
aquantity Amount
assertedamt forall a. Num a => a -> a -> a
- Amount -> DecimalRaw Integer
aquantity Amount
actualbalincomm)
(String
acct forall a. [a] -> [a] -> [a]
++ if Bool
isinclusive then String
"" else String
"$")
(if Bool
istotal then String
"" else (String
" cur:" forall a. [a] -> [a] -> [a]
++ String -> String
quoteForCommandLine (CommoditySymbol -> String
T.unpack CommoditySymbol
assertedcomm)))
where
acct :: String
acct = CommoditySymbol -> String
T.unpack forall a b. (a -> b) -> a -> b
$ Posting -> CommoditySymbol
paccount Posting
p
ass :: BalanceAssertion
ass = forall a. HasCallStack => Maybe a -> a
fromJust forall a b. (a -> b) -> a -> b
$ Posting -> Maybe BalanceAssertion
pbalanceassertion Posting
p
pos :: SourcePos
pos = BalanceAssertion -> SourcePos
baposition BalanceAssertion
ass
(String
_,Int
_,Maybe (Int, Maybe Int)
_,CommoditySymbol
ex) = Posting -> (String, Int, Maybe (Int, Maybe Int), CommoditySymbol)
makeBalanceAssertionErrorExcerpt Posting
p
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
pass forall a b. (a -> b) -> a -> b
$ forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError String
errmsg
checkIllegalBalanceAssignmentB :: Posting -> Balancing s ()
checkIllegalBalanceAssignmentB :: forall s. Posting -> Balancing s ()
checkIllegalBalanceAssignmentB Posting
p = do
forall s. Posting -> Balancing s ()
checkBalanceAssignmentPostingDateB Posting
p
forall s. Posting -> Balancing s ()
checkBalanceAssignmentUnassignableAccountB Posting
p
checkBalanceAssignmentPostingDateB :: Posting -> Balancing s ()
checkBalanceAssignmentPostingDateB :: forall s. Posting -> Balancing s ()
checkBalanceAssignmentPostingDateB Posting
p =
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Posting -> Bool
hasBalanceAssignment Posting
p Bool -> Bool -> Bool
&& forall a. Maybe a -> Bool
isJust (Posting -> Maybe Day
pdate Posting
p)) forall a b. (a -> b) -> a -> b
$
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError forall a b. (a -> b) -> a -> b
$ String -> String
chomp forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines [
String
"Balance assignments and custom posting dates may not be combined."
,String
""
,String -> String
chomp1 forall a b. (a -> b) -> a -> b
$ CommoditySymbol -> String
T.unpack forall a b. (a -> b) -> a -> b
$ forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([CommoditySymbol] -> CommoditySymbol
T.unlines forall a b. (a -> b) -> a -> b
$ Posting -> [CommoditySymbol]
showPostingLines Posting
p) Transaction -> CommoditySymbol
showTransaction forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
,String
"Balance assignments may not be used on postings with a custom posting date"
,String
"(it makes balancing the journal impossible)."
,String
"Please write the posting amount explicitly (or remove the posting date)."
]
checkBalanceAssignmentUnassignableAccountB :: Posting -> Balancing s ()
checkBalanceAssignmentUnassignableAccountB :: forall s. Posting -> Balancing s ()
checkBalanceAssignmentUnassignableAccountB Posting
p = do
Set CommoditySymbol
unassignable <- forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
R.asks forall s. BalancingState s -> Set CommoditySymbol
bsUnassignable
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Posting -> Bool
hasBalanceAssignment Posting
p Bool -> Bool -> Bool
&& Posting -> CommoditySymbol
paccount Posting
p forall a. Ord a => a -> Set a -> Bool
`S.member` Set CommoditySymbol
unassignable) forall a b. (a -> b) -> a -> b
$
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError forall a b. (a -> b) -> a -> b
$ String -> String
chomp forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines [
String
"Balance assignments and auto postings may not be combined."
,String
""
,String -> String
chomp1 forall a b. (a -> b) -> a -> b
$ CommoditySymbol -> String
T.unpack forall a b. (a -> b) -> a -> b
$ forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([CommoditySymbol] -> CommoditySymbol
T.unlines forall a b. (a -> b) -> a -> b
$ Posting -> [CommoditySymbol]
showPostingLines Posting
p) (Transaction -> CommoditySymbol
showTransaction) forall a b. (a -> b) -> a -> b
$ Posting -> Maybe Transaction
ptransaction Posting
p
,String
"Balance assignments may not be used on accounts affected by auto posting rules"
,String
"(it makes balancing the journal impossible)."
,String
"Please write the posting amount explicitly (or remove the auto posting rule(s))."
]
makeHledgerClassyLenses ''BalancingOpts
tests_Balancing :: TestTree
tests_Balancing :: TestTree
tests_Balancing =
String -> [TestTree] -> TestTree
testGroup String
"Balancing" [
String -> Assertion -> TestTree
testCase String
"inferBalancingAmount" forall a b. (a -> b) -> a -> b
$ do
(forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
inferBalancingAmount forall k a. Map k a
M.empty Transaction
nulltransaction) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= forall a b. b -> Either a b
Right Transaction
nulltransaction
(forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
inferBalancingAmount forall k a. Map k a
M.empty Transaction
nulltransaction{tpostings :: [Posting]
tpostings = [CommoditySymbol
"a" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
5), CommoditySymbol
"b" CommoditySymbol -> Amount -> Posting
`post` Amount
missingamt]}) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right Transaction
nulltransaction{tpostings :: [Posting]
tpostings = [CommoditySymbol
"a" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
5), CommoditySymbol
"b" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd DecimalRaw Integer
5]}
(forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map CommoditySymbol AmountStyle
-> Transaction
-> Either String (Transaction, [(CommoditySymbol, MixedAmount)])
inferBalancingAmount forall k a. Map k a
M.empty Transaction
nulltransaction{tpostings :: [Posting]
tpostings = [CommoditySymbol
"a" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
5), CommoditySymbol
"b" CommoditySymbol -> Amount -> Posting
`post` (DecimalRaw Integer -> Amount
eur DecimalRaw Integer
3 Amount -> Amount -> Amount
@@ DecimalRaw Integer -> Amount
usd DecimalRaw Integer
4), CommoditySymbol
"c" CommoditySymbol -> Amount -> Posting
`post` Amount
missingamt]}) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right Transaction
nulltransaction{tpostings :: [Posting]
tpostings = [CommoditySymbol
"a" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
5), CommoditySymbol
"b" CommoditySymbol -> Amount -> Posting
`post` (DecimalRaw Integer -> Amount
eur DecimalRaw Integer
3 Amount -> Amount -> Amount
@@ DecimalRaw Integer -> Amount
usd DecimalRaw Integer
4), CommoditySymbol
"c" CommoditySymbol -> Amount -> Posting
`post` DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1]}
, String -> [TestTree] -> TestTree
testGroup String
"balanceTransaction" [
String -> Assertion -> TestTree
testCase String
"detect unbalanced entry, sign error" forall a b. (a -> b) -> a -> b
$
forall b a. (HasCallStack, Eq b, Show b) => Either a b -> Assertion
assertLeft
(BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"test"
CommoditySymbol
""
[]
[Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1)}, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1)}]))
,String -> Assertion -> TestTree
testCase String
"detect unbalanced entry, multiple missing amounts" forall a b. (a -> b) -> a -> b
$
forall b a. (HasCallStack, Eq b, Show b) => Either a b -> Assertion
assertLeft forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"test"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = MixedAmount
missingmixedamt}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = MixedAmount
missingmixedamt}
])
,String -> Assertion -> TestTree
testCase String
"one missing amount is inferred" forall a b. (a -> b) -> a -> b
$
(Posting -> MixedAmount
pamount forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> a
last forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
""
CommoditySymbol
""
[]
[Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1)}, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = MixedAmount
missingmixedamt}])) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right (Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1))
,String -> Assertion -> TestTree
testCase String
"conversion price is inferred" forall a b. (a -> b) -> a -> b
$
(Posting -> MixedAmount
pamount forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> a
head forall b c a. (b -> c) -> (a -> b) -> a -> c
. Transaction -> [Posting]
tpostings forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2007 Int
01 Int
28)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
""
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.35)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
eur (-DecimalRaw Integer
1))}
])) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right (Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.35 Amount -> Amount -> Amount
@@ DecimalRaw Integer -> Amount
eur DecimalRaw Integer
1)
,String -> Assertion -> TestTree
testCase String
"balanceTransaction balances based on cost if there are unit prices" forall a b. (a -> b) -> a -> b
$
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2011 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
""
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1 Amount -> Amount -> Amount
`at` DecimalRaw Integer -> Amount
eur DecimalRaw Integer
2}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2) Amount -> Amount -> Amount
`at` DecimalRaw Integer -> Amount
eur DecimalRaw Integer
1}
])
,String -> Assertion -> TestTree
testCase String
"balanceTransaction balances based on cost if there are total prices" forall a b. (a -> b) -> a -> b
$
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Either String Transaction
balanceTransaction BalancingOpts
defbalancingopts
(Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2011 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
""
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1 Amount -> Amount -> Amount
@@ DecimalRaw Integer -> Amount
eur DecimalRaw Integer
1}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"a", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount forall a b. (a -> b) -> a -> b
$ DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
2) Amount -> Amount -> Amount
@@ DecimalRaw Integer -> Amount
eur (-DecimalRaw Integer
1)}
])
]
, String -> [TestTree] -> TestTree
testGroup String
"isTransactionBalanced" [
String -> Assertion -> TestTree
testCase String
"detect balanced" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"c", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1.00))}
]
,String -> Assertion -> TestTree
testCase String
"detect unbalanced" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
Bool -> Bool
not forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"c", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1.01))}
]
,String -> Assertion -> TestTree
testCase String
"detect unbalanced, one posting" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
Bool -> Bool
not forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}]
,String -> Assertion -> TestTree
testCase String
"one zero posting is considered balanced for now" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
0)}]
,String -> Assertion -> TestTree
testCase String
"virtual postings don't need to balance" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"c", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1.00))}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"d", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
100), ptype :: PostingType
ptype = PostingType
VirtualPosting}
]
,String -> Assertion -> TestTree
testCase String
"balanced virtual postings need to balance among themselves" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
Bool -> Bool
not forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"c", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1.00))}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"d", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
100), ptype :: PostingType
ptype = PostingType
BalancedVirtualPosting}
]
,String -> Assertion -> TestTree
testCase String
"balanced virtual postings need to balance among themselves (2)" forall a b. (a -> b) -> a -> b
$
HasCallStack => String -> Bool -> Assertion
assertBool String
"" forall a b. (a -> b) -> a -> b
$
BalancingOpts -> Transaction -> Bool
isTransactionBalanced BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Integer
-> CommoditySymbol
-> (SourcePos, SourcePos)
-> Day
-> Maybe Day
-> Status
-> CommoditySymbol
-> CommoditySymbol
-> CommoditySymbol
-> [Tag]
-> [Posting]
-> Transaction
Transaction
Integer
0
CommoditySymbol
""
(SourcePos, SourcePos)
nullsourcepos
(Integer -> Int -> Int -> Day
fromGregorian Integer
2009 Int
01 Int
01)
forall a. Maybe a
Nothing
Status
Unmarked
CommoditySymbol
""
CommoditySymbol
"a"
CommoditySymbol
""
[]
[ Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"b", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
1.00)}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"c", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
1.00))}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"d", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd DecimalRaw Integer
100), ptype :: PostingType
ptype = PostingType
BalancedVirtualPosting}
, Posting
posting {paccount :: CommoditySymbol
paccount = CommoditySymbol
"3", pamount :: MixedAmount
pamount = Amount -> MixedAmount
mixedAmount (DecimalRaw Integer -> Amount
usd (-DecimalRaw Integer
100)), ptype :: PostingType
ptype = PostingType
BalancedVirtualPosting}
]
]
,String -> [TestTree] -> TestTree
testGroup String
"journalBalanceTransactions" [
String -> Assertion -> TestTree
testCase String
"missing-amounts" forall a b. (a -> b) -> a -> b
$ do
let ej :: Either String Journal
ej = BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$ Bool -> Journal
samplejournalMaybeExplicit Bool
False
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight Either String Journal
ej
Journal -> [Posting]
journalPostings forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Either String Journal
ej forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= forall a b. b -> Either a b
Right (Journal -> [Posting]
journalPostings Journal
samplejournal)
,String -> Assertion -> TestTree
testCase String
"balance-assignment" forall a b. (a -> b) -> a -> b
$ do
let ej :: Either String Journal
ej = BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Journal
nulljournal{ jtxns :: [Transaction]
jtxns = [
Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" Amount
missingamt (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1)) ]
]}
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight Either String Journal
ej
case Either String Journal
ej of Right Journal
j -> (Journal -> [Transaction]
jtxns Journal
j forall a b. a -> (a -> b) -> b
& forall a. [a] -> a
head forall a b. a -> (a -> b) -> b
& Transaction -> [Posting]
tpostings forall a b. a -> (a -> b) -> b
& forall a. [a] -> a
head forall a b. a -> (a -> b) -> b
& Posting -> MixedAmount
pamount forall a b. a -> (a -> b) -> b
& MixedAmount -> [Amount]
amountsRaw) forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?= [DecimalRaw Integer -> Amount
num DecimalRaw Integer
1]
Left String
_ -> forall a. String -> a
error' String
"balance-assignment test: shouldn't happen"
,String -> Assertion -> TestTree
testCase String
"same-day-1" forall a b. (a -> b) -> a -> b
$ do
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight forall a b. (a -> b) -> a -> b
$ BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Journal
nulljournal{ jtxns :: [Transaction]
jtxns = [
Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" Amount
missingamt (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1)) ]
,Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1) (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
2)) ]
]}
,String -> Assertion -> TestTree
testCase String
"same-day-2" forall a b. (a -> b) -> a -> b
$ do
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight forall a b. (a -> b) -> a -> b
$ BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Journal
nulljournal{ jtxns :: [Transaction]
jtxns = [
Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
2) (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
2)) ]
,Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [
CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
post' CommoditySymbol
"b" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1) forall a. Maybe a
Nothing
,CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
post' CommoditySymbol
"a" Amount
missingamt forall a. Maybe a
Nothing
]
,Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
post' CommoditySymbol
"a" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
0) (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1)) ]
]}
,String -> Assertion -> TestTree
testCase String
"out-of-order" forall a b. (a -> b) -> a -> b
$ do
forall a b. (HasCallStack, Eq a, Show a) => Either a b -> Assertion
assertRight forall a b. (a -> b) -> a -> b
$ BalancingOpts -> Journal -> Either String Journal
journalBalanceTransactions BalancingOpts
defbalancingopts forall a b. (a -> b) -> a -> b
$
Journal
nulljournal{ jtxns :: [Transaction]
jtxns = [
Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
02) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1) (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
2)) ]
,Day -> [Posting] -> Transaction
transaction (Integer -> Int -> Int -> Day
fromGregorian Integer
2019 Int
01 Int
01) [ CommoditySymbol -> Amount -> Maybe BalanceAssertion -> Posting
vpost' CommoditySymbol
"a" (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1) (Amount -> Maybe BalanceAssertion
balassert (DecimalRaw Integer -> Amount
num DecimalRaw Integer
1)) ]
]}
]
,String -> [TestTree] -> TestTree
testGroup String
"commodityStylesFromAmounts" forall a b. (a -> b) -> a -> b
$ [
String -> Assertion -> TestTree
testCase String
"1091a" forall a b. (a -> b) -> a -> b
$ do
[Amount] -> Either String (Map CommoditySymbol AmountStyle)
commodityStylesFromAmounts [
Amount
nullamt{aquantity :: DecimalRaw Integer
aquantity=DecimalRaw Integer
1000, astyle :: AmountStyle
astyle=Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
3) (forall a. a -> Maybe a
Just Char
',') forall a. Maybe a
Nothing}
,Amount
nullamt{aquantity :: DecimalRaw Integer
aquantity=DecimalRaw Integer
1000, astyle :: AmountStyle
astyle=Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
2) (forall a. a -> Maybe a
Just Char
'.') (forall a. a -> Maybe a
Just (Char -> [Word8] -> DigitGroupStyle
DigitGroups Char
',' [Word8
3]))}
]
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right (forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [
(CommoditySymbol
"", Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
3) (forall a. a -> Maybe a
Just Char
'.') (forall a. a -> Maybe a
Just (Char -> [Word8] -> DigitGroupStyle
DigitGroups Char
',' [Word8
3])))
])
,String -> Assertion -> TestTree
testCase String
"1091b" forall a b. (a -> b) -> a -> b
$ do
[Amount] -> Either String (Map CommoditySymbol AmountStyle)
commodityStylesFromAmounts [
Amount
nullamt{aquantity :: DecimalRaw Integer
aquantity=DecimalRaw Integer
1000, astyle :: AmountStyle
astyle=Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
2) (forall a. a -> Maybe a
Just Char
'.') (forall a. a -> Maybe a
Just (Char -> [Word8] -> DigitGroupStyle
DigitGroups Char
',' [Word8
3]))}
,Amount
nullamt{aquantity :: DecimalRaw Integer
aquantity=DecimalRaw Integer
1000, astyle :: AmountStyle
astyle=Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
3) (forall a. a -> Maybe a
Just Char
',') forall a. Maybe a
Nothing}
]
forall a. (Eq a, Show a, HasCallStack) => a -> a -> Assertion
@?=
forall a b. b -> Either a b
Right (forall k a. Ord k => [(k, a)] -> Map k a
M.fromList [
(CommoditySymbol
"", Side
-> Bool
-> AmountPrecision
-> Maybe Char
-> Maybe DigitGroupStyle
-> AmountStyle
AmountStyle Side
L Bool
False (Word8 -> AmountPrecision
Precision Word8
3) (forall a. a -> Maybe a
Just Char
'.') (forall a. a -> Maybe a
Just (Char -> [Word8] -> DigitGroupStyle
DigitGroups Char
',' [Word8
3])))
])
]
]