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:
parent
3059a0b2ca
commit
6c2398e4d7
@ -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
|
||||
-- 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=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
|
||||
, 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}}
|
||||
,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})
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
161
tests/close.test
161
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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user