static void gncOwnerOffsetLots (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) { gnc_numeric target_offset; Split *split; /* from lot should not be a document lot because we're removing a split from there ! */ if (gncInvoiceGetInvoiceFromLot (from_lot)) { PWARN ("from_lot %p is a document lot. That is not allowed in gncOwnerOffsetLots", from_lot); return; } /* Get best matching split from from_lot to offset to_lot */ target_offset = gnc_lot_get_balance (to_lot); if (gnc_numeric_zero_p (target_offset)) return; // to_lot is already balanced, nothing more to do split = gncOwnerFindOffsettingSplit (from_lot, target_offset); if (!split) return; // No suitable offsetting split found, nothing more to do /* If the offsetting split is bigger than the amount needed to balance * to_lot, reduce the split so its reduced value closes to_lot exactly. * Note the negation in the reduction function. The split must be of * opposite sign of to_lot's balance in order to be able to close it. */ if (gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (split)), gnc_numeric_abs (target_offset)) > 0) gncOwnerReduceSplitTo (split, gnc_numeric_neg (target_offset)); /* Move the reduced split from from_lot to to_lot */ gnc_lot_add_split (to_lot, split); }
static gint default_sort (GNCPrice *price_a, GNCPrice *price_b) { gnc_commodity *curr_a, *curr_b; Timespec ts_a, ts_b; gint result; /* Primary sort (i.e. commodity name) handled by the tree structure. */ /* secondary sort: currency */ curr_a = gnc_price_get_currency (price_a); curr_b = gnc_price_get_currency (price_b); result = safe_utf8_collate (gnc_commodity_get_namespace (curr_a), gnc_commodity_get_namespace (curr_b)); if (result != 0) return result; result = safe_utf8_collate (gnc_commodity_get_mnemonic (curr_a), gnc_commodity_get_mnemonic (curr_b)); if (result != 0) return result; /* tertiary sort: time */ ts_a = gnc_price_get_time (price_a); ts_b = gnc_price_get_time (price_b); result = timespec_cmp (&ts_a, &ts_b); if (result) /* Reverse the result to present the most recent quote first. */ return -result; /* last sort: value */ return gnc_numeric_compare (gnc_price_get_value (price_a), gnc_price_get_value (price_b)); }
gint kvp_value_compare(const KvpValue * kva, const KvpValue * kvb) { if (kva == kvb) return 0; /* nothing is always less than something */ if (!kva && kvb) return -1; if (kva && !kvb) return 1; if (kva->type < kvb->type) return -1; if (kva->type > kvb->type) return 1; switch (kva->type) { case KVP_TYPE_GINT64: if (kva->value.int64 < kvb->value.int64) return -1; if (kva->value.int64 > kvb->value.int64) return 1; return 0; break; case KVP_TYPE_DOUBLE: return double_compare(kva->value.dbl, kvb->value.dbl); break; case KVP_TYPE_NUMERIC: return gnc_numeric_compare (kva->value.numeric, kvb->value.numeric); break; case KVP_TYPE_STRING: return strcmp(kva->value.str, kvb->value.str); break; case KVP_TYPE_GUID: return guid_compare(kva->value.guid, kvb->value.guid); break; case KVP_TYPE_TIMESPEC: return timespec_cmp(&(kva->value.timespec), &(kvb->value.timespec)); break; case KVP_TYPE_GDATE: return g_date_compare(&(kva->value.gdate), &(kvb->value.gdate)); break; case KVP_TYPE_BINARY: /* I don't know that this is a good compare. Ab is bigger than Acef. But I'm not sure that actually matters here. */ if (kva->value.binary.datasize < kvb->value.binary.datasize) return -1; if (kva->value.binary.datasize > kvb->value.binary.datasize) return 1; return memcmp(kva->value.binary.data, kvb->value.binary.data, kva->value.binary.datasize); break; case KVP_TYPE_GLIST: return kvp_glist_compare(kva->value.list, kvb->value.list); break; case KVP_TYPE_FRAME: return kvp_frame_compare(kva->value.frame, kvb->value.frame); break; default: break; } PERR ("reached unreachable code."); return FALSE; }
// A helper function that takes two splits. If the splits are of opposite sign // it reduces the biggest split to have the same value (but with opposite sign) // of the smaller split. // To make sure everything still continues to balance in addition a "remainder" split // will be created that will be added to the same lot and transaction as the biggest // split. // The opposite sign restriction is because that's the only scenario that makes sense // in the context of scrubbing business lots below. // If we created new splits, return TRUE, otherwise FALSE static gboolean reduce_biggest_split (Split *splitA, Split *splitB) { gnc_numeric valA = xaccSplitGetValue (splitA); gnc_numeric valB = xaccSplitGetValue (splitB); if (gnc_numeric_compare (gnc_numeric_abs (valA), gnc_numeric_abs (valB)) >= 0) return gncOwnerReduceSplitTo (splitA, gnc_numeric_neg (valB)); else return gncOwnerReduceSplitTo (splitB, gnc_numeric_neg (valA)); }
static gboolean check_edit_amount (GtkWidget *dialog, GtkWidget *amount, gnc_numeric *min, gnc_numeric *max, const char * error_message) { if (!gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (amount))) { if (error_message) gnc_error_dialog (dialog, "%s", error_message); return TRUE; } /* We've got a valid-looking number; check mix/max */ if (min || max) { gnc_numeric val = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (amount)); if ((min && gnc_numeric_compare (*min, val) > 0) || (max && gnc_numeric_compare (val, *max) > 0)) { if (error_message) gnc_error_dialog (dialog, "%s", error_message); return TRUE; } } return FALSE; }
static gint sort_by_value (GtkTreeModel *f_model, GtkTreeIter *f_iter_a, GtkTreeIter *f_iter_b, gpointer user_data) { gnc_commodity *comm_a, *comm_b; GNCPrice *price_a, *price_b; gboolean result; gint value; if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b)) return sort_ns_or_cm (f_model, f_iter_a, f_iter_b); /* * Sorted by commodity because of the tree structure. Now sort by * currency so we're only comparing numbers in the same currency * denomination. */ comm_a = gnc_price_get_currency (price_a); comm_b = gnc_price_get_currency (price_b); if (comm_a && comm_b) { value = safe_utf8_collate (gnc_commodity_get_namespace (comm_a), gnc_commodity_get_namespace (comm_b)); if (value != 0) return value; value = safe_utf8_collate (gnc_commodity_get_mnemonic (comm_a), gnc_commodity_get_mnemonic (comm_b)); if (value != 0) return value; } /* * Now do the actual price comparison now we're sure that its an * apples to apples comparison. */ result = gnc_numeric_compare (gnc_price_get_value (price_a), gnc_price_get_value (price_b)); if (result) return result; return default_sort (price_a, price_b); }
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b) { if (gnc_numeric_check(a)) { /* a is not a valid number, check b */ if (gnc_numeric_check(b)) /* Both invalid, consider them equal */ return TRUE; else /* a invalid, b valid */ return FALSE; } if (gnc_numeric_check(b)) /* a valid, b invalid */ return FALSE; return gnc_numeric_compare (a, b) == 0; }
int gncTaxTableEntryCompare (const GncTaxTableEntry *a, const GncTaxTableEntry *b) { char *name_a, *name_b; int retval; if (!a && !b) return 0; if (!a) return -1; if (!b) return 1; name_a = gnc_account_get_full_name (a->account); name_b = gnc_account_get_full_name (b->account); retval = g_strcmp0(name_a, name_b); g_free(name_a); g_free(name_b); if (retval) return retval; return gnc_numeric_compare (a->amount, b->amount); }
static gint sort_by_xxx_value (GtkTreeModel *f_model, GtkTreeIter *f_iter_a, GtkTreeIter *f_iter_b, gpointer user_data) { GncOwner *owner_a, *owner_b; gnc_numeric balance_a, balance_b; gint result; /* Find the owners */ sort_cb_setup (f_model, f_iter_a, f_iter_b, (const GncOwner**)&owner_a, (const GncOwner**)&owner_b); balance_a = gnc_ui_owner_get_balance_full(owner_a, NULL, NULL); balance_b = gnc_ui_owner_get_balance_full(owner_b, NULL, NULL); result = gnc_numeric_compare(balance_a, balance_b); if (result != 0) return result; return gncOwnerCompare(owner_a, owner_b); }
static void gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) { const gchar *action = _("Lot Link"); Account *acct = gnc_lot_get_account (from_lot); const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); Transaction *ll_txn = NULL; gnc_numeric from_lot_bal, to_lot_bal; Timespec from_ts, to_ts; time64 time_posted; Split *split; /* Sanity check */ if (!gncInvoiceGetInvoiceFromLot (from_lot) || !gncInvoiceGetInvoiceFromLot (to_lot)) return; /* Determine transaction date based on lot splits */ from_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot))); to_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot))); if (timespecToTime64 (from_ts) >= timespecToTime64 (to_ts)) time_posted = timespecToTime64 (from_ts); else time_posted = timespecToTime64 (to_ts); /* Figure out how much we can offset between the lots */ from_lot_bal = gnc_lot_get_balance (from_lot); to_lot_bal = gnc_lot_get_balance (to_lot); if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal), gnc_numeric_abs (to_lot_bal)) > 0) from_lot_bal = gnc_numeric_neg (to_lot_bal); else to_lot_bal = gnc_numeric_neg (from_lot_bal); xaccAccountBeginEdit (acct); /* Look for a pre-existing lot link we can extend */ ll_txn = get_ll_transaction_from_lot (from_lot); if (!ll_txn) ll_txn = get_ll_transaction_from_lot (to_lot); if (!ll_txn) { /* No pre-existing lot link. Create one. */ Timespec ts; timespecFromTime64 (&ts, time_posted); ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot)); xaccTransBeginEdit (ll_txn); xaccTransSetDescription (ll_txn, name ? name : "(Unknown)"); xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL)); xaccTransSetDatePostedTS (ll_txn, &ts); xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK); } else { Timespec ts = xaccTransRetDatePostedTS (ll_txn); xaccTransBeginEdit (ll_txn); /* Maybe we need to update the post date of the transaction ? */ if (time_posted > timespecToTime64 (ts)) { timespecFromTime64 (&ts, time_posted); xaccTransSetDatePostedTS (ll_txn, &ts); } } /* Create a split for the from_lot */ split = xaccMallocSplit (gnc_lot_get_book (from_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (from_lot, split); /* Create a split for the to_lot */ split = xaccMallocSplit (gnc_lot_get_book (to_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (to_lot, split); xaccTransCommitEdit (ll_txn); /* Do some post-cleaning on the lots * The above actions may have created splits that are * in the same transaction and lot. These can be merged. */ xaccScrubMergeLotSubSplits (to_lot, FALSE); xaccScrubMergeLotSubSplits (from_lot, FALSE); /* And finally set the same memo for all remaining splits * It's a convenience for the users to identify all documents * involved in the link. */ gncOwnerSetLotLinkMemo (ll_txn); xaccAccountCommitEdit (acct); }
static void check_reciprocal(void) { gnc_numeric a, b, ans, val; double flo; val = gnc_numeric_create(-60, 20); check_unary_op (gnc_numeric_eq, gnc_numeric_create (-3, -1), gnc_numeric_convert(val, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER), val, "expected %s got %s = (%s as RECIP(1))"); a = gnc_numeric_create(200, 100); b = gnc_numeric_create(300, 100); /* 2 + 3 = 5 */ ans = gnc_numeric_add(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER); check_binary_op (gnc_numeric_create(5, -1), ans, a, b, "expected %s got %s = %s + %s for reciprocal"); /* 2 + 3 = 5 */ a = gnc_numeric_create(2, -1); b = gnc_numeric_create(300, 100); ans = gnc_numeric_add(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER); check_binary_op (gnc_numeric_create(5, -1), ans, a, b, "expected %s got %s = %s + %s for reciprocal"); /* check gnc_numeric_to_double */ flo = gnc_numeric_to_double(gnc_numeric_create(5, -1)); do_test ((5.0 == flo), "reciprocal conversion"); /* check gnc_numeric_compare */ a = gnc_numeric_create(2, 1); b = gnc_numeric_create(2, -1); do_test((0 == gnc_numeric_compare(a, b)), " 2 == 2 "); a = gnc_numeric_create(2, 1); b = gnc_numeric_create(3, -1); do_test((-1 == gnc_numeric_compare(a, b)), " 2 < 3 "); a = gnc_numeric_create(-2, 1); b = gnc_numeric_create(2, -1); do_test((-1 == gnc_numeric_compare(a, b)), " -2 < 2 "); a = gnc_numeric_create(2, -1); b = gnc_numeric_create(3, -1); do_test((-1 == gnc_numeric_compare(a, b)), " 2 < 3 "); /* check for equality */ a = gnc_numeric_create(2, 1); b = gnc_numeric_create(2, -1); do_test(gnc_numeric_equal(a, b), " 2 == 2 "); /* check gnc_numeric_mul */ a = gnc_numeric_create(2, 1); b = gnc_numeric_create(3, -1); ans = gnc_numeric_mul(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER); check_binary_op (gnc_numeric_create(6, -1), ans, a, b, "expected %s got %s = %s * %s for reciprocal"); /* check gnc_numeric_div */ /* -60 / 20 = -3 */ a = gnc_numeric_create(-60, 1); b = gnc_numeric_create(2, -10); ans = gnc_numeric_div(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER); check_binary_op (gnc_numeric_create(-3, -1), ans, a, b, "expected %s got %s = %s / %s for reciprocal"); /* 60 / 20 = 3 */ a = gnc_numeric_create(60, 1); b = gnc_numeric_create(2, -10); ans = gnc_numeric_div(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER); check_binary_op (gnc_numeric_create(3, -1), ans, a, b, "expected %s got %s = %s / %s for reciprocal"); }
static gboolean new_tax_table_ok_cb (NewTaxTable *ntt) { TaxTableWindow *ttw; const char *name = NULL; char *message; Account *acc; gnc_numeric amount; g_return_val_if_fail (ntt, FALSE); ttw = ntt->ttw; /* Verify that we've got real, valid data */ /* verify the name, maybe */ if (ntt->new_table) { name = gtk_entry_get_text (GTK_ENTRY (ntt->name_entry)); if (name == NULL || *name == '\0') { message = _("You must provide a name for this Tax Table."); gnc_error_dialog (ntt->dialog, "%s", message); return FALSE; } if (gncTaxTableLookupByName (ttw->book, name)) { message = g_strdup_printf(_( "You must provide a unique name for this Tax Table. " "Your choice \"%s\" is already in use."), name); gnc_error_dialog (ntt->dialog, "%s", message); g_free (message); return FALSE; } } /* verify the amount. Note that negative values are allowed (required for European tax rules) */ amount = gnc_amount_edit_get_amount (GNC_AMOUNT_EDIT (ntt->amount_entry)); if (ntt->type == GNC_AMT_TYPE_PERCENT && gnc_numeric_compare (gnc_numeric_abs (amount), gnc_numeric_create (100, 1)) > 0) { message = _("Percentage amount must be between -100 and 100."); gnc_error_dialog (ntt->dialog, "%s", message); return FALSE; } /* verify the account */ acc = gnc_tree_view_account_get_selected_account (GNC_TREE_VIEW_ACCOUNT(ntt->acct_tree)); if (acc == NULL) { message = _("You must choose a Tax Account."); gnc_error_dialog (ntt->dialog, "%s", message); return FALSE; } gnc_suspend_gui_refresh (); /* Ok, it's all valid, now either change to add this thing */ if (ntt->new_table) { GncTaxTable *table = gncTaxTableCreate (ttw->book); gncTaxTableBeginEdit (table); gncTaxTableSetName (table, name); /* Reset the current table */ ttw->current_table = table; ntt->created_table = table; } else gncTaxTableBeginEdit (ttw->current_table); /* Create/edit the entry */ { GncTaxTableEntry *entry; if (ntt->entry) { entry = ntt->entry; } else { entry = gncTaxTableEntryCreate (); gncTaxTableAddEntry (ttw->current_table, entry); ttw->current_entry = entry; } gncTaxTableEntrySetAccount (entry, acc); gncTaxTableEntrySetType (entry, ntt->type); gncTaxTableEntrySetAmount (entry, amount); } /* Mark the table as changed and commit it */ gncTaxTableChanged (ttw->current_table); gncTaxTableCommitEdit (ttw->current_table); gnc_resume_gui_refresh(); return TRUE; }
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b) { return gnc_numeric_compare (a, b) == 0; }
void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) { GList *base_iter; /* General note: in the code below the term "payment" can * both mean a true payment or a document of * the opposite sign (invoice vs credit note) relative to * the lot being processed. In general this function will * perform a balancing action on a set of lots, so you * will also find frequent references to balancing instead. */ /* Payments can only be applied when at least an owner * and a list of lots to use are given */ if (!owner) return; if (!lots) return; for (base_iter = lots; base_iter; base_iter = base_iter->next) { GNCLot *base_lot = base_iter->data; QofBook *book; Account *acct; const gchar *name; GList *lot_list, *lot_iter; Transaction *txn = NULL; gnc_numeric base_lot_bal, val_to_pay, val_paid = { 0, 1 }; gboolean base_bal_is_pos; const gchar *action, *memo; /* Only attempt to apply payments to open lots. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ base_lot_bal = gnc_lot_get_balance (base_lot); if (gnc_numeric_zero_p (base_lot_bal)) continue; book = gnc_lot_get_book (base_lot); acct = gnc_lot_get_account (base_lot); name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); lot_list = base_iter->next; /* Strings used when creating splits later on. */ action = _("Lot Link"); memo = _("Internal link between invoice and payment lots"); /* Note: to balance the lot the payment to assign * must have the opposite sign of the existing lot balance */ val_to_pay = gnc_numeric_neg (base_lot_bal); base_bal_is_pos = gnc_numeric_positive_p (base_lot_bal); /* Create splits in a linking transaction between lots until * - either the invoice lot is balanced * - or there are no more balancing lots. */ for (lot_iter = lot_list; lot_iter; lot_iter = lot_iter->next) { gnc_numeric payment_lot_balance; Split *split; Account *bal_acct; gnc_numeric split_amt; GNCLot *balancing_lot = lot_iter->data; /* Only attempt to use open lots to balance the base lot. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ if (gnc_lot_is_closed (balancing_lot)) continue; /* Balancing transactions for invoice/payments can only happen * in the same account. */ bal_acct = gnc_lot_get_account (balancing_lot); if (acct != bal_acct) continue; payment_lot_balance = gnc_lot_get_balance (balancing_lot); /* Only attempt to balance if the base lot and balancing lot are * of the opposite sign. (Otherwise we would increase the balance * of the lot - Duh */ if (base_bal_is_pos == gnc_numeric_positive_p (payment_lot_balance)) continue; /* * If there is less to pay than there's open in the lot; we're done -- apply the base_lot_vale. * Note that payment_value and balance are opposite in sign, so we have to compare absolute values here * * Otherwise, apply the balance, subtract that from the payment_value, * and move on to the next one. */ if (gnc_numeric_compare (gnc_numeric_abs (val_to_pay), gnc_numeric_abs (payment_lot_balance)) <= 0) { /* abs(val_to_pay) <= abs(balance) */ split_amt = val_to_pay; } else { /* abs(val_to_pay) > abs(balance) * Remember payment_value and balance are opposite in sign, * and we want a payment to neutralize the current balance * so we need to negate here */ split_amt = payment_lot_balance; } /* If not created yet, create a new transaction linking * the base lot and the balancing lot(s) */ if (!txn) { Timespec ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (base_lot))); xaccAccountBeginEdit (acct); txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); xaccTransSetDescription (txn, name ? name : ""); xaccTransSetCurrency (txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &ts); xaccTransSetTxnType (txn, TXN_TYPE_LINK); } /* Create the split for this link in current balancing lot */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (split_amt), xaccAccountGetCommodity(acct)); gnc_lot_add_split (balancing_lot, split); /* If the balancing lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ { GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(balancing_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } val_paid = gnc_numeric_add (val_paid, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); val_to_pay = gnc_numeric_sub (val_to_pay, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); if (gnc_numeric_zero_p (val_to_pay)) break; } /* If the above loop managed to create a transaction and some balancing splits, * create the final split for the link transaction in the base lot */ if (txn) { GncInvoice *this_invoice; Split *split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action with utiltity function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, val_paid, xaccAccountGetCommodity(acct)); gnc_lot_add_split (base_lot, split); xaccTransCommitEdit (txn); xaccAccountCommitEdit (acct); /* If the base lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ this_invoice = gncInvoiceGetInvoiceFromLot(base_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } } }
static gboolean scrub_other_link (GNCLot *from_lot, Split *ll_from_split, GNCLot *to_lot, Split *ll_to_split) { Split *real_from_split; // This refers to the split in the payment lot representing the payment itself gboolean modified = FALSE; gnc_numeric real_from_val; gnc_numeric from_val = xaccSplitGetValue (ll_from_split); gnc_numeric to_val = xaccSplitGetValue (ll_to_split); Transaction *ll_txn = xaccSplitGetParent (ll_to_split); // Per iteration we can only scrub at most min (val-doc-split, val-pay-split) // So set the ceiling for finding a potential offsetting split in the lot if (gnc_numeric_compare (gnc_numeric_abs (from_val), gnc_numeric_abs (to_val)) >= 0) from_val = gnc_numeric_neg (to_val); // Next we have to find the original payment split so we can // add (part of) it to the document lot real_from_split = gncOwnerFindOffsettingSplit (from_lot, from_val); if (!real_from_split) return FALSE; // No usable split in the payment lot // We now have found 3 splits involved in the scrub action: // 2 lot link splits which we want to reduce // 1 other split to move into the original lot instead of the lot link split // As said only value of the split can be offset. // So split the bigger ones in two if needed and continue with equal valued splits only // The remainder is added to the lot link transaction and the lot to keep everything balanced // and will be processed in a future iteration modified = reduce_biggest_split (ll_from_split, ll_to_split); modified |= reduce_biggest_split (real_from_split, ll_from_split); modified |= reduce_biggest_split (ll_from_split, ll_to_split); // At this point ll_to_split and real_from_split should have the same value // If not, flag a warning and skip to the next iteration to_val = xaccSplitGetValue (ll_to_split); from_val = xaccSplitGetValue (ll_from_split); real_from_val = xaccSplitGetValue (real_from_split); if (!gnc_numeric_equal (real_from_val, to_val)) { // This is unexpected - write a warning message and skip this split PWARN("real_from_val (%s) and to_val (%s) differ. " "This is unexpected! Skip scrubbing of real_from_split %p against ll_to_split %p.", gnc_numeric_to_string (real_from_val), // gnc_numeric_denom (real_from_val), gnc_numeric_to_string (to_val), // gnc_numeric_denom (to_val), real_from_split, ll_to_split); return modified; } // Now do the actual split dance // - move real payment split to doc lot // - delete both lot link splits from the lot link transaction gnc_lot_add_split (to_lot, real_from_split); xaccTransBeginEdit (ll_txn); xaccSplitDestroy (ll_to_split); xaccSplitDestroy (ll_from_split); xaccTransCommitEdit (ll_txn); // Cleanup the lots xaccScrubMergeLotSubSplits (to_lot, FALSE); xaccScrubMergeLotSubSplits (from_lot, FALSE); return TRUE; // We did change splits/transactions/lots... }
static gboolean gncScrubLotDanglingPayments (GNCLot *lot) { SplitList * split_list, *filtered_list = NULL, *match_list = NULL, *node; Split *ll_split = gnc_lot_get_earliest_split (lot); Transaction *ll_trans = xaccSplitGetParent (ll_split); gnc_numeric ll_val = xaccSplitGetValue (ll_split); time64 ll_date = xaccTransGetDate (ll_trans); const char *ll_desc = xaccTransGetDescription (ll_trans); // look for free splits (i.e. not in any lot) which, // compared to the lot link split // - have the same date // - have the same description // - have an opposite sign amount // - free split's abs value is less than or equal to ll split's abs value split_list = xaccAccountGetSplitList(gnc_lot_get_account (lot)); for (node = split_list; node; node = node->next) { Split *free_split = node->data; Transaction *free_trans; gnc_numeric free_val; if (NULL != xaccSplitGetLot(free_split)) continue; free_trans = xaccSplitGetParent (free_split); if (ll_date != xaccTransGetDate (free_trans)) continue; if (0 != g_strcmp0 (ll_desc, xaccTransGetDescription (free_trans))) continue; free_val = xaccSplitGetValue (free_split); if (gnc_numeric_positive_p (ll_val) == gnc_numeric_positive_p (free_val)) continue; if (gnc_numeric_compare (gnc_numeric_abs (free_val), gnc_numeric_abs (ll_val)) > 0) continue; filtered_list = g_list_append(filtered_list, free_split); } match_list = gncSLFindOffsSplits (filtered_list, ll_val); g_list_free (filtered_list); for (node = match_list; node; node = node->next) { Split *match_split = node->data; gnc_lot_add_split (lot, match_split); } if (match_list) { g_list_free (match_list); return TRUE; } else return FALSE; }
static gboolean gncScrubLotLinks (GNCLot *scrub_lot) { gboolean modified = FALSE, restart_needed = FALSE; SplitList *sls_iter = NULL; scrub_start: restart_needed = FALSE; // Iterate over all splits in the lot for (sls_iter = gnc_lot_get_split_list (scrub_lot); sls_iter; sls_iter = sls_iter->next) { Split *sl_split = sls_iter->data; Transaction *ll_txn = NULL; // ll_txn = "Lot Link Transaction" SplitList *lts_iter = NULL; if (!sl_split) continue; // next scrub lot split // Only lot link transactions need to be scrubbed ll_txn = xaccSplitGetParent (sl_split); if (!ll_txn) { // Ooops - the split doesn't belong to any transaction ! // This is not expected so issue a warning and continue with next split PWARN("Encountered a split in a business lot that's not part of any transaction. " "This is unexpected! Skipping split %p.", sl_split); continue; } if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK) continue; // next scrub lot split // Iterate over all splits in the lot link transaction for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next) { Split *ll_txn_split = lts_iter->data; // These all refer to splits in the lot link transaction GNCLot *remote_lot = NULL; // lot at the other end of the lot link transaction gboolean sl_is_doc_lot, rl_is_doc_lot; if (!ll_txn_split) continue; // next lot link transaction split // Skip the split in the lot we're currently scrubbing if (sl_split == ll_txn_split) continue; // next lot link transaction split // Only splits of opposite signed values can be scrubbed if (gnc_numeric_positive_p (xaccSplitGetValue (sl_split)) == gnc_numeric_positive_p (xaccSplitGetValue (ll_txn_split))) continue; // next lot link transaction split // Find linked lot via split remote_lot = xaccSplitGetLot (ll_txn_split); if (!remote_lot) { // This is unexpected - write a warning message and skip this split PWARN("Encountered a Lot Link transaction with a split that's not in any lot. " "This is unexpected! Skipping split %p from transaction %p.", ll_txn_split, ll_txn); continue; } sl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (scrub_lot) != NULL); rl_is_doc_lot = (gncInvoiceGetInvoiceFromLot (remote_lot) != NULL); // Depending on the type of lots we're comparing, we need different actions // - Two document lots (an invoice and a credit note): // Special treatment - look for all document lots linked via ll_txn // and update the memo to be of more use to the uses. // - Two payment lots: // (Part of) the link will be eliminated and instead (part of) // one payment will be added to the other lot to keep the balance. // If the payments are not equal in abs value part of the bigger payment // will be moved to the smaller payment's lot. // - A document and a payment lot: // (Part of) the link will be eliminated and instead (part of) the real // payment will be added to the document lot to handle the payment. if (sl_is_doc_lot && rl_is_doc_lot) gncOwnerSetLotLinkMemo (ll_txn); else if (!sl_is_doc_lot && !rl_is_doc_lot) { gint cmp = gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetValue (sl_split)), gnc_numeric_abs (xaccSplitGetValue (ll_txn_split))); if (cmp >= 0) restart_needed = scrub_other_link (scrub_lot, sl_split, remote_lot, ll_txn_split); else restart_needed = scrub_other_link (remote_lot, ll_txn_split, scrub_lot, sl_split); } else { GNCLot *doc_lot = sl_is_doc_lot ? scrub_lot : remote_lot; GNCLot *pay_lot = sl_is_doc_lot ? remote_lot : scrub_lot; Split *ll_doc_split = sl_is_doc_lot ? sl_split : ll_txn_split; Split *ll_pay_split = sl_is_doc_lot ? ll_txn_split : sl_split; // Ok, let's try to move a payment from pay_lot to doc_lot restart_needed = scrub_other_link (pay_lot, ll_pay_split, doc_lot, ll_doc_split); } // If we got here, the splits in our lot and ll_txn have been severely mixed up // And our iterator lists are probably no longer valid // So let's start over if (restart_needed) { modified = TRUE; goto scrub_start; } } } return modified; }
Split *gncOwnerFindOffsettingSplit (GNCLot *lot, gnc_numeric target_value) { SplitList *ls_iter = NULL; Split *best_split = NULL; gnc_numeric best_val = { 0, 1}; gint best_flags = 0; if (!lot) return NULL; for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next) { Split *split = ls_iter->data; Transaction *txn; gnc_numeric split_value; gint new_flags = 0; gint val_cmp = 0; if (!split) continue; txn = xaccSplitGetParent (split); if (!txn) { // Ooops - the split doesn't belong to any transaction ! // This is not expected so issue a warning and continue with next split PWARN("Encountered a split in a payment lot that's not part of any transaction. " "This is unexpected! Skipping split %p.", split); continue; } // Check if this split has the opposite sign of the target value we want to offset split_value = xaccSplitGetValue (split); if (gnc_numeric_positive_p (target_value) == gnc_numeric_positive_p (split_value)) continue; // Ok we have found a split that potentially can offset the target value // Let's see if it's better than what we have found already. val_cmp = gnc_numeric_compare (gnc_numeric_abs (split_value), gnc_numeric_abs (target_value)); if (val_cmp == 0) new_flags += is_equal; else if (val_cmp > 0) new_flags += is_more; else new_flags += is_less; if (xaccTransGetTxnType (txn) != TXN_TYPE_LINK) new_flags += is_pay_split; if ((new_flags >= best_flags) && (gnc_numeric_compare (gnc_numeric_abs (split_value), gnc_numeric_abs (best_val)) > 0)) { // The new split is a better match than what we found so far best_split = split; best_flags = new_flags; best_val = split_value; } } return best_split; }
void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) { GList *left_iter; /* General note: in the code below the term "payment" can * both mean a true payment or a document of * the opposite sign (invoice vs credit note) relative to * the lot being processed. In general this function will * perform a balancing action on a set of lots, so you * will also find frequent references to balancing instead. */ /* Payments can only be applied when at least an owner * and a list of lots to use are given */ if (!owner) return; if (!lots) return; for (left_iter = lots; left_iter; left_iter = left_iter->next) { GNCLot *left_lot = left_iter->data; gnc_numeric left_lot_bal; gboolean left_lot_has_doc; gboolean left_modified = FALSE; Account *acct; GList *right_iter; /* Only attempt to apply payments to open lots. * Note that due to the iterative nature of this function lots * in the list may become empty/closed before they are evaluated as * base lot, so we should check this for each lot. */ if (!left_lot || qof_instance_get_destroying (left_lot)) continue; if (gnc_lot_count_splits (left_lot) == 0) { gnc_lot_destroy (left_lot); continue; } if (gnc_lot_is_closed (left_lot)) continue; acct = gnc_lot_get_account (left_lot); xaccAccountBeginEdit (acct); left_lot_bal = gnc_lot_get_balance (left_lot); left_lot_has_doc = (gncInvoiceGetInvoiceFromLot (left_lot) != NULL); /* Attempt to offset left_lot with any of the remaining lots. To do so * iterate over the remaining lots adding lot links or moving payments * around. */ for (right_iter = left_iter->next; right_iter; right_iter = right_iter->next) { GNCLot *right_lot = right_iter->data; gnc_numeric right_lot_bal; gboolean right_lot_has_doc; /* Only attempt to use open lots to balance the base lot. * Note that due to the iterative nature of this function lots * in the list may become empty/closed before they are evaluated as * base lot, so we should check this for each lot. */ if (!right_lot || qof_instance_get_destroying (right_lot)) continue; if (gnc_lot_count_splits (right_lot) == 0) { gnc_lot_destroy (right_lot); continue; } if (gnc_lot_is_closed (right_lot)) continue; /* Balancing transactions for invoice/payments can only happen * in the same account. */ if (acct != gnc_lot_get_account (right_lot)) continue; /* Only attempt to balance if the base lot and balancing lot are * of the opposite sign. (Otherwise we would increase the balance * of the lot - Duh */ right_lot_bal = gnc_lot_get_balance (right_lot); if (gnc_numeric_positive_p (left_lot_bal) == gnc_numeric_positive_p (right_lot_bal)) continue; /* Ok we found two lots than can (partly) offset each other. * Depending on the lot types, a different action is needed to accomplish this. * 1. Both lots are document lots (invoices/credit notes) * -> Create a lot linking transaction between the lots * 2. Both lots are payment lots (lots without a document attached) * -> Use part of the bigger lot to the close the smaller lot * 3. One document lot with one payment lot * -> Use (part of) the payment to offset (part of) the document lot, * Which one will be closed depends on which is the bigger one */ right_lot_has_doc = (gncInvoiceGetInvoiceFromLot (right_lot) != NULL); if (left_lot_has_doc && right_lot_has_doc) gncOwnerCreateLotLink (left_lot, right_lot, owner); else if (!left_lot_has_doc && !right_lot_has_doc) { gint cmp = gnc_numeric_compare (gnc_numeric_abs (left_lot_bal), gnc_numeric_abs (right_lot_bal)); if (cmp >= 0) gncOwnerOffsetLots (left_lot, right_lot, owner); else gncOwnerOffsetLots (right_lot, left_lot, owner); } else { GNCLot *doc_lot = left_lot_has_doc ? left_lot : right_lot; GNCLot *pay_lot = left_lot_has_doc ? right_lot : left_lot; // Ok, let's try to move a payment from pay_lot to doc_lot gncOwnerOffsetLots (pay_lot, doc_lot, owner); } /* If we get here, then right_lot was modified * If the lot has a document, send an event for send an event for it as well * so it gets potentially updated as paid */ { GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(right_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } left_modified = TRUE; } /* If left_lot was modified and the lot has a document, * send an event for send an event for it as well * so it gets potentially updated as paid */ if (left_modified) { GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(left_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } xaccAccountCommitEdit (acct); } }