X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fhomebank;a=blobdiff_plain;f=src%2Fhb-transaction.c;h=29812d9ce5e1050640b55f05998c512d66a95c0a;hp=3a5f6cfa7676fa649827765995f74e6c0d829a1e;hb=cd13d9691c46c2b2d6d459e9e6a76bed1c21b7a6;hpb=27f6e3b112df235c8e9afc9911b1f6bce208a001 diff --git a/src/hb-transaction.c b/src/hb-transaction.c index 3a5f6cf..29812d9 100644 --- a/src/hb-transaction.c +++ b/src/hb-transaction.c @@ -1,5 +1,5 @@ /* HomeBank -- Free, easy, personal accounting for everyone. - * Copyright (C) 1995-2014 Maxime DOYEN + * Copyright (C) 1995-2018 Maxime DOYEN * * This file is part of HomeBank. * @@ -21,9 +21,10 @@ #include "hb-transaction.h" #include "hb-tag.h" +#include "hb-split.h" /****************************************************************************/ -/* Debug macros */ +/* Debug macros */ /****************************************************************************/ #define MYDEBUG 0 @@ -38,131 +39,6 @@ extern struct HomeBank *GLOBALS; extern struct Preferences *PREFS; -/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ - - -static void da_split_free(Split *item) -{ - if(item != NULL) - { - if(item->memo != NULL) - g_free(item->memo); - - g_free(item); - } -} - - -static Split *da_split_malloc(void) -{ - return g_malloc0(sizeof(Split)); -} - - -Split *da_split_new(guint32 kcat, gdouble amount, gchar *memo) -{ -Split *split = da_split_malloc(); - - split->kcat = kcat; - split->amount = amount; - split->memo = g_strdup(memo); - return split; -} - - - -static Split *da_split_clone(Split *src_split) -{ -Split *new_split = g_memdup(src_split, sizeof(Split)); - - DB( g_print("da_split_clone\n") ); - - if(new_split) - { - //duplicate the string - new_split->memo = g_strdup(src_split->memo); - DB( g_print(" clone %p -> %p\n", src_split, new_split) ); - - } - return new_split; -} - -/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ - - -guint da_transaction_splits_count(Transaction *txn) -{ -guint i, count = 0; - - for(i=0;isplits[i] == NULL) - break; - count++; - } - return count; -} - - -void da_transaction_splits_free(Transaction *txn) -{ -guint count, i=0; - - count = da_transaction_splits_count(txn); - if(count == 0) - return; - - DB( g_print("da_transaction_splits_free\n") ); - - for(;i<=count;i++) - { - DB( g_print("- freeing %d :: %p\n", i, txn->splits[i]) ); - - da_split_free(txn->splits[i]); - txn->splits[i] = NULL; - } - //remove the flag - txn->flags &= ~(OF_SPLIT); - -} - - -void da_transaction_splits_append(Transaction *txn, Split *split) -{ -guint count = da_transaction_splits_count(txn); - - DB( g_print("da_transaction_splits_append\n") ); - - DB( g_print("- split[%d] at %p for ope %p\n", count, split, txn) ); - - txn->flags |= OF_SPLIT; - txn->splits[count] = split; - txn->splits[count + 1] = NULL; - - DB( g_print("- %d splits\n", da_transaction_splits_count(txn)) ); -} - - -void da_transaction_splits_clone(Transaction *stxn, Transaction *dtxn) -{ -gint i, count; - - DB( g_print("da_transaction_splits_clone\n") ); - - count = da_transaction_splits_count(stxn); - for(i=0;isplits[i] = da_split_clone(stxn->splits[i]); - } - - if(count > 0) - dtxn->flags |= OF_SPLIT; - - DB( g_print(" clone %p -> %p, %d splits\n", stxn, dtxn, count) ); -} - - - /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ void @@ -170,10 +46,10 @@ da_transaction_clean(Transaction *item) { if(item != NULL) { - if(item->wording != NULL) + if(item->memo != NULL) { - g_free(item->wording); - item->wording = NULL; + g_free(item->memo); + item->memo = NULL; } if(item->info != NULL) { @@ -187,7 +63,8 @@ da_transaction_clean(Transaction *item) item->tags = NULL; } - da_transaction_splits_free(item); + da_splits_free(item->splits); + item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared if(item->same != NULL) { @@ -198,7 +75,6 @@ da_transaction_clean(Transaction *item) } - void da_transaction_free(Transaction *item) { @@ -219,8 +95,6 @@ da_transaction_malloc(void) Transaction *da_transaction_copy(Transaction *src_txn, Transaction *dst_txn) { -guint count; - DB( g_print("da_transaction_copy\n") ); da_transaction_clean (dst_txn); @@ -228,16 +102,14 @@ guint count; memmove(dst_txn, src_txn, sizeof(Transaction)); //duplicate the string - dst_txn->wording = g_strdup(src_txn->wording); + dst_txn->memo = g_strdup(src_txn->memo); dst_txn->info = g_strdup(src_txn->info); //duplicate tags - dst_txn->tags = NULL; - count = transaction_tags_count(src_txn); - if(count > 0) - dst_txn->tags = g_memdup(src_txn->tags, count*sizeof(guint32)); + transaction_tags_clone(src_txn, dst_txn); - da_transaction_splits_clone(src_txn, dst_txn); + if (da_splits_clone(src_txn->splits, dst_txn->splits) > 0) + dst_txn->flags |= OF_SPLIT; //Flag that Splits are active return dst_txn; } @@ -246,15 +118,20 @@ guint count; Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc) { //txn->date = 0; - txn->amount = arc->amount; - txn->kacc = arc->kacc; + txn->amount = arc->amount; + //#1258344 keep the current account if tpl is empty + if(arc->kacc) + txn->kacc = arc->kacc; txn->paymode = arc->paymode; txn->flags = arc->flags | OF_ADDED; + txn->status = arc->status; txn->kpay = arc->kpay; txn->kcat = arc->kcat; txn->kxferacc = arc->kxferacc; - txn->wording = g_strdup(arc->wording); + txn->memo = g_strdup(arc->memo); txn->info = NULL; + if( da_splits_clone(arc->splits, txn->splits) > 0) + txn->flags |= OF_SPLIT; //Flag that Splits are active return txn; } @@ -263,28 +140,26 @@ Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc) Transaction *da_transaction_clone(Transaction *src_item) { Transaction *new_item = g_memdup(src_item, sizeof(Transaction)); -guint count; DB( g_print("da_transaction_clone\n") ); if(new_item) { //duplicate the string - new_item->wording = g_strdup(src_item->wording); + new_item->memo = g_strdup(src_item->memo); new_item->info = g_strdup(src_item->info); //duplicate tags - new_item->tags = NULL; - count = transaction_tags_count(src_item); - if(count > 0) - new_item->tags = g_memdup(src_item->tags, count*sizeof(guint32)); - - da_transaction_splits_clone(src_item, new_item); + transaction_tags_clone(src_item, new_item); + + if( da_splits_clone(src_item->splits, new_item->splits) > 0) + new_item->flags |= OF_SPLIT; //Flag that Splits are active } return new_item; } + GList * da_transaction_new(void) { @@ -292,17 +167,58 @@ da_transaction_new(void) } -void da_transaction_destroy(GList *list) +guint +da_transaction_length(void) +{ +GList *lst_acc, *lnk_acc; +guint count = 0; + + lst_acc = g_hash_table_get_values(GLOBALS->h_acc); + lnk_acc = g_list_first(lst_acc); + while (lnk_acc != NULL) + { + Account *acc = lnk_acc->data; + + count += g_queue_get_length (acc->txn_queue); + lnk_acc = g_list_next(lnk_acc); + } + g_list_free(lst_acc); + return count; +} + + +static void da_transaction_queue_free_ghfunc(Transaction *item, gpointer data) +{ + da_transaction_free (item); +} + + +void da_transaction_destroy(void) { -GList *tmplist = g_list_first(list); +GList *lacc, *list; - while (tmplist != NULL) + lacc = g_hash_table_get_values(GLOBALS->h_acc); + list = g_list_first(lacc); + while (list != NULL) { - Transaction *item = tmplist->data; - da_transaction_free(item); - tmplist = g_list_next(tmplist); + Account *acc = list->data; + + g_queue_foreach(acc->txn_queue, (GFunc)da_transaction_queue_free_ghfunc, NULL); + list = g_list_next(list); } - g_list_free(list); + g_list_free(lacc); +} + + +static gint da_transaction_compare_datafunc(Transaction *a, Transaction *b, gpointer data) +{ + return ((gint)a->date - b->date); +} + + +void da_transaction_queue_sort(GQueue *queue) +{ + g_queue_sort(queue, (GCompareDataFunc)da_transaction_compare_datafunc, NULL); } @@ -318,37 +234,44 @@ GList *da_transaction_sort(GList *list) } -static void da_transaction_insert_memo(Transaction *item) +gboolean da_transaction_insert_memo(Transaction *item) { +gboolean retval = FALSE; + // append the memo if new - if( item->wording != NULL ) + if( item->memo != NULL ) { - if( g_hash_table_lookup(GLOBALS->h_memo, item->wording) == NULL ) + if( g_hash_table_lookup(GLOBALS->h_memo, item->memo) == NULL ) { - g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->wording), NULL); + retval = g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->memo), NULL); } } + return retval; } - gboolean da_transaction_insert_sorted(Transaction *newitem) { -GList *tmplist = g_list_first(GLOBALS->ope_list); +Account *acc; +GList *lnk_txn; - // find the breaking date - while (tmplist != NULL) + acc = da_acc_get(newitem->kacc); + if(!acc) + return FALSE; + + lnk_txn = g_queue_peek_tail_link(acc->txn_queue); + while (lnk_txn != NULL) { - Transaction *item = tmplist->data; + Transaction *item = lnk_txn->data; - if(item->date > newitem->date) + if(item->date <= newitem->date) break; - - tmplist = g_list_next(tmplist); + + lnk_txn = g_list_previous(lnk_txn); } - // here we're at the insert point, let's insert our new txn just before - GLOBALS->ope_list = g_list_insert_before(GLOBALS->ope_list, tmplist, newitem); + // we're at insert point, insert after txn + g_queue_insert_after(acc->txn_queue, lnk_txn, newitem); da_transaction_insert_memo(newitem); return TRUE; @@ -358,7 +281,15 @@ GList *tmplist = g_list_first(GLOBALS->ope_list); // nota: this is called only when loading xml file gboolean da_transaction_prepend(Transaction *item) { - GLOBALS->ope_list = g_list_prepend(GLOBALS->ope_list, item); +Account *acc; + + acc = da_acc_get(item->kacc); + //#1661279 + if(!acc) + return FALSE; + + item->kcur = acc->kcur; + g_queue_push_tail(acc->txn_queue, item); da_transaction_insert_memo(item); return TRUE; } @@ -366,37 +297,52 @@ gboolean da_transaction_prepend(Transaction *item) /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ -guint32 +static guint32 da_transaction_get_max_kxfer(void) { -guint32 max_key = 0; +GList *lst_acc, *lnk_acc; GList *list; -Transaction *item; +guint32 max_key = 0; DB( g_print("da_transaction_get_max_kxfer\n") ); - list = g_list_first(GLOBALS->ope_list); - while (list != NULL) + lst_acc = g_hash_table_get_values(GLOBALS->h_acc); + lnk_acc = g_list_first(lst_acc); + while (lnk_acc != NULL) { - item = list->data; - if( item->paymode == PAYMODE_INTXFER) + Account *acc = lnk_acc->data; + + list = g_queue_peek_head_link(acc->txn_queue); + while (list != NULL) { - if( item->kxfer > max_key) - max_key = item->kxfer; + Transaction *item = list->data; + + if( item->paymode == PAYMODE_INTXFER ) + { + max_key = MAX(max_key, item->kxfer); + } + list = g_list_next(list); } - list = g_list_next(list); + + lnk_acc = g_list_next(lnk_acc); } + g_list_free(lst_acc); DB( g_print(" max_key : %d \n", max_key) ); - return max_key; } + static void da_transaction_goto_orphan(Transaction *txn) { const gchar *oatn = "orphaned transactions"; -Account *acc; +Account *ori_acc, *acc; +gboolean found; + + DB( g_print("\n[transaction] goto orphan\n") ); + + g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn->date, txn->memo, txn->amount); acc = da_acc_get_by_name((gchar *)oatn); if(acc == NULL) @@ -404,8 +350,21 @@ Account *acc; acc = da_acc_malloc(); acc->name = g_strdup(oatn); da_acc_append(acc); + DB( g_print(" - created orphan acc %d\n", acc->key) ); + } + + ori_acc = da_acc_get(txn->kacc); + if( ori_acc ) + { + found = g_queue_remove(ori_acc->txn_queue, txn); + DB( g_print(" - found in origin ? %d\n", found) ); + if(found) + { + txn->kacc = acc->key; + da_transaction_insert_sorted (txn); + DB( g_print("moved txn to %d\n", txn->kacc) ); + } } - txn->kacc = acc->key; } @@ -414,7 +373,12 @@ void da_transaction_consistency(Transaction *item) Account *acc; Category *cat; Payee *pay; -guint i, nbsplit; +gint nbsplit; + + DB( g_print("\n[transaction] consistency\n") ); + + // ensure date is between range + item->date = CLAMP(item->date, HB_MINDATE, HB_MAXDATE); // check account exists acc = da_acc_get(item->kacc); @@ -422,6 +386,7 @@ guint i, nbsplit; { g_warning("txn consistency: fixed invalid acc %d", item->kacc); da_transaction_goto_orphan(item); + GLOBALS->changes_count++; } // check category exists @@ -430,19 +395,19 @@ guint i, nbsplit; { g_warning("txn consistency: fixed invalid cat %d", item->kcat); item->kcat = 0; + GLOBALS->changes_count++; } // check split category #1340142 - nbsplit = da_transaction_splits_count(item); - for(i=0;isplits); + + //# 1416624 empty category when split + nbsplit = da_splits_count(item->splits); + if(nbsplit > 0 && item->kcat > 0) { - Split *split = item->splits[i]; - cat = da_cat_get(split->kcat); - if(cat == NULL) - { - g_warning("txn consistency: fixed invalid split cat %d", split->kcat); - split->kcat = 0; - } + g_warning("txn consistency: fixed invalid cat on split txn"); + item->kcat = 0; + GLOBALS->changes_count++; } // check payee exists @@ -451,11 +416,35 @@ guint i, nbsplit; { g_warning("txn consistency: fixed invalid pay %d", item->kpay); item->kpay = 0; + GLOBALS->changes_count++; } // reset dst acc for non xfer transaction if( item->paymode != PAYMODE_INTXFER ) + { + item->kxfer = 0; item->kxferacc = 0; + } + + // check dst account exists + if( item->paymode == PAYMODE_INTXFER ) + { + gint tak = item->kxferacc; + + item->kxferacc = ABS(tak); //I crossed negative here one day + acc = da_acc_get(item->kxferacc); + if(acc == NULL) + { + g_warning("txn consistency: fixed invalid dst_acc %d", item->kxferacc); + da_transaction_goto_orphan(item); + item->kxfer = 0; + item->paymode = PAYMODE_XFER; + GLOBALS->changes_count++; + } + } + + //#1628678 tags for internal xfer should be checked as well + //#1295877 ensure income flag is correctly set item->flags &= ~(OF_INCOME); @@ -463,8 +452,9 @@ guint i, nbsplit; item->flags |= (OF_INCOME); //#1308745 ensure remind flag unset if reconciled - if( item->flags & OF_VALID ) - item->flags &= ~(OF_REMIND); + //useless since 5.0 + //if( item->flags & OF_VALID ) + // item->flags &= ~(OF_REMIND); } @@ -472,82 +462,154 @@ guint i, nbsplit; /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /* new transfer functions */ -Transaction *transaction_strong_get_child_transfer(Transaction *src) +static void transaction_xfer_create_child(Transaction *ope) { -GList *list; - - DB( g_print("\n[transaction] transaction_strong_get_child_transfer\n") ); +Transaction *child; +Account *acc; +gchar swap; - DB( g_print(" - search: %d %s %f %d=>%d\n", src->date, src->wording, src->amount, src->kacc, src->kxferacc) ); + DB( g_print("\n[transaction] xfer_create_child\n") ); - list = g_list_first(GLOBALS->ope_list); - while (list != NULL) + if( ope->kxferacc > 0 ) { - Transaction *item = list->data; - //#1252230 - //if( item->paymode == PAYMODE_INTXFER && item->kacc == src->kxferacc && item->kxfer == src->kxfer ) - if( item->paymode == PAYMODE_INTXFER && item->kxfer == src->kxfer && item != src ) + child = da_transaction_clone(ope); + + ope->flags |= OF_CHANGED; + child->flags |= OF_ADDED; + + child->amount = -child->amount; + child->flags ^= (OF_INCOME); // invert flag + //#1268026 #1690555 + if( child->status != TXN_STATUS_REMIND ) + child->status = TXN_STATUS_NONE; + //child->flags &= ~(OF_VALID); // delete reconcile state + + swap = child->kacc; + child->kacc = child->kxferacc; + child->kxferacc = swap; + + /* update acc flags */ + acc = da_acc_get( child->kacc ); + if(acc != NULL) { - DB( g_print(" - found : %d %s %f %d=>%d\n", item->date, item->wording, item->amount, item->kacc, item->kxferacc) ); - return item; + acc->flags |= AF_ADDED; + + //strong link + guint maxkey = da_transaction_get_max_kxfer(); + + DB( g_print(" + maxkey is %d\n", maxkey) ); + + + ope->kxfer = maxkey+1; + child->kxfer = maxkey+1; + + DB( g_print(" + strong link to %d\n", ope->kxfer) ); + + + DB( g_print(" + add transfer, %p to acc %d\n", child, acc->key) ); + + da_transaction_insert_sorted(child); + + account_balances_add (child); + } - list = g_list_next(list); } - DB( g_print(" - not found...\n") ); - return NULL; + } -/* - * this function retrieve into a glist the potential child transfer - * for the source transaction - */ -GList *transaction_match_get_child_transfer(Transaction *src) +//todo: add strong control and extend to payee, maybe memo ? +static gboolean transaction_xfer_child_might(Transaction *stxn, Transaction *dtxn, gint daygap) { -GList *list; -GList *match = NULL; +gboolean retval = FALSE; - DB( g_print("\n[transaction] transaction_match_get_child_transfer\n") ); + //DB( g_print("\n[transaction] xfer_child_might\n") ); - //DB( g_print(" - search : %d %s %f %d=>%d\n", src->date, src->wording, src->amount, src->account, src->kxferacc) ); + if(stxn == dtxn) + return FALSE; - list = g_list_first(GLOBALS->ope_list); - while (list != NULL) + /*g_print("test\n"); + + g_print(" %d %d %d %f %d\n", + stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer ); + + + g_print(" %d %d %d %f %d\n", + dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer ); + */ + + if( stxn->kcur == dtxn->kcur && + stxn->date == dtxn->date && + //v5.1 make no sense: stxn->kxferacc == dtxn->kacc && + stxn->kacc != dtxn->kacc && + ABS(stxn->amount) == ABS(dtxn->amount) && + dtxn->kxfer == 0) { - Transaction *item = list->data; - if( src->date == item->date && - src->kxferacc == item->kacc && - ABS(src->amount) == ABS(item->amount) && - item->kxfer == 0) - { - //DB( g_print(" - match : %d %s %f %d=>%d\n", item->date, item->wording, item->amount, item->account, item->kxferacc) ); + retval = TRUE; + } + + //g_print(" return %d\n", retval); + return retval; +} - match = g_list_append(match, item); + +static GList *transaction_xfer_child_might_list_get(Transaction *ope) +{ +GList *lst_acc, *lnk_acc; +GList *list, *matchlist = NULL; + + //DB( g_print("\n[transaction] xfer_child_might_list_get\n") ); + + lst_acc = g_hash_table_get_values(GLOBALS->h_acc); + lnk_acc = g_list_first(lst_acc); + while (lnk_acc != NULL) + { + Account *acc = lnk_acc->data; + + if( !(acc->flags & AF_CLOSED) && (acc->key != ope->kacc) ) + { + list = g_queue_peek_tail_link(acc->txn_queue); + while (list != NULL) + { + Transaction *item = list->data; + + // no need to go higher than src txn date + if(item->date < ope->date) + break; + + if( transaction_xfer_child_might(ope, item, 0) == TRUE ) + { + //DB( g_print(" - match : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) ); + matchlist = g_list_append(matchlist, item); + } + list = g_list_previous(list); + } } - list = g_list_next(list); + + lnk_acc = g_list_next(lnk_acc); } + g_list_free(lst_acc); - DB( g_print(" - found : %d\n", g_list_length(match)) ); - - return match; + return matchlist; } -void transaction_xfer_search_or_add_child(Transaction *ope, GtkWidget *treeview) +void transaction_xfer_search_or_add_child(GtkWindow *parentwindow, Transaction *ope, gboolean manual) { -GList *matchlist = transaction_match_get_child_transfer(ope); +GList *matchlist; +gint count; -guint count = g_list_length(matchlist); + DB( g_print("\n[transaction] xfer_search_or_add_child\n") ); + matchlist = transaction_xfer_child_might_list_get(ope); + count = g_list_length(matchlist); - DB( g_print("\n[transaction] transaction_xfer_search_or_add_child\n") ); - - DB( g_print(" - found result is %d, switching\n", count) ); + DB( g_print(" - found %d might match, switching\n", count) ); switch(count) { case 0: //we should create the child - transaction_xfer_create_child(ope, treeview); + transaction_xfer_create_child(ope); break; //todo: maybe with just 1 match the user must choose ? @@ -565,84 +627,68 @@ guint count = g_list_length(matchlist); { Transaction *child; - child = ui_dialog_transaction_xfer_select_child(matchlist); + child = ui_dialog_transaction_xfer_select_child(ope, matchlist); if(child == NULL) - transaction_xfer_create_child(ope, treeview); + transaction_xfer_create_child(ope); else transaction_xfer_change_to_child(ope, child); - break; } } g_list_free(matchlist); - } - - - - -void transaction_xfer_create_child(Transaction *ope, GtkWidget *treeview) +Transaction *transaction_xfer_child_strong_get(Transaction *src) { -Transaction *child; -Account *acc; -gchar swap; +Account *dstacc; +GList *list; - DB( g_print("\n[transaction] transaction_xfer_create_child\n") ); + DB( g_print("\n[transaction] xfer_child_strong_get\n") ); - if( ope->kxferacc > 0 ) - { - child = da_transaction_clone(ope); + dstacc = da_acc_get(src->kxferacc); + if( !dstacc || src->kxfer <= 0 ) + return NULL; - child->amount = -child->amount; - child->flags ^= (OF_INCOME); // invert flag - child->flags &= ~(OF_REMIND); // remove flag - //#1268026 - child->flags &= ~(OF_VALID); // remove reconcile state - + DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) ); - swap = child->kacc; - child->kacc = child->kxferacc; - child->kxferacc = swap; + list = g_queue_peek_tail_link(dstacc->txn_queue); + while (list != NULL) + { + Transaction *item = list->data; - /* update acc flags */ - acc = da_acc_get( child->kacc ); - if(acc != NULL) + //#1252230 + //if( item->paymode == PAYMODE_INTXFER + // && item->kacc == src->kxferacc + // && item->kxfer == src->kxfer ) + if( item->paymode == PAYMODE_INTXFER + && item->kxfer == src->kxfer + && item != src ) { - acc->flags |= AF_ADDED; - - //strong link - guint maxkey = da_transaction_get_max_kxfer(); - - DB( g_print(" + maxkey is %d\n", maxkey) ); - - - ope->kxfer = maxkey+1; - child->kxfer = maxkey+1; - - DB( g_print(" + strong link to %d\n", ope->kxfer) ); - - - DB( g_print(" + add transfer, %p\n", child) ); - - da_transaction_insert_sorted(child); - - account_balances_add (child); - - if(treeview != NULL) - transaction_add_treeview(child, treeview, ope->kacc); + DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item->date, item->memo, item->amount, item->kacc, item->kxferacc, src->kxfer) ); + return item; } + list = g_list_previous(list); } - + + DB( g_print(" - not found...\n") ); + return NULL; } + + void transaction_xfer_change_to_child(Transaction *ope, Transaction *child) { -Account *acc; +Account *dstacc; - DB( g_print("\n[transaction] transaction_xfer_change_to_child\n") ); + DB( g_print("\n[transaction] xfer_change_to_child\n") ); + + if(ope->kcur != child->kcur) + return; + + ope->flags |= OF_CHANGED; + child->flags |= OF_CHANGED; child->paymode = PAYMODE_INTXFER; @@ -650,9 +696,9 @@ Account *acc; child->kxferacc = ope->kacc; /* update acc flags */ - acc = da_acc_get( child->kacc); - if(acc != NULL) - acc->flags |= AF_CHANGED; + dstacc = da_acc_get( child->kacc); + if(dstacc != NULL) + dstacc->flags |= AF_CHANGED; //strong link guint maxkey = da_transaction_get_max_kxfer(); @@ -662,10 +708,24 @@ Account *acc; } -void transaction_xfer_sync_child(Transaction *s_txn, Transaction *child) +void transaction_xfer_child_sync(Transaction *s_txn, Transaction *child) { +Account *acc; - DB( g_print("\n[transaction] transaction_xfer_sync_child\n") ); + DB( g_print("\n[transaction] xfer_child_sync\n") ); + + if( child == NULL ) + { + DB( g_print(" - no child found\n") ); + return; + } + + DB( g_print(" - found do sync\n") ); + + /* update acc flags */ + acc = da_acc_get( child->kacc); + if(acc != NULL) + acc->flags |= AF_CHANGED; account_balances_sub (child); @@ -678,71 +738,103 @@ void transaction_xfer_sync_child(Transaction *s_txn, Transaction *child) child->flags |= (OF_INCOME); child->kpay = s_txn->kpay; child->kcat = s_txn->kcat; - if(child->wording) - g_free(child->wording); - child->wording = g_strdup(s_txn->wording); + if(child->memo) + g_free(child->memo); + child->memo = g_strdup(s_txn->memo); if(child->info) g_free(child->info); child->info = g_strdup(s_txn->info); + account_balances_add (child); + //#1252230 sync account also - child->kacc = s_txn->kxferacc; - child->kxferacc = s_txn->kacc; + //#1663789 idem after 5.1 + //source changed: update child key (move of s_txn is done in external_edit) + if( s_txn->kacc != child->kxferacc ) + { + child->kxferacc = s_txn->kacc; + } - account_balances_add (child); - - //todo: synchronise tags here also ? + //dest changed: move child & update child key + if( s_txn->kxferacc != child->kacc ) + { + transaction_acc_move(child, child->kacc, s_txn->kxferacc); + } + + //synchronise tags since 5.1 + if(child->tags) + g_free(child->tags); + transaction_tags_clone (s_txn, child); } -void transaction_xfer_delete_child(Transaction *src) +void transaction_xfer_remove_child(Transaction *src) { Transaction *dst; - DB( g_print("\n[transaction] transaction_xfer_delete_child\n") ); - - dst = transaction_strong_get_child_transfer( src ); - - DB( g_print(" -> return is %s, %p\n", dst->wording, dst) ); + DB( g_print("\n[transaction] xfer_remove_child\n") ); + dst = transaction_xfer_child_strong_get( src ); if( dst != NULL ) { - DB( g_print("deleting...") ); - src->kxfer = 0; - src->kxferacc = 0; - account_balances_sub(dst); - GLOBALS->ope_list = g_list_remove(GLOBALS->ope_list, dst); + Account *acc = da_acc_get(dst->kacc); + + if( acc != NULL ) + { + DB( g_print("deleting...") ); + account_balances_sub(dst); + g_queue_remove(acc->txn_queue, dst); + //#1419304 we keep the deleted txn to a trash stack + //da_transaction_free (dst); + g_trash_stack_push(&GLOBALS->txn_stk, dst); + + //#1691992 + acc->flags |= AF_CHANGED; + } } + + src->kxfer = 0; + src->kxferacc = 0; } +// still useful for upgrade from < file v0.6 (hb v4.4 kxfer) Transaction *transaction_old_get_child_transfer(Transaction *src) { +Account *acc; GList *list; -Transaction *item; - DB( g_print("\n[transaction] transaction_get_child_transfer\n") ); + DB( g_print("\n[transaction] get_child_transfer\n") ); - //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->wording, src->amount, src->account, src->kxferacc) ); + //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) ); + acc = da_acc_get(src->kxferacc); - list = g_list_first(GLOBALS->ope_list); - while (list != NULL) + if( acc != NULL ) { - item = list->data; - if( item->paymode == PAYMODE_INTXFER) + list = g_queue_peek_head_link(acc->txn_queue); + while (list != NULL) { - if( src->date == item->date && - src->kacc == item->kxferacc && - src->kxferacc == item->kacc && - ABS(src->amount) == ABS(item->amount) ) + Transaction *item = list->data; + + // no need to go higher than src txn date + if(item->date > src->date) + break; + + if( item->paymode == PAYMODE_INTXFER) { - //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->wording, item->amount, item->account, item->kxferacc) ); + if( src->date == item->date && + src->kacc == item->kxferacc && + src->kxferacc == item->kacc && + ABS(src->amount) == ABS(item->amount) ) + { + //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) ); - return item; + return item; + } } + list = g_list_next(list); } - list = g_list_next(list); } DB( g_print(" not found...\n") ); @@ -751,31 +843,58 @@ Transaction *item; } +/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ -/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ +void transaction_remove(Transaction *ope) +{ +Account *acc; + + //controls accounts valid (archive scheduled maybe bad) + acc = da_acc_get(ope->kacc); + if(acc == NULL) return; + + account_balances_sub(ope); + + if( ope->paymode == PAYMODE_INTXFER ) + { + transaction_xfer_remove_child( ope ); + } + + g_queue_remove(acc->txn_queue, ope); + acc->flags |= AF_CHANGED; + //#1419304 we keep the deleted txn to a trash stack + //da_transaction_free(entry); + g_trash_stack_push(&GLOBALS->txn_stk, ope); +} -void transaction_add(Transaction *ope, GtkWidget *treeview, guint32 accnum) +Transaction *transaction_add(Transaction *ope) { Transaction *newope; Account *acc; - DB( g_print("\n[transaction] transaction add\n") ); + DB( g_print("\n[transaction] transaction_add\n") ); //controls accounts valid (archive scheduled maybe bad) acc = da_acc_get(ope->kacc); - if(acc == NULL) return; + if(acc == NULL) return NULL; + DB( g_print(" acc is '%s' %d\n", acc->name, acc->key) ); + + ope->kcur = acc->kcur; + if(ope->paymode == PAYMODE_INTXFER) { acc = da_acc_get(ope->kxferacc); - if(acc == NULL) return; + if(acc == NULL) return NULL; - // remove any splits - da_transaction_splits_free(ope); + // delete any splits + da_splits_free(ope->splits); + ope->flags &= ~(OF_SPLIT); //Flag that Splits are cleared } + //allocate a new entry and copy from our edited structure newope = da_transaction_clone(ope); @@ -795,16 +914,19 @@ Account *acc; /* get the active account and the corresponding cheque number */ acc = da_acc_get( newope->kacc); - cheque = atol(newope->info); - - //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) ); - if( newope->flags & OF_CHEQ2 ) + if( acc != NULL ) { - acc->cheque2 = MAX(acc->cheque2, cheque); - } - else - { - acc->cheque1 = MAX(acc->cheque1, cheque); + cheque = atol(newope->info); + + //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) ); + if( newope->flags & OF_CHEQ2 ) + { + acc->cheque2 = MAX(acc->cheque2, cheque); + } + else + { + acc->cheque1 = MAX(acc->cheque1, cheque); + } } } @@ -814,57 +936,59 @@ Account *acc; { acc->flags |= AF_ADDED; - DB( g_print(" + add normal %p\n", newope) ); + DB( g_print(" + add normal %p to acc %d\n", newope, acc->key) ); //da_transaction_append(newope); da_transaction_insert_sorted(newope); - if(treeview != NULL) - transaction_add_treeview(newope, treeview, accnum); - account_balances_add(newope); if(newope->paymode == PAYMODE_INTXFER) { - transaction_xfer_search_or_add_child(newope, treeview); + transaction_xfer_search_or_add_child(NULL, newope, FALSE); } } + + return newope; } - - -void transaction_add_treeview(Transaction *ope, GtkWidget *treeview, guint32 accnum) +gboolean transaction_acc_move(Transaction *txn, guint32 okacc, guint32 nkacc) { -GtkTreeModel *model; -GtkTreeIter iter; -//GtkTreePath *path; -//GtkTreeSelection *sel; - - DB( g_print("\n[transaction] transaction add treeview\n") ); +Account *oacc, *nacc; - if(ope->kacc == accnum) - { - model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); - gtk_list_store_append (GTK_LIST_STORE(model), &iter); - - gtk_list_store_set (GTK_LIST_STORE(model), &iter, - LST_DSPOPE_DATAS, ope, - -1); - - //activate that new line - //path = gtk_tree_model_get_path(model, &iter); - //gtk_tree_view_expand_to_path(GTK_TREE_VIEW(treeview), path); - - //sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); - //gtk_tree_selection_select_iter(sel, &iter); + DB( g_print("\n[transaction] acc_move\n") ); - //gtk_tree_path_free(path); + if( okacc == nkacc ) + return TRUE; + oacc = da_acc_get(okacc); + nacc = da_acc_get(nkacc); + if( oacc && nacc ) + { + account_balances_sub(txn); + if( g_queue_remove(oacc->txn_queue, txn) ) + { + g_queue_push_tail(nacc->txn_queue, txn); + txn->kacc = nacc->key; + txn->kcur = nacc->kcur; + nacc->flags |= AF_CHANGED; + account_balances_add(txn); + return TRUE; + } + else + { + //ensure to keep txn into current account + txn->kacc = okacc; + account_balances_add(txn); + } } + return FALSE; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ + + static gboolean misc_text_match(gchar *text, gchar *searchtext, gboolean exact) { gboolean match = FALSE; @@ -872,7 +996,7 @@ gboolean match = FALSE; if(text == NULL) return FALSE; - //DB( g_print("search %s in %s\n", rul->name, ope->wording) ); + //DB( g_print("search %s in %s\n", rul->name, ope->memo) ); if( searchtext != NULL ) { if( exact == TRUE ) @@ -901,12 +1025,30 @@ gboolean match = FALSE; return match; } +static gboolean misc_regex_match(gchar *text, gchar *searchtext, gboolean exact) +{ +gboolean match = FALSE; + + if(text == NULL) + return FALSE; + + DB( g_print("- match RE %s in %s\n", searchtext, text) ); + if( searchtext != NULL ) + { + match = g_regex_match_simple(searchtext, text, ((exact == TRUE)?0:G_REGEX_CASELESS) | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY ); + if (match == TRUE) { DB( g_print(" found pattern '%s'\n", searchtext) ); } + } + return match; +} + static Assign *transaction_auto_assign_eval_txn(GList *l_rul, Transaction *txn) { Assign *rule = NULL; GList *list; - + + DB( g_print("\n[transaction] auto_assign_eval_txn\n") ); + DB( g_print("- eval every rules, and return the last that match\n") ); list = g_list_first(l_rul); @@ -915,15 +1057,24 @@ GList *list; Assign *rul = list->data; gchar *text; - text = txn->wording; + text = txn->memo; if(rul->field == 1) //payee { Payee *pay = da_pay_get(txn->kpay); if(pay) text = pay->name; } - if( misc_text_match(text, rul->name, rul->flags & ASGF_EXACT)) - rule = rul; + + if( !(rul->flags & ASGF_REGEX) ) + { + if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) ) + rule = rul; + } + else + { + if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) ) + rule = rul; + } list = g_list_next(list); } @@ -936,6 +1087,8 @@ static Assign *transaction_auto_assign_eval(GList *l_rul, gchar *text) { Assign *rule = NULL; GList *list; + + DB( g_print("\n[transaction] auto_assign_eval\n") ); DB( g_print("- eval every rules, and return the last that match\n") ); @@ -946,8 +1099,16 @@ GList *list; if( rul->field == 0 ) //memo { - if( misc_text_match(text, rul->name, rul->flags & ASGF_EXACT)) - rule = rul; + if( !(rul->flags & ASGF_REGEX) ) + { + if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) ) + rule = rul; + } + else + { + if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) ) + rule = rul; + } } list = g_list_next(list); } @@ -956,81 +1117,87 @@ GList *list; } -gint transaction_auto_assign(GList *ope_list, guint32 key) +gint transaction_auto_assign(GList *ope_list, guint32 kacc) { GList *l_ope; GList *l_rul; gint changes = 0; - DB( g_print("\n[transaction] transaction_auto_assign\n") ); + DB( g_print("\n[transaction] auto_assign\n") ); - l_ope = g_list_first(ope_list); l_rul = g_hash_table_get_values(GLOBALS->h_rul); + l_ope = g_list_first(ope_list); while (l_ope != NULL) { Transaction *ope = l_ope->data; + gboolean changed = FALSE; - DB( g_print("- eval ope '%s' : acc=%d, pay=%d, cat=%d\n", ope->wording, ope->kacc, ope->kpay, ope->kcat) ); + DB( g_print("- eval ope '%s' : acc=%d, pay=%d, cat=%d\n", ope->memo, ope->kacc, ope->kpay, ope->kcat) ); - //#1215521: added key == 0 - if( (key == ope->kacc || key == 0) ) + //#1215521: added kacc == 0 + if( (kacc == ope->kacc || kacc == 0) ) { Assign *rul; - if( !(ope->flags & OF_SPLIT) && (ope->kpay == 0 || ope->kcat == 0) ) + rul = transaction_auto_assign_eval_txn(l_rul, ope); + if( rul != NULL ) { - rul = transaction_auto_assign_eval_txn(l_rul, ope); - if( rul != NULL ) + if( (ope->kpay == 0 && (rul->flags & ASGF_DOPAY)) || (rul->flags & ASGF_OVWPAY) ) { - if( ope->kpay == 0 && (rul->flags & ASGF_DOPAY) ) + if(ope->kpay != rul->kpay) { changed = TRUE; } + ope->kpay = rul->kpay; + } + + if( !(ope->flags & OF_SPLIT) ) + { + if( (ope->kcat == 0 && (rul->flags & ASGF_DOCAT)) || (rul->flags & ASGF_OVWCAT) ) { - ope->kpay = rul->kpay; - ope->flags |= OF_CHANGED; - changes++; + if(ope->kcat != rul->kcat) { changed = TRUE; } + ope->kcat = rul->kcat; } - if( ope->kcat == 0 && (rul->flags & ASGF_DOCAT) ) + } + + if( (ope->paymode == 0 && (rul->flags & ASGF_DOMOD)) || (rul->flags & ASGF_OVWMOD) ) + { + //ugly hack - don't allow modify intxfer + if(ope->paymode != PAYMODE_INTXFER && rul->paymode != PAYMODE_INTXFER) { - ope->kcat = rul->kcat; - ope->flags |= OF_CHANGED; - changes++; + if(ope->paymode != rul->paymode) { changed = TRUE; } + ope->paymode = rul->paymode; } - } + } - else if( ope->flags & OF_SPLIT ) + + if( ope->flags & OF_SPLIT ) { - guint i, nbsplit = da_transaction_splits_count(ope); - Split *split; - gboolean split_change = FALSE; + guint i, nbsplit = da_splits_count(ope->splits); for(i=0;isplits[i]; - + Split *split = ope->splits[i]; + DB( g_print("- eval split '%s'\n", split->memo) ); - if(split->kcat == 0) + rul = transaction_auto_assign_eval(l_rul, split->memo); + if( rul != NULL ) { - rul = transaction_auto_assign_eval(l_rul, split->memo); - if( rul != NULL ) + //#1501144: check if user wants to set category in rule + if( (split->kcat == 0 || (rul->flags & ASGF_OVWCAT)) && (rul->flags & ASGF_DOCAT) ) { - if( split->kcat == 0 && rul->kcat > 0 ) - { - split->kcat = rul->kcat; - ope->flags |= OF_CHANGED; - split_change = TRUE; - } + if(split->kcat != rul->kcat) { changed = TRUE; } + split->kcat = rul->kcat; } } - } + } - if(split_change == TRUE) - changes++; - + if(changed == TRUE) + { + ope->flags |= OF_CHANGED; + changes++; } - } l_ope = g_list_next(l_ope); @@ -1051,6 +1218,8 @@ transaction_tags_count(Transaction *ope) guint count = 0; guint32 *ptr = ope->tags; + //DB( g_print("\n[transaction] tags_count\n") ); + if( ope->tags == NULL ) return 0; @@ -1061,82 +1230,19 @@ guint32 *ptr = ope->tags; } - - -guint transaction_splits_parse(Transaction *ope, gchar *cats, gchar *amounts, gchar *memos) +void transaction_tags_clone(Transaction *src_txn, Transaction *dst_txn) { -gchar **cat_a, **amt_a, **mem_a; -guint count, i; -guint32 kcat; -gdouble amount; -Split *split; - - DB( g_print(" split parse %s :: %s :: %s\n", cats, amounts, memos) ); - - cat_a = g_strsplit (cats, "||", 0); - amt_a = g_strsplit (amounts, "||", 0); - mem_a = g_strsplit (memos, "||", 0); +guint count; - count = g_strv_length(amt_a); - if( (count == g_strv_length(cat_a)) && (count == g_strv_length(mem_a)) ) - { - for(i=0;iflags |= OF_SPLIT; - } - else + dst_txn->tags = NULL; + count = transaction_tags_count(src_txn); + if(count > 0) { - g_warning("invalid split parse"); + //1501962: we must also copy the final 0 + dst_txn->tags = g_memdup(src_txn->tags, (count+1)*sizeof(guint32)); } - - g_strfreev (mem_a); - g_strfreev (amt_a); - g_strfreev (cat_a); - - return count; -} - - - -guint transaction_splits_tostring(Transaction *ope, gchar **cats, gchar **amounts, gchar **memos) -{ -guint count, i; -Split *split; -char buf[G_ASCII_DTOSTR_BUF_SIZE]; -GString *cat_a = g_string_new (NULL); -GString *amt_a = g_string_new (NULL); -GString *mem_a = g_string_new (NULL); - - count = da_transaction_splits_count(ope); - for(i=0;isplits[i]; - g_string_append_printf (cat_a, "%d", split->kcat); - g_string_append(amt_a, g_ascii_dtostr (buf, sizeof (buf), split->amount) ); - g_string_append(mem_a, split->memo); - - if((i+1) < count) - { - g_string_append(cat_a, "||"); - g_string_append(amt_a, "||"); - g_string_append(mem_a, "||"); - } - } - - *cats = g_string_free(cat_a, FALSE); - *amounts = g_string_free(amt_a, FALSE); - *memos = g_string_free(mem_a, FALSE); - - return count; } - guint transaction_tags_parse(Transaction *ope, const gchar *tagstring) { @@ -1144,7 +1250,7 @@ gchar **str_array; guint count, i; Tag *tag; - DB( g_print("(transaction_set_tags)\n") ); + DB( g_print("\n[transaction] tags_parse\n") ); DB( g_print(" - tagstring='%s'\n", tagstring) ); @@ -1180,6 +1286,7 @@ Tag *tag; ope->tags[i] = tag->key; } + ope->tags[i] = 0; } //hex_dump(ope->tags, sizeof(guint32*)*count+1); @@ -1197,7 +1304,7 @@ gchar **str_array; gchar *tagstring; Tag *tag; - DB( g_print("transaction_get_tagstring\n") ); + DB( g_print("\n[transaction] tags_tostring\n") ); DB( g_print(" -> tags at=%p\n", ope->tags) );