/* 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
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
{
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)
{
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)
{
}
-
void
da_transaction_free(Transaction *item)
{
}
-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;
}
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)
{
}
-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)
{
-GList *tmplist = g_list_first(list);
+ da_transaction_free (item);
+}
- while (tmplist != NULL)
+
+void da_transaction_destroy(void)
+{
+GList *lacc, *list;
+
+ 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);
}
}
-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;
// 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;
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
-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)
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;
}
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);
{
g_warning("txn consistency: fixed invalid acc %d", item->kacc);
da_transaction_goto_orphan(item);
+ GLOBALS->changes_count++;
}
// check category exists
{
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++;
}
}
{
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);
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);
}
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
/* 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) );
- match = g_list_append(match, item);
+ 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) && ( (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);
+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, kdstacc);
+ 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 ?
- //#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
{
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 )
+ {
+ //#1827193 child can be null...
+ DB( g_print(" child %p\n", child) );
+ if( child != NULL )
+ transaction_xfer_change_to_child(ope, child);
+ else
+ transaction_xfer_create_child(ope);
+ }
+ 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;
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();
}
-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);
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") );
}
+/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
-/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
+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);
/* 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);
+ }
}
}
{
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;
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;
}
}
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);
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)
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);
}
-/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
-
-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;
}
+*/
+
+