static Split* dom_tree_to_split(xmlNodePtr node, QofBook *book) { struct split_pdata pdata; Split *ret; g_return_val_if_fail (book, NULL); ret = xaccMallocSplit(book); g_return_val_if_fail(ret, NULL); pdata.split = ret; pdata.book = book; /* this isn't going to work in a testing setup */ if (dom_tree_generic_parse(node, spl_dom_handlers, &pdata)) { return ret; } else { xaccSplitDestroy(ret); return NULL; } }
static void merge_splits (Split *sa, Split *sb) { Account *act; Transaction *txn; gnc_numeric amt, val; act = xaccSplitGetAccount (sb); xaccAccountBeginEdit (act); txn = sa->parent; xaccTransBeginEdit (txn); /* Remove the guid of sb from the 'gemini' of sa */ remove_guids (sa, sb); /* Add amount of sb into sa, ditto for value. */ amt = xaccSplitGetAmount (sa); amt = gnc_numeric_add_fixed (amt, xaccSplitGetAmount (sb)); xaccSplitSetAmount (sa, amt); val = xaccSplitGetValue (sa); val = gnc_numeric_add_fixed (val, xaccSplitGetValue (sb)); xaccSplitSetValue (sa, val); /* Set reconcile to no; after this much violence, * no way its reconciled. */ xaccSplitSetReconcile (sa, NREC); /* If sb has associated gains splits, trash them. */ if ((sb->gains_split) && (sb->gains_split->gains & GAINS_STATUS_GAINS)) { Transaction *t = sb->gains_split->parent; xaccTransBeginEdit (t); xaccTransDestroy (t); xaccTransCommitEdit (t); } /* Finally, delete sb */ xaccSplitDestroy(sb); xaccTransCommitEdit (txn); xaccAccountCommitEdit (act); }
/** Creates a list of transactions from parsed data. Transactions that * could be created from rows are placed in parse_data->transactions; * rows that fail are placed in parse_data->error_lines. (Note: there * is no way for this function to "fail," i.e. it only returns 0, so * it may be changed to a void function in the future.) * @param parse_data Data that is being parsed * @param account Account with which transactions are created * @param redo_errors TRUE to convert only error data, FALSE for all data * @return 0 on success, 1 on failure */ int gnc_csv_parse_to_trans (GncCsvParseData* parse_data, Account* account, gboolean redo_errors) { gboolean hasBalanceColumn; int i, j, max_cols = 0; GArray* column_types = parse_data->column_types; GList *error_lines = NULL, *begin_error_lines = NULL; /* last_transaction points to the last element in * parse_data->transactions, or NULL if it's empty. */ GList* last_transaction = NULL; /* Free parse_data->error_lines and parse_data->transactions if they * already exist. */ if (redo_errors) /* If we're redoing errors, we save freeing until the end. */ { begin_error_lines = error_lines = parse_data->error_lines; } else { if (parse_data->error_lines != NULL) { g_list_free(parse_data->error_lines); } if (parse_data->transactions != NULL) { g_list_free (parse_data->transactions); } } parse_data->error_lines = NULL; if (redo_errors) /* If we're looking only at error data ... */ { if (parse_data->transactions == NULL) { last_transaction = NULL; } else { /* Move last_transaction to the end. */ last_transaction = parse_data->transactions; while (g_list_next (last_transaction) != NULL) { last_transaction = g_list_next (last_transaction); } } /* ... we use only the lines in error_lines. */ if (error_lines == NULL) i = parse_data->orig_lines->len; /* Don't go into the for loop. */ else i = GPOINTER_TO_INT(error_lines->data); } else /* Otherwise, we look at all the data. */ { /* The following while-loop effectively behaves like the following for-loop: * for(i = 0; i < parse_data->orig_lines->len; i++). */ i = parse_data->start_row; last_transaction = NULL; } /* set parse_data->end_row to number of lines */ if (parse_data->end_row > parse_data->orig_lines->len) parse_data->end_row = parse_data->orig_lines->len; while (i < parse_data->end_row) { GPtrArray* line = parse_data->orig_lines->pdata[i]; /* This flag is TRUE if there are any errors in this row. */ gboolean errors = FALSE; gchar* error_message = NULL; TransPropertyList* list = trans_property_list_new (account, parse_data->date_format, parse_data->currency_format); GncCsvTransLine* trans_line = NULL; for (j = 0; j < line->len; j++) { /* We do nothing in "None" or "Account" columns. */ if ((column_types->data[j] != GNC_CSV_NONE) && (column_types->data[j] != GNC_CSV_ACCOUNT)) { /* Affect the transaction appropriately. */ TransProperty* property = trans_property_new (column_types->data[j], list); gboolean succeeded = trans_property_set (property, line->pdata[j]); /* TODO Maybe move error handling to within TransPropertyList functions? */ if (succeeded) { trans_property_list_add (property); } else { errors = TRUE; error_message = g_strdup_printf (_("%s column could not be understood."), _(gnc_csv_column_type_strs[property->type])); trans_property_free (property); break; } } } /* If we had success, add the transaction to parse_data->transaction. */ if (!errors) { trans_line = trans_property_list_to_trans (list, &error_message); errors = trans_line == NULL; } trans_property_list_free (list); /* If there were errors, add this line to parse_data->error_lines. */ if (errors) { parse_data->error_lines = g_list_append (parse_data->error_lines, GINT_TO_POINTER(i)); /* If there's already an error message, we need to replace it. */ if (line->len > (int)(parse_data->orig_row_lengths->data[i])) { g_free(line->pdata[line->len - 1]); line->pdata[line->len - 1] = error_message; } else { /* Put the error message at the end of the line. */ g_ptr_array_add (line, error_message); } } else { /* If all went well, add this transaction to the list. */ trans_line->line_no = i; /* We keep the transactions sorted by date. We start at the end * of the list and go backward, simply because the file itself * is probably also sorted by date (but we need to handle the * exception anyway). */ /* If we can just put it at the end, do so and increment last_transaction. */ if (last_transaction == NULL || xaccTransGetDate (((GncCsvTransLine*)(last_transaction->data))->trans) <= xaccTransGetDate (trans_line->trans)) { parse_data->transactions = g_list_append (parse_data->transactions, trans_line); /* If this is the first transaction, we need to get last_transaction on track. */ if (last_transaction == NULL) last_transaction = parse_data->transactions; else /* Otherwise, we can just continue. */ last_transaction = g_list_next (last_transaction); } /* Otherwise, search backward for the correct spot. */ else { GList* insertion_spot = last_transaction; while (insertion_spot != NULL && xaccTransGetDate (((GncCsvTransLine*)(insertion_spot->data))->trans) > xaccTransGetDate (trans_line->trans)) { insertion_spot = g_list_previous (insertion_spot); } /* Move insertion_spot one location forward since we have to * use the g_list_insert_before function. */ if (insertion_spot == NULL) /* We need to handle the case of inserting at the beginning of the list. */ insertion_spot = parse_data->transactions; else insertion_spot = g_list_next (insertion_spot); parse_data->transactions = g_list_insert_before (parse_data->transactions, insertion_spot, trans_line); } } /* Increment to the next row. */ if (redo_errors) { /* Move to the next error line in the list. */ error_lines = g_list_next (error_lines); if (error_lines == NULL) i = parse_data->orig_lines->len; /* Don't continue the for loop. */ else i = GPOINTER_TO_INT(error_lines->data); } else { if (parse_data->skip_rows == FALSE) i++; else i = i + 2; } } /* If we have a balance column, set the appropriate amounts on the transactions. */ hasBalanceColumn = FALSE; for (i = 0; i < parse_data->column_types->len; i++) { if (parse_data->column_types->data[i] == GNC_CSV_BALANCE) { hasBalanceColumn = TRUE; break; } } if (hasBalanceColumn) { GList* transactions = parse_data->transactions; /* balance_offset is how much the balance currently in the account * differs from what it will be after the transactions are * imported. This will be sum of all the previous transactions for * any given transaction. */ gnc_numeric balance_offset = double_to_gnc_numeric (0.0, xaccAccountGetCommoditySCU (account), GNC_HOW_RND_ROUND_HALF_UP); while (transactions != NULL) { GncCsvTransLine* trans_line = (GncCsvTransLine*)transactions->data; if (trans_line->balance_set) { time64 date = xaccTransGetDate (trans_line->trans); /* Find what the balance should be by adding the offset to the actual balance. */ gnc_numeric existing_balance = gnc_numeric_add (balance_offset, xaccAccountGetBalanceAsOfDate (account, date), xaccAccountGetCommoditySCU (account), GNC_HOW_RND_ROUND_HALF_UP); /* The amount of the transaction is the difference between the new and existing balance. */ gnc_numeric amount = gnc_numeric_sub (trans_line->balance, existing_balance, xaccAccountGetCommoditySCU (account), GNC_HOW_RND_ROUND_HALF_UP); SplitList* splits = xaccTransGetSplitList (trans_line->trans); while (splits) { SplitList* next_splits = g_list_next (splits); xaccSplitDestroy ((Split*)splits->data); splits = next_splits; } trans_add_split (trans_line->trans, account, gnc_account_get_book (account), amount, trans_line->num); if (trans_line->num) g_free (trans_line->num); /* This new transaction needs to be added to the balance offset. */ balance_offset = gnc_numeric_add (balance_offset, amount, xaccAccountGetCommoditySCU (account), GNC_HOW_RND_ROUND_HALF_UP); } transactions = g_list_next (transactions); } } if (redo_errors) /* Now that we're at the end, we do the freeing. */ { g_list_free (begin_error_lines); } /* We need to resize parse_data->column_types since errors may have added columns. */ for (i = 0; i < parse_data->orig_lines->len; i++) { if (max_cols < ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len) max_cols = ((GPtrArray*)(parse_data->orig_lines->pdata[i]))->len; } i = parse_data->column_types->len; parse_data->column_types = g_array_set_size (parse_data->column_types, max_cols); for (; i < max_cols; i++) { parse_data->column_types->data[i] = GNC_CSV_NONE; } return 0; }
GNCLot * gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn, Account *posted_acc, Account *xfer_acc, gnc_numeric amount, gnc_numeric exch, Timespec date, const char *memo, const char *num) { QofBook *book; Split *split; const char *name; gnc_commodity *commodity; Split *xfer_split = NULL; GNCLot *payment_lot; /* Verify our arguments */ if (!owner || !posted_acc || !xfer_acc) return NULL; g_return_val_if_fail (owner->owner.undefined != NULL, NULL); /* Compute the ancillary data */ book = gnc_account_get_book (posted_acc); name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner)); commodity = gncOwnerGetCurrency (owner); // reverse = use_reversed_payment_amounts(owner); if (txn) { /* Pre-existing transaction was specified. We completely clear it, * except for the split in the transfer account, unless the * transaction can't be reused (wrong currency, wrong transfer account). * In that case, the transaction is simply removed and an new * one created. */ xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc); if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner)) { g_message("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction."); xfer_split = NULL; } if (!xfer_split) { g_message("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one."); xaccTransBeginEdit (txn); xaccTransDestroy (txn); xaccTransCommitEdit (txn); txn = NULL; } else { int i = 0; xaccTransBeginEdit (txn); while (i < xaccTransCountSplits(txn)) { Split *split = xaccTransGetSplit (txn, i); if (split == xfer_split) { gnc_set_num_action (NULL, split, num, _("Payment")); ++i; } else { xaccSplitDestroy(split); } } /* Note: don't commit transaction now - that would insert an imbalance split.*/ } } /* Create the transaction if we don't have one yet */ if (!txn) { txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); } /* Insert a split for the transfer account if we don't have one yet */ if (!xfer_split) { /* Set up the transaction */ xaccTransSetDescription (txn, name ? name : ""); /* set per book option */ xaccTransSetCurrency (txn, commodity); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &date); /* The split for the transfer account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (xfer_acc); xaccAccountInsertSplit (xfer_acc, split); xaccAccountCommitEdit (xfer_acc); xaccTransAppendSplit (txn, split); if (gnc_commodity_equal(xaccAccountGetCommodity(xfer_acc), commodity)) { xaccSplitSetBaseValue (split, amount, commodity); } else { /* Need to value the payment in terms of the owner commodity */ gnc_numeric payment_value = gnc_numeric_mul(amount, exch, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount(split, amount); xaccSplitSetValue(split, payment_value); } } /* Add a split in the post account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (posted_acc); xaccAccountInsertSplit (posted_acc, split); xaccAccountCommitEdit (posted_acc); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (amount), commodity); /* Create a new lot for the payment */ payment_lot = gnc_lot_new (book); gncOwnerAttachToLot (owner, payment_lot); gnc_lot_add_split (payment_lot, split); /* Mark the transaction as a payment */ gnc_set_num_action (txn, NULL, num, _("Payment")); xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT); /* Commit this new transaction */ xaccTransCommitEdit (txn); return payment_lot; }
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... }