simonmichael / hledger

Robust, fast, intuitive plain text accounting tool with CLI, TUI and web interfaces.
https://hledger.org
GNU General Public License v3.0
2.93k stars 316 forks source link

failed balance assertion explanation could include a breakdown of subaccounts when ==* is used #1535

Open lestephane opened 3 years ago

lestephane commented 3 years ago

My understanding is that ==* is very useful to check that virtual / transfer accounts are all zero at the end of the month, or beginning of the year. I call those accounts zerosum accounts.

2021-02-01 Zerosum accounts start the month empty
    ; generated-transaction: ~ monthly from 2021-01-01 to 2022-01-01
    ...
    Transfers                                                     0 ==* 0 EUR
    Transfers                                                     0 ==* 0 USD
    Transfers                                                     0 ==* 0 GBP
    nothing

assertion details:
date:       2021-02-01
account:    Transfers (and subs)
commodity:  EUR
calculated: -22.30000000
asserted:   0
difference: 22.30000000

Then I need to figure out which of the sub-accounts contributed to the assertion failure .

Putting the breakdown of those non-zero subaccount balances would save time.

I started using ==* only this month, and realized how many of my transfers account were non-zero (a typical sign of bugs in my own hledger-flow import scripts). It even found some EUR in subaccounts that were actually named with USD at the end.

Workaround (Edit 2021-04-21)

To find out the culprit(s) (in this example)

hledger bal -I --end 2018-04-02 Transfers: cur:EUR

notice the date is the date from the assertion details + 1 day

(Edit 2021-04-22)

Use reg and slide the date forwards to discover whether the situation 'corrects' itself in time.

hledger reg -I --end 2018-04-15 Transfers: cur:EUR

If not, it's a bug (ie, the journal contains money in a transitory transfer account that is only put in, and never taken out, or vice versa). If it does correct itself, consider changing the date of that assertions to another day where there is less noise. Most of the time, the failure will not be a bug, but a transfer that was still ongoing at the time the balance_assertion is checked (left the source account, but did not arrive yet in the destination account).

simonmichael commented 3 years ago

Maybe you could draft some mockups/examples ?

lestephane commented 3 years ago

Having this:

$ hledger bal Transfers: -I --end 2018-04-02 --empty
                   0  Transfers:DKB:Personal:EUR
                   0  Transfers:Forex
          7,0400 EUR  Transfers:PayPal
                   0  Transfers:Relai
                   0  Transfers:TransferWise:Business-CY:EUR
                   0  Transfers:TransferWise:Business-CY:USD
                   0  Transfers:TransferWise:Personal:EUR
                   0  Transfers:TransferWise:Personal:USD
--------------------
          7,0400 EUR  

And an assertion failure failing like this

2018-04-01 Zerosum accounts start the month empty  ; applied-autoposting:recurring.checks.topups.2018.zerosum_monthly
    Transfers:Forex                                               0 ==* 0 EUR  ; check:zerosum
    Transfers:Forex                                               0 ==* 0 GBP  ; check:zerosum
    Transfers:Forex                                               0 ==* 0 USD  ; check:zerosum
    Transfers:Forex                                               0 ==* 0 XBT  ; check:zerosum
    Transfers:Relai                                               0 ==* 0 EUR  ; check:zerosum
    Transfers:Relai                                               0 ==* 0 XBT  ; check:zerosum
    Transfers:TransferWise:Personal:EUR                           0 ==* 0 EUR  ; check:zerosum
    Transfers:TransferWise:Personal:USD                           0 ==* 0 USD  ; check:zerosum
    Transfers:TransferWise:Business-CY:EUR                        0 ==* 0 USD  ; check:zerosum
    Transfers:TransferWise:Business-CY:USD                        0 ==* 0 USD  ; check:zerosum
    Transfers                                                     0 ==* 0 EUR  ; check:zerosum
    Transfers                                                     0 ==* 0 USD  ; check:zerosum
    Transfers                                                     0 ==* 0 GBP  ; check:zerosum
    Transfers                                                     0 ==* 0 XBT  ; check:zerosum
    Equity:Opening Balances                                       0

assertion details:
date:       2018-04-01
account:    Transfers (and subs)
commodity:  EUR
calculated: 7.04
asserted:   0
difference: -7.04

You could either indent each offending account below the account field in the assertion details


assertion details:
date:       2018-04-01
account:    Transfers (and subs)
  \-Transfers:PayPal  7,0400 EUR
  ...
commodity:  EUR
calculated: 7.04
asserted:   0
difference: -7.04

Or indent each offending account below the position containing the balance assertion

2018-04-01 Zerosum accounts start the month empty  ; applied-autoposting:recurring.checks.topups.2018.zerosum_monthly
    ...
    Transfers                                                     0 ==* 0 EUR  ; check:zerosum
      \- Transfers:PayPal  7,0400 EUR
lestephane commented 3 years ago

I'm not clear whether two subaccounts cancelling each other out, say,

Transfers:PayPal  7,0400 EUR
Transfers:CashApp  -7,0400 EUR

Would trigger the balance assertion at the level of the parent currently.

I think they each in isolation violate the parent assertion, and should therefore both be listed.

  | - Transfers:PayPal  7,0400 EUR 
  \ - Transfers:CashApp  -7,0400 EUR
simonmichael commented 3 years ago

To make this still more concrete, if you have time you could make an example or two, including the minimal journal and the desired output.

simonmichael commented 3 years ago

PS it does sound like a useful feature, if these thought experiments show no big problem.

simonmichael commented 3 years ago

If the two subaccounts cancel each other out, the parent account's assertion is satisfied, so it should pass (and does). If you want to ensure each subaccount is zero, you have to assert for each of them.

With subaccount-including assertions, it's possible to have more than one assertion failing at once. Generally hledger only reports a single error, the first one it hits. Reporting multiple failing subaccount assertions at once might be a bit inconsistent, though if it provides significant value in practice, it could be worth it.

Whether reporting one or many assertion errors, I think your idea of possibly showing them inline in the displayed journal entry is a good one.

lestephane commented 3 years ago

So it's going to be taking some time (again), because command line accounting for the month of march is still ongoing here. I'm 3 weeks late delivering my records to the accountant. I don't know how you manage this project and have time left to do billable work!

simonmichael commented 3 years ago

It has always been difficult and is getting more so...

lestephane commented 3 years ago

Mockups

"╚╝╬═╩╠╣╦╔╗║" from https://social.technet.microsoft.com/wiki/contents/articles/20557.visual-basic-how-to-draw-a-border-of-ascii-text-in-a-console-application.aspx

Scenario 1: one assertion per currency

(which is the only way hledger supports today)

Input

commodity 9.999,99 EUR
commodity 9,999.99 USD
commodity 9,999.99999999 XBT

2021-01-01 * "opening balances"
    Equity:Opening-Balances
    Transfers:Personal:Revolut:EUR             1,11 EUR
    Transfers:Business:Revolut:EUR            -2,22 EUR
    Transfers:Personal:WiseDotCom:USD         -3.33 USD
    Transfers:Business:WiseDotCom:USD          4.44 USD
    Transfers:Personal:Coinbase                0.00555555 XBT
    Transfers:Business:Coinbase               -0.00666666 XBT

2021-05-01 * "monthly assertions"
    Assertions
    Transfers                                  0 ==* 0 EUR
    Transfers                                  0 ==* 0 USD
    Transfers                                  0 ==* 0 XBT

Result

$ hledger bal  -f input-transactions.journal 
hledger-39623934c38e12007434aeb4bf6fc0a280b62889: balance assertion: "/home/lestephane/Vault/Finance/input-transactions.journal" (line 13, column 50)
transaction:

2021-05-01 * "monthly assertions"
    Assertions               0
    Transfers                0 ==* 0 EUR
      ╔═════════════════════════════════════════════════════╗
      ║Transfers:Personal:Revolut:EUR               1,11 EUR║
      ║Transfers:Business:Revolut:EUR              -2,22 EUR║
      ╠═════════════════════════════════════════════════════╣
      ╚Total                                       -1,11 EUR╝
    Transfers                0 ==* 0 USD
    Transfers                0 ==* 0 XBT

assertion details:
date:       2021-05-01
account:    Transfers (and subs)
commodity:  EUR
calculated: 1.11
asserted:   0
difference: -1.11

Scenario 2: one multi currency assertion

Input

commodity 9.999,99 EUR
commodity 9,999.99 USD
commodity 9,999.99999999 XBT

2021-01-01 * "opening balances"
    Equity:Opening-Balances
    Transfers:Personal:Revolut:EUR             1,11 EUR
    Transfers:Business:Revolut:EUR            -2,22 EUR
    Transfers:Personal:WiseDotCom:USD         -3.33 USD
    Transfers:Business:WiseDotCom:USD          4.44 USD
    Transfers:Personal:Coinbase                0.00555555 XBT
    Transfers:Business:Coinbase               -0.00666666 XBT

2021-05-01 * "monthly assertions"
    Assertions
    Transfers                                  0 ==* 0 EUR,USD,XBT

Result

$ hledger bal  -f input-transactions.journal 
hledger-39623934c38e12007434aeb4bf6fc0a280b62889: balance assertion: "/home/lestephane/Vault/Finance/input-transactions.journal" (line 13, column 50)
transaction:

2021-05-01 * "monthly assertions"
    Assertions               0
    Transfers                0 ==* 0 EUR
      ╔═════════════════════════════════════════════════════╗
      ║Transfers:Personal:Revolut:EUR               1,11 EUR║
      ║Transfers:Business:Revolut:EUR              -2,22 EUR║
      ╠═════════════════════════════════════════════════════╣
      ╚Total                                       -1,11 EUR╝
    Transfers                0 ==* 0 USD
      ╔═════════════════════════════════════════════════════╗
      ║Transfers:Personal:WiseDotCom:USD           -3.33 USD║
      ║Transfers:Business:WiseDotCom:USD            4.44 USD║
      ╠═════════════════════════════════════════════════════╣
      ╚Total                                        1.11 USD╝
    Transfers                0 ==* 0 XBT
      ╔═════════════════════════════════════════════════════╗
      ║Transfers:Personal:Coinbase            0.00555555 XBT║
      ║Transfers:Business:Coinbase           -0.00666666 XBT║
      ╠═════════════════════════════════════════════════════╣
      ╚Total                                 -0.00111111 XBT╝

assertion details:
date:       2021-05-01
account:    Transfers (and subs)
commodity:  (multiple)
calculated: (see breakdown)
asserted:   0
difference: (see breakdown)