1 /*  HomeBank -- Free, easy, personal accounting for everyone.
2  *  Copyright (C) 1995-2021 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-account.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 
37 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
38 
39 void
da_acc_free(Account * item)40 da_acc_free(Account *item)
41 {
42 	DB( g_print("da_acc_free\n") );
43 	if(item != NULL)
44 	{
45 		DB( g_print(" => %d, %s\n", item->key, item->name) );
46 
47 		g_free(item->name);
48 		g_free(item->number);
49 		g_free(item->bankname);
50 		g_free(item->notes);
51 
52 		g_queue_free (item->txn_queue);
53 
54 		g_free(item);
55 	}
56 }
57 
58 
59 Account *
da_acc_malloc(void)60 da_acc_malloc(void)
61 {
62 Account *item;
63 
64 	DB( g_print("da_acc_malloc\n") );
65 	item = g_malloc0(sizeof(Account));
66 	item->kcur = GLOBALS->kcur;
67 	item->txn_queue = g_queue_new ();
68 	return item;
69 }
70 
71 
72 void
da_acc_destroy(void)73 da_acc_destroy(void)
74 {
75 	DB( g_print("da_acc_destroy\n") );
76 	g_hash_table_destroy(GLOBALS->h_acc);
77 }
78 
79 
80 void
da_acc_new(void)81 da_acc_new(void)
82 {
83 	DB( g_print("da_acc_new\n") );
84 	GLOBALS->h_acc = g_hash_table_new_full(g_int_hash, g_int_equal, (GDestroyNotify)g_free, (GDestroyNotify)da_acc_free);
85 }
86 
87 
88 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
89 
90 
91 /**
92  * da_acc_length:
93  *
94  * Return value: the number of elements
95  */
96 guint
da_acc_length(void)97 da_acc_length(void)
98 {
99 	return g_hash_table_size(GLOBALS->h_acc);
100 }
101 
102 
da_acc_max_key_ghfunc(gpointer key,Account * item,guint32 * max_key)103 static void da_acc_max_key_ghfunc(gpointer key, Account *item, guint32 *max_key)
104 {
105 	*max_key = MAX(*max_key, item->key);
106 }
107 
108 
109 /**
110  * da_acc_get_max_key:
111  *
112  * Get the biggest key from the GHashTable
113  *
114  * Return value: the biggest key value
115  *
116  */
117 guint32
da_acc_get_max_key(void)118 da_acc_get_max_key(void)
119 {
120 guint32 max_key = 0;
121 
122 	g_hash_table_foreach(GLOBALS->h_acc, (GHFunc)da_acc_max_key_ghfunc, &max_key);
123 	return max_key;
124 }
125 
126 
127 /**
128  * da_acc_remove:
129  *
130  * delete an account from the GHashTable
131  *
132  * Return value: TRUE if the key was found and deleted
133  *
134  */
135 gboolean
da_acc_delete(guint32 key)136 da_acc_delete(guint32 key)
137 {
138 	DB( g_print("da_acc_remove %d\n", key) );
139 
140 	return g_hash_table_remove(GLOBALS->h_acc, &key);
141 }
142 
143 
144 //#1889659: ensure name != null/empty
145 static gboolean
da_acc_ensure_name(Account * item)146 da_acc_ensure_name(Account *item)
147 {
148 	// (no account) do not exists
149 	if( item->key > 0 )
150 	{
151 		if( item->name == NULL || strlen(item->name) == 0 )
152 		{
153 			g_free(item->name);
154 			item->name = g_strdup_printf("no name %d", item->key);
155 			return TRUE;
156 		}
157 	}
158 	return FALSE;
159 }
160 
161 
162 static void
da_acc_rename(Account * item,gchar * newname)163 da_acc_rename(Account *item, gchar *newname)
164 {
165 
166 	DB( g_print("- renaming '%s' => '%s'\n", item->name, newname) );
167 
168 	g_free(item->name);
169 	item->name = g_strdup(newname);
170 
171 	//#1889659: ensure name != null/empty
172 	da_acc_ensure_name(item);
173 
174 }
175 
176 
177 /**
178  * da_acc_insert:
179  *
180  * insert an account into the GHashTable
181  *
182  * Return value: TRUE if inserted
183  *
184  */
185 gboolean
da_acc_insert(Account * item)186 da_acc_insert(Account *item)
187 {
188 guint32 *new_key;
189 
190 	DB( g_print("da_acc_insert\n") );
191 
192 	new_key = g_new0(guint32, 1);
193 	*new_key = item->key;
194 
195 	//#1889659: ensure name != null/empty
196 	da_acc_ensure_name(item);
197 
198 	g_hash_table_insert(GLOBALS->h_acc, new_key, item);
199 
200 	return TRUE;
201 }
202 
203 
204 /**
205  * da_acc_append:
206  *
207  * insert an account into the GHashTable
208  *
209  * Return value: TRUE if inserted
210  *
211  */
212 gboolean
da_acc_append(Account * item)213 da_acc_append(Account *item)
214 {
215 Account *existitem;
216 
217 	DB( g_print("da_acc_append\n") );
218 
219 	existitem = da_acc_get_by_name( item->name );
220 	if( existitem == NULL )
221 	{
222 		item->key = da_acc_get_max_key() + 1;
223 		item->pos = da_acc_length() + 1;
224 		da_acc_insert(item);
225 		return TRUE;
226 	}
227 
228 	DB( g_print(" -> %s already exist: %d\n", item->name, item->key) );
229 
230 	return FALSE;
231 }
232 
233 
da_acc_name_grfunc(gpointer key,Account * item,gchar * name)234 static gboolean da_acc_name_grfunc(gpointer key, Account *item, gchar *name)
235 {
236 	if( name && item->name )
237 	{
238 		if(!strcasecmp(name, item->name))
239 			return TRUE;
240 	}
241 	return FALSE;
242 }
243 
244 /**
245  * da_acc_get_by_name:
246  *
247  * Get an account structure by its name
248  *
249  * Return value: Account * or NULL if not found
250  *
251  */
252 Account *
da_acc_get_by_name(gchar * rawname)253 da_acc_get_by_name(gchar *rawname)
254 {
255 Account *retval = NULL;
256 gchar *stripname;
257 
258 	DB( g_print("da_acc_get_by_name\n") );
259 
260 	if( rawname )
261 	{
262 		stripname = g_strdup(rawname);
263 		g_strstrip(stripname);
264 		if( strlen(stripname) > 0 )
265 			retval = g_hash_table_find(GLOBALS->h_acc, (GHRFunc)da_acc_name_grfunc, stripname);
266 
267 		g_free(stripname);
268 	}
269 
270 	return retval;
271 }
272 
273 
274 /**
275  * da_acc_get:
276  *
277  * Get an account structure by key
278  *
279  * Return value: Account * or NULL if not found
280  *
281  */
282 Account *
da_acc_get(guint32 key)283 da_acc_get(guint32 key)
284 {
285 	//DB( g_print("da_acc_get\n") );
286 
287 	return g_hash_table_lookup(GLOBALS->h_acc, &key);
288 }
289 
290 
da_acc_consistency(Account * item)291 void da_acc_consistency(Account *item)
292 {
293 	g_strstrip(item->name);
294 
295 	//#1889659: ensure name != null/empty
296 	da_acc_ensure_name(item);
297 }
298 
299 
300 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
301 #if MYDEBUG
302 
303 static void
da_acc_debug_list_ghfunc(gpointer key,gpointer value,gpointer user_data)304 da_acc_debug_list_ghfunc(gpointer key, gpointer value, gpointer user_data)
305 {
306 guint32 *id = key;
307 Account *item = value;
308 
309 	DB( g_print(" %d :: %s\n", *id, item->name) );
310 
311 }
312 
313 static void
da_acc_debug_list(void)314 da_acc_debug_list(void)
315 {
316 
317 	DB( g_print("\n** debug **\n") );
318 
319 	g_hash_table_foreach(GLOBALS->h_acc, da_acc_debug_list_ghfunc, NULL);
320 
321 	DB( g_print("\n** end debug **\n") );
322 
323 }
324 
325 #endif
326 
327 
328 static gint
account_glist_name_compare_func(Account * a,Account * b)329 account_glist_name_compare_func(Account *a, Account *b)
330 {
331 	return hb_string_utf8_compare(a->name, b->name);
332 }
333 
334 
335 static gint
account_glist_key_compare_func(Account * a,Account * b)336 account_glist_key_compare_func(Account *a, Account *b)
337 {
338 	return a->key - b->key;
339 }
340 
341 
account_glist_sorted(gint column)342 GList *account_glist_sorted(gint column)
343 {
344 GList *list = g_hash_table_get_values(GLOBALS->h_acc);
345 
346 	if(column == 0)
347 		return g_list_sort(list, (GCompareFunc)account_glist_key_compare_func);
348 	else
349 		return g_list_sort(list, (GCompareFunc)account_glist_name_compare_func);
350 }
351 
352 
353 
354 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
355 
356 GtkWindow *
account_window(guint32 key)357 account_window(guint32 key)
358 {
359 Account *acc = da_acc_get(key);
360 
361 	if( acc != NULL && acc->window != NULL )
362 	{
363 		if( GTK_IS_WINDOW(acc->window) )
364 			return acc->window;
365 	}
366 	return NULL;
367 }
368 
369 
370 /**
371  * account_is_used:
372  *
373  * controls if an account is used by any archive or transaction
374  *
375  * Return value: TRUE if used, FALSE, otherwise
376  */
377 gboolean
account_is_used(guint32 key)378 account_is_used(guint32 key)
379 {
380 Account *acc;
381 GList *list;
382 GList *lst_acc, *lnk_acc;
383 GList *lnk_txn;
384 gboolean retval;
385 
386 	retval = FALSE;
387 	lst_acc = NULL;
388 
389 	acc = da_acc_get(key);
390 	if( g_queue_get_length(acc->txn_queue) > 0 )
391 	{
392 		retval = TRUE;
393 		goto end;
394 	}
395 
396 	lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
397 	lnk_acc = g_list_first(lst_acc);
398 	while (lnk_acc != NULL)
399 	{
400 	Account *acc = lnk_acc->data;
401 
402 		if(acc->key != key)
403 		{
404 			lnk_txn = g_queue_peek_head_link(acc->txn_queue);
405 			while (lnk_txn != NULL)
406 			{
407 			Transaction *entry = lnk_txn->data;
408 
409 				if( key == entry->kxferacc)
410 				{
411 					retval = TRUE;
412 					goto end;
413 				}
414 
415 				lnk_txn = g_list_next(lnk_txn);
416 			}
417 		}
418 		lnk_acc = g_list_next(lnk_acc);
419 	}
420 
421 	list = g_list_first(GLOBALS->arc_list);
422 	while (list != NULL)
423 	{
424 	Archive *entry = list->data;
425 
426 		if( key == entry->kacc || key == entry->kxferacc)
427 		{
428 			retval = TRUE;
429 			goto end;
430 		}
431 
432 		list = g_list_next(list);
433 	}
434 
435 end:
436 	g_list_free(lst_acc);
437 
438 	return retval;
439 }
440 
441 
442 gboolean
account_exists(gchar * name)443 account_exists(gchar *name)
444 {
445 Account *existitem;
446 gchar *stripname;
447 
448 	stripname = g_strdup(name);
449 	g_strstrip(stripname);
450 
451 	existitem = da_acc_get_by_name(stripname);
452 
453 	g_free(stripname);
454 
455 	return existitem == NULL ? FALSE : TRUE;
456 }
457 
458 
459 gboolean
account_rename(Account * item,gchar * newname)460 account_rename(Account *item, gchar *newname)
461 {
462 Account *existitem;
463 gchar *stripname;
464 gboolean retval = FALSE;
465 
466 	stripname = g_strdup(newname);
467 	g_strstrip(stripname);
468 
469 	if( strlen(stripname) > 0 )
470 	{
471 		existitem = da_acc_get_by_name(stripname);
472 
473 		if( existitem != NULL )
474 		{
475 			DB( g_print("- error, same name already exist with other key %d <> %d\n", existitem->key, item->key) );
476 		}
477 		else
478 		{
479 			DB( g_print("- renaming\n") );
480 
481 			da_acc_rename (item, stripname);
482 			retval = TRUE;
483 		}
484 	}
485 
486 	g_free(stripname);
487 
488 	return retval;
489 }
490 
491 
492 /*
493  * change the account currency
494  * change every txn to currency
495  * ensure dst xfer transaction account will be set to same currency
496  */
account_set_currency(Account * acc,guint32 kcur)497 void account_set_currency(Account *acc, guint32 kcur)
498 {
499 GList *list;
500 Account *dstacc;
501 gboolean *xfer_list;
502 guint32 maxkey, i;
503 
504 	DB( g_print("\n[account] set currency\n") );
505 
506 	if(acc->kcur == kcur)
507 	{
508 		DB( g_print(" - already ok, return\n") );
509 		return;
510 	}
511 
512 	DB( g_print(" - set for '%s'\n", acc->name)  );
513 
514 	maxkey = da_acc_get_max_key () + 1;
515 	xfer_list = g_malloc0(sizeof(gboolean) * maxkey );
516 	DB( g_print(" - alloc for %d account\n", da_acc_length() ) );
517 
518 	list = g_queue_peek_head_link(acc->txn_queue);
519 	while (list != NULL)
520 	{
521 	Transaction *txn = list->data;
522 
523 		txn->kcur = kcur;
524 		if( (txn->flags & OF_INTXFER) && (txn->kxferacc > 0) && (txn->kxfer > 0) )
525 		{
526 			xfer_list[txn->kxferacc] = TRUE;
527 		}
528 		list = g_list_next(list);
529 	}
530 
531 	acc->kcur = kcur;
532 	DB( g_print(" - '%s'\n", acc->name) );
533 
534 	for(i=1;i<maxkey;i++)
535 	{
536 		DB( g_print(" - %d '%d'\n", i, xfer_list[i]) );
537 		if( xfer_list[i] == TRUE )
538 		{
539 			dstacc = da_acc_get(i);
540 			account_set_currency(dstacc, kcur);
541 		}
542 	}
543 
544 	g_free(xfer_list);
545 
546 }
547 
548 
549 /**
550  * private function to sub transaction amount from account balances
551  */
account_balances_sub_internal(Account * acc,Transaction * trn)552 static void account_balances_sub_internal(Account *acc, Transaction *trn)
553 {
554 	acc->bal_future -= trn->amount;
555 
556 	if(trn->date <= GLOBALS->today)
557 		acc->bal_today -= trn->amount;
558 
559 	if(trn->status == TXN_STATUS_CLEARED)
560 		acc->bal_clear -= trn->amount;
561 
562 	if(trn->status == TXN_STATUS_RECONCILED)
563 	{
564 		acc->bal_recon -= trn->amount;
565 		acc->bal_clear -= trn->amount;
566 	}
567 }
568 
569 /**
570  * private function to add transaction amount from account balances
571  */
account_balances_add_internal(Account * acc,Transaction * trn)572 static void account_balances_add_internal(Account *acc, Transaction *trn)
573 {
574 	acc->bal_future += trn->amount;
575 
576 	if(trn->date <= GLOBALS->today)
577 		acc->bal_today += trn->amount;
578 
579 	if(trn->status == TXN_STATUS_CLEARED)
580 		acc->bal_clear += trn->amount;
581 
582 	if(trn->status == TXN_STATUS_RECONCILED)
583 	{
584 		acc->bal_recon += trn->amount;
585 		acc->bal_clear += trn->amount;
586 	}
587 }
588 
589 
590 /**
591  * public function to sub transaction amount from account balances
592  */
account_balances_sub(Transaction * trn)593 gboolean account_balances_sub(Transaction *trn)
594 {
595 
596 	if( transaction_is_balanceable(trn) )
597 	//if(!(trn->flags & OF_REMIND))
598 	{
599 		Account *acc = da_acc_get(trn->kacc);
600 		if(acc == NULL) return FALSE;
601 		account_balances_sub_internal(acc, trn);
602 		return TRUE;
603 	}
604 	return FALSE;
605 }
606 
607 
608 /**
609  * public function to add transaction amount from account balances
610  */
account_balances_add(Transaction * trn)611 gboolean account_balances_add(Transaction *trn)
612 {
613 	if( transaction_is_balanceable(trn) )
614 	//if(!(trn->flags & OF_REMIND))
615 	{
616 		Account *acc = da_acc_get(trn->kacc);
617 		if(acc == NULL) return FALSE;
618 		account_balances_add_internal(acc, trn);
619 		return TRUE;
620 	}
621 	return FALSE;
622 }
623 
624 
625 //todo: optim called 2 times from dsp_mainwindow
account_compute_balances(void)626 void account_compute_balances(void)
627 {
628 GList *lst_acc, *lnk_acc;
629 GList *lnk_txn;
630 
631 	DB( g_print("\naccount_compute_balances start\n") );
632 
633 	lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
634 	lnk_acc = g_list_first(lst_acc);
635 	while (lnk_acc != NULL)
636 	{
637 	Account *acc = lnk_acc->data;
638 
639 		/* set initial amount */
640 		acc->bal_clear = acc->initial;
641 		acc->bal_recon = acc->initial;
642 		acc->bal_today = acc->initial;
643 		acc->bal_future = acc->initial;
644 
645 		/* add every txn */
646 		lnk_txn = g_queue_peek_head_link(acc->txn_queue);
647 		while (lnk_txn != NULL)
648 		{
649 		Transaction *txn = lnk_txn->data;
650 
651 			if( transaction_is_balanceable(txn) )
652 			{
653 				account_balances_add_internal(acc, txn);
654 			}
655 			lnk_txn = g_list_next(lnk_txn);
656 		}
657 
658 		lnk_acc = g_list_next(lnk_acc);
659 	}
660 	g_list_free(lst_acc);
661 
662 	DB( g_print("\naccount_compute_balances end\n") );
663 
664 }
665 
666 
account_convert_euro(Account * acc)667 void account_convert_euro(Account *acc)
668 {
669 GList *lnk_txn;
670 
671 	lnk_txn = g_queue_peek_head_link(acc->txn_queue);
672 	while (lnk_txn != NULL)
673 	{
674 	Transaction *txn = lnk_txn->data;
675 	gdouble oldamount = txn->amount;
676 
677 		txn->amount = hb_amount_to_euro(oldamount);
678 		DB( g_print("%10.6f => %10.6f, %s\n", oldamount, txn->amount, txn->memo) );
679 		//todo: sync child xfer
680 		lnk_txn = g_list_next(lnk_txn);
681 	}
682 
683 	acc->initial = hb_amount_to_euro(acc->initial);
684 //	acc->warning = hb_amount_to_euro(acc->warning);
685 	acc->minimum = hb_amount_to_euro(acc->minimum);
686 	acc->maximum = hb_amount_to_euro(acc->maximum);
687 }
688 
689 
690