]> 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 //#1827193 child can be null...
662 DB( g_print(" child %p\n", child) );
663 if( child != NULL )
664 transaction_xfer_change_to_child(ope, child);
665 else
666 transaction_xfer_create_child(ope);
667 }
668 else //GTK_RESPONSE_CANCEL
669 {
670 ope->paymode = PAYMODE_NONE;
671 ope->kxfer = 0;
672 ope->kxferacc = 0;
673 }
674 }
675 }
676
677 g_list_free(matchlist);
678 }
679
680
681 Transaction *transaction_xfer_child_strong_get(Transaction *src)
682 {
683 Account *dstacc;
684 GList *list;
685
686 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
687
688 dstacc = da_acc_get(src->kxferacc);
689 if( !dstacc || src->kxfer <= 0 )
690 return NULL;
691
692 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) );
693
694 list = g_queue_peek_tail_link(dstacc->txn_queue);
695 while (list != NULL)
696 {
697 Transaction *item = list->data;
698
699 //#1252230
700 //if( item->paymode == PAYMODE_INTXFER
701 // && item->kacc == src->kxferacc
702 // && item->kxfer == src->kxfer )
703 if( item->paymode == PAYMODE_INTXFER
704 && item->kxfer == src->kxfer
705 && item != src )
706 {
707 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item->date, item->memo, item->amount, item->kacc, item->kxferacc, src->kxfer) );
708 return item;
709 }
710 list = g_list_previous(list);
711 }
712
713 DB( g_print(" - not found...\n") );
714 return NULL;
715 }
716
717
718
719
720 void transaction_xfer_change_to_child(Transaction *ope, Transaction *child)
721 {
722 Account *dstacc;
723
724 DB( g_print("\n[transaction] xfer_change_to_child\n") );
725
726 if(ope->kcur != child->kcur)
727 return;
728
729 ope->flags |= OF_CHANGED;
730 child->flags |= OF_CHANGED;
731
732 child->paymode = PAYMODE_INTXFER;
733
734 ope->kxferacc = child->kacc;
735 child->kxferacc = ope->kacc;
736
737 /* update acc flags */
738 dstacc = da_acc_get( child->kacc);
739 if(dstacc != NULL)
740 dstacc->flags |= AF_CHANGED;
741
742 //strong link
743 guint maxkey = da_transaction_get_max_kxfer();
744 ope->kxfer = maxkey+1;
745 child->kxfer = maxkey+1;
746
747 }
748
749
750 void transaction_xfer_child_sync(Transaction *s_txn, Transaction *child)
751 {
752 Account *acc;
753
754 DB( g_print("\n[transaction] xfer_child_sync\n") );
755
756 if( child == NULL )
757 {
758 DB( g_print(" - no child found\n") );
759 return;
760 }
761
762 DB( g_print(" - found do sync\n") );
763
764 /* update acc flags */
765 acc = da_acc_get( child->kacc);
766 if(acc != NULL)
767 acc->flags |= AF_CHANGED;
768
769 account_balances_sub (child);
770
771 child->date = s_txn->date;
772 child->amount = -s_txn->amount;
773 child->flags = child->flags | OF_CHANGED;
774 //#1295877
775 child->flags &= ~(OF_INCOME);
776 if( child->amount > 0)
777 child->flags |= (OF_INCOME);
778 child->kpay = s_txn->kpay;
779 child->kcat = s_txn->kcat;
780 if(child->memo)
781 g_free(child->memo);
782 child->memo = g_strdup(s_txn->memo);
783 if(child->info)
784 g_free(child->info);
785 child->info = g_strdup(s_txn->info);
786
787 account_balances_add (child);
788
789 //#1252230 sync account also
790 //#1663789 idem after 5.1
791 //source changed: update child key (move of s_txn is done in external_edit)
792 if( s_txn->kacc != child->kxferacc )
793 {
794 child->kxferacc = s_txn->kacc;
795 }
796
797 //dest changed: move child & update child key
798 if( s_txn->kxferacc != child->kacc )
799 {
800 transaction_acc_move(child, child->kacc, s_txn->kxferacc);
801 }
802
803 //synchronise tags since 5.1
804 g_free(child->tags);
805 child->tags = tags_clone (s_txn->tags);
806
807 }
808
809
810 void transaction_xfer_remove_child(Transaction *src)
811 {
812 Transaction *dst;
813
814 DB( g_print("\n[transaction] xfer_remove_child\n") );
815
816 dst = transaction_xfer_child_strong_get( src );
817 if( dst != NULL )
818 {
819 Account *acc = da_acc_get(dst->kacc);
820
821 if( acc != NULL )
822 {
823 DB( g_print("deleting...") );
824 account_balances_sub(dst);
825 g_queue_remove(acc->txn_queue, dst);
826 //#1419304 we keep the deleted txn to a trash stack
827 //da_transaction_free (dst);
828 g_trash_stack_push(&GLOBALS->txn_stk, dst);
829
830 //#1691992
831 acc->flags |= AF_CHANGED;
832 }
833 }
834
835 src->kxfer = 0;
836 src->kxferacc = 0;
837 }
838
839
840 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
841 Transaction *transaction_old_get_child_transfer(Transaction *src)
842 {
843 Account *acc;
844 GList *list;
845
846 DB( g_print("\n[transaction] get_child_transfer\n") );
847
848 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
849 acc = da_acc_get(src->kxferacc);
850
851 if( acc != NULL )
852 {
853 list = g_queue_peek_head_link(acc->txn_queue);
854 while (list != NULL)
855 {
856 Transaction *item = list->data;
857
858 // no need to go higher than src txn date
859 if(item->date > src->date)
860 break;
861
862 if( item->paymode == PAYMODE_INTXFER)
863 {
864 if( src->date == item->date &&
865 src->kacc == item->kxferacc &&
866 src->kxferacc == item->kacc &&
867 ABS(src->amount) == ABS(item->amount) )
868 {
869 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
870
871 return item;
872 }
873 }
874 list = g_list_next(list);
875 }
876 }
877
878 DB( g_print(" not found...\n") );
879
880 return NULL;
881 }
882
883
884 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
885
886
887 void transaction_remove(Transaction *ope)
888 {
889 Account *acc;
890
891 //controls accounts valid (archive scheduled maybe bad)
892 acc = da_acc_get(ope->kacc);
893 if(acc == NULL) return;
894
895 account_balances_sub(ope);
896
897 if( ope->paymode == PAYMODE_INTXFER )
898 {
899 transaction_xfer_remove_child( ope );
900 }
901
902 g_queue_remove(acc->txn_queue, ope);
903 acc->flags |= AF_CHANGED;
904 //#1419304 we keep the deleted txn to a trash stack
905 //da_transaction_free(entry);
906 g_trash_stack_push(&GLOBALS->txn_stk, ope);
907 }
908
909
910 void transaction_changed(Transaction *txn)
911 {
912 Account *acc;
913
914 if( txn == NULL )
915 return;
916
917 acc = da_acc_get(txn->kacc);
918 if(acc == NULL)
919 return;
920
921 acc->flags |= AF_CHANGED;
922 }
923
924
925 Transaction *transaction_add(GtkWindow *parent, Transaction *ope)
926 {
927 Transaction *newope;
928 Account *acc;
929
930 DB( g_print("\n[transaction] transaction_add\n") );
931
932 //controls accounts valid (archive scheduled maybe bad)
933 acc = da_acc_get(ope->kacc);
934 if(acc == NULL) return NULL;
935
936 DB( g_print(" acc is '%s' %d\n", acc->name, acc->key) );
937
938 ope->kcur = acc->kcur;
939
940 if(ope->paymode == PAYMODE_INTXFER)
941 {
942 acc = da_acc_get(ope->kxferacc);
943 if(acc == NULL) return NULL;
944
945 // delete any splits
946 da_split_destroy(ope->splits);
947 ope->splits = NULL;
948 ope->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
949 }
950
951
952 //allocate a new entry and copy from our edited structure
953 newope = da_transaction_clone(ope);
954
955 //init flag and keep remind status
956 // already done in deftransaction_get
957 //ope->flags |= (OF_ADDED);
958 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
959 //ope->flags &= (~OF_REMIND);
960
961 /* cheque number is already stored in deftransaction_get */
962 /* todo:move this to transaction add
963 store a new cheque number into account ? */
964
965 if( (newope->paymode == PAYMODE_CHECK) && (newope->info) && !(newope->flags & OF_INCOME) )
966 {
967 guint cheque;
968
969 /* get the active account and the corresponding cheque number */
970 acc = da_acc_get( newope->kacc);
971 if( acc != NULL )
972 {
973 cheque = atol(newope->info);
974
975 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
976 if( newope->flags & OF_CHEQ2 )
977 {
978 acc->cheque2 = MAX(acc->cheque2, cheque);
979 }
980 else
981 {
982 acc->cheque1 = MAX(acc->cheque1, cheque);
983 }
984 }
985 }
986
987 /* add normal transaction */
988 acc = da_acc_get( newope->kacc);
989 if(acc != NULL)
990 {
991 acc->flags |= AF_ADDED;
992
993 DB( g_print(" + add normal %p to acc %d\n", newope, acc->key) );
994 //da_transaction_append(newope);
995 da_transaction_insert_sorted(newope);
996
997 account_balances_add(newope);
998
999 if(newope->paymode == PAYMODE_INTXFER)
1000 {
1001 transaction_xfer_search_or_add_child(parent, newope, newope->kxferacc);
1002 }
1003
1004 GValue txn_value = G_VALUE_INIT;
1005 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value, newope), NULL);
1006 }
1007
1008 return newope;
1009 }
1010
1011
1012 gboolean transaction_acc_move(Transaction *txn, guint32 okacc, guint32 nkacc)
1013 {
1014 Account *oacc, *nacc;
1015
1016 DB( g_print("\n[transaction] acc_move\n") );
1017
1018 if( okacc == nkacc )
1019 return TRUE;
1020
1021 oacc = da_acc_get(okacc);
1022 nacc = da_acc_get(nkacc);
1023 if( oacc && nacc )
1024 {
1025 account_balances_sub(txn);
1026 if( g_queue_remove(oacc->txn_queue, txn) )
1027 {
1028 g_queue_push_tail(nacc->txn_queue, txn);
1029 txn->kacc = nacc->key;
1030 txn->kcur = nacc->kcur;
1031 nacc->flags |= AF_CHANGED;
1032 account_balances_add(txn);
1033 return TRUE;
1034 }
1035 else
1036 {
1037 //ensure to keep txn into current account
1038 txn->kacc = okacc;
1039 account_balances_add(txn);
1040 }
1041 }
1042 return FALSE;
1043 }
1044
1045
1046 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1047
1048
1049 static gboolean misc_text_match(gchar *text, gchar *searchtext, gboolean exact)
1050 {
1051 gboolean match = FALSE;
1052
1053 if(text == NULL)
1054 return FALSE;
1055
1056 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1057 if( searchtext != NULL )
1058 {
1059 if( exact == TRUE )
1060 {
1061 if( g_strrstr(text, searchtext) != NULL )
1062 {
1063 DB( g_print("-- found case '%s'\n", searchtext) );
1064 match = TRUE;
1065 }
1066 }
1067 else
1068 {
1069 gchar *word = g_utf8_casefold(text, -1);
1070 gchar *needle = g_utf8_casefold(searchtext, -1);
1071
1072 if( g_strrstr(word, needle) != NULL )
1073 {
1074 DB( g_print("-- found nocase '%s'\n", searchtext) );
1075 match = TRUE;
1076 }
1077 g_free(word);
1078 g_free(needle);
1079 }
1080 }
1081
1082 return match;
1083 }
1084
1085 static gboolean misc_regex_match(gchar *text, gchar *searchtext, gboolean exact)
1086 {
1087 gboolean match = FALSE;
1088
1089 if(text == NULL)
1090 return FALSE;
1091
1092 DB( g_print("-- match RE %s in %s\n", searchtext, text) );
1093 if( searchtext != NULL )
1094 {
1095 match = g_regex_match_simple(searchtext, text, ((exact == TRUE)?0:G_REGEX_CASELESS) | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY );
1096 if (match == TRUE) { DB( g_print("-- found pattern '%s'\n", searchtext) ); }
1097 }
1098 return match;
1099 }
1100
1101
1102 static GList *transaction_auto_assign_eval_txn(GList *l_rul, Transaction *txn)
1103 {
1104 GList *ret_list = NULL;
1105 GList *list;
1106 gchar *text;
1107
1108 list = g_list_first(l_rul);
1109 while (list != NULL)
1110 {
1111 Assign *rul = list->data;
1112
1113 text = txn->memo;
1114 if(rul->field == 1) //payee
1115 {
1116 Payee *pay = da_pay_get(txn->kpay);
1117 if(pay)
1118 text = pay->name;
1119 }
1120
1121 if( !(rul->flags & ASGF_REGEX) )
1122 {
1123 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1124 ret_list = g_list_append(ret_list, rul);
1125 }
1126 else
1127 {
1128 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1129 ret_list = g_list_append(ret_list, rul);
1130 }
1131
1132 list = g_list_next(list);
1133 }
1134
1135 DB( g_print("- evaluated txn '%s'=> %d match\n", text, g_list_length (ret_list)) );
1136
1137 return ret_list;
1138 }
1139
1140
1141 static GList *transaction_auto_assign_eval(GList *l_rul, gchar *text)
1142 {
1143 GList *ret_list = NULL;
1144 GList *list;
1145
1146 list = g_list_first(l_rul);
1147 while (list != NULL)
1148 {
1149 Assign *rul = list->data;
1150
1151 if( rul->field == 0 ) //memo
1152 {
1153 if( !(rul->flags & ASGF_REGEX) )
1154 {
1155 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1156 ret_list = g_list_append(ret_list, rul);
1157 }
1158 else
1159 {
1160 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1161 ret_list = g_list_append(ret_list, rul);
1162 }
1163 }
1164 list = g_list_next(list);
1165 }
1166
1167 DB( g_print("- evaluated split '%s' => %d match\n", text, g_list_length (ret_list)) );
1168
1169 return ret_list;
1170 }
1171
1172
1173 guint transaction_auto_assign(GList *ope_list, guint32 kacc)
1174 {
1175 GList *l_ope;
1176 GList *l_rul;
1177 GList *l_match, *l_tmp;
1178 guint changes = 0;
1179
1180 DB( g_print("\n[transaction] auto_assign\n") );
1181
1182 l_rul = g_hash_table_get_values(GLOBALS->h_rul);
1183
1184 l_ope = g_list_first(ope_list);
1185 while (l_ope != NULL)
1186 {
1187 Transaction *ope = l_ope->data;
1188 gboolean changed = FALSE;
1189
1190 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" : "" ) );
1191
1192 //#1215521: added kacc == 0
1193 if( (kacc == ope->kacc || kacc == 0) )
1194 {
1195 if( !(ope->flags & OF_SPLIT) )
1196 {
1197 l_match = l_tmp = transaction_auto_assign_eval_txn(l_rul, ope);
1198 while( l_tmp != NULL )
1199 {
1200 Assign *rul = l_tmp->data;
1201
1202 if( (ope->kpay == 0 && (rul->flags & ASGF_DOPAY)) || (rul->flags & ASGF_OVWPAY) )
1203 {
1204 if(ope->kpay != rul->kpay) { changed = TRUE; }
1205 ope->kpay = rul->kpay;
1206 }
1207
1208 if( (ope->kcat == 0 && (rul->flags & ASGF_DOCAT)) || (rul->flags & ASGF_OVWCAT) )
1209 {
1210 if(ope->kcat != rul->kcat) { changed = TRUE; }
1211 ope->kcat = rul->kcat;
1212 }
1213
1214 if( (ope->paymode == 0 && (rul->flags & ASGF_DOMOD)) || (rul->flags & ASGF_OVWMOD) )
1215 {
1216 //ugly hack - don't allow modify intxfer
1217 if(ope->paymode != PAYMODE_INTXFER && rul->paymode != PAYMODE_INTXFER)
1218 {
1219 if(ope->paymode != rul->paymode) { changed = TRUE; }
1220 ope->paymode = rul->paymode;
1221 }
1222 }
1223 l_tmp = g_list_next(l_tmp);
1224 }
1225 g_list_free(l_match);
1226 }
1227 else
1228 {
1229 guint i, nbsplit = da_splits_length(ope->splits);
1230
1231 for(i=0;i<nbsplit;i++)
1232 {
1233 Split *split = da_splits_get(ope->splits, i);
1234
1235 DB( g_print("- eval split '%s'\n", split->memo) );
1236
1237 l_match = l_tmp = transaction_auto_assign_eval(l_rul, split->memo);
1238 while( l_tmp != NULL )
1239 {
1240 Assign *rul = l_tmp->data;
1241
1242 //#1501144: check if user wants to set category in rule
1243 if( (split->kcat == 0 || (rul->flags & ASGF_OVWCAT)) && (rul->flags & ASGF_DOCAT) )
1244 {
1245 if(split->kcat != rul->kcat) { changed = TRUE; }
1246 split->kcat = rul->kcat;
1247 }
1248 l_tmp = g_list_next(l_tmp);
1249 }
1250 g_list_free(l_match);
1251 }
1252 }
1253
1254 if(changed == TRUE)
1255 {
1256 ope->flags |= OF_CHANGED;
1257 changes++;
1258 }
1259 }
1260
1261 l_ope = g_list_next(l_ope);
1262 }
1263
1264 g_list_free(l_rul);
1265
1266 return changes;
1267 }
1268
1269
1270
1271 static gboolean transaction_similar_match(Transaction *stxn, Transaction *dtxn, guint32 daygap)
1272 {
1273 gboolean retval = FALSE;
1274
1275 if(stxn == dtxn)
1276 return FALSE;
1277
1278 DB( g_print(" date: %d - %d = %d\n", stxn->date, dtxn->date, stxn->date - dtxn->date) );
1279
1280 if( stxn->kcur == dtxn->kcur
1281 && stxn->amount == dtxn->amount
1282 && ( (stxn->date - dtxn->date) <= daygap )
1283 //todo: at import we also check payee, but maybe too strict here
1284 && (hb_string_compare(stxn->memo, dtxn->memo) == 0)
1285 )
1286 {
1287 retval = TRUE;
1288 }
1289 return retval;
1290 }
1291
1292
1293 void transaction_similar_unmark(Account *acc)
1294 {
1295 GList *lnk_txn;
1296
1297 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1298 while (lnk_txn != NULL)
1299 {
1300 Transaction *stxn = lnk_txn->data;
1301 stxn->marker = TXN_MARK_NONE;
1302 lnk_txn = g_list_previous(lnk_txn);
1303 }
1304 }
1305
1306
1307 gint transaction_similar_mark(Account *acc, guint32 daygap)
1308 {
1309 GList *lnk_txn, *list2;
1310 gint nball = 0;
1311 gint nbdup = 0;
1312
1313 //warning the list must be sorted by date then amount
1314 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1315 DB( g_print("\n[transaction] check duplicate\n") );
1316
1317 DB( g_print("\n - account:'%s' gap:%d\n", acc->name, daygap) );
1318
1319 #if MYDEBUG == 1
1320 GTimer *t = g_timer_new();
1321 g_print(" - start parse\n");
1322 #endif
1323
1324
1325 /*
1326 llast = g_list_last(old ope list);
1327 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1328 g_timer_reset(t);
1329
1330 ltxn = llast->data;
1331 g_date_clear(&gd, 1);
1332 g_date_set_julian(&gd, ltxn->date);
1333 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));
1334
1335 minjulian = ltxn->date - (366*2);
1336 g_date_clear(&gd, 1);
1337 g_date_set_julian(&gd, minjulian);
1338 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));
1339 */
1340
1341 transaction_similar_unmark(acc);
1342
1343 //mark duplicate
1344 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1345 while (lnk_txn != NULL)
1346 {
1347 Transaction *stxn = lnk_txn->data;
1348
1349 //if(stxn->date < minjulian)
1350 // break;
1351 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1352
1353 list2 = g_list_previous(lnk_txn);
1354 while (list2 != NULL)
1355 {
1356 Transaction *dtxn = list2->data;
1357
1358 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1359
1360 if( (stxn->date - dtxn->date) > daygap )
1361 {
1362 DB( g_print(" break %d %d\n", (dtxn->date - daygap) , (stxn->date - daygap)) );
1363 break;
1364 }
1365
1366 if( dtxn->marker == TXN_MARK_NONE )
1367 {
1368 if( transaction_similar_match(stxn, dtxn, daygap) )
1369 {
1370 stxn->marker = TXN_MARK_DUPSRC;
1371 dtxn->marker = TXN_MARK_DUPDST;
1372 DB( g_print(" = dtxn marker=%d\n", dtxn->marker) );
1373 nball++;
1374 }
1375 }
1376 else
1377 {
1378 DB( g_print(" already marked %d\n", dtxn->marker) );
1379 }
1380
1381
1382 list2 = g_list_previous(list2);
1383 }
1384
1385 DB( g_print(" = stxn marker=%d\n", stxn->marker) );
1386 if( stxn->marker == TXN_MARK_DUPSRC )
1387 nbdup++;
1388
1389 lnk_txn = g_list_previous(lnk_txn);
1390 }
1391
1392 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1393 DB( g_timer_destroy (t) );
1394
1395 DB( g_print(" - found: %d/%d dup\n", nbdup, nball ) );
1396
1397 return nbdup;
1398 }
1399
1400
1401 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1402 /* = = experimental = = */
1403 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1404
1405
1406 /*
1407 probably add a structure hosted into a glist here
1408 with kind of problem: duplicate, child xfer, orphan xfer
1409 and collect all that with target txn
1410 */
1411
1412
1413 /*void future_transaction_test_account(Account *acc)
1414 {
1415 GList *lnk_txn, *list2;
1416 gint nball = 0;
1417 gint nbdup = 0;
1418 gint nbxfer = 0;
1419 GPtrArray *array;
1420
1421 //future
1422 gint gapday = 0, i;
1423
1424 //warning the list must be sorted by date then amount
1425 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1426
1427 DB( g_print("\n[transaction] check duplicate\n") );
1428
1429
1430
1431 DB( g_print("\n - account:'%s'\n", acc->name) );
1432
1433 GTimer *t = g_timer_new();
1434 g_print(" - start parse\n");
1435
1436
1437 llast = g_list_last(old ope list);
1438 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1439 g_timer_reset(t);
1440
1441 ltxn = llast->data;
1442 g_date_clear(&gd, 1);
1443 g_date_set_julian(&gd, ltxn->date);
1444 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));
1445
1446 minjulian = ltxn->date - (366*2);
1447 g_date_clear(&gd, 1);
1448 g_date_set_julian(&gd, minjulian);
1449 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));
1450
1451 array = g_ptr_array_sized_new (25);
1452
1453 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1454 while (lnk_txn != NULL)
1455 {
1456 Transaction *stxn = lnk_txn->data;
1457
1458 //if(stxn->date < minjulian)
1459 // break;
1460 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1461
1462 stxn->marker = 0;
1463 list2 = g_list_previous(lnk_txn);
1464 while (list2 != NULL)
1465 {
1466 Transaction *dtxn = list2->data;
1467
1468 stxn->marker = 0;
1469 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1470 break;
1471
1472 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1473
1474 if( transaction_similar_match(stxn, dtxn, gapday) )
1475 {
1476 g_ptr_array_add (array, stxn);
1477 g_ptr_array_add (array, dtxn);
1478 nbdup++;
1479 DB( g_print(" + dst=1 src=1\n") );
1480 }
1481
1482 nball++;
1483 list2 = g_list_previous(list2);
1484 }
1485
1486 lnk_txn = g_list_previous(lnk_txn);
1487 }
1488
1489 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1490 DB( g_timer_destroy (t) );
1491
1492 for(i=0;i<array->len;i++)
1493 {
1494 Transaction *txn = g_ptr_array_index(array, i);
1495 txn->marker = 1;
1496 }
1497
1498 g_ptr_array_free(array, TRUE);
1499
1500 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
1501
1502 }
1503
1504
1505
1506
1507
1508 //todo: add a limitation, no need to go through all txn
1509 // 1 year in th past, or abolute number ?
1510 gint future_transaction_test_notification(void)
1511 {
1512 GList *lst_acc, *lnk_acc;
1513
1514 DB( g_print("\ntransaction_test_notification\n") );
1515
1516 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1517 lnk_acc = g_list_first(lst_acc);
1518 while (lnk_acc != NULL)
1519 {
1520 Account *acc = lnk_acc->data;
1521
1522 transaction_similar_mark(acc);
1523
1524 lnk_acc = g_list_next(lnk_acc);
1525 }
1526 g_list_free(lst_acc);
1527
1528 return 0;
1529 }
1530 */
1531
1532
1533
This page took 0.109874 seconds and 4 git commands to generate.