]> Dogcows Code - chaz/homebank/blob - src/hb-filter.c
import homebank-5.1.7
[chaz/homebank] / src / hb-filter.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2018 Maxime DOYEN
3 *
4 * This file is part of HomeBank.
5 *
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "homebank.h"
21 #include "hb-filter.h"
22
23 /****************************************************************************/
24 /* Debug macros */
25 /****************************************************************************/
26 #define MYDEBUG 0
27
28 #if MYDEBUG
29 #define DB(x) (x);
30 #else
31 #define DB(x);
32 #endif
33
34 /* our global datas */
35 extern struct HomeBank *GLOBALS;
36 extern struct Preferences *PREFS;
37
38
39
40
41 /* = = = = = = = = = = = = = = = = = = = = */
42 /* Filter */
43
44 Filter *da_filter_malloc(void)
45 {
46 return g_malloc0(sizeof(Filter));
47 }
48
49 void da_filter_free(Filter *flt)
50 {
51 if(flt != NULL)
52 {
53 g_free(flt->memo);
54 g_free(flt->info);
55 g_free(flt->tag);
56 g_free(flt);
57 }
58 }
59
60 /* = = = = = = = = = = = = = = = = = = = = */
61
62 gchar *filter_daterange_text_get(Filter *flt)
63 {
64 gchar buffer1[128];
65 gchar buffer2[128];
66 GDate *date;
67
68 date = g_date_new_julian(flt->mindate);
69 g_date_strftime (buffer1, 128-1, PREFS->date_format, date);
70 g_date_set_julian(date, flt->maxdate);
71 g_date_strftime (buffer2, 128-1, PREFS->date_format, date);
72 g_date_free(date);
73
74 return g_strdup_printf(_("<i>from</i> %s <i>to</i> %s"), buffer1, buffer2);
75 }
76
77
78
79 static void filter_default_date_set(Filter *flt)
80 {
81 flt->mindate = HB_MINDATE;
82 flt->maxdate = HB_MAXDATE;
83 }
84
85
86 static void filter_clear(Filter *flt)
87 {
88 guint i;
89
90 for(i=0;i<FILTER_MAX;i++)
91 {
92 flt->option[i] = 0;
93 }
94
95 g_free(flt->info);
96 g_free(flt->memo);
97 g_free(flt->tag);
98 flt->info = NULL;
99 flt->memo = NULL;
100 flt->tag = NULL;
101
102 *flt->last_tab = '\0';
103 }
104
105
106 void filter_default_all_set(Filter *flt)
107 {
108 GHashTableIter iter;
109 gpointer key, value;
110 gint i;
111
112 DB( g_print("(filter) reset %p\n", flt) );
113
114 filter_clear(flt);
115
116 flt->nbdaysfuture = 0;
117
118 flt->range = FLT_RANGE_LAST12MONTHS;
119 flt->type = FLT_TYPE_ALL;
120 flt->status = FLT_STATUS_ALL;
121
122 flt->forceremind = PREFS->showremind;
123
124 flt->option[FILTER_DATE] = 1;
125 filter_default_date_set(flt);
126
127 for(i=0;i<NUM_PAYMODE_MAX;i++)
128 flt->paymode[i] = TRUE;
129
130 filter_preset_daterange_set(flt, flt->range, 0);
131
132 // set all account
133 g_hash_table_iter_init (&iter, GLOBALS->h_acc);
134 while (g_hash_table_iter_next (&iter, &key, &value))
135 {
136 Account *item = value;
137 item->filter = TRUE;
138 }
139
140 // set all payee
141 g_hash_table_iter_init (&iter, GLOBALS->h_pay);
142 while (g_hash_table_iter_next (&iter, &key, &value))
143 {
144 Payee *item = value;
145 item->filter = TRUE;
146 }
147
148 // set all payee
149 g_hash_table_iter_init (&iter, GLOBALS->h_cat);
150 while (g_hash_table_iter_next (&iter, &key, &value))
151 {
152 Category *item = value;
153 item->filter = TRUE;
154 }
155
156 }
157
158
159 static void filter_set_date_bounds(Filter *flt, guint32 kacc)
160 {
161 GList *lst_acc, *lnk_acc;
162 GList *lnk_txn;
163
164 DB( g_print("(filter) set date bounds %p\n", flt) );
165
166 flt->mindate = 0;
167 flt->maxdate = 0;
168
169 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
170 lnk_acc = g_list_first(lst_acc);
171 while (lnk_acc != NULL)
172 {
173 Account *acc = lnk_acc->data;
174
175 //#1674045 ony rely on nosummary
176 //if( !(acc->flags & AF_CLOSED) )
177 {
178 Transaction *txn;
179
180 DB( g_print(" - do '%s'\n", acc->name) );
181
182 lnk_txn = g_queue_peek_head_link(acc->txn_queue);
183 if(lnk_txn) {
184 txn = lnk_txn->data;
185 if( (kacc == 0) || (txn->kacc == kacc) )
186 {
187 if( flt->mindate == 0 )
188 flt->mindate = txn->date;
189 else
190 flt->mindate = MIN(flt->mindate, txn->date);
191 }
192 }
193
194 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
195 if(lnk_txn) {
196 txn = lnk_txn->data;
197 if( (kacc == 0) || (txn->kacc == kacc) )
198 {
199 if( flt->maxdate == 0 )
200 flt->maxdate = txn->date;
201 else
202 flt->maxdate = MAX(flt->maxdate, txn->date);
203 }
204 }
205
206 }
207 lnk_acc = g_list_next(lnk_acc);
208 }
209
210 if( flt->mindate == 0 )
211 flt->mindate = HB_MINDATE;
212
213 if( flt->maxdate == 0 )
214 flt->maxdate = HB_MAXDATE;
215
216 g_list_free(lst_acc);
217 }
218
219
220 void filter_preset_daterange_add_futuregap(Filter *filter, gint nbdays)
221 {
222
223 if( nbdays <= 0 )
224 {
225 filter->nbdaysfuture = 0;
226 return;
227 }
228
229 switch( filter->range )
230 {
231 case FLT_RANGE_THISMONTH:
232 case FLT_RANGE_THISQUARTER:
233 case FLT_RANGE_THISYEAR:
234 case FLT_RANGE_LAST30DAYS:
235 case FLT_RANGE_LAST60DAYS:
236 case FLT_RANGE_LAST90DAYS:
237 case FLT_RANGE_LAST12MONTHS:
238 filter->nbdaysfuture = nbdays;
239 break;
240 }
241
242 }
243
244
245 void filter_preset_daterange_set(Filter *flt, gint range, guint32 kacc)
246 {
247 GDate *date;
248 guint32 refjuliandate, month, year, qnum;
249
250 DB( g_print("(filter) daterange set %p %d\n", flt, range) );
251
252 //filter_default_date_set(flt);
253 filter_set_date_bounds(flt, kacc);
254
255 flt->range = range;
256
257 // by default refjuliandate is today
258 // but we adjust if to max transaction date found
259 // removed for 5.0.4
260 refjuliandate = GLOBALS->today;
261 /*if(flt->maxdate < refjuliandate)
262 refjuliandate = flt->maxdate;*/
263
264 date = g_date_new_julian(refjuliandate);
265 month = g_date_get_month(date);
266 year = g_date_get_year(date);
267 qnum = ((month - 1) / 3) + 1;
268
269 DB( g_print("m=%d, y=%d, qnum=%d\n", month, year, qnum) );
270
271 switch( range )
272 {
273 case FLT_RANGE_THISMONTH:
274 g_date_set_day(date, 1);
275 flt->mindate = g_date_get_julian(date);
276 g_date_add_days(date, g_date_get_days_in_month(month, year)-1);
277 flt->maxdate = g_date_get_julian(date);
278 break;
279
280 case FLT_RANGE_LASTMONTH:
281 g_date_set_day(date, 1);
282 g_date_subtract_months(date, 1);
283 flt->mindate = g_date_get_julian(date);
284 month = g_date_get_month(date);
285 year = g_date_get_year(date);
286 g_date_add_days(date, g_date_get_days_in_month(month, year)-1);
287 flt->maxdate = g_date_get_julian(date);
288 break;
289
290 case FLT_RANGE_THISQUARTER:
291 g_date_set_day(date, 1);
292 g_date_set_month(date, (qnum-1)*3+1);
293 flt->mindate = g_date_get_julian(date);
294 g_date_add_months(date, 3);
295 g_date_subtract_days(date, 1);
296 flt->maxdate = g_date_get_julian(date);
297 break;
298
299 case FLT_RANGE_LASTQUARTER:
300 g_date_set_day(date, 1);
301 g_date_set_month(date, (qnum-1)*3+1);
302 g_date_subtract_months(date, 3);
303 flt->mindate = g_date_get_julian(date);
304 g_date_add_months(date, 3);
305 g_date_subtract_days(date, 1);
306 flt->maxdate = g_date_get_julian(date);
307 break;
308
309 case FLT_RANGE_THISYEAR:
310 g_date_set_dmy(date, PREFS->fisc_year_day, PREFS->fisc_year_month, year);
311 if( refjuliandate >= g_date_get_julian (date))
312 {
313 flt->mindate = g_date_get_julian(date);
314 }
315 else
316 {
317 g_date_set_dmy(date, PREFS->fisc_year_day, PREFS->fisc_year_month, year-1);
318 flt->mindate = g_date_get_julian(date);
319 }
320 g_date_add_years (date, 1);
321 g_date_subtract_days (date, 1);
322 flt->maxdate = g_date_get_julian(date);
323 break;
324
325 case FLT_RANGE_LASTYEAR:
326 g_date_set_dmy(date, PREFS->fisc_year_day, PREFS->fisc_year_month, year);
327 if( refjuliandate >= g_date_get_julian (date))
328 {
329 g_date_set_dmy(date, PREFS->fisc_year_day, PREFS->fisc_year_month, year-1);
330 flt->mindate = g_date_get_julian(date);
331 }
332 else
333 {
334 g_date_set_dmy(date, PREFS->fisc_year_day, PREFS->fisc_year_month, year-2);
335 flt->mindate = g_date_get_julian(date);
336 }
337 g_date_add_years (date, 1);
338 g_date_subtract_days (date, 1);
339 flt->maxdate = g_date_get_julian(date);
340 break;
341
342 case FLT_RANGE_LAST30DAYS:
343 flt->mindate = refjuliandate - 30;
344 flt->maxdate = refjuliandate;
345 break;
346
347 case FLT_RANGE_LAST60DAYS:
348 flt->mindate = refjuliandate - 60;
349 flt->maxdate = refjuliandate;
350 break;
351
352 case FLT_RANGE_LAST90DAYS:
353 flt->mindate = refjuliandate - 90;
354 flt->maxdate = refjuliandate;
355 break;
356
357 case FLT_RANGE_LAST12MONTHS:
358 g_date_subtract_months(date, 12);
359 flt->mindate = g_date_get_julian(date);
360 flt->maxdate = refjuliandate;
361 break;
362
363 // case FLT_RANGE_OTHER:
364
365 // case FLT_RANGE_ALLDATE:
366
367
368 }
369 g_date_free(date);
370
371 }
372
373 void filter_preset_type_set(Filter *flt, gint type)
374 {
375
376 /* any type */
377 flt->type = type;
378 flt->option[FILTER_AMOUNT] = 0;
379 flt->minamount = G_MINDOUBLE;
380 flt->maxamount = G_MINDOUBLE;
381
382 switch( type )
383 {
384 case FLT_TYPE_EXPENSE:
385 flt->option[FILTER_AMOUNT] = 1;
386 flt->minamount = -G_MAXDOUBLE;
387 flt->maxamount = G_MINDOUBLE;
388 break;
389
390 case FLT_TYPE_INCOME:
391 flt->option[FILTER_AMOUNT] = 1;
392 flt->minamount = G_MINDOUBLE;
393 flt->maxamount = G_MAXDOUBLE;
394 break;
395 }
396
397 }
398
399
400 void filter_preset_status_set(Filter *flt, gint status)
401 {
402 Category *catitem;
403 GList *lcat, *list;
404
405 /* any status */
406 flt->status = status;
407 flt->option[FILTER_STATUS] = 0;
408 flt->reconciled = TRUE;
409 flt->cleared = TRUE;
410 //#1602835 fautly set
411 //flt->forceadd = TRUE;
412 //flt->forcechg = TRUE;
413
414 flt->option[FILTER_CATEGORY] = 0;
415 lcat = list = g_hash_table_get_values(GLOBALS->h_cat);
416 while (list != NULL)
417 {
418 catitem = list->data;
419 catitem->filter = FALSE;
420 list = g_list_next(list);
421 }
422 g_list_free(lcat);
423
424 switch( status )
425 {
426 case FLT_STATUS_UNCATEGORIZED:
427 flt->option[FILTER_CATEGORY] = 1;
428 catitem = da_cat_get(0); // no category
429 if(catitem != NULL)
430 catitem->filter = TRUE;
431 break;
432
433 case FLT_STATUS_UNRECONCILED:
434 flt->option[FILTER_STATUS] = 2;
435 flt->reconciled = TRUE;
436 flt->cleared = FALSE;
437 break;
438
439 case FLT_STATUS_UNCLEARED:
440 flt->option[FILTER_STATUS] = 2;
441 flt->reconciled = FALSE;
442 flt->cleared = TRUE;
443 break;
444
445 case FLT_STATUS_RECONCILED:
446 flt->option[FILTER_STATUS] = 1;
447 flt->reconciled = TRUE;
448 flt->cleared = FALSE;
449 break;
450
451 case FLT_STATUS_CLEARED:
452 flt->option[FILTER_STATUS] = 1;
453 flt->reconciled = FALSE;
454 flt->cleared = TRUE;
455 break;
456
457 }
458 }
459
460
461 static gint filter_text_compare(gchar *txntext, gchar *searchtext, gboolean exact)
462 {
463 gint retval = 0;
464
465 if( exact )
466 {
467 if( g_strstr_len(txntext, -1, searchtext) != NULL )
468 {
469 DB( g_print(" found case '%s'\n", searchtext) );
470 retval = 1;
471 }
472 }
473 else
474 {
475 gchar *word = g_utf8_casefold(txntext, -1);
476 gchar *needle = g_utf8_casefold(searchtext, -1);
477
478 if( g_strrstr(word, needle) != NULL )
479 {
480 DB( g_print(" found nocase '%s'\n", needle) );
481 retval = 1;
482 }
483
484 g_free(word);
485 g_free(needle);
486 }
487 return retval;
488 }
489
490
491 /* used for quicksearch text into transaction */
492 gboolean filter_txn_search_match(gchar *needle, Transaction *txn, gint flags)
493 {
494 gboolean retval = FALSE;
495 Payee *payitem;
496 Category *catitem;
497 gchar *tags;
498
499 if(flags & FLT_QSEARCH_MEMO)
500 {
501 //#1668036 always try match on txn memo first
502 if(txn->memo)
503 {
504 retval |= filter_text_compare(txn->memo, needle, FALSE);
505 }
506 if(retval) goto end;
507
508 //#1509485
509 if(txn->flags & OF_SPLIT)
510 {
511 guint count, i;
512 Split *split;
513
514 count = da_splits_count(txn->splits);
515 for(i=0;i<count;i++)
516 {
517 gint tmpinsert = 0;
518
519 split = txn->splits[i];
520 tmpinsert = filter_text_compare(split->memo, needle, FALSE);
521 retval |= tmpinsert;
522 if( tmpinsert )
523 break;
524 }
525 }
526 if(retval) goto end;
527 }
528
529 if(flags & FLT_QSEARCH_INFO)
530 {
531 if(txn->info)
532 {
533 retval |= filter_text_compare(txn->info, needle, FALSE);
534 }
535 if(retval) goto end;
536 }
537
538 if(flags & FLT_QSEARCH_PAYEE)
539 {
540 payitem = da_pay_get(txn->kpay);
541 if(payitem)
542 {
543 retval |= filter_text_compare(payitem->name, needle, FALSE);
544 }
545 if(retval) goto end;
546 }
547
548 if(flags & FLT_QSEARCH_CATEGORY)
549 {
550 //#1509485
551 if(txn->flags & OF_SPLIT)
552 {
553 guint count, i;
554 Split *split;
555
556 count = da_splits_count(txn->splits);
557 for(i=0;i<count;i++)
558 {
559 gint tmpinsert = 0;
560
561 split = txn->splits[i];
562 catitem = da_cat_get(split->kcat);
563 if(catitem)
564 {
565 gchar *fullname = da_cat_get_fullname (catitem);
566
567 tmpinsert = filter_text_compare(fullname, needle, FALSE);
568 retval |= tmpinsert;
569 g_free(fullname);
570 }
571
572 if( tmpinsert )
573 break;
574 }
575 }
576 else
577 {
578 catitem = da_cat_get(txn->kcat);
579 if(catitem)
580 {
581 gchar *fullname = da_cat_get_fullname (catitem);
582
583 retval |= filter_text_compare(fullname, needle, FALSE);
584 g_free(fullname);
585 }
586 }
587 if(retval) goto end;
588 }
589
590 if(flags & FLT_QSEARCH_TAGS)
591 {
592 tags = transaction_tags_tostring(txn);
593 if(tags)
594 {
595 retval |= filter_text_compare(tags, needle, FALSE);
596 }
597 g_free(tags);
598 if(retval) goto end;
599 }
600
601 //#1741339 add quicksearch for amount
602 if(flags & FLT_QSEARCH_AMOUNT)
603 {
604 gchar formatd_buf[G_ASCII_DTOSTR_BUF_SIZE];
605
606 hb_strfnum(formatd_buf, G_ASCII_DTOSTR_BUF_SIZE-1, txn->amount, txn->kcur, FALSE);
607 retval |= filter_text_compare(formatd_buf, needle, FALSE);
608 }
609
610
611 end:
612 return retval;
613 }
614
615
616 gint filter_test(Filter *flt, Transaction *txn)
617 {
618 Account *accitem;
619 Payee *payitem;
620 Category *catitem;
621 gint insert;
622
623 //DB( g_print("(filter) test\n") );
624
625 insert = 1;
626
627 /*** start filtering ***/
628
629 /* force display */
630 if(flt->forceadd == TRUE && (txn->flags & OF_ADDED))
631 goto end;
632
633 if(flt->forcechg == TRUE && (txn->flags & OF_CHANGED))
634 goto end;
635
636 /* force remind if not filter on status */
637 if(flt->forceremind == TRUE && (txn->status == TXN_STATUS_REMIND))
638 goto end;
639
640 /* date */
641 if(flt->option[FILTER_DATE]) {
642 insert = ( (txn->date >= flt->mindate) && (txn->date <= (flt->maxdate + flt->nbdaysfuture) ) ) ? 1 : 0;
643 if(flt->option[FILTER_DATE] == 2) insert ^= 1;
644 }
645 if(!insert) goto end;
646
647 /* account */
648 if(flt->option[FILTER_ACCOUNT]) {
649 accitem = da_acc_get(txn->kacc);
650 if(accitem)
651 {
652 insert = ( accitem->filter == TRUE ) ? 1 : 0;
653 if(flt->option[FILTER_ACCOUNT] == 2) insert ^= 1;
654 }
655 }
656 if(!insert) goto end;
657
658 /* payee */
659 if(flt->option[FILTER_PAYEE]) {
660 payitem = da_pay_get(txn->kpay);
661 if(payitem)
662 {
663 insert = ( payitem->filter == TRUE ) ? 1 : 0;
664 if(flt->option[FILTER_PAYEE] == 2) insert ^= 1;
665 }
666 }
667 if(!insert) goto end;
668
669 /* category */
670 if(flt->option[FILTER_CATEGORY]) {
671 if(txn->flags & OF_SPLIT)
672 {
673 guint count, i;
674 Split *split;
675
676 insert = 0; //fix: 1151259
677 count = da_splits_count(txn->splits);
678 for(i=0;i<count;i++)
679 {
680 gint tmpinsert = 0;
681
682 split = txn->splits[i];
683 catitem = da_cat_get(split->kcat);
684 if(catitem)
685 {
686 tmpinsert = ( catitem->filter == TRUE ) ? 1 : 0;
687 if(flt->option[FILTER_CATEGORY] == 2) tmpinsert ^= 1;
688 }
689 insert |= tmpinsert;
690 }
691 }
692 else
693 {
694 catitem = da_cat_get(txn->kcat);
695 if(catitem)
696 {
697 insert = ( catitem->filter == TRUE ) ? 1 : 0;
698 if(flt->option[FILTER_CATEGORY] == 2) insert ^= 1;
699 }
700 }
701 }
702 if(!insert) goto end;
703
704 /* status */
705 if(flt->option[FILTER_STATUS]) {
706 gint insert1 = 0, insert2 = 0;
707
708 if(flt->reconciled)
709 insert1 = ( txn->status == TXN_STATUS_RECONCILED ) ? 1 : 0;
710 if(flt->cleared)
711 insert2 = ( txn->status == TXN_STATUS_CLEARED ) ? 1 : 0;
712
713 insert = insert1 | insert2;
714 if(flt->option[FILTER_STATUS] == 2) insert ^= 1;
715 }
716 if(!insert) goto end;
717
718 /* paymode */
719 if(flt->option[FILTER_PAYMODE]) {
720 insert = ( flt->paymode[txn->paymode] == TRUE) ? 1 : 0;
721 if(flt->option[FILTER_PAYMODE] == 2) insert ^= 1;
722 }
723 if(!insert) goto end;
724
725 /* amount */
726 if(flt->option[FILTER_AMOUNT]) {
727 insert = ( (txn->amount >= flt->minamount) && (txn->amount <= flt->maxamount) ) ? 1 : 0;
728
729 if(flt->option[FILTER_AMOUNT] == 2) insert ^= 1;
730 }
731 if(!insert) goto end;
732
733 /* info/memo/tag */
734 if(flt->option[FILTER_TEXT])
735 {
736 gchar *tags;
737 gint insert1, insert2, insert3;
738
739 insert1 = insert2 = insert3 = 0;
740 if(flt->info)
741 {
742 if(txn->info)
743 {
744 insert1 = filter_text_compare(txn->info, flt->info, flt->exact);
745 }
746 }
747 else
748 insert1 = 1;
749
750 if(flt->memo)
751 {
752 //#1668036 always try match on txn memo first
753 if(txn->memo)
754 {
755 insert2 = filter_text_compare(txn->memo, flt->memo, flt->exact);
756 }
757
758 if( (insert2 == 0) && (txn->flags & OF_SPLIT) )
759 {
760 guint count, i;
761 Split *split;
762
763 count = da_splits_count(txn->splits);
764 for(i=0;i<count;i++)
765 {
766 gint tmpinsert = 0;
767
768 split = txn->splits[i];
769 tmpinsert = filter_text_compare(split->memo, flt->memo, flt->exact);
770 insert2 |= tmpinsert;
771 if( tmpinsert )
772 break;
773 }
774 }
775 }
776 else
777 insert2 = 1;
778
779 if(flt->tag)
780 {
781 tags = transaction_tags_tostring(txn);
782 if(tags)
783 {
784 insert3 = filter_text_compare(tags, flt->tag, flt->exact);
785 }
786 g_free(tags);
787 }
788 else
789 insert3 = 1;
790
791 insert = insert1 && insert2 && insert3 ? 1 : 0;
792
793 if(flt->option[FILTER_TEXT] == 2) insert ^= 1;
794
795 }
796 if(!insert) goto end;
797
798 end:
799 // DB( g_print(" %d :: %d :: %d\n", flt->mindate, txn->date, flt->maxdate) );
800 // DB( g_print(" [%d] %s => %d (%d)\n", txn->account, txn->memo, insert, count) );
801 return(insert);
802 }
803
This page took 0.067796 seconds and 5 git commands to generate.