diff --git a/hledger/Hledger/Cli/Commands/Close.hs b/hledger/Hledger/Cli/Commands/Close.hs index 31160b96d..f29ea4487 100644 --- a/hledger/Hledger/Cli/Commands/Close.hs +++ b/hledger/Hledger/Cli/Commands/Close.hs @@ -32,6 +32,8 @@ closemode = hledgerCommandMode [flagNone ["close"] (setboolopt "close") "show a closing transaction (default)" ,flagNone ["open"] (setboolopt "open") "show an opening transaction" ,flagNone ["migrate"] (setboolopt "migrate") "show both closing and opening transactions" + ,flagNone ["assert"] (setboolopt "assert") "show closing balance assertions" + ,flagNone ["assign"] (setboolopt "assign") "show opening balance assignments (an alternative to closing/opening transactions)" ,flagNone ["retain"] (setboolopt "retain") "show a retain earnings transaction (for RX accounts)" ,flagNone ["explicit","x"] (setboolopt "explicit") "show all amounts explicitly" ,flagNone ["show-costs"] (setboolopt "show-costs") "show amounts with different costs separately" @@ -57,11 +59,14 @@ closemode = hledgerCommandMode -- This code is also used by the close command. close copts@CliOpts{rawopts_=rawopts, reportspec_=rspec0} j = do let - (close_, open_, defclosedesc_, defopendesc_, defcloseacct_, defacctsq_) = if - | boolopt "retain" rawopts -> (True, False, defretaindesc, undefined, defretainacct, Type [Revenue, Expense]) - | boolopt "migrate" rawopts -> (True, True, defclosedesc, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) - | boolopt "open" rawopts -> (False, True, undefined, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) - | otherwise -> (True, False, defclosedesc, undefined, defcloseacct, Type [Asset, Liability, Equity]) + -- currently only one of the six mode flags takes effect at a time (hledger close --close --open only does --open). + (close_, open_, assert_, assign_, defclosedesc_, defopendesc_, defcloseacct_, defacctsq_) = if + | boolopt "retain" rawopts -> (True, False, False, False, defretaindesc, undefined, defretainacct, Type [Revenue, Expense]) + | boolopt "migrate" rawopts -> (True, True, False, False, defclosedesc, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) + | boolopt "assign" rawopts -> (False, False, False, True, undefined, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) + | boolopt "assert" rawopts -> (False, False, True, False, defclosedesc, undefined, defcloseacct, Type [Asset, Liability, Equity]) + | boolopt "open" rawopts -> (False, True, False, False, undefined, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) + | otherwise -> (True, False, False, False, defclosedesc, undefined, defcloseacct, Type [Asset, Liability, Equity]) -- close -- descriptions to use for the closing/opening transactions closedesc = T.pack $ fromMaybe defclosedesc_ $ maybestringopt "close-desc" rawopts @@ -106,59 +111,101 @@ close copts@CliOpts{rawopts_=rawopts, reportspec_=rspec0} j = do -- interleave equity postings next to the corresponding closing posting, or put them all at the end ? interleaved = boolopt "interleaved" rawopts - -- the closing transaction + -- the closing (balance-asserting or balance-zeroing) transaction closetxn = nulltransaction{tdate=closedate, tdescription=closedesc, tpostings=closeps} - closeps = - concat [ - posting{paccount = a - ,pamount = mixedAmount . precise $ negate b - -- after each commodity's last posting, assert 0 balance (#1035) - -- balance assertion amounts are unpriced (#824) - ,pbalanceassertion = - if islast - then Just nullassertion{baamount=precise b{aquantity=0, aprice=Nothing}} - else Nothing - } - - -- maybe an interleaved posting transferring this balance to equity - : [posting{paccount=closeacct, pamount=mixedAmount $ precise b} | interleaved] - - | -- get the balances for each commodity and transaction price - (a,mb) <- acctbals - , let bs0 = amounts mb - -- mark the last balance in each commodity with True - , let bs2 = concat [reverse $ zip (reverse bs1) (True : repeat False) - | bs1 <- groupBy ((==) `on` acommodity) bs0] - , (b, islast) <- bs2 + closeps + -- XXX some duplication + | assert_ = + [ posting{ + paccount = a + ,pamount = mixedAmount $ precise b{aquantity=0, aprice=Nothing} + -- after each commodity's last posting, assert 0 balance (#1035) + -- balance assertion amounts are unpriced (#824) + ,pbalanceassertion = + if islast + then Just nullassertion{baamount=precise b} + else Nothing + } + | -- get the balances for each commodity and transaction price + (a,mb) <- acctbals + , let bs0 = amounts mb + -- mark the last balance in each commodity with True + , let bs2 = concat [reverse $ zip (reverse bs1) (True : repeat False) + | bs1 <- groupBy ((==) `on` acommodity) bs0] + , (b, islast) <- bs2 ] - -- or a final multicommodity posting transferring all balances to equity - -- (print will show this as multiple single-commodity postings) - ++ [posting{paccount=closeacct, pamount=if explicit then mixedAmountSetFullPrecision totalamt else missingmixedamt} | not interleaved] + | otherwise = + concat [ + posting{paccount = a + ,pamount = mixedAmount . precise $ negate b + -- after each commodity's last posting, assert 0 balance (#1035) + -- balance assertion amounts are unpriced (#824) + ,pbalanceassertion = + if islast + then Just nullassertion{baamount=precise b{aquantity=0, aprice=Nothing}} + else Nothing + } - -- the opening transaction + -- maybe an interleaved posting transferring this balance to equity + : [posting{paccount=closeacct, pamount=mixedAmount $ precise b} | interleaved] + + | -- get the balances for each commodity and transaction price + (a,mb) <- acctbals + , let bs0 = amounts mb + -- mark the last balance in each commodity with True + , let bs2 = concat [reverse $ zip (reverse bs1) (True : repeat False) + | bs1 <- groupBy ((==) `on` acommodity) bs0] + , (b, islast) <- bs2 + ] + + -- or a final multicommodity posting transferring all balances to equity + -- (print will show this as multiple single-commodity postings) + ++ [posting{paccount=closeacct, pamount=if explicit then mixedAmountSetFullPrecision totalamt else missingmixedamt} | not interleaved] + + -- the opening (balance-assigning or balance-unzeroing) transaction opentxn = nulltransaction{tdate=opendate, tdescription=opendesc, tpostings=openps} - openps = - concat [ - posting{paccount = a - ,pamount = mixedAmount $ precise b - ,pbalanceassertion = - case mcommoditysum of - Just s -> Just nullassertion{baamount=precise s{aprice=Nothing}} - Nothing -> Nothing - } - : [posting{paccount=openacct, pamount=mixedAmount . precise $ negate b} | interleaved] + openps + | assign_ = + [ posting{paccount = a + ,pamount = missingmixedamt + ,pbalanceassertion = Just nullassertion{baamount=b} + -- case mcommoditysum of + -- Just s -> Just nullassertion{baamount=precise s} + -- Nothing -> Nothing + } - | (a,mb) <- acctbals - , let bs0 = amounts mb - -- mark the last balance in each commodity with the unpriced sum in that commodity (for a balance assertion) - , let bs2 = concat [reverse $ zip (reverse bs1) (Just commoditysum : repeat Nothing) - | bs1 <- groupBy ((==) `on` acommodity) bs0 - , let commoditysum = (sum bs1)] - , (b, mcommoditysum) <- bs2 + | (a,mb) <- acctbals + , let bs0 = amounts mb + -- mark the last balance in each commodity with the unpriced sum in that commodity (for a balance assertion) + , let bs2 = concat [reverse $ zip (reverse bs1) (Just commoditysum : repeat Nothing) + | bs1 <- groupBy ((==) `on` acommodity) bs0 + , let commoditysum = (sum bs1)] + , (b, _mcommoditysum) <- bs2 ] - ++ [posting{paccount=openacct, pamount=if explicit then mixedAmountSetFullPrecision (maNegate totalamt) else missingmixedamt} | not interleaved] + ++ [posting{paccount=openacct, pamount=if explicit then mixedAmountSetFullPrecision (maNegate totalamt) else missingmixedamt} | not interleaved] + + | otherwise = + concat [ + posting{paccount = a + ,pamount = mixedAmount $ precise b + ,pbalanceassertion = + case mcommoditysum of + Just s -> Just nullassertion{baamount=precise s{aprice=Nothing}} + Nothing -> Nothing + } + : [posting{paccount=openacct, pamount=mixedAmount . precise $ negate b} | interleaved] + + | (a,mb) <- acctbals + , let bs0 = amounts mb + -- mark the last balance in each commodity with the unpriced sum in that commodity (for a balance assertion) + , let bs2 = concat [reverse $ zip (reverse bs1) (Just commoditysum : repeat Nothing) + | bs1 <- groupBy ((==) `on` acommodity) bs0 + , let commoditysum = (sum bs1)] + , (b, mcommoditysum) <- bs2 + ] + ++ [posting{paccount=openacct, pamount=if explicit then mixedAmountSetFullPrecision (maNegate totalamt) else missingmixedamt} | not interleaved] -- print them - when close_ . T.putStr $ showTransaction closetxn - when open_ . T.putStr $ showTransaction opentxn + when (close_ || assert_) . T.putStr $ showTransaction closetxn + when (open_ || assign_) . T.putStr $ showTransaction opentxn diff --git a/hledger/Hledger/Cli/Commands/Close.md b/hledger/Hledger/Cli/Commands/Close.md index d6bb6739a..6ee167765 100644 --- a/hledger/Hledger/Cli/Commands/Close.md +++ b/hledger/Hledger/Cli/Commands/Close.md @@ -2,42 +2,58 @@ (equity) -Generate transactions which transfer account balances to and/or from -another account (typically equity). -This can be useful for migrating balances to a new journal file, -or for merging earnings into equity at end of accounting period. - -By default, it prints a transaction that zeroes out ALE accounts -(asset, liability, equity accounts; this requires account types to be configured); -or if ACCTQUERY is provided, the accounts matched by that. +A transaction-generating command which generates several kinds of "closing" +and/or "opening" transactions useful in certain situations. +It prints one or two transactions to stdout, but does not write them to the journal automatically. *(experimental)* _FLAGS -This command has four main modes, corresponding to the most common use cases: +This command is most often used when migrating balances to a new +journal file, at the start of a new financial year. There are a few +ways to do that, we're not sure which is best, and this command has +other uses as well; so it currently has six modes, selected by a mode +flag. Use only one of these flags at a time: -1. With `--close` (default), it prints a "closing balances" transaction -that zeroes out ALE (asset, liability, equity) accounts by default -(this requires [account types](hledger.md#account-types) to be inferred or declared); -or, the accounts matched by the provided ACCTQUERY arguments. +1. With `--close` (or no mode flag) it prints a "closing balances" transaction +that zeroes out all the ALE (asset, liability, equity) accounts, by default +(this requires inferred or declared [account types](hledger.md#account-types)); +or, the accounts matched by ACCTQUERY arguments you provide. +The balances are transferred to an equity account. -2. With `--open`, it prints an opposite "opening balances" transaction that restores -those balances from zero. This is similar to Ledger's equity command. +2. With `--open`, it prints an opposite "opening balances" transaction that +"unzeros" the same accounts, restoring their balances from zero. +(This mode is similar to Ledger's equity command.) -3. With `--migrate`, it prints both the closing and opening transactions. -This is the preferred way to migrate balances to a new file: -run `hledger close --migrate`, -add the closing transaction at the end of the old file, and -add the opening transaction at the start of the new file. -The matching closing/opening transactions cancel each other out, -preserving correct balances during multi-file reporting. +3. With `--migrate`, it prints both the `--close` and the `--open` transactions. +This is a common way to migrate balances to a new file at year end; +run `hledger close --migrate` (or `hledger close --close` and `hleddger close --open`) +and add the closing transaction at the end of the old file, +and the opening transaction at the start of the new file. +This allows you to use the new file by itself, or together with previous file(s), +and still get correct balances either way, +because the matching closing/opening transactions will cancel each other out. +(You will need to exclude them sometimes, eg to see an end of year balance sheet; +`not:opening/closing` often works.) -4. With `--retain`, it prints a "retain earnings" transaction that transfers +4. With `--assert` it prints a "closing balances" transaction that +just asserts the current balances, without changing them. + +5. With `--assign` it prints an "opening balances" transaction that +restores the account balances by using balance assignments, +which always succeed and do need to be preceded by a closing transaction. +This is an alternative to `--close` and `--open`: at year end +you can `--assert` in the old file and `--assign` in the new file; +(or skip the `--assert` and just do the `--assign`). +This sacrifices some error checking, but could be convenient if you are +often doing cleanups or fixes which break your closing/opening transactions. + +6. With `--retain`, it prints a "retain earnings" transaction that transfers RX (revenue and expense) balances to `equity:retained earnings`. -Businesses traditionally do this at the end of each accounting period; -it is less necessary with computer-based accounting, but it could still be useful -if you want to see the accounting equation (A=L+E) satisfied. +This is a traditional end-of-period bookkeeping operation also called "closing the books"; +in personal accounting you probably will not need this but it could be useful +if you want to see the accounting equation (A=L+E) balanced. In all modes, the defaults can be overridden: