diff --git a/gnucash/register/ledger-core/gncEntryLedger.c b/gnucash/register/ledger-core/gncEntryLedger.c index 645e8091e0..796667dd90 100644 --- a/gnucash/register/ledger-core/gncEntryLedger.c +++ b/gnucash/register/ledger-core/gncEntryLedger.c @@ -727,6 +727,7 @@ gnc_entry_ledger_compute_value (GncEntryLedger *ledger, GncTaxTable *table; GList *taxes = NULL; int denom = 100; + gnc_numeric value_unrounded, taxes_unrounded; gnc_entry_ledger_get_numeric (ledger, ENTRY_QTY_CELL, &qty); gnc_entry_ledger_get_numeric (ledger, ENTRY_PRIC_CELL, &price); @@ -778,12 +779,18 @@ gnc_entry_ledger_compute_value (GncEntryLedger *ledger, } gncEntryComputeValue (qty, price, (taxable ? table : NULL), taxincluded, - discount, disc_type, disc_how, denom, - value, NULL, &taxes); + discount, disc_type, disc_how, 0, + &value_unrounded, NULL, &taxes); + + if (value) + *value = gnc_numeric_convert (value_unrounded, denom, + GNC_HOW_RND_ROUND_HALF_UP); /* return the tax value */ + taxes_unrounded = gncAccountValueTotal (taxes); if (tax_value) - *tax_value = gncAccountValueTotal (taxes); + *tax_value = gnc_numeric_convert (taxes_unrounded, denom, + GNC_HOW_RND_ROUND_HALF_UP); } gboolean diff --git a/libgnucash/engine/gncEntry.c b/libgnucash/engine/gncEntry.c index f850b2ace2..a47fd3b1fb 100644 --- a/libgnucash/engine/gncEntry.c +++ b/libgnucash/engine/gncEntry.c @@ -1084,15 +1084,17 @@ GncOrder * gncEntryGetOrder (const GncEntry *entry) * the amount the merchant gets; the taxes are the amount the gov't * gets, and the customer pays the sum or value + taxes. * - * The SCU is the denominator to convert the value. - * * The discount return value is just for entertainment -- you may want * to let a consumer know how much they saved. + * + * Note this function will not do any rounding unless forced to prevent overflow. + * It's the caller's responsability to round to the proper commodity + * denominator if needed. */ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, const GncTaxTable *tax_table, gboolean tax_included, gnc_numeric discount, GncAmountType discount_type, - GncDiscountHow discount_how, int SCU, + GncDiscountHow discount_how, gnc_numeric *value, gnc_numeric *discount_value, GList **tax_value, gnc_numeric *net_price) { @@ -1110,7 +1112,7 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, /* Step 1: compute the aggregate price */ - aggregate = gnc_numeric_mul (qty, price, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD | GNC_HOW_RND_ROUND); + aggregate = gnc_numeric_mul (qty, price, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); /* Step 2: compute the pre-tax aggregate */ @@ -1154,10 +1156,10 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, gnc_numeric_add (tpercent, gnc_numeric_create (1, 1), GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD), - GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); if (!gnc_numeric_zero_p(qty)) { - i_net_price = gnc_numeric_div (pretax, qty, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + i_net_price = gnc_numeric_div (pretax, qty, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); } } else @@ -1195,7 +1197,7 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, discount = gnc_numeric_div (discount, percent, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER); discount = gnc_numeric_mul (pretax, discount, GNC_DENOM_AUTO, - GNC_HOW_DENOM_LCD); + GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); } result = gnc_numeric_sub (pretax, discount, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); @@ -1212,14 +1214,14 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, { gnc_numeric after_tax; - tax = gnc_numeric_mul (pretax, tpercent, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + tax = gnc_numeric_mul (pretax, tpercent, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); after_tax = gnc_numeric_add (pretax, tax, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); after_tax = gnc_numeric_add (after_tax, tvalue, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); discount = gnc_numeric_div (discount, percent, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER); discount = gnc_numeric_mul (after_tax, discount, GNC_DENOM_AUTO, - GNC_HOW_DENOM_LCD); + GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); } result = gnc_numeric_sub (pretax, discount, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); @@ -1238,16 +1240,10 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, */ if (discount_value != NULL) - { - if (SCU) discount = gnc_numeric_convert(discount, SCU, GNC_HOW_RND_ROUND_HALF_UP); *discount_value = discount; - } if (value != NULL) - { - if (SCU) result = gnc_numeric_convert(result, SCU, GNC_HOW_RND_ROUND_HALF_UP); *value = result; - } /* Now... Compute the list of tax values (if the caller wants it) */ @@ -1266,14 +1262,12 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, switch (gncTaxTableEntryGetType (entry)) { case GNC_AMT_TYPE_VALUE: - if (SCU) amount = gnc_numeric_convert(amount, SCU, GNC_HOW_RND_ROUND_HALF_UP); taxes = gncAccountValueAdd (taxes, acc, amount); break; case GNC_AMT_TYPE_PERCENT: amount = gnc_numeric_div (amount, percent, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER); - tax = gnc_numeric_mul (pretax, amount, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - if (SCU) tax = gnc_numeric_convert(tax, SCU, GNC_HOW_RND_ROUND_HALF_UP); + tax = gnc_numeric_mul (pretax, amount, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND); taxes = gncAccountValueAdd (taxes, acc, tax); break; default: @@ -1284,10 +1278,7 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, } if (net_price != NULL) - { - if (SCU) i_net_price = gnc_numeric_convert(i_net_price, SCU, GNC_HOW_RND_ROUND_HALF_UP); *net_price = i_net_price; - } return; } @@ -1295,12 +1286,12 @@ static void gncEntryComputeValueInt (gnc_numeric qty, gnc_numeric price, void gncEntryComputeValue (gnc_numeric qty, gnc_numeric price, const GncTaxTable *tax_table, gboolean tax_included, gnc_numeric discount, GncAmountType discount_type, - GncDiscountHow discount_how, int SCU, + GncDiscountHow discount_how, G_GNUC_UNUSED int SCU, gnc_numeric *value, gnc_numeric *discount_value, GList **tax_value) { gncEntryComputeValueInt (qty, price, tax_table, tax_included, discount, discount_type, - discount_how, SCU, value, discount_value, tax_value, NULL); + discount_how, value, discount_value, tax_value, NULL); } static int @@ -1328,6 +1319,7 @@ static void gncEntryRecomputeValues (GncEntry *entry) { int denom; + GList *tv_iter; /* See if either tax table changed since we last computed values */ if (entry->i_tax_table) @@ -1386,18 +1378,28 @@ gncEntryRecomputeValues (GncEntry *entry) &(entry->b_value), NULL, &(entry->b_tax_values)); entry->i_value_rounded = gnc_numeric_convert (entry->i_value, denom, - GNC_HOW_RND_ROUND_HALF_UP); + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); entry->i_disc_value_rounded = gnc_numeric_convert (entry->i_disc_value, denom, - GNC_HOW_RND_ROUND_HALF_UP); + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); entry->i_tax_value = gncAccountValueTotal (entry->i_tax_values); - entry->i_tax_value_rounded = gnc_numeric_convert (entry->i_tax_value, denom, - GNC_HOW_RND_ROUND_HALF_UP); + entry->i_tax_value_rounded = gnc_numeric_zero(); + for (tv_iter = entry->i_tax_values; tv_iter; tv_iter=tv_iter->next) + { + GncAccountValue *acc_val = tv_iter->data; + entry->i_tax_value_rounded = gnc_numeric_add (entry->i_tax_value_rounded, acc_val->value, + denom, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + } entry->b_value_rounded = gnc_numeric_convert (entry->b_value, denom, - GNC_HOW_RND_ROUND_HALF_UP); + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); entry->b_tax_value = gncAccountValueTotal (entry->b_tax_values); - entry->b_tax_value_rounded = gnc_numeric_convert (entry->b_tax_value, denom, - GNC_HOW_RND_ROUND_HALF_UP); + entry->b_tax_value_rounded = gnc_numeric_zero(); + for (tv_iter = entry->b_tax_values; tv_iter; tv_iter=tv_iter->next) + { + GncAccountValue *acc_val = tv_iter->data; + entry->b_tax_value_rounded = gnc_numeric_add (entry->b_tax_value_rounded, acc_val->value, + denom, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + } entry->values_dirty = FALSE; } @@ -1441,6 +1443,10 @@ static gnc_numeric gncEntryGetIntDiscountValue (GncEntry *entry, gboolean round, return (is_cust_doc ? entry->i_disc_value : gnc_numeric_zero()); } +/* Note contrary to the GetDoc*Value and GetBal*Value functions below + * this function will always round the net price to the entry's + * currency denominator (being the invoice/bill denom or 100000 if not + * included in a bill or invoice) */ gnc_numeric gncEntryGetPrice (const GncEntry *entry, gboolean cust_doc, gboolean net) { gnc_numeric result; @@ -1448,10 +1454,8 @@ gnc_numeric gncEntryGetPrice (const GncEntry *entry, gboolean cust_doc, gboolean if (!entry) return gnc_numeric_zero(); if (!net) return (cust_doc ? entry->i_price : entry->b_price); - - /* Determine the commodity denominator */ - denom = get_entry_commodity_denom (entry); - + + /* Compute the net price */ if (cust_doc) gncEntryComputeValueInt (entry->quantity, entry->i_price, @@ -1459,16 +1463,20 @@ gnc_numeric gncEntryGetPrice (const GncEntry *entry, gboolean cust_doc, gboolean entry->i_taxincluded, entry->i_discount, entry->i_disc_type, entry->i_disc_how, - denom, NULL, NULL, NULL, &result); else gncEntryComputeValueInt (entry->quantity, entry->b_price, (entry->b_taxable ? entry->b_tax_table : NULL), entry->b_taxincluded, gnc_numeric_zero(), GNC_AMT_TYPE_VALUE, GNC_DISC_PRETAX, - denom, NULL, NULL, NULL, &result); + /* Determine the commodity denominator */ + denom = get_entry_commodity_denom (entry); + + result = gnc_numeric_convert (result, denom, + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + return result; } diff --git a/libgnucash/engine/gncEntry.h b/libgnucash/engine/gncEntry.h index f555d20269..a45d83b27a 100644 --- a/libgnucash/engine/gncEntry.h +++ b/libgnucash/engine/gncEntry.h @@ -47,6 +47,8 @@ typedef enum GNC_DISC_POSTTAX } GncDiscountHow; +typedef GList AccountValueList; + #ifdef GNUCASH_MAJOR_VERSION #include "gncBusiness.h" #endif @@ -243,7 +245,6 @@ void gncEntryCopy (const GncEntry *src, GncEntry *dest, gboolean add_entry); * these functions. @{ */ -typedef GList AccountValueList; gnc_numeric gncEntryGetDocValue (GncEntry *entry, gboolean round, gboolean is_cust_doc, gboolean is_cn); gnc_numeric gncEntryGetDocTaxValue (GncEntry *entry, gboolean round, gboolean is_cust_doc, gboolean is_cn); /** Careful: the returned list is NOT owned by the entry and should be freed by the caller */ diff --git a/libgnucash/engine/gncInvoice.c b/libgnucash/engine/gncInvoice.c index 298a6684f6..4950204958 100644 --- a/libgnucash/engine/gncInvoice.c +++ b/libgnucash/engine/gncInvoice.c @@ -857,15 +857,39 @@ GncOwnerType gncInvoiceGetOwnerType (const GncInvoice *invoice) } static gnc_numeric -gncInvoiceGetTotalInternal (GncInvoice *invoice, gboolean use_value, - gboolean use_tax, - gboolean use_payment_type, GncEntryPaymentType type) +gncInvoiceSumTaxesInternal (AccountValueList *taxes) +{ + gnc_numeric tt = gnc_numeric_zero(); + + if (taxes) + { + GList *node; + // Note we can use GNC_DENOM_AUTO below for rounding because + // the values passed to this function should already have been rounded + // to the desired denom and addition will just preserve it in that case. + for (node = taxes; node; node=node->next) + { + GncAccountValue *acc_val = node->data; + tt = gnc_numeric_add (tt, acc_val->value, GNC_DENOM_AUTO, + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + } + } + return tt; +} + +static gnc_numeric +gncInvoiceGetNetAndTaxesInternal (GncInvoice *invoice, gboolean use_value, + AccountValueList **taxes, + gboolean use_payment_type, GncEntryPaymentType type + ) { GList *node; - gnc_numeric total = gnc_numeric_zero(); + gnc_numeric net_total = gnc_numeric_zero(); gboolean is_cust_doc, is_cn; + AccountValueList *tv_list = NULL; + int denom = gnc_commodity_get_fraction(gncInvoiceGetCurrency(invoice)); - g_return_val_if_fail (invoice, total); + g_return_val_if_fail (invoice, net_total); /* Is the current document an invoice/credit note related to a customer or a vendor/employee ? * The GncEntry code needs to know to return the proper entry amounts @@ -873,6 +897,7 @@ gncInvoiceGetTotalInternal (GncInvoice *invoice, gboolean use_value, is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER); is_cn = gncInvoiceGetIsCreditNote (invoice); + for (node = gncInvoiceGetEntries(invoice); node; node = node->next) { GncEntry *entry = node->data; @@ -881,23 +906,64 @@ gncInvoiceGetTotalInternal (GncInvoice *invoice, gboolean use_value, if (use_payment_type && gncEntryGetBillPayment (entry) != type) continue; - value = gncEntryGetDocValue (entry, FALSE, is_cust_doc, is_cn); - if (gnc_numeric_check (value) == GNC_ERROR_OK) + if (use_value) + { + // Always use rounded net values to prevent creating imbalanced transactions on posting + // https://bugzilla.gnome.org/show_bug.cgi?id=628903 + value = gncEntryGetDocValue (entry, TRUE, is_cust_doc, is_cn); + if (gnc_numeric_check (value) == GNC_ERROR_OK) + net_total = gnc_numeric_add (net_total, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + else + g_warning ("bad value in our entry"); + } + + if (taxes) { - if (use_value) - total = gnc_numeric_add (total, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + AccountValueList *entrytaxes = gncEntryGetDocTaxValues (entry, is_cust_doc, is_cn); + tv_list = gncAccountValueAddList (tv_list, entrytaxes); + gncAccountValueDestroy (entrytaxes); } - else - g_warning ("bad value in our entry"); + } - if (use_tax) + if (taxes) + { + GList *node; + // Round tax totals (accumulated per tax account) to prevent creating imbalanced transactions on posting + // which could otherwise happen when using a tax table with multiple tax rates + for (node = tv_list; node; node=node->next) { - tax = gncEntryGetDocTaxValue (entry, FALSE, is_cust_doc, is_cn); - if (gnc_numeric_check (tax) == GNC_ERROR_OK) - total = gnc_numeric_add (total, tax, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - else - g_warning ("bad tax-value in our entry"); + GncAccountValue *acc_val = node->data; + acc_val->value = gnc_numeric_convert (acc_val->value, + denom, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); } + *taxes = tv_list; + } + + return net_total; +} + +static gnc_numeric +gncInvoiceGetTotalInternal(GncInvoice *invoice, gboolean use_value, + gboolean use_tax, + gboolean use_payment_type, GncEntryPaymentType type) +{ + AccountValueList *taxes; + gnc_numeric total; + int denom; + + if (!invoice) return gnc_numeric_zero(); + + denom = gnc_commodity_get_fraction(gncInvoiceGetCurrency(invoice)); + total = gncInvoiceGetNetAndTaxesInternal(invoice, use_value, use_tax? &taxes : NULL, use_payment_type, type); + + if (use_tax) + { + // Note we can use GNC_DENOM_AUTO below for rounding because + // the values passed to this function should already have been rounded + // to the desired denom and addition will just preserve it in that case. + total = gnc_numeric_add (total, gncInvoiceSumTaxesInternal (taxes), + GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + gncAccountValueDestroy (taxes); } return total; } @@ -926,6 +992,16 @@ gnc_numeric gncInvoiceGetTotalOf (GncInvoice *invoice, GncEntryPaymentType type) return gncInvoiceGetTotalInternal(invoice, TRUE, TRUE, TRUE, type); } +AccountValueList *gncInvoiceGetTotalTaxList (GncInvoice *invoice) +{ + gnc_numeric unused; + AccountValueList *taxes; + if (!invoice) return NULL; + + unused = gncInvoiceGetNetAndTaxesInternal(invoice, FALSE, &taxes, FALSE, 0); + return taxes; +} + GList * gncInvoiceGetTypeListForOwnerType (GncOwnerType type) { GList *type_list = NULL; @@ -1376,6 +1452,8 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, char *lot_title; Account *ccard_acct = NULL; const GncOwner *owner; + int denom = xaccAccountGetCommoditySCU(acc); + AccountValueList *taxes; if (!invoice || !acc) return NULL; if (gncInvoiceIsPosted (invoice)) return NULL; @@ -1427,14 +1505,29 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, xaccTransSetDateDue (txn, due_date); + /* Get invoice total and taxes. */ + total = gncInvoiceGetTotal (invoice); + taxes = gncInvoiceGetTotalTaxList (invoice); + /* The two functions above return signs relative to the document + * We need to convert them to balance values before we can use them here */ + if (is_cust_doc) + { + GList *node; + total = gnc_numeric_neg (total); + for (node = taxes; node; node = node->next) + { + GncAccountValue *acc_val = node->data; + acc_val->value = gnc_numeric_neg (acc_val->value); + } + } + /* Iterate through the entries; sum up everything for each account. * then create the appropriate splits in this txn. */ - total = gnc_numeric_zero(); + for (iter = gncInvoiceGetEntries(invoice); iter; iter = iter->next) { gnc_numeric value, tax; - GList *taxes; GncEntry * entry = iter->data; Account *this_acc; @@ -1458,10 +1551,10 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, } gncEntryCommitEdit (entry); - /* Obtain the Entry's Value and TaxValues */ - value = gncEntryGetBalValue (entry, FALSE, is_cust_doc); - tax = gncEntryGetBalTaxValue (entry, FALSE, is_cust_doc); - taxes = gncEntryGetBalTaxValues (entry, is_cust_doc); + /* Obtain the Entry's Value and TaxValues + Note we use rounded values here and below to prevent creating an imbalanced transaction */ + value = gncEntryGetBalValue (entry, TRUE, is_cust_doc); + tax = gncEntryGetBalTaxValue (entry, TRUE, is_cust_doc); /* add the value for the account split */ this_acc = (is_cust_doc ? gncEntryGetInvAccount (entry) : @@ -1472,6 +1565,7 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, { if (accumulatesplits) splitinfo = gncAccountValueAdd (splitinfo, this_acc, value); + /* Adding to total in case of accumulatesplits will be deferred to later when each split is effectively added */ else if (!gncInvoicePostAddSplit (book, this_acc, txn, value, gncEntryGetDescription (entry), type, invoice)) @@ -1484,7 +1578,7 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, } /* If there is a credit-card account, and this is a CCard - * payment type, the don't add it to the total, and instead + * payment type, subtract it from the total, and instead * create a split to the CC Acct with a memo of the entry * description instead of the provided memo. Note that the * value reversal is the same as the post account. @@ -1496,6 +1590,9 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, { Split *split; + total = gnc_numeric_sub (total, value, denom, + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); + split = xaccMallocSplit (book); xaccSplitSetMemo (split, gncEntryGetDescription (entry)); /* set action based on book option */ @@ -1508,30 +1605,30 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, invoice->currency); } - else - total = gnc_numeric_add (total, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); } else g_warning ("bad value in our entry"); } - /* now merge in the TaxValues */ - splitinfo = gncAccountValueAddList (splitinfo, taxes); - - /* ... and add the tax total */ - if (gnc_numeric_check (tax) == GNC_ERROR_OK) - total = gnc_numeric_add (total, tax, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - else + /* check the taxes */ + if (gnc_numeric_check (tax) != GNC_ERROR_OK) g_warning ("bad tax in our entry"); - gncAccountValueDestroy (taxes); } /* for */ + + /* now merge in the TaxValues */ + splitinfo = gncAccountValueAddList (splitinfo, taxes); + gncAccountValueDestroy (taxes); + /* Iterate through the splitinfo list and generate the splits */ for (iter = splitinfo; iter; iter = iter->next) { GncAccountValue *acc_val = iter->data; + + //gnc_numeric amt_rounded = gnc_numeric_convert(acc_val->value, + // denom, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); if (!gncInvoicePostAddSplit (book, acc_val->account, txn, acc_val->value, memo, type, invoice)) { @@ -1567,8 +1664,8 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc, xaccSplitSetBaseValue (split, gnc_numeric_neg (to_charge_bal_amount), invoice->currency); - total = gnc_numeric_sub (total, to_charge_bal_amount, - GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + total = gnc_numeric_sub (total, to_charge_bal_amount, denom, + GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); } /* Now create the Posted split (which is the opposite sign of the above splits) */ diff --git a/libgnucash/engine/gncInvoice.h b/libgnucash/engine/gncInvoice.h index 1b5b65a736..fa9eedc2a1 100644 --- a/libgnucash/engine/gncInvoice.h +++ b/libgnucash/engine/gncInvoice.h @@ -164,6 +164,9 @@ gnc_numeric gncInvoiceGetTotal (GncInvoice *invoice); gnc_numeric gncInvoiceGetTotalOf (GncInvoice *invoice, GncEntryPaymentType type); gnc_numeric gncInvoiceGetTotalSubtotal (GncInvoice *invoice); gnc_numeric gncInvoiceGetTotalTax (GncInvoice *invoice); +/** Return a list of tax totals accumulated per tax account. + */ +AccountValueList *gncInvoiceGetTotalTaxList (GncInvoice *invoice); typedef GList EntryList; EntryList * gncInvoiceGetEntries (GncInvoice *invoice); diff --git a/libgnucash/engine/gncTaxTable.c b/libgnucash/engine/gncTaxTable.c index 148a4c03a4..f691f70b68 100644 --- a/libgnucash/engine/gncTaxTable.c +++ b/libgnucash/engine/gncTaxTable.c @@ -936,7 +936,7 @@ GList *gncAccountValueAdd (GList *list, Account *acc, gnc_numeric value) if (res->account == acc) { res->value = gnc_numeric_add (res->value, value, GNC_DENOM_AUTO, - GNC_HOW_DENOM_LCD); + GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND_HALF_UP); return list; } } @@ -970,7 +970,7 @@ gnc_numeric gncAccountValueTotal (GList *list) for ( ; list ; list = list->next) { GncAccountValue *val = list->data; - total = gnc_numeric_add (total, val->value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + total = gnc_numeric_add (total, val->value, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND_HALF_UP); } return total; } diff --git a/libgnucash/engine/test/utest-Entry.c b/libgnucash/engine/test/utest-Entry.c index 102b26936e..b8c536165e 100644 --- a/libgnucash/engine/test/utest-Entry.c +++ b/libgnucash/engine/test/utest-Entry.c @@ -25,6 +25,7 @@ #include #include #include "../gncEntry.h" +#include "../gncTaxTableP.h" static const gchar *suitename = "/engine/gncEntry"; void test_suite_gncEntry ( void ); @@ -33,9 +34,11 @@ typedef struct { QofBook *book; Account *account; + Account *vatacct; GncOwner owner; GncCustomer *customer; gnc_commodity *commodity; + GncInvoice *invoice; } Fixture; static void @@ -47,6 +50,11 @@ setup( Fixture *fixture, gconstpointer pData ) fixture->commodity = gnc_commodity_new(fixture->book, "foo", "bar", "xy", "xy", 100); xaccAccountSetCommodity(fixture->account, fixture->commodity); + fixture->vatacct = xaccMallocAccount(fixture->book); + xaccAccountSetCommodity(fixture->vatacct, fixture->commodity); + + fixture->invoice = gncInvoiceCreate(fixture->book); + gncInvoiceSetCurrency(fixture->invoice, fixture->commodity); } static void @@ -54,8 +62,13 @@ teardown( Fixture *fixture, gconstpointer pData ) { xaccAccountBeginEdit(fixture->account); xaccAccountDestroy(fixture->account); - gnc_commodity_destroy(fixture->commodity); + xaccAccountBeginEdit(fixture->vatacct); + xaccAccountDestroy(fixture->vatacct); + + gncInvoiceBeginEdit(fixture->invoice); + gncInvoiceDestroy(fixture->invoice); + gnc_commodity_destroy(fixture->commodity); qof_book_destroy( fixture->book ); } @@ -109,8 +122,168 @@ test_entry_basics ( Fixture *fixture, gconstpointer pData ) } +static void +test_entry_rounding ( Fixture *fixture, gconstpointer pData ) +{ + GncEntry *entry = gncEntryCreate(fixture->book); + GncTaxTable *taxtable; + GncTaxTableEntry *tt_entry; + + gncTaxTableRegister(); + taxtable = gncTaxTableCreate(fixture->book); + tt_entry = gncTaxTableEntryCreate(); + gncTaxTableSetName(taxtable, "Percent tax"); + gncTaxTableEntrySetAccount(tt_entry, fixture->vatacct); + gncTaxTableEntrySetType(tt_entry, GNC_AMT_TYPE_PERCENT); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(1000000, 100000)); + gncTaxTableAddEntry(taxtable, tt_entry); + + // 1. Freestanding entry - a default denominator of 100000 is expected during rounding + + // Test with numbers that don't require rounding + /* Tax 10% (high precision GncNumeric), tax not included */ + gncEntrySetInvTaxable(entry, TRUE); + gncEntrySetInvTaxIncluded(entry, FALSE); + gncEntrySetInvTaxTable(entry, taxtable); + gncEntrySetQuantity(entry, gnc_numeric_create (2, 1)); + gncEntrySetInvPrice(entry, gnc_numeric_create (3, 1)); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (6, 1))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (6, 10))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (6, 1))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (6, 10))); + + // Test with numbers that do require rounding + /* Tax 10% (high precision GncNumeric), tax included */ + gncEntrySetInvTaxIncluded(entry, TRUE); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 11))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 110))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (545455, 100000))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (54545, 100000))); + + // Use different taxtable percentage precision + /* Tax 10% (low precision GncNumeric), tax included */ + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(10, 1)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 11))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 110))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (545455, 100000))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (54545, 100000))); + + // Test with odd tax percentage (Taken from a mailing list example) + /* Tax 13% (high precision GncNumeric), tax not included */ + gncEntrySetInvTaxIncluded(entry, FALSE); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(1300000, 100000)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + gncEntrySetQuantity(entry, gnc_numeric_create (1, 1)); + gncEntrySetInvPrice(entry, gnc_numeric_create (27750, 100)); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Note on the above expected result: the standard gncEntry denom is 100000 if the entry has no invoice or + * bill set. So with the example above no rounding is required yet */ + + // Test with odd tax percentage (Taken from a mailing list example) + /* Tax 13% (low precision GncNumeric), tax not included */ + gncEntrySetInvTaxIncluded(entry, FALSE); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(13, 1)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Note on the above expected result: the standard gncEntry denom is 100000 if the entry has no invoice or + * bill set. So with the example above no rounding is required yet */ + + // 2. gncEntry as part of a gncInvoice - the invoice currency's denominator is expected during rounding + gncInvoiceAddEntry(fixture->invoice, entry); + + // Test with numbers that don't require rounding + /* Tax 10% (high precision GncNumeric), tax not included */ + gncEntrySetInvTaxIncluded(entry, FALSE); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(1000000, 100000)); + gncEntrySetQuantity(entry, gnc_numeric_create (2, 1)); + gncEntrySetInvPrice(entry, gnc_numeric_create (3, 1)); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (6, 1))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (6, 10))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (6, 1))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (6, 10))); + + // Test with numbers that do require rounding + /* Tax 10% (high precision GncNumeric), tax included */ + gncEntrySetInvTaxIncluded(entry, TRUE); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 11))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 110))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (545, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (55, 100))); + + // Use different taxtable percentage precision + /* Tax 10% (low precision GncNumeric), tax included */ + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(10, 1)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 11))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (60, 110))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (545, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (55, 100))); + + // Test with odd tax percentage (Taken from a mailing list example) + /* Tax 13% (high precision GncNumeric), tax not included */ + gncEntrySetInvTaxIncluded(entry, FALSE); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(1300000, 100000)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + gncEntrySetQuantity(entry, gnc_numeric_create (1, 1)); + gncEntrySetInvPrice(entry, gnc_numeric_create (27750, 100)); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (3608, 100))); + /* Note on the above expected result: the standard gncEntry denom is 100000 if the entry has no invoice or + * bill set. So with the example above no rounding is required yet */ + + // Test with odd tax percentage (Taken from a mailing list example) + /* Tax 13% (low precision GncNumeric), tax not included */ + gncEntrySetInvTaxIncluded(entry, FALSE); + gncTaxTableEntrySetAmount(tt_entry, gnc_numeric_create(13, 1)); + gncEntrySetInvTaxTable(entry, NULL); + gncEntrySetInvTaxTable(entry, taxtable); + /* Check unrounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, FALSE, TRUE, FALSE), gnc_numeric_create (36075, 1000))); + /* Check rounded result */ + g_assert(gnc_numeric_equal (gncEntryGetDocValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (27750, 100))); + g_assert(gnc_numeric_equal (gncEntryGetDocTaxValue(entry, TRUE, TRUE, FALSE), gnc_numeric_create (3608, 100))); + /* Note on the above expected result: the standard gncEntry denom is 100000 if the entry has no invoice or + * bill set. So with the example above no rounding is required yet */ + + gncTaxTableBeginEdit(taxtable); + gncTaxTableDestroy(taxtable); +} void test_suite_gncEntry ( void ) { GNC_TEST_ADD( suitename, "basics", Fixture, NULL, setup, test_entry_basics, teardown ); + GNC_TEST_ADD( suitename, "value rounding", Fixture, NULL, setup, test_entry_rounding, teardown ); }