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 transaction_xfer_change_to_child(ope
, child
);
657 else //GTK_RESPONSE_CANCEL
659 ope
->paymode
= PAYMODE_NONE
;
666 g_list_free(matchlist
);
670 Transaction
*transaction_xfer_child_strong_get(Transaction
*src
)
675 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
677 dstacc
= da_acc_get(src
->kxferacc
);
678 if( !dstacc
|| src
->kxfer
<= 0 )
681 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src
->date
, src
->memo
, src
->amount
, src
->kacc
, src
->kxferacc
, src
->kxfer
) );
683 list
= g_queue_peek_tail_link(dstacc
->txn_queue
);
686 Transaction
*item
= list
->data
;
689 //if( item->paymode == PAYMODE_INTXFER
690 // && item->kacc == src->kxferacc
691 // && item->kxfer == src->kxfer )
692 if( item
->paymode
== PAYMODE_INTXFER
693 && item
->kxfer
== src
->kxfer
696 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item
->date
, item
->memo
, item
->amount
, item
->kacc
, item
->kxferacc
, src
->kxfer
) );
699 list
= g_list_previous(list
);
702 DB( g_print(" - not found...\n") );
709 void transaction_xfer_change_to_child(Transaction
*ope
, Transaction
*child
)
713 DB( g_print("\n[transaction] xfer_change_to_child\n") );
715 if(ope
->kcur
!= child
->kcur
)
718 ope
->flags
|= OF_CHANGED
;
719 child
->flags
|= OF_CHANGED
;
721 child
->paymode
= PAYMODE_INTXFER
;
723 ope
->kxferacc
= child
->kacc
;
724 child
->kxferacc
= ope
->kacc
;
726 /* update acc flags */
727 dstacc
= da_acc_get( child
->kacc
);
729 dstacc
->flags
|= AF_CHANGED
;
732 guint maxkey
= da_transaction_get_max_kxfer();
733 ope
->kxfer
= maxkey
+1;
734 child
->kxfer
= maxkey
+1;
739 void transaction_xfer_child_sync(Transaction
*s_txn
, Transaction
*child
)
743 DB( g_print("\n[transaction] xfer_child_sync\n") );
747 DB( g_print(" - no child found\n") );
751 DB( g_print(" - found do sync\n") );
753 /* update acc flags */
754 acc
= da_acc_get( child
->kacc
);
756 acc
->flags
|= AF_CHANGED
;
758 account_balances_sub (child
);
760 child
->date
= s_txn
->date
;
761 child
->amount
= -s_txn
->amount
;
762 child
->flags
= child
->flags
| OF_CHANGED
;
764 child
->flags
&= ~(OF_INCOME
);
765 if( child
->amount
> 0)
766 child
->flags
|= (OF_INCOME
);
767 child
->kpay
= s_txn
->kpay
;
768 child
->kcat
= s_txn
->kcat
;
771 child
->memo
= g_strdup(s_txn
->memo
);
774 child
->info
= g_strdup(s_txn
->info
);
776 account_balances_add (child
);
778 //#1252230 sync account also
779 //#1663789 idem after 5.1
780 //source changed: update child key (move of s_txn is done in external_edit)
781 if( s_txn
->kacc
!= child
->kxferacc
)
783 child
->kxferacc
= s_txn
->kacc
;
786 //dest changed: move child & update child key
787 if( s_txn
->kxferacc
!= child
->kacc
)
789 transaction_acc_move(child
, child
->kacc
, s_txn
->kxferacc
);
792 //synchronise tags since 5.1
794 child
->tags
= tags_clone (s_txn
->tags
);
799 void transaction_xfer_remove_child(Transaction
*src
)
803 DB( g_print("\n[transaction] xfer_remove_child\n") );
805 dst
= transaction_xfer_child_strong_get( src
);
808 Account
*acc
= da_acc_get(dst
->kacc
);
812 DB( g_print("deleting...") );
813 account_balances_sub(dst
);
814 g_queue_remove(acc
->txn_queue
, dst
);
815 //#1419304 we keep the deleted txn to a trash stack
816 //da_transaction_free (dst);
817 g_trash_stack_push(&GLOBALS
->txn_stk
, dst
);
820 acc
->flags
|= AF_CHANGED
;
829 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
830 Transaction
*transaction_old_get_child_transfer(Transaction
*src
)
835 DB( g_print("\n[transaction] get_child_transfer\n") );
837 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
838 acc
= da_acc_get(src
->kxferacc
);
842 list
= g_queue_peek_head_link(acc
->txn_queue
);
845 Transaction
*item
= list
->data
;
847 // no need to go higher than src txn date
848 if(item
->date
> src
->date
)
851 if( item
->paymode
== PAYMODE_INTXFER
)
853 if( src
->date
== item
->date
&&
854 src
->kacc
== item
->kxferacc
&&
855 src
->kxferacc
== item
->kacc
&&
856 ABS(src
->amount
) == ABS(item
->amount
) )
858 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
863 list
= g_list_next(list
);
867 DB( g_print(" not found...\n") );
873 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
876 void transaction_remove(Transaction
*ope
)
880 //controls accounts valid (archive scheduled maybe bad)
881 acc
= da_acc_get(ope
->kacc
);
882 if(acc
== NULL
) return;
884 account_balances_sub(ope
);
886 if( ope
->paymode
== PAYMODE_INTXFER
)
888 transaction_xfer_remove_child( ope
);
891 g_queue_remove(acc
->txn_queue
, ope
);
892 acc
->flags
|= AF_CHANGED
;
893 //#1419304 we keep the deleted txn to a trash stack
894 //da_transaction_free(entry);
895 g_trash_stack_push(&GLOBALS
->txn_stk
, ope
);
899 void transaction_changed(Transaction
*txn
)
906 acc
= da_acc_get(txn
->kacc
);
910 acc
->flags
|= AF_CHANGED
;
914 Transaction
*transaction_add(GtkWindow
*parent
, Transaction
*ope
)
919 DB( g_print("\n[transaction] transaction_add\n") );
921 //controls accounts valid (archive scheduled maybe bad)
922 acc
= da_acc_get(ope
->kacc
);
923 if(acc
== NULL
) return NULL
;
925 DB( g_print(" acc is '%s' %d\n", acc
->name
, acc
->key
) );
927 ope
->kcur
= acc
->kcur
;
929 if(ope
->paymode
== PAYMODE_INTXFER
)
931 acc
= da_acc_get(ope
->kxferacc
);
932 if(acc
== NULL
) return NULL
;
935 da_split_destroy(ope
->splits
);
937 ope
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
941 //allocate a new entry and copy from our edited structure
942 newope
= da_transaction_clone(ope
);
944 //init flag and keep remind status
945 // already done in deftransaction_get
946 //ope->flags |= (OF_ADDED);
947 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
948 //ope->flags &= (~OF_REMIND);
950 /* cheque number is already stored in deftransaction_get */
951 /* todo:move this to transaction add
952 store a new cheque number into account ? */
954 if( (newope
->paymode
== PAYMODE_CHECK
) && (newope
->info
) && !(newope
->flags
& OF_INCOME
) )
958 /* get the active account and the corresponding cheque number */
959 acc
= da_acc_get( newope
->kacc
);
962 cheque
= atol(newope
->info
);
964 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
965 if( newope
->flags
& OF_CHEQ2
)
967 acc
->cheque2
= MAX(acc
->cheque2
, cheque
);
971 acc
->cheque1
= MAX(acc
->cheque1
, cheque
);
976 /* add normal transaction */
977 acc
= da_acc_get( newope
->kacc
);
980 acc
->flags
|= AF_ADDED
;
982 DB( g_print(" + add normal %p to acc %d\n", newope
, acc
->key
) );
983 //da_transaction_append(newope);
984 da_transaction_insert_sorted(newope
);
986 account_balances_add(newope
);
988 if(newope
->paymode
== PAYMODE_INTXFER
)
990 transaction_xfer_search_or_add_child(parent
, newope
, newope
->kxferacc
);
998 gboolean
transaction_acc_move(Transaction
*txn
, guint32 okacc
, guint32 nkacc
)
1000 Account
*oacc
, *nacc
;
1002 DB( g_print("\n[transaction] acc_move\n") );
1004 if( okacc
== nkacc
)
1007 oacc
= da_acc_get(okacc
);
1008 nacc
= da_acc_get(nkacc
);
1011 account_balances_sub(txn
);
1012 if( g_queue_remove(oacc
->txn_queue
, txn
) )
1014 g_queue_push_tail(nacc
->txn_queue
, txn
);
1015 txn
->kacc
= nacc
->key
;
1016 txn
->kcur
= nacc
->kcur
;
1017 nacc
->flags
|= AF_CHANGED
;
1018 account_balances_add(txn
);
1023 //ensure to keep txn into current account
1025 account_balances_add(txn
);
1032 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1035 static gboolean
misc_text_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1037 gboolean match
= FALSE
;
1042 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1043 if( searchtext
!= NULL
)
1047 if( g_strrstr(text
, searchtext
) != NULL
)
1049 DB( g_print("-- found case '%s'\n", searchtext
) );
1055 gchar
*word
= g_utf8_casefold(text
, -1);
1056 gchar
*needle
= g_utf8_casefold(searchtext
, -1);
1058 if( g_strrstr(word
, needle
) != NULL
)
1060 DB( g_print("-- found nocase '%s'\n", searchtext
) );
1071 static gboolean
misc_regex_match(gchar
*text
, gchar
*searchtext
, gboolean exact
)
1073 gboolean match
= FALSE
;
1078 DB( g_print("-- match RE %s in %s\n", searchtext
, text
) );
1079 if( searchtext
!= NULL
)
1081 match
= g_regex_match_simple(searchtext
, text
, ((exact
== TRUE
)?0:G_REGEX_CASELESS
) | G_REGEX_OPTIMIZE
, G_REGEX_MATCH_NOTEMPTY
);
1082 if (match
== TRUE
) { DB( g_print("-- found pattern '%s'\n", searchtext
) ); }
1088 static GList
*transaction_auto_assign_eval_txn(GList
*l_rul
, Transaction
*txn
)
1090 GList
*ret_list
= NULL
;
1094 list
= g_list_first(l_rul
);
1095 while (list
!= NULL
)
1097 Assign
*rul
= list
->data
;
1100 if(rul
->field
== 1) //payee
1102 Payee
*pay
= da_pay_get(txn
->kpay
);
1107 if( !(rul
->flags
& ASGF_REGEX
) )
1109 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1110 ret_list
= g_list_append(ret_list
, rul
);
1114 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1115 ret_list
= g_list_append(ret_list
, rul
);
1118 list
= g_list_next(list
);
1121 DB( g_print("- evaluated txn '%s'=> %d match\n", text
, g_list_length (ret_list
)) );
1127 static GList
*transaction_auto_assign_eval(GList
*l_rul
, gchar
*text
)
1129 GList
*ret_list
= NULL
;
1132 list
= g_list_first(l_rul
);
1133 while (list
!= NULL
)
1135 Assign
*rul
= list
->data
;
1137 if( rul
->field
== 0 ) //memo
1139 if( !(rul
->flags
& ASGF_REGEX
) )
1141 if( misc_text_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1142 ret_list
= g_list_append(ret_list
, rul
);
1146 if( misc_regex_match(text
, rul
->text
, rul
->flags
& ASGF_EXACT
) )
1147 ret_list
= g_list_append(ret_list
, rul
);
1150 list
= g_list_next(list
);
1153 DB( g_print("- evaluated split '%s' => %d match\n", text
, g_list_length (ret_list
)) );
1159 guint
transaction_auto_assign(GList
*ope_list
, guint32 kacc
)
1163 GList
*l_match
, *l_tmp
;
1166 DB( g_print("\n[transaction] auto_assign\n") );
1168 l_rul
= g_hash_table_get_values(GLOBALS
->h_rul
);
1170 l_ope
= g_list_first(ope_list
);
1171 while (l_ope
!= NULL
)
1173 Transaction
*ope
= l_ope
->data
;
1174 gboolean changed
= FALSE
;
1176 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" : "" ) );
1178 //#1215521: added kacc == 0
1179 if( (kacc
== ope
->kacc
|| kacc
== 0) )
1181 if( !(ope
->flags
& OF_SPLIT
) )
1183 l_match
= l_tmp
= transaction_auto_assign_eval_txn(l_rul
, ope
);
1184 while( l_tmp
!= NULL
)
1186 Assign
*rul
= l_tmp
->data
;
1188 if( (ope
->kpay
== 0 && (rul
->flags
& ASGF_DOPAY
)) || (rul
->flags
& ASGF_OVWPAY
) )
1190 if(ope
->kpay
!= rul
->kpay
) { changed
= TRUE
; }
1191 ope
->kpay
= rul
->kpay
;
1194 if( (ope
->kcat
== 0 && (rul
->flags
& ASGF_DOCAT
)) || (rul
->flags
& ASGF_OVWCAT
) )
1196 if(ope
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1197 ope
->kcat
= rul
->kcat
;
1200 if( (ope
->paymode
== 0 && (rul
->flags
& ASGF_DOMOD
)) || (rul
->flags
& ASGF_OVWMOD
) )
1202 //ugly hack - don't allow modify intxfer
1203 if(ope
->paymode
!= PAYMODE_INTXFER
&& rul
->paymode
!= PAYMODE_INTXFER
)
1205 if(ope
->paymode
!= rul
->paymode
) { changed
= TRUE
; }
1206 ope
->paymode
= rul
->paymode
;
1209 l_tmp
= g_list_next(l_tmp
);
1211 g_list_free(l_match
);
1215 guint i
, nbsplit
= da_splits_length(ope
->splits
);
1217 for(i
=0;i
<nbsplit
;i
++)
1219 Split
*split
= da_splits_get(ope
->splits
, i
);
1221 DB( g_print("- eval split '%s'\n", split
->memo
) );
1223 l_match
= l_tmp
= transaction_auto_assign_eval(l_rul
, split
->memo
);
1224 while( l_tmp
!= NULL
)
1226 Assign
*rul
= l_tmp
->data
;
1228 //#1501144: check if user wants to set category in rule
1229 if( (split
->kcat
== 0 || (rul
->flags
& ASGF_OVWCAT
)) && (rul
->flags
& ASGF_DOCAT
) )
1231 if(split
->kcat
!= rul
->kcat
) { changed
= TRUE
; }
1232 split
->kcat
= rul
->kcat
;
1234 l_tmp
= g_list_next(l_tmp
);
1236 g_list_free(l_match
);
1242 ope
->flags
|= OF_CHANGED
;
1247 l_ope
= g_list_next(l_ope
);
1257 static gboolean
transaction_similar_match(Transaction
*stxn
, Transaction
*dtxn
, guint32 daygap
)
1259 gboolean retval
= FALSE
;
1264 DB( g_print(" date: %d - %d = %d\n", stxn
->date
, dtxn
->date
, stxn
->date
- dtxn
->date
) );
1266 if( stxn
->kcur
== dtxn
->kcur
1267 && stxn
->amount
== dtxn
->amount
1268 && ( (stxn
->date
- dtxn
->date
) <= daygap
)
1269 //todo: at import we also check payee, but maybe too strict here
1270 && (hb_string_compare(stxn
->memo
, dtxn
->memo
) == 0)
1279 void transaction_similar_unmark(Account
*acc
)
1283 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1284 while (lnk_txn
!= NULL
)
1286 Transaction
*stxn
= lnk_txn
->data
;
1287 stxn
->marker
= TXN_MARK_NONE
;
1288 lnk_txn
= g_list_previous(lnk_txn
);
1293 gint
transaction_similar_mark(Account
*acc
, guint32 daygap
)
1295 GList
*lnk_txn
, *list2
;
1299 //warning the list must be sorted by date then amount
1300 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1301 DB( g_print("\n[transaction] check duplicate\n") );
1303 DB( g_print("\n - account:'%s' gap:%d\n", acc
->name
, daygap
) );
1306 GTimer
*t
= g_timer_new();
1307 g_print(" - start parse\n");
1312 llast = g_list_last(old ope list);
1313 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1317 g_date_clear(&gd, 1);
1318 g_date_set_julian(&gd, ltxn->date);
1319 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));
1321 minjulian = ltxn->date - (366*2);
1322 g_date_clear(&gd, 1);
1323 g_date_set_julian(&gd, minjulian);
1324 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));
1327 transaction_similar_unmark(acc
);
1330 lnk_txn
= g_queue_peek_tail_link(acc
->txn_queue
);
1331 while (lnk_txn
!= NULL
)
1333 Transaction
*stxn
= lnk_txn
->data
;
1335 //if(stxn->date < minjulian)
1337 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn
->date
, stxn
->info
, stxn
->memo
, stxn
->amount
) );
1339 list2
= g_list_previous(lnk_txn
);
1340 while (list2
!= NULL
)
1342 Transaction
*dtxn
= list2
->data
;
1344 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn
->date
, dtxn
->info
, dtxn
->memo
, dtxn
->amount
) );
1346 if( (stxn
->date
- dtxn
->date
) > daygap
)
1348 DB( g_print(" break %d %d\n", (dtxn
->date
- daygap
) , (stxn
->date
- daygap
)) );
1352 if( dtxn
->marker
== TXN_MARK_NONE
)
1354 if( transaction_similar_match(stxn
, dtxn
, daygap
) )
1356 stxn
->marker
= TXN_MARK_DUPSRC
;
1357 dtxn
->marker
= TXN_MARK_DUPDST
;
1358 DB( g_print(" = dtxn marker=%d\n", dtxn
->marker
) );
1364 DB( g_print(" already marked %d\n", dtxn
->marker
) );
1368 list2
= g_list_previous(list2
);
1371 DB( g_print(" = stxn marker=%d\n", stxn
->marker
) );
1372 if( stxn
->marker
== TXN_MARK_DUPSRC
)
1375 lnk_txn
= g_list_previous(lnk_txn
);
1378 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t
, NULL
)) );
1379 DB( g_timer_destroy (t
) );
1381 DB( g_print(" - found: %d/%d dup\n", nbdup
, nball
) );
1387 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1388 /* = = experimental = = */
1389 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1393 probably add a structure hosted into a glist here
1394 with kind of problem: duplicate, child xfer, orphan xfer
1395 and collect all that with target txn
1399 /*void future_transaction_test_account(Account *acc)
1401 GList *lnk_txn, *list2;
1410 //warning the list must be sorted by date then amount
1411 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1413 DB( g_print("\n[transaction] check duplicate\n") );
1417 DB( g_print("\n - account:'%s'\n", acc->name) );
1419 GTimer *t = g_timer_new();
1420 g_print(" - start parse\n");
1423 llast = g_list_last(old ope list);
1424 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1428 g_date_clear(&gd, 1);
1429 g_date_set_julian(&gd, ltxn->date);
1430 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));
1432 minjulian = ltxn->date - (366*2);
1433 g_date_clear(&gd, 1);
1434 g_date_set_julian(&gd, minjulian);
1435 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));
1437 array = g_ptr_array_sized_new (25);
1439 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1440 while (lnk_txn != NULL)
1442 Transaction *stxn = lnk_txn->data;
1444 //if(stxn->date < minjulian)
1446 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1449 list2 = g_list_previous(lnk_txn);
1450 while (list2 != NULL)
1452 Transaction *dtxn = list2->data;
1455 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1458 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1460 if( transaction_similar_match(stxn, dtxn, gapday) )
1462 g_ptr_array_add (array, stxn);
1463 g_ptr_array_add (array, dtxn);
1465 DB( g_print(" + dst=1 src=1\n") );
1469 list2 = g_list_previous(list2);
1472 lnk_txn = g_list_previous(lnk_txn);
1475 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1476 DB( g_timer_destroy (t) );
1478 for(i=0;i<array->len;i++)
1480 Transaction *txn = g_ptr_array_index(array, i);
1484 g_ptr_array_free(array, TRUE);
1486 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
1494 //todo: add a limitation, no need to go through all txn
1495 // 1 year in th past, or abolute number ?
1496 gint future_transaction_test_notification(void)
1498 GList *lst_acc, *lnk_acc;
1500 DB( g_print("\ntransaction_test_notification\n") );
1502 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1503 lnk_acc = g_list_first(lst_acc);
1504 while (lnk_acc != NULL)
1506 Account *acc = lnk_acc->data;
1508 transaction_similar_mark(acc);
1510 lnk_acc = g_list_next(lnk_acc);
1512 g_list_free(lst_acc);