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"
29 /****************************************************************************/
31 /****************************************************************************/
40 /* our global datas */
41 extern struct HomeBank
*GLOBALS
;
42 extern struct Preferences
*PREFS
;
45 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
48 da_transaction_clean(Transaction
*item
)
52 if(item
->memo
!= NULL
)
57 if(item
->info
!= NULL
)
62 if(item
->tags
!= NULL
)
64 DB( g_print(" -> item->tags %p\n", item
->tags
) );
69 if(item
->splits
!= NULL
)
71 da_split_destroy(item
->splits
);
73 item
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
76 if(item
->same
!= NULL
)
78 g_list_free(item
->same
);
86 da_transaction_free(Transaction
*item
)
90 da_transaction_clean(item
);
97 da_transaction_malloc(void)
99 return rc_alloc(sizeof(Transaction
));
103 Transaction
*da_transaction_init_from_template(Transaction
*txn
, Archive
*arc
)
105 DB( g_print("da_transaction_init_from_template\n") );
108 txn
->amount
= arc
->amount
;
109 //#1258344 keep the current account if tpl is empty
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
);
121 //copy tags (with free previous here)
123 txn
->tags
= tags_clone(arc
->tags
);
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
134 Transaction
*da_transaction_set_default_template(Transaction
*txn
)
139 DB( g_print("da_transaction_set_default_template\n") );
141 acc
= da_acc_get(txn
->kacc
);
142 if(acc
!= NULL
&& acc
->karc
> 0)
144 arc
= da_archive_get(acc
->karc
);
147 DB( g_print(" - init with default template\n") );
148 da_transaction_init_from_template(txn
, arc
);
156 Transaction
*da_transaction_clone(Transaction
*src_item
)
158 Transaction
*new_item
= rc_dup(src_item
, sizeof(Transaction
));
160 DB( g_print("da_transaction_clone\n") );
164 //duplicate the string
165 new_item
->memo
= g_strdup(src_item
->memo
);
166 new_item
->info
= g_strdup(src_item
->info
);
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
);
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
182 da_transaction_new(void)
189 da_transaction_length(void)
191 GList
*lst_acc
, *lnk_acc
;
194 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
195 lnk_acc
= g_list_first(lst_acc
);
196 while (lnk_acc
!= NULL
)
198 Account
*acc
= lnk_acc
->data
;
200 count
+= g_queue_get_length (acc
->txn_queue
);
201 lnk_acc
= g_list_next(lnk_acc
);
203 g_list_free(lst_acc
);
208 static void da_transaction_queue_free_ghfunc(Transaction
*item
, gpointer data
)
210 da_transaction_free (item
);
214 void da_transaction_destroy(void)
218 lacc
= g_hash_table_get_values(GLOBALS
->h_acc
);
219 list
= g_list_first(lacc
);
222 Account
*acc
= list
->data
;
224 g_queue_foreach(acc
->txn_queue
, (GFunc
)da_transaction_queue_free_ghfunc
, NULL
);
225 list
= g_list_next(list
);
231 static gint
da_transaction_compare_datafunc(Transaction
*a
, Transaction
*b
, gpointer data
)
233 return ((gint
)a
->date
- b
->date
);
237 void da_transaction_queue_sort(GQueue
*queue
)
239 g_queue_sort(queue
, (GCompareDataFunc
)da_transaction_compare_datafunc
, NULL
);
243 static gint
da_transaction_compare_func(Transaction
*a
, Transaction
*b
)
245 return ((gint
)a
->date
- b
->date
);
249 GList
*da_transaction_sort(GList
*list
)
251 return( g_list_sort(list
, (GCompareFunc
)da_transaction_compare_func
));
255 gboolean
da_transaction_insert_memo(Transaction
*item
)
257 gboolean retval
= FALSE
;
259 if( item
->memo
!= NULL
)
261 //# 1673048 add filter on status and date obsolete
262 if( (PREFS
->txn_memoacp
== TRUE
) && (item
->date
>= (GLOBALS
->today
- PREFS
->txn_memoacp_days
)) )
264 if( g_hash_table_lookup(GLOBALS
->h_memo
, item
->memo
) == NULL
)
266 retval
= g_hash_table_insert(GLOBALS
->h_memo
, g_strdup(item
->memo
), NULL
);
274 gboolean
da_transaction_insert_sorted(Transaction
*newitem
)
279 acc
= da_acc_get(newitem
->kacc
);
283 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
284 while (lnk_txn
!= NULL
)
286 Transaction
*item
= lnk_txn
->data
;
288 if(item
->date
<= newitem
->date
)
291 lnk_txn
= g_list_previous(lnk_txn
);
294 // we're at insert point, insert after txn
295 g_queue_insert_after(acc
->txn_queue
, lnk_txn
, newitem
);
297 da_transaction_insert_memo(newitem
);
302 // nota: this is called only when loading xml file
303 gboolean
da_transaction_prepend(Transaction
*item
)
307 acc
= da_acc_get(item
->kacc
);
312 item
->kcur
= acc
->kcur
;
313 g_queue_push_tail(acc
->txn_queue
, item
);
314 da_transaction_insert_memo(item
);
319 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
322 da_transaction_get_max_kxfer(void)
324 GList
*lst_acc
, *lnk_acc
;
328 DB( g_print("da_transaction_get_max_kxfer\n") );
330 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
331 lnk_acc
= g_list_first(lst_acc
);
332 while (lnk_acc
!= NULL
)
334 Account
*acc
= lnk_acc
->data
;
336 list
= g_queue_peek_head_link(acc
->txn_queue
);
339 Transaction
*item
= list
->data
;
341 if( item
->paymode
== PAYMODE_INTXFER
)
343 max_key
= MAX(max_key
, item
->kxfer
);
345 list
= g_list_next(list
);
348 lnk_acc
= g_list_next(lnk_acc
);
350 g_list_free(lst_acc
);
352 DB( g_print(" max_key : %d \n", max_key
) );
358 static void da_transaction_goto_orphan(Transaction
*txn
)
360 const gchar
*oatn
= "orphaned transactions";
361 Account
*ori_acc
, *acc
;
364 DB( g_print("\n[transaction] goto orphan\n") );
366 g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn
->date
, txn
->memo
, txn
->amount
);
368 acc
= da_acc_get_by_name((gchar
*)oatn
);
371 acc
= da_acc_malloc();
372 acc
->name
= g_strdup(oatn
);
374 DB( g_print(" - created orphan acc %d\n", acc
->key
) );
377 ori_acc
= da_acc_get(txn
->kacc
);
380 found
= g_queue_remove(ori_acc
->txn_queue
, txn
);
381 DB( g_print(" - found in origin ? %d\n", found
) );
384 txn
->kacc
= acc
->key
;
385 da_transaction_insert_sorted (txn
);
386 DB( g_print("moved txn to %d\n", txn
->kacc
) );
392 void da_transaction_consistency(Transaction
*item
)
399 DB( g_print("\n[transaction] consistency\n") );
401 // ensure date is between range
402 item
->date
= CLAMP(item
->date
, HB_MINDATE
, HB_MAXDATE
);
404 // check account exists
405 acc
= da_acc_get(item
->kacc
);
408 g_warning("txn consistency: fixed invalid acc %d", item
->kacc
);
409 da_transaction_goto_orphan(item
);
410 GLOBALS
->changes_count
++;
413 // check category exists
414 cat
= da_cat_get(item
->kcat
);
417 g_warning("txn consistency: fixed invalid cat %d", item
->kcat
);
419 GLOBALS
->changes_count
++;
422 //#1340142 check split category
423 if( item
->splits
!= NULL
)
425 nbsplit
= da_splits_consistency(item
->splits
);
426 //# 1416624 empty category when split
427 if(nbsplit
> 0 && item
->kcat
> 0)
429 g_warning("txn consistency: fixed invalid cat on split txn");
431 GLOBALS
->changes_count
++;
435 // check payee exists
436 pay
= da_pay_get(item
->kpay
);
439 g_warning("txn consistency: fixed invalid pay %d", item
->kpay
);
441 GLOBALS
->changes_count
++;
444 // reset dst acc for non xfer transaction
445 if( item
->paymode
!= PAYMODE_INTXFER
)
451 // check dst account exists
452 if( item
->paymode
== PAYMODE_INTXFER
)
454 gint tak
= item
->kxferacc
;
456 item
->kxferacc
= ABS(tak
); //I crossed negative here one day
457 acc
= da_acc_get(item
->kxferacc
);
460 g_warning("txn consistency: fixed invalid dst_acc %d", item
->kxferacc
);
461 da_transaction_goto_orphan(item
);
463 item
->paymode
= PAYMODE_XFER
;
464 GLOBALS
->changes_count
++;
468 //#1628678 tags for internal xfer should be checked as well
469 //#1787826 intxfer should not have split
471 //#1295877 ensure income flag is correctly set
472 item
->flags
&= ~(OF_INCOME
);
473 if( item
->amount
> 0)
474 item
->flags
|= (OF_INCOME
);
476 //#1308745 ensure remind flag unset if reconciled
478 //if( item->flags & OF_VALID )
479 // item->flags &= ~(OF_REMIND);
484 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
485 /* new transfer functions */
487 static void transaction_xfer_create_child(Transaction
*ope
)
493 DB( g_print("\n[transaction] xfer_create_child\n") );
495 if( ope
->kxferacc
> 0 )
497 child
= da_transaction_clone(ope
);
499 ope
->flags
|= OF_CHANGED
;
500 child
->flags
|= OF_ADDED
;
502 child
->amount
= -child
->amount
;
503 child
->flags
^= (OF_INCOME
); // invert flag
505 if( child
->status
!= TXN_STATUS_REMIND
)
506 child
->status
= TXN_STATUS_NONE
;
507 //child->flags &= ~(OF_VALID); // delete reconcile state
510 child
->kacc
= child
->kxferacc
;
511 child
->kxferacc
= swap
;
513 /* update acc flags */
514 acc
= da_acc_get( child
->kacc
);
517 acc
->flags
|= AF_ADDED
;
520 guint maxkey
= da_transaction_get_max_kxfer();
522 DB( g_print(" + maxkey is %d\n", maxkey
) );
525 ope
->kxfer
= maxkey
+1;
526 child
->kxfer
= maxkey
+1;
528 DB( g_print(" + strong link to %d\n", ope
->kxfer
) );
531 DB( g_print(" + add transfer, %p to acc %d\n", child
, acc
->key
) );
533 da_transaction_insert_sorted(child
);
535 account_balances_add (child
);
537 GValue txn_value
= G_VALUE_INIT
;
538 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value
, child
), NULL
);
546 //todo: add strong control and extend to payee, maybe memo ?
547 static gboolean
transaction_xfer_child_might(Transaction
*stxn
, Transaction
*dtxn
, gint daygap
)
549 gboolean retval
= FALSE
;
551 //DB( g_print("\n[transaction] xfer_child_might\n") );
558 g_print(" %d %d %d %f %d\n",
559 stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer );
562 g_print(" %d %d %d %f %d\n",
563 dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer );
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
) &&
576 //g_print(" return %d\n", retval);
581 static GList
*transaction_xfer_child_might_list_get(Transaction
*ope
, guint32 kdstacc
)
583 GList
*lst_acc
, *lnk_acc
;
584 GList
*list
, *matchlist
= NULL
;
586 DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
588 DB( g_print(" - kdstacc:%d\n", kdstacc
) );
590 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
591 lnk_acc
= g_list_first(lst_acc
);
592 while (lnk_acc
!= NULL
)
594 Account
*acc
= lnk_acc
->data
;
596 if( !(acc
->flags
& AF_CLOSED
) && (acc
->key
!= ope
->kacc
) && ( (acc
->key
== kdstacc
) || kdstacc
== 0 ) )
598 list
= g_queue_peek_tail_link(acc
->txn_queue
);
601 Transaction
*item
= list
->data
;
603 // no need to go higher than src txn date
604 if(item
->date
< ope
->date
)
607 if( transaction_xfer_child_might(ope
, item
, 0) == TRUE
)
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
);
612 list
= g_list_previous(list
);
616 lnk_acc
= g_list_next(lnk_acc
);
618 g_list_free(lst_acc
);
624 void transaction_xfer_search_or_add_child(GtkWindow
*parent
, Transaction
*ope
, guint32 kdstacc
)
629 DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
631 matchlist
= transaction_xfer_child_might_list_get(ope
, kdstacc
);
632 count
= g_list_length(matchlist
);
634 DB( g_print(" - found %d might match, switching\n", count
) );
638 case 0: //we should create the child
639 transaction_xfer_create_child(ope
);
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
645 case 1: //transform the transaction to a child transfer
647 GList *list = g_list_first(matchlist);
648 transaction_xfer_change_to_child(ope, list->data);
653 default: //the user must choose himself
658 result
= ui_dialog_transaction_xfer_select_child(parent
, ope
, matchlist
, &child
);
659 if( result
== GTK_RESPONSE_ACCEPT
)
661 transaction_xfer_change_to_child(ope
, child
);
663 else //GTK_RESPONSE_CANCEL
665 ope
->paymode
= PAYMODE_NONE
;
672 g_list_free(matchlist
);
676 Transaction
*transaction_xfer_child_strong_get(Transaction
*src
)
681 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
683 dstacc
= da_acc_get(src
->kxferacc
);
684 if( !dstacc
|| src
->kxfer
<= 0 )
687 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src
->date
, src
->memo
, src
->amount
, src
->kacc
, src
->kxferacc
, src
->kxfer
) );
689 list
= g_queue_peek_tail_link(dstacc
->txn_queue
);
692 Transaction
*item
= list
->data
;
695 //if( item->paymode == PAYMODE_INTXFER
696 // && item->kacc == src->kxferacc
697 // && item->kxfer == src->kxfer )
698 if( item
->paymode
== PAYMODE_INTXFER
699 && item
->kxfer
== src
->kxfer
702 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item
->date
, item
->memo
, item
->amount
, item
->kacc
, item
->kxferacc
, src
->kxfer
) );
705 list
= g_list_previous(list
);
708 DB( g_print(" - not found...\n") );
715 void transaction_xfer_change_to_child(Transaction
*ope
, Transaction
*child
)
719 DB( g_print("\n[transaction] xfer_change_to_child\n") );
721 if(ope
->kcur
!= child
->kcur
)
724 ope
->flags
|= OF_CHANGED
;
725 child
->flags
|= OF_CHANGED
;
727 child
->paymode
= PAYMODE_INTXFER
;
729 ope
->kxferacc
= child
->kacc
;
730 child
->kxferacc
= ope
->kacc
;
732 /* update acc flags */
733 dstacc
= da_acc_get( child
->kacc
);
735 dstacc
->flags
|= AF_CHANGED
;
738 guint maxkey
= da_transaction_get_max_kxfer();
739 ope
->kxfer
= maxkey
+1;
740 child
->kxfer
= maxkey
+1;
745 void transaction_xfer_child_sync(Transaction
*s_txn
, Transaction
*child
)
749 DB( g_print("\n[transaction] xfer_child_sync\n") );
753 DB( g_print(" - no child found\n") );
757 DB( g_print(" - found do sync\n") );
759 /* update acc flags */
760 acc
= da_acc_get( child
->kacc
);
762 acc
->flags
|= AF_CHANGED
;
764 account_balances_sub (child
);
766 child
->date
= s_txn
->date
;
767 child
->amount
= -s_txn
->amount
;
768 child
->flags
= child
->flags
| OF_CHANGED
;
770 child
->flags
&= ~(OF_INCOME
);
771 if( child
->amount
> 0)
772 child
->flags
|= (OF_INCOME
);
773 child
->kpay
= s_txn
->kpay
;
774 child
->kcat
= s_txn
->kcat
;
777 child
->memo
= g_strdup(s_txn
->memo
);
780 child
->info
= g_strdup(s_txn
->info
);
782 account_balances_add (child
);
784 //#1252230 sync account also
785 //#1663789 idem after 5.1
786 //source changed: update child key (move of s_txn is done in external_edit)
787 if( s_txn
->kacc
!= child
->kxferacc
)
789 child
->kxferacc
= s_txn
->kacc
;
792 //dest changed: move child & update child key
793 if( s_txn
->kxferacc
!= child
->kacc
)
795 transaction_acc_move(child
, child
->kacc
, s_txn
->kxferacc
);
798 //synchronise tags since 5.1
800 child
->tags
= tags_clone (s_txn
->tags
);
805 void transaction_xfer_remove_child(Transaction
*src
)
809 DB( g_print("\n[transaction] xfer_remove_child\n") );
811 dst
= transaction_xfer_child_strong_get( src
);
814 Account
*acc
= da_acc_get(dst
->kacc
);
818 DB( g_print("deleting...") );
819 account_balances_sub(dst
);
820 g_queue_remove(acc
->txn_queue
, dst
);
821 //#1419304 we keep the deleted txn to a trash stack
822 //da_transaction_free (dst);
823 g_trash_stack_push(&GLOBALS
->txn_stk
, dst
);
826 acc
->flags
|= AF_CHANGED
;
835 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
836 Transaction
*transaction_old_get_child_transfer(Transaction
*src
)
841 DB( g_print("\n[transaction] get_child_transfer\n") );
843 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
844 acc
= da_acc_get(src
->kxferacc
);
848 list
= g_queue_peek_head_link(acc
->txn_queue
);
851 Transaction
*item
= list
->data
;
853 // no need to go higher than src txn date
854 if(item
->date
> src
->date
)
857 if( item
->paymode
== PAYMODE_INTXFER
)
859 if( src
->date
== item
->date
&&
860 src
->kacc
== item
->kxferacc
&&
861 src
->kxferacc
== item
->kacc
&&
862 ABS(src
->amount
) == ABS(item
->amount
) )
864 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
869 list
= g_list_next(list
);
873 DB( g_print(" not found...\n") );
879 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
882 void transaction_remove(Transaction
*ope
)
886 //controls accounts valid (archive scheduled maybe bad)
887 acc
= da_acc_get(ope
->kacc
);
888 if(acc
== NULL
) return;
890 account_balances_sub(ope
);
892 if( ope
->paymode
== PAYMODE_INTXFER
)
894 transaction_xfer_remove_child( ope
);
897 g_queue_remove(acc
->txn_queue
, ope
);
898 acc
->flags
|= AF_CHANGED
;
899 //#1419304 we keep the deleted txn to a trash stack
900 //da_transaction_free(entry);
901 g_trash_stack_push(&GLOBALS
->txn_stk
, ope
);
905 void transaction_changed(Transaction
*txn
)
912 acc
= da_acc_get(txn
->kacc
);
916 acc
->flags
|= AF_CHANGED
;
920 Transaction
*transaction_add(GtkWindow
*parent
, Transaction
*ope
)
925 DB( g_print("\n[transaction] transaction_add\n") );
927 //controls accounts valid (archive scheduled maybe bad)
928 acc
= da_acc_get(ope
->kacc
);
929 if(acc
== NULL
) return NULL
;
931 DB( g_print(" acc is '%s' %d\n", acc
->name
, acc
->key
) );
933 ope
->kcur
= acc
->kcur
;
935 if(ope
->paymode
== PAYMODE_INTXFER
)
937 acc
= da_acc_get(ope
->kxferacc
);
938 if(acc
== NULL
) return NULL
;
941 da_split_destroy(ope
->splits
);
943 ope
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
947 //allocate a new entry and copy from our edited structure
948 newope
= da_transaction_clone(ope
);
950 //init flag and keep remind status
951 // already done in deftransaction_get
952 //ope->flags |= (OF_ADDED);
953 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
954 //ope->flags &= (~OF_REMIND);
956 /* cheque number is already stored in deftransaction_get */
957 /* todo:move this to transaction add
958 store a new cheque number into account ? */
960 if( (newope
->paymode
== PAYMODE_CHECK
) && (newope
->info
) && !(newope
->flags
& OF_INCOME
) )
964 /* get the active account and the corresponding cheque number */
965 acc
= da_acc_get( newope
->kacc
);
968 cheque
= atol(newope
->info
);
970 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
971 if( newope
->flags
& OF_CHEQ2
)
973 acc
->cheque2
= MAX(acc
->cheque2
, cheque
);
977 acc
->cheque1
= MAX(acc
->cheque1
, cheque
);
982 /* add normal transaction */
983 acc
= da_acc_get( newope
->kacc
);
986 acc
->flags
|= AF_ADDED
;
988 DB( g_print(" + add normal %p to acc %d\n", newope
, acc
->key
) );
989 //da_transaction_append(newope);
990 da_transaction_insert_sorted(newope
);
992 account_balances_add(newope
);
994 if(newope
->paymode
== PAYMODE_INTXFER
)
996 transaction_xfer_search_or_add_child(parent
, newope
, newope
->kxferacc
);
999 GValue txn_value
= G_VALUE_INIT
;
1000 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value
, newope
), NULL
);
1007 gboolean
transaction_acc_move(Transaction
*txn
, guint32 okacc
, guint32 nkacc
)
1009 Account
*oacc
, *nacc
;
1011 DB( g_print("\n[transaction] acc_move\n") );
1013 if( okacc
== nkacc
)
1016 oacc
= da_acc_get(okacc
);
1017 nacc
= da_acc_get(nkacc
);
1020 account_balances_sub(txn
);
1021 if( g_queue_remove(oacc
->txn_queue
, txn
) )
1023 g_queue_push_tail(nacc
->txn_queue
, txn
);
1024 txn
->kacc
= nacc
->key
;
1025 txn
->kcur
= nacc
->kcur
;
1026 nacc
->flags
|= AF_CHANGED
;
1027 account_balances_add(txn
);
1032 //ensure to keep txn into current account
1034 account_balances_add(txn
);
1041 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1044 static gboolean
misc_text_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1046 gboolean match
= FALSE
;
1051 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1052 if( searchtext
!= NULL
)
1056 if( g_strrstr(text
, searchtext
) != NULL
)
1058 DB( g_print("-- found case '%s'\n", searchtext
) );
1064 gchar
*word
= g_utf8_casefold(text
, -1);
1065 gchar
*needle
= g_utf8_casefold(searchtext
, -1);
1067 if( g_strrstr(word
, needle
) != NULL
)
1069 DB( g_print("-- found nocase '%s'\n", searchtext
) );
1080 static gboolean
misc_regex_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1082 gboolean match
= FALSE
;
1087 DB( g_print("-- match RE %s in %s\n", searchtext
, text
) );
1088 if( searchtext
!= NULL
)
1090 match
= g_regex_match_simple(searchtext
, text
, ((exact
== TRUE
)?0:G_REGEX_CASELESS
) | G_REGEX_OPTIMIZE
, G_REGEX_MATCH_NOTEMPTY
);
1091 if (match
== TRUE
) { DB( g_print("-- found pattern '%s'\n", searchtext
) ); }
1097 static GList
*transaction_auto_assign_eval_txn(GList
*l_rul
, Transaction
*txn
)
1099 GList
*ret_list
= NULL
;
1103 list
= g_list_first(l_rul
);
1104 while (list
!= NULL
)
1106 Assign
*rul
= list
->data
;
1109 if(rul
->field
== 1) //payee
1111 Payee
*pay
= da_pay_get(txn
->kpay
);
1116 if( !(rul
->flags
& ASGF_REGEX
) )
1118 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1119 ret_list
= g_list_append(ret_list
, rul
);
1123 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1124 ret_list
= g_list_append(ret_list
, rul
);
1127 list
= g_list_next(list
);
1130 DB( g_print("- evaluated txn '%s'=> %d match\n", text
, g_list_length (ret_list
)) );
1136 static GList
*transaction_auto_assign_eval(GList
*l_rul
, gchar
*text
)
1138 GList
*ret_list
= NULL
;
1141 list
= g_list_first(l_rul
);
1142 while (list
!= NULL
)
1144 Assign
*rul
= list
->data
;
1146 if( rul
->field
== 0 ) //memo
1148 if( !(rul
->flags
& ASGF_REGEX
) )
1150 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1151 ret_list
= g_list_append(ret_list
, rul
);
1155 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1156 ret_list
= g_list_append(ret_list
, rul
);
1159 list
= g_list_next(list
);
1162 DB( g_print("- evaluated split '%s' => %d match\n", text
, g_list_length (ret_list
)) );
1168 guint
transaction_auto_assign(GList
*ope_list
, guint32 kacc
)
1172 GList
*l_match
, *l_tmp
;
1175 DB( g_print("\n[transaction] auto_assign\n") );
1177 l_rul
= g_hash_table_get_values(GLOBALS
->h_rul
);
1179 l_ope
= g_list_first(ope_list
);
1180 while (l_ope
!= NULL
)
1182 Transaction
*ope
= l_ope
->data
;
1183 gboolean changed
= FALSE
;
1185 DB( g_print("\n- work on txn '%s' : acc=%d, pay=%d, cat=%d, %s\n", ope
->memo
, ope
->kacc
, ope
->kpay
, ope
->kcat
, (ope
->flags
& OF_SPLIT
) ? "is_split" : "" ) );
1187 //#1215521: added kacc == 0
1188 if( (kacc
== ope
->kacc
|| kacc
== 0) )
1190 if( !(ope
->flags
& OF_SPLIT
) )
1192 l_match
= l_tmp
= transaction_auto_assign_eval_txn(l_rul
, ope
);
1193 while( l_tmp
!= NULL
)
1195 Assign
*rul
= l_tmp
->data
;
1197 if( (ope
->kpay
== 0 && (rul
->flags
& ASGF_DOPAY
)) || (rul
->flags
& ASGF_OVWPAY
) )
1199 if(ope
->kpay
!= rul
->kpay
) { changed
= TRUE
; }
1200 ope
->kpay
= rul
->kpay
;
1203 if( (ope
->kcat
== 0 && (rul
->flags
& ASGF_DOCAT
)) || (rul
->flags
& ASGF_OVWCAT
) )
1205 if(ope
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1206 ope
->kcat
= rul
->kcat
;
1209 if( (ope
->paymode
== 0 && (rul
->flags
& ASGF_DOMOD
)) || (rul
->flags
& ASGF_OVWMOD
) )
1211 //ugly hack - don't allow modify intxfer
1212 if(ope
->paymode
!= PAYMODE_INTXFER
&& rul
->paymode
!= PAYMODE_INTXFER
)
1214 if(ope
->paymode
!= rul
->paymode
) { changed
= TRUE
; }
1215 ope
->paymode
= rul
->paymode
;
1218 l_tmp
= g_list_next(l_tmp
);
1220 g_list_free(l_match
);
1224 guint i
, nbsplit
= da_splits_length(ope
->splits
);
1226 for(i
=0;i
<nbsplit
;i
++)
1228 Split
*split
= da_splits_get(ope
->splits
, i
);
1230 DB( g_print("- eval split '%s'\n", split
->memo
) );
1232 l_match
= l_tmp
= transaction_auto_assign_eval(l_rul
, split
->memo
);
1233 while( l_tmp
!= NULL
)
1235 Assign
*rul
= l_tmp
->data
;
1237 //#1501144: check if user wants to set category in rule
1238 if( (split
->kcat
== 0 || (rul
->flags
& ASGF_OVWCAT
)) && (rul
->flags
& ASGF_DOCAT
) )
1240 if(split
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1241 split
->kcat
= rul
->kcat
;
1243 l_tmp
= g_list_next(l_tmp
);
1245 g_list_free(l_match
);
1251 ope
->flags
|= OF_CHANGED
;
1256 l_ope
= g_list_next(l_ope
);
1266 static gboolean
transaction_similar_match(Transaction
*stxn
, Transaction
*dtxn
, guint32 daygap
)
1268 gboolean retval
= FALSE
;
1273 DB( g_print(" date: %d - %d = %d\n", stxn
->date
, dtxn
->date
, stxn
->date
- dtxn
->date
) );
1275 if( stxn
->kcur
== dtxn
->kcur
1276 && stxn
->amount
== dtxn
->amount
1277 && ( (stxn
->date
- dtxn
->date
) <= daygap
)
1278 //todo: at import we also check payee, but maybe too strict here
1279 && (hb_string_compare(stxn
->memo
, dtxn
->memo
) == 0)
1288 void transaction_similar_unmark(Account
*acc
)
1292 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1293 while (lnk_txn
!= NULL
)
1295 Transaction
*stxn
= lnk_txn
->data
;
1296 stxn
->marker
= TXN_MARK_NONE
;
1297 lnk_txn
= g_list_previous(lnk_txn
);
1302 gint
transaction_similar_mark(Account
*acc
, guint32 daygap
)
1304 GList
*lnk_txn
, *list2
;
1308 //warning the list must be sorted by date then amount
1309 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1310 DB( g_print("\n[transaction] check duplicate\n") );
1312 DB( g_print("\n - account:'%s' gap:%d\n", acc
->name
, daygap
) );
1315 GTimer
*t
= g_timer_new();
1316 g_print(" - start parse\n");
1321 llast = g_list_last(old ope list);
1322 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1326 g_date_clear(&gd, 1);
1327 g_date_set_julian(&gd, ltxn->date);
1328 g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1330 minjulian = ltxn->date - (366*2);
1331 g_date_clear(&gd, 1);
1332 g_date_set_julian(&gd, minjulian);
1333 g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1336 transaction_similar_unmark(acc
);
1339 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1340 while (lnk_txn
!= NULL
)
1342 Transaction
*stxn
= lnk_txn
->data
;
1344 //if(stxn->date < minjulian)
1346 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn
->date
, stxn
->info
, stxn
->memo
, stxn
->amount
) );
1348 list2
= g_list_previous(lnk_txn
);
1349 while (list2
!= NULL
)
1351 Transaction
*dtxn
= list2
->data
;
1353 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn
->date
, dtxn
->info
, dtxn
->memo
, dtxn
->amount
) );
1355 if( (stxn
->date
- dtxn
->date
) > daygap
)
1357 DB( g_print(" break %d %d\n", (dtxn
->date
- daygap
) , (stxn
->date
- daygap
)) );
1361 if( dtxn
->marker
== TXN_MARK_NONE
)
1363 if( transaction_similar_match(stxn
, dtxn
, daygap
) )
1365 stxn
->marker
= TXN_MARK_DUPSRC
;
1366 dtxn
->marker
= TXN_MARK_DUPDST
;
1367 DB( g_print(" = dtxn marker=%d\n", dtxn
->marker
) );
1373 DB( g_print(" already marked %d\n", dtxn
->marker
) );
1377 list2
= g_list_previous(list2
);
1380 DB( g_print(" = stxn marker=%d\n", stxn
->marker
) );
1381 if( stxn
->marker
== TXN_MARK_DUPSRC
)
1384 lnk_txn
= g_list_previous(lnk_txn
);
1387 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t
, NULL
)) );
1388 DB( g_timer_destroy (t
) );
1390 DB( g_print(" - found: %d/%d dup\n", nbdup
, nball
) );
1396 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1397 /* = = experimental = = */
1398 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1402 probably add a structure hosted into a glist here
1403 with kind of problem: duplicate, child xfer, orphan xfer
1404 and collect all that with target txn
1408 /*void future_transaction_test_account(Account *acc)
1410 GList *lnk_txn, *list2;
1419 //warning the list must be sorted by date then amount
1420 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1422 DB( g_print("\n[transaction] check duplicate\n") );
1426 DB( g_print("\n - account:'%s'\n", acc->name) );
1428 GTimer *t = g_timer_new();
1429 g_print(" - start parse\n");
1432 llast = g_list_last(old ope list);
1433 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1437 g_date_clear(&gd, 1);
1438 g_date_set_julian(&gd, ltxn->date);
1439 g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1441 minjulian = ltxn->date - (366*2);
1442 g_date_clear(&gd, 1);
1443 g_date_set_julian(&gd, minjulian);
1444 g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1446 array = g_ptr_array_sized_new (25);
1448 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1449 while (lnk_txn != NULL)
1451 Transaction *stxn = lnk_txn->data;
1453 //if(stxn->date < minjulian)
1455 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1458 list2 = g_list_previous(lnk_txn);
1459 while (list2 != NULL)
1461 Transaction *dtxn = list2->data;
1464 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1467 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1469 if( transaction_similar_match(stxn, dtxn, gapday) )
1471 g_ptr_array_add (array, stxn);
1472 g_ptr_array_add (array, dtxn);
1474 DB( g_print(" + dst=1 src=1\n") );
1478 list2 = g_list_previous(list2);
1481 lnk_txn = g_list_previous(lnk_txn);
1484 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1485 DB( g_timer_destroy (t) );
1487 for(i=0;i<array->len;i++)
1489 Transaction *txn = g_ptr_array_index(array, i);
1493 g_ptr_array_free(array, TRUE);
1495 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
1503 //todo: add a limitation, no need to go through all txn
1504 // 1 year in th past, or abolute number ?
1505 gint future_transaction_test_notification(void)
1507 GList *lst_acc, *lnk_acc;
1509 DB( g_print("\ntransaction_test_notification\n") );
1511 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1512 lnk_acc = g_list_first(lst_acc);
1513 while (lnk_acc != NULL)
1515 Account *acc = lnk_acc->data;
1517 transaction_similar_mark(acc);
1519 lnk_acc = g_list_next(lnk_acc);
1521 g_list_free(lst_acc);