1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 Maxime DOYEN
4 * This file is part of HomeBank.
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.
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.
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/>.
21 #include "hb-category.h"
27 /****************************************************************************/
29 /****************************************************************************/
38 /* our global datas */
39 extern struct HomeBank
*GLOBALS
;
41 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
44 da_cat_clone(Category
*src_item
)
46 Category
*new_item
= rc_dup(src_item
, sizeof(Category
));
48 DB( g_print("da_cat_clone\n") );
51 //duplicate the string
52 new_item
->name
= g_strdup(src_item
->name
);
53 new_item
->fullname
= g_strdup(src_item
->fullname
);
60 da_cat_free(Category
*item
)
62 DB( g_print("da_cat_free\n") );
65 DB( g_print(" => %d, %s\n", item
->key
, item
->name
) );
68 g_free(item
->fullname
);
77 DB( g_print("da_cat_malloc\n") );
78 return rc_alloc(sizeof(Category
));
85 DB( g_print("da_cat_destroy\n") );
86 g_hash_table_destroy(GLOBALS
->h_cat
);
95 DB( g_print("da_cat_new\n") );
96 GLOBALS
->h_cat
= g_hash_table_new_full(g_int_hash
, g_int_equal
, (GDestroyNotify
)g_free
, (GDestroyNotify
)da_cat_free
);
98 // insert our 'no category'
99 item
= da_cat_malloc();
101 item
->name
= g_strdup("");
102 item
->fullname
= g_strdup("");
107 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
112 * Return value: the number of elements
117 return g_hash_table_size(GLOBALS
->h_cat
);
122 da_cat_max_key_ghfunc(gpointer key
, Category
*cat
, guint32
*max_key
)
124 *max_key
= MAX(*max_key
, cat
->key
);
128 * da_cat_get_max_key:
130 * Get the biggest key from the GHashTable
132 * Return value: the biggest key value
136 da_cat_get_max_key(void)
140 g_hash_table_foreach(GLOBALS
->h_cat
, (GHFunc
)da_cat_max_key_ghfunc
, &max_key
);
146 da_cat_remove_grfunc(gpointer key
, Category
*cat
, guint32
*remkey
)
148 if(cat
->key
== *remkey
|| cat
->parent
== *remkey
)
158 * delete a category from the GHashTable
160 * Return value: TRUE if the key was found and deleted
164 da_cat_remove(guint32 key
)
166 DB( g_print("\nda_cat_remove %d\n", key
) );
168 return g_hash_table_foreach_remove(GLOBALS
->h_cat
, (GHRFunc
)da_cat_remove_grfunc
, &key
);
173 da_cat_build_fullname(Category
*item
)
177 g_free(item
->fullname
);
178 if( item
->parent
== 0 )
179 item
->fullname
= g_strdup(item
->name
);
182 parent
= da_cat_get(item
->parent
);
184 item
->fullname
= g_strconcat(parent
->name
, ":", item
->name
, NULL
);
187 DB( g_print("- updated %d:'%s' fullname='%s'\n", item
->key
, item
->name
, item
->fullname
) );
193 da_cat_rename(Category
*item
, gchar
*newname
)
196 DB( g_print("- renaming %s' => '%s'\n", item
->name
, newname
) );
199 item
->name
= g_strdup(newname
);
200 da_cat_build_fullname(item
);
202 if( item
->parent
== 0 )
207 DB( g_print("- updating subcat fullname\n") );
209 g_hash_table_iter_init (&iter
, GLOBALS
->h_cat
);
210 while (g_hash_table_iter_next (&iter
, NULL
, &value
))
212 Category
*subcat
= value
;
214 if( subcat
->parent
== item
->key
)
215 da_cat_build_fullname(subcat
);
225 * insert a category into the GHashTable
227 * Return value: TRUE if inserted
231 da_cat_insert(Category
*item
)
235 DB( g_print("\nda_cat_insert\n") );
237 DB( g_print("- '%s'\n", item
->name
) );
239 new_key
= g_new0(guint32
, 1);
240 *new_key
= item
->key
;
241 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, item
);
243 da_cat_build_fullname(item
);
252 * append a category into the GHashTable
254 * Return value: TRUE if inserted
257 // used only to add cat/subcat from ui_category with the 2 inputs
259 da_cat_append(Category
*cat
)
263 DB( g_print("\nda_cat_append\n") );
266 da_cat_build_fullname(cat
);
268 existitem
= da_cat_get_by_fullname( cat
->fullname
);
269 if( existitem
== NULL
)
271 cat
->key
= da_cat_get_max_key() + 1;
276 DB( g_print(" -> %s already exist\n", cat
->name
) );
283 /* fullname i.e. car:refuel */
284 struct fullcatcontext
292 da_cat_fullname_grfunc(gpointer key
, Category
*item
, struct fullcatcontext
*ctx
)
295 //DB( g_print("'%s' == '%s'\n", ctx->name, item->name) );
296 if( item
->parent
== ctx
->parent
)
298 if( ctx
->name
&& item
->name
)
299 if(!strcasecmp(ctx
->name
, item
->name
))
306 static Category
*da_cat_get_by_name_find_internal(guint32 parent
, gchar
*name
)
308 struct fullcatcontext ctx
;
312 DB( g_print("- searching %s %d '%s'\n", (parent
== 0) ? "lv1cat" : "lv2cat", parent
, name
) );
313 return g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
317 static gchar
**da_cat_get_by_fullname_split_clean(gchar
*rawfullname
, guint
*outlen
)
319 gchar
**partstr
= g_strsplit(rawfullname
, ":", 2);
320 guint len
= g_strv_length(partstr
);
321 gboolean valid
= TRUE
;
323 DB( g_print("- spliclean '%s' - %d parts\n", rawfullname
, g_strv_length(partstr
)) );
330 g_strstrip(partstr
[0]);
331 if( strlen(partstr
[0]) == 0 )
336 g_strstrip(partstr
[1]);
337 if( strlen(partstr
[1]) == 0 )
345 DB( g_print("- is invalid\n") );
353 da_cat_get_by_fullname(gchar
*rawfullname
)
356 Category
*parent
= NULL
;
357 Category
*retval
= NULL
;
360 DB( g_print("\nda_cat_get_by_fullname\n") );
364 if( (partstr
= da_cat_get_by_fullname_split_clean(rawfullname
, &len
)) != NULL
)
368 parent
= da_cat_get_by_name_find_internal(0, partstr
[0]);
372 if( len
== 2 && parent
!= NULL
)
374 retval
= da_cat_get_by_name_find_internal(parent
->key
, partstr
[1]);
386 * da_cat_append_ifnew_by_fullname:
388 * append a category if it is new by fullname
394 da_cat_append_ifnew_by_fullname(gchar
*rawfullname
)
397 Category
*parent
= NULL
;
398 Category
*newcat
= NULL
;
399 Category
*retval
= NULL
;
402 DB( g_print("\nda_cat_append_ifnew_by_fullname\n") );
406 if( (partstr
= da_cat_get_by_fullname_split_clean(rawfullname
, &len
)) != NULL
)
410 parent
= da_cat_get_by_name_find_internal(0, partstr
[0]);
413 parent
= da_cat_malloc();
414 parent
->key
= da_cat_get_max_key() + 1;
415 parent
->name
= g_strdup(partstr
[0]);
416 da_cat_insert(parent
);
421 /* if we have a subcategory - xxx:xxx */
422 if( len
== 2 && parent
!= NULL
)
424 newcat
= da_cat_get_by_name_find_internal(parent
->key
, partstr
[1]);
427 newcat
= da_cat_malloc();
428 newcat
->key
= da_cat_get_max_key() + 1;
429 newcat
->parent
= parent
->key
;
430 newcat
->name
= g_strdup(partstr
[1]);
431 newcat
->flags
|= GF_SUB
;
432 //#1713413 take parent type into account
433 if(parent
->flags
& GF_INCOME
)
434 newcat
->flags
|= GF_INCOME
;
435 da_cat_insert(newcat
);
451 * Get a category structure by key
453 * Return value: Category * or NULL if not found
457 da_cat_get(guint32 key
)
459 //DB( g_print("da_cat_get\n") );
461 return g_hash_table_lookup(GLOBALS
->h_cat
, &key
);
465 gchar
*da_cat_get_name(Category
*item
)
471 name
= item
->key
== 0 ? _("(no category)") : item
->fullname
;
477 void da_cat_consistency(Category
*item
)
481 if((item
->flags
& GF_SUB
) && item
->key
> 0)
483 //check for existing parent
484 if( da_cat_get(item
->parent
) == NULL
)
486 Category
*parent
= da_cat_append_ifnew_by_fullname ("orphaned");
488 item
->parent
= parent
->key
;
489 da_cat_build_fullname(item
);
490 g_warning("category consistency: fixed missing parent %d", item
->parent
);
494 // ensure type equal for categories and its children
495 if(!(item
->flags
& GF_SUB
) && item
->key
> 0)
497 isIncome
= (item
->flags
& GF_INCOME
) ? TRUE
: FALSE
;
498 if( category_change_type(item
, isIncome
) > 0 )
500 g_warning("category consistency: fixed type for child");
501 GLOBALS
->changes_count
++;
505 if( item
->name
!= NULL
)
506 g_strstrip(item
->name
);
509 item
->name
= g_strdup("void");
510 da_cat_build_fullname(item
);
511 g_warning("category consistency: fixed null name");
512 GLOBALS
->changes_count
++;
519 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
524 da_cat_debug_list_ghfunc(gpointer key
, gpointer value
, gpointer user_data
)
527 Category
*cat
= value
;
529 DB( g_print(" %d :: %s (parent=%d\n", *id
, cat
->name
, cat
->parent
) );
534 da_cat_debug_list(void)
537 DB( g_print("\n** debug **\n") );
539 g_hash_table_foreach(GLOBALS
->h_cat
, da_cat_debug_list_ghfunc
, NULL
);
541 DB( g_print("\n** end debug **\n") );
549 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
552 guint32
category_report_id(guint32 key
, gboolean subcat
)
558 Category
*catentry
= da_cat_get(key
);
560 retval
= (catentry
->flags
& GF_SUB
) ? catentry
->parent
: catentry
->key
;
566 //DB( g_print("- cat '%s' reportid = %d\n", catentry->name, retval) );
572 category_delete_unused(void)
576 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
579 Category
*entry
= list
->data
;
581 if(entry
->usage_count
<= 0 && entry
->key
> 0)
582 da_cat_remove (entry
->key
);
584 list
= g_list_next(list
);
591 category_fill_usage_count(guint32 kcat
)
593 Category
*cat
= da_cat_get (kcat
);
599 if( cat
->parent
> 0 )
601 parent
= da_cat_get(cat
->parent
);
604 parent
->usage_count
++;
612 category_fill_usage(void)
615 GList
*lst_acc
, *lnk_acc
;
617 GList
*lpay
, *lrul
, *list
;
620 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
623 Category
*entry
= list
->data
;
624 entry
->usage_count
= 0;
625 list
= g_list_next(list
);
630 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
631 lnk_acc
= g_list_first(lst_acc
);
632 while (lnk_acc
!= NULL
)
634 Account
*acc
= lnk_acc
->data
;
636 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
637 while (lnk_txn
!= NULL
)
639 Transaction
*txn
= lnk_txn
->data
;
641 //#1689308 count split as well
642 if( txn
->flags
& OF_SPLIT
)
644 nbsplit
= da_splits_length(txn
->splits
);
645 for(i
=0;i
<nbsplit
;i
++)
647 Split
*split
= da_splits_get(txn
->splits
, i
);
649 category_fill_usage_count(split
->kcat
);
653 category_fill_usage_count(txn
->kcat
);
655 lnk_txn
= g_list_next(lnk_txn
);
657 lnk_acc
= g_list_next(lnk_acc
);
659 g_list_free(lst_acc
);
661 lpay
= list
= g_hash_table_get_values(GLOBALS
->h_pay
);
664 Payee
*entry
= list
->data
;
666 category_fill_usage_count(entry
->kcat
);
667 list
= g_list_next(list
);
672 list
= g_list_first(GLOBALS
->arc_list
);
675 Archive
*entry
= list
->data
;
677 //#1689308 count split as well
678 if( entry
->flags
& OF_SPLIT
)
680 nbsplit
= da_splits_length(entry
->splits
);
681 for(i
=0;i
<nbsplit
;i
++)
683 Split
*split
= da_splits_get(entry
->splits
, i
);
685 category_fill_usage_count(split
->kcat
);
689 category_fill_usage_count(entry
->kcat
);
691 list
= g_list_next(list
);
695 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
698 Assign
*entry
= list
->data
;
700 category_fill_usage_count(entry
->kcat
);
701 list
= g_list_next(list
);
709 category_move(guint32 key1
, guint32 key2
)
711 GList
*lst_acc
, *lnk_acc
;
716 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
717 lnk_acc
= g_list_first(lst_acc
);
718 while (lnk_acc
!= NULL
)
720 Account
*acc
= lnk_acc
->data
;
722 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
723 while (lnk_txn
!= NULL
)
725 Transaction
*txn
= lnk_txn
->data
;
727 if(txn
->kcat
== key1
)
730 txn
->flags
|= OF_CHANGED
;
733 // move split category #1340142
734 nbsplit
= da_splits_length(txn
->splits
);
735 for(i
=0;i
<nbsplit
;i
++)
737 Split
*split
= da_splits_get(txn
->splits
, i
);
739 if( split
->kcat
== key1
)
742 txn
->flags
|= OF_CHANGED
;
746 lnk_txn
= g_list_next(lnk_txn
);
749 lnk_acc
= g_list_next(lnk_acc
);
751 g_list_free(lst_acc
);
754 list
= g_list_first(GLOBALS
->arc_list
);
757 Archive
*entry
= list
->data
;
758 if(entry
->kcat
== key1
)
762 list
= g_list_next(list
);
765 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
768 Assign
*entry
= list
->data
;
770 if(entry
->kcat
== key1
)
774 list
= g_list_next(list
);
782 category_rename(Category
*item
, const gchar
*newname
)
784 Category
*parent
, *existitem
;
785 gchar
*fullname
= NULL
;
789 DB( g_print("\n(category) rename\n") );
791 stripname
= g_strdup(newname
);
792 g_strstrip(stripname
);
794 if( item
->parent
== 0)
795 fullname
= g_strdup(stripname
);
798 parent
= da_cat_get(item
->parent
);
801 fullname
= g_strdup_printf("%s:%s", parent
->name
, stripname
);
805 DB( g_print(" - search: %s\n", fullname
) );
807 existitem
= da_cat_get_by_fullname( fullname
);
809 if( existitem
!= NULL
&& existitem
->key
!= item
->key
)
811 DB( g_print("- error, same name already exist with other key %d <> %d\n",existitem
->key
, item
->key
) );
816 DB( g_print("- renaming\n") );
818 da_cat_rename (item
, stripname
);
830 category_glist_name_compare_func(Category
*c1
, Category
*c2
)
834 if( c1
!= NULL
&& c2
!= NULL
)
836 retval
= hb_string_utf8_compare(c1
->fullname
, c2
->fullname
);
843 category_glist_key_compare_func(Category
*a
, Category
*b
)
845 gint ka
, kb
, retval
= 0;
847 if(a
->parent
== 0 && b
->parent
== a
->key
)
850 if(b
->parent
== 0 && a
->parent
== b
->key
)
854 ka
= a
->parent
!= 0 ? a
->parent
: a
->key
;
855 kb
= b
->parent
!= 0 ? b
->parent
: b
->key
;
872 DB( g_print("compare a=%2d:%2d to b=%2d:%2d :: %d [%s]\n", a
->key
, a
->parent
, b
->key
, b
->parent
, retval
, str
) );
880 category_glist_sorted(gint column
)
882 GList
*list
= g_hash_table_get_values(GLOBALS
->h_cat
);
885 return g_list_sort(list
, (GCompareFunc
)category_glist_key_compare_func
);
887 return g_list_sort(list
, (GCompareFunc
)category_glist_name_compare_func
);
892 category_load_csv(gchar
*filename
, gchar
**error
)
899 gchar
*lastcatname
= NULL
;
904 const gchar
*encoding
;
906 encoding
= homebank_file_getencoding(filename
);
907 DB( g_print(" -> encoding should be %s\n", encoding
) );
911 io
= g_io_channel_new_file(filename
, "r", NULL
);
914 if( encoding
!= NULL
)
916 g_io_channel_set_encoding(io
, encoding
, NULL
);
923 io_stat
= g_io_channel_read_line(io
, &tmpstr
, NULL
, NULL
, &err
);
925 DB( g_print(" + iostat %d\n", io_stat
) );
927 if( io_stat
== G_IO_STATUS_ERROR
)
929 DB (g_print(" + ERROR %s\n",err
->message
));
932 if( io_stat
== G_IO_STATUS_EOF
)
934 if( io_stat
== G_IO_STATUS_NORMAL
)
938 DB( g_print(" + strip %s\n", tmpstr
) );
939 hb_string_strip_crlf(tmpstr
);
941 DB( g_print(" + split\n") );
942 str_array
= g_strsplit (tmpstr
, ";", 3);
945 if( g_strv_length (str_array
) != 3 )
947 *error
= _("invalid CSV format");
949 DB( g_print(" + error %s\n", *error
) );
953 DB( g_print(" + read %s : %s : %s\n", str_array
[0], str_array
[1], str_array
[2]) );
956 if( g_str_has_prefix(str_array
[0], "1") )
958 fullcatname
= g_strdup(str_array
[2]);
960 lastcatname
= g_strdup(str_array
[2]);
962 type
= g_str_has_prefix(str_array
[1], "+") ? GF_INCOME
: 0;
964 DB( g_print(" + type = %d\n", type
) );
968 if( g_str_has_prefix(str_array
[0], "2") )
970 fullcatname
= g_strdup_printf("%s:%s", lastcatname
, str_array
[2]);
973 item
= da_cat_append_ifnew_by_fullname(fullcatname
);
974 DB( g_print(" + item %p\n", item
) );
978 DB( g_print(" + assign flags: '%c'\n", type
) );
985 g_strfreev (str_array
);
991 g_io_channel_unref (io
);
1001 category_save_csv(gchar
*filename
, gchar
**error
)
1003 gboolean retval
= FALSE
;
1008 io
= g_io_channel_new_file(filename
, "w", NULL
);
1011 lcat
= list
= category_glist_sorted(1);
1013 while (list
!= NULL
)
1015 Category
*item
= list
->data
;
1021 if( item
->parent
== 0)
1024 type
= (item
->flags
& GF_INCOME
) ? '+' : '-';
1032 outstr
= g_strdup_printf("%c;%c;%s\n", lvel
, type
, item
->name
);
1034 DB( g_print(" + export %s\n", outstr
) );
1036 g_io_channel_write_chars(io
, outstr
, -1, NULL
, NULL
);
1040 list
= g_list_next(list
);
1047 g_io_channel_unref (io
);
1055 category_type_get(Category
*item
)
1057 if( (item
->flags
& (GF_INCOME
)) )
1064 category_get_type_char(Category
*item
)
1066 return (item
->flags
& GF_INCOME
) ? '+' : '-';
1071 category_change_type_eval(Category
*item
, gboolean isIncome
)
1073 if( (item
->flags
& (GF_INCOME
)) && !isIncome
)
1080 category_change_type(Category
*item
, gboolean isIncome
)
1085 changes
+= category_change_type_eval(item
, isIncome
);
1087 item
->flags
&= ~(GF_INCOME
); //delete flag
1088 if(isIncome
== TRUE
)
1089 item
->flags
|= GF_INCOME
;
1091 // change also childs
1092 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
1093 while (list
!= NULL
)
1095 Category
*child
= list
->data
;
1097 if(child
->parent
== item
->key
)
1099 changes
+= category_change_type_eval(child
, isIncome
);
1100 child
->flags
&= ~(GF_INCOME
); //delete flag
1101 if(isIncome
== TRUE
)
1102 child
->flags
|= GF_INCOME
;
1104 list
= g_list_next(list
);
1114 * category_find_preset:
1116 * find a user language compatible file for category preset
1118 * Return value: a pathname to the file or NULL
1122 category_find_preset(gchar
**lang
)
1129 DB( g_print("** category_find_preset **\n") );
1131 langs
= (gchar
**)g_get_language_names ();
1133 DB( g_print(" -> %d languages detected\n", g_strv_length(langs
)) );
1135 for(i
=0;i
<g_strv_length(langs
);i
++)
1137 DB( g_print(" -> %d '%s'\n", i
, langs
[i
]) );
1138 filename
= g_strdup_printf("hb-categories-%s.csv", langs
[i
]);
1139 gchar
*pathfilename
= g_build_filename(homebank_app_get_datas_dir(), filename
, NULL
);
1140 exists
= g_file_test(pathfilename
, G_FILE_TEST_EXISTS
);
1141 DB( g_print(" -> '%s' exists=%d\n", pathfilename
, exists
) );
1146 return pathfilename
;
1149 g_free(pathfilename
);
1152 DB( g_print("return NULL\n") );