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 ["close"] (setboolopt "close") "show a closing transaction (default)"
,flagNone ["open"] (setboolopt "open") "show an opening transaction" ,flagNone ["open"] (setboolopt "open") "show an opening transaction"
,flagNone ["migrate"] (setboolopt "migrate") "show both closing and opening transactions" ,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 ["retain"] (setboolopt "retain") "show a retain earnings transaction (for RX accounts)"
,flagNone ["explicit","x"] (setboolopt "explicit") "show all amounts explicitly" ,flagNone ["explicit","x"] (setboolopt "explicit") "show all amounts explicitly"
,flagNone ["show-costs"] (setboolopt "show-costs") "show amounts with different costs separately" ,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. -- This code is also used by the close command.
close copts@CliOpts{rawopts_=rawopts, reportspec_=rspec0} j = do close copts@CliOpts{rawopts_=rawopts, reportspec_=rspec0} j = do
let let
(close_, open_, defclosedesc_, defopendesc_, defcloseacct_, defacctsq_) = if -- currently only one of the six mode flags takes effect at a time (hledger close --close --open only does --open).
| boolopt "retain" rawopts -> (True, False, defretaindesc, undefined, defretainacct, Type [Revenue, Expense]) (close_, open_, assert_, assign_, defclosedesc_, defopendesc_, defcloseacct_, defacctsq_) = if
| boolopt "migrate" rawopts -> (True, True, defclosedesc, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) | boolopt "retain" rawopts -> (True, False, False, False, defretaindesc, undefined, defretainacct, Type [Revenue, Expense])
| boolopt "open" rawopts -> (False, True, undefined, defopendesc, defcloseacct, Type [Asset, Liability, Equity]) | boolopt "migrate" rawopts -> (True, True, False, False, defclosedesc, defopendesc, defcloseacct, Type [Asset, Liability, Equity])
| otherwise -> (True, False, defclosedesc, undefined, 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 -- descriptions to use for the closing/opening transactions
closedesc = T.pack $ fromMaybe defclosedesc_ $ maybestringopt "close-desc" rawopts 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 ? -- interleave equity postings next to the corresponding closing posting, or put them all at the end ?
interleaved = boolopt "interleaved" rawopts interleaved = boolopt "interleaved" rawopts
-- the closing transaction -- the closing (balance-asserting or balance-zeroing) transaction
closetxn = nulltransaction{tdate=closedate, tdescription=closedesc, tpostings=closeps} closetxn = nulltransaction{tdate=closedate, tdescription=closedesc, tpostings=closeps}
closeps = closeps
concat [ -- XXX some duplication
posting{paccount = a | assert_ =
,pamount = mixedAmount . precise $ negate b [ posting{
-- after each commodity's last posting, assert 0 balance (#1035) paccount = a
-- balance assertion amounts are unpriced (#824) ,pamount = mixedAmount $ precise b{aquantity=0, aprice=Nothing}
,pbalanceassertion = -- after each commodity's last posting, assert 0 balance (#1035)
if islast -- balance assertion amounts are unpriced (#824)
then Just nullassertion{baamount=precise b{aquantity=0, aprice=Nothing}} ,pbalanceassertion =
else Nothing if islast
} then Just nullassertion{baamount=precise b}
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
| -- get the balances for each commodity and transaction price , let bs0 = amounts mb
(a,mb) <- acctbals -- mark the last balance in each commodity with True
, let bs0 = amounts mb , let bs2 = concat [reverse $ zip (reverse bs1) (True : repeat False)
-- mark the last balance in each commodity with True | bs1 <- groupBy ((==) `on` acommodity) bs0]
, let bs2 = concat [reverse $ zip (reverse bs1) (True : repeat False) , (b, islast) <- bs2
| bs1 <- groupBy ((==) `on` acommodity) bs0]
, (b, islast) <- bs2
] ]
-- or a final multicommodity posting transferring all balances to equity | otherwise =
-- (print will show this as multiple single-commodity postings) concat [
++ [posting{paccount=closeacct, pamount=if explicit then mixedAmountSetFullPrecision totalamt else missingmixedamt} | not interleaved] 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} opentxn = nulltransaction{tdate=opendate, tdescription=opendesc, tpostings=openps}
openps = openps
concat [ | assign_ =
posting{paccount = a [ posting{paccount = a
,pamount = mixedAmount $ precise b ,pamount = missingmixedamt
,pbalanceassertion = ,pbalanceassertion = Just nullassertion{baamount=b}
case mcommoditysum of -- case mcommoditysum of
Just s -> Just nullassertion{baamount=precise s{aprice=Nothing}} -- Just s -> Just nullassertion{baamount=precise s}
Nothing -> Nothing -- Nothing -> Nothing
} }
: [posting{paccount=openacct, pamount=mixedAmount . precise $ negate b} | interleaved]
| (a,mb) <- acctbals | (a,mb) <- acctbals
, let bs0 = amounts mb , let bs0 = amounts mb
-- mark the last balance in each commodity with the unpriced sum in that commodity (for a balance assertion) -- 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) , let bs2 = concat [reverse $ zip (reverse bs1) (Just commoditysum : repeat Nothing)
| bs1 <- groupBy ((==) `on` acommodity) bs0 | bs1 <- groupBy ((==) `on` acommodity) bs0
, let commoditysum = (sum bs1)] , let commoditysum = (sum bs1)]
, (b, mcommoditysum) <- bs2 , (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 -- print them
when close_ . T.putStr $ showTransaction closetxn when (close_ || assert_) . T.putStr $ showTransaction closetxn
when open_ . T.putStr $ showTransaction opentxn when (open_ || assign_) . T.putStr $ showTransaction opentxn

View File

@ -2,42 +2,58 @@
(equity) (equity)
Generate transactions which transfer account balances to and/or from A transaction-generating command which generates several kinds of "closing"
another account (typically equity). and/or "opening" transactions useful in certain situations.
This can be useful for migrating balances to a new journal file, It prints one or two transactions to stdout, but does not write them to the journal automatically.
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.
*(experimental)* *(experimental)*
_FLAGS _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 1. With `--close` (or no mode flag) it prints a "closing balances" transaction
that zeroes out ALE (asset, liability, equity) accounts by default that zeroes out all the ALE (asset, liability, equity) accounts, by default
(this requires [account types](hledger.md#account-types) to be inferred or declared); (this requires inferred or declared [account types](hledger.md#account-types));
or, the accounts matched by the provided ACCTQUERY arguments. 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 2. With `--open`, it prints an opposite "opening balances" transaction that
those balances from zero. This is similar to Ledger's equity command. "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. 3. With `--migrate`, it prints both the `--close` and the `--open` transactions.
This is the preferred way to migrate balances to a new file: This is a common way to migrate balances to a new file at year end;
run `hledger close --migrate`, run `hledger close --migrate` (or `hledger close --close` and `hleddger close --open`)
add the closing transaction at the end of the old file, and and add the closing transaction at the end of the old file,
add the opening transaction at the start of the new file. and the opening transaction at the start of the new file.
The matching closing/opening transactions cancel each other out, This allows you to use the new file by itself, or together with previous file(s),
preserving correct balances during multi-file reporting. 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`. RX (revenue and expense) balances to `equity:retained earnings`.
Businesses traditionally do this at the end of each accounting period; This is a traditional end-of-period bookkeeping operation also called "closing the books";
it is less necessary with computer-based accounting, but it could still be useful 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) satisfied. if you want to see the accounting equation (A=L+E) balanced.
In all modes, the defaults can be overridden: In all modes, the defaults can be overridden: