]> Dogcows Code - chaz/homebank/blobdiff - src/hb-transaction.c
import homebank-5.2.4
[chaz/homebank] / src / hb-transaction.c
index 3a5f6cfa7676fa649827765995f74e6c0d829a1e..0368946f9fbb4267bb383d7ace7200f70f03a6a2 100644 (file)
@@ -1,5 +1,5 @@
 /*  HomeBank -- Free, easy, personal accounting for everyone.
- *  Copyright (C) 1995-2014 Maxime DOYEN
+ *  Copyright (C) 1995-2019 Maxime DOYEN
  *
  *  This file is part of HomeBank.
  *
 
 #include "hb-transaction.h"
 #include "hb-tag.h"
+#include "hb-split.h"
 
 /****************************************************************************/
-/* Debug macros                                                                                 */
+/* Debug macro                                                             */
 /****************************************************************************/
 #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;i<TXN_MAX_SPLIT;i++)
-       {
-               if(txn->splits[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;i<count;i++)
-       {
-               dtxn->splits[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,12 @@ da_transaction_clean(Transaction *item)
                        item->tags = NULL;
                }
 
-               da_transaction_splits_free(item);
+               if(item->splits != NULL)
+               {
+                       da_split_destroy(item->splits);
+                       item->splits = NULL;
+                       item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
+               }
 
                if(item->same != NULL)
                {
@@ -198,7 +79,6 @@ da_transaction_clean(Transaction *item)
 }
 
 
-
 void
 da_transaction_free(Transaction *item)
 {
@@ -217,45 +97,55 @@ 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);
-
-       memmove(dst_txn, src_txn, sizeof(Transaction));
-       
-       //duplicate the string
-       dst_txn->wording = g_strdup(src_txn->wording);
-       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));
-
-       da_transaction_splits_clone(src_txn, dst_txn);
-
-       return dst_txn;
-}
-
-
 Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc)
 {
+       DB( g_print("da_transaction_init_from_template\n") );
+
        //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;
 
+       //copy tags (with free previous here)
+       g_free(txn->tags);
+       txn->tags = tags_clone(arc->tags);
+
+       da_split_destroy (txn->splits);
+       txn->splits = da_splits_clone(arc->splits);
+       if( da_splits_length (txn->splits) > 0 )
+               txn->flags |= OF_SPLIT; //Flag that Splits are active
+
+       return txn;
+}
+
+
+Transaction *da_transaction_set_default_template(Transaction *txn)
+{
+Account *acc;
+Archive *arc;
+
+       DB( g_print("da_transaction_set_default_template\n") );
+
+       acc = da_acc_get(txn->kacc);
+       if(acc != NULL && acc->karc > 0)
+       {
+               arc = da_archive_get(acc->karc);
+               if( arc )
+               {
+                       DB( g_print(" - init with default template\n") );
+                       da_transaction_init_from_template(txn, arc);
+               }
+       }
+
        return txn;
 }
 
@@ -263,28 +153,28 @@ 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);
+               //duplicate tags/splits
+               //no g_free here to avoid free the src tags (memdup copied the ptr)
+               new_item->tags = tags_clone(src_item->tags);
+               
+               new_item->splits = da_splits_clone(src_item->splits);
+               if( da_splits_length (new_item->splits) > 0 )
+                       new_item->flags |= OF_SPLIT; //Flag that Splits are active
 
        }
        return new_item;
 }
 
+
 GList *
 da_transaction_new(void)
 {
@@ -292,17 +182,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 +249,47 @@ GList *da_transaction_sort(GList *list)
 }
 
 
-static void da_transaction_insert_memo(Transaction *item)
+gboolean da_transaction_insert_memo(Transaction *item)
 {
-       // append the memo if new
-       if( item->wording != NULL )
+gboolean retval = FALSE;
+
+       if( item->memo != NULL )
        {
-               if( g_hash_table_lookup(GLOBALS->h_memo, item->wording) == NULL )
+               //# 1673048 add filter on status and date obsolete
+               if( (PREFS->txn_memoacp == TRUE) && (item->date >= (GLOBALS->today - PREFS->txn_memoacp_days)) )
                {
-                       g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->wording), NULL);
+                       if( g_hash_table_lookup(GLOBALS->h_memo, item->memo) == 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 +299,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 +315,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 +368,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 +391,12 @@ void da_transaction_consistency(Transaction *item)
 Account *acc;
 Category *cat;
 Payee *pay;
-guint i, nbsplit;
+guint 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 +404,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,18 +413,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;i<nbsplit;i++)
+       //#1340142 check split category 
+       if( item->splits != NULL )
        {
-       Split *split = item->splits[i];
-               cat = da_cat_get(split->kcat);
-               if(cat == NULL)
+               nbsplit = da_splits_consistency(item->splits);
+               //# 1416624 empty category when split
+               if(nbsplit > 0 && item->kcat > 0)
                {
-                       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++;
                }
        }
        
@@ -451,11 +435,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
+       //#1787826 intxfer should not have split
 
        //#1295877 ensure income flag is correctly set
        item->flags &= ~(OF_INCOME);
@@ -463,8 +471,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,86 +481,160 @@ 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;
+}
+
+
+static GList *transaction_xfer_child_might_list_get(Transaction *ope, guint32 kdstacc)
+{
+GList *lst_acc, *lnk_acc;
+GList *list, *matchlist = NULL;
+
+       DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
+
+       DB( g_print(" - kdstacc:%d\n", kdstacc) );
+
+       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;
 
-                       match = g_list_append(match, item);
+               if( !(acc->flags & AF_CLOSED) && (acc->key != ope->kacc) && ( (acc->key == kdstacc) || kdstacc == 0 ) )
+               {
+                       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->kacc, 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 *parent, Transaction *ope, guint32 kdstacc)
 {
-GList *matchlist = transaction_match_get_child_transfer(ope);
-
-guint count = g_list_length(matchlist);
+GList *matchlist;
+gint count;
 
+       DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
 
-       DB( g_print("\n[transaction] transaction_xfer_search_or_add_child\n") );
+       matchlist = transaction_xfer_child_might_list_get(ope, kdstacc);
+       count = g_list_length(matchlist);
 
-       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 ?
-               //#942346: bad idea so to no let the user confirm, so let hil confirm
+               //#942346: bad idea so to no let the user confirm, so let him confirm
                /*
                case 1:         //transform the transaction to a child transfer
                {
@@ -563,86 +646,77 @@ guint count = g_list_length(matchlist);
 
                default:        //the user must choose himself
                {
+               gint result;
                Transaction *child;
 
-                       child = ui_dialog_transaction_xfer_select_child(matchlist);
-                       if(child == NULL)
-                               transaction_xfer_create_child(ope, treeview);
-                       else
-                               transaction_xfer_change_to_child(ope, child);
-                       break;
-               }
-       }
-
-       g_list_free(matchlist);
-
-}
-
-
-
-
-
-
-void transaction_xfer_create_child(Transaction *ope, GtkWidget *treeview)
-{
-Transaction *child;
-Account *acc;
-gchar swap;
-
-       DB( g_print("\n[transaction] transaction_xfer_create_child\n") );
-
-       if( ope->kxferacc > 0 )
-       {
-               child = da_transaction_clone(ope);
-
-               child->amount = -child->amount;
-               child->flags ^= (OF_INCOME);    // invert flag
-               child->flags &= ~(OF_REMIND);   // remove flag
-               //#1268026
-               child->flags &= ~(OF_VALID);    // remove reconcile state
-               
-
-               swap = child->kacc;
-               child->kacc = child->kxferacc;
-               child->kxferacc = swap;
-
-               /* update acc flags */
-               acc = da_acc_get( child->kacc );
-               if(acc != NULL)
-               {
-                       acc->flags |= AF_ADDED;
-
-                       //strong link
-                       guint maxkey = da_transaction_get_max_kxfer();
-
-                       DB( g_print(" + maxkey is %d\n", maxkey) );
+                       result = ui_dialog_transaction_xfer_select_child(parent, ope, matchlist, &child);
+                       if( result == GTK_RESPONSE_ACCEPT )
+                       {
+                               transaction_xfer_change_to_child(ope, child);
+                       }
+                       else //GTK_RESPONSE_CANCEL
+                       {
+                               ope->paymode = PAYMODE_NONE;
+                               ope->kxfer = 0;
+                               ope->kxferacc = 0;
+                       }
+               }
+       }
 
+       g_list_free(matchlist);
+}
 
-                       ope->kxfer = maxkey+1;
-                       child->kxfer = maxkey+1;
 
-                       DB( g_print(" + strong link to %d\n", ope->kxfer) );
+Transaction *transaction_xfer_child_strong_get(Transaction *src)
+{
+Account *dstacc;
+GList *list;
 
+       DB( g_print("\n[transaction] xfer_child_strong_get\n") );
 
-                       DB( g_print(" + add transfer, %p\n", child) );
+       dstacc = da_acc_get(src->kxferacc);
+       if( !dstacc || src->kxfer <= 0 )
+               return NULL;
 
-                       da_transaction_insert_sorted(child);
+       DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) );
 
-                       account_balances_add (child);
+       list = g_queue_peek_tail_link(dstacc->txn_queue);
+       while (list != NULL)
+       {
+       Transaction *item = list->data;
 
-                       if(treeview != NULL)
-                               transaction_add_treeview(child, treeview, ope->kacc);
+               //#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 )
+               {
+                       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 +724,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 +736,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] xfer_child_sync\n") );
+
+       if( child == NULL )
+       {
+               DB( g_print(" - no child found\n") );
+               return;
+       }
 
-       DB( g_print("\n[transaction] transaction_xfer_sync_child\n") );
+       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 +766,102 @@ 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
+       g_free(child->tags);
+       child->tags = tags_clone (s_txn->tags);
 
 }
 
 
-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 +870,74 @@ 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_changed(Transaction *txn)
+{
+Account *acc;
+
+       if( txn == NULL )
+               return;
+
+       acc = da_acc_get(txn->kacc);
+       if(acc == NULL)
+               return; 
+
+       acc->flags |= AF_CHANGED;
+}
 
 
-void transaction_add(Transaction *ope, GtkWidget *treeview, guint32 accnum)
+Transaction *transaction_add(GtkWindow *parent, 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_split_destroy(ope->splits);
+               ope->splits = NULL;
+               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 +957,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 )
-               {
-                       acc->cheque2 = MAX(acc->cheque2, cheque);
-               }
-               else
+               if( acc != NULL )
                {
-                       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 +979,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(parent, newope, newope->kxferacc);
                }
        }
+       
+       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") );
-
-       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);
+Account *oacc, *nacc;
 
-               //activate that new line
-               //path = gtk_tree_model_get_path(model, &iter);
-               //gtk_tree_view_expand_to_path(GTK_TREE_VIEW(treeview), path);
+       DB( g_print("\n[transaction] acc_move\n") );
 
-               //sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
-               //gtk_tree_selection_select_iter(sel, &iter);
-
-               //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,14 +1039,14 @@ 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 )
                {
                        if( g_strrstr(text, searchtext) != NULL )
                        {
-                               DB( g_print(" found case '%s'\n", searchtext) );
+                               DB( g_print("-- found case '%s'\n", searchtext) );
                                match = TRUE;
                        }
                }
@@ -890,7 +1057,7 @@ gboolean match = FALSE;
 
                        if( g_strrstr(word, needle) != NULL )
                        {
-                               DB( g_print(" found nocase '%s'\n", searchtext) );
+                               DB( g_print("-- found nocase '%s'\n", searchtext) );
                                match = TRUE;
                        }
                        g_free(word);
@@ -901,43 +1068,66 @@ 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)
+static GList *transaction_auto_assign_eval_txn(GList *l_rul, Transaction *txn)
 {
-Assign *rule = NULL;
+GList *ret_list = NULL;
 GList *list;
-       
-       DB( g_print("- eval every rules, and return the last that match\n") );
+gchar *text;
 
        list = g_list_first(l_rul);
        while (list != NULL)
        {
        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) )
+                               ret_list = g_list_append(ret_list, rul);
+               }
+               else
+               {
+                       if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
+                               ret_list = g_list_append(ret_list, rul);
+               }
 
                list = g_list_next(list);
        }
 
-       return rule;
+       DB( g_print("- evaluated txn '%s'=> %d match\n", text, g_list_length (ret_list)) );
+       
+       return ret_list;
 }
 
 
-static Assign *transaction_auto_assign_eval(GList *l_rul, gchar *text)
+static GList *transaction_auto_assign_eval(GList *l_rul, gchar *text)
 {
-Assign *rule = NULL;
+GList *ret_list = NULL;
 GList *list;
-       
-       DB( g_print("- eval every rules, and return the last that match\n") );
 
        list = g_list_first(l_rul);
        while (list != NULL)
@@ -946,91 +1136,112 @@ 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) )
+                                       ret_list = g_list_append(ret_list, rul);
+                       }
+                       else
+                       {
+                               if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
+                                       ret_list = g_list_append(ret_list, rul);
+                       }
                }
                list = g_list_next(list);
        }
 
-       return rule;
+       DB( g_print("- evaluated split '%s' => %d match\n", text, g_list_length (ret_list)) );
+       
+       return ret_list;
 }
 
 
-gint transaction_auto_assign(GList *ope_list, guint32 key)
+guint transaction_auto_assign(GList *ope_list, guint32 kacc)
 {
 GList *l_ope;
 GList *l_rul;
-gint changes = 0;
+GList *l_match, *l_tmp;
+guint 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("\n- work on txn '%s' : acc=%d, pay=%d, cat=%d, %s\n", ope->memo, ope->kacc, ope->kpay, ope->kcat, (ope->flags & OF_SPLIT) ? "is_split" : "" ) );
 
-               //#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) )
+                       if( !(ope->flags & OF_SPLIT) )
                        {
-                               rul = transaction_auto_assign_eval_txn(l_rul, ope);
-                               if( rul != NULL )
+                               l_match = l_tmp = transaction_auto_assign_eval_txn(l_rul, ope);
+                               while( l_tmp != NULL )
                                {
-                                       if( ope->kpay == 0 && (rul->flags & ASGF_DOPAY) )
+                               Assign *rul = l_tmp->data;
+                                       
+                                       if( (ope->kpay == 0 && (rul->flags & ASGF_DOPAY)) || (rul->flags & ASGF_OVWPAY) )
                                        {
+                                               if(ope->kpay != rul->kpay) { changed = TRUE; }
                                                ope->kpay = rul->kpay;
-                                               ope->flags |= OF_CHANGED;
-                                               changes++;
                                        }
-                                       if( ope->kcat == 0 && (rul->flags & ASGF_DOCAT) )
+
+                                       if( (ope->kcat == 0 && (rul->flags & ASGF_DOCAT)) || (rul->flags & ASGF_OVWCAT) )
                                        {
+                                               if(ope->kcat != rul->kcat) { changed = TRUE; }
                                                ope->kcat = rul->kcat;
-                                               ope->flags |= OF_CHANGED;
-                                               changes++;
                                        }
-                                       
+
+                                       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) 
+                                               {
+                                                       if(ope->paymode != rul->paymode) { changed = TRUE; }
+                                                       ope->paymode = rul->paymode;
+                                               }
+                                       }
+                                       l_tmp = g_list_next(l_tmp);
                                }
+                               g_list_free(l_match);
                        }
-                       else if( ope->flags & OF_SPLIT )
+                       else
                        {
-                       guint i, nbsplit = da_transaction_splits_count(ope);
-                       Split *split;
-                       gboolean split_change = FALSE;
-
+                       guint i, nbsplit = da_splits_length(ope->splits);
+                               
                                for(i=0;i<nbsplit;i++)
                                {
-                                       split = ope->splits[i];
-
+                               Split *split = da_splits_get(ope->splits, i);
+                                       
                                        DB( g_print("- eval split '%s'\n", split->memo) );
 
-                                       if(split->kcat == 0)
+                                       l_match = l_tmp = transaction_auto_assign_eval(l_rul, split->memo);
+                                       while( l_tmp != NULL )
                                        {
-                                               rul = transaction_auto_assign_eval(l_rul, split->memo);
-                                               if( rul != NULL )
+                                       Assign *rul = l_tmp->data;
+
+                                               //#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;
                                                }
-                                       }
-
+                                               l_tmp = g_list_next(l_tmp);
+                                       }       
+                                       g_list_free(l_match);
                                }
+                       }
 
-                               if(split_change == TRUE)
-                                       changes++;
-
+                       if(changed == TRUE)
+                       {
+                               ope->flags |= OF_CHANGED;
+                               changes++;
                        }
-                       
                }
 
                l_ope = g_list_next(l_ope);
@@ -1042,204 +1253,267 @@ gint changes = 0;
 }
 
 
-/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
-
 
-guint
-transaction_tags_count(Transaction *ope)
+static gboolean transaction_similar_match(Transaction *stxn, Transaction *dtxn, guint32 daygap)
 {
-guint count = 0;
-guint32 *ptr = ope->tags;
+gboolean retval = FALSE;
 
-       if( ope->tags == NULL )
-               return 0;
-
-       while(*ptr++ != 0 && count < 32)
-               count++;
+       if(stxn == dtxn)
+               return FALSE;
 
-       return count;
+       DB( g_print(" date: %d - %d = %d\n", stxn->date, dtxn->date, stxn->date - dtxn->date) );
+       
+       if( stxn->kcur == dtxn->kcur
+        &&     stxn->amount == dtxn->amount
+        && ( (stxn->date - dtxn->date) <= daygap )
+        //todo: at import we also check payee, but maybe too strict here
+        && (hb_string_compare(stxn->memo, dtxn->memo) == 0)
+         )
+       {
+               retval = TRUE;
+       }
+       return retval;
 }
 
 
+void transaction_similar_unmark(Account *acc)
+{
+GList *lnk_txn;
+
+       lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
+       while (lnk_txn != NULL)
+       {
+       Transaction *stxn = lnk_txn->data;
+               stxn->marker = TXN_MARK_NONE;
+               lnk_txn = g_list_previous(lnk_txn);
+       }
+}
 
 
-guint transaction_splits_parse(Transaction *ope, gchar *cats, gchar *amounts, gchar *memos)
+gint transaction_similar_mark(Account *acc, guint32 daygap)
 {
-gchar **cat_a, **amt_a, **mem_a;
-guint count, i;
-guint32 kcat;
-gdouble amount;
-Split *split;
+GList *lnk_txn, *list2;
+gint nball = 0;
+gint nbdup = 0;
+
+       //warning the list must be sorted by date then amount
+       //ideally (easier to parse) we shoudl get a list sorted by amount, then date
+       DB( g_print("\n[transaction] check duplicate\n") );
+
+       DB( g_print("\n - account:'%s' gap:%d\n", acc->name, daygap) );
+
+       #if MYDEBUG == 1
+       GTimer *t = g_timer_new();
+       g_print(" - start parse\n");
+       #endif
+
+
+       /*
+       llast = g_list_last(old ope list);
+       DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
+       g_timer_reset(t);
 
-       DB( g_print(" split parse %s :: %s :: %s\n", cats, amounts, memos) );
+       ltxn = llast->data;
+       g_date_clear(&gd, 1);
+       g_date_set_julian(&gd, ltxn->date);
+       g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
 
-       cat_a = g_strsplit (cats, "||", 0);
-       amt_a = g_strsplit (amounts, "||", 0);
-       mem_a = g_strsplit (memos, "||", 0);
+       minjulian = ltxn->date - (366*2);
+       g_date_clear(&gd, 1);
+       g_date_set_julian(&gd, minjulian);
+       g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
+       */
 
-       count = g_strv_length(amt_a);
-       if( (count == g_strv_length(cat_a)) && (count == g_strv_length(mem_a)) )
+       transaction_similar_unmark(acc);
+
+       //mark duplicate
+       lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
+       while (lnk_txn != NULL)
        {
-               for(i=0;i<count;i++)
+       Transaction *stxn = lnk_txn->data;
+
+               //if(stxn->date < minjulian)
+               //      break;
+               DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
+
+               list2 = g_list_previous(lnk_txn);
+               while (list2 != NULL)
                {
-                       kcat = atoi(cat_a[i]);
-                       amount = g_ascii_strtod(amt_a[i], NULL);
-                       split = da_split_new(kcat, amount, mem_a[i]);
-                       da_transaction_splits_append (ope, split);
+               Transaction *dtxn = list2->data;
+
+                       DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
+
+                       if( (stxn->date - dtxn->date) > daygap )
+                       {
+                               DB( g_print(" break %d %d\n", (dtxn->date - daygap) , (stxn->date - daygap)) );
+                               break;
+                       }
+                               
+                       if( dtxn->marker == TXN_MARK_NONE )
+                       {
+                               if( transaction_similar_match(stxn, dtxn, daygap) )
+                               {
+                                       stxn->marker = TXN_MARK_DUPSRC;
+                                       dtxn->marker = TXN_MARK_DUPDST;
+                                       DB( g_print(" = dtxn marker=%d\n", dtxn->marker) );
+                                       nball++;
+                               }
+                       }
+                       else
+                       {
+                               DB( g_print(" already marked %d\n", dtxn->marker) );
+                       }
+
+                       
+                       list2 = g_list_previous(list2);
                }
+       
+               DB( g_print(" = stxn marker=%d\n", stxn->marker) );
+               if( stxn->marker == TXN_MARK_DUPSRC )
+                       nbdup++;
                
-               ope->flags |= OF_SPLIT;
-       }
-       else
-       {
-               g_warning("invalid split parse");
+               lnk_txn = g_list_previous(lnk_txn);
        }
 
-       g_strfreev (mem_a);
-       g_strfreev (amt_a);
-       g_strfreev (cat_a);
+       DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
+       DB( g_timer_destroy (t) );
 
-       return count;
+       DB( g_print(" - found: %d/%d dup\n", nbdup, nball ) );
+
+       return nbdup;
 }
 
 
+/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
+/* = = experimental = = */
+/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
 
-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;i<count;i++)
-       {
-               split = ope->splits[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;
-}
+/*
+probably add a structure hosted into a glist here
+with kind of problem: duplicate, child xfer, orphan xfer       
+and collect all that with target txn
+*/
 
 
-guint
-transaction_tags_parse(Transaction *ope, const gchar *tagstring)
+/*void future_transaction_test_account(Account *acc)
 {
-gchar **str_array;
-guint count, i;
-Tag *tag;
-
-       DB( g_print("(transaction_set_tags)\n") );
+GList *lnk_txn, *list2;
+gint nball = 0;
+gint nbdup = 0;
+gint nbxfer = 0;
+GPtrArray *array;
 
-       DB( g_print(" - tagstring='%s'\n", tagstring) );
+//future
+gint gapday = 0, i;
 
-       str_array = g_strsplit (tagstring, " ", 0);
-       count = g_strv_length( str_array );
+       //warning the list must be sorted by date then amount
+       //ideally (easier to parse) we shoudl get a list sorted by amount, then date
 
-       g_free(ope->tags);
-       ope->tags = NULL;
+       DB( g_print("\n[transaction] check duplicate\n") );
 
-       DB( g_print(" -> reset storage %p\n", ope->tags) );
 
 
-       if( count > 0 )
-       {
+       DB( g_print("\n - account:'%s'\n", acc->name) );
 
-               ope->tags = g_new0(guint32, count + 1);
+       GTimer *t = g_timer_new();
+       g_print(" - start parse\n");
 
-               DB( g_print(" -> storage %p\n", ope->tags) );
 
-               for(i=0;i<count;i++)
-               {
-                       tag = da_tag_get_by_name(str_array[i]);
-                       if(tag == NULL)
-                       {
-                       Tag *newtag = da_tag_malloc();
+       llast = g_list_last(old ope list);
+       DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
+       g_timer_reset(t);
 
-                               newtag->name = g_strdup(str_array[i]);
-                               da_tag_append(newtag);
-                               tag = da_tag_get_by_name(str_array[i]);
-                       }
+       ltxn = llast->data;
+       g_date_clear(&gd, 1);
+       g_date_set_julian(&gd, ltxn->date);
+       g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
 
-                       DB( g_print(" -> storing %d=>%s at tags pos %d\n", tag->key, tag->name, i) );
+       minjulian = ltxn->date - (366*2);
+       g_date_clear(&gd, 1);
+       g_date_set_julian(&gd, minjulian);
+       g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
 
-                       ope->tags[i] = tag->key;
-               }
-       }
+       array = g_ptr_array_sized_new (25);
 
-       //hex_dump(ope->tags, sizeof(guint32*)*count+1);
+       lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
+       while (lnk_txn != NULL)
+       {
+       Transaction *stxn = lnk_txn->data;
 
-       g_strfreev (str_array);
+               //if(stxn->date < minjulian)
+               //      break;
+               DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
 
-       return count;
-}
+               stxn->marker = 0;
+               list2 = g_list_previous(lnk_txn);
+               while (list2 != NULL)
+               {
+               Transaction *dtxn = list2->data;
 
-gchar *
-transaction_tags_tostring(Transaction *ope)
-{
-guint count, i;
-gchar **str_array;
-gchar *tagstring;
-Tag *tag;
+                       stxn->marker = 0;
+                       if( (dtxn->date + gapday) < (stxn->date + gapday) )
+                               break;
 
-       DB( g_print("transaction_get_tagstring\n") );
+                       DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
 
-       DB( g_print(" -> tags at=%p\n", ope->tags) );
+                       if( transaction_similar_match(stxn, dtxn, gapday) )
+                       {
+                               g_ptr_array_add (array, stxn);
+                               g_ptr_array_add (array, dtxn);
+                               nbdup++;
+                               DB( g_print(" + dst=1 src=1\n") );
+                       }
 
-       if( ope->tags == NULL )
-       {
+                       nball++;
+                       list2 = g_list_previous(list2);
+               }
 
-               return NULL;
+               lnk_txn = g_list_previous(lnk_txn);
        }
-       else
+
+       DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
+       DB( g_timer_destroy (t) );
+
+       for(i=0;i<array->len;i++)
        {
-               count = transaction_tags_count(ope);
+       Transaction *txn = g_ptr_array_index(array, i);
+               txn->marker = 1;
+       }
 
-               DB( g_print(" -> tags at=%p, nbtags=%d\n", ope->tags, count) );
+       g_ptr_array_free(array, TRUE);
 
-               str_array = g_new0(gchar*, count+1);
+       DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
 
-               DB( g_print(" -> str_array at %p\n", str_array) );
+}
 
-               //hex_dump(ope->tags, sizeof(guint32*)*(count+1));
 
-               for(i=0;i<count;i++)
-               {
-                       DB( g_print(" -> try to get tag %d\n", ope->tags[i]) );
 
-                       tag = da_tag_get(ope->tags[i]);
-                       if( tag )
-                       {
-                               DB( g_print(" -> get %s at %d\n", tag->name, i) );
-                               str_array[i] = tag->name;
-                       }
-                       else
-                               str_array[i] = NULL;
 
 
-               }
+//todo: add a limitation, no need to go through all txn
+// 1 year in th past, or abolute number ?
+gint future_transaction_test_notification(void)
+{
+GList *lst_acc, *lnk_acc;
 
-               tagstring = g_strjoinv(" ", str_array);
+       DB( g_print("\ntransaction_test_notification\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;
 
-               g_free (str_array);
+               transaction_similar_mark(acc);
 
+               lnk_acc = g_list_next(lnk_acc);
        }
-
-       return tagstring;
+       g_list_free(lst_acc);
+       
+       return 0;
 }
+*/
+
+
 
This page took 0.068324 seconds and 4 git commands to generate.