GnuCash
Contact   Instructions
Bug 797063 - gncEntryGetDocValue is modifying the invoice or entry
Summary: gncEntryGetDocValue is modifying the invoice or entry
Status: NEW
Alias: None
Product: GnuCash
Classification: Unclassified
Component: Business (show other bugs)
Version: git-maint
Hardware: PC Linux
: Normal normal
Target Milestone: ---
Assignee: core
QA Contact: core
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-01-24 09:53 EST by Christopher Lam
Modified: 2019-01-26 01:25 EST (History)
4 users (show)

See Also:


Attachments
inv-8 combinatorics output (7.95 KB, text/html)
2019-01-25 10:52 EST, Christopher Lam
no flags Details

Description Christopher Lam 2019-01-24 09:53:33 EST
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
Comment 1 John Ralls 2019-01-24 10:18:37 EST
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.
Comment 2 Christopher Lam 2019-01-24 16:10:27 EST
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?
Comment 3 John Ralls 2019-01-24 17:23:12 EST
That would be hard since you pay the invoice after calling gncEntryGetDocValue.

Is the sum of the new values actually 1747918/100?
Comment 4 Christopher Lam 2019-01-24 20:52:18 EST
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...
Comment 5 John Ralls 2019-01-24 23:12:43 EST
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.
Comment 6 Christopher Lam 2019-01-25 06:01:15 EST
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.
Comment 7 Christopher Lam 2019-01-25 06:33:58 EST
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!
Comment 8 John Ralls 2019-01-25 10:06:34 EST
Did you flip without/after there? If not, I'm confused because it would seem that the "without calling" case is the unbalanced one.
Comment 9 Christopher Lam 2019-01-25 10:20:38 EST
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.
Comment 10 John Ralls 2019-01-25 10:44:33 EST
What's in the second (lot-balance) column?
Comment 11 Christopher Lam 2019-01-25 10:52:35 EST
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?
Comment 12 John Ralls 2019-01-25 12:27:52 EST
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.
Comment 13 Christopher Lam 2019-01-25 20:48:00 EST
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.
Comment 14 John Ralls 2019-01-25 23:08:54 EST
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.
Comment 15 Christopher Lam 2019-01-26 01:03:28 EST
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
Comment 16 Christopher Lam 2019-01-26 01:25:29 EST
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.

Note You need to log in before you can comment on or make changes to this bug.