Wish to document this subtle bug found while creating test-invoice.scm before I forget. While creating test-invoice.scm various combinations of entry properties discount/discount-type/taxable/tax-included, I *dumped* the entry properties and the entry *value* obtained via gncEntryGetDocValue. However the act of *calling* this 'read-only' procedure caused a later test to fail. This bug can be reproduced in test-invoice.scm by uncommenting the following lines 500-502 in test-invoice.scm ;; (format #t "inv-8: adding ~a to invoice, entry amount is ~a\n" ;; desc ;; (exact->inexact (gncEntryGetDocValue each-entry #f #t #f))) and running test-invoice.scm -- note this snippet does *not* aim to modify the invoice entry, yet, the subsequent (test-assert "inv-8 is fully paid up!" (gncInvoiceIsPaid inv-8)) inexplicably fails. This means gncEntryGetDocValue is modifying the invoice properties slightly so that the invoice is no longer considered paid. TO REPRODUCE: uncomment lines above EXPECTED: test-invoice becomes more verbose, and test still passes ACTUAL: test-invoice becomes more verbose, but test fails
Yup, it calls gncEntryRecomputeValues via gncEntryGetIntValue. Why do you assume gncEntryGetDocValue a read-only function? ISTM gncEntryRecomputeValues is what actually applies the tax table to the entry, so the values aren't actually valid until it has been called at least once.
Ok that's fine it's not a read-only function. (intuition would suggest so though). But why would (gncInvoiceIsPaid inv-8) suddenly fail if (gncEntryGetDocValue entry ...) is called? Is the paid flag being reset for no reason?
That would be hard since you pay the invoice after calling gncEntryGetDocValue. Is the sum of the new values actually 1747918/100?
I can review the invoice details tonight at home; from memory the invoice total was visually $0.00 after paying 1747918/100 which I felt was caused by some rounding error while calculating the invoice entries subtotals. I had designed this last test-invoice using the awkward numbers as follows specifically to shake out any rounding errors in the entry "amount-due" calculators. taxrate 109/10 discount 7/2 unit price 777/4 quantity 11 and I used 2-tuple combinatorics for discount/discount-type/taxable/tax-included? So I'm afraid there's likely a rounding error somewhere internal...
Heh, if you want awkward rounding use primes for both numerators and denominators, like 79/7, 59/17, and 2521/13. ;-) Don't get too carried away, though, or you'll overflow. Regardless, you're looking for a consistent result, so run the calculation in GnuCash to find out what the rounded value is and use that as the compare-to constant. It's really more a test of either guile's rationals or GnuCash's and so it probably doesn't belong in a report test, but that's a separate issue.
Ok weirdness. There's no issue with rounding. The subtotals/taxes/etc are all 2 decimal places no matter what. It's the lot that's being unclosed when (gncEntryGetDocValue) is run. Note the order of operations in test-invoice is: 1. create invoice 2. set owner/currency/terms 3. add 8 entries for 2-tuple binary combinations 3a. optionally run (gncEntryGetDocValue) for the entry 4. set invoice notes 5. post invoice to A/R account 6. apply payment of 1747918/100 7. test invoice getters In step 3a. above seems to effect the gnc_invoice_is_closed(invoice->posted_lot) status. Proof: modify test-invoice as follows Unstaged changes (1) modified gnucash/report/business-reports/test/test-invoice.scm @@ -543,6 +543,9 @@ "$1,851.95" "$1,859.30" "$16,368.17" "$1,111.01" "$17,479.18" "-$17,479.18" "$0.00") (sxml-get-row-col "entries-table" sxml #f -1)) + (format #t "posted=~a. " + (gnc-lot-is-closed + (gncInvoiceGetPostedLot inv-8))) (test-assert "inv-8 is fully paid up!" (gncInvoiceIsPaid inv-8)))) (test-end "combinations of gncEntry options"))) and optionally uncomment the block above. EXPECTED: posted=#t irrespective of gncEntryGetDocValue OBTAINED: posted=#f when gncEntryGetDocValue is run in step 3a above. posted=#t when gncEntryGetDocValue is skipped in step 3a above.
conclusion: the invoice is considered unpaid because there's a 1/100 amount remaining. without calling lot_balance for posted_lot is 17479.19. invoice_total is 17479.18 after calling lot_balance for posted_lot is 17479.18. invoice_total is 17479.18 PROOF: modified gnucash/report/business-reports/test/test-invoice.scm @@ -520,6 +520,10 @@ (current-time) "trans-posting-memo" #t #f) + (format #t "before payment: lot=~a, balance=~a. \n" + (gncInvoiceGetTotal inv-8) + (gnc-lot-get-balance + (gncInvoiceGetPostedLot inv-8))) (gncInvoiceApplyPayment inv-8 '() bank 1747918/100 1 (current-time) "trans-payment-memo-1" "trans-payment-num-1") (let* ((options (default-testing-options inv-8)) This is where my limit stands!
Did you flip without/after there? If not, I'm confused because it would seem that the "without calling" case is the unbalanced one.
You're right. I even flipped the format string label. CORRECT: (format #t "before payment: invoice-balance=~a, lot-balance=~a.\n" (gncInvoiceGetTotal inv-8) (gnc-lot-get-balance (gncInvoiceGetPostedLot inv-8))) The above logger will output the various entry post-discount post-tax amounts as follows obtained via the gncEntryGetDocValue call as: 2133.25 2061.96375 1851.949548016231 2133.25 2133.25 2061.96375 2133.25 1859.3000450856628 output without calling gncEntryGetDocValue: before payment: invoice-balance=873959/50, lot-balance=873959/50. output with calling gncEntryGetDocValue: before payment: invoice-balance=873959/50, lot-balance=1747919/100.
What's in the second (lot-balance) column?
Created attachment 373141 [details] inv-8 combinatorics output huh. these numbers (2133.25, 2061.96375, etc) are the entries' amounts, all added together to produce the total 16368.1770931019, which is then added to the tax amount 1111.01 to obtain the invoice total 17479.18. note the raw numbers total is 16368.17709... whereas the invoice total is 16368.17 -- maybe this is the source of error?
Maybe. What are both columns of the output of (format #t "before payment: invoice-balance=~a, lot-balance=~a.\n" (gncInvoiceGetTotal inv-8) (gnc-lot-get-balance (gncInvoiceGetPostedLot inv-8))) ? You only gave one column in comment 9. Since it's "format #t" I don't think it would go to the HTML invoice.
correct (format #t "var is ~a" var) is how I usually dump 'var' to console. not to HTML invoice. the output *without* calling 88: before payment: invoice-balance=873959/50, lot-balance=873959/50. the output *with* calling 88: before payment: invoice-balance=873959/50, lot-balance=1747919/100.
Ah, sorry, I thought it was in a loop that spit out each line from comment 9. What I'm looking for is the numbers that sum up to the lot balance.
It's the C function gncInvoicePostToAccount that's showing discrepancy. I'm not sure which part is differing. Here's a tracer patch for gncInvoice.c modified libgnucash/engine/gncInvoice.c @@ -1485,6 +1485,11 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, gncInvoiceAttachToLot (invoice, lot); gnc_lot_begin_edit (lot); + PWARN ("after attaching invoice (lot=%p) %s bal=%s", + lot, + gnc_lot_get_title (lot), + gnc_num_dbg_to_string (gnc_lot_get_balance(lot))); + type = gncInvoiceGetTypeString (invoice); /* Set the lot title */ @@ -1565,6 +1570,11 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, value = gncEntryGetBalValue (entry, TRUE, is_cust_doc); tax = gncEntryGetBalTaxValue (entry, TRUE, is_cust_doc); + PWARN ("entry = %p, value = %s, tax = %s\n", + entry, + gnc_num_dbg_to_string (value), + gnc_num_dbg_to_string (tax)); + /* add the value for the account split */ this_acc = (is_cust_doc ? gncEntryGetInvAccount (entry) : gncEntryGetBillAccount (entry)); @@ -1695,6 +1705,11 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, /* add this split to the lot */ gnc_lot_add_split (lot, split); + PWARN ("after adding split (lot=%p, split=%p) %s bal=%s, split-amt=%s", + lot, split, + gnc_lot_get_title (lot), + gnc_num_dbg_to_string (gnc_lot_get_balance(lot)), + gnc_num_dbg_to_string (xaccSplitGetAmount(split))); } /* Now attach this invoice to the txn and account */ ... and the resulting discrepancy is as follows: 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e4370, value = -213325/100, tax = 0/1 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125d9c50, value = -206196/100, tax = 0/1 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e4590, value = -213325/100, tax = 0/1 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e47b0, value = -206196/100, tax = -23291/100 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125d9a30, value = -213325/100, tax = -23252/100 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e49d0, value = -213325/100, tax = -23291/100 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e4150, value = -185195/100, tax = -21001/100 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55dd125e4bf0, value = -185930/100, tax = -20266/100 88: * 13:56:57 WARN <gnc.business> [gncInvoicePostToAccount()] after adding split (lot=0x55dd1224a760, split=0x55dd125dd050) Invoice bal=1747919/100, split-amt=1747919/100 ... 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4a370, value = -213325/100, tax = 0/1 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fad1c50, value = -206196/100, tax = 0/1 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4a590, value = -213325/100, tax = 0/1 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4a7b0, value = -206196/100, tax = -23291/100 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fad1a30, value = -213325/100, tax = -23252/100 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4a9d0, value = -213325/100, tax = -23291/100 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4a150, value = -185195/100, tax = -21001/100 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] entry = 0x55cd2fa4abf0, value = -185930/100, tax = -20266/100 88: * 13:57:34 WARN <gnc.business> [gncInvoicePostToAccount()] after adding split (lot=0x55cd2fa2ff60, split=0x55cd2fae0dd0) Invoice bal=1747918/100, split-amt=1747918/100
Found it, somewhere within the gncInvoicePostToAccount definition the invoice total is 1747918/100 but the split-amount is 1747919/100, leading to an Imbalance-USD amount of 1/100. Discrepancy between line 1565 and 1695 tracers above.