]> Dogcows Code - chaz/homebank/blob - src/hb-transaction.c
Merge branch 'master' into ext-perl
[chaz/homebank] / src / hb-transaction.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
22 #include "hb-transaction.h"
23 #include "hb-tag.h"
24 #include "hb-split.h"
25
26 #include "ext.h"
27 #include "refcount.h"
28
29 /****************************************************************************/
30 /* Debug macros */
31 /****************************************************************************/
32 #define MYDEBUG 0
33
34 #if MYDEBUG
35 #define DB(x) (x);
36 #else
37 #define DB(x);
38 #endif
39
40 /* our global datas */
41 extern struct HomeBank *GLOBALS;
42 extern struct Preferences *PREFS;
43
44
45 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
46
47 void
48 da_transaction_clean(Transaction *item)
49 {
50 if(item != NULL)
51 {
52 if(item->memo != NULL)
53 {
54 g_free(item->memo);
55 item->memo = NULL;
56 }
57 if(item->info != NULL)
58 {
59 g_free(item->info);
60 item->info = NULL;
61 }
62 if(item->tags != NULL)
63 {
64 DB( g_print(" -> item->tags %p\n", item->tags) );
65 g_free(item->tags);
66 item->tags = NULL;
67 }
68
69 da_splits_free(item->splits);
70 item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
71
72 if(item->same != NULL)
73 {
74 g_list_free(item->same);
75 item->same = NULL;
76 }
77 }
78 }
79
80
81 void
82 da_transaction_free(Transaction *item)
83 {
84 if(rc_unref(item))
85 {
86 da_transaction_clean(item);
87 rc_free(item);
88 }
89 }
90
91
92 Transaction *
93 da_transaction_malloc(void)
94 {
95 return rc_alloc(sizeof(Transaction));
96 }
97
98
99 Transaction *da_transaction_copy(Transaction *src_txn, Transaction *dst_txn)
100 {
101 DB( g_print("da_transaction_copy\n") );
102
103 da_transaction_clean (dst_txn);
104
105 memmove(dst_txn, src_txn, sizeof(Transaction));
106
107 //duplicate the string
108 dst_txn->memo = g_strdup(src_txn->memo);
109 dst_txn->info = g_strdup(src_txn->info);
110
111 //duplicate tags
112 transaction_tags_clone(src_txn, dst_txn);
113
114 if (da_splits_clone(src_txn->splits, dst_txn->splits) > 0)
115 dst_txn->flags |= OF_SPLIT; //Flag that Splits are active
116
117 return dst_txn;
118 }
119
120
121 Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc)
122 {
123 //txn->date = 0;
124 txn->amount = arc->amount;
125 //#1258344 keep the current account if tpl is empty
126 if(arc->kacc)
127 txn->kacc = arc->kacc;
128 txn->paymode = arc->paymode;
129 txn->flags = arc->flags | OF_ADDED;
130 txn->status = arc->status;
131 txn->kpay = arc->kpay;
132 txn->kcat = arc->kcat;
133 txn->kxferacc = arc->kxferacc;
134 txn->memo = g_strdup(arc->memo);
135 txn->info = NULL;
136 if( da_splits_clone(arc->splits, txn->splits) > 0)
137 txn->flags |= OF_SPLIT; //Flag that Splits are active
138
139 return txn;
140 }
141
142
143 Transaction *da_transaction_clone(Transaction *src_item)
144 {
145 Transaction *new_item = rc_dup(src_item, sizeof(Transaction));
146
147 DB( g_print("da_transaction_clone\n") );
148
149 if(new_item)
150 {
151 //duplicate the string
152 new_item->memo = g_strdup(src_item->memo);
153 new_item->info = g_strdup(src_item->info);
154
155 //duplicate tags
156 transaction_tags_clone(src_item, new_item);
157
158 if( da_splits_clone(src_item->splits, new_item->splits) > 0)
159 new_item->flags |= OF_SPLIT; //Flag that Splits are active
160
161 }
162 return new_item;
163 }
164
165
166 GList *
167 da_transaction_new(void)
168 {
169 return NULL;
170 }
171
172
173 guint
174 da_transaction_length(void)
175 {
176 GList *lst_acc, *lnk_acc;
177 guint count = 0;
178
179 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
180 lnk_acc = g_list_first(lst_acc);
181 while (lnk_acc != NULL)
182 {
183 Account *acc = lnk_acc->data;
184
185 count += g_queue_get_length (acc->txn_queue);
186 lnk_acc = g_list_next(lnk_acc);
187 }
188 g_list_free(lst_acc);
189 return count;
190 }
191
192
193 static void da_transaction_queue_free_ghfunc(Transaction *item, gpointer data)
194 {
195 da_transaction_free (item);
196 }
197
198
199 void da_transaction_destroy(void)
200 {
201 GList *lacc, *list;
202
203 lacc = g_hash_table_get_values(GLOBALS->h_acc);
204 list = g_list_first(lacc);
205 while (list != NULL)
206 {
207 Account *acc = list->data;
208
209 g_queue_foreach(acc->txn_queue, (GFunc)da_transaction_queue_free_ghfunc, NULL);
210 list = g_list_next(list);
211 }
212 g_list_free(lacc);
213 }
214
215
216 static gint da_transaction_compare_datafunc(Transaction *a, Transaction *b, gpointer data)
217 {
218 return ((gint)a->date - b->date);
219 }
220
221
222 void da_transaction_queue_sort(GQueue *queue)
223 {
224 g_queue_sort(queue, (GCompareDataFunc)da_transaction_compare_datafunc, NULL);
225 }
226
227
228 static gint da_transaction_compare_func(Transaction *a, Transaction *b)
229 {
230 return ((gint)a->date - b->date);
231 }
232
233
234 GList *da_transaction_sort(GList *list)
235 {
236 return( g_list_sort(list, (GCompareFunc)da_transaction_compare_func));
237 }
238
239
240 gboolean da_transaction_insert_memo(Transaction *item)
241 {
242 gboolean retval = FALSE;
243
244 // append the memo if new
245 if( item->memo != NULL )
246 {
247 if( g_hash_table_lookup(GLOBALS->h_memo, item->memo) == NULL )
248 {
249 retval = g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->memo), NULL);
250 }
251 }
252 return retval;
253 }
254
255
256 gboolean da_transaction_insert_sorted(Transaction *newitem)
257 {
258 Account *acc;
259 GList *lnk_txn;
260
261 acc = da_acc_get(newitem->kacc);
262 if(!acc)
263 return FALSE;
264
265 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
266 while (lnk_txn != NULL)
267 {
268 Transaction *item = lnk_txn->data;
269
270 if(item->date <= newitem->date)
271 break;
272
273 lnk_txn = g_list_previous(lnk_txn);
274 }
275
276 // we're at insert point, insert after txn
277 g_queue_insert_after(acc->txn_queue, lnk_txn, newitem);
278
279 da_transaction_insert_memo(newitem);
280 return TRUE;
281 }
282
283
284 // nota: this is called only when loading xml file
285 gboolean da_transaction_prepend(Transaction *item)
286 {
287 Account *acc;
288
289 acc = da_acc_get(item->kacc);
290 //#1661279
291 if(!acc)
292 return FALSE;
293
294 item->kcur = acc->kcur;
295 g_queue_push_tail(acc->txn_queue, item);
296 da_transaction_insert_memo(item);
297 return TRUE;
298 }
299
300
301 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
302
303 static guint32
304 da_transaction_get_max_kxfer(void)
305 {
306 GList *lst_acc, *lnk_acc;
307 GList *list;
308 guint32 max_key = 0;
309
310 DB( g_print("da_transaction_get_max_kxfer\n") );
311
312 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
313 lnk_acc = g_list_first(lst_acc);
314 while (lnk_acc != NULL)
315 {
316 Account *acc = lnk_acc->data;
317
318 list = g_queue_peek_head_link(acc->txn_queue);
319 while (list != NULL)
320 {
321 Transaction *item = list->data;
322
323 if( item->paymode == PAYMODE_INTXFER )
324 {
325 max_key = MAX(max_key, item->kxfer);
326 }
327 list = g_list_next(list);
328 }
329
330 lnk_acc = g_list_next(lnk_acc);
331 }
332 g_list_free(lst_acc);
333
334 DB( g_print(" max_key : %d \n", max_key) );
335
336 return max_key;
337 }
338
339
340 static void da_transaction_goto_orphan(Transaction *txn)
341 {
342 const gchar *oatn = "orphaned transactions";
343 Account *ori_acc, *acc;
344 gboolean found;
345
346 DB( g_print("\n[transaction] goto orphan\n") );
347
348 g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn->date, txn->memo, txn->amount);
349
350 acc = da_acc_get_by_name((gchar *)oatn);
351 if(acc == NULL)
352 {
353 acc = da_acc_malloc();
354 acc->name = g_strdup(oatn);
355 da_acc_append(acc);
356 DB( g_print(" - created orphan acc %d\n", acc->key) );
357 }
358
359 ori_acc = da_acc_get(txn->kacc);
360 if( ori_acc )
361 {
362 found = g_queue_remove(ori_acc->txn_queue, txn);
363 DB( g_print(" - found in origin ? %d\n", found) );
364 if(found)
365 {
366 txn->kacc = acc->key;
367 da_transaction_insert_sorted (txn);
368 DB( g_print("moved txn to %d\n", txn->kacc) );
369 }
370 }
371 }
372
373
374 void da_transaction_consistency(Transaction *item)
375 {
376 Account *acc;
377 Category *cat;
378 Payee *pay;
379 gint nbsplit;
380
381 DB( g_print("\n[transaction] consistency\n") );
382
383 // ensure date is between range
384 item->date = CLAMP(item->date, HB_MINDATE, HB_MAXDATE);
385
386 // check account exists
387 acc = da_acc_get(item->kacc);
388 if(acc == NULL)
389 {
390 g_warning("txn consistency: fixed invalid acc %d", item->kacc);
391 da_transaction_goto_orphan(item);
392 GLOBALS->changes_count++;
393 }
394
395 // check category exists
396 cat = da_cat_get(item->kcat);
397 if(cat == NULL)
398 {
399 g_warning("txn consistency: fixed invalid cat %d", item->kcat);
400 item->kcat = 0;
401 GLOBALS->changes_count++;
402 }
403
404 // check split category #1340142
405 split_cat_consistency(item->splits);
406
407 //# 1416624 empty category when split
408 nbsplit = da_splits_count(item->splits);
409 if(nbsplit > 0 && item->kcat > 0)
410 {
411 g_warning("txn consistency: fixed invalid cat on split txn");
412 item->kcat = 0;
413 GLOBALS->changes_count++;
414 }
415
416 // check payee exists
417 pay = da_pay_get(item->kpay);
418 if(pay == NULL)
419 {
420 g_warning("txn consistency: fixed invalid pay %d", item->kpay);
421 item->kpay = 0;
422 GLOBALS->changes_count++;
423 }
424
425 // reset dst acc for non xfer transaction
426 if( item->paymode != PAYMODE_INTXFER )
427 {
428 item->kxfer = 0;
429 item->kxferacc = 0;
430 }
431
432 // check dst account exists
433 if( item->paymode == PAYMODE_INTXFER )
434 {
435 gint tak = item->kxferacc;
436
437 item->kxferacc = ABS(tak); //I crossed negative here one day
438 acc = da_acc_get(item->kxferacc);
439 if(acc == NULL)
440 {
441 g_warning("txn consistency: fixed invalid dst_acc %d", item->kxferacc);
442 da_transaction_goto_orphan(item);
443 item->kxfer = 0;
444 item->paymode = PAYMODE_XFER;
445 GLOBALS->changes_count++;
446 }
447 }
448
449 //#1628678 tags for internal xfer should be checked as well
450
451
452 //#1295877 ensure income flag is correctly set
453 item->flags &= ~(OF_INCOME);
454 if( item->amount > 0)
455 item->flags |= (OF_INCOME);
456
457 //#1308745 ensure remind flag unset if reconciled
458 //useless since 5.0
459 //if( item->flags & OF_VALID )
460 // item->flags &= ~(OF_REMIND);
461
462 }
463
464
465 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
466 /* new transfer functions */
467
468 static void transaction_xfer_create_child(Transaction *ope)
469 {
470 Transaction *child;
471 Account *acc;
472 gchar swap;
473
474 DB( g_print("\n[transaction] xfer_create_child\n") );
475
476 if( ope->kxferacc > 0 )
477 {
478 child = da_transaction_clone(ope);
479
480 ope->flags |= OF_CHANGED;
481 child->flags |= OF_ADDED;
482
483 child->amount = -child->amount;
484 child->flags ^= (OF_INCOME); // invert flag
485 //#1268026 #1690555
486 if( child->status != TXN_STATUS_REMIND )
487 child->status = TXN_STATUS_NONE;
488 //child->flags &= ~(OF_VALID); // delete reconcile state
489
490 swap = child->kacc;
491 child->kacc = child->kxferacc;
492 child->kxferacc = swap;
493
494 /* update acc flags */
495 acc = da_acc_get( child->kacc );
496 if(acc != NULL)
497 {
498 acc->flags |= AF_ADDED;
499
500 //strong link
501 guint maxkey = da_transaction_get_max_kxfer();
502
503 DB( g_print(" + maxkey is %d\n", maxkey) );
504
505
506 ope->kxfer = maxkey+1;
507 child->kxfer = maxkey+1;
508
509 DB( g_print(" + strong link to %d\n", ope->kxfer) );
510
511
512 DB( g_print(" + add transfer, %p to acc %d\n", child, acc->key) );
513
514 da_transaction_insert_sorted(child);
515
516 account_balances_add (child);
517
518 GValue txn_value = G_VALUE_INIT;
519 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value, child), NULL);
520
521 }
522 }
523
524 }
525
526
527 //todo: add strong control and extend to payee, maybe memo ?
528 static gboolean transaction_xfer_child_might(Transaction *stxn, Transaction *dtxn, gint daygap)
529 {
530 gboolean retval = FALSE;
531
532 //DB( g_print("\n[transaction] xfer_child_might\n") );
533
534 if(stxn == dtxn)
535 return FALSE;
536
537 /*g_print("test\n");
538
539 g_print(" %d %d %d %f %d\n",
540 stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer );
541
542
543 g_print(" %d %d %d %f %d\n",
544 dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer );
545 */
546
547 if( stxn->kcur == dtxn->kcur &&
548 stxn->date == dtxn->date &&
549 //v5.1 make no sense: stxn->kxferacc == dtxn->kacc &&
550 stxn->kacc != dtxn->kacc &&
551 ABS(stxn->amount) == ABS(dtxn->amount) &&
552 dtxn->kxfer == 0)
553 {
554 retval = TRUE;
555 }
556
557 //g_print(" return %d\n", retval);
558 return retval;
559 }
560
561
562 static GList *transaction_xfer_child_might_list_get(Transaction *ope)
563 {
564 GList *lst_acc, *lnk_acc;
565 GList *list, *matchlist = NULL;
566
567 //DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
568
569 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
570 lnk_acc = g_list_first(lst_acc);
571 while (lnk_acc != NULL)
572 {
573 Account *acc = lnk_acc->data;
574
575 if( !(acc->flags & AF_CLOSED) && (acc->key != ope->kacc) )
576 {
577 list = g_queue_peek_tail_link(acc->txn_queue);
578 while (list != NULL)
579 {
580 Transaction *item = list->data;
581
582 // no need to go higher than src txn date
583 if(item->date < ope->date)
584 break;
585
586 if( transaction_xfer_child_might(ope, item, 0) == TRUE )
587 {
588 //DB( g_print(" - match : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
589 matchlist = g_list_append(matchlist, item);
590 }
591 list = g_list_previous(list);
592 }
593 }
594
595 lnk_acc = g_list_next(lnk_acc);
596 }
597 g_list_free(lst_acc);
598
599 return matchlist;
600 }
601
602
603 void transaction_xfer_search_or_add_child(GtkWindow *parentwindow, Transaction *ope, gboolean manual)
604 {
605 GList *matchlist;
606 gint count;
607
608 DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
609
610 matchlist = transaction_xfer_child_might_list_get(ope);
611 count = g_list_length(matchlist);
612
613 DB( g_print(" - found %d might match, switching\n", count) );
614
615 switch(count)
616 {
617 case 0: //we should create the child
618 transaction_xfer_create_child(ope);
619 break;
620
621 //todo: maybe with just 1 match the user must choose ?
622 //#942346: bad idea so to no let the user confirm, so let hil confirm
623 /*
624 case 1: //transform the transaction to a child transfer
625 {
626 GList *list = g_list_first(matchlist);
627 transaction_xfer_change_to_child(ope, list->data);
628 break;
629 }
630 */
631
632 default: //the user must choose himself
633 {
634 Transaction *child;
635
636 child = ui_dialog_transaction_xfer_select_child(ope, matchlist);
637 if(child == NULL)
638 transaction_xfer_create_child(ope);
639 else
640 transaction_xfer_change_to_child(ope, child);
641 }
642 }
643
644 g_list_free(matchlist);
645 }
646
647
648 Transaction *transaction_xfer_child_strong_get(Transaction *src)
649 {
650 Account *dstacc;
651 GList *list;
652
653 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
654
655 dstacc = da_acc_get(src->kxferacc);
656 if( !dstacc || src->kxfer <= 0 )
657 return NULL;
658
659 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) );
660
661 list = g_queue_peek_tail_link(dstacc->txn_queue);
662 while (list != NULL)
663 {
664 Transaction *item = list->data;
665
666 //#1252230
667 //if( item->paymode == PAYMODE_INTXFER
668 // && item->kacc == src->kxferacc
669 // && item->kxfer == src->kxfer )
670 if( item->paymode == PAYMODE_INTXFER
671 && item->kxfer == src->kxfer
672 && item != src )
673 {
674 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item->date, item->memo, item->amount, item->kacc, item->kxferacc, src->kxfer) );
675 return item;
676 }
677 list = g_list_previous(list);
678 }
679
680 DB( g_print(" - not found...\n") );
681 return NULL;
682 }
683
684
685
686
687 void transaction_xfer_change_to_child(Transaction *ope, Transaction *child)
688 {
689 Account *dstacc;
690
691 DB( g_print("\n[transaction] xfer_change_to_child\n") );
692
693 if(ope->kcur != child->kcur)
694 return;
695
696 ope->flags |= OF_CHANGED;
697 child->flags |= OF_CHANGED;
698
699 child->paymode = PAYMODE_INTXFER;
700
701 ope->kxferacc = child->kacc;
702 child->kxferacc = ope->kacc;
703
704 /* update acc flags */
705 dstacc = da_acc_get( child->kacc);
706 if(dstacc != NULL)
707 dstacc->flags |= AF_CHANGED;
708
709 //strong link
710 guint maxkey = da_transaction_get_max_kxfer();
711 ope->kxfer = maxkey+1;
712 child->kxfer = maxkey+1;
713
714 }
715
716
717 void transaction_xfer_child_sync(Transaction *s_txn, Transaction *child)
718 {
719 Account *acc;
720
721 DB( g_print("\n[transaction] xfer_child_sync\n") );
722
723 if( child == NULL )
724 {
725 DB( g_print(" - no child found\n") );
726 return;
727 }
728
729 DB( g_print(" - found do sync\n") );
730
731 /* update acc flags */
732 acc = da_acc_get( child->kacc);
733 if(acc != NULL)
734 acc->flags |= AF_CHANGED;
735
736 account_balances_sub (child);
737
738 child->date = s_txn->date;
739 child->amount = -s_txn->amount;
740 child->flags = child->flags | OF_CHANGED;
741 //#1295877
742 child->flags &= ~(OF_INCOME);
743 if( child->amount > 0)
744 child->flags |= (OF_INCOME);
745 child->kpay = s_txn->kpay;
746 child->kcat = s_txn->kcat;
747 if(child->memo)
748 g_free(child->memo);
749 child->memo = g_strdup(s_txn->memo);
750 if(child->info)
751 g_free(child->info);
752 child->info = g_strdup(s_txn->info);
753
754 account_balances_add (child);
755
756 //#1252230 sync account also
757 //#1663789 idem after 5.1
758 //source changed: update child key (move of s_txn is done in external_edit)
759 if( s_txn->kacc != child->kxferacc )
760 {
761 child->kxferacc = s_txn->kacc;
762 }
763
764 //dest changed: move child & update child key
765 if( s_txn->kxferacc != child->kacc )
766 {
767 transaction_acc_move(child, child->kacc, s_txn->kxferacc);
768 }
769
770 //synchronise tags since 5.1
771 if(child->tags)
772 g_free(child->tags);
773 transaction_tags_clone (s_txn, child);
774
775 }
776
777
778 void transaction_xfer_remove_child(Transaction *src)
779 {
780 Transaction *dst;
781
782 DB( g_print("\n[transaction] xfer_remove_child\n") );
783
784 dst = transaction_xfer_child_strong_get( src );
785 if( dst != NULL )
786 {
787 Account *acc = da_acc_get(dst->kacc);
788
789 if( acc != NULL )
790 {
791 DB( g_print("deleting...") );
792 account_balances_sub(dst);
793 g_queue_remove(acc->txn_queue, dst);
794 //#1419304 we keep the deleted txn to a trash stack
795 //da_transaction_free (dst);
796 g_trash_stack_push(&GLOBALS->txn_stk, dst);
797
798 //#1691992
799 acc->flags |= AF_CHANGED;
800 }
801 }
802
803 src->kxfer = 0;
804 src->kxferacc = 0;
805 }
806
807
808 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
809 Transaction *transaction_old_get_child_transfer(Transaction *src)
810 {
811 Account *acc;
812 GList *list;
813
814 DB( g_print("\n[transaction] get_child_transfer\n") );
815
816 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
817 acc = da_acc_get(src->kxferacc);
818
819 if( acc != NULL )
820 {
821 list = g_queue_peek_head_link(acc->txn_queue);
822 while (list != NULL)
823 {
824 Transaction *item = list->data;
825
826 // no need to go higher than src txn date
827 if(item->date > src->date)
828 break;
829
830 if( item->paymode == PAYMODE_INTXFER)
831 {
832 if( src->date == item->date &&
833 src->kacc == item->kxferacc &&
834 src->kxferacc == item->kacc &&
835 ABS(src->amount) == ABS(item->amount) )
836 {
837 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
838
839 return item;
840 }
841 }
842 list = g_list_next(list);
843 }
844 }
845
846 DB( g_print(" not found...\n") );
847
848 return NULL;
849 }
850
851
852 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
853
854
855 void transaction_remove(Transaction *ope)
856 {
857 Account *acc;
858
859 //controls accounts valid (archive scheduled maybe bad)
860 acc = da_acc_get(ope->kacc);
861 if(acc == NULL) return;
862
863 account_balances_sub(ope);
864
865 if( ope->paymode == PAYMODE_INTXFER )
866 {
867 transaction_xfer_remove_child( ope );
868 }
869
870 g_queue_remove(acc->txn_queue, ope);
871 acc->flags |= AF_CHANGED;
872 //#1419304 we keep the deleted txn to a trash stack
873 //da_transaction_free(entry);
874 g_trash_stack_push(&GLOBALS->txn_stk, ope);
875 }
876
877
878 Transaction *transaction_add(Transaction *ope)
879 {
880 Transaction *newope;
881 Account *acc;
882
883 DB( g_print("\n[transaction] transaction_add\n") );
884
885 //controls accounts valid (archive scheduled maybe bad)
886 acc = da_acc_get(ope->kacc);
887 if(acc == NULL) return NULL;
888
889 DB( g_print(" acc is '%s' %d\n", acc->name, acc->key) );
890
891 ope->kcur = acc->kcur;
892
893 if(ope->paymode == PAYMODE_INTXFER)
894 {
895 acc = da_acc_get(ope->kxferacc);
896 if(acc == NULL) return NULL;
897
898 // delete any splits
899 da_splits_free(ope->splits);
900 ope->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
901 }
902
903
904 //allocate a new entry and copy from our edited structure
905 newope = da_transaction_clone(ope);
906
907 //init flag and keep remind status
908 // already done in deftransaction_get
909 //ope->flags |= (OF_ADDED);
910 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
911 //ope->flags &= (~OF_REMIND);
912
913 /* cheque number is already stored in deftransaction_get */
914 /* todo:move this to transaction add
915 store a new cheque number into account ? */
916
917 if( (newope->paymode == PAYMODE_CHECK) && (newope->info) && !(newope->flags & OF_INCOME) )
918 {
919 guint cheque;
920
921 /* get the active account and the corresponding cheque number */
922 acc = da_acc_get( newope->kacc);
923 if( acc != NULL )
924 {
925 cheque = atol(newope->info);
926
927 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
928 if( newope->flags & OF_CHEQ2 )
929 {
930 acc->cheque2 = MAX(acc->cheque2, cheque);
931 }
932 else
933 {
934 acc->cheque1 = MAX(acc->cheque1, cheque);
935 }
936 }
937 }
938
939 /* add normal transaction */
940 acc = da_acc_get( newope->kacc);
941 if(acc != NULL)
942 {
943 acc->flags |= AF_ADDED;
944
945 DB( g_print(" + add normal %p to acc %d\n", newope, acc->key) );
946 //da_transaction_append(newope);
947 da_transaction_insert_sorted(newope);
948
949 account_balances_add(newope);
950
951 if(newope->paymode == PAYMODE_INTXFER)
952 {
953 transaction_xfer_search_or_add_child(NULL, newope, FALSE);
954 }
955
956 GValue txn_value = G_VALUE_INIT;
957 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value, newope), NULL);
958 }
959
960 return newope;
961 }
962
963
964 gboolean transaction_acc_move(Transaction *txn, guint32 okacc, guint32 nkacc)
965 {
966 Account *oacc, *nacc;
967
968 DB( g_print("\n[transaction] acc_move\n") );
969
970 if( okacc == nkacc )
971 return TRUE;
972
973 oacc = da_acc_get(okacc);
974 nacc = da_acc_get(nkacc);
975 if( oacc && nacc )
976 {
977 account_balances_sub(txn);
978 if( g_queue_remove(oacc->txn_queue, txn) )
979 {
980 g_queue_push_tail(nacc->txn_queue, txn);
981 txn->kacc = nacc->key;
982 txn->kcur = nacc->kcur;
983 nacc->flags |= AF_CHANGED;
984 account_balances_add(txn);
985 return TRUE;
986 }
987 else
988 {
989 //ensure to keep txn into current account
990 txn->kacc = okacc;
991 account_balances_add(txn);
992 }
993 }
994 return FALSE;
995 }
996
997
998 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
999
1000
1001 static gboolean misc_text_match(gchar *text, gchar *searchtext, gboolean exact)
1002 {
1003 gboolean match = FALSE;
1004
1005 if(text == NULL)
1006 return FALSE;
1007
1008 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1009 if( searchtext != NULL )
1010 {
1011 if( exact == TRUE )
1012 {
1013 if( g_strrstr(text, searchtext) != NULL )
1014 {
1015 DB( g_print(" found case '%s'\n", searchtext) );
1016 match = TRUE;
1017 }
1018 }
1019 else
1020 {
1021 gchar *word = g_utf8_casefold(text, -1);
1022 gchar *needle = g_utf8_casefold(searchtext, -1);
1023
1024 if( g_strrstr(word, needle) != NULL )
1025 {
1026 DB( g_print(" found nocase '%s'\n", searchtext) );
1027 match = TRUE;
1028 }
1029 g_free(word);
1030 g_free(needle);
1031 }
1032 }
1033
1034 return match;
1035 }
1036
1037 static gboolean misc_regex_match(gchar *text, gchar *searchtext, gboolean exact)
1038 {
1039 gboolean match = FALSE;
1040
1041 if(text == NULL)
1042 return FALSE;
1043
1044 DB( g_print("- match RE %s in %s\n", searchtext, text) );
1045 if( searchtext != NULL )
1046 {
1047 match = g_regex_match_simple(searchtext, text, ((exact == TRUE)?0:G_REGEX_CASELESS) | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY );
1048 if (match == TRUE) { DB( g_print(" found pattern '%s'\n", searchtext) ); }
1049 }
1050 return match;
1051 }
1052
1053
1054 static Assign *transaction_auto_assign_eval_txn(GList *l_rul, Transaction *txn)
1055 {
1056 Assign *rule = NULL;
1057 GList *list;
1058
1059 DB( g_print("\n[transaction] auto_assign_eval_txn\n") );
1060
1061 DB( g_print("- eval every rules, and return the last that match\n") );
1062
1063 list = g_list_first(l_rul);
1064 while (list != NULL)
1065 {
1066 Assign *rul = list->data;
1067 gchar *text;
1068
1069 text = txn->memo;
1070 if(rul->field == 1) //payee
1071 {
1072 Payee *pay = da_pay_get(txn->kpay);
1073 if(pay)
1074 text = pay->name;
1075 }
1076
1077 if( !(rul->flags & ASGF_REGEX) )
1078 {
1079 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1080 rule = rul;
1081 }
1082 else
1083 {
1084 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1085 rule = rul;
1086 }
1087
1088 list = g_list_next(list);
1089 }
1090
1091 return rule;
1092 }
1093
1094
1095 static Assign *transaction_auto_assign_eval(GList *l_rul, gchar *text)
1096 {
1097 Assign *rule = NULL;
1098 GList *list;
1099
1100 DB( g_print("\n[transaction] auto_assign_eval\n") );
1101
1102 DB( g_print("- eval every rules, and return the last that match\n") );
1103
1104 list = g_list_first(l_rul);
1105 while (list != NULL)
1106 {
1107 Assign *rul = list->data;
1108
1109 if( rul->field == 0 ) //memo
1110 {
1111 if( !(rul->flags & ASGF_REGEX) )
1112 {
1113 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1114 rule = rul;
1115 }
1116 else
1117 {
1118 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1119 rule = rul;
1120 }
1121 }
1122 list = g_list_next(list);
1123 }
1124
1125 return rule;
1126 }
1127
1128
1129 gint transaction_auto_assign(GList *ope_list, guint32 kacc)
1130 {
1131 GList *l_ope;
1132 GList *l_rul;
1133 gint changes = 0;
1134
1135 DB( g_print("\n[transaction] auto_assign\n") );
1136
1137 l_rul = g_hash_table_get_values(GLOBALS->h_rul);
1138
1139 l_ope = g_list_first(ope_list);
1140 while (l_ope != NULL)
1141 {
1142 Transaction *ope = l_ope->data;
1143 gboolean changed = FALSE;
1144
1145 DB( g_print("- eval ope '%s' : acc=%d, pay=%d, cat=%d\n", ope->memo, ope->kacc, ope->kpay, ope->kcat) );
1146
1147 //#1215521: added kacc == 0
1148 if( (kacc == ope->kacc || kacc == 0) )
1149 {
1150 Assign *rul;
1151
1152 rul = transaction_auto_assign_eval_txn(l_rul, ope);
1153 if( rul != NULL )
1154 {
1155 if( (ope->kpay == 0 && (rul->flags & ASGF_DOPAY)) || (rul->flags & ASGF_OVWPAY) )
1156 {
1157 if(ope->kpay != rul->kpay) { changed = TRUE; }
1158 ope->kpay = rul->kpay;
1159 }
1160
1161 if( !(ope->flags & OF_SPLIT) )
1162 {
1163 if( (ope->kcat == 0 && (rul->flags & ASGF_DOCAT)) || (rul->flags & ASGF_OVWCAT) )
1164 {
1165 if(ope->kcat != rul->kcat) { changed = TRUE; }
1166 ope->kcat = rul->kcat;
1167 }
1168 }
1169
1170 if( (ope->paymode == 0 && (rul->flags & ASGF_DOMOD)) || (rul->flags & ASGF_OVWMOD) )
1171 {
1172 //ugly hack - don't allow modify intxfer
1173 if(ope->paymode != PAYMODE_INTXFER && rul->paymode != PAYMODE_INTXFER)
1174 {
1175 if(ope->paymode != rul->paymode) { changed = TRUE; }
1176 ope->paymode = rul->paymode;
1177 }
1178 }
1179
1180 }
1181
1182 if( ope->flags & OF_SPLIT )
1183 {
1184 guint i, nbsplit = da_splits_count(ope->splits);
1185
1186 for(i=0;i<nbsplit;i++)
1187 {
1188 Split *split = ope->splits[i];
1189
1190 DB( g_print("- eval split '%s'\n", split->memo) );
1191
1192 rul = transaction_auto_assign_eval(l_rul, split->memo);
1193 if( rul != NULL )
1194 {
1195 //#1501144: check if user wants to set category in rule
1196 if( (split->kcat == 0 || (rul->flags & ASGF_OVWCAT)) && (rul->flags & ASGF_DOCAT) )
1197 {
1198 if(split->kcat != rul->kcat) { changed = TRUE; }
1199 split->kcat = rul->kcat;
1200 }
1201 }
1202 }
1203 }
1204
1205 if(changed == TRUE)
1206 {
1207 ope->flags |= OF_CHANGED;
1208 changes++;
1209 }
1210 }
1211
1212 l_ope = g_list_next(l_ope);
1213 }
1214
1215 g_list_free(l_rul);
1216
1217 return changes;
1218 }
1219
1220
1221 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1222
1223
1224 guint
1225 transaction_tags_count(Transaction *ope)
1226 {
1227 guint count = 0;
1228 guint32 *ptr = ope->tags;
1229
1230 //DB( g_print("\n[transaction] tags_count\n") );
1231
1232 if( ope->tags == NULL )
1233 return 0;
1234
1235 while(*ptr++ != 0 && count < 32)
1236 count++;
1237
1238 return count;
1239 }
1240
1241
1242 void transaction_tags_clone(Transaction *src_txn, Transaction *dst_txn)
1243 {
1244 guint count;
1245
1246 dst_txn->tags = NULL;
1247 count = transaction_tags_count(src_txn);
1248 if(count > 0)
1249 {
1250 //1501962: we must also copy the final 0
1251 dst_txn->tags = g_memdup(src_txn->tags, (count+1)*sizeof(guint32));
1252 }
1253 }
1254
1255 guint
1256 transaction_tags_parse(Transaction *ope, const gchar *tagstring)
1257 {
1258 gchar **str_array;
1259 guint count, i;
1260 Tag *tag;
1261
1262 DB( g_print("\n[transaction] tags_parse\n") );
1263
1264 DB( g_print(" - tagstring='%s'\n", tagstring) );
1265
1266 str_array = g_strsplit (tagstring, " ", 0);
1267 count = g_strv_length( str_array );
1268
1269 g_free(ope->tags);
1270 ope->tags = NULL;
1271
1272 DB( g_print(" -> reset storage %p\n", ope->tags) );
1273
1274
1275 if( count > 0 )
1276 {
1277
1278 ope->tags = g_new0(guint32, count + 1);
1279
1280 DB( g_print(" -> storage %p\n", ope->tags) );
1281
1282 for(i=0;i<count;i++)
1283 {
1284 tag = da_tag_get_by_name(str_array[i]);
1285 if(tag == NULL)
1286 {
1287 Tag *newtag = da_tag_malloc();
1288
1289 newtag->name = g_strdup(str_array[i]);
1290 da_tag_append(newtag);
1291 tag = da_tag_get_by_name(str_array[i]);
1292 }
1293
1294 DB( g_print(" -> storing %d=>%s at tags pos %d\n", tag->key, tag->name, i) );
1295
1296 ope->tags[i] = tag->key;
1297 }
1298 ope->tags[i] = 0;
1299 }
1300
1301 //hex_dump(ope->tags, sizeof(guint32*)*count+1);
1302
1303 g_strfreev (str_array);
1304
1305 return count;
1306 }
1307
1308 gchar *
1309 transaction_tags_tostring(Transaction *ope)
1310 {
1311 guint count, i;
1312 gchar **str_array;
1313 gchar *tagstring;
1314 Tag *tag;
1315
1316 DB( g_print("\n[transaction] tags_tostring\n") );
1317
1318 DB( g_print(" -> tags at=%p\n", ope->tags) );
1319
1320 if( ope->tags == NULL )
1321 {
1322
1323 return NULL;
1324 }
1325 else
1326 {
1327 count = transaction_tags_count(ope);
1328
1329 DB( g_print(" -> tags at=%p, nbtags=%d\n", ope->tags, count) );
1330
1331 str_array = g_new0(gchar*, count+1);
1332
1333 DB( g_print(" -> str_array at %p\n", str_array) );
1334
1335 //hex_dump(ope->tags, sizeof(guint32*)*(count+1));
1336
1337 for(i=0;i<count;i++)
1338 {
1339 DB( g_print(" -> try to get tag %d\n", ope->tags[i]) );
1340
1341 tag = da_tag_get(ope->tags[i]);
1342 if( tag )
1343 {
1344 DB( g_print(" -> get %s at %d\n", tag->name, i) );
1345 str_array[i] = tag->name;
1346 }
1347 else
1348 str_array[i] = NULL;
1349
1350
1351 }
1352
1353 tagstring = g_strjoinv(" ", str_array);
1354
1355 g_free (str_array);
1356
1357 }
1358
1359 return tagstring;
1360 }
1361
This page took 0.102884 seconds and 4 git commands to generate.