diff --git a/hledger/Hledger/Cli/Commands/Close.hs b/hledger/Hledger/Cli/Commands/Close.hs index c51ff6571..e0f28dd1c 100755 --- a/hledger/Hledger/Cli/Commands/Close.hs +++ b/hledger/Hledger/Cli/Commands/Close.hs @@ -8,6 +8,8 @@ module Hledger.Cli.Commands.Close ( where import Control.Monad (when) +import Data.Function (on) +import Data.List (groupBy) import Data.Maybe import Data.Time.Calendar import System.Console.CmdArgs.Explicit as C @@ -19,6 +21,7 @@ closemode = hledgerCommandMode $(embedFileRelative "Hledger/Cli/Commands/Close.txt") [flagNone ["opening"] (setboolopt "opening") "show just opening transaction" ,flagNone ["closing"] (setboolopt "closing") "show just closing transaction" + -- ,flagNone ["explicit","x"] (setboolopt "explicit") "show all amounts explicitly" ] [generalflagsgroup1] hiddenflags @@ -36,31 +39,61 @@ close CliOpts{rawopts_=rawopts, reportopts_=ropts} j = do openingdate = fromMaybe today $ queryEndDate False q closingdate = addDays (-1) openingdate (acctbals,_) = balanceReportFromMultiBalanceReport ropts_ q j - balancingamt = negate $ sum $ map (\(_,_,_,b) -> normaliseMixedAmountSquashPricesForDisplay b) acctbals + balancingamt = negate $ sum $ map (\(_,_,_,b) -> normaliseMixedAmount b) acctbals -- since balance assertion amounts are required to be exact, the -- amounts in opening/closing transactions should be too (#941) -- setprec = setFullPrecision setprec = setNaturalPrecision - -- balance assertion amounts will be unpriced, cf #824 - closingps = [posting{paccount=a - ,pamount=mixed [setprec $ negate b] - ,pbalanceassertion=Just assertion{baamount=setprec b{aquantity=0, aprice=Nothing}} + -- balance assertion amounts will be unpriced (#824) + -- only the last posting in each commodity will have a balance assertion (#1035) + closingps = [posting{paccount = a + ,pamount = mixed [setprec $ negate b] + ,pbalanceassertion = if islast then Just assertion{baamount=setprec b{aquantity=0, aprice=Nothing}} else Nothing } | (a,_,_,mb) <- acctbals - , b <- amounts $ normaliseMixedAmountSquashPricesForDisplay mb + -- the balances in each commodity, and for each transaction price + , let bs = amounts $ normaliseMixedAmount mb + -- mark the last balance in each commodity + , let bs' = concat [reverse $ zip (reverse bs) (True : repeat False) + | bs <- groupBy ((==) `on` acommodity) bs] + , (b, islast) <- bs' + ] + -- The balancing posting to equity. Allow this one to have a multicommodity amount, + -- and don't try to assert its balance. + ++ + [posting{paccount = "equity:closing balances" + ,pamount = negate balancingamt + } ] - ++ [posting{paccount="equity:closing balances", pamount=negate balancingamt}] - openingps = [posting{paccount=a - ,pamount=mixed [setprec b] - ,pbalanceassertion=Just assertion{baamount=setprec b{aprice=Nothing}} + openingps = [posting{paccount = a + ,pamount = mixed [setprec b] + ,pbalanceassertion = case mcommoditysum of + Just s -> Just assertion{baamount=setprec s{aprice=Nothing}} + Nothing -> Nothing } | (a,_,_,mb) <- acctbals - , b <- amounts $ normaliseMixedAmountSquashPricesForDisplay mb + -- the balances in each commodity, and for each transaction price + , let bs = amounts $ normaliseMixedAmount mb + -- mark the last balance in each commodity, with the unpriced sum in that commodity + , let bs' = concat [reverse $ zip (reverse bs) (Just commoditysum : repeat Nothing) + | bs <- groupBy ((==) `on` acommodity) bs + , let commoditysum = (sum bs)] + , (b, mcommoditysum) <- bs' + ] + ++ + [posting{paccount = "equity:opening balances" + ,pamount = balancingamt + } ] - ++ [posting{paccount="equity:opening balances", pamount=balancingamt}] - when closing $ putStr $ showTransaction (nulltransaction{tdate=closingdate, tdescription="closing balances", tpostings=closingps}) - when opening $ putStr $ showTransaction (nulltransaction{tdate=openingdate, tdescription="opening balances", tpostings=openingps}) + -- With -x, show all amounts explicitly (ie, also in the balancing equity posting(s)). + -- print also does it for -B; I think that isn't needed here. + -- showtxn | boolopt "explicit" rawopts = showTransactionUnelided + -- | otherwise = showTransaction + showtxn = showTransactionUnelided + + when closing $ putStr $ showtxn (nulltransaction{tdate=closingdate, tdescription="closing balances", tpostings=closingps}) + when opening $ putStr $ showtxn (nulltransaction{tdate=openingdate, tdescription="opening balances", tpostings=openingps}) diff --git a/hledger/Hledger/Cli/Commands/Close.md b/hledger/Hledger/Cli/Commands/Close.md index ee710d8a8..0ff7393e8 100644 --- a/hledger/Hledger/Cli/Commands/Close.md +++ b/hledger/Hledger/Cli/Commands/Close.md @@ -42,6 +42,10 @@ the generated balance assertions will depend on these flags. Likewise, if you run this command with --auto, the balance assertions will probably always require --auto. +When account balances have cost information (transaction prices), the +closing/opening transactions will preserve it, so that eg balance -B reports +will not be affected. + Examples: Carrying asset/liability balances into a new file for 2019, all from command line: diff --git a/tests/close.test b/tests/close.test index b59af2949..782783ef3 100644 --- a/tests/close.test +++ b/tests/close.test @@ -23,53 +23,17 @@ $ hledger close -f- -p 2016 assets liabilities assets:bank $-80 = $0 assets:cash $-10 = $0 liabilities $-25 = $0 - equity:closing balances + equity:closing balances $115 2017/01/01 opening balances assets:bank $80 = $80 assets:cash $10 = $10 liabilities $25 = $25 - equity:opening balances + equity:opening balances $-115 >=0 -# 2. Test aggregation of postings with prices -< -Y2016 -01/31 - liabilities:employer $5,000.00 - income:salary - -02/05 - liabilities:employer -$5,000.00 @ 0.95 EUR - expenses:tax 1,852.50 EUR - assets:bank 2,897.00 EUR - liabilities:employer - -02/29 - liabilities:employer $5,000.00 - income:salary - -03/04 - liabilities:employer -$5,000.0 @ 0.93 EUR - expenses:tax 1,813.50 EUR - assets:bank 2,836.00 EUR - liabilities:employer - -$ hledger close -f- -p 2016 assets liabilities -2016/12/31 closing balances - assets:bank -5,733 EUR = 0 EUR - liabilities:employer -1 EUR = 0 EUR - equity:closing balances - -2017/01/01 opening balances - assets:bank 5,733 EUR = 5,733 EUR - liabilities:employer 1 EUR = 1 EUR - equity:opening balances - ->=0 - -# 3. A begin date should be ignored +# 2. A begin date should be ignored < 2017/1/1 (a) 1 @@ -77,15 +41,15 @@ $ hledger close -f- -p 2016 assets liabilities $ hledger close -f- -b2017/6/1 -e2018 2017/12/31 closing balances a -1 = 0 - equity:closing balances + equity:closing balances 1 2018/01/01 opening balances a 1 = 1 - equity:opening balances + equity:opening balances -1 >=0 -# 4. Print just the opening transaction +# 3. Print just the opening transaction < 2016/1/1 open assets:bank $100 @@ -109,11 +73,11 @@ $ hledger close -f- -p 2016 assets liabilities --opening assets:bank $80 = $80 assets:cash $10 = $10 liabilities $25 = $25 - equity:opening balances + equity:opening balances $-115 >=0 -# 5. Print just the closing transaction +# 4. Print just the closing transaction < 2016/1/1 open assets:bank $100 @@ -137,11 +101,11 @@ $ hledger close -f- -p 2016 assets liabilities --closing assets:bank $-80 = $0 assets:cash $-10 = $0 liabilities $-25 = $0 - equity:closing balances + equity:closing balances $115 >=0 -# 6. Supplying --opening --closing is the same as just "close" +# 5. Supplying --opening --closing is the same as just "close" < 2016/1/1 open assets:bank $100 @@ -165,12 +129,113 @@ $ hledger close -f- -p 2016 assets liabilities --opening --closing assets:bank $-80 = $0 assets:cash $-10 = $0 liabilities $-25 = $0 - equity:closing balances + equity:closing balances $115 2017/01/01 opening balances assets:bank $80 = $80 assets:cash $10 = $10 liabilities $25 = $25 - equity:opening balances + equity:opening balances $-115 + +>=0 + +# 6. Closing a multi-priced balance. The "lot" prices are preserved. +# Only the last posting in each commodity gets a balance assertion (#1035). +# Balance assertion amounts do not have a price. +< +2019/01/01 + assets 1A @ 1B + assets 1A @ 1C + equity + +$ hledger -f- close assets -p 2019 +2019/12/31 closing balances + assets -1A @ 1B + assets -1A @ 1C = 0A + equity:closing balances 1A @ 1B + equity:closing balances 1A @ 1C + +2020/01/01 opening balances + assets 1A @ 1B + assets 1A @ 1C = 2A + equity:opening balances -1A @ 1B + equity:opening balances -1A @ 1C + +>=0 + +# 7. Closing a multi-priced balance, slightly more complex +# (different price in each transaction). Hopefully +# equivalent to 8. +< +2019/01/01 + (assets) 1A @ 1B + +2019/01/02 + (assets) 1A @ 2B + +$ hledger -f- close assets -p 2019 +2019/12/31 closing balances + assets -1A @ 1B + assets -1A @ 2B = 0A + equity:closing balances 1A @ 1B + equity:closing balances 1A @ 2B + +2020/01/01 opening balances + assets 1A @ 1B + assets 1A @ 2B = 2A + equity:opening balances -1A @ 1B + equity:opening balances -1A @ 2B + +>=0 + +# 8. Closing a multi-priced balance, a more complex example. +< +2016/01/31 + liabilities:employer $5,000.00 + income:salary + +2016/02/05 + liabilities:employer $-5,000.00 @ 0.95 EUR + expenses:tax 1,852.50 EUR + assets:bank 2,897.00 EUR + liabilities:employer + +2016/02/29 + liabilities:employer $5,000.00 + income:salary + +2016/03/04 + liabilities:employer $-5,000.00 @ 0.93 EUR + expenses:tax 1,813.50 EUR + assets:bank 2,836.00 EUR + liabilities:employer + +; Note: without these declarations, the closing/opening entries below +; would cause decimal marks to be misparsed. (How ?) +;commodity $1,000.00 +;commodity 1,000.00 EUR + +$ hledger -f- close -p 2016 assets liabilities +2016/12/31 closing balances + assets:bank -5,733 EUR = 0 EUR + liabilities:employer $-10,000 + liabilities:employer $5,000 @ 0.93 EUR + liabilities:employer $5,000 @ 0.95 EUR = $0 + liabilities:employer -1 EUR = 0 EUR + equity:closing balances $10,000.00 + equity:closing balances $-5,000.00 @ 0.93 EUR + equity:closing balances $-5,000.00 @ 0.95 EUR + equity:closing balances 5,734.00 EUR + +2017/01/01 opening balances + assets:bank 5,733 EUR = 5,733 EUR + liabilities:employer $10,000 + liabilities:employer $-5,000 @ 0.93 EUR + liabilities:employer $-5,000 @ 0.95 EUR = $0 + liabilities:employer 1 EUR = 1 EUR + equity:opening balances $-10,000.00 + equity:opening balances $5,000.00 @ 0.93 EUR + equity:opening balances $5,000.00 @ 0.95 EUR + equity:opening balances -5,734.00 EUR >=0 diff --git a/tests/journal/balance-assertions.test b/tests/journal/balance-assertions.test index bf22e8c53..8e5259189 100755 --- a/tests/journal/balance-assertions.test +++ b/tests/journal/balance-assertions.test @@ -365,23 +365,7 @@ hledger -f- print --explicit >>>=0 -# 21. close generates balance assertions without prices -hledger -f- close -e 2019/1/2 -<<< -2019/01/01 - (a) 1A @ 1B = 1A @ 2B ->>> -2019/01/01 closing balances - a -1A @ 1B = 0A - equity:closing balances - -2019/01/02 opening balances - a 1A @ 1B = 1A - equity:opening balances - ->>>=0 - -# 22. The exact amounts are compared; display precision does not affect assertions. +# 21. The exact amounts are compared; display precision does not affect assertions. hledger -f- print <<< commodity $1000.00 @@ -396,7 +380,7 @@ commodity $1000.00 >>>2 >>>=0 -# 23. This fails +# 22. This fails hledger -f- print <<< commodity $1000.00 @@ -410,7 +394,7 @@ commodity $1000.00 >>>2 /difference: 0\.004/ >>>=1 -# 24. This fails +# 23. This fails hledger -f- print <<< commodity $1000.00 @@ -424,7 +408,7 @@ commodity $1000.00 >>>2 /difference: 0\.0001/ >>>=1 -# 25. Inclusive assertions include balances from subaccounts. +# 24. Inclusive assertions include balances from subaccounts. hledger -f- print <<< 2019/1/1