Add Support for Rewriting Multipler Postings Into Different Commodities (#557)

When generating a new posting as a multiple of an existing posting,
support conversion to a different commodity.  For example, postings in
hours can be used to generate postings in USD.

Automatic transactions generated from rewrite rules use the commodity,
amount style, and transaction price if the rewrite defines a commodity.
This commit is contained in:
Christian G. Warden 2017-05-30 07:30:15 -07:00 committed by Simon Michael
parent 35f77ee5e4
commit d39040c634
7 changed files with 107 additions and 18 deletions

View File

@ -60,12 +60,15 @@ More:
$ hledger rewrite -- [QUERY] --add-posting "ACCT AMTEXPR" ...
$ hledger rewrite -- ^income --add-posting '(liabilities:tax) *.33'
$ hledger rewrite -- expenses:gifts --add-posting '(budget:gifts) *-1"'
$ hledger rewrite -- ^income --add-posting '(budget:foreign currency) *0.25 JPY; diversify'
```
Argument for `--add-posting` option is a usual posting of transaction with an
exception for amount specification. More precisely you can use `'*'` (star
symbol) in place of currency to indicate that that this is a factor for an
amount of original matched posting.
exception for amount specification. More precisely, you can use `'*'` (star
symbol) before the amount to indicate that that this is a factor for an
amount of original matched posting. If the amount includes a commodity name,
the new posting amount will be in the new commodity; otherwise, it will be in
the matched posting amount's commodity.
#### Re-write rules in a file

View File

@ -151,7 +151,7 @@ instance Num Amount where
-- | The empty simple amount.
amount, nullamt :: Amount
amount = Amount{acommodity="", aquantity=0, aprice=NoPrice, astyle=amountstyle}
amount = Amount{acommodity="", aquantity=0, aprice=NoPrice, astyle=amountstyle, amultiplier=False}
nullamt = amount
-- | A temporary value for parsed transactions which had no amount specified.

View File

@ -55,7 +55,7 @@ import Hledger.Query
-- ping $1.00
-- <BLANKLINE>
-- <BLANKLINE>
-- >>> runModifierTransaction Any (ModifierTransaction "ping" ["pong" `post` amount{acommodity="*", aquantity=3}]) nulltransaction{tpostings=["ping" `post` usd 2]}
-- >>> runModifierTransaction Any (ModifierTransaction "ping" ["pong" `post` amount{amultiplier=True, aquantity=3}]) nulltransaction{tpostings=["ping" `post` usd 2]}
-- 0000/01/01
-- ping $2.00
-- pong $6.00
@ -107,7 +107,7 @@ tdates t = tdate t : concatMap pdates (tpostings t) ++ maybeToList (tdate2 t) wh
postingScale :: Posting -> Maybe Quantity
postingScale p =
case amounts $ pamount p of
[a] | acommodity a == "*" -> Just $ aquantity a
[a] | amultiplier a -> Just $ aquantity a
_ -> Nothing
runModifierPosting :: Posting -> (Posting -> Posting)
@ -117,10 +117,12 @@ runModifierPosting p' = modifier where
, pdate2 = pdate2 p
, pamount = amount' p
}
amount' =
case postingScale p' of
Nothing -> const $ pamount p'
Just n -> \p -> pamount p `divideMixedAmount` (1/n)
amount' = case postingScale p' of
Nothing -> const $ pamount p'
Just n -> \p -> withAmountType (head $ amounts $ pamount p') $ pamount p `divideMixedAmount` (1/n)
withAmountType amount (Mixed as) = case acommodity amount of
"" -> Mixed as
c -> Mixed [a{acommodity = c, astyle = astyle amount, aprice = aprice amount} | a <- as]
renderPostingCommentDates :: Posting -> Posting
renderPostingCommentDates p = p { pcomment = comment' }

View File

@ -24,7 +24,7 @@ import Hledger.Utils
-- characters that may not be used in a non-quoted commodity symbol
nonsimplecommoditychars = "0123456789-+.@;\n \"{}=" :: [Char]
nonsimplecommoditychars = "0123456789-+.@*;\n \"{}=" :: [Char]
quoteCommoditySymbolIfNeeded s | any (`elem` nonsimplecommoditychars) (T.unpack s) = "\"" <> s <> "\""
| otherwise = s

View File

@ -158,10 +158,11 @@ data Commodity = Commodity {
instance NFData Commodity
data Amount = Amount {
acommodity :: CommoditySymbol,
aquantity :: Quantity,
aprice :: Price, -- ^ the (fixed) price for this amount, if any
astyle :: AmountStyle
acommodity :: CommoditySymbol,
aquantity :: Quantity,
aprice :: Price, -- ^ the (fixed) price for this amount, if any
astyle :: AmountStyle,
amultiplier :: Bool -- ^ amount is a multipier for AutoTransactions
} deriving (Eq,Ord,Typeable,Data,Generic)
instance NFData Amount

View File

@ -371,30 +371,39 @@ signp = do
return $ case sign of Just '-' -> "-"
_ -> ""
multiplierp :: TextParser m Bool
multiplierp = do
multiplier <- optional $ oneOf ("*" :: [Char])
return $ case multiplier of Just '*' -> True
_ -> False
leftsymbolamountp :: Monad m => JournalStateParser m Amount
leftsymbolamountp = do
sign <- lift signp
m <- lift multiplierp
c <- lift commoditysymbolp
sp <- lift $ many spacenonewline
(q,prec,mdec,mgrps) <- lift numberp
let s = amountstyle{ascommodityside=L, ascommodityspaced=not $ null sp, asprecision=prec, asdecimalpoint=mdec, asdigitgroups=mgrps}
p <- priceamountp
let applysign = if sign=="-" then negate else id
return $ applysign $ Amount c q p s
return $ applysign $ Amount c q p s m
<?> "left-symbol amount"
rightsymbolamountp :: Monad m => JournalStateParser m Amount
rightsymbolamountp = do
m <- lift multiplierp
(q,prec,mdec,mgrps) <- lift numberp
sp <- lift $ many spacenonewline
c <- lift commoditysymbolp
p <- priceamountp
let s = amountstyle{ascommodityside=R, ascommodityspaced=not $ null sp, asprecision=prec, asdecimalpoint=mdec, asdigitgroups=mgrps}
return $ Amount c q p s
return $ Amount c q p s m
<?> "right-symbol amount"
nosymbolamountp :: Monad m => JournalStateParser m Amount
nosymbolamountp = do
m <- lift multiplierp
(q,prec,mdec,mgrps) <- lift numberp
p <- priceamountp
-- apply the most recently seen default commodity and style to this commodityless amount
@ -402,7 +411,7 @@ nosymbolamountp = do
let (c,s) = case defcs of
Just (defc,defs) -> (defc, defs{asprecision=max (asprecision defs) prec})
Nothing -> ("", amountstyle{asprecision=prec, asdecimalpoint=mdec, asdigitgroups=mgrps})
return $ Amount c q p s
return $ Amount c q p s m
<?> "no-symbol amount"
commoditysymbolp :: TextParser m CommoditySymbol

View File

@ -49,6 +49,80 @@
>>>2
>>>=0
# Add postings in another commodity
../../bin/hledger-rewrite -f-
<<<
2017/04/24 * 09:00-09:25
(assets:unbilled:client1) 0.42h
2017/04/25 * 10:00-11:15
(assets:unbilled:client1) 1.25h
2017/04/25 * 14:00-15:32
(assets:unbilled:client2) 1.54h
; billing rules
= ^assets:unbilled:client1
(assets:to bill:client1) *100.00 CAD
= ^assets:unbilled:client2
(assets:to bill:client2) *150.00 CAD
>>>
2017/04/24 * 09:00-09:25
(assets:unbilled:client1) 0.42h
(assets:to bill:client1) 42.00 CAD
2017/04/25 * 10:00-11:15
(assets:unbilled:client1) 1.25h
(assets:to bill:client1) 125.00 CAD
2017/04/25 * 14:00-15:32
(assets:unbilled:client2) 1.54h
(assets:to bill:client2) 231.00 CAD
>>>2
>>>=0
# Add postings with prices
../../bin/hledger-rewrite -f- -B
<<<
2017/04/24 * 09:00-09:25
(assets:unbilled:client1) 0.42h
2017/04/25 * 10:00-11:15
(assets:unbilled:client1) 1.25h
2017/04/25 * 14:00-15:32
(assets:unbilled:client2) 1.54h
; billing rules
= ^assets:unbilled:client1
assets:to bill:client1 *1.00 hours @ $100.00
income:consulting:client1
= ^assets:unbilled:client2
assets:to bill:client2 *1.00 hours @ $150.00
income:consulting:client2
>>>
2017/04/24 * 09:00-09:25
(assets:unbilled:client1) 0.42h
assets:to bill:client1 $42.00
income:consulting:client1
2017/04/25 * 10:00-11:15
(assets:unbilled:client1) 1.25h
assets:to bill:client1 $125.00
income:consulting:client1
2017/04/25 * 14:00-15:32
(assets:unbilled:client2) 1.54h
assets:to bill:client2 $231.00
income:consulting:client2
>>>2
>>>=0
# Add absolute bank processing fee
../../bin/hledger-rewrite -f- assets:bank and 'amt:<0' --add-posting 'expenses:fee $5' --add-posting 'assets:bank $-5'
<<<