]> Dogcows Code - chaz/homebank/blob - src/hb-transaction.c
import homebank-5.1.7
[chaz/homebank] / src / hb-transaction.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2018 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 macros */
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 void
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 DB( g_print(" -> item->tags %p\n", item->tags) );
62 g_free(item->tags);
63 item->tags = NULL;
64 }
65
66 da_splits_free(item->splits);
67 item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
68
69 if(item->same != NULL)
70 {
71 g_list_free(item->same);
72 item->same = NULL;
73 }
74 }
75 }
76
77
78 void
79 da_transaction_free(Transaction *item)
80 {
81 if(item != NULL)
82 {
83 da_transaction_clean(item);
84 g_free(item);
85 }
86 }
87
88
89 Transaction *
90 da_transaction_malloc(void)
91 {
92 return g_malloc0(sizeof(Transaction));
93 }
94
95
96 Transaction *da_transaction_copy(Transaction *src_txn, Transaction *dst_txn)
97 {
98 DB( g_print("da_transaction_copy\n") );
99
100 da_transaction_clean (dst_txn);
101
102 memmove(dst_txn, src_txn, sizeof(Transaction));
103
104 //duplicate the string
105 dst_txn->memo = g_strdup(src_txn->memo);
106 dst_txn->info = g_strdup(src_txn->info);
107
108 //duplicate tags
109 transaction_tags_clone(src_txn, dst_txn);
110
111 if (da_splits_clone(src_txn->splits, dst_txn->splits) > 0)
112 dst_txn->flags |= OF_SPLIT; //Flag that Splits are active
113
114 return dst_txn;
115 }
116
117
118 Transaction *da_transaction_init_from_template(Transaction *txn, Archive *arc)
119 {
120 //txn->date = 0;
121 txn->amount = arc->amount;
122 //#1258344 keep the current account if tpl is empty
123 if(arc->kacc)
124 txn->kacc = arc->kacc;
125 txn->paymode = arc->paymode;
126 txn->flags = arc->flags | OF_ADDED;
127 txn->status = arc->status;
128 txn->kpay = arc->kpay;
129 txn->kcat = arc->kcat;
130 txn->kxferacc = arc->kxferacc;
131 txn->memo = g_strdup(arc->memo);
132 txn->info = NULL;
133 if( da_splits_clone(arc->splits, txn->splits) > 0)
134 txn->flags |= OF_SPLIT; //Flag that Splits are active
135
136 return txn;
137 }
138
139
140 Transaction *da_transaction_clone(Transaction *src_item)
141 {
142 Transaction *new_item = g_memdup(src_item, sizeof(Transaction));
143
144 DB( g_print("da_transaction_clone\n") );
145
146 if(new_item)
147 {
148 //duplicate the string
149 new_item->memo = g_strdup(src_item->memo);
150 new_item->info = g_strdup(src_item->info);
151
152 //duplicate tags
153 transaction_tags_clone(src_item, new_item);
154
155 if( da_splits_clone(src_item->splits, new_item->splits) > 0)
156 new_item->flags |= OF_SPLIT; //Flag that Splits are active
157
158 }
159 return new_item;
160 }
161
162
163 GList *
164 da_transaction_new(void)
165 {
166 return NULL;
167 }
168
169
170 guint
171 da_transaction_length(void)
172 {
173 GList *lst_acc, *lnk_acc;
174 guint count = 0;
175
176 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
177 lnk_acc = g_list_first(lst_acc);
178 while (lnk_acc != NULL)
179 {
180 Account *acc = lnk_acc->data;
181
182 count += g_queue_get_length (acc->txn_queue);
183 lnk_acc = g_list_next(lnk_acc);
184 }
185 g_list_free(lst_acc);
186 return count;
187 }
188
189
190 static void da_transaction_queue_free_ghfunc(Transaction *item, gpointer data)
191 {
192 da_transaction_free (item);
193 }
194
195
196 void da_transaction_destroy(void)
197 {
198 GList *lacc, *list;
199
200 lacc = g_hash_table_get_values(GLOBALS->h_acc);
201 list = g_list_first(lacc);
202 while (list != NULL)
203 {
204 Account *acc = list->data;
205
206 g_queue_foreach(acc->txn_queue, (GFunc)da_transaction_queue_free_ghfunc, NULL);
207 list = g_list_next(list);
208 }
209 g_list_free(lacc);
210 }
211
212
213 static gint da_transaction_compare_datafunc(Transaction *a, Transaction *b, gpointer data)
214 {
215 return ((gint)a->date - b->date);
216 }
217
218
219 void da_transaction_queue_sort(GQueue *queue)
220 {
221 g_queue_sort(queue, (GCompareDataFunc)da_transaction_compare_datafunc, NULL);
222 }
223
224
225 static gint da_transaction_compare_func(Transaction *a, Transaction *b)
226 {
227 return ((gint)a->date - b->date);
228 }
229
230
231 GList *da_transaction_sort(GList *list)
232 {
233 return( g_list_sort(list, (GCompareFunc)da_transaction_compare_func));
234 }
235
236
237 gboolean da_transaction_insert_memo(Transaction *item)
238 {
239 gboolean retval = FALSE;
240
241 // append the memo if new
242 if( item->memo != NULL )
243 {
244 if( g_hash_table_lookup(GLOBALS->h_memo, item->memo) == NULL )
245 {
246 retval = g_hash_table_insert(GLOBALS->h_memo, g_strdup(item->memo), NULL);
247 }
248 }
249 return retval;
250 }
251
252
253 gboolean da_transaction_insert_sorted(Transaction *newitem)
254 {
255 Account *acc;
256 GList *lnk_txn;
257
258 acc = da_acc_get(newitem->kacc);
259 if(!acc)
260 return FALSE;
261
262 lnk_txn = g_queue_peek_tail_link(acc->txn_queue);
263 while (lnk_txn != NULL)
264 {
265 Transaction *item = lnk_txn->data;
266
267 if(item->date <= newitem->date)
268 break;
269
270 lnk_txn = g_list_previous(lnk_txn);
271 }
272
273 // we're at insert point, insert after txn
274 g_queue_insert_after(acc->txn_queue, lnk_txn, newitem);
275
276 da_transaction_insert_memo(newitem);
277 return TRUE;
278 }
279
280
281 // nota: this is called only when loading xml file
282 gboolean da_transaction_prepend(Transaction *item)
283 {
284 Account *acc;
285
286 acc = da_acc_get(item->kacc);
287 //#1661279
288 if(!acc)
289 return FALSE;
290
291 item->kcur = acc->kcur;
292 g_queue_push_tail(acc->txn_queue, item);
293 da_transaction_insert_memo(item);
294 return TRUE;
295 }
296
297
298 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
299
300 static guint32
301 da_transaction_get_max_kxfer(void)
302 {
303 GList *lst_acc, *lnk_acc;
304 GList *list;
305 guint32 max_key = 0;
306
307 DB( g_print("da_transaction_get_max_kxfer\n") );
308
309 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
310 lnk_acc = g_list_first(lst_acc);
311 while (lnk_acc != NULL)
312 {
313 Account *acc = lnk_acc->data;
314
315 list = g_queue_peek_head_link(acc->txn_queue);
316 while (list != NULL)
317 {
318 Transaction *item = list->data;
319
320 if( item->paymode == PAYMODE_INTXFER )
321 {
322 max_key = MAX(max_key, item->kxfer);
323 }
324 list = g_list_next(list);
325 }
326
327 lnk_acc = g_list_next(lnk_acc);
328 }
329 g_list_free(lst_acc);
330
331 DB( g_print(" max_key : %d \n", max_key) );
332
333 return max_key;
334 }
335
336
337 static void da_transaction_goto_orphan(Transaction *txn)
338 {
339 const gchar *oatn = "orphaned transactions";
340 Account *ori_acc, *acc;
341 gboolean found;
342
343 DB( g_print("\n[transaction] goto orphan\n") );
344
345 g_warning("txn consistency: moving to orphan %d '%s' %.2f", txn->date, txn->memo, txn->amount);
346
347 acc = da_acc_get_by_name((gchar *)oatn);
348 if(acc == NULL)
349 {
350 acc = da_acc_malloc();
351 acc->name = g_strdup(oatn);
352 da_acc_append(acc);
353 DB( g_print(" - created orphan acc %d\n", acc->key) );
354 }
355
356 ori_acc = da_acc_get(txn->kacc);
357 if( ori_acc )
358 {
359 found = g_queue_remove(ori_acc->txn_queue, txn);
360 DB( g_print(" - found in origin ? %d\n", found) );
361 if(found)
362 {
363 txn->kacc = acc->key;
364 da_transaction_insert_sorted (txn);
365 DB( g_print("moved txn to %d\n", txn->kacc) );
366 }
367 }
368 }
369
370
371 void da_transaction_consistency(Transaction *item)
372 {
373 Account *acc;
374 Category *cat;
375 Payee *pay;
376 gint nbsplit;
377
378 DB( g_print("\n[transaction] consistency\n") );
379
380 // ensure date is between range
381 item->date = CLAMP(item->date, HB_MINDATE, HB_MAXDATE);
382
383 // check account exists
384 acc = da_acc_get(item->kacc);
385 if(acc == NULL)
386 {
387 g_warning("txn consistency: fixed invalid acc %d", item->kacc);
388 da_transaction_goto_orphan(item);
389 GLOBALS->changes_count++;
390 }
391
392 // check category exists
393 cat = da_cat_get(item->kcat);
394 if(cat == NULL)
395 {
396 g_warning("txn consistency: fixed invalid cat %d", item->kcat);
397 item->kcat = 0;
398 GLOBALS->changes_count++;
399 }
400
401 // check split category #1340142
402 split_cat_consistency(item->splits);
403
404 //# 1416624 empty category when split
405 nbsplit = da_splits_count(item->splits);
406 if(nbsplit > 0 && item->kcat > 0)
407 {
408 g_warning("txn consistency: fixed invalid cat on split txn");
409 item->kcat = 0;
410 GLOBALS->changes_count++;
411 }
412
413 // check payee exists
414 pay = da_pay_get(item->kpay);
415 if(pay == NULL)
416 {
417 g_warning("txn consistency: fixed invalid pay %d", item->kpay);
418 item->kpay = 0;
419 GLOBALS->changes_count++;
420 }
421
422 // reset dst acc for non xfer transaction
423 if( item->paymode != PAYMODE_INTXFER )
424 {
425 item->kxfer = 0;
426 item->kxferacc = 0;
427 }
428
429 // check dst account exists
430 if( item->paymode == PAYMODE_INTXFER )
431 {
432 gint tak = item->kxferacc;
433
434 item->kxferacc = ABS(tak); //I crossed negative here one day
435 acc = da_acc_get(item->kxferacc);
436 if(acc == NULL)
437 {
438 g_warning("txn consistency: fixed invalid dst_acc %d", item->kxferacc);
439 da_transaction_goto_orphan(item);
440 item->kxfer = 0;
441 item->paymode = PAYMODE_XFER;
442 GLOBALS->changes_count++;
443 }
444 }
445
446 //#1628678 tags for internal xfer should be checked as well
447
448
449 //#1295877 ensure income flag is correctly set
450 item->flags &= ~(OF_INCOME);
451 if( item->amount > 0)
452 item->flags |= (OF_INCOME);
453
454 //#1308745 ensure remind flag unset if reconciled
455 //useless since 5.0
456 //if( item->flags & OF_VALID )
457 // item->flags &= ~(OF_REMIND);
458
459 }
460
461
462 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
463 /* new transfer functions */
464
465 static void transaction_xfer_create_child(Transaction *ope)
466 {
467 Transaction *child;
468 Account *acc;
469 gchar swap;
470
471 DB( g_print("\n[transaction] xfer_create_child\n") );
472
473 if( ope->kxferacc > 0 )
474 {
475 child = da_transaction_clone(ope);
476
477 ope->flags |= OF_CHANGED;
478 child->flags |= OF_ADDED;
479
480 child->amount = -child->amount;
481 child->flags ^= (OF_INCOME); // invert flag
482 //#1268026 #1690555
483 if( child->status != TXN_STATUS_REMIND )
484 child->status = TXN_STATUS_NONE;
485 //child->flags &= ~(OF_VALID); // delete reconcile state
486
487 swap = child->kacc;
488 child->kacc = child->kxferacc;
489 child->kxferacc = swap;
490
491 /* update acc flags */
492 acc = da_acc_get( child->kacc );
493 if(acc != NULL)
494 {
495 acc->flags |= AF_ADDED;
496
497 //strong link
498 guint maxkey = da_transaction_get_max_kxfer();
499
500 DB( g_print(" + maxkey is %d\n", maxkey) );
501
502
503 ope->kxfer = maxkey+1;
504 child->kxfer = maxkey+1;
505
506 DB( g_print(" + strong link to %d\n", ope->kxfer) );
507
508
509 DB( g_print(" + add transfer, %p to acc %d\n", child, acc->key) );
510
511 da_transaction_insert_sorted(child);
512
513 account_balances_add (child);
514
515 }
516 }
517
518 }
519
520
521 //todo: add strong control and extend to payee, maybe memo ?
522 static gboolean transaction_xfer_child_might(Transaction *stxn, Transaction *dtxn, gint daygap)
523 {
524 gboolean retval = FALSE;
525
526 //DB( g_print("\n[transaction] xfer_child_might\n") );
527
528 if(stxn == dtxn)
529 return FALSE;
530
531 /*g_print("test\n");
532
533 g_print(" %d %d %d %f %d\n",
534 stxn->kcur, stxn->date, stxn->kacc, ABS(stxn->amount), stxn->kxfer );
535
536
537 g_print(" %d %d %d %f %d\n",
538 dtxn->kcur, dtxn->date, dtxn->kacc, ABS(dtxn->amount), dtxn->kxfer );
539 */
540
541 if( stxn->kcur == dtxn->kcur &&
542 stxn->date == dtxn->date &&
543 //v5.1 make no sense: stxn->kxferacc == dtxn->kacc &&
544 stxn->kacc != dtxn->kacc &&
545 ABS(stxn->amount) == ABS(dtxn->amount) &&
546 dtxn->kxfer == 0)
547 {
548 retval = TRUE;
549 }
550
551 //g_print(" return %d\n", retval);
552 return retval;
553 }
554
555
556 static GList *transaction_xfer_child_might_list_get(Transaction *ope)
557 {
558 GList *lst_acc, *lnk_acc;
559 GList *list, *matchlist = NULL;
560
561 //DB( g_print("\n[transaction] xfer_child_might_list_get\n") );
562
563 lst_acc = g_hash_table_get_values(GLOBALS->h_acc);
564 lnk_acc = g_list_first(lst_acc);
565 while (lnk_acc != NULL)
566 {
567 Account *acc = lnk_acc->data;
568
569 if( !(acc->flags & AF_CLOSED) && (acc->key != ope->kacc) )
570 {
571 list = g_queue_peek_tail_link(acc->txn_queue);
572 while (list != NULL)
573 {
574 Transaction *item = list->data;
575
576 // no need to go higher than src txn date
577 if(item->date < ope->date)
578 break;
579
580 if( transaction_xfer_child_might(ope, item, 0) == TRUE )
581 {
582 //DB( g_print(" - match : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
583 matchlist = g_list_append(matchlist, item);
584 }
585 list = g_list_previous(list);
586 }
587 }
588
589 lnk_acc = g_list_next(lnk_acc);
590 }
591 g_list_free(lst_acc);
592
593 return matchlist;
594 }
595
596
597 void transaction_xfer_search_or_add_child(GtkWindow *parentwindow, Transaction *ope, gboolean manual)
598 {
599 GList *matchlist;
600 gint count;
601
602 DB( g_print("\n[transaction] xfer_search_or_add_child\n") );
603
604 matchlist = transaction_xfer_child_might_list_get(ope);
605 count = g_list_length(matchlist);
606
607 DB( g_print(" - found %d might match, switching\n", count) );
608
609 switch(count)
610 {
611 case 0: //we should create the child
612 transaction_xfer_create_child(ope);
613 break;
614
615 //todo: maybe with just 1 match the user must choose ?
616 //#942346: bad idea so to no let the user confirm, so let hil confirm
617 /*
618 case 1: //transform the transaction to a child transfer
619 {
620 GList *list = g_list_first(matchlist);
621 transaction_xfer_change_to_child(ope, list->data);
622 break;
623 }
624 */
625
626 default: //the user must choose himself
627 {
628 Transaction *child;
629
630 child = ui_dialog_transaction_xfer_select_child(ope, matchlist);
631 if(child == NULL)
632 transaction_xfer_create_child(ope);
633 else
634 transaction_xfer_change_to_child(ope, child);
635 }
636 }
637
638 g_list_free(matchlist);
639 }
640
641
642 Transaction *transaction_xfer_child_strong_get(Transaction *src)
643 {
644 Account *dstacc;
645 GList *list;
646
647 DB( g_print("\n[transaction] xfer_child_strong_get\n") );
648
649 dstacc = da_acc_get(src->kxferacc);
650 if( !dstacc || src->kxfer <= 0 )
651 return NULL;
652
653 DB( g_print(" - search: %d %s %f %d=>%d - %d\n", src->date, src->memo, src->amount, src->kacc, src->kxferacc, src->kxfer) );
654
655 list = g_queue_peek_tail_link(dstacc->txn_queue);
656 while (list != NULL)
657 {
658 Transaction *item = list->data;
659
660 //#1252230
661 //if( item->paymode == PAYMODE_INTXFER
662 // && item->kacc == src->kxferacc
663 // && item->kxfer == src->kxfer )
664 if( item->paymode == PAYMODE_INTXFER
665 && item->kxfer == src->kxfer
666 && item != src )
667 {
668 DB( g_print(" - found : %d %s %f %d=>%d - %d\n", item->date, item->memo, item->amount, item->kacc, item->kxferacc, src->kxfer) );
669 return item;
670 }
671 list = g_list_previous(list);
672 }
673
674 DB( g_print(" - not found...\n") );
675 return NULL;
676 }
677
678
679
680
681 void transaction_xfer_change_to_child(Transaction *ope, Transaction *child)
682 {
683 Account *dstacc;
684
685 DB( g_print("\n[transaction] xfer_change_to_child\n") );
686
687 if(ope->kcur != child->kcur)
688 return;
689
690 ope->flags |= OF_CHANGED;
691 child->flags |= OF_CHANGED;
692
693 child->paymode = PAYMODE_INTXFER;
694
695 ope->kxferacc = child->kacc;
696 child->kxferacc = ope->kacc;
697
698 /* update acc flags */
699 dstacc = da_acc_get( child->kacc);
700 if(dstacc != NULL)
701 dstacc->flags |= AF_CHANGED;
702
703 //strong link
704 guint maxkey = da_transaction_get_max_kxfer();
705 ope->kxfer = maxkey+1;
706 child->kxfer = maxkey+1;
707
708 }
709
710
711 void transaction_xfer_child_sync(Transaction *s_txn, Transaction *child)
712 {
713 Account *acc;
714
715 DB( g_print("\n[transaction] xfer_child_sync\n") );
716
717 if( child == NULL )
718 {
719 DB( g_print(" - no child found\n") );
720 return;
721 }
722
723 DB( g_print(" - found do sync\n") );
724
725 /* update acc flags */
726 acc = da_acc_get( child->kacc);
727 if(acc != NULL)
728 acc->flags |= AF_CHANGED;
729
730 account_balances_sub (child);
731
732 child->date = s_txn->date;
733 child->amount = -s_txn->amount;
734 child->flags = child->flags | OF_CHANGED;
735 //#1295877
736 child->flags &= ~(OF_INCOME);
737 if( child->amount > 0)
738 child->flags |= (OF_INCOME);
739 child->kpay = s_txn->kpay;
740 child->kcat = s_txn->kcat;
741 if(child->memo)
742 g_free(child->memo);
743 child->memo = g_strdup(s_txn->memo);
744 if(child->info)
745 g_free(child->info);
746 child->info = g_strdup(s_txn->info);
747
748 account_balances_add (child);
749
750 //#1252230 sync account also
751 //#1663789 idem after 5.1
752 //source changed: update child key (move of s_txn is done in external_edit)
753 if( s_txn->kacc != child->kxferacc )
754 {
755 child->kxferacc = s_txn->kacc;
756 }
757
758 //dest changed: move child & update child key
759 if( s_txn->kxferacc != child->kacc )
760 {
761 transaction_acc_move(child, child->kacc, s_txn->kxferacc);
762 }
763
764 //synchronise tags since 5.1
765 if(child->tags)
766 g_free(child->tags);
767 transaction_tags_clone (s_txn, child);
768
769 }
770
771
772 void transaction_xfer_remove_child(Transaction *src)
773 {
774 Transaction *dst;
775
776 DB( g_print("\n[transaction] xfer_remove_child\n") );
777
778 dst = transaction_xfer_child_strong_get( src );
779 if( dst != NULL )
780 {
781 Account *acc = da_acc_get(dst->kacc);
782
783 if( acc != NULL )
784 {
785 DB( g_print("deleting...") );
786 account_balances_sub(dst);
787 g_queue_remove(acc->txn_queue, dst);
788 //#1419304 we keep the deleted txn to a trash stack
789 //da_transaction_free (dst);
790 g_trash_stack_push(&GLOBALS->txn_stk, dst);
791
792 //#1691992
793 acc->flags |= AF_CHANGED;
794 }
795 }
796
797 src->kxfer = 0;
798 src->kxferacc = 0;
799 }
800
801
802 // still useful for upgrade from < file v0.6 (hb v4.4 kxfer)
803 Transaction *transaction_old_get_child_transfer(Transaction *src)
804 {
805 Account *acc;
806 GList *list;
807
808 DB( g_print("\n[transaction] get_child_transfer\n") );
809
810 //DB( g_print(" search: %d %s %f %d=>%d\n", src->date, src->memo, src->amount, src->account, src->kxferacc) );
811 acc = da_acc_get(src->kxferacc);
812
813 if( acc != NULL )
814 {
815 list = g_queue_peek_head_link(acc->txn_queue);
816 while (list != NULL)
817 {
818 Transaction *item = list->data;
819
820 // no need to go higher than src txn date
821 if(item->date > src->date)
822 break;
823
824 if( item->paymode == PAYMODE_INTXFER)
825 {
826 if( src->date == item->date &&
827 src->kacc == item->kxferacc &&
828 src->kxferacc == item->kacc &&
829 ABS(src->amount) == ABS(item->amount) )
830 {
831 //DB( g_print(" found : %d %s %f %d=>%d\n", item->date, item->memo, item->amount, item->account, item->kxferacc) );
832
833 return item;
834 }
835 }
836 list = g_list_next(list);
837 }
838 }
839
840 DB( g_print(" not found...\n") );
841
842 return NULL;
843 }
844
845
846 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
847
848
849 void transaction_remove(Transaction *ope)
850 {
851 Account *acc;
852
853 //controls accounts valid (archive scheduled maybe bad)
854 acc = da_acc_get(ope->kacc);
855 if(acc == NULL) return;
856
857 account_balances_sub(ope);
858
859 if( ope->paymode == PAYMODE_INTXFER )
860 {
861 transaction_xfer_remove_child( ope );
862 }
863
864 g_queue_remove(acc->txn_queue, ope);
865 acc->flags |= AF_CHANGED;
866 //#1419304 we keep the deleted txn to a trash stack
867 //da_transaction_free(entry);
868 g_trash_stack_push(&GLOBALS->txn_stk, ope);
869 }
870
871
872 Transaction *transaction_add(Transaction *ope)
873 {
874 Transaction *newope;
875 Account *acc;
876
877 DB( g_print("\n[transaction] transaction_add\n") );
878
879 //controls accounts valid (archive scheduled maybe bad)
880 acc = da_acc_get(ope->kacc);
881 if(acc == NULL) return NULL;
882
883 DB( g_print(" acc is '%s' %d\n", acc->name, acc->key) );
884
885 ope->kcur = acc->kcur;
886
887 if(ope->paymode == PAYMODE_INTXFER)
888 {
889 acc = da_acc_get(ope->kxferacc);
890 if(acc == NULL) return NULL;
891
892 // delete any splits
893 da_splits_free(ope->splits);
894 ope->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
895 }
896
897
898 //allocate a new entry and copy from our edited structure
899 newope = da_transaction_clone(ope);
900
901 //init flag and keep remind status
902 // already done in deftransaction_get
903 //ope->flags |= (OF_ADDED);
904 //remind = (ope->flags & OF_REMIND) ? TRUE : FALSE;
905 //ope->flags &= (~OF_REMIND);
906
907 /* cheque number is already stored in deftransaction_get */
908 /* todo:move this to transaction add
909 store a new cheque number into account ? */
910
911 if( (newope->paymode == PAYMODE_CHECK) && (newope->info) && !(newope->flags & OF_INCOME) )
912 {
913 guint cheque;
914
915 /* get the active account and the corresponding cheque number */
916 acc = da_acc_get( newope->kacc);
917 if( acc != NULL )
918 {
919 cheque = atol(newope->info);
920
921 //DB( g_print(" -> should store cheque number %d to %d", cheque, newope->account) );
922 if( newope->flags & OF_CHEQ2 )
923 {
924 acc->cheque2 = MAX(acc->cheque2, cheque);
925 }
926 else
927 {
928 acc->cheque1 = MAX(acc->cheque1, cheque);
929 }
930 }
931 }
932
933 /* add normal transaction */
934 acc = da_acc_get( newope->kacc);
935 if(acc != NULL)
936 {
937 acc->flags |= AF_ADDED;
938
939 DB( g_print(" + add normal %p to acc %d\n", newope, acc->key) );
940 //da_transaction_append(newope);
941 da_transaction_insert_sorted(newope);
942
943 account_balances_add(newope);
944
945 if(newope->paymode == PAYMODE_INTXFER)
946 {
947 transaction_xfer_search_or_add_child(NULL, newope, FALSE);
948 }
949 }
950
951 return newope;
952 }
953
954
955 gboolean transaction_acc_move(Transaction *txn, guint32 okacc, guint32 nkacc)
956 {
957 Account *oacc, *nacc;
958
959 DB( g_print("\n[transaction] acc_move\n") );
960
961 if( okacc == nkacc )
962 return TRUE;
963
964 oacc = da_acc_get(okacc);
965 nacc = da_acc_get(nkacc);
966 if( oacc && nacc )
967 {
968 account_balances_sub(txn);
969 if( g_queue_remove(oacc->txn_queue, txn) )
970 {
971 g_queue_push_tail(nacc->txn_queue, txn);
972 txn->kacc = nacc->key;
973 txn->kcur = nacc->kcur;
974 nacc->flags |= AF_CHANGED;
975 account_balances_add(txn);
976 return TRUE;
977 }
978 else
979 {
980 //ensure to keep txn into current account
981 txn->kacc = okacc;
982 account_balances_add(txn);
983 }
984 }
985 return FALSE;
986 }
987
988
989 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
990
991
992 static gboolean misc_text_match(gchar *text, gchar *searchtext, gboolean exact)
993 {
994 gboolean match = FALSE;
995
996 if(text == NULL)
997 return FALSE;
998
999 //DB( g_print("search %s in %s\n", rul->name, ope->memo) );
1000 if( searchtext != NULL )
1001 {
1002 if( exact == TRUE )
1003 {
1004 if( g_strrstr(text, searchtext) != NULL )
1005 {
1006 DB( g_print(" found case '%s'\n", searchtext) );
1007 match = TRUE;
1008 }
1009 }
1010 else
1011 {
1012 gchar *word = g_utf8_casefold(text, -1);
1013 gchar *needle = g_utf8_casefold(searchtext, -1);
1014
1015 if( g_strrstr(word, needle) != NULL )
1016 {
1017 DB( g_print(" found nocase '%s'\n", searchtext) );
1018 match = TRUE;
1019 }
1020 g_free(word);
1021 g_free(needle);
1022 }
1023 }
1024
1025 return match;
1026 }
1027
1028 static gboolean misc_regex_match(gchar *text, gchar *searchtext, gboolean exact)
1029 {
1030 gboolean match = FALSE;
1031
1032 if(text == NULL)
1033 return FALSE;
1034
1035 DB( g_print("- match RE %s in %s\n", searchtext, text) );
1036 if( searchtext != NULL )
1037 {
1038 match = g_regex_match_simple(searchtext, text, ((exact == TRUE)?0:G_REGEX_CASELESS) | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY );
1039 if (match == TRUE) { DB( g_print(" found pattern '%s'\n", searchtext) ); }
1040 }
1041 return match;
1042 }
1043
1044
1045 static Assign *transaction_auto_assign_eval_txn(GList *l_rul, Transaction *txn)
1046 {
1047 Assign *rule = NULL;
1048 GList *list;
1049
1050 DB( g_print("\n[transaction] auto_assign_eval_txn\n") );
1051
1052 DB( g_print("- eval every rules, and return the last that match\n") );
1053
1054 list = g_list_first(l_rul);
1055 while (list != NULL)
1056 {
1057 Assign *rul = list->data;
1058 gchar *text;
1059
1060 text = txn->memo;
1061 if(rul->field == 1) //payee
1062 {
1063 Payee *pay = da_pay_get(txn->kpay);
1064 if(pay)
1065 text = pay->name;
1066 }
1067
1068 if( !(rul->flags & ASGF_REGEX) )
1069 {
1070 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1071 rule = rul;
1072 }
1073 else
1074 {
1075 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1076 rule = rul;
1077 }
1078
1079 list = g_list_next(list);
1080 }
1081
1082 return rule;
1083 }
1084
1085
1086 static Assign *transaction_auto_assign_eval(GList *l_rul, gchar *text)
1087 {
1088 Assign *rule = NULL;
1089 GList *list;
1090
1091 DB( g_print("\n[transaction] auto_assign_eval\n") );
1092
1093 DB( g_print("- eval every rules, and return the last that match\n") );
1094
1095 list = g_list_first(l_rul);
1096 while (list != NULL)
1097 {
1098 Assign *rul = list->data;
1099
1100 if( rul->field == 0 ) //memo
1101 {
1102 if( !(rul->flags & ASGF_REGEX) )
1103 {
1104 if( misc_text_match(text, rul->text, rul->flags & ASGF_EXACT) )
1105 rule = rul;
1106 }
1107 else
1108 {
1109 if( misc_regex_match(text, rul->text, rul->flags & ASGF_EXACT) )
1110 rule = rul;
1111 }
1112 }
1113 list = g_list_next(list);
1114 }
1115
1116 return rule;
1117 }
1118
1119
1120 gint transaction_auto_assign(GList *ope_list, guint32 kacc)
1121 {
1122 GList *l_ope;
1123 GList *l_rul;
1124 gint changes = 0;
1125
1126 DB( g_print("\n[transaction] auto_assign\n") );
1127
1128 l_rul = g_hash_table_get_values(GLOBALS->h_rul);
1129
1130 l_ope = g_list_first(ope_list);
1131 while (l_ope != NULL)
1132 {
1133 Transaction *ope = l_ope->data;
1134 gboolean changed = FALSE;
1135
1136 DB( g_print("- eval ope '%s' : acc=%d, pay=%d, cat=%d\n", ope->memo, ope->kacc, ope->kpay, ope->kcat) );
1137
1138 //#1215521: added kacc == 0
1139 if( (kacc == ope->kacc || kacc == 0) )
1140 {
1141 Assign *rul;
1142
1143 rul = transaction_auto_assign_eval_txn(l_rul, ope);
1144 if( rul != NULL )
1145 {
1146 if( (ope->kpay == 0 && (rul->flags & ASGF_DOPAY)) || (rul->flags & ASGF_OVWPAY) )
1147 {
1148 if(ope->kpay != rul->kpay) { changed = TRUE; }
1149 ope->kpay = rul->kpay;
1150 }
1151
1152 if( !(ope->flags & OF_SPLIT) )
1153 {
1154 if( (ope->kcat == 0 && (rul->flags & ASGF_DOCAT)) || (rul->flags & ASGF_OVWCAT) )
1155 {
1156 if(ope->kcat != rul->kcat) { changed = TRUE; }
1157 ope->kcat = rul->kcat;
1158 }
1159 }
1160
1161 if( (ope->paymode == 0 && (rul->flags & ASGF_DOMOD)) || (rul->flags & ASGF_OVWMOD) )
1162 {
1163 //ugly hack - don't allow modify intxfer
1164 if(ope->paymode != PAYMODE_INTXFER && rul->paymode != PAYMODE_INTXFER)
1165 {
1166 if(ope->paymode != rul->paymode) { changed = TRUE; }
1167 ope->paymode = rul->paymode;
1168 }
1169 }
1170
1171 }
1172
1173 if( ope->flags & OF_SPLIT )
1174 {
1175 guint i, nbsplit = da_splits_count(ope->splits);
1176
1177 for(i=0;i<nbsplit;i++)
1178 {
1179 Split *split = ope->splits[i];
1180
1181 DB( g_print("- eval split '%s'\n", split->memo) );
1182
1183 rul = transaction_auto_assign_eval(l_rul, split->memo);
1184 if( rul != NULL )
1185 {
1186 //#1501144: check if user wants to set category in rule
1187 if( (split->kcat == 0 || (rul->flags & ASGF_OVWCAT)) && (rul->flags & ASGF_DOCAT) )
1188 {
1189 if(split->kcat != rul->kcat) { changed = TRUE; }
1190 split->kcat = rul->kcat;
1191 }
1192 }
1193 }
1194 }
1195
1196 if(changed == TRUE)
1197 {
1198 ope->flags |= OF_CHANGED;
1199 changes++;
1200 }
1201 }
1202
1203 l_ope = g_list_next(l_ope);
1204 }
1205
1206 g_list_free(l_rul);
1207
1208 return changes;
1209 }
1210
1211
1212 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
1213
1214
1215 guint
1216 transaction_tags_count(Transaction *ope)
1217 {
1218 guint count = 0;
1219 guint32 *ptr = ope->tags;
1220
1221 //DB( g_print("\n[transaction] tags_count\n") );
1222
1223 if( ope->tags == NULL )
1224 return 0;
1225
1226 while(*ptr++ != 0 && count < 32)
1227 count++;
1228
1229 return count;
1230 }
1231
1232
1233 void transaction_tags_clone(Transaction *src_txn, Transaction *dst_txn)
1234 {
1235 guint count;
1236
1237 dst_txn->tags = NULL;
1238 count = transaction_tags_count(src_txn);
1239 if(count > 0)
1240 {
1241 //1501962: we must also copy the final 0
1242 dst_txn->tags = g_memdup(src_txn->tags, (count+1)*sizeof(guint32));
1243 }
1244 }
1245
1246 guint
1247 transaction_tags_parse(Transaction *ope, const gchar *tagstring)
1248 {
1249 gchar **str_array;
1250 guint count, i;
1251 Tag *tag;
1252
1253 DB( g_print("\n[transaction] tags_parse\n") );
1254
1255 DB( g_print(" - tagstring='%s'\n", tagstring) );
1256
1257 str_array = g_strsplit (tagstring, " ", 0);
1258 count = g_strv_length( str_array );
1259
1260 g_free(ope->tags);
1261 ope->tags = NULL;
1262
1263 DB( g_print(" -> reset storage %p\n", ope->tags) );
1264
1265
1266 if( count > 0 )
1267 {
1268
1269 ope->tags = g_new0(guint32, count + 1);
1270
1271 DB( g_print(" -> storage %p\n", ope->tags) );
1272
1273 for(i=0;i<count;i++)
1274 {
1275 tag = da_tag_get_by_name(str_array[i]);
1276 if(tag == NULL)
1277 {
1278 Tag *newtag = da_tag_malloc();
1279
1280 newtag->name = g_strdup(str_array[i]);
1281 da_tag_append(newtag);
1282 tag = da_tag_get_by_name(str_array[i]);
1283 }
1284
1285 DB( g_print(" -> storing %d=>%s at tags pos %d\n", tag->key, tag->name, i) );
1286
1287 ope->tags[i] = tag->key;
1288 }
1289 ope->tags[i] = 0;
1290 }
1291
1292 //hex_dump(ope->tags, sizeof(guint32*)*count+1);
1293
1294 g_strfreev (str_array);
1295
1296 return count;
1297 }
1298
1299 gchar *
1300 transaction_tags_tostring(Transaction *ope)
1301 {
1302 guint count, i;
1303 gchar **str_array;
1304 gchar *tagstring;
1305 Tag *tag;
1306
1307 DB( g_print("\n[transaction] tags_tostring\n") );
1308
1309 DB( g_print(" -> tags at=%p\n", ope->tags) );
1310
1311 if( ope->tags == NULL )
1312 {
1313
1314 return NULL;
1315 }
1316 else
1317 {
1318 count = transaction_tags_count(ope);
1319
1320 DB( g_print(" -> tags at=%p, nbtags=%d\n", ope->tags, count) );
1321
1322 str_array = g_new0(gchar*, count+1);
1323
1324 DB( g_print(" -> str_array at %p\n", str_array) );
1325
1326 //hex_dump(ope->tags, sizeof(guint32*)*(count+1));
1327
1328 for(i=0;i<count;i++)
1329 {
1330 DB( g_print(" -> try to get tag %d\n", ope->tags[i]) );
1331
1332 tag = da_tag_get(ope->tags[i]);
1333 if( tag )
1334 {
1335 DB( g_print(" -> get %s at %d\n", tag->name, i) );
1336 str_array[i] = tag->name;
1337 }
1338 else
1339 str_array[i] = NULL;
1340
1341
1342 }
1343
1344 tagstring = g_strjoinv(" ", str_array);
1345
1346 g_free (str_array);
1347
1348 }
1349
1350 return tagstring;
1351 }
1352
This page took 0.091417 seconds and 4 git commands to generate.