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"
24 /****************************************************************************/
26 /****************************************************************************/
35 /* our global datas */
36 extern struct HomeBank
*GLOBALS
;
38 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
41 da_cat_clone(Category
*src_item
)
43 Category
*new_item
= g_memdup(src_item
, sizeof(Category
));
45 DB( g_print("da_cat_clone\n") );
48 //duplicate the string
49 new_item
->name
= g_strdup(src_item
->name
);
50 new_item
->fullname
= g_strdup(src_item
->fullname
);
57 da_cat_free(Category
*item
)
59 DB( g_print("da_cat_free\n") );
62 DB( g_print(" => %d, %s\n", item
->key
, item
->name
) );
65 g_free(item
->fullname
);
74 DB( g_print("da_cat_malloc\n") );
75 return g_malloc0(sizeof(Category
));
82 DB( g_print("da_cat_destroy\n") );
83 g_hash_table_destroy(GLOBALS
->h_cat
);
92 DB( g_print("da_cat_new\n") );
93 GLOBALS
->h_cat
= g_hash_table_new_full(g_int_hash
, g_int_equal
, (GDestroyNotify
)g_free
, (GDestroyNotify
)da_cat_free
);
95 // insert our 'no category'
96 item
= da_cat_malloc();
98 item
->name
= g_strdup("");
99 item
->fullname
= g_strdup("");
104 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
109 * Return value: the number of elements
114 return g_hash_table_size(GLOBALS
->h_cat
);
119 da_cat_max_key_ghfunc(gpointer key
, Category
*cat
, guint32
*max_key
)
121 *max_key
= MAX(*max_key
, cat
->key
);
125 * da_cat_get_max_key:
127 * Get the biggest key from the GHashTable
129 * Return value: the biggest key value
133 da_cat_get_max_key(void)
137 g_hash_table_foreach(GLOBALS
->h_cat
, (GHFunc
)da_cat_max_key_ghfunc
, &max_key
);
143 da_cat_remove_grfunc(gpointer key
, Category
*cat
, guint32
*remkey
)
145 if(cat
->key
== *remkey
|| cat
->parent
== *remkey
)
155 * delete a category from the GHashTable
157 * Return value: TRUE if the key was found and deleted
161 da_cat_remove(guint32 key
)
163 DB( g_print("\nda_cat_remove %d\n", key
) );
165 return g_hash_table_foreach_remove(GLOBALS
->h_cat
, (GHRFunc
)da_cat_remove_grfunc
, &key
);
170 da_cat_build_fullname(Category
*item
)
174 g_free(item
->fullname
);
175 if( item
->parent
== 0 )
176 item
->fullname
= g_strdup(item
->name
);
179 parent
= da_cat_get(item
->parent
);
181 item
->fullname
= g_strconcat(parent
->name
, ":", item
->name
, NULL
);
184 DB( g_print("- updated %d:'%s' fullname='%s'\n", item
->key
, item
->name
, item
->fullname
) );
190 da_cat_rename(Category
*item
, gchar
*newname
)
193 DB( g_print("- renaming %s' => '%s'\n", item
->name
, newname
) );
196 item
->name
= g_strdup(newname
);
197 da_cat_build_fullname(item
);
199 if( item
->parent
== 0 )
204 DB( g_print("- updating subcat fullname\n") );
206 g_hash_table_iter_init (&iter
, GLOBALS
->h_cat
);
207 while (g_hash_table_iter_next (&iter
, NULL
, &value
))
209 Category
*subcat
= value
;
211 if( subcat
->parent
== item
->key
)
212 da_cat_build_fullname(subcat
);
222 * insert a category into the GHashTable
224 * Return value: TRUE if inserted
228 da_cat_insert(Category
*item
)
232 DB( g_print("\nda_cat_insert\n") );
234 DB( g_print("- '%s'\n", item
->name
) );
236 new_key
= g_new0(guint32
, 1);
237 *new_key
= item
->key
;
238 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, item
);
240 da_cat_build_fullname(item
);
249 * append a category into the GHashTable
251 * Return value: TRUE if inserted
254 // used only to add cat/subcat from ui_category with the 2 inputs
256 da_cat_append(Category
*cat
)
260 DB( g_print("\nda_cat_append\n") );
263 da_cat_build_fullname(cat
);
265 existitem
= da_cat_get_by_fullname( cat
->fullname
);
266 if( existitem
== NULL
)
268 cat
->key
= da_cat_get_max_key() + 1;
273 DB( g_print(" -> %s already exist\n", cat
->name
) );
280 /* fullname i.e. car:refuel */
281 struct fullcatcontext
289 da_cat_fullname_grfunc(gpointer key
, Category
*item
, struct fullcatcontext
*ctx
)
292 //DB( g_print("'%s' == '%s'\n", ctx->name, item->name) );
293 if( item
->parent
== ctx
->parent
)
295 if( ctx
->name
&& item
->name
)
296 if(!strcasecmp(ctx
->name
, item
->name
))
303 static Category
*da_cat_get_by_name_find_internal(guint32 parent
, gchar
*name
)
305 struct fullcatcontext ctx
;
309 DB( g_print("- searching %s %d '%s'\n", (parent
== 0) ? "lv1cat" : "lv2cat", parent
, name
) );
310 return g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
314 static gchar
**da_cat_get_by_fullname_split_clean(gchar
*rawfullname
, guint
*outlen
)
316 gchar
**partstr
= g_strsplit(rawfullname
, ":", 2);
317 guint len
= g_strv_length(partstr
);
318 gboolean valid
= TRUE
;
320 DB( g_print("- spliclean '%s' - %d parts\n", rawfullname
, g_strv_length(partstr
)) );
327 g_strstrip(partstr
[0]);
328 if( strlen(partstr
[0]) == 0 )
333 g_strstrip(partstr
[1]);
334 if( strlen(partstr
[1]) == 0 )
342 DB( g_print("- is invalid\n") );
350 da_cat_get_by_fullname(gchar
*rawfullname
)
353 Category
*parent
= NULL
;
354 Category
*retval
= NULL
;
357 DB( g_print("\nda_cat_get_by_fullname\n") );
361 if( (partstr
= da_cat_get_by_fullname_split_clean(rawfullname
, &len
)) != NULL
)
365 parent
= da_cat_get_by_name_find_internal(0, partstr
[0]);
369 if( len
== 2 && parent
!= NULL
)
371 retval
= da_cat_get_by_name_find_internal(parent
->key
, partstr
[1]);
383 * da_cat_append_ifnew_by_fullname:
385 * append a category if it is new by fullname
391 da_cat_append_ifnew_by_fullname(gchar
*rawfullname
)
394 Category
*parent
= NULL
;
395 Category
*newcat
= NULL
;
396 Category
*retval
= NULL
;
399 DB( g_print("\nda_cat_append_ifnew_by_fullname\n") );
403 if( (partstr
= da_cat_get_by_fullname_split_clean(rawfullname
, &len
)) != NULL
)
407 parent
= da_cat_get_by_name_find_internal(0, partstr
[0]);
410 parent
= da_cat_malloc();
411 parent
->key
= da_cat_get_max_key() + 1;
412 parent
->name
= g_strdup(partstr
[0]);
413 da_cat_insert(parent
);
418 /* if we have a subcategory - xxx:xxx */
419 if( len
== 2 && parent
!= NULL
)
421 newcat
= da_cat_get_by_name_find_internal(parent
->key
, partstr
[1]);
424 newcat
= da_cat_malloc();
425 newcat
->key
= da_cat_get_max_key() + 1;
426 newcat
->parent
= parent
->key
;
427 newcat
->name
= g_strdup(partstr
[1]);
428 newcat
->flags
|= GF_SUB
;
429 //#1713413 take parent type into account
430 if(parent
->flags
& GF_INCOME
)
431 newcat
->flags
|= GF_INCOME
;
432 da_cat_insert(newcat
);
448 * Get a category structure by key
450 * Return value: Category * or NULL if not found
454 da_cat_get(guint32 key
)
456 //DB( g_print("da_cat_get\n") );
458 return g_hash_table_lookup(GLOBALS
->h_cat
, &key
);
462 gchar
*da_cat_get_name(Category
*item
)
468 name
= item
->key
== 0 ? _("(no category)") : item
->fullname
;
474 void da_cat_consistency(Category
*item
)
478 if((item
->flags
& GF_SUB
) && item
->key
> 0)
480 //check for existing parent
481 if( da_cat_get(item
->parent
) == NULL
)
483 Category
*parent
= da_cat_append_ifnew_by_fullname ("orphaned");
485 item
->parent
= parent
->key
;
486 da_cat_build_fullname(item
);
487 g_warning("category consistency: fixed missing parent %d", item
->parent
);
491 // ensure type equal for categories and its children
492 if(!(item
->flags
& GF_SUB
) && item
->key
> 0)
494 isIncome
= (item
->flags
& GF_INCOME
) ? TRUE
: FALSE
;
495 if( category_change_type(item
, isIncome
) > 0 )
497 g_warning("category consistency: fixed type for child");
498 GLOBALS
->changes_count
++;
502 if( item
->name
!= NULL
)
503 g_strstrip(item
->name
);
506 item
->name
= g_strdup("void");
507 da_cat_build_fullname(item
);
508 g_warning("category consistency: fixed null name");
509 GLOBALS
->changes_count
++;
516 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
521 da_cat_debug_list_ghfunc(gpointer key
, gpointer value
, gpointer user_data
)
524 Category
*cat
= value
;
526 DB( g_print(" %d :: %s (parent=%d\n", *id
, cat
->name
, cat
->parent
) );
531 da_cat_debug_list(void)
534 DB( g_print("\n** debug **\n") );
536 g_hash_table_foreach(GLOBALS
->h_cat
, da_cat_debug_list_ghfunc
, NULL
);
538 DB( g_print("\n** end debug **\n") );
546 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
549 guint32
category_report_id(guint32 key
, gboolean subcat
)
555 Category
*catentry
= da_cat_get(key
);
557 retval
= (catentry
->flags
& GF_SUB
) ? catentry
->parent
: catentry
->key
;
563 //DB( g_print("- cat '%s' reportid = %d\n", catentry->name, retval) );
569 category_delete_unused(void)
573 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
576 Category
*entry
= list
->data
;
578 if(entry
->usage_count
<= 0 && entry
->key
> 0)
579 da_cat_remove (entry
->key
);
581 list
= g_list_next(list
);
588 category_fill_usage_count(guint32 kcat
)
590 Category
*cat
= da_cat_get (kcat
);
596 if( cat
->parent
> 0 )
598 parent
= da_cat_get(cat
->parent
);
601 parent
->usage_count
++;
609 category_fill_usage(void)
612 GList
*lst_acc
, *lnk_acc
;
614 GList
*lpay
, *lrul
, *list
;
617 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
620 Category
*entry
= list
->data
;
621 entry
->usage_count
= 0;
622 list
= g_list_next(list
);
627 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
628 lnk_acc
= g_list_first(lst_acc
);
629 while (lnk_acc
!= NULL
)
631 Account
*acc
= lnk_acc
->data
;
633 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
634 while (lnk_txn
!= NULL
)
636 Transaction
*txn
= lnk_txn
->data
;
638 //#1689308 count split as well
639 if( txn
->flags
& OF_SPLIT
)
641 nbsplit
= da_splits_length(txn
->splits
);
642 for(i
=0;i
<nbsplit
;i
++)
644 Split
*split
= da_splits_get(txn
->splits
, i
);
646 category_fill_usage_count(split
->kcat
);
650 category_fill_usage_count(txn
->kcat
);
652 lnk_txn
= g_list_next(lnk_txn
);
654 lnk_acc
= g_list_next(lnk_acc
);
656 g_list_free(lst_acc
);
658 lpay
= list
= g_hash_table_get_values(GLOBALS
->h_pay
);
661 Payee
*entry
= list
->data
;
663 category_fill_usage_count(entry
->kcat
);
664 list
= g_list_next(list
);
669 list
= g_list_first(GLOBALS
->arc_list
);
672 Archive
*entry
= list
->data
;
674 //#1689308 count split as well
675 if( entry
->flags
& OF_SPLIT
)
677 nbsplit
= da_splits_length(entry
->splits
);
678 for(i
=0;i
<nbsplit
;i
++)
680 Split
*split
= da_splits_get(entry
->splits
, i
);
682 category_fill_usage_count(split
->kcat
);
686 category_fill_usage_count(entry
->kcat
);
688 list
= g_list_next(list
);
692 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
695 Assign
*entry
= list
->data
;
697 category_fill_usage_count(entry
->kcat
);
698 list
= g_list_next(list
);
706 category_move(guint32 key1
, guint32 key2
)
708 GList
*lst_acc
, *lnk_acc
;
713 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
714 lnk_acc
= g_list_first(lst_acc
);
715 while (lnk_acc
!= NULL
)
717 Account
*acc
= lnk_acc
->data
;
719 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
720 while (lnk_txn
!= NULL
)
722 Transaction
*txn
= lnk_txn
->data
;
724 if(txn
->kcat
== key1
)
727 txn
->flags
|= OF_CHANGED
;
730 // move split category #1340142
731 nbsplit
= da_splits_length(txn
->splits
);
732 for(i
=0;i
<nbsplit
;i
++)
734 Split
*split
= da_splits_get(txn
->splits
, i
);
736 if( split
->kcat
== key1
)
739 txn
->flags
|= OF_CHANGED
;
743 lnk_txn
= g_list_next(lnk_txn
);
746 lnk_acc
= g_list_next(lnk_acc
);
748 g_list_free(lst_acc
);
751 list
= g_list_first(GLOBALS
->arc_list
);
754 Archive
*entry
= list
->data
;
755 if(entry
->kcat
== key1
)
759 list
= g_list_next(list
);
762 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
765 Assign
*entry
= list
->data
;
767 if(entry
->kcat
== key1
)
771 list
= g_list_next(list
);
779 category_rename(Category
*item
, const gchar
*newname
)
781 Category
*parent
, *existitem
;
782 gchar
*fullname
= NULL
;
786 DB( g_print("\n(category) rename\n") );
788 stripname
= g_strdup(newname
);
789 g_strstrip(stripname
);
791 if( item
->parent
== 0)
792 fullname
= g_strdup(stripname
);
795 parent
= da_cat_get(item
->parent
);
798 fullname
= g_strdup_printf("%s:%s", parent
->name
, stripname
);
802 DB( g_print(" - search: %s\n", fullname
) );
804 existitem
= da_cat_get_by_fullname( fullname
);
806 if( existitem
!= NULL
&& existitem
->key
!= item
->key
)
808 DB( g_print("- error, same name already exist with other key %d <> %d\n",existitem
->key
, item
->key
) );
813 DB( g_print("- renaming\n") );
815 da_cat_rename (item
, stripname
);
827 category_glist_name_compare_func(Category
*c1
, Category
*c2
)
831 if( c1
!= NULL
&& c2
!= NULL
)
833 retval
= hb_string_utf8_compare(c1
->fullname
, c2
->fullname
);
840 category_glist_key_compare_func(Category
*a
, Category
*b
)
842 gint ka
, kb
, retval
= 0;
844 if(a
->parent
== 0 && b
->parent
== a
->key
)
847 if(b
->parent
== 0 && a
->parent
== b
->key
)
851 ka
= a
->parent
!= 0 ? a
->parent
: a
->key
;
852 kb
= b
->parent
!= 0 ? b
->parent
: b
->key
;
869 DB( g_print("compare a=%2d:%2d to b=%2d:%2d :: %d [%s]\n", a
->key
, a
->parent
, b
->key
, b
->parent
, retval
, str
) );
877 category_glist_sorted(gint column
)
879 GList
*list
= g_hash_table_get_values(GLOBALS
->h_cat
);
882 return g_list_sort(list
, (GCompareFunc
)category_glist_key_compare_func
);
884 return g_list_sort(list
, (GCompareFunc
)category_glist_name_compare_func
);
889 category_load_csv(gchar
*filename
, gchar
**error
)
896 gchar
*lastcatname
= NULL
;
901 const gchar
*encoding
;
903 encoding
= homebank_file_getencoding(filename
);
904 DB( g_print(" -> encoding should be %s\n", encoding
) );
908 io
= g_io_channel_new_file(filename
, "r", NULL
);
911 if( encoding
!= NULL
)
913 g_io_channel_set_encoding(io
, encoding
, NULL
);
920 io_stat
= g_io_channel_read_line(io
, &tmpstr
, NULL
, NULL
, &err
);
922 DB( g_print(" + iostat %d\n", io_stat
) );
924 if( io_stat
== G_IO_STATUS_ERROR
)
926 DB (g_print(" + ERROR %s\n",err
->message
));
929 if( io_stat
== G_IO_STATUS_EOF
)
931 if( io_stat
== G_IO_STATUS_NORMAL
)
935 DB( g_print(" + strip %s\n", tmpstr
) );
936 hb_string_strip_crlf(tmpstr
);
938 DB( g_print(" + split\n") );
939 str_array
= g_strsplit (tmpstr
, ";", 3);
942 if( g_strv_length (str_array
) != 3 )
944 *error
= _("invalid CSV format");
946 DB( g_print(" + error %s\n", *error
) );
950 DB( g_print(" + read %s : %s : %s\n", str_array
[0], str_array
[1], str_array
[2]) );
953 if( g_str_has_prefix(str_array
[0], "1") )
955 fullcatname
= g_strdup(str_array
[2]);
957 lastcatname
= g_strdup(str_array
[2]);
959 type
= g_str_has_prefix(str_array
[1], "+") ? GF_INCOME
: 0;
961 DB( g_print(" + type = %d\n", type
) );
965 if( g_str_has_prefix(str_array
[0], "2") )
967 fullcatname
= g_strdup_printf("%s:%s", lastcatname
, str_array
[2]);
970 item
= da_cat_append_ifnew_by_fullname(fullcatname
);
971 DB( g_print(" + item %p\n", item
) );
975 DB( g_print(" + assign flags: '%c'\n", type
) );
982 g_strfreev (str_array
);
988 g_io_channel_unref (io
);
998 category_save_csv(gchar
*filename
, gchar
**error
)
1000 gboolean retval
= FALSE
;
1005 io
= g_io_channel_new_file(filename
, "w", NULL
);
1008 lcat
= list
= category_glist_sorted(1);
1010 while (list
!= NULL
)
1012 Category
*item
= list
->data
;
1018 if( item
->parent
== 0)
1021 type
= (item
->flags
& GF_INCOME
) ? '+' : '-';
1029 outstr
= g_strdup_printf("%c;%c;%s\n", lvel
, type
, item
->name
);
1031 DB( g_print(" + export %s\n", outstr
) );
1033 g_io_channel_write_chars(io
, outstr
, -1, NULL
, NULL
);
1037 list
= g_list_next(list
);
1044 g_io_channel_unref (io
);
1052 category_type_get(Category
*item
)
1054 if( (item
->flags
& (GF_INCOME
)) )
1061 category_get_type_char(Category
*item
)
1063 return (item
->flags
& GF_INCOME
) ? '+' : '-';
1068 category_change_type_eval(Category
*item
, gboolean isIncome
)
1070 if( (item
->flags
& (GF_INCOME
)) && !isIncome
)
1077 category_change_type(Category
*item
, gboolean isIncome
)
1082 changes
+= category_change_type_eval(item
, isIncome
);
1084 item
->flags
&= ~(GF_INCOME
); //delete flag
1085 if(isIncome
== TRUE
)
1086 item
->flags
|= GF_INCOME
;
1088 // change also childs
1089 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
1090 while (list
!= NULL
)
1092 Category
*child
= list
->data
;
1094 if(child
->parent
== item
->key
)
1096 changes
+= category_change_type_eval(child
, isIncome
);
1097 child
->flags
&= ~(GF_INCOME
); //delete flag
1098 if(isIncome
== TRUE
)
1099 child
->flags
|= GF_INCOME
;
1101 list
= g_list_next(list
);
1111 * category_find_preset:
1113 * find a user language compatible file for category preset
1115 * Return value: a pathname to the file or NULL
1119 category_find_preset(gchar
**lang
)
1126 DB( g_print("** category_find_preset **\n") );
1128 langs
= (gchar
**)g_get_language_names ();
1130 DB( g_print(" -> %d languages detected\n", g_strv_length(langs
)) );
1132 for(i
=0;i
<g_strv_length(langs
);i
++)
1134 DB( g_print(" -> %d '%s'\n", i
, langs
[i
]) );
1135 filename
= g_strdup_printf("hb-categories-%s.csv", langs
[i
]);
1136 gchar
*pathfilename
= g_build_filename(homebank_app_get_datas_dir(), filename
, NULL
);
1137 exists
= g_file_test(pathfilename
, G_FILE_TEST_EXISTS
);
1138 DB( g_print(" -> '%s' exists=%d\n", pathfilename
, exists
) );
1143 return pathfilename
;
1146 g_free(pathfilename
);
1149 DB( g_print("return NULL\n") );