Skip to content
Permalink
Browse files

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.
  • Loading branch information...
simonmichael committed Jul 9, 2019
1 parent 3059a0b commit 6c2398e4d7c271ffc58ea3654c21b7a7dfc6921d
Showing with 168 additions and 82 deletions.
  1. +47 −14 hledger/Hledger/Cli/Commands/Close.hs
  2. +4 −0 hledger/Hledger/Cli/Commands/Close.md
  3. +113 −48 tests/close.test
  4. +4 −20 tests/journal/balance-assertions.test
@@ -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
}
]

-- 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 $ showTransaction (nulltransaction{tdate=closingdate, tdescription="closing balances", tpostings=closingps})
when opening $ putStr $ showTransaction (nulltransaction{tdate=openingdate, tdescription="opening balances", tpostings=openingps})
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:
@@ -23,69 +23,33 @@ $ 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

$ 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

0 comments on commit 6c2398e

Please sign in to comment.
You can’t perform that action at this time.