/* HomeBank -- Free, easy, personal accounting for everyone. * Copyright (C) 1995-2019 Maxime DOYEN * * This file is part of HomeBank. * * HomeBank is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * HomeBank is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "homebank.h" #include "hb-filter.h" /****************************************************************************/ /* Debug macros */ /****************************************************************************/ #define MYDEBUG 0 #if MYDEBUG #define DB(x) (x); #else #define DB(x); #endif /* our global datas */ extern struct HomeBank *GLOBALS; extern struct Preferences *PREFS; /* = = = = = = = = = = = = = = = = */ void da_flt_free(Filter *flt) { DB( g_print("da_flt_free\n") ); if(flt != NULL) { g_free(flt->memo); g_free(flt->info); g_free(flt->tag); g_free(flt); } } Filter *da_flt_malloc(void) { DB( g_print("da_flt_malloc\n") ); return g_malloc0(sizeof(Filter)); } /* = = = = = = = = = = = = = = = = = = = = */ void filter_status_acc_clear_except(Filter *flt, guint32 selkey) { GHashTableIter iter; gpointer key, value; // set all account g_hash_table_iter_init (&iter, GLOBALS->h_acc); while (g_hash_table_iter_next (&iter, &key, &value)) { Account *item = value; item->flt_select = item->key == selkey ? TRUE : FALSE; } } void filter_status_pay_clear_except(Filter *flt, guint32 selkey) { GHashTableIter iter; gpointer key, value; // set all payee g_hash_table_iter_init (&iter, GLOBALS->h_pay); while (g_hash_table_iter_next (&iter, &key, &value)) { Payee *item = value; item->flt_select = item->key == selkey ? TRUE : FALSE; } } void filter_status_cat_clear_except(Filter *flt, guint32 selkey) { GHashTableIter iter; gpointer key, value; // set all category g_hash_table_iter_init (&iter, GLOBALS->h_cat); while (g_hash_table_iter_next (&iter, &key, &value)) { Category *item = value; item->flt_select = FALSE; if( (item->key == selkey) //#1824561 don't forget subcat //#1829076 but not when selkey is 0 || ((item->parent == selkey) && selkey > 0) ) item->flt_select = TRUE; } } /* = = = = = = = = = = = = = = = = */ void filter_reset(Filter *flt) { gint i; DB( g_print("\n[filter] default reset all %p\n", flt) ); for(i=0;ioption[i] = 0; } flt->option[FILTER_DATE] = 1; flt->range = FLT_RANGE_LAST12MONTHS; filter_preset_daterange_set(flt, flt->range, 0); for(i=0;ipaymode[i] = TRUE; g_free(flt->info); g_free(flt->memo); g_free(flt->tag); flt->info = NULL; flt->memo = NULL; flt->tag = NULL; //unsaved flt->nbdaysfuture = 0; flt->type = FLT_TYPE_ALL; flt->status = FLT_STATUS_ALL; flt->forceremind = PREFS->showremind; *flt->last_tab = '\0'; } void filter_set_tag_by_id(Filter *flt, guint32 key) { Tag *tag; DB( g_print("\n[filter] set tag by id\n") ); if(flt->tag) { g_free(flt->tag); flt->tag = NULL; } tag = da_tag_get(key); if(tag) { flt->tag = g_strdup(tag->name); } } static void filter_set_date_bounds(Filter *flt, guint32 kacc) { GList *lst_acc, *lnk_acc; GList *lnk_txn; DB( g_print("\n[filter] set date bounds %p\n", flt) ); flt->mindate = 0; flt->maxdate = 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; //#1674045 only rely on nosummary //if( !(acc->flags & AF_CLOSED) ) { Transaction *txn; DB( g_print(" - do '%s'\n", acc->name) ); lnk_txn = g_queue_peek_head_link(acc->txn_queue); if(lnk_txn) { txn = lnk_txn->data; if( (kacc == 0) || (txn->kacc == kacc) ) { if( flt->mindate == 0 ) flt->mindate = txn->date; else flt->mindate = MIN(flt->mindate, txn->date); } } lnk_txn = g_queue_peek_tail_link(acc->txn_queue); if(lnk_txn) { txn = lnk_txn->data; if( (kacc == 0) || (txn->kacc == kacc) ) { if( flt->maxdate == 0 ) flt->maxdate = txn->date; else flt->maxdate = MAX(flt->maxdate, txn->date); } } } lnk_acc = g_list_next(lnk_acc); } if( flt->mindate == 0 ) //changed 5.3 //flt->mindate = HB_MINDATE; flt->mindate = GLOBALS->today - 365; if( flt->maxdate == 0 ) //changed 5.3 //flt->maxdate = HB_MAXDATE; flt->maxdate = GLOBALS->today + flt->nbdaysfuture; g_list_free(lst_acc); } gboolean filter_preset_daterange_future_enable(gint range) { switch( range ) { case FLT_RANGE_THISMONTH: case FLT_RANGE_THISQUARTER: case FLT_RANGE_THISYEAR: case FLT_RANGE_LAST30DAYS: case FLT_RANGE_LAST60DAYS: case FLT_RANGE_LAST90DAYS: case FLT_RANGE_LAST12MONTHS: return TRUE; break; } return FALSE; } void filter_preset_daterange_add_futuregap(Filter *filter, gint nbdays) { DB( g_print("\n[filter] range add future gap\n") ); //fixed > 5.1.7 /*if( nbdays <= 0 ) { filter->nbdaysfuture = 0; return; }*/ filter->nbdaysfuture = 0; if( filter_preset_daterange_future_enable(filter->range) ) filter->nbdaysfuture = nbdays; } void filter_preset_daterange_set(Filter *flt, gint range, guint32 kacc) { GDate *tmpdate; guint32 jtoday, jfiscal; guint16 month, year, yfiscal, qnum; DB( g_print("\n[filter] daterange set %p %d\n", flt, range) ); flt->range = range; jtoday = GLOBALS->today; tmpdate = g_date_new_julian(jtoday); month = g_date_get_month(tmpdate); year = g_date_get_year(tmpdate); DB( hb_print_date(jtoday , "today ") ); g_date_set_dmy(tmpdate, PREFS->fisc_year_day, PREFS->fisc_year_month, year); jfiscal = g_date_get_julian(tmpdate); DB( hb_print_date(jfiscal, "fiscal") ); yfiscal = (jtoday >= jfiscal) ? year : year-1; qnum = 0; if( range == FLT_RANGE_THISQUARTER || range == FLT_RANGE_LASTQUARTER ) { g_date_set_dmy(tmpdate, PREFS->fisc_year_day, PREFS->fisc_year_month, yfiscal); while( (qnum < 5) && (g_date_get_julian(tmpdate) < jtoday) ) { qnum++; g_date_add_months (tmpdate, 3); } DB( g_print(" qnum: %d\n", qnum ) ); } switch( range ) { case FLT_RANGE_THISMONTH: case FLT_RANGE_LASTMONTH: g_date_set_dmy(tmpdate, 1, month, year); if( range == FLT_RANGE_LASTMONTH ) g_date_subtract_months(tmpdate, 1); flt->mindate = g_date_get_julian(tmpdate); month = g_date_get_month(tmpdate); year = g_date_get_year(tmpdate); g_date_add_days(tmpdate, g_date_get_days_in_month(month, year)); flt->maxdate = g_date_get_julian(tmpdate) - 1; break; case FLT_RANGE_THISQUARTER: case FLT_RANGE_LASTQUARTER: g_date_set_dmy(tmpdate, PREFS->fisc_year_day, PREFS->fisc_year_month, yfiscal); if( range == FLT_RANGE_LASTQUARTER ) g_date_subtract_months(tmpdate, 3); g_date_add_months(tmpdate, 3 * (qnum-1) ); flt->mindate = g_date_get_julian(tmpdate); g_date_add_months(tmpdate, 3); flt->maxdate = g_date_get_julian(tmpdate) - 1; break; case FLT_RANGE_THISYEAR: case FLT_RANGE_LASTYEAR: g_date_set_dmy(tmpdate, PREFS->fisc_year_day, PREFS->fisc_year_month, yfiscal); if( range == FLT_RANGE_LASTYEAR ) g_date_subtract_years(tmpdate, 1); flt->mindate = g_date_get_julian(tmpdate); g_date_add_years (tmpdate, 1); flt->maxdate = g_date_get_julian(tmpdate) - 1; break; case FLT_RANGE_LAST30DAYS: flt->mindate = jtoday - 30; flt->maxdate = jtoday; break; case FLT_RANGE_LAST60DAYS: flt->mindate = jtoday - 60; flt->maxdate = jtoday; break; case FLT_RANGE_LAST90DAYS: flt->mindate = jtoday - 90; flt->maxdate = jtoday; break; case FLT_RANGE_LAST12MONTHS: g_date_set_julian (tmpdate, jtoday); g_date_subtract_months(tmpdate, 12); flt->mindate = g_date_get_julian(tmpdate); flt->maxdate = jtoday; break; // case FLT_RANGE_OTHER: //nothing to do case FLT_RANGE_ALLDATE: filter_set_date_bounds(flt, kacc); break; } g_date_free(tmpdate); } void filter_preset_type_set(Filter *flt, gint type) { DB( g_print("\n[filter] preset type set\n") ); /* any type */ flt->type = type; flt->option[FILTER_AMOUNT] = 0; flt->minamount = G_MINDOUBLE; flt->maxamount = G_MINDOUBLE; switch( type ) { case FLT_TYPE_EXPENSE: flt->option[FILTER_AMOUNT] = 1; flt->minamount = -G_MAXDOUBLE; flt->maxamount = G_MINDOUBLE; break; case FLT_TYPE_INCOME: flt->option[FILTER_AMOUNT] = 1; flt->minamount = G_MINDOUBLE; flt->maxamount = G_MAXDOUBLE; break; } } void filter_preset_status_set(Filter *flt, gint status) { DB( g_print("\n[filter] preset status set\n") ); /* any status */ flt->status = status; flt->option[FILTER_STATUS] = 0; flt->option[FILTER_CATEGORY] = 0; flt->option[FILTER_PAYMODE] = 0; flt->reconciled = TRUE; flt->cleared = TRUE; //#1602835 fautly set //flt->forceadd = TRUE; //flt->forcechg = TRUE; switch( status ) { case FLT_STATUS_UNCATEGORIZED: flt->option[FILTER_CATEGORY] = 1; filter_status_cat_clear_except(flt, 0); flt->option[FILTER_PAYMODE] = 1; flt->paymode[PAYMODE_INTXFER] = FALSE; break; case FLT_STATUS_UNRECONCILED: flt->option[FILTER_STATUS] = 2; flt->reconciled = TRUE; flt->cleared = FALSE; break; case FLT_STATUS_UNCLEARED: flt->option[FILTER_STATUS] = 2; flt->reconciled = FALSE; flt->cleared = TRUE; break; case FLT_STATUS_RECONCILED: flt->option[FILTER_STATUS] = 1; flt->reconciled = TRUE; flt->cleared = FALSE; break; case FLT_STATUS_CLEARED: flt->option[FILTER_STATUS] = 1; flt->reconciled = FALSE; flt->cleared = TRUE; break; } } gchar *filter_daterange_text_get(Filter *flt) { gchar buffer1[128]; gchar buffer2[128]; gchar buffer3[128]; GDate *date; gchar *retval = NULL; DB( g_print("\n[filter] daterange text get\n") ); date = g_date_new_julian(flt->mindate); g_date_strftime (buffer1, 128-1, PREFS->date_format, date); g_date_set_julian(date, flt->maxdate); g_date_strftime (buffer2, 128-1, PREFS->date_format, date); if( flt->nbdaysfuture > 0 ) { g_date_set_julian(date, flt->maxdate + flt->nbdaysfuture); g_date_strftime (buffer3, 128-1, PREFS->date_format, date); retval = g_strdup_printf("%s — %s %s", buffer1, buffer2, buffer3); } else retval = g_strdup_printf("%s — %s", buffer1, buffer2); g_date_free(date); //return g_strdup_printf(_("from %s to %s — "), buffer1, buffer2); return retval; } /* used for quicksearch text into transaction */ gboolean filter_txn_search_match(gchar *needle, Transaction *txn, gint flags) { gboolean retval = FALSE; Payee *payitem; Category *catitem; gchar *tags; DB( g_print("\n[filter] tnx search match\n") ); if(flags & FLT_QSEARCH_MEMO) { //#1668036 always try match on txn memo first if(txn->memo) { retval |= hb_string_utf8_strstr(txn->memo, needle, FALSE); } if(retval) goto end; //#1509485 if(txn->flags & OF_SPLIT) { guint count, i; Split *split; count = da_splits_length(txn->splits); for(i=0;isplits, i); tmpinsert = hb_string_utf8_strstr(split->memo, needle, FALSE); retval |= tmpinsert; if( tmpinsert ) break; } } if(retval) goto end; } if(flags & FLT_QSEARCH_INFO) { if(txn->info) { retval |= hb_string_utf8_strstr(txn->info, needle, FALSE); } if(retval) goto end; } if(flags & FLT_QSEARCH_PAYEE) { payitem = da_pay_get(txn->kpay); if(payitem) { retval |= hb_string_utf8_strstr(payitem->name, needle, FALSE); } if(retval) goto end; } if(flags & FLT_QSEARCH_CATEGORY) { //#1509485 if(txn->flags & OF_SPLIT) { guint count, i; Split *split; count = da_splits_length(txn->splits); for(i=0;isplits, i); catitem = da_cat_get(split->kcat); if(catitem) { tmpinsert = hb_string_utf8_strstr(catitem->fullname, needle, FALSE); retval |= tmpinsert; } if( tmpinsert ) break; } } else { catitem = da_cat_get(txn->kcat); if(catitem) { retval |= hb_string_utf8_strstr(catitem->fullname, needle, FALSE); } } if(retval) goto end; } if(flags & FLT_QSEARCH_TAGS) { tags = tags_tostring(txn->tags); if(tags) { retval |= hb_string_utf8_strstr(tags, needle, FALSE); } g_free(tags); if(retval) goto end; } //#1741339 add quicksearch for amount if(flags & FLT_QSEARCH_AMOUNT) { gchar formatd_buf[G_ASCII_DTOSTR_BUF_SIZE]; hb_strfnum(formatd_buf, G_ASCII_DTOSTR_BUF_SIZE-1, txn->amount, txn->kcur, FALSE); retval |= hb_string_utf8_strstr(formatd_buf, needle, FALSE); } end: return retval; } gint filter_txn_match(Filter *flt, Transaction *txn) { Account *accitem; Payee *payitem; Category *catitem; gint insert; //DB( g_print("\n[filter] txn match\n") ); insert = 1; /*** start filtering ***/ /* force display */ if(flt->forceadd == TRUE && (txn->flags & OF_ADDED)) goto end; if(flt->forcechg == TRUE && (txn->flags & OF_CHANGED)) goto end; /* force remind if not filter on status */ if(flt->forceremind == TRUE && (txn->status == TXN_STATUS_REMIND)) goto end; /* date */ if(flt->option[FILTER_DATE]) { insert = ( (txn->date >= flt->mindate) && (txn->date <= (flt->maxdate + flt->nbdaysfuture) ) ) ? 1 : 0; if(flt->option[FILTER_DATE] == 2) insert ^= 1; } if(!insert) goto end; /* account */ if(flt->option[FILTER_ACCOUNT]) { accitem = da_acc_get(txn->kacc); if(accitem) { insert = ( accitem->flt_select == TRUE ) ? 1 : 0; if(flt->option[FILTER_ACCOUNT] == 2) insert ^= 1; } } if(!insert) goto end; /* payee */ if(flt->option[FILTER_PAYEE]) { payitem = da_pay_get(txn->kpay); if(payitem) { insert = ( payitem->flt_select == TRUE ) ? 1 : 0; if(flt->option[FILTER_PAYEE] == 2) insert ^= 1; } } if(!insert) goto end; /* category */ if(flt->option[FILTER_CATEGORY]) { if(txn->flags & OF_SPLIT) { guint count, i; Split *split; insert = 0; //fix: 1151259 count = da_splits_length(txn->splits); for(i=0;isplits, i); catitem = da_cat_get(split->kcat); if(catitem) { tmpinsert = ( catitem->flt_select == TRUE ) ? 1 : 0; if(flt->option[FILTER_CATEGORY] == 2) tmpinsert ^= 1; } insert |= tmpinsert; } } else { catitem = da_cat_get(txn->kcat); if(catitem) { insert = ( catitem->flt_select == TRUE ) ? 1 : 0; if(flt->option[FILTER_CATEGORY] == 2) insert ^= 1; } } } if(!insert) goto end; /* status */ if(flt->option[FILTER_STATUS]) { gint insert1 = 0, insert2 = 0; if(flt->reconciled) insert1 = ( txn->status == TXN_STATUS_RECONCILED ) ? 1 : 0; if(flt->cleared) insert2 = ( txn->status == TXN_STATUS_CLEARED ) ? 1 : 0; insert = insert1 | insert2; if(flt->option[FILTER_STATUS] == 2) insert ^= 1; } if(!insert) goto end; /* paymode */ if(flt->option[FILTER_PAYMODE]) { insert = ( flt->paymode[txn->paymode] == TRUE) ? 1 : 0; if(flt->option[FILTER_PAYMODE] == 2) insert ^= 1; } if(!insert) goto end; /* amount */ if(flt->option[FILTER_AMOUNT]) { insert = ( (txn->amount >= flt->minamount) && (txn->amount <= flt->maxamount) ) ? 1 : 0; if(flt->option[FILTER_AMOUNT] == 2) insert ^= 1; } if(!insert) goto end; /* info/memo/tag */ if(flt->option[FILTER_TEXT]) { gchar *tags; gint insert1, insert2, insert3; insert1 = insert2 = insert3 = 0; if(flt->info) { if(txn->info) { insert1 = hb_string_utf8_strstr(txn->info, flt->info, flt->exact); } } else insert1 = 1; if(flt->memo) { //#1668036 always try match on txn memo first if(txn->memo) { insert2 = hb_string_utf8_strstr(txn->memo, flt->memo, flt->exact); } if( (insert2 == 0) && (txn->flags & OF_SPLIT) ) { guint count, i; Split *split; count = da_splits_length(txn->splits); for(i=0;isplits, i); tmpinsert = hb_string_utf8_strstr(split->memo, flt->memo, flt->exact); insert2 |= tmpinsert; if( tmpinsert ) break; } } } else insert2 = 1; if(flt->tag) { tags = tags_tostring(txn->tags); if(tags) { insert3 = hb_string_utf8_strstr(tags, flt->tag, flt->exact); } g_free(tags); } else insert3 = 1; insert = insert1 && insert2 && insert3 ? 1 : 0; if(flt->option[FILTER_TEXT] == 2) insert ^= 1; } if(!insert) goto end; end: // DB( g_print(" %d :: %d :: %d\n", flt->mindate, txn->date, flt->maxdate) ); // DB( g_print(" [%d] %s => %d (%d)\n", txn->account, txn->memo, insert, count) ); return(insert); }