close: preserve transaction prices (costs) accurately (#1035)

Transaction prices were being collapsed/misreported after close/open;
this is fixed. Now each separately-priced amount gets its own posting,
and only the last of these (for each commodity) gets a balance
assertion. Also the equity posting's amount is now always shown
explicitly, which in multicommodity situations means that multiple
equity postings are shown. The upshot is that a balance -B report
will be unchanged after closing & opening transactions.
This commit is contained in:
Simon Michael 2019-07-09 11:21:22 +01:00
parent 3059a0b2ca
commit 6c2398e4d7
4 changed files with 168 additions and 82 deletions

View File

@ -8,6 +8,8 @@ module Hledger.Cli.Commands.Close (
where where
import Control.Monad (when) import Control.Monad (when)
import Data.Function (on)
import Data.List (groupBy)
import Data.Maybe import Data.Maybe
import Data.Time.Calendar import Data.Time.Calendar
import System.Console.CmdArgs.Explicit as C import System.Console.CmdArgs.Explicit as C
@ -19,6 +21,7 @@ closemode = hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Close.txt") $(embedFileRelative "Hledger/Cli/Commands/Close.txt")
[flagNone ["opening"] (setboolopt "opening") "show just opening transaction" [flagNone ["opening"] (setboolopt "opening") "show just opening transaction"
,flagNone ["closing"] (setboolopt "closing") "show just closing transaction" ,flagNone ["closing"] (setboolopt "closing") "show just closing transaction"
-- ,flagNone ["explicit","x"] (setboolopt "explicit") "show all amounts explicitly"
] ]
[generalflagsgroup1] [generalflagsgroup1]
hiddenflags hiddenflags
@ -36,31 +39,61 @@ close CliOpts{rawopts_=rawopts, reportopts_=ropts} j = do
openingdate = fromMaybe today $ queryEndDate False q openingdate = fromMaybe today $ queryEndDate False q
closingdate = addDays (-1) openingdate closingdate = addDays (-1) openingdate
(acctbals,_) = balanceReportFromMultiBalanceReport ropts_ q j (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 -- since balance assertion amounts are required to be exact, the
-- amounts in opening/closing transactions should be too (#941) -- amounts in opening/closing transactions should be too (#941)
-- setprec = setFullPrecision -- setprec = setFullPrecision
setprec = setNaturalPrecision setprec = setNaturalPrecision
-- balance assertion amounts will be unpriced, cf #824 -- balance assertion amounts will be unpriced (#824)
-- only the last posting in each commodity will have a balance assertion (#1035)
closingps = [posting{paccount = a closingps = [posting{paccount = a
,pamount = mixed [setprec $ negate b] ,pamount = mixed [setprec $ negate b]
,pbalanceassertion=Just assertion{baamount=setprec b{aquantity=0, aprice=Nothing}} ,pbalanceassertion = if islast then Just assertion{baamount=setprec b{aquantity=0, aprice=Nothing}} else Nothing
} }
| (a,_,_,mb) <- acctbals | (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 openingps = [posting{paccount = a
,pamount = mixed [setprec b] ,pamount = mixed [setprec b]
,pbalanceassertion=Just assertion{baamount=setprec b{aprice=Nothing}} ,pbalanceassertion = case mcommoditysum of
Just s -> Just assertion{baamount=setprec s{aprice=Nothing}}
Nothing -> Nothing
} }
| (a,_,_,mb) <- acctbals | (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}) -- With -x, show all amounts explicitly (ie, also in the balancing equity posting(s)).
when opening $ putStr $ showTransaction (nulltransaction{tdate=openingdate, tdescription="opening balances", tpostings=openingps}) -- 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})

View File

@ -42,6 +42,10 @@ the generated balance assertions will depend on these flags.
Likewise, if you run this command with --auto, the balance assertions Likewise, if you run this command with --auto, the balance assertions
will probably always require --auto. 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: Examples:
Carrying asset/liability balances into a new file for 2019, all from command line: Carrying asset/liability balances into a new file for 2019, all from command line:

View File

@ -23,53 +23,17 @@ $ hledger close -f- -p 2016 assets liabilities
assets:bank $-80 = $0 assets:bank $-80 = $0
assets:cash $-10 = $0 assets:cash $-10 = $0
liabilities $-25 = $0 liabilities $-25 = $0
equity:closing balances equity:closing balances $115
2017/01/01 opening balances 2017/01/01 opening balances
assets:bank $80 = $80 assets:bank $80 = $80
assets:cash $10 = $10 assets:cash $10 = $10
liabilities $25 = $25 liabilities $25 = $25
equity:opening balances equity:opening balances $-115
>=0 >=0
# 2. Test aggregation of postings with prices # 2. A begin date should be ignored
<
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
< <
2017/1/1 2017/1/1
(a) 1 (a) 1
@ -77,15 +41,15 @@ $ hledger close -f- -p 2016 assets liabilities
$ hledger close -f- -b2017/6/1 -e2018 $ hledger close -f- -b2017/6/1 -e2018
2017/12/31 closing balances 2017/12/31 closing balances
a -1 = 0 a -1 = 0
equity:closing balances equity:closing balances 1
2018/01/01 opening balances 2018/01/01 opening balances
a 1 = 1 a 1 = 1
equity:opening balances equity:opening balances -1
>=0 >=0
# 4. Print just the opening transaction # 3. Print just the opening transaction
< <
2016/1/1 open 2016/1/1 open
assets:bank $100 assets:bank $100
@ -109,11 +73,11 @@ $ hledger close -f- -p 2016 assets liabilities --opening
assets:bank $80 = $80 assets:bank $80 = $80
assets:cash $10 = $10 assets:cash $10 = $10
liabilities $25 = $25 liabilities $25 = $25
equity:opening balances equity:opening balances $-115
>=0 >=0
# 5. Print just the closing transaction # 4. Print just the closing transaction
< <
2016/1/1 open 2016/1/1 open
assets:bank $100 assets:bank $100
@ -137,11 +101,11 @@ $ hledger close -f- -p 2016 assets liabilities --closing
assets:bank $-80 = $0 assets:bank $-80 = $0
assets:cash $-10 = $0 assets:cash $-10 = $0
liabilities $-25 = $0 liabilities $-25 = $0
equity:closing balances equity:closing balances $115
>=0 >=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 2016/1/1 open
assets:bank $100 assets:bank $100
@ -165,12 +129,113 @@ $ hledger close -f- -p 2016 assets liabilities --opening --closing
assets:bank $-80 = $0 assets:bank $-80 = $0
assets:cash $-10 = $0 assets:cash $-10 = $0
liabilities $-25 = $0 liabilities $-25 = $0
equity:closing balances equity:closing balances $115
2017/01/01 opening balances 2017/01/01 opening balances
assets:bank $80 = $80 assets:bank $80 = $80
assets:cash $10 = $10 assets:cash $10 = $10
liabilities $25 = $25 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 >=0

View File

@ -365,23 +365,7 @@ hledger -f- print --explicit
>>>=0 >>>=0
# 21. close generates balance assertions without prices # 21. The exact amounts are compared; display precision does not affect assertions.
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.
hledger -f- print hledger -f- print
<<< <<<
commodity $1000.00 commodity $1000.00
@ -396,7 +380,7 @@ commodity $1000.00
>>>2 >>>2
>>>=0 >>>=0
# 23. This fails # 22. This fails
hledger -f- print hledger -f- print
<<< <<<
commodity $1000.00 commodity $1000.00
@ -410,7 +394,7 @@ commodity $1000.00
>>>2 /difference: 0\.004/ >>>2 /difference: 0\.004/
>>>=1 >>>=1
# 24. This fails # 23. This fails
hledger -f- print hledger -f- print
<<< <<<
commodity $1000.00 commodity $1000.00
@ -424,7 +408,7 @@ commodity $1000.00
>>>2 /difference: 0\.0001/ >>>2 /difference: 0\.0001/
>>>=1 >>>=1
# 25. Inclusive assertions include balances from subaccounts. # 24. Inclusive assertions include balances from subaccounts.
hledger -f- print hledger -f- print
<<< <<<
2019/1/1 2019/1/1