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); }
// 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)); }
gnc_numeric gnc_reconcile_view_reconciled_balance (GNCReconcileView *view) { gnc_numeric total = gnc_numeric_zero (); g_return_val_if_fail (view != NULL, total); g_return_val_if_fail (GNC_IS_RECONCILE_VIEW (view), total); if (view->reconciled == NULL) return total; g_hash_table_foreach (view->reconciled, grv_balance_hash_helper, &total); return gnc_numeric_abs (total); }
void xaccScrubSubSplitPrice (Split *split, int maxmult, int maxamtscu) { gnc_numeric src_amt, src_val; SplitList *node; if (FALSE == is_subsplit (split)) return; ENTER (" "); /* Get 'price' of the indicated split */ src_amt = xaccSplitGetAmount (split); src_val = xaccSplitGetValue (split); /* Loop over splits, adjust each so that it has the same * ratio (i.e. price). Change the value to get things * right; do not change the amount */ for (node = split->parent->splits; node; node = node->next) { Split *s = node->data; Transaction *txn = s->parent; gnc_numeric dst_amt, dst_val, target_val; gnc_numeric frac, delta; int scu; /* Skip the reference split */ if (s == split) continue; scu = gnc_commodity_get_fraction (txn->common_currency); dst_amt = xaccSplitGetAmount (s); dst_val = xaccSplitGetValue (s); frac = gnc_numeric_div (dst_amt, src_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE); target_val = gnc_numeric_mul (frac, src_val, scu, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP); if (gnc_numeric_check (target_val)) { PERR ("Numeric overflow of value\n" "\tAcct=%s txn=%s\n" "\tdst_amt=%s src_val=%s src_amt=%s\n", xaccAccountGetName (s->acc), xaccTransGetDescription(txn), gnc_num_dbg_to_string(dst_amt), gnc_num_dbg_to_string(src_val), gnc_num_dbg_to_string(src_amt)); continue; } /* If the required price changes are 'small', do nothing. * That is a case that the user will have to deal with * manually. This routine is really intended only for * a gross level of synchronization. */ delta = gnc_numeric_sub_fixed (target_val, dst_val); delta = gnc_numeric_abs (delta); if (maxmult * delta.num < delta.denom) continue; /* If the amount is small, pass on that too */ if ((-maxamtscu < dst_amt.num) && (dst_amt.num < maxamtscu)) continue; /* Make the actual adjustment */ xaccTransBeginEdit (txn); xaccSplitSetValue (s, target_val); xaccTransCommitEdit (txn); } LEAVE (" "); }
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; }
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); } }
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 gnc_query_list_fill(GNCQueryList *list) { GNCQueryListPriv *priv; gchar *strings[list->num_columns + 1]; GList *entries, *item; const GncGUID *guid; gint i; /* Clear all watches */ priv = GNC_QUERY_LIST_GET_PRIVATE(list); gnc_gui_component_clear_watches (priv->component_id); /* Reverse the list now because 'append()' takes too long */ entries = qof_query_run(list->query); for (item = entries; item; item = item->next) { GList *node; gint row; const QofParam *gup; QofParam *qp = NULL; for (i = 0, node = list->column_params; node; node = node->next) { GNCSearchParam *param = node->data; GSList *converters = gnc_search_param_get_converters (param); const char *type = gnc_search_param_get_param_type (param); gpointer res = item->data; /* if this is a boolean, ignore it now -- we'll use a checkmark later */ if (!safe_strcmp (type, QOF_TYPE_BOOLEAN)) { strings[i++] = g_strdup(""); continue; } /* Do all the object conversions */ for (; converters; converters = converters->next) { qp = converters->data; if (converters->next) { res = (qp->param_getfcn)(res, qp); } } /* Now convert this to a text value for the row */ if (!safe_strcmp(type, QOF_TYPE_DEBCRED) || !safe_strcmp(type, QOF_TYPE_NUMERIC)) { gnc_numeric (*nfcn)(gpointer, QofParam *) = (gnc_numeric(*)(gpointer, QofParam *))(qp->param_getfcn); gnc_numeric value = nfcn(res, qp); if (list->numeric_abs) value = gnc_numeric_abs (value); strings[i++] = g_strdup(xaccPrintAmount(value, gnc_default_print_info(FALSE))); } else strings[i++] = qof_query_core_to_string (type, res, qp); } row = gtk_clist_append (GTK_CLIST(list), (gchar **) strings); gtk_clist_set_row_data (GTK_CLIST(list), row, item->data); /* Free up our strings */ for (i = 0; i < list->num_columns; i++) { if (strings[i]) g_free (strings[i]); } /* Now update any checkmarks */ update_booleans (list, row); /* and set a watcher on this item */ gup = priv->get_guid; guid = (const GncGUID*)((gup->param_getfcn)(item->data, gup)); gnc_gui_component_watch_entity (priv->component_id, guid, QOF_EVENT_MODIFY | QOF_EVENT_DESTROY); list->num_entries++; } }
static gboolean gnc_entry_ledger_auto_completion (GncEntryLedger *ledger, gncTableTraversalDir dir, VirtualLocation *p_new_virt_loc) { GncEntry *entry; GncEntry *blank_entry; GncEntry *auto_entry; const char* cell_name; const char *desc; BasicCell *cell = NULL; char *account_name = NULL; char *new_value = NULL; g_assert(ledger); g_assert(ledger->table); blank_entry = gnc_entry_ledger_get_blank_entry (ledger); /* auto-completion is only triggered by a tab out */ if (dir != GNC_TABLE_TRAVERSE_RIGHT) return FALSE; entry = gnc_entry_ledger_get_current_entry (ledger); if (entry == NULL) return FALSE; cell_name = gnc_table_get_current_cell_name (ledger->table); /* Auto-completion is done only in an entry ledger */ switch (ledger->type) { case GNCENTRY_ORDER_ENTRY: case GNCENTRY_INVOICE_ENTRY: case GNCENTRY_BILL_ENTRY: case GNCENTRY_EXPVOUCHER_ENTRY: case GNCENTRY_CUST_CREDIT_NOTE_ENTRY: case GNCENTRY_VEND_CREDIT_NOTE_ENTRY: case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY: break; default: return FALSE; } /* Further conditions before we actually do auto-completion: */ /* There must be a blank entry */ if (blank_entry == NULL) return FALSE; /* we must be on the blank entry */ if (entry != blank_entry) return FALSE; /* and leaving the description cell */ if (!gnc_cell_name_equal (cell_name, ENTRY_DESC_CELL)) return FALSE; /* nothing but the date and description should be changed */ /* FIXME, this should be refactored. */ if (gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_ACTN_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_QTY_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_PRIC_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_DISC_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_DISTYPE_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_DISHOW_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_IACCT_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_BACCT_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_TAXABLE_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_TAXINCLUDED_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_TAXTABLE_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_VALUE_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_TAXVAL_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_BILLABLE_CELL, TRUE) || gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_PAYMENT_CELL, TRUE)) return FALSE; /* and the description should indeed be changed */ if (!gnc_table_layout_get_cell_changed (ledger->table->layout, ENTRY_DESC_CELL, TRUE)) return FALSE; /* to a non-empty value */ desc = gnc_table_layout_get_cell_value (ledger->table->layout, ENTRY_DESC_CELL); if ((desc == NULL) || (*desc == '\0')) return FALSE; /* Ok, we are sure we want to trigger auto-completion. Now find an * entry to copy the values from. FIXME: Currently we only use * the entries from the current invoice/bill, but it would be * better to draw this from a larger set of entries. */ auto_entry = /* Use this for book-wide auto-completion of the invoice entries */ find_entry_in_book_by_desc(ledger, desc); /* #else */ /* gnc_find_entry_in_reg_by_desc(ledger, desc); */ /* #endif */ if (auto_entry == NULL) return FALSE; /* now perform the completion */ gnc_suspend_gui_refresh (); /* Auto-complete the action field */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_ACTN_CELL); set_value_combo_cell (cell, gncEntryGetAction (auto_entry)); /* Auto-complete the account field */ switch (ledger->type) { case GNCENTRY_INVOICE_ENTRY: case GNCENTRY_CUST_CREDIT_NOTE_ENTRY: cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_IACCT_CELL); account_name = gnc_get_account_name_for_register (gncEntryGetInvAccount(auto_entry)); break; case GNCENTRY_EXPVOUCHER_ENTRY: case GNCENTRY_BILL_ENTRY: case GNCENTRY_VEND_CREDIT_NOTE_ENTRY: case GNCENTRY_EMPL_CREDIT_NOTE_ENTRY: cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_BACCT_CELL); account_name = gnc_get_account_name_for_register (gncEntryGetBillAccount(auto_entry)); break; case GNCENTRY_ORDER_ENTRY: default: cell = NULL; account_name = NULL; break; } set_value_combo_cell (cell, account_name); g_free (account_name); /* Auto-complete quantity cell * Note: we always autofill a positive quantity value. This allows us to * - reuse invoice entries on credit note ledgers, meaning you can credit * some invoice entry via autofill without having to manually fix the sign * on the credit note. * - autofill credit note entries on other credit note entries (without having * to juggle sign reversals internally) * - autofill credit note entries on invoice ledgers * * Disadvantage: invoice entries with explicitly set negative quantities will * be autofilled to positive quantities in later uses. But it seems less common * to me to require a negative entry again next time. */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_QTY_CELL); set_value_price_cell (cell, gnc_numeric_abs(gncEntryGetQuantity (auto_entry))); /* Auto-complete price cell */ { gnc_numeric price; switch (ledger->type) { case GNCENTRY_INVOICE_ENTRY: case GNCENTRY_CUST_CREDIT_NOTE_ENTRY: price = gncEntryGetInvPrice (auto_entry); break; default: price = gncEntryGetBillPrice (auto_entry); } /* Auto-complete price cell */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_PRIC_CELL); set_value_price_cell (cell, price); } /* We intentionally skip the discount column */ /* Taxable?, Tax-include?, Tax table */ { gboolean taxable, taxincluded; GncTaxTable *taxtable; switch (ledger->type) { case GNCENTRY_INVOICE_ENTRY: case GNCENTRY_CUST_CREDIT_NOTE_ENTRY: taxable = gncEntryGetInvTaxable (auto_entry); taxincluded = gncEntryGetInvTaxIncluded (auto_entry); taxtable = gncEntryGetInvTaxTable (auto_entry); break; default: taxable = gncEntryGetBillTaxable (auto_entry); taxincluded = gncEntryGetBillTaxIncluded (auto_entry); taxtable = gncEntryGetBillTaxTable (auto_entry); } /* Taxable? cell */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXABLE_CELL); gnc_checkbox_cell_set_flag ((CheckboxCell *) cell, taxable); gnc_basic_cell_set_changed (cell, TRUE); /* taxincluded? cell */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXINCLUDED_CELL); gnc_checkbox_cell_set_flag ((CheckboxCell *) cell, taxincluded); gnc_basic_cell_set_changed (cell, TRUE); /* Taxable? cell */ cell = gnc_table_layout_get_cell (ledger->table->layout, ENTRY_TAXTABLE_CELL); set_value_combo_cell(cell, gncTaxTableGetName (taxtable)); } gnc_resume_gui_refresh (); /* now move to the non-empty amount column unless config setting says not */ if ( !gnc_gconf_get_bool(GCONF_GENERAL_REGISTER, "tab_includes_transfer_on_memorised", NULL) ) { VirtualLocation new_virt_loc; const char *cell_name = ENTRY_QTY_CELL; if (gnc_table_get_current_cell_location (ledger->table, cell_name, &new_virt_loc)) *p_new_virt_loc = new_virt_loc; } return TRUE; }