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