1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2021 Maxime DOYEN
3 *
4 * This file is part of HomeBank.
5 *
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "homebank.h"
21
22 #include "hb-transaction.h"
23 #include "hb-tag.h"
24 #include "hb-split.h"
25
26 /****************************************************************************/
27 /* Debug macro */
28 /****************************************************************************/
29 #define MYDEBUG 0
30
31 #if MYDEBUG
32 #define DB(x) (x);
33 #else
34 #define DB(x);
35 #endif
36
37 /* our global datas */
38 extern struct HomeBank *GLOBALS;
39 extern struct Preferences *PREFS;
40
41
42 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
43
44 static void
da_transaction_clean(Transaction * item)45 da_transaction_clean(Transaction *item)
46 {
47 if(item != NULL)
48 {
49 if(item->memo != NULL)
50 {
51 g_free(item->memo);
52 item->memo = NULL;
53 }
54 if(item->info != NULL)
55 {
56 g_free(item->info);
57 item->info = NULL;
58 }
59 if(item->tags != NULL)
60 {
61 g_free(item->tags);
62 item->tags = NULL;
63 }
64
65 if(item->splits != NULL)
66 {
67 da_split_destroy(item->splits);
68 item->splits = NULL;
69 item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
70 }
71 }
72 }
73
74
75 void
da_transaction_free(Transaction * item)76 da_transaction_free(Transaction *item)
77 {
78 if(item != NULL)
79 {
80 da_transaction_clean(item);
81 g_free(item);
82 }
83 }
84
85
86 Transaction *
da_transaction_malloc(void)87 da_transaction_malloc(void)
88 {
89 return g_malloc0(sizeof(Transaction));
90 }
91
92
da_transaction_init(Transaction * txn,guint32 kacc)93 Transaction *da_transaction_init(Transaction *txn, guint32 kacc)
94 {
95 guint32 date;
96
97 DB( g_print("da_transaction_init\n") );
98
99 //#1860309 keep the date when init for add/inherit
100 date = txn->date;
101 da_transaction_clean(txn);
102 memset(txn, 0, sizeof(Transaction));
103 txn->date = date;
104
105 //fix: 318733 / 1335285
106 if( PREFS->heritdate == FALSE )
107 txn->date = GLOBALS->today;
108
109 txn->kacc = kacc;
110
111 da_transaction_set_default_template(txn);
112
113 return txn;
114 }
115
116
da_transaction_init_from_template(Transaction * txn,Archive * arc)117 Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc)
118 {
119 guint32 date;
120
121 DB( g_print("da_transaction_init_from_template\n") );
122
123 //#5.4.2 date must remains when we set a template, as template has no date
124 date = txn->date;
125 da_transaction_clean(txn);
126 txn->date = date;
127
128 txn->amount = arc->amount;
129 //#1258344 keep the current account if tpl is empty
130 if(arc->kacc)
131 txn->kacc = arc->kacc;
132 txn->paymode = arc->paymode;
133 txn->flags = arc->flags;
134 txn->status = arc->status;
135 txn->kpay = arc->kpay;
136 txn->kcat = arc->kcat;
137 txn->kxferacc = arc->kxferacc;
138 if(arc->memo != NULL)
139 txn->memo = g_strdup(arc->memo);
140 if(arc->info != NULL)
141 txn->info = g_strdup(arc->info);
142
143 txn->tags = tags_clone(arc->tags);
144
145 txn->splits = da_splits_clone(arc->splits);
146 if( da_splits_length (txn->splits) > 0 )
147 txn->flags |= OF_SPLIT; //Flag that Splits are active
148
149 return txn;
150 }
151
152
da_transaction_set_default_template(Transaction * txn)153 Transaction *da_transaction_set_default_template(Transaction *txn)
154 {
155 Account *acc;
156 Archive *arc;
157
158 DB( g_print("da_transaction_set_default_template\n") );
159
160 acc = da_acc_get(txn->kacc);
161 if(acc != NULL && acc->karc > 0)
162 {
163 arc = da_archive_get(acc->karc);
164 if( arc )
165 {
166 DB( g_print(" - init with default template\n") );
167 da_transaction_init_from_template(txn, arc);
168 }
169 }
170
171 return txn;
172 }
173
174
da_transaction_clone(Transaction * src_item)175 Transaction *da_transaction_clone(Transaction *src_item)
176 {
177 Transaction *new_item = g_memdup(src_item, sizeof(Transaction));
178
179 DB( g_print("da_transaction_clone\n") );
180
181 if(new_item)
182 {
183 //duplicate the string
184 new_item->memo = g_strdup(src_item->memo);
185 new_item->info = g_strdup(src_item->info);
186
187 //duplicate tags/splits
188 //no g_free here to avoid free the src tags (memdup copied the ptr)
189 new_item->tags = tags_clone(src_item->tags);
190
191 new_item->splits = da_splits_clone(src_item->splits);
192 if( da_splits_length (new_item->splits) > 0 )
193 new_item->flags |= OF_SPLIT; //Flag that Splits are active
194
195 }
196 return new_item;
197 }
198
199
200 GList *
da_transaction_new(void)201 da_transaction_new(void)
202 {
203 return NULL;
204 }
205
206
207 guint
da_transaction_length(void)208 da_transaction_length(void)
209 {
210 GList *lst_acc, *lnk_acc;
211 guint count = 0;
212
213 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
214 lnk_acc = g_list_first(lst_acc);
215 while (lnk_acc != NULL)
216 {
217 Account *acc = lnk_acc->data;
218
219 count += g_queue_get_length (acc->txn_queue);
220 lnk_acc = g_list_next(lnk_acc);
221 }
222 g_list_free(lst_acc);
223 return count;
224 }
225
226
da_transaction_queue_free_ghfunc(Transaction * item,gpointer data)227 static void da_transaction_queue_free_ghfunc(Transaction *item, gpointer data)
228 {
229 da_transaction_free (item);
230 }
231
232
da_transaction_destroy(void)233 void da_transaction_destroy(void)
234 {
235 GList *lacc, *list;
236
237 lacc = g_hash_table_get_values(GLOBALS->h_acc);
238 list = g_list_first(lacc);
239 while (list != NULL)
240 {
241 Account *acc = list->data;
242
243 g_queue_foreach(acc->txn_queue, (GFunc)da_transaction_queue_free_ghfunc, NULL);
244 //txn queue is freed into account
245 list = g_list_next(list);
246 }
247 g_list_free(lacc);
248 }
249
250
251 // used from register only
da_transaction_compare_datafunc(Transaction * a,Transaction * b,gpointer data)252 static gint da_transaction_compare_datafunc(Transaction *a, Transaction *b, gpointer data)
253 {
254 gint retval = (gint)a->date - b->date;
255
256 if(!retval) //#1749457
257 retval = a->pos - b->pos;
258
259 return retval;
260 }
261
262
da_transaction_queue_sort(GQueue * queue)263 void da_transaction_queue_sort(GQueue *queue)
264 {
265 g_queue_sort(queue, (GCompareDataFunc)da_transaction_compare_datafunc, NULL);
266 }
267
268
da_transaction_compare_func(Transaction * a,Transaction * b)269 static gint da_transaction_compare_func(Transaction *a, Transaction *b)
270 {
271 return ((gint)a->date - b->date);
272 }
273
274
da_transaction_sort(GList * list)275 GList *da_transaction_sort(GList *list)
276 {
277 return( g_list_sort(list, (GCompareFunc)da_transaction_compare_func));
278 }
279
280
da_transaction_insert_memo(Transaction * item)281 gboolean da_transaction_insert_memo(Transaction *item)
282 {
283 gboolean retval = FALSE;
284
285 if( item->memo != NULL )
286 {
287 //# 1673048 add filter on status and date obsolete
288 if( (PREFS->txn_memoacp == TRUE) && (item->date >= (GLOBALS->today - PREFS->txn_memoacp_days)) )
289 {
290 if( g_hash_table_lookup(GLOBALS->h_memo, item->memo) == NULL )
291 {
292 retval = g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->memo), NULL);
293 }
294 }
295 }
296 return retval;
297 }
298
299
da_transaction_insert_sorted(Transaction * newitem)300 gboolean da_transaction_insert_sorted(Transaction *newitem)
301 {
302 Account *acc;
303 GList *lnk_txn;
304
305 acc = da_acc_get(newitem->kacc);
306 if(!acc)
307 return FALSE;
308
309 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
310 while (lnk_txn != NULL)
311 {
312 Transaction *item = lnk_txn->data;
313
314 if(item->date <= newitem->date)
315 break;
316
317 lnk_txn = g_list_previous(lnk_txn);
318 }
319
320 // we're at insert point, insert after txn
321 g_queue_insert_after(acc->txn_queue, lnk_txn, newitem);
322
323 da_transaction_insert_memo(newitem);
324 return TRUE;
325 }
326
327
328 // nota: this is called only when loading xml file
da_transaction_prepend(Transaction * item)329 gboolean da_transaction_prepend(Transaction *item)
330 {
331 Account *acc;
332
333 acc = da_acc_get(item->kacc);
334 //#1661279
335 if(!acc)
336 return FALSE;
337
338 item->kcur = acc->kcur;
339 g_queue_push_tail(acc->txn_queue, item);
340 da_transaction_insert_memo(item);
341 return TRUE;
342 }
343
344
345 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
346
347 static guint32
da_transaction_get_max_kxfer(void)348 da_transaction_get_max_kxfer(void)
349 {
350 GList *lst_acc, *lnk_acc;
351 GList *list;
352 guint32 max_key = 0;
353
354 DB( g_print("da_transaction_get_max_kxfer\n") );
355
356 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
357 lnk_acc = g_list_first(lst_acc);
358 while (lnk_acc != NULL)
359 {
360 Account *acc = lnk_acc->data;
361
362 list = g_queue_peek_head_link(acc->txn_queue);
363 while (list != NULL)
364 {
365 Transaction *item = list->data;
366
367 if( item->flags & OF_INTXFER )
368 {
369 max_key = MAX(max_key, item->kxfer);
370 }
371 list = g_list_next(list);
372 }
373
374 lnk_acc = g_list_next(lnk_acc);
375 }
376 g_list_free(lst_acc);
377
378 DB( g_print(" max_key : %d \n", max_key) );
379
380 return max_key;
381 }
382
383
da_transaction_goto_orphan(Transaction * txn)384 static void da_transaction_goto_orphan(Transaction *txn)
385 {
386 const gchar *oatn = "orphaned transactions";
387 Account *ori_acc, *acc;
388 gboolean found;
389
390 DB( g_print("\n[transaction] goto orphan\n") );
391
392 g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn->date, txn->memo, txn->amount);
393
394 acc = da_acc_get_by_name((gchar *)oatn);
395 if(acc == NULL)
396 {
397 acc = da_acc_malloc();
398 acc->name = g_strdup(oatn);
399 da_acc_append(acc);
400 DB( g_print(" - created orphan acc %d\n", acc->key) );
401 }
402
403 ori_acc = da_acc_get(txn->kacc);
404 if( ori_acc )
405 {
406 found = g_queue_remove(ori_acc->txn_queue, txn);
407 DB( g_print(" - found in origin ? %d\n", found) );
408 if(found)
409 {
410 txn->kacc = acc->key;
411 da_transaction_insert_sorted (txn);
412 DB( g_print("moved txn to %d\n", txn->kacc) );
413 }
414 }
415 }
416
417
da_transaction_set_flag(Transaction * item)418 void da_transaction_set_flag(Transaction *item)
419 {
420 //DB( g_print("\n[transaction] set flag\n") );
421 item->flags &= ~(OF_INCOME);
422 if( item->amount > 0)
423 item->flags |= (OF_INCOME);
424 }
425
426
da_transaction_consistency(Transaction * item)427 void da_transaction_consistency(Transaction *item)
428 {
429 Account *acc;
430 Category *cat;
431 Payee *pay;
432 guint nbsplit;
433
434 DB( g_print("\n[transaction] consistency\n") );
435
436
437 DB( g_print(" %d %.2f %s\n", item->date, item->amount, item->memo) );
438
439 // ensure date is between range
440 item->date = CLAMP(item->date, HB_MINDATE, HB_MAXDATE);
441
442 // check account exists
443 acc = da_acc_get(item->kacc);
444 if(acc == NULL)
445 {
446 g_warning("txn consistency: fixed invalid acc %d", item->kacc);
447 da_transaction_goto_orphan(item);
448 GLOBALS->changes_count++;
449 }
450
451 // check category exists
452 cat = da_cat_get(item->kcat);
453 if(cat == NULL)
454 {
455 g_warning("txn consistency: fixed invalid cat %d", item->kcat);
456 item->kcat = 0;
457 GLOBALS->changes_count++;
458 }
459
460 //#1340142 check split category
461 if( item->splits != NULL )
462 {
463 nbsplit = da_splits_consistency(item->splits);
464 //# 1416624 empty category when split
465 if(nbsplit > 0 && item->kcat > 0)
466 {
467 g_warning("txn consistency: fixed invalid cat on split txn");
468 item->kcat = 0;
469 GLOBALS->changes_count++;
470 }
471 }
472
473 // check payee exists
474 pay = da_pay_get(item->kpay);
475 if(pay == NULL)
476 {
477 g_warning("txn consistency: fixed invalid pay %d", item->kpay);
478 item->kpay = 0;
479 GLOBALS->changes_count++;
480 }
481
482 // 5.3: fix split on intxfer
483 if( ((item->flags & OF_INTXFER) || (item->paymode == OLDPAYMODE_INTXFER)) && (item->splits != NULL) )
484 {
485 g_warning("txn consistency: fixed invalid split on xfer");
486 item->flags &= ~(OF_INTXFER);
487 item->paymode = PAYMODE_XFER;
488 item->kxfer = 0;
489 item->kxferacc = 0;
490 }
491
492 // reset dst acc for non xfer transaction
493 if( !((item->flags & OF_INTXFER) || (item->paymode == OLDPAYMODE_INTXFER)) )
494 {
495 if( (item->kxfer != 0) || (item->kxferacc != 0) )
496 {
497 g_warning("txn consistency: fixed invalid xfer");
498 item->kxfer = 0;
499 item->kxferacc = 0;
500 }
501 }
502
503 // intxfer: check dst account exists
504 if( (item->flags & OF_INTXFER) || (item->paymode == OLDPAYMODE_INTXFER) )
505 {
506 gint tak = item->kxferacc;
507
508 item->kxferacc = ABS(tak); //I crossed negative here one day
509 acc = da_acc_get(item->kxferacc);
510 if(acc == NULL)
511 {
512 g_warning("txn consistency: fixed invalid dst_acc %d", item->kxferacc);
513 da_transaction_goto_orphan(item);
514 item->kxfer = 0;
515 item->paymode = PAYMODE_XFER;
516 GLOBALS->changes_count++;
517 }
518 }
519
520 //#1628678 tags for internal xfer should be checked as well
521 //#1787826 intxfer should not have split
522
523 //#1295877 ensure income flag is correctly set
524 da_transaction_set_flag(item);
525
526 //#1308745 ensure remind flag unset if reconciled
527 //useless since 5.0
528 //if( item->flags & OF_VALID )
529 // item->flags &= ~(OF_REMIND);
530
531 }
532
533
534 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
535 /* new transfer functions */
536
transaction_xfer_create_child(Transaction * ope)537 static void transaction_xfer_create_child(Transaction *ope)
538 {
539 Transaction *child;
540 Account *acc;
541 guint32 swap;
542
543 DB( g_print("\n[transaction] xfer_create_child\n") );
544
545 if( ope->kxferacc > 0 )
546 {
547 child = da_transaction_clone(ope);
548
549 ope->flags |= (OF_CHANGED | OF_INTXFER);
550 child->flags |= (OF_ADDED | OF_INTXFER);
551
552 child->amount = -child->amount;
553 child->flags ^= (OF_INCOME); // invert flag
554 //#1268026 #1690555
555 if( (child->status == TXN_STATUS_CLEARED) || (child->status == TXN_STATUS_RECONCILED) )
556 child->status = TXN_STATUS_NONE;
557 //child->flags &= ~(OF_VALID); // delete reconcile state
558
559 swap = child->kacc;
560 child->kacc = child->kxferacc;
561 child->kxferacc = swap;
562
563 /* update acc flags */
564 acc = da_acc_get( child->kacc );
565 if(acc != NULL)
566 {
567 acc->flags |= AF_ADDED;
568
569 //strong link
570 guint maxkey = da_transaction_get_max_kxfer();
571
572 DB( g_print(" + maxkey is %d\n", maxkey) );
573
574
575 ope->kxfer = maxkey+1;
576 child->kxfer = maxkey+1;
577
578 DB( g_print(" + strong link to %d\n", ope->kxfer) );
579
580
581 DB( g_print(" + add transfer, %p to acc %d\n", child, acc->key) );
582
583 da_transaction_insert_sorted(child);
584
585 account_balances_add (child);
586
587 }
588 }
589
590 }
591
592
transaction_xfer_child_match_rate(Transaction * stxn,Transaction * ptxn)593 static gint transaction_xfer_child_match_rate(Transaction *stxn, Transaction *ptxn)
594 {
595 gint rate = 0;
596
597 DB( g_print("\n[transaction] xfer_child_match_rate\n") );
598
599 //default rate is based on exact match field: cur+amount
600 rate = 2;
601 if( stxn->kpay == ptxn->kpay)
602 rate++;
603 if( stxn->kcat == ptxn->kcat)
604 rate++;
605 if( hb_string_compare(stxn->memo, ptxn->memo) == 0 )
606 rate++;
607 //TODO: maybe add info & tag here
608
609 rate = (rate *100) / 5;
610
611 DB( g_print(" return %d\n", rate) );
612
613 return rate;
614 }
615
616
617 //todo: add strong control and extend to payee, maybe memo ?
618 // #1708974 enable different date
transaction_xfer_child_might(Transaction * stxn,Transaction * dtxn,guint32 daygap)619 static gboolean transaction_xfer_child_might(Transaction *stxn, Transaction *dtxn, guint32 daygap)
620 {
621 gboolean retval = FALSE;
622
623 DB( g_print("\n[transaction] xfer_child_might\n") );
624
625 if(stxn == dtxn)
626 return FALSE;
627
628 DB( g_print(" src: %d %d %d %f %d\n", stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer ) );
629 DB( g_print(" dst: %d %d %d %f %d\n", dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer ) );
630
631 if( stxn->kcur == dtxn->kcur &&
632 //stxn->date == dtxn->date &&
633 //# 1708974 enable different date
634 (dtxn->date <= (stxn->date + daygap)) &&
635 (dtxn->date >= (stxn->date - daygap)) &&
636 //v5.1 make no sense: stxn->kxferacc == dtxn->kacc &&
637 stxn->kacc != dtxn->kacc &&
638 ABS(stxn->amount) == ABS(dtxn->amount) &&
639 dtxn->kxfer == 0)
640 {
641 //TODO: we could evaluate here a match rate based on several fields
642 retval = TRUE;
643 }
644
645 DB( g_print(" return %d\n", retval) );
646 return retval;
647 }
648
649
transaction_xfer_child_might_list_get(Transaction * ope,guint32 kdstacc)650 static GList *transaction_xfer_child_might_list_get(Transaction *ope, guint32 kdstacc)
651 {
652 GList *lst_acc, *lnk_acc;
653 GList *list, *matchlist = NULL;
654
655 DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
656
657 DB( g_print(" - kdstacc:%d\n", kdstacc) );
658
659 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
660 lnk_acc = g_list_first(lst_acc);
661 while (lnk_acc != NULL)
662 {
663 Account *acc = lnk_acc->data;
664
665 if( !(acc->flags & AF_CLOSED) && (acc->key != ope->kacc) && ( (acc->key == kdstacc) || kdstacc == 0 ) )
666 {
667 list = g_queue_peek_tail_link(acc->txn_queue);
668 while (list != NULL)
669 {
670 Transaction *item = list->data;
671
672 // no need to go higher than src txn date - daygap
673 if(item->date < (ope->date - PREFS->txn_xfer_daygap))
674 break;
675
676 if( transaction_xfer_child_might(ope, item, PREFS->txn_xfer_daygap) == TRUE )
677 {
678 item->matchrate = transaction_xfer_child_match_rate(ope, item);
679 DB( g_print(" - match %3d: %d %s %f %d=>%d\n", item->matchrate, item->date, item->memo, item->amount, item->kacc, item->kxferacc) );
680 matchlist = g_list_append(matchlist, item);
681 }
682 list = g_list_previous(list);
683 }
684 }
685
686 lnk_acc = g_list_next(lnk_acc);
687 }
688 g_list_free(lst_acc);
689
690 return matchlist;
691 }
692
693
transaction_xfer_search_or_add_child(GtkWindow * parent,Transaction * ope,guint32 kdstacc)694 void transaction_xfer_search_or_add_child(GtkWindow *parent, Transaction *ope, guint32 kdstacc)
695 {
696 GList *matchlist;
697 gint count;
698
699 DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
700
701 matchlist = transaction_xfer_child_might_list_get(ope, kdstacc);
702 count = g_list_length(matchlist);
703
704 DB( g_print(" - found %d might match, switching\n", count) );
705
706 switch(count)
707 {
708 case 0: //we should create the child
709 transaction_xfer_create_child(ope);
710 break;
711
712 //todo: maybe with just 1 match the user must choose ?
713 //#942346: bad idea so to no let the user confirm, so let him confirm
714 /*
715 case 1: //transform the transaction to a child transfer
716 {
717 GList *list = g_list_first(matchlist);
718 transaction_xfer_change_to_child(ope, list->data);
719 break;
720 }
721 */
722
723 default: //the user must choose himself
724 {
725 gint result;
726 Transaction *child;
727
728 result = ui_dialog_transaction_xfer_select_child(parent, ope, matchlist, &child);
729 if( result == GTK_RESPONSE_ACCEPT )
730 {
731 //#1827193 child can be null...
732 DB( g_print(" child %p\n", child) );
733 if( child != NULL )
734 transaction_xfer_change_to_child(ope, child);
735 else
736 transaction_xfer_create_child(ope);
737 }
738 else //GTK_RESPONSE_CANCEL
739 {
740 ope->paymode = PAYMODE_NONE;
741 ope->kxfer = 0;
742 ope->kxferacc = 0;
743 //fixed 5.5 when cancel xfer remains
744 ope->flags &= ~(OF_INTXFER);
745 da_transaction_set_flag(ope);
746 }
747 }
748 }
749
750 g_list_free(matchlist);
751 }
752
753
transaction_xfer_child_strong_get(Transaction * src)754 Transaction *transaction_xfer_child_strong_get(Transaction *src)
755 {
756 Account *dstacc;
757 GList *list;
758
759 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
760
761 dstacc = da_acc_get(src->kxferacc);
762 if( !dstacc || src->kxfer <= 0 )
763 return NULL;
764
765 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) );
766
767 list = g_queue_peek_tail_link(dstacc->txn_queue);
768 while (list != NULL)
769 {
770 Transaction *item = list->data;
771
772 //#1252230
773 //if( item->paymode == PAYMODE_INTXFER
774 // && item->kacc == src->kxferacc
775 if( (item->flags & OF_INTXFER)
776 && (item->kxfer == src->kxfer)
777 && (item != src) )
778 {
779 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item->date, item->memo, item->amount, item->kacc, item->kxferacc, src->kxfer) );
780 return item;
781 }
782 list = g_list_previous(list);
783 }
784
785 DB( g_print(" - not found...\n") );
786 return NULL;
787 }
788
789
790
791 //TODO: should be static but used in hb_import
transaction_xfer_change_to_child(Transaction * ope,Transaction * child)792 void transaction_xfer_change_to_child(Transaction *ope, Transaction *child)
793 {
794 Account *dstacc;
795
796 DB( g_print("\n[transaction] xfer_change_to_child\n") );
797
798 if(ope->kcur != child->kcur)
799 return;
800
801 ope->flags |= (OF_CHANGED | OF_INTXFER);
802 child->flags |= (OF_ADDED | OF_INTXFER);
803
804 ope->kxferacc = child->kacc;
805 child->kxferacc = ope->kacc;
806
807 /* update acc flags */
808 dstacc = da_acc_get( child->kacc);
809 if(dstacc != NULL)
810 dstacc->flags |= AF_CHANGED;
811
812 //strong link
813 guint maxkey = da_transaction_get_max_kxfer();
814 ope->kxfer = maxkey+1;
815 child->kxfer = maxkey+1;
816
817 }
818
819
transaction_xfer_child_sync(Transaction * s_txn,Transaction * child)820 void transaction_xfer_child_sync(Transaction *s_txn, Transaction *child)
821 {
822 Account *acc;
823
824 DB( g_print("\n[transaction] xfer_child_sync\n") );
825
826 if( child == NULL )
827 {
828 DB( g_print(" - no child found\n") );
829 return;
830 }
831
832 DB( g_print(" - found do sync\n") );
833
834 /* update acc flags */
835 acc = da_acc_get( child->kacc);
836 if(acc != NULL)
837 acc->flags |= AF_CHANGED;
838
839 account_balances_sub (child);
840
841 //# 1708974 enable different date
842 //child->date = s_txn->date;
843 child->amount = -s_txn->amount;
844 child->flags = child->flags | OF_CHANGED;
845 //#1295877
846 child->flags &= ~(OF_INCOME);
847 if( child->amount > 0)
848 child->flags |= (OF_INCOME);
849 child->kpay = s_txn->kpay;
850 child->kcat = s_txn->kcat;
851 if(child->memo)
852 g_free(child->memo);
853 child->memo = g_strdup(s_txn->memo);
854 if(child->info)
855 g_free(child->info);
856 child->info = g_strdup(s_txn->info);
857
858 account_balances_add (child);
859
860 //#1252230 sync account also
861 //#1663789 idem after 5.1
862 //source changed: update child key (move of s_txn is done in external_edit)
863 if( s_txn->kacc != child->kxferacc )
864 {
865 child->kxferacc = s_txn->kacc;
866 }
867
868 //dest changed: move child & update child key
869 if( s_txn->kxferacc != child->kacc )
870 {
871 transaction_acc_move(child, child->kacc, s_txn->kxferacc);
872 }
873
874 //synchronise tags since 5.1
875 g_free(child->tags);
876 child->tags = tags_clone (s_txn->tags);
877
878 }
879
880
transaction_xfer_remove_child(Transaction * src)881 void transaction_xfer_remove_child(Transaction *src)
882 {
883 Transaction *dst;
884
885 DB( g_print("\n[transaction] xfer_remove_child\n") );
886
887 dst = transaction_xfer_child_strong_get( src );
888 if( dst != NULL )
889 {
890 Account *acc = da_acc_get(dst->kacc);
891
892 if( acc != NULL )
893 {
894 DB( g_print("deleting...") );
895 account_balances_sub(dst);
896 g_queue_remove(acc->txn_queue, dst);
897 //#1419304 we keep the deleted txn to a trash stack
898 //da_transaction_free (dst);
899 //g_trash_stack_push(&GLOBALS->txn_stk, dst);
900 GLOBALS->deltxn_list = g_slist_prepend(GLOBALS->deltxn_list, dst);
901
902 //#1691992
903 acc->flags |= AF_CHANGED;
904 }
905 }
906
907 src->kxfer = 0;
908 src->kxferacc = 0;
909 }
910
911
912 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
transaction_old_get_child_transfer(Transaction * src)913 Transaction *transaction_old_get_child_transfer(Transaction *src)
914 {
915 Account *acc;
916 GList *list;
917
918 DB( g_print("\n[transaction] get_child_transfer\n") );
919
920 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
921 acc = da_acc_get(src->kxferacc);
922
923 if( acc != NULL )
924 {
925 list = g_queue_peek_head_link(acc->txn_queue);
926 while (list != NULL)
927 {
928 Transaction *item = list->data;
929
930 // no need to go higher than src txn date
931 if(item->date > src->date)
932 break;
933
934 // keep this one for backward compatibility
935 if( item->paymode == OLDPAYMODE_INTXFER)
936 {
937 if( src->date == item->date &&
938 src->kacc == item->kxferacc &&
939 src->kxferacc == item->kacc &&
940 ABS(src->amount) == ABS(item->amount) )
941 {
942 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
943
944 return item;
945 }
946 }
947 list = g_list_next(list);
948 }
949 }
950
951 DB( g_print(" not found...\n") );
952
953 return NULL;
954 }
955
956
957 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
958
959
transaction_get_status_string(Transaction * txn)960 gchar *transaction_get_status_string(Transaction *txn)
961 {
962 gchar *retval = "";
963
964 if(txn->status == TXN_STATUS_CLEARED)
965 retval = "c";
966 else
967 if(txn->status == TXN_STATUS_RECONCILED)
968 retval = "R";
969 //else
970 //if(txn->status == TXN_STATUS_REMIND)
971 // retval = "?";
972 else
973 if(txn->status == TXN_STATUS_VOID)
974 retval = "v";
975
976 return retval;
977 }
978
979
transaction_is_balanceable(Transaction * ope)980 gboolean transaction_is_balanceable(Transaction *ope)
981 {
982 gboolean retval = TRUE;
983
984 if( (ope->status == TXN_STATUS_VOID) )
985 retval = FALSE;
986 else if( (ope->status == TXN_STATUS_REMIND) && (PREFS->includeremind == FALSE) )
987 retval = FALSE;
988
989 return retval;
990 }
991
992
transaction_remove(Transaction * ope)993 void transaction_remove(Transaction *ope)
994 {
995 Account *acc;
996
997 //controls accounts valid (archive scheduled maybe bad)
998 acc = da_acc_get(ope->kacc);
999 if(acc == NULL) return;
1000
1001 account_balances_sub(ope);
1002
1003 if( ope->flags & OF_INTXFER )
1004 {
1005 transaction_xfer_remove_child( ope );
1006 }
1007
1008 g_queue_remove(acc->txn_queue, ope);
1009 acc->flags |= AF_CHANGED;
1010 //#1419304 we keep the deleted txn to a trash stack
1011 //da_transaction_free(entry);
1012 //g_trash_stack_push(&GLOBALS->txn_stk, ope);
1013 GLOBALS->deltxn_list = g_slist_prepend(GLOBALS->deltxn_list, ope);
1014 }
1015
1016
transaction_changed(Transaction * txn,gboolean saverecondate)1017 void transaction_changed(Transaction *txn, gboolean saverecondate)
1018 {
1019 Account *acc;
1020
1021 if( txn == NULL )
1022 return;
1023
1024 acc = da_acc_get(txn->kacc);
1025 if(acc == NULL)
1026 return;
1027
1028 acc->flags |= AF_CHANGED;
1029
1030 //#1581863 store reconciled date
1031 if( saverecondate == TRUE )
1032 acc->rdate = GLOBALS->today;
1033
1034 }
1035
1036
transaction_add(GtkWindow * parent,Transaction * ope)1037 Transaction *transaction_add(GtkWindow *parent, Transaction *ope)
1038 {
1039 Transaction *newope;
1040 Account *acc;
1041
1042 DB( g_print("\n[transaction] transaction_add\n") );
1043
1044 //controls accounts valid (archive scheduled maybe bad)
1045 acc = da_acc_get(ope->kacc);
1046 if(acc == NULL) return NULL;
1047
1048 DB( g_print(" acc is '%s' %d\n", acc->name, acc->key) );
1049
1050 ope->kcur = acc->kcur;
1051
1052 if(ope->flags & OF_INTXFER)
1053 {
1054 acc = da_acc_get(ope->kxferacc);
1055 if(acc == NULL) return NULL;
1056
1057 // delete any splits
1058 da_split_destroy(ope->splits);
1059 ope->splits = NULL;
1060 ope->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
1061 }
1062
1063
1064 //allocate a new entry and copy from our edited structure
1065 newope = da_transaction_clone(ope);
1066
1067 //init flag and keep remind status
1068 // already done in deftransaction_get
1069 //ope->flags |= (OF_ADDED);
1070 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
1071 //ope->flags &= (~OF_REMIND);
1072
1073 /* cheque number is already stored in deftransaction_get */
1074 /* todo:move this to transaction add
1075 store a new cheque number into account ? */
1076
1077 if( (newope->paymode == PAYMODE_CHECK) && (newope->info) && !(newope->flags & OF_INCOME) )
1078 {
1079 guint cheque;
1080
1081 /* get the active account and the corresponding cheque number */
1082 acc = da_acc_get( newope->kacc);
1083 if( acc != NULL )
1084 {
1085 cheque = atol(newope->info);
1086
1087 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
1088 if( newope->flags & OF_CHEQ2 )
1089 {
1090 acc->cheque2 = MAX(acc->cheque2, cheque);
1091 }
1092 else
1093 {
1094 acc->cheque1 = MAX(acc->cheque1, cheque);
1095 }
1096 }
1097 }
1098
1099 /* add normal transaction */
1100 acc = da_acc_get( newope->kacc);
1101 if(acc != NULL)
1102 {
1103 acc->flags |= AF_ADDED;
1104
1105 DB( g_print(" + add normal %p to acc %d\n", newope, acc->key) );
1106 //da_transaction_append(newope);
1107 da_transaction_insert_sorted(newope);
1108
1109 account_balances_add(newope);
1110
1111 if(newope->flags & OF_INTXFER)
1112 {
1113 transaction_xfer_search_or_add_child(parent, newope, newope->kxferacc);
1114 }
1115
1116 //#1581863 store reconciled date
1117 if( newope->status == TXN_STATUS_RECONCILED )
1118 acc->rdate = GLOBALS->today;
1119 }
1120
1121 return newope;
1122 }
1123
1124
transaction_acc_move(Transaction * txn,guint32 okacc,guint32 nkacc)1125 gboolean transaction_acc_move(Transaction *txn, guint32 okacc, guint32 nkacc)
1126 {
1127 Account *oacc, *nacc;
1128
1129 DB( g_print("\n[transaction] acc_move\n") );
1130
1131 if( okacc == nkacc )
1132 return TRUE;
1133
1134 oacc = da_acc_get(okacc);
1135 nacc = da_acc_get(nkacc);
1136 if( oacc && nacc )
1137 {
1138 account_balances_sub(txn);
1139 if( g_queue_remove(oacc->txn_queue, txn) )
1140 {
1141 g_queue_push_tail(nacc->txn_queue, txn);
1142 txn->kacc = nacc->key;
1143 txn->kcur = nacc->kcur;
1144 nacc->flags |= AF_CHANGED;
1145 account_balances_add(txn);
1146 //#1865083 src acc also changed (balance)
1147 oacc->flags |= AF_CHANGED;
1148 return TRUE;
1149 }
1150 else
1151 {
1152 //ensure to keep txn into current account
1153 txn->kacc = okacc;
1154 account_balances_add(txn);
1155 }
1156 }
1157 return FALSE;
1158 }
1159
1160
1161 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1162
1163
transaction_similar_match(Transaction * stxn,Transaction * dtxn,guint32 daygap)1164 static gboolean transaction_similar_match(Transaction *stxn, Transaction *dtxn, guint32 daygap)
1165 {
1166 gboolean retval = FALSE;
1167
1168 if(stxn == dtxn)
1169 return FALSE;
1170
1171 DB( g_print(" date: %d - %d = %d\n", stxn->date, dtxn->date, stxn->date - dtxn->date) );
1172
1173 if( stxn->kcur == dtxn->kcur
1174 && stxn->amount == dtxn->amount
1175 && ( (stxn->date - dtxn->date) <= daygap )
1176 //todo: at import we also check payee, but maybe too strict here
1177 && (hb_string_compare(stxn->memo, dtxn->memo) == 0)
1178 )
1179 {
1180 retval = TRUE;
1181 }
1182 return retval;
1183 }
1184
1185
transaction_similar_unmark(Account * acc)1186 void transaction_similar_unmark(Account *acc)
1187 {
1188 GList *lnk_txn;
1189
1190 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1191 while (lnk_txn != NULL)
1192 {
1193 Transaction *stxn = lnk_txn->data;
1194
1195 stxn->dspflags &= ~(TXN_DSPFLG_DUPSRC|TXN_DSPFLG_DUPDST);
1196 //stxn->marker = TXN_MARK_NONE;
1197 lnk_txn = g_list_previous(lnk_txn);
1198 }
1199 }
1200
1201
transaction_similar_mark(Account * acc,guint32 daygap)1202 gint transaction_similar_mark(Account *acc, guint32 daygap)
1203 {
1204 GList *lnk_txn, *list2;
1205 gint nball = 0;
1206 gint nbdup = 0;
1207
1208 //warning the list must be sorted by date then amount
1209 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1210 DB( g_print("\n[transaction] check duplicate\n") );
1211
1212 DB( g_print("\n - account:'%s' gap:%d\n", acc->name, daygap) );
1213
1214 #if MYDEBUG == 1
1215 GTimer *t = g_timer_new();
1216 g_print(" - start parse\n");
1217 #endif
1218
1219
1220 /*
1221 llast = g_list_last(old ope list);
1222 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1223 g_timer_reset(t);
1224
1225 ltxn = llast->data;
1226 g_date_clear(&gd, 1);
1227 g_date_set_julian(&gd, ltxn->date);
1228 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));
1229
1230 minjulian = ltxn->date - (366*2);
1231 g_date_clear(&gd, 1);
1232 g_date_set_julian(&gd, minjulian);
1233 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));
1234 */
1235
1236 transaction_similar_unmark(acc);
1237
1238 //mark duplicate
1239 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1240 while (lnk_txn != NULL)
1241 {
1242 Transaction *stxn = lnk_txn->data;
1243
1244 //if(stxn->date < minjulian)
1245 // break;
1246 DB( g_print("------\n eval src: %d, '%s', '%s', %.2f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1247
1248 list2 = g_list_previous(lnk_txn);
1249 while (list2 != NULL)
1250 {
1251 Transaction *dtxn = list2->data;
1252
1253 DB( g_print(" + with dst: %d, '%s', '%s', %.2f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1254
1255 if( (stxn->date - dtxn->date) > daygap )
1256 {
1257 DB( g_print(" break %d %d\n", (dtxn->date - daygap) , (stxn->date - daygap)) );
1258 break;
1259 }
1260
1261 //if( dtxn->marker == TXN_MARK_NONE )
1262 if( (dtxn->dspflags & (TXN_DSPFLG_DUPSRC|TXN_DSPFLG_DUPDST)) == 0 )
1263 {
1264 if( transaction_similar_match(stxn, dtxn, daygap) )
1265 {
1266 //stxn->marker = TXN_MARK_DUPSRC;
1267 stxn->dspflags |= TXN_DSPFLG_DUPSRC;
1268 //dtxn->marker = TXN_MARK_DUPDST;
1269 dtxn->dspflags |= TXN_DSPFLG_DUPDST;
1270 DB( g_print(" = dtxn marker=%d\n", dtxn->marker) );
1271 nball++;
1272 }
1273 }
1274 else
1275 {
1276 DB( g_print(" already marked %d\n", dtxn->marker) );
1277 }
1278
1279
1280 list2 = g_list_previous(list2);
1281 }
1282
1283 DB( g_print(" = stxn marker=%d\n", stxn->marker) );
1284 //if( stxn->marker == TXN_MARK_DUPSRC )
1285 if( stxn->dspflags & TXN_DSPFLG_DUPSRC )
1286 nbdup++;
1287
1288 lnk_txn = g_list_previous(lnk_txn);
1289 }
1290
1291 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1292 DB( g_timer_destroy (t) );
1293
1294 DB( g_print(" - found: %d/%d dup\n", nbdup, nball ) );
1295
1296 return nbdup;
1297 }
1298
1299
1300 guint
transaction_auto_all_from_payee(GList * txnlist)1301 transaction_auto_all_from_payee(GList *txnlist)
1302 {
1303 GList *list;
1304 Payee *pay;
1305 guint changes = 0;
1306
1307 DB( g_print("\n[transaction] auto from payee\n") );
1308
1309 list = g_list_first(txnlist);
1310 while(list != NULL)
1311 {
1312 Transaction *txn = list->data;
1313
1314 pay = da_pay_get(txn->kpay);
1315 if( pay != NULL )
1316 {
1317 if( txn->kcat == 0 )
1318 {
1319 txn->kcat = pay->kcat;
1320 changes++;
1321 }
1322
1323 if( txn->paymode == PAYMODE_NONE )
1324 {
1325 txn->paymode = pay->paymode;
1326 changes++;
1327 }
1328 }
1329 list = g_list_next(list);
1330 }
1331
1332 return changes;
1333 }
1334
1335
1336
1337 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1338 /* = = experimental = = */
1339 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1340
1341
1342 /*
1343 probably add a structure hosted into a glist here
1344 with kind of problem: duplicate, child xfer, orphan xfer
1345 and collect all that with target txn
1346 */
1347
1348
1349 /*void future_transaction_test_account(Account *acc)
1350 {
1351 GList *lnk_txn, *list2;
1352 gint nball = 0;
1353 gint nbdup = 0;
1354 gint nbxfer = 0;
1355 GPtrArray *array;
1356
1357 //future
1358 gint gapday = 0, i;
1359
1360 //warning the list must be sorted by date then amount
1361 //ideally (easier to parse) we shoudl get a list sorted by amount, then date
1362
1363 DB( g_print("\n[transaction] check duplicate\n") );
1364
1365
1366
1367 DB( g_print("\n - account:'%s'\n", acc->name) );
1368
1369 GTimer *t = g_timer_new();
1370 g_print(" - start parse\n");
1371
1372
1373 llast = g_list_last(old ope list);
1374 DB( g_print("- end last : %f sec\n", g_timer_elapsed(t, NULL)) );
1375 g_timer_reset(t);
1376
1377 ltxn = llast->data;
1378 g_date_clear(&gd, 1);
1379 g_date_set_julian(&gd, ltxn->date);
1380 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));
1381
1382 minjulian = ltxn->date - (366*2);
1383 g_date_clear(&gd, 1);
1384 g_date_set_julian(&gd, minjulian);
1385 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));
1386
1387 array = g_ptr_array_sized_new (25);
1388
1389 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
1390 while (lnk_txn != NULL)
1391 {
1392 Transaction *stxn = lnk_txn->data;
1393
1394 //if(stxn->date < minjulian)
1395 // break;
1396 DB( g_print("------\n eval src: %d, '%s', '%s', %2.f\n", stxn->date, stxn->info, stxn->memo, stxn->amount) );
1397
1398 stxn->marker = 0;
1399 list2 = g_list_previous(lnk_txn);
1400 while (list2 != NULL)
1401 {
1402 Transaction *dtxn = list2->data;
1403
1404 stxn->marker = 0;
1405 if( (dtxn->date + gapday) < (stxn->date + gapday) )
1406 break;
1407
1408 DB( g_print(" + with dst: %d, '%s', '%s', %2.f\n", dtxn->date, dtxn->info, dtxn->memo, dtxn->amount) );
1409
1410 if( transaction_similar_match(stxn, dtxn, gapday) )
1411 {
1412 g_ptr_array_add (array, stxn);
1413 g_ptr_array_add (array, dtxn);
1414 nbdup++;
1415 DB( g_print(" + dst=1 src=1\n") );
1416 }
1417
1418 nball++;
1419 list2 = g_list_previous(list2);
1420 }
1421
1422 lnk_txn = g_list_previous(lnk_txn);
1423 }
1424
1425 DB( g_print(" - end parse : %f sec\n", g_timer_elapsed(t, NULL)) );
1426 DB( g_timer_destroy (t) );
1427
1428 for(i=0;i<array->len;i++)
1429 {
1430 Transaction *txn = g_ptr_array_index(array, i);
1431 txn->marker = 1;
1432 }
1433
1434 g_ptr_array_free(array, TRUE);
1435
1436 DB( g_print(" - found: %d/%d dup, %d xfer\n", nbdup, nball, nbxfer ) );
1437
1438 }
1439
1440
1441
1442
1443
1444 //todo: add a limitation, no need to go through all txn
1445 // 1 year in th past, or abolute number ?
1446 gint future_transaction_test_notification(void)
1447 {
1448 GList *lst_acc, *lnk_acc;
1449
1450 DB( g_print("\ntransaction_test_notification\n") );
1451
1452 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
1453 lnk_acc = g_list_first(lst_acc);
1454 while (lnk_acc != NULL)
1455 {
1456 Account *acc = lnk_acc->data;
1457
1458 transaction_similar_mark(acc);
1459
1460 lnk_acc = g_list_next(lnk_acc);
1461 }
1462 g_list_free(lst_acc);
1463
1464 return 0;
1465 }
1466 */
1467
1468
1469
1470