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