Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amount expressions, multi-commodity postings #934

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hledger-lib/Hledger/Data/Amount.hs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ instance Num Amount where

-- | The empty simple amount.
amount, nullamt :: Amount
amount = Amount{acommodity="", aquantity=0, aprice=NoPrice, astyle=amountstyle, amultiplier=False}
amount = Amount{acommodity="", aquantity=0, aprice=NoPrice, astyle=amountstyle}
nullamt = amount

-- | A temporary value for parsed transactions which had no amount specified.
Expand Down
8 changes: 2 additions & 6 deletions hledger-lib/Hledger/Data/Commodity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ are thousands separated by comma, significant decimal places and so on.

module Hledger.Data.Commodity
where
import Data.Char (isDigit)
import Data.List
import Data.Maybe (fromMaybe)
#if !(MIN_VERSION_base(4,11,0))
Expand All @@ -26,13 +25,10 @@ import Hledger.Utils


-- characters that may not be used in a non-quoted commodity symbol
nonsimplecommoditychars = "0123456789-+.@*;\n \"{}=" :: [Char]
nonsimplecommoditychars = "0123456789-+.@*;\n \"(){}=" :: [Char]

isNonsimpleCommodityChar :: Char -> Bool
isNonsimpleCommodityChar c = isDigit c || c `textElem` otherChars
where
otherChars = "-+.@*;\n \"{}=" :: T.Text
textElem = T.any . (==)
isNonsimpleCommodityChar = flip elem nonsimplecommoditychars

quoteCommoditySymbolIfNeeded s | T.any (isNonsimpleCommodityChar) s = "\"" <> s <> "\""
| otherwise = s
Expand Down
16 changes: 8 additions & 8 deletions hledger-lib/Hledger/Data/Journal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -570,14 +570,14 @@ journalCheckBalanceAssertions j =
-- fails.
checkBalanceAssertion :: Posting -> MixedAmount -> Either String ()
checkBalanceAssertion p@Posting{ pbalanceassertion = Just ass } bal =
foldl' fold (Right ()) amts
foldl' fold (Right ()) amt0
where fold (Right _) cass = checkBalanceAssertionCommodity p cass bal
fold err _ = err
amt = baamount ass
amts = amt : if baexact ass
then map (\a -> a{ aquantity = 0 }) $ amounts $ filterMixedAmount (\a -> acommodity a /= assertedcomm) bal
amts = amounts $ baamount ass
amt0 = amts ++ if baexact ass
then map (\a -> a{ aquantity = 0 }) $ amounts $ filterMixedAmount (\a -> not $ elem (acommodity a) assertedcomms) bal
else []
assertedcomm = acommodity amt
assertedcomms = map acommodity amts
checkBalanceAssertion _ _ = Right ()

checkBalanceAssertionCommodity :: Posting -> Amount -> MixedAmount -> Either String ()
Expand Down Expand Up @@ -759,14 +759,14 @@ checkInferAndRegisterAmounts (Right oldTx) = do
let acc = paccount p
case pbalanceassertion p of
Just ba | baexact ba -> do
diff <- setMixedBalance acc $ Mixed [baamount ba]
diff <- setMixedBalance acc $ baamount ba
fullPosting diff p
Just ba | otherwise -> do
old <- liftModifier $ \Env{ eBalances = bals } -> HT.lookup bals acc
let amt = baamount ba
assertedcomm = acommodity amt
assertedcomms = map acommodity $ amounts amt
diff <- setMixedBalance acc $
Mixed [amt] + filterMixedAmount (\a -> acommodity a /= assertedcomm) (fromMaybe nullmixedamt old)
amt + filterMixedAmount (\a -> not $ acommodity a `elem` assertedcomms) (fromMaybe nullmixedamt old)
fullPosting diff p
Nothing -> return p
fullPosting amt p = return p
Expand Down
3 changes: 2 additions & 1 deletion hledger-lib/Hledger/Data/Posting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ nullposting = Posting
,pcomment=""
,ptype=RegularPosting
,ptags=[]
,pmultiplier=Nothing
,pbalanceassertion=Nothing
,ptransaction=Nothing
,porigin=Nothing
Expand All @@ -104,7 +105,7 @@ nullsourcepos = JournalSourcePos "" (1,1)

nullassertion, assertion :: BalanceAssertion
nullassertion = BalanceAssertion
{baamount=nullamt
{baamount=nullmixedamt
,baexact=False
,baposition=nullsourcepos
}
Expand Down
2 changes: 1 addition & 1 deletion hledger-lib/Hledger/Data/Transaction.hs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ postingAsLines elideamount onelineamounts pstoalignwith p = concat [
| postingblock <- postingblocks]
where
postingblocks = [map rstrip $ lines $ concatTopPadded [statusandaccount, " ", amount, assertion, samelinecomment] | amount <- shownAmounts]
assertion = maybe "" ((" = " ++) . showAmountWithZeroCommodity . baamount) $ pbalanceassertion p
assertion = maybe "" ((" = " ++) . showMixedAmountWithZeroCommodity . baamount) $ pbalanceassertion p
statusandaccount = indent $ fitString (Just $ minwidth) Nothing False True $ pstatusandacct p
where
-- pad to the maximum account name width, plus 2 to leave room for status flags, to keep amounts aligned
Expand Down
22 changes: 8 additions & 14 deletions hledger-lib/Hledger/Data/TransactionModifier.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import Hledger.Utils.Debug
-- 0000/01/01
-- ping $1.00
-- <BLANKLINE>
-- >>> putStr $ showTransaction $ transactionModifierToFunction (TransactionModifier "ping" ["pong" `post` amount{amultiplier=True, aquantity=3}]) nulltransaction{tpostings=["ping" `post` usd 2]}
-- >>> putStr $ showTransaction $ transactionModifierToFunction (TransactionModifier "ping" [nullposting{paccount="pong", pmultiplier=Just $ num 3}]) nulltransaction{tpostings=["ping" `post` usd 2]}
-- 0000/01/01
-- ping $2.00
-- pong $6.00
Expand Down Expand Up @@ -86,33 +86,27 @@ tmPostingRuleToFunction pr =
{ pdate = pdate p
, pdate2 = pdate2 p
, pamount = amount' p
, pmultiplier = Nothing
}
where
amount' = case postingRuleMultiplier pr of
amount' = case pmultiplier pr of
Nothing -> const $ pamount pr
Just n -> \p ->
Just n -> \p -> pamount pr +
-- Multiply the old posting's amount by the posting rule's multiplier.
let
pramount = dbg6 "pramount" $ head $ amounts $ pamount pr
matchedamount = dbg6 "matchedamount" $ pamount p
-- Handle a matched amount with a total price carefully so as to keep the transaction balanced (#928).
-- Approach 1: convert to a unit price and increase the display precision slightly
-- Mixed as = dbg6 "multipliedamount" $ n `multiplyMixedAmount` mixedAmountTotalPriceToUnitPrice matchedamount
-- Mixed as = dbg6 "multipliedamount" $ aquantity n `multiplyMixedAmount` mixedAmountTotalPriceToUnitPrice matchedamount
-- Approach 2: multiply the total price (keeping it positive) as well as the quantity
Mixed as = dbg6 "multipliedamount" $ n `multiplyMixedAmountAndPrice` matchedamount
Mixed as = dbg6 "multipliedamount" $ aquantity n `multiplyMixedAmountAndPrice` matchedamount
in
case acommodity pramount of
case acommodity n of
"" -> Mixed as
-- TODO multipliers with commodity symbols are not yet a documented feature.
-- For now: in addition to multiplying the quantity, it also replaces the
-- matched amount's commodity, display style, and price with those of the posting rule.
c -> Mixed [a{acommodity = c, astyle = astyle pramount, aprice = aprice pramount} | a <- as]

postingRuleMultiplier :: TMPostingRule -> Maybe Quantity
postingRuleMultiplier p =
case amounts $ pamount p of
[a] | amultiplier a -> Just $ aquantity a
_ -> Nothing
c -> Mixed [a{acommodity = c, astyle = astyle n, aprice = aprice n} | a <- as]

renderPostingCommentDates :: Posting -> Posting
renderPostingCommentDates p = p { pcomment = comment' }
Expand Down
10 changes: 5 additions & 5 deletions hledger-lib/Hledger/Data/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,7 @@ data Amount = Amount {
acommodity :: CommoditySymbol,
aquantity :: Quantity,
aprice :: Price, -- ^ the (fixed) price for this amount, if any
astyle :: AmountStyle,
amultiplier :: Bool -- ^ kludge: a flag marking this amount and posting as a multiplier
-- in a TMPostingRule. In a regular Posting, should always be false.
astyle :: AmountStyle
} deriving (Eq,Ord,Typeable,Data,Generic,Show)

instance NFData Amount
Expand Down Expand Up @@ -240,7 +238,7 @@ instance Show Status where -- custom show.. bad idea.. don't do it..
-- | The amount to compare an account's balance to, to verify that the history
-- leading to a given point is correct or to set the account to a known value.
data BalanceAssertion = BalanceAssertion {
baamount :: Amount, -- ^ the expected value of a particular commodity
baamount :: MixedAmount, -- ^ the expected value of particular commodities
baexact :: Bool, -- ^ whether the assertion is exclusive, and doesn't allow other commodities alongside 'baamount'
baposition :: GenericSourcePos
} deriving (Eq,Typeable,Data,Generic,Show)
Expand All @@ -256,6 +254,7 @@ data Posting = Posting {
pcomment :: Text, -- ^ this posting's comment lines, as a single non-indented multi-line string
ptype :: PostingType,
ptags :: [Tag], -- ^ tag names and values, extracted from the comment
pmultiplier :: Maybe Amount, -- ^ optional: the proportion of the base value to use in a 'TransactionModifier'
pbalanceassertion :: Maybe BalanceAssertion, -- ^ optional: the expected balance in this commodity in the account after this posting
ptransaction :: Maybe Transaction, -- ^ this posting's parent transaction (co-recursive types).
-- Tying this knot gets tedious, Maybe makes it easier/optional.
Expand All @@ -271,7 +270,7 @@ instance NFData Posting
-- identity, to avoid recuring ad infinitum.
-- XXX could check that it's Just or Nothing.
instance Eq Posting where
(==) (Posting a1 b1 c1 d1 e1 f1 g1 h1 i1 _ _) (Posting a2 b2 c2 d2 e2 f2 g2 h2 i2 _ _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 && g1==g2 && h1==h2 && i1==i2
(==) (Posting a1 b1 c1 d1 e1 f1 g1 h1 i1 j1 _ _) (Posting a2 b2 c2 d2 e2 f2 g2 h2 i2 j2 _ _) = a1==a2 && b1==b2 && c1==c2 && d1==d2 && e1==e2 && f1==f2 && g1==g2 && h1==h2 && i1==i2 && j1==j2

-- | Posting's show instance elides the parent transaction so as not to recurse forever.
instance Show Posting where
Expand All @@ -284,6 +283,7 @@ instance Show Posting where
,("pcomment=" ++ show pcomment)
,("ptype=" ++ show ptype)
,("ptags=" ++ show ptags)
,("pmultiplier=" ++ show pmultiplier)
,("pbalanceassertion=" ++ show pbalanceassertion)
,("ptransaction=" ++ show (const "<txn>" <$> ptransaction))
,("porigin=" ++ show porigin)
Expand Down
Loading