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