Example #1
0
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);

}
Example #2
0
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));
}
Example #3
0
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;
}
Example #4
0
// 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;
}
Example #6
0
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);
}
Example #7
0
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;
}
Example #8
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);
}
Example #9
0
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);
}
Example #10
0
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;
}
Example #13
0
gboolean
gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
{
    return gnc_numeric_compare (a, b) == 0;
}
Example #14
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);

        }

    }
}
Example #15
0
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...
}
Example #16
0
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;
}
Example #17
0
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;
}
Example #18
0
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;
}
Example #19
0
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);

    }
}