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