1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 Maxime DOYEN
4 * This file is part of HomeBank.
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.
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.
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/>.
22 #include "hb-transaction.h"
26 /****************************************************************************/
28 /****************************************************************************/
37 /* our global datas */
38 extern struct HomeBank
*GLOBALS
;
39 extern struct Preferences
*PREFS
;
42 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
45 da_transaction_clean(Transaction
*item
)
49 if(item
->memo
!= NULL
)
54 if(item
->info
!= NULL
)
59 if(item
->tags
!= NULL
)
61 DB( g_print(" -> item->tags %p\n", item
->tags
) );
66 if(item
->splits
!= NULL
)
68 da_split_destroy(item
->splits
);
70 item
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
73 if(item
->same
!= NULL
)
75 g_list_free(item
->same
);
83 da_transaction_free(Transaction
*item
)
87 da_transaction_clean(item
);
94 da_transaction_malloc(void)
96 return g_malloc0(sizeof(Transaction
));
100 Transaction
*da_transaction_init_from_template(Transaction
*txn
, Archive
*arc
)
102 DB( g_print("da_transaction_init_from_template\n") );
105 txn
->amount
= arc
->amount
;
106 //#1258344 keep the current account if tpl is empty
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
);
118 //copy tags (with free previous here)
120 txn
->tags
= tags_clone(arc
->tags
);
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
131 Transaction
*da_transaction_set_default_template(Transaction
*txn
)
136 DB( g_print("da_transaction_set_default_template\n") );
138 acc
= da_acc_get(txn
->kacc
);
139 if(acc
!= NULL
&& acc
->karc
> 0)
141 arc
= da_archive_get(acc
->karc
);
144 DB( g_print(" - init with default template\n") );
145 da_transaction_init_from_template(txn
, arc
);
153 Transaction
*da_transaction_clone(Transaction
*src_item
)
155 Transaction
*new_item
= g_memdup(src_item
, sizeof(Transaction
));
157 DB( g_print("da_transaction_clone\n") );
161 //duplicate the string
162 new_item
->memo
= g_strdup(src_item
->memo
);
163 new_item
->info
= g_strdup(src_item
->info
);
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
);
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
179 da_transaction_new(void)
186 da_transaction_length(void)
188 GList
*lst_acc
, *lnk_acc
;
191 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
192 lnk_acc
= g_list_first(lst_acc
);
193 while (lnk_acc
!= NULL
)
195 Account
*acc
= lnk_acc
->data
;
197 count
+= g_queue_get_length (acc
->txn_queue
);
198 lnk_acc
= g_list_next(lnk_acc
);
200 g_list_free(lst_acc
);
205 static void da_transaction_queue_free_ghfunc(Transaction
*item
, gpointer data
)
207 da_transaction_free (item
);
211 void da_transaction_destroy(void)
215 lacc
= g_hash_table_get_values(GLOBALS
->h_acc
);
216 list
= g_list_first(lacc
);
219 Account
*acc
= list
->data
;
221 g_queue_foreach(acc
->txn_queue
, (GFunc
)da_transaction_queue_free_ghfunc
, NULL
);
222 list
= g_list_next(list
);
228 static gint
da_transaction_compare_datafunc(Transaction
*a
, Transaction
*b
, gpointer data
)
230 return ((gint
)a
->date
- b
->date
);
234 void da_transaction_queue_sort(GQueue
*queue
)
236 g_queue_sort(queue
, (GCompareDataFunc
)da_transaction_compare_datafunc
, NULL
);
240 static gint
da_transaction_compare_func(Transaction
*a
, Transaction
*b
)
242 return ((gint
)a
->date
- b
->date
);
246 GList
*da_transaction_sort(GList
*list
)
248 return( g_list_sort(list
, (GCompareFunc
)da_transaction_compare_func
));
252 gboolean
da_transaction_insert_memo(Transaction
*item
)
254 gboolean retval
= FALSE
;
256 if( item
->memo
!= NULL
)
258 //# 1673048 add filter on status and date obsolete
259 if( (PREFS
->txn_memoacp
== TRUE
) && (item
->date
>= (GLOBALS
->today
- PREFS
->txn_memoacp_days
)) )
261 if( g_hash_table_lookup(GLOBALS
->h_memo
, item
->memo
) == NULL
)
263 retval
= g_hash_table_insert(GLOBALS
->h_memo
, g_strdup(item
->memo
), NULL
);
271 gboolean
da_transaction_insert_sorted(Transaction
*newitem
)
276 acc
= da_acc_get(newitem
->kacc
);
280 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
281 while (lnk_txn
!= NULL
)
283 Transaction
*item
= lnk_txn
->data
;
285 if(item
->date
<= newitem
->date
)
288 lnk_txn
= g_list_previous(lnk_txn
);
291 // we're at insert point, insert after txn
292 g_queue_insert_after(acc
->txn_queue
, lnk_txn
, newitem
);
294 da_transaction_insert_memo(newitem
);
299 // nota: this is called only when loading xml file
300 gboolean
da_transaction_prepend(Transaction
*item
)
304 acc
= da_acc_get(item
->kacc
);
309 item
->kcur
= acc
->kcur
;
310 g_queue_push_tail(acc
->txn_queue
, item
);
311 da_transaction_insert_memo(item
);
316 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
319 da_transaction_get_max_kxfer(void)
321 GList
*lst_acc
, *lnk_acc
;
325 DB( g_print("da_transaction_get_max_kxfer\n") );
327 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
328 lnk_acc
= g_list_first(lst_acc
);
329 while (lnk_acc
!= NULL
)
331 Account
*acc
= lnk_acc
->data
;
333 list
= g_queue_peek_head_link(acc
->txn_queue
);
336 Transaction
*item
= list
->data
;
338 if( item
->paymode
== PAYMODE_INTXFER
)
340 max_key
= MAX(max_key
, item
->kxfer
);
342 list
= g_list_next(list
);
345 lnk_acc
= g_list_next(lnk_acc
);
347 g_list_free(lst_acc
);
349 DB( g_print(" max_key : %d \n", max_key
) );
355 static void da_transaction_goto_orphan(Transaction
*txn
)
357 const gchar
*oatn
= "orphaned transactions";
358 Account
*ori_acc
, *acc
;
361 DB( g_print("\n[transaction] goto orphan\n") );
363 g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn
->date
, txn
->memo
, txn
->amount
);
365 acc
= da_acc_get_by_name((gchar
*)oatn
);
368 acc
= da_acc_malloc();
369 acc
->name
= g_strdup(oatn
);
371 DB( g_print(" - created orphan acc %d\n", acc
->key
) );
374 ori_acc
= da_acc_get(txn
->kacc
);
377 found
= g_queue_remove(ori_acc
->txn_queue
, txn
);
378 DB( g_print(" - found in origin ? %d\n", found
) );
381 txn
->kacc
= acc
->key
;
382 da_transaction_insert_sorted (txn
);
383 DB( g_print("moved txn to %d\n", txn
->kacc
) );
389 void da_transaction_consistency(Transaction
*item
)
396 DB( g_print("\n[transaction] consistency\n") );
398 // ensure date is between range
399 item
->date
= CLAMP(item
->date
, HB_MINDATE
, HB_MAXDATE
);
401 // check account exists
402 acc
= da_acc_get(item
->kacc
);
405 g_warning("txn consistency: fixed invalid acc %d", item
->kacc
);
406 da_transaction_goto_orphan(item
);
407 GLOBALS
->changes_count
++;
410 // check category exists
411 cat
= da_cat_get(item
->kcat
);
414 g_warning("txn consistency: fixed invalid cat %d", item
->kcat
);
416 GLOBALS
->changes_count
++;
419 //#1340142 check split category
420 if( item
->splits
!= NULL
)
422 nbsplit
= da_splits_consistency(item
->splits
);
423 //# 1416624 empty category when split
424 if(nbsplit
> 0 && item
->kcat
> 0)
426 g_warning("txn consistency: fixed invalid cat on split txn");
428 GLOBALS
->changes_count
++;
432 // check payee exists
433 pay
= da_pay_get(item
->kpay
);
436 g_warning("txn consistency: fixed invalid pay %d", item
->kpay
);
438 GLOBALS
->changes_count
++;
441 // reset dst acc for non xfer transaction
442 if( item
->paymode
!= PAYMODE_INTXFER
)
448 // check dst account exists
449 if( item
->paymode
== PAYMODE_INTXFER
)
451 gint tak
= item
->kxferacc
;
453 item
->kxferacc
= ABS(tak
); //I crossed negative here one day
454 acc
= da_acc_get(item
->kxferacc
);
457 g_warning("txn consistency: fixed invalid dst_acc %d", item
->kxferacc
);
458 da_transaction_goto_orphan(item
);
460 item
->paymode
= PAYMODE_XFER
;
461 GLOBALS
->changes_count
++;
465 //#1628678 tags for internal xfer should be checked as well
466 //#1787826 intxfer should not have split
468 //#1295877 ensure income flag is correctly set
469 item
->flags
&= ~(OF_INCOME
);
470 if( item
->amount
> 0)
471 item
->flags
|= (OF_INCOME
);
473 //#1308745 ensure remind flag unset if reconciled
475 //if( item->flags & OF_VALID )
476 // item->flags &= ~(OF_REMIND);
481 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
482 /* new transfer functions */
484 static void transaction_xfer_create_child(Transaction
*ope
)
490 DB( g_print("\n[transaction] xfer_create_child\n") );
492 if( ope
->kxferacc
> 0 )
494 child
= da_transaction_clone(ope
);
496 ope
->flags
|= OF_CHANGED
;
497 child
->flags
|= OF_ADDED
;
499 child
->amount
= -child
->amount
;
500 child
->flags
^= (OF_INCOME
); // invert flag
502 if( child
->status
!= TXN_STATUS_REMIND
)
503 child
->status
= TXN_STATUS_NONE
;
504 //child->flags &= ~(OF_VALID); // delete reconcile state
507 child
->kacc
= child
->kxferacc
;
508 child
->kxferacc
= swap
;
510 /* update acc flags */
511 acc
= da_acc_get( child
->kacc
);
514 acc
->flags
|= AF_ADDED
;
517 guint maxkey
= da_transaction_get_max_kxfer();
519 DB( g_print(" + maxkey is %d\n", maxkey
) );
522 ope
->kxfer
= maxkey
+1;
523 child
->kxfer
= maxkey
+1;
525 DB( g_print(" + strong link to %d\n", ope
->kxfer
) );
528 DB( g_print(" + add transfer, %p to acc %d\n", child
, acc
->key
) );
530 da_transaction_insert_sorted(child
);
532 account_balances_add (child
);
540 //todo: add strong control and extend to payee, maybe memo ?
541 static gboolean
transaction_xfer_child_might(Transaction
*stxn
, Transaction
*dtxn
, gint daygap
)
543 gboolean retval
= FALSE
;
545 //DB( g_print("\n[transaction] xfer_child_might\n") );
552 g_print(" %d %d %d %f %d\n",
553 stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer );
556 g_print(" %d %d %d %f %d\n",
557 dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer );
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
) &&
570 //g_print(" return %d\n", retval);
575 static GList
*transaction_xfer_child_might_list_get(Transaction
*ope
, guint32 kdstacc
)
577 GList
*lst_acc
, *lnk_acc
;
578 GList
*list
, *matchlist
= NULL
;
580 DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
582 DB( g_print(" - kdstacc:%d\n", kdstacc
) );
584 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
585 lnk_acc
= g_list_first(lst_acc
);
586 while (lnk_acc
!= NULL
)
588 Account
*acc
= lnk_acc
->data
;
590 if( !(acc
->flags
& AF_CLOSED
) && (acc
->key
!= ope
->kacc
) && ( (acc
->key
== kdstacc
) || kdstacc
== 0 ) )
592 list
= g_queue_peek_tail_link(acc
->txn_queue
);
595 Transaction
*item
= list
->data
;
597 // no need to go higher than src txn date
598 if(item
->date
< ope
->date
)
601 if( transaction_xfer_child_might(ope
, item
, 0) == TRUE
)
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
);
606 list
= g_list_previous(list
);
610 lnk_acc
= g_list_next(lnk_acc
);
612 g_list_free(lst_acc
);
618 void transaction_xfer_search_or_add_child(GtkWindow
*parent
, Transaction
*ope
, guint32 kdstacc
)
623 DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
625 matchlist
= transaction_xfer_child_might_list_get(ope
, kdstacc
);
626 count
= g_list_length(matchlist
);
628 DB( g_print(" - found %d might match, switching\n", count
) );
632 case 0: //we should create the child
633 transaction_xfer_create_child(ope
);
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
639 case 1: //transform the transaction to a child transfer
641 GList *list = g_list_first(matchlist);
642 transaction_xfer_change_to_child(ope, list->data);
647 default: //the user must choose himself
652 result
= ui_dialog_transaction_xfer_select_child(parent
, ope
, matchlist
, &child
);
653 if( result
== GTK_RESPONSE_ACCEPT
)
655 //#1827193 child can be null...
656 DB( g_print(" child %p\n", child
) );
658 transaction_xfer_change_to_child(ope
, child
);
660 transaction_xfer_create_child(ope
);
662 else //GTK_RESPONSE_CANCEL
664 ope
->paymode
= PAYMODE_NONE
;
671 g_list_free(matchlist
);
675 Transaction
*transaction_xfer_child_strong_get(Transaction
*src
)
680 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
682 dstacc
= da_acc_get(src
->kxferacc
);
683 if( !dstacc
|| src
->kxfer
<= 0 )
686 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src
->date
, src
->memo
, src
->amount
, src
->kacc
, src
->kxferacc
, src
->kxfer
) );
688 list
= g_queue_peek_tail_link(dstacc
->txn_queue
);
691 Transaction
*item
= list
->data
;
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
701 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item
->date
, item
->memo
, item
->amount
, item
->kacc
, item
->kxferacc
, src
->kxfer
) );
704 list
= g_list_previous(list
);
707 DB( g_print(" - not found...\n") );
714 void transaction_xfer_change_to_child(Transaction
*ope
, Transaction
*child
)
718 DB( g_print("\n[transaction] xfer_change_to_child\n") );
720 if(ope
->kcur
!= child
->kcur
)
723 ope
->flags
|= OF_CHANGED
;
724 child
->flags
|= OF_CHANGED
;
726 child
->paymode
= PAYMODE_INTXFER
;
728 ope
->kxferacc
= child
->kacc
;
729 child
->kxferacc
= ope
->kacc
;
731 /* update acc flags */
732 dstacc
= da_acc_get( child
->kacc
);
734 dstacc
->flags
|= AF_CHANGED
;
737 guint maxkey
= da_transaction_get_max_kxfer();
738 ope
->kxfer
= maxkey
+1;
739 child
->kxfer
= maxkey
+1;
744 void transaction_xfer_child_sync(Transaction
*s_txn
, Transaction
*child
)
748 DB( g_print("\n[transaction] xfer_child_sync\n") );
752 DB( g_print(" - no child found\n") );
756 DB( g_print(" - found do sync\n") );
758 /* update acc flags */
759 acc
= da_acc_get( child
->kacc
);
761 acc
->flags
|= AF_CHANGED
;
763 account_balances_sub (child
);
765 child
->date
= s_txn
->date
;
766 child
->amount
= -s_txn
->amount
;
767 child
->flags
= child
->flags
| OF_CHANGED
;
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
;
776 child
->memo
= g_strdup(s_txn
->memo
);
779 child
->info
= g_strdup(s_txn
->info
);
781 account_balances_add (child
);
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
)
788 child
->kxferacc
= s_txn
->kacc
;
791 //dest changed: move child & update child key
792 if( s_txn
->kxferacc
!= child
->kacc
)
794 transaction_acc_move(child
, child
->kacc
, s_txn
->kxferacc
);
797 //synchronise tags since 5.1
799 child
->tags
= tags_clone (s_txn
->tags
);
804 void transaction_xfer_remove_child(Transaction
*src
)
808 DB( g_print("\n[transaction] xfer_remove_child\n") );
810 dst
= transaction_xfer_child_strong_get( src
);
813 Account
*acc
= da_acc_get(dst
->kacc
);
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
);
825 acc
->flags
|= AF_CHANGED
;
834 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
835 Transaction
*transaction_old_get_child_transfer(Transaction
*src
)
840 DB( g_print("\n[transaction] get_child_transfer\n") );
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
);
847 list
= g_queue_peek_head_link(acc
->txn_queue
);
850 Transaction
*item
= list
->data
;
852 // no need to go higher than src txn date
853 if(item
->date
> src
->date
)
856 if( item
->paymode
== PAYMODE_INTXFER
)
858 if( src
->date
== item
->date
&&
859 src
->kacc
== item
->kxferacc
&&
860 src
->kxferacc
== item
->kacc
&&
861 ABS(src
->amount
) == ABS(item
->amount
) )
863 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
868 list
= g_list_next(list
);
872 DB( g_print(" not found...\n") );
878 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
881 void transaction_remove(Transaction
*ope
)
885 //controls accounts valid (archive scheduled maybe bad)
886 acc
= da_acc_get(ope
->kacc
);
887 if(acc
== NULL
) return;
889 account_balances_sub(ope
);
891 if( ope
->paymode
== PAYMODE_INTXFER
)
893 transaction_xfer_remove_child( ope
);
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
);
904 void transaction_changed(Transaction
*txn
)
911 acc
= da_acc_get(txn
->kacc
);
915 acc
->flags
|= AF_CHANGED
;
919 Transaction
*transaction_add(GtkWindow
*parent
, Transaction
*ope
)
924 DB( g_print("\n[transaction] transaction_add\n") );
926 //controls accounts valid (archive scheduled maybe bad)
927 acc
= da_acc_get(ope
->kacc
);
928 if(acc
== NULL
) return NULL
;
930 DB( g_print(" acc is '%s' %d\n", acc
->name
, acc
->key
) );
932 ope
->kcur
= acc
->kcur
;
934 if(ope
->paymode
== PAYMODE_INTXFER
)
936 acc
= da_acc_get(ope
->kxferacc
);
937 if(acc
== NULL
) return NULL
;
940 da_split_destroy(ope
->splits
);
942 ope
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
946 //allocate a new entry and copy from our edited structure
947 newope
= da_transaction_clone(ope
);
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);
955 /* cheque number is already stored in deftransaction_get */
956 /* todo:move this to transaction add
957 store a new cheque number into account ? */
959 if( (newope
->paymode
== PAYMODE_CHECK
) && (newope
->info
) && !(newope
->flags
& OF_INCOME
) )
963 /* get the active account and the corresponding cheque number */
964 acc
= da_acc_get( newope
->kacc
);
967 cheque
= atol(newope
->info
);
969 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
970 if( newope
->flags
& OF_CHEQ2
)
972 acc
->cheque2
= MAX(acc
->cheque2
, cheque
);
976 acc
->cheque1
= MAX(acc
->cheque1
, cheque
);
981 /* add normal transaction */
982 acc
= da_acc_get( newope
->kacc
);
985 acc
->flags
|= AF_ADDED
;
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
);
991 account_balances_add(newope
);
993 if(newope
->paymode
== PAYMODE_INTXFER
)
995 transaction_xfer_search_or_add_child(parent
, newope
, newope
->kxferacc
);
1003 gboolean
transaction_acc_move(Transaction
*txn
, guint32 okacc
, guint32 nkacc
)
1005 Account
*oacc
, *nacc
;
1007 DB( g_print("\n[transaction] acc_move\n") );
1009 if( okacc
== nkacc
)
1012 oacc
= da_acc_get(okacc
);
1013 nacc
= da_acc_get(nkacc
);
1016 account_balances_sub(txn
);
1017 if( g_queue_remove(oacc
->txn_queue
, txn
) )
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
);
1028 //ensure to keep txn into current account
1030 account_balances_add(txn
);
1037 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1040 static gboolean
misc_text_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1042 gboolean match
= FALSE
;
1047 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1048 if( searchtext
!= NULL
)
1052 if( g_strrstr(text
, searchtext
) != NULL
)
1054 DB( g_print("-- found case '%s'\n", searchtext
) );
1060 gchar
*word
= g_utf8_casefold(text
, -1);
1061 gchar
*needle
= g_utf8_casefold(searchtext
, -1);
1063 if( g_strrstr(word
, needle
) != NULL
)
1065 DB( g_print("-- found nocase '%s'\n", searchtext
) );
1076 static gboolean
misc_regex_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1078 gboolean match
= FALSE
;
1083 DB( g_print("-- match RE %s in %s\n", searchtext
, text
) );
1084 if( searchtext
!= NULL
)
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
) ); }
1093 static GList
*transaction_auto_assign_eval_txn(GList
*l_rul
, Transaction
*txn
)
1095 GList
*ret_list
= NULL
;
1099 list
= g_list_first(l_rul
);
1100 while (list
!= NULL
)
1102 Assign
*rul
= list
->data
;
1105 if(rul
->field
== 1) //payee
1107 Payee
*pay
= da_pay_get(txn
->kpay
);
1112 if( !(rul
->flags
& ASGF_REGEX
) )
1114 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1115 ret_list
= g_list_append(ret_list
, rul
);
1119 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1120 ret_list
= g_list_append(ret_list
, rul
);
1123 list
= g_list_next(list
);
1126 DB( g_print("- evaluated txn '%s'=> %d match\n", text
, g_list_length (ret_list
)) );
1132 static GList
*transaction_auto_assign_eval(GList
*l_rul
, gchar
*text
)
1134 GList
*ret_list
= NULL
;
1137 list
= g_list_first(l_rul
);
1138 while (list
!= NULL
)
1140 Assign
*rul
= list
->data
;
1142 if( rul
->field
== 0 ) //memo
1144 if( !(rul
->flags
& ASGF_REGEX
) )
1146 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1147 ret_list
= g_list_append(ret_list
, rul
);
1151 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1152 ret_list
= g_list_append(ret_list
, rul
);
1155 list
= g_list_next(list
);
1158 DB( g_print("- evaluated split '%s' => %d match\n", text
, g_list_length (ret_list
)) );
1164 guint
transaction_auto_assign(GList
*ope_list
, guint32 kacc
)
1168 GList
*l_match
, *l_tmp
;
1171 DB( g_print("\n[transaction] auto_assign\n") );
1173 l_rul
= g_hash_table_get_values(GLOBALS
->h_rul
);
1175 l_ope
= g_list_first(ope_list
);
1176 while (l_ope
!= NULL
)
1178 Transaction
*ope
= l_ope
->data
;
1179 gboolean changed
= FALSE
;
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" : "" ) );
1183 //#1215521: added kacc == 0
1184 if( (kacc
== ope
->kacc
|| kacc
== 0) )
1186 if( !(ope
->flags
& OF_SPLIT
) )
1188 l_match
= l_tmp
= transaction_auto_assign_eval_txn(l_rul
, ope
);
1189 while( l_tmp
!= NULL
)
1191 Assign
*rul
= l_tmp
->data
;
1193 if( (ope
->kpay
== 0 && (rul
->flags
& ASGF_DOPAY
)) || (rul
->flags
& ASGF_OVWPAY
) )
1195 if(ope
->kpay
!= rul
->kpay
) { changed
= TRUE
; }
1196 ope
->kpay
= rul
->kpay
;
1199 if( (ope
->kcat
== 0 && (rul
->flags
& ASGF_DOCAT
)) || (rul
->flags
& ASGF_OVWCAT
) )
1201 if(ope
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1202 ope
->kcat
= rul
->kcat
;
1205 if( (ope
->paymode
== 0 && (rul
->flags
& ASGF_DOMOD
)) || (rul
->flags
& ASGF_OVWMOD
) )
1207 //ugly hack - don't allow modify intxfer
1208 if(ope
->paymode
!= PAYMODE_INTXFER
&& rul
->paymode
!= PAYMODE_INTXFER
)
1210 if(ope
->paymode
!= rul
->paymode
) { changed
= TRUE
; }
1211 ope
->paymode
= rul
->paymode
;
1214 l_tmp
= g_list_next(l_tmp
);
1216 g_list_free(l_match
);
1220 guint i
, nbsplit
= da_splits_length(ope
->splits
);
1222 for(i
=0;i
<nbsplit
;i
++)
1224 Split
*split
= da_splits_get(ope
->splits
, i
);
1226 DB( g_print("- eval split '%s'\n", split
->memo
) );
1228 l_match
= l_tmp
= transaction_auto_assign_eval(l_rul
, split
->memo
);
1229 while( l_tmp
!= NULL
)
1231 Assign
*rul
= l_tmp
->data
;
1233 //#1501144: check if user wants to set category in rule
1234 if( (split
->kcat
== 0 || (rul
->flags
& ASGF_OVWCAT
)) && (rul
->flags
& ASGF_DOCAT
) )
1236 if(split
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1237 split
->kcat
= rul
->kcat
;
1239 l_tmp
= g_list_next(l_tmp
);
1241 g_list_free(l_match
);
1247 ope
->flags
|= OF_CHANGED
;
1252 l_ope
= g_list_next(l_ope
);
1262 static gboolean
transaction_similar_match(Transaction
*stxn
, Transaction
*dtxn
, guint32 daygap
)
1264 gboolean retval
= FALSE
;
1269 DB( g_print(" date: %d - %d = %d\n", stxn
->date
, dtxn
->date
, stxn
->date
- dtxn
->date
) );
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)
1284 void transaction_similar_unmark(Account
*acc
)
1288 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1289 while (lnk_txn
!= NULL
)
1291 Transaction
*stxn
= lnk_txn
->data
;
1292 stxn
->marker
= TXN_MARK_NONE
;
1293 lnk_txn
= g_list_previous(lnk_txn
);
1298 gint
transaction_similar_mark(Account
*acc
, guint32 daygap
)
1300 GList
*lnk_txn
, *list2
;
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") );
1308 DB( g_print("\n - account:'%s' gap:%d\n", acc
->name
, daygap
) );
1311 GTimer
*t
= g_timer_new();
1312 g_print(" - start parse\n");
1317 llast = g_list_last(old ope list);
1318 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
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));
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));
1332 transaction_similar_unmark(acc
);
1335 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1336 while (lnk_txn
!= NULL
)
1338 Transaction
*stxn
= lnk_txn
->data
;
1340 //if(stxn->date < minjulian)
1342 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn
->date
, stxn
->info
, stxn
->memo
, stxn
->amount
) );
1344 list2
= g_list_previous(lnk_txn
);
1345 while (list2
!= NULL
)
1347 Transaction
*dtxn
= list2
->data
;
1349 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn
->date
, dtxn
->info
, dtxn
->memo
, dtxn
->amount
) );
1351 if( (stxn
->date
- dtxn
->date
) > daygap
)
1353 DB( g_print(" break %d %d\n", (dtxn
->date
- daygap
) , (stxn
->date
- daygap
)) );
1357 if( dtxn
->marker
== TXN_MARK_NONE
)
1359 if( transaction_similar_match(stxn
, dtxn
, daygap
) )
1361 stxn
->marker
= TXN_MARK_DUPSRC
;
1362 dtxn
->marker
= TXN_MARK_DUPDST
;
1363 DB( g_print(" = dtxn marker=%d\n", dtxn
->marker
) );
1369 DB( g_print(" already marked %d\n", dtxn
->marker
) );
1373 list2
= g_list_previous(list2
);
1376 DB( g_print(" = stxn marker=%d\n", stxn
->marker
) );
1377 if( stxn
->marker
== TXN_MARK_DUPSRC
)
1380 lnk_txn
= g_list_previous(lnk_txn
);
1383 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t
, NULL
)) );
1384 DB( g_timer_destroy (t
) );
1386 DB( g_print(" - found: %d/%d dup\n", nbdup
, nball
) );
1392 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1393 /* = = experimental = = */
1394 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
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
1404 /*void future_transaction_test_account(Account *acc)
1406 GList *lnk_txn, *list2;
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
1418 DB( g_print("\n[transaction] check duplicate\n") );
1422 DB( g_print("\n - account:'%s'\n", acc->name) );
1424 GTimer *t = g_timer_new();
1425 g_print(" - start parse\n");
1428 llast = g_list_last(old ope list);
1429 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
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));
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));
1442 array = g_ptr_array_sized_new (25);
1444 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1445 while (lnk_txn != NULL)
1447 Transaction *stxn = lnk_txn->data;
1449 //if(stxn->date < minjulian)
1451 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1454 list2 = g_list_previous(lnk_txn);
1455 while (list2 != NULL)
1457 Transaction *dtxn = list2->data;
1460 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1463 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1465 if( transaction_similar_match(stxn, dtxn, gapday) )
1467 g_ptr_array_add (array, stxn);
1468 g_ptr_array_add (array, dtxn);
1470 DB( g_print(" + dst=1 src=1\n") );
1474 list2 = g_list_previous(list2);
1477 lnk_txn = g_list_previous(lnk_txn);
1480 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1481 DB( g_timer_destroy (t) );
1483 for(i=0;i<array->len;i++)
1485 Transaction *txn = g_ptr_array_index(array, i);
1489 g_ptr_array_free(array, TRUE);
1491 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
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)
1503 GList *lst_acc, *lnk_acc;
1505 DB( g_print("\ntransaction_test_notification\n") );
1507 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1508 lnk_acc = g_list_first(lst_acc);
1509 while (lnk_acc != NULL)
1511 Account *acc = lnk_acc->data;
1513 transaction_similar_mark(acc);
1515 lnk_acc = g_list_next(lnk_acc);
1517 g_list_free(lst_acc);