Revisit invoice payment in multi-currency context

- Show proper amount in dialog when applying or editing an existing transaction as payment
- Be more careful not to waste the existing payment split
- If the user changed the payment amount while starting from an existing transaction
  unreconcile the changed payment split
- Avoid needlessly changing transaction currency (only do so if the user chose
  a new transfer account and the old currency is neither the new transfer account's
  currency nor the post account's currency)
This commit is contained in:
Geert Janssens
2023-01-29 22:48:50 +01:00
parent 2d3d05068d
commit 894f8241e1
2 changed files with 64 additions and 79 deletions

View File

@@ -1825,7 +1825,8 @@ PaymentWindow * gnc_ui_payment_new_with_txn (GtkWindow* parent, GncOwner *owner,
GDate txn_date = xaccTransGetDatePostedGDate (txn); GDate txn_date = xaccTransGetDatePostedGDate (txn);
gnc_ui_payment_window_set_date(pw, &txn_date); gnc_ui_payment_window_set_date(pw, &txn_date);
} }
gnc_ui_payment_window_set_amount(pw, xaccSplitGetValue(payment_split));
gnc_ui_payment_window_set_amount(pw, xaccSplitConvertAmount (payment_split, post_acct));
if (payment_split) if (payment_split)
gnc_ui_payment_window_set_xferaccount(pw, xaccSplitGetAccount(payment_split)); gnc_ui_payment_window_set_xferaccount(pw, xaccSplitGetAccount(payment_split));
return pw; return pw;

View File

@@ -756,10 +756,12 @@ gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
QofBook *book; QofBook *book;
Split *split; Split *split;
const char *name; const char *name;
gnc_commodity *commodity; gnc_commodity *post_comm, *xfer_comm;
Split *xfer_split = NULL; Split *xfer_split = NULL;
Transaction *txn = NULL; Transaction *txn = NULL;
GNCLot *payment_lot; GNCLot *payment_lot;
gnc_numeric xfer_amount = gnc_numeric_zero();
gnc_numeric txn_value = gnc_numeric_zero();
/* Verify our arguments */ /* Verify our arguments */
if (!owner || !posted_acc || !xfer_acc) return NULL; if (!owner || !posted_acc || !xfer_acc) return NULL;
@@ -768,7 +770,9 @@ gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
/* Compute the ancillary data */ /* Compute the ancillary data */
book = gnc_account_get_book (posted_acc); book = gnc_account_get_book (posted_acc);
name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner)); name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner));
commodity = gncOwnerGetCurrency (owner); post_comm = xaccAccountGetCommodity (posted_acc);
xfer_comm = xaccAccountGetCommodity (xfer_acc);
// reverse = use_reversed_payment_amounts(owner); // reverse = use_reversed_payment_amounts(owner);
if (preset_txn && *preset_txn) if (preset_txn && *preset_txn)
@@ -776,110 +780,90 @@ gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
if (txn) if (txn)
{ {
xaccTransSetDescription (txn, name ? name : ""); int i = 0;
/* Pre-existing transaction was specified. We completely clear it, /* Pre-existing transaction was specified. We completely clear it,
* except for the split in the transfer account, unless the * except for a pre-existing transfer split. We're very conservative
* transaction can't be reused (wrong currency, wrong transfer account). * in preserving that one as it may have been reconciled already. */
* In that case, the transaction is simply removed and an new
* one created. */
xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc); xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc);
xaccTransBeginEdit (txn);
if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner)) while (i < xaccTransCountSplits(txn))
{ {
PINFO("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction."); Split *split = xaccTransGetSplit (txn, i);
xfer_split = NULL; if (split == xfer_split)
} ++i;
else
if (!xfer_split) xaccSplitDestroy(split);
{
PINFO("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one.");
xaccTransBeginEdit (txn);
xaccTransDestroy (txn);
xaccTransCommitEdit (txn);
txn = NULL;
}
else
{
int i = 0;
xaccTransBeginEdit (txn);
while (i < xaccTransCountSplits(txn))
{
Split *split = xaccTransGetSplit (txn, i);
if (split == xfer_split)
{
gnc_set_num_action (NULL, split, num, _("Payment"));
++i;
}
else
{
xaccSplitDestroy(split);
}
}
/* Note: don't commit transaction now - that would insert an imbalance split.*/
} }
/* Note: don't commit transaction now - that would insert an imbalance split.*/
} }
else
/* Create the transaction if we don't have one yet */
if (!txn)
{ {
txn = xaccMallocTransaction (book); txn = xaccMallocTransaction (book);
xaccTransBeginEdit (txn); xaccTransBeginEdit (txn);
} }
/* Complete transaction setup */
xaccTransSetDescription (txn, name ? name : "");
if (!gnc_commodity_equal(xaccTransGetCurrency (txn), post_comm) &&
!gnc_commodity_equal (xaccTransGetCurrency (txn), xfer_comm))
xaccTransSetCurrency (txn, xfer_comm);
/* With all commodities involved known, define split amounts and txn value.
* - post amount (amount passed in as parameter) is always in post_acct commodity,
* - xfer amount requires conversion if the xfer account has a different
* commodity than the post account.
* - txn value requires conversion if the post account has a different
* commodity than the transaction */
if (gnc_commodity_equal(post_comm, xfer_comm))
xfer_amount = amount;
else
xfer_amount = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
GNC_HOW_RND_ROUND_HALF_UP);
if (gnc_commodity_equal(post_comm, xaccTransGetCurrency (txn)))
txn_value = amount;
else
txn_value = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
GNC_HOW_RND_ROUND_HALF_UP);
/* Insert a split for the transfer account if we don't have one yet */ /* Insert a split for the transfer account if we don't have one yet */
if (!xfer_split) if (!xfer_split)
{ {
/* Set up the transaction */
xaccTransSetDescription (txn, name ? name : "");
/* set per book option */
xaccTransSetCurrency (txn, commodity);
/* The split for the transfer account */ /* The split for the transfer account */
split = xaccMallocSplit (book); xfer_split = xaccMallocSplit (book);
xaccSplitSetMemo (split, memo); xaccSplitSetMemo (xfer_split, memo);
/* set per book option */ /* set per book option */
gnc_set_num_action (NULL, split, num, _("Payment")); gnc_set_num_action (NULL, xfer_split, num, _("Payment"));
xaccAccountBeginEdit (xfer_acc); xaccAccountBeginEdit (xfer_acc);
xaccAccountInsertSplit (xfer_acc, split); xaccAccountInsertSplit (xfer_acc, xfer_split);
xaccAccountCommitEdit (xfer_acc); xaccAccountCommitEdit (xfer_acc);
xaccTransAppendSplit (txn, split); xaccTransAppendSplit (txn, xfer_split);
if (gnc_commodity_equal(xaccAccountGetCommodity(xfer_acc), commodity)) xaccSplitSetAmount(xfer_split, xfer_amount); /* Payment in xfer account currency */
{ xaccSplitSetValue(xfer_split, txn_value); /* Payment in transaction currency */
xaccSplitSetBaseValue (split, amount, commodity); }
} /* For a pre-existing xfer split, let's check if the amount and value
else * have changed. If so, update them and unreconcile. */
{ else if (!gnc_numeric_equal (xaccSplitGetAmount (xfer_split), xfer_amount) ||
/* This will be a multi-currency transaction. The amount passed to this !gnc_numeric_equal (xaccSplitGetValue (xfer_split), txn_value))
* function is in the owner commodity (also used by the post account). {
* For the xfer split we also need to value the payment in the xfer account's xaccSplitSetAmount (xfer_split, xfer_amount);
* commodity. xaccSplitSetValue (xfer_split, txn_value);
* exch is from post account to xfer account so that can be used directly xaccSplitSetReconcile (xfer_split, NREC);
* to calculate the equivalent amount in the xfer account's commodity. */
gnc_numeric xfer_amount = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
GNC_HOW_RND_ROUND_HALF_UP);
xaccSplitSetAmount(split, xfer_amount); /* Payment in xfer account currency */
xaccSplitSetValue(split, amount); /* Payment in transaction currency */
}
} }
/* Add a split in the post account */ /* Add a split in the post account */
split = xaccMallocSplit (book); split = xaccMallocSplit (book);
xaccSplitSetMemo (split, memo); xaccSplitSetMemo (split, memo);
/* set per book option */ /* set per book option */
gnc_set_num_action (NULL, split, num, _("Payment")); xaccSplitSetAction (split, _("Payment"));
xaccAccountBeginEdit (posted_acc); xaccAccountBeginEdit (posted_acc);
xaccAccountInsertSplit (posted_acc, split); xaccAccountInsertSplit (posted_acc, split);
xaccAccountCommitEdit (posted_acc); xaccAccountCommitEdit (posted_acc);
xaccTransAppendSplit (txn, split); xaccTransAppendSplit (txn, split);
xaccSplitSetBaseValue (split, gnc_numeric_neg (amount), commodity); xaccSplitSetAmount (split, gnc_numeric_neg (amount));
xaccSplitSetValue (split, gnc_numeric_neg (txn_value));
/* Create a new lot for the payment */ /* Create a new lot for the payment */
payment_lot = gnc_lot_new (book); payment_lot = gnc_lot_new (book);
@@ -887,7 +871,7 @@ gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
gnc_lot_add_split (payment_lot, split); gnc_lot_add_split (payment_lot, split);
/* Mark the transaction as a payment */ /* Mark the transaction as a payment */
gnc_set_num_action (txn, NULL, num, _("Payment")); xaccTransSetNum (txn, num);
xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT); xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT);
/* Set date for transaction */ /* Set date for transaction */