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