enh:close: add --assert and --assign modes, generating assertions or assignments

This commit is contained in:
Simon Michael 2024-01-06 06:03:50 -10:00
parent 30de688765
commit 59999e4ada
2 changed files with 141 additions and 78 deletions

View File

@ -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

View File

@ -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: