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