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 //#1827193 child can be null...
662 DB( g_print(" child %p\n", child
) );
664 transaction_xfer_change_to_child(ope
, child
);
666 transaction_xfer_create_child(ope
);
668 else //GTK_RESPONSE_CANCEL
670 ope
->paymode
= PAYMODE_NONE
;
677 g_list_free(matchlist
);
681 Transaction
*transaction_xfer_child_strong_get(Transaction
*src
)
686 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
688 dstacc
= da_acc_get(src
->kxferacc
);
689 if( !dstacc
|| src
->kxfer
<= 0 )
692 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src
->date
, src
->memo
, src
->amount
, src
->kacc
, src
->kxferacc
, src
->kxfer
) );
694 list
= g_queue_peek_tail_link(dstacc
->txn_queue
);
697 Transaction
*item
= list
->data
;
700 //if( item->paymode == PAYMODE_INTXFER
701 // && item->kacc == src->kxferacc
702 // && item->kxfer == src->kxfer )
703 if( item
->paymode
== PAYMODE_INTXFER
704 && item
->kxfer
== src
->kxfer
707 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item
->date
, item
->memo
, item
->amount
, item
->kacc
, item
->kxferacc
, src
->kxfer
) );
710 list
= g_list_previous(list
);
713 DB( g_print(" - not found...\n") );
720 void transaction_xfer_change_to_child(Transaction
*ope
, Transaction
*child
)
724 DB( g_print("\n[transaction] xfer_change_to_child\n") );
726 if(ope
->kcur
!= child
->kcur
)
729 ope
->flags
|= OF_CHANGED
;
730 child
->flags
|= OF_CHANGED
;
732 child
->paymode
= PAYMODE_INTXFER
;
734 ope
->kxferacc
= child
->kacc
;
735 child
->kxferacc
= ope
->kacc
;
737 /* update acc flags */
738 dstacc
= da_acc_get( child
->kacc
);
740 dstacc
->flags
|= AF_CHANGED
;
743 guint maxkey
= da_transaction_get_max_kxfer();
744 ope
->kxfer
= maxkey
+1;
745 child
->kxfer
= maxkey
+1;
750 void transaction_xfer_child_sync(Transaction
*s_txn
, Transaction
*child
)
754 DB( g_print("\n[transaction] xfer_child_sync\n") );
758 DB( g_print(" - no child found\n") );
762 DB( g_print(" - found do sync\n") );
764 /* update acc flags */
765 acc
= da_acc_get( child
->kacc
);
767 acc
->flags
|= AF_CHANGED
;
769 account_balances_sub (child
);
771 child
->date
= s_txn
->date
;
772 child
->amount
= -s_txn
->amount
;
773 child
->flags
= child
->flags
| OF_CHANGED
;
775 child
->flags
&= ~(OF_INCOME
);
776 if( child
->amount
> 0)
777 child
->flags
|= (OF_INCOME
);
778 child
->kpay
= s_txn
->kpay
;
779 child
->kcat
= s_txn
->kcat
;
782 child
->memo
= g_strdup(s_txn
->memo
);
785 child
->info
= g_strdup(s_txn
->info
);
787 account_balances_add (child
);
789 //#1252230 sync account also
790 //#1663789 idem after 5.1
791 //source changed: update child key (move of s_txn is done in external_edit)
792 if( s_txn
->kacc
!= child
->kxferacc
)
794 child
->kxferacc
= s_txn
->kacc
;
797 //dest changed: move child & update child key
798 if( s_txn
->kxferacc
!= child
->kacc
)
800 transaction_acc_move(child
, child
->kacc
, s_txn
->kxferacc
);
803 //synchronise tags since 5.1
805 child
->tags
= tags_clone (s_txn
->tags
);
810 void transaction_xfer_remove_child(Transaction
*src
)
814 DB( g_print("\n[transaction] xfer_remove_child\n") );
816 dst
= transaction_xfer_child_strong_get( src
);
819 Account
*acc
= da_acc_get(dst
->kacc
);
823 DB( g_print("deleting...") );
824 account_balances_sub(dst
);
825 g_queue_remove(acc
->txn_queue
, dst
);
826 //#1419304 we keep the deleted txn to a trash stack
827 //da_transaction_free (dst);
828 g_trash_stack_push(&GLOBALS
->txn_stk
, dst
);
831 acc
->flags
|= AF_CHANGED
;
840 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
841 Transaction
*transaction_old_get_child_transfer(Transaction
*src
)
846 DB( g_print("\n[transaction] get_child_transfer\n") );
848 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
849 acc
= da_acc_get(src
->kxferacc
);
853 list
= g_queue_peek_head_link(acc
->txn_queue
);
856 Transaction
*item
= list
->data
;
858 // no need to go higher than src txn date
859 if(item
->date
> src
->date
)
862 if( item
->paymode
== PAYMODE_INTXFER
)
864 if( src
->date
== item
->date
&&
865 src
->kacc
== item
->kxferacc
&&
866 src
->kxferacc
== item
->kacc
&&
867 ABS(src
->amount
) == ABS(item
->amount
) )
869 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
874 list
= g_list_next(list
);
878 DB( g_print(" not found...\n") );
884 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
887 void transaction_remove(Transaction
*ope
)
891 //controls accounts valid (archive scheduled maybe bad)
892 acc
= da_acc_get(ope
->kacc
);
893 if(acc
== NULL
) return;
895 account_balances_sub(ope
);
897 if( ope
->paymode
== PAYMODE_INTXFER
)
899 transaction_xfer_remove_child( ope
);
902 g_queue_remove(acc
->txn_queue
, ope
);
903 acc
->flags
|= AF_CHANGED
;
904 //#1419304 we keep the deleted txn to a trash stack
905 //da_transaction_free(entry);
906 g_trash_stack_push(&GLOBALS
->txn_stk
, ope
);
910 void transaction_changed(Transaction
*txn
)
917 acc
= da_acc_get(txn
->kacc
);
921 acc
->flags
|= AF_CHANGED
;
925 Transaction
*transaction_add(GtkWindow
*parent
, Transaction
*ope
)
930 DB( g_print("\n[transaction] transaction_add\n") );
932 //controls accounts valid (archive scheduled maybe bad)
933 acc
= da_acc_get(ope
->kacc
);
934 if(acc
== NULL
) return NULL
;
936 DB( g_print(" acc is '%s' %d\n", acc
->name
, acc
->key
) );
938 ope
->kcur
= acc
->kcur
;
940 if(ope
->paymode
== PAYMODE_INTXFER
)
942 acc
= da_acc_get(ope
->kxferacc
);
943 if(acc
== NULL
) return NULL
;
946 da_split_destroy(ope
->splits
);
948 ope
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
952 //allocate a new entry and copy from our edited structure
953 newope
= da_transaction_clone(ope
);
955 //init flag and keep remind status
956 // already done in deftransaction_get
957 //ope->flags |= (OF_ADDED);
958 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
959 //ope->flags &= (~OF_REMIND);
961 /* cheque number is already stored in deftransaction_get */
962 /* todo:move this to transaction add
963 store a new cheque number into account ? */
965 if( (newope
->paymode
== PAYMODE_CHECK
) && (newope
->info
) && !(newope
->flags
& OF_INCOME
) )
969 /* get the active account and the corresponding cheque number */
970 acc
= da_acc_get( newope
->kacc
);
973 cheque
= atol(newope
->info
);
975 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
976 if( newope
->flags
& OF_CHEQ2
)
978 acc
->cheque2
= MAX(acc
->cheque2
, cheque
);
982 acc
->cheque1
= MAX(acc
->cheque1
, cheque
);
987 /* add normal transaction */
988 acc
= da_acc_get( newope
->kacc
);
991 acc
->flags
|= AF_ADDED
;
993 DB( g_print(" + add normal %p to acc %d\n", newope
, acc
->key
) );
994 //da_transaction_append(newope);
995 da_transaction_insert_sorted(newope
);
997 account_balances_add(newope
);
999 if(newope
->paymode
== PAYMODE_INTXFER
)
1001 transaction_xfer_search_or_add_child(parent
, newope
, newope
->kxferacc
);
1004 GValue txn_value
= G_VALUE_INIT
;
1005 ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value
, newope
), NULL
);
1012 gboolean
transaction_acc_move(Transaction
*txn
, guint32 okacc
, guint32 nkacc
)
1014 Account
*oacc
, *nacc
;
1016 DB( g_print("\n[transaction] acc_move\n") );
1018 if( okacc
== nkacc
)
1021 oacc
= da_acc_get(okacc
);
1022 nacc
= da_acc_get(nkacc
);
1025 account_balances_sub(txn
);
1026 if( g_queue_remove(oacc
->txn_queue
, txn
) )
1028 g_queue_push_tail(nacc
->txn_queue
, txn
);
1029 txn
->kacc
= nacc
->key
;
1030 txn
->kcur
= nacc
->kcur
;
1031 nacc
->flags
|= AF_CHANGED
;
1032 account_balances_add(txn
);
1037 //ensure to keep txn into current account
1039 account_balances_add(txn
);
1046 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1049 static gboolean
misc_text_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1051 gboolean match
= FALSE
;
1056 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1057 if( searchtext
!= NULL
)
1061 if( g_strrstr(text
, searchtext
) != NULL
)
1063 DB( g_print("-- found case '%s'\n", searchtext
) );
1069 gchar
*word
= g_utf8_casefold(text
, -1);
1070 gchar
*needle
= g_utf8_casefold(searchtext
, -1);
1072 if( g_strrstr(word
, needle
) != NULL
)
1074 DB( g_print("-- found nocase '%s'\n", searchtext
) );
1085 static gboolean
misc_regex_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1087 gboolean match
= FALSE
;
1092 DB( g_print("-- match RE %s in %s\n", searchtext
, text
) );
1093 if( searchtext
!= NULL
)
1095 match
= g_regex_match_simple(searchtext
, text
, ((exact
== TRUE
)?0:G_REGEX_CASELESS
) | G_REGEX_OPTIMIZE
, G_REGEX_MATCH_NOTEMPTY
);
1096 if (match
== TRUE
) { DB( g_print("-- found pattern '%s'\n", searchtext
) ); }
1102 static GList
*transaction_auto_assign_eval_txn(GList
*l_rul
, Transaction
*txn
)
1104 GList
*ret_list
= NULL
;
1108 list
= g_list_first(l_rul
);
1109 while (list
!= NULL
)
1111 Assign
*rul
= list
->data
;
1114 if(rul
->field
== 1) //payee
1116 Payee
*pay
= da_pay_get(txn
->kpay
);
1121 if( !(rul
->flags
& ASGF_REGEX
) )
1123 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1124 ret_list
= g_list_append(ret_list
, rul
);
1128 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1129 ret_list
= g_list_append(ret_list
, rul
);
1132 list
= g_list_next(list
);
1135 DB( g_print("- evaluated txn '%s'=> %d match\n", text
, g_list_length (ret_list
)) );
1141 static GList
*transaction_auto_assign_eval(GList
*l_rul
, gchar
*text
)
1143 GList
*ret_list
= NULL
;
1146 list
= g_list_first(l_rul
);
1147 while (list
!= NULL
)
1149 Assign
*rul
= list
->data
;
1151 if( rul
->field
== 0 ) //memo
1153 if( !(rul
->flags
& ASGF_REGEX
) )
1155 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1156 ret_list
= g_list_append(ret_list
, rul
);
1160 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1161 ret_list
= g_list_append(ret_list
, rul
);
1164 list
= g_list_next(list
);
1167 DB( g_print("- evaluated split '%s' => %d match\n", text
, g_list_length (ret_list
)) );
1173 guint
transaction_auto_assign(GList
*ope_list
, guint32 kacc
)
1177 GList
*l_match
, *l_tmp
;
1180 DB( g_print("\n[transaction] auto_assign\n") );
1182 l_rul
= g_hash_table_get_values(GLOBALS
->h_rul
);
1184 l_ope
= g_list_first(ope_list
);
1185 while (l_ope
!= NULL
)
1187 Transaction
*ope
= l_ope
->data
;
1188 gboolean changed
= FALSE
;
1190 DB( g_print("\n- work on txn '%s' : acc=%d, pay=%d, cat=%d, %s\n", ope
->memo
, ope
->kacc
, ope
->kpay
, ope
->kcat
, (ope
->flags
& OF_SPLIT
) ? "is_split" : "" ) );
1192 //#1215521: added kacc == 0
1193 if( (kacc
== ope
->kacc
|| kacc
== 0) )
1195 if( !(ope
->flags
& OF_SPLIT
) )
1197 l_match
= l_tmp
= transaction_auto_assign_eval_txn(l_rul
, ope
);
1198 while( l_tmp
!= NULL
)
1200 Assign
*rul
= l_tmp
->data
;
1202 if( (ope
->kpay
== 0 && (rul
->flags
& ASGF_DOPAY
)) || (rul
->flags
& ASGF_OVWPAY
) )
1204 if(ope
->kpay
!= rul
->kpay
) { changed
= TRUE
; }
1205 ope
->kpay
= rul
->kpay
;
1208 if( (ope
->kcat
== 0 && (rul
->flags
& ASGF_DOCAT
)) || (rul
->flags
& ASGF_OVWCAT
) )
1210 if(ope
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1211 ope
->kcat
= rul
->kcat
;
1214 if( (ope
->paymode
== 0 && (rul
->flags
& ASGF_DOMOD
)) || (rul
->flags
& ASGF_OVWMOD
) )
1216 //ugly hack - don't allow modify intxfer
1217 if(ope
->paymode
!= PAYMODE_INTXFER
&& rul
->paymode
!= PAYMODE_INTXFER
)
1219 if(ope
->paymode
!= rul
->paymode
) { changed
= TRUE
; }
1220 ope
->paymode
= rul
->paymode
;
1223 l_tmp
= g_list_next(l_tmp
);
1225 g_list_free(l_match
);
1229 guint i
, nbsplit
= da_splits_length(ope
->splits
);
1231 for(i
=0;i
<nbsplit
;i
++)
1233 Split
*split
= da_splits_get(ope
->splits
, i
);
1235 DB( g_print("- eval split '%s'\n", split
->memo
) );
1237 l_match
= l_tmp
= transaction_auto_assign_eval(l_rul
, split
->memo
);
1238 while( l_tmp
!= NULL
)
1240 Assign
*rul
= l_tmp
->data
;
1242 //#1501144: check if user wants to set category in rule
1243 if( (split
->kcat
== 0 || (rul
->flags
& ASGF_OVWCAT
)) && (rul
->flags
& ASGF_DOCAT
) )
1245 if(split
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1246 split
->kcat
= rul
->kcat
;
1248 l_tmp
= g_list_next(l_tmp
);
1250 g_list_free(l_match
);
1256 ope
->flags
|= OF_CHANGED
;
1261 l_ope
= g_list_next(l_ope
);
1271 static gboolean
transaction_similar_match(Transaction
*stxn
, Transaction
*dtxn
, guint32 daygap
)
1273 gboolean retval
= FALSE
;
1278 DB( g_print(" date: %d - %d = %d\n", stxn
->date
, dtxn
->date
, stxn
->date
- dtxn
->date
) );
1280 if( stxn
->kcur
== dtxn
->kcur
1281 && stxn
->amount
== dtxn
->amount
1282 && ( (stxn
->date
- dtxn
->date
) <= daygap
)
1283 //todo: at import we also check payee, but maybe too strict here
1284 && (hb_string_compare(stxn
->memo
, dtxn
->memo
) == 0)
1293 void transaction_similar_unmark(Account
*acc
)
1297 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1298 while (lnk_txn
!= NULL
)
1300 Transaction
*stxn
= lnk_txn
->data
;
1301 stxn
->marker
= TXN_MARK_NONE
;
1302 lnk_txn
= g_list_previous(lnk_txn
);
1307 gint
transaction_similar_mark(Account
*acc
, guint32 daygap
)
1309 GList
*lnk_txn
, *list2
;
1313 //warning the list must be sorted by date then amount
1314 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1315 DB( g_print("\n[transaction] check duplicate\n") );
1317 DB( g_print("\n - account:'%s' gap:%d\n", acc
->name
, daygap
) );
1320 GTimer
*t
= g_timer_new();
1321 g_print(" - start parse\n");
1326 llast = g_list_last(old ope list);
1327 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1331 g_date_clear(&gd, 1);
1332 g_date_set_julian(&gd, ltxn->date);
1333 g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1335 minjulian = ltxn->date - (366*2);
1336 g_date_clear(&gd, 1);
1337 g_date_set_julian(&gd, minjulian);
1338 g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1341 transaction_similar_unmark(acc
);
1344 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1345 while (lnk_txn
!= NULL
)
1347 Transaction
*stxn
= lnk_txn
->data
;
1349 //if(stxn->date < minjulian)
1351 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn
->date
, stxn
->info
, stxn
->memo
, stxn
->amount
) );
1353 list2
= g_list_previous(lnk_txn
);
1354 while (list2
!= NULL
)
1356 Transaction
*dtxn
= list2
->data
;
1358 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn
->date
, dtxn
->info
, dtxn
->memo
, dtxn
->amount
) );
1360 if( (stxn
->date
- dtxn
->date
) > daygap
)
1362 DB( g_print(" break %d %d\n", (dtxn
->date
- daygap
) , (stxn
->date
- daygap
)) );
1366 if( dtxn
->marker
== TXN_MARK_NONE
)
1368 if( transaction_similar_match(stxn
, dtxn
, daygap
) )
1370 stxn
->marker
= TXN_MARK_DUPSRC
;
1371 dtxn
->marker
= TXN_MARK_DUPDST
;
1372 DB( g_print(" = dtxn marker=%d\n", dtxn
->marker
) );
1378 DB( g_print(" already marked %d\n", dtxn
->marker
) );
1382 list2
= g_list_previous(list2
);
1385 DB( g_print(" = stxn marker=%d\n", stxn
->marker
) );
1386 if( stxn
->marker
== TXN_MARK_DUPSRC
)
1389 lnk_txn
= g_list_previous(lnk_txn
);
1392 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t
, NULL
)) );
1393 DB( g_timer_destroy (t
) );
1395 DB( g_print(" - found: %d/%d dup\n", nbdup
, nball
) );
1401 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1402 /* = = experimental = = */
1403 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1407 probably add a structure hosted into a glist here
1408 with kind of problem: duplicate, child xfer, orphan xfer
1409 and collect all that with target txn
1413 /*void future_transaction_test_account(Account *acc)
1415 GList *lnk_txn, *list2;
1424 //warning the list must be sorted by date then amount
1425 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1427 DB( g_print("\n[transaction] check duplicate\n") );
1431 DB( g_print("\n - account:'%s'\n", acc->name) );
1433 GTimer *t = g_timer_new();
1434 g_print(" - start parse\n");
1437 llast = g_list_last(old ope list);
1438 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1442 g_date_clear(&gd, 1);
1443 g_date_set_julian(&gd, ltxn->date);
1444 g_print(" - last julian=%u %02d-%02d-%04d\n", ltxn->date, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1446 minjulian = ltxn->date - (366*2);
1447 g_date_clear(&gd, 1);
1448 g_date_set_julian(&gd, minjulian);
1449 g_print(" - min julian=%u %02d-%02d-%04d\n", minjulian, g_date_get_day (&gd), g_date_get_month (&gd), g_date_get_year(&gd));
1451 array = g_ptr_array_sized_new (25);
1453 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1454 while (lnk_txn != NULL)
1456 Transaction *stxn = lnk_txn->data;
1458 //if(stxn->date < minjulian)
1460 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1463 list2 = g_list_previous(lnk_txn);
1464 while (list2 != NULL)
1466 Transaction *dtxn = list2->data;
1469 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1472 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1474 if( transaction_similar_match(stxn, dtxn, gapday) )
1476 g_ptr_array_add (array, stxn);
1477 g_ptr_array_add (array, dtxn);
1479 DB( g_print(" + dst=1 src=1\n") );
1483 list2 = g_list_previous(list2);
1486 lnk_txn = g_list_previous(lnk_txn);
1489 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1490 DB( g_timer_destroy (t) );
1492 for(i=0;i<array->len;i++)
1494 Transaction *txn = g_ptr_array_index(array, i);
1498 g_ptr_array_free(array, TRUE);
1500 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
1508 //todo: add a limitation, no need to go through all txn
1509 // 1 year in th past, or abolute number ?
1510 gint future_transaction_test_notification(void)
1512 GList *lst_acc, *lnk_acc;
1514 DB( g_print("\ntransaction_test_notification\n") );
1516 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1517 lnk_acc = g_list_first(lst_acc);
1518 while (lnk_acc != NULL)
1520 Account *acc = lnk_acc->data;
1522 transaction_similar_mark(acc);
1524 lnk_acc = g_list_next(lnk_acc);
1526 g_list_free(lst_acc);