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 | 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}) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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: | ||||||
|  | |||||||
							
								
								
									
										161
									
								
								tests/close.test
									
									
									
									
									
								
							
							
						
						
									
										161
									
								
								tests/close.test
									
									
									
									
									
								
							| @ -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 | ||||||
|  | |||||||
| @ -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 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user