1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 Maxime DOYEN
4 * This file is part of HomeBank.
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "hb-archive.h"
24 /****************************************************************************/
26 /****************************************************************************/
35 /* our global datas */
36 extern struct HomeBank
*GLOBALS
;
39 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
43 da_archive_clean(Archive
*item
)
47 if(item
->memo
!= NULL
)
57 //5.3 added as it was a leak
58 if(item
->tags
!= NULL
)
63 if(item
->splits
!= NULL
)
65 da_split_destroy(item
->splits
);
67 item
->flags
&= ~(OF_SPLIT
); //Flag that Splits are cleared
73 void da_archive_free(Archive
*item
)
77 da_archive_clean(item
);
83 Archive
*da_archive_malloc(void)
87 item
= g_malloc0(sizeof(Archive
));
93 Archive
*da_archive_clone(Archive
*src_item
)
95 Archive
*new_item
= g_memdup(src_item
, sizeof(Archive
));
99 //duplicate the string
100 new_item
->memo
= g_strdup(src_item
->memo
);
103 //no g_free here to avoid free the src tags (memdup copie dthe ptr)
104 new_item
->tags
= tags_clone(src_item
->tags
);
107 //no g_free here to avoid free the src tags (memdup copie dthe ptr)
108 new_item
->splits
= da_splits_clone(src_item
->splits
);
109 if( da_splits_length (new_item
->splits
) > 0 )
110 new_item
->flags
|= OF_SPLIT
; //Flag that Splits are active
116 void da_archive_destroy(GList
*list
)
118 GList
*tmplist
= g_list_first(list
);
120 while (tmplist
!= NULL
)
122 Archive
*item
= tmplist
->data
;
123 da_archive_free(item
);
124 tmplist
= g_list_next(tmplist
);
130 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
133 static gint
da_archive_glist_compare_func(Archive
*a
, Archive
*b
)
135 return hb_string_utf8_compare(a
->memo
, b
->memo
);
139 GList
*da_archive_sort(GList
*list
)
141 return g_list_sort(list
, (GCompareFunc
)da_archive_glist_compare_func
);
145 guint
da_archive_length(void)
147 return g_list_length(GLOBALS
->arc_list
);
151 /* append a fav with an existing key (from xml file only) */
153 da_archive_append(Archive
*item
)
155 GLOBALS
->arc_list
= g_list_append(GLOBALS
->arc_list
, item
);
161 da_archive_append_new(Archive
*item
)
163 item
->key
= da_archive_get_max_key() + 1;
164 GLOBALS
->arc_list
= g_list_append(GLOBALS
->arc_list
, item
);
170 da_archive_get_max_key(void)
172 GList
*tmplist
= g_list_first(GLOBALS
->arc_list
);
175 while (tmplist
!= NULL
)
177 Archive
*item
= tmplist
->data
;
179 max_key
= MAX(item
->key
, max_key
);
180 tmplist
= g_list_next(tmplist
);
188 da_archive_get(guint32 key
)
191 Archive
*retval
= NULL
;
193 tmplist
= g_list_first(GLOBALS
->arc_list
);
194 while (tmplist
!= NULL
)
196 Archive
*item
= tmplist
->data
;
203 tmplist
= g_list_next(tmplist
);
209 void da_archive_consistency(Archive
*item
)
216 // check category exists
217 cat
= da_cat_get(item
->kcat
);
220 g_warning("arc consistency: fixed invalid cat %d", item
->kcat
);
222 GLOBALS
->changes_count
++;
225 //#1340142 check split category
226 if( item
->splits
!= NULL
)
228 nbsplit
= da_splits_consistency(item
->splits
);
229 //# 1416624 empty category when split
230 if(nbsplit
> 0 && item
->kcat
> 0)
232 g_warning("txn consistency: fixed invalid cat on split txn");
234 GLOBALS
->changes_count
++;
238 // check payee exists
239 pay
= da_pay_get(item
->kpay
);
242 g_warning("arc consistency: fixed invalid pay %d", item
->kpay
);
244 GLOBALS
->changes_count
++;
247 // reset dst acc for non xfer transaction
248 if( item
->paymode
!= PAYMODE_INTXFER
)
251 // delete automation if dst_acc not exists
252 if(item
->paymode
== PAYMODE_INTXFER
)
254 acc
= da_acc_get(item
->kxferacc
);
257 item
->flags
&= ~(OF_AUTO
); //delete flag
263 /* = = = = = = = = = = = = = = = = = = = = */
265 Archive
*da_archive_init_from_transaction(Archive
*arc
, Transaction
*txn
)
267 DB( g_print("\n[scheduled] init from txn\n") );
270 arc
->amount
= txn
->amount
;
271 arc
->kacc
= txn
->kacc
;
272 arc
->kxferacc
= txn
->kxferacc
;
273 arc
->paymode
= txn
->paymode
;
274 arc
->flags
= txn
->flags
& (OF_INCOME
);
275 arc
->status
= txn
->status
;
276 arc
->kpay
= txn
->kpay
;
277 arc
->kcat
= txn
->kcat
;
278 if(txn
->memo
!= NULL
)
279 arc
->memo
= g_strdup(txn
->memo
);
281 arc
->memo
= g_strdup(_("(new archive)"));
283 arc
->tags
= tags_clone(txn
->tags
);
284 arc
->splits
= da_splits_clone(txn
->splits
);
285 if( da_splits_length (arc
->splits
) > 0 )
286 arc
->flags
|= OF_SPLIT
; //Flag that Splits are active
292 static guint32
_sched_date_get_next_post(GDate
*tmpdate
, Archive
*arc
, guint32 nextdate
)
294 guint32 nextpostdate
= nextdate
;
296 //DB( g_print("\n[scheduled] date_get_next_post\n") );
298 g_date_set_julian(tmpdate
, nextpostdate
);
300 //DB( g_print("in : %2d-%2d-%4d\n", g_date_get_day(tmpdate), g_date_get_month (tmpdate), g_date_get_year(tmpdate) ) );
305 g_date_add_days(tmpdate
, arc
->every
);
308 g_date_add_days(tmpdate
, 7 * arc
->every
);
310 case AUTO_UNIT_MONTH
:
311 g_date_add_months(tmpdate
, arc
->every
);
314 g_date_add_years(tmpdate
, arc
->every
);
318 //DB( g_print("out: %2d-%2d-%4d\n", g_date_get_day(tmpdate), g_date_get_month (tmpdate), g_date_get_year(tmpdate) ) );
321 /* get the final post date and free */
322 nextpostdate
= g_date_get_julian(tmpdate
);
328 gboolean
scheduled_is_postable(Archive
*arc
)
332 value
= hb_amount_round(arc
->amount
, 2);
333 if( (arc
->flags
& OF_AUTO
) && (arc
->kacc
> 0) && (value
!= 0.0) )
340 guint32
scheduled_get_postdate(Archive
*arc
, guint32 postdate
)
344 guint32 finalpostdate
;
347 DB( g_print("\n[scheduled] get_postdate\n") );
350 finalpostdate
= postdate
;
352 tmpdate
= g_date_new_julian(finalpostdate
);
353 /* manage weekend exception */
354 if( arc
->weekend
> 0 )
356 wday
= g_date_get_weekday(tmpdate
);
358 DB( g_print(" %s wday=%d\n", arc
->memo
, wday
) );
360 if( wday
>= G_DATE_SATURDAY
)
364 case 1: /* shift before : sun 7-5=+2 , sat 6-5=+1 */
365 shift
= wday
- G_DATE_FRIDAY
;
366 DB( g_print("sub=%d\n", shift
) );
367 g_date_subtract_days (tmpdate
, shift
);
370 case 2: /* shift after : sun 8-7=1 , sat 8-6=2 */
372 DB( g_print("add=%d\n", shift
) );
373 g_date_add_days (tmpdate
, shift
);
379 /* get the final post date and free */
380 finalpostdate
= g_date_get_julian(tmpdate
);
381 g_date_free(tmpdate
);
383 return finalpostdate
;
387 guint32
scheduled_get_latepost_count(Archive
*arc
, guint32 jrefdate
)
393 //DB( g_print("\n[scheduled] get_latepost_count\n") );
396 curdate = jrefdate - arc->nextdate;
400 nbpost = (curdate / arc->every);
401 g_print("debug d: %d => %f\n", curdate, nbpost);
405 nbpost = (curdate / ( 7 * arc->every));
406 g_print("debug w: %d => %f\n", curdate, nbpost);
409 case AUTO_UNIT_MONTH:
410 //approximate is sufficient
411 nbpost = (curdate / (( 365.2425 / 12) * arc->every));
412 g_print("debug m: %d => %f\n", curdate, nbpost);
416 //approximate is sufficient
417 nbpost = (curdate / ( 365.2425 * arc->every));
418 g_print("debug y: %d => %f\n", curdate, nbpost);
422 nblate = floor(nbpost);
424 if(arc->flags & OF_LIMIT)
425 nblate = MIN(nblate, arc->limit);
427 nblate = MIN(nblate, 11);
432 post_date
= g_date_new();
433 curdate
= arc
->nextdate
;
434 while(curdate
<= jrefdate
)
436 curdate
= _sched_date_get_next_post(post_date
, arc
, curdate
);
438 // break if over limit or at 11 max (to display +10)
439 if( nblate
>= 11 || ( (arc
->flags
& OF_LIMIT
) && (nblate
>= arc
->limit
) ) )
443 //DB( g_print(" nblate=%d\n", nblate) );
445 g_date_free(post_date
);
451 /* return 0 is max number of post is reached */
452 guint32
scheduled_date_advance(Archive
*arc
)
457 DB( g_print("\n[scheduled] date_advance\n") );
459 DB( g_print(" arc: '%s'\n", arc
->memo
) );
461 post_date
= g_date_new();
462 g_date_set_julian(post_date
, arc
->nextdate
);
463 // saved the current day number
464 lastday
= g_date_get_day(post_date
);
466 arc
->nextdate
= _sched_date_get_next_post(post_date
, arc
, arc
->nextdate
);
468 DB( g_print(" raw next post date: %2d-%2d-%4d\n", g_date_get_day(post_date
), g_date_get_month (post_date
), g_date_get_year(post_date
) ) );
470 //for day > 28 we might have a gap to compensate later
471 if( (arc
->unit
==AUTO_UNIT_MONTH
) || (arc
->unit
==AUTO_UNIT_YEAR
) )
475 DB( g_print(" lastday:%d, daygap:%d\n", lastday
, arc
->daygap
) );
476 if( arc
->daygap
> 0 )
478 g_date_add_days (post_date
, arc
->daygap
);
479 arc
->nextdate
= g_date_get_julian (post_date
);
480 lastday
+= arc
->daygap
;
481 DB( g_print(" adjusted post date: %2d-%2d-%4d\n", g_date_get_day(post_date
), g_date_get_month (post_date
), g_date_get_year(post_date
) ) );
484 arc
->daygap
= CLAMP(lastday
- g_date_get_day(post_date
), 0, 3);
486 DB( g_print(" daygap is %d\n", arc
->daygap
) );
494 /* check limit, update and maybe break */
495 if(arc
->flags
& OF_LIMIT
)
500 arc
->flags
^= (OF_LIMIT
| OF_AUTO
); // invert flags
505 g_date_free(post_date
);
507 return arc
->nextdate
;
512 * return the maximum date a scheduled txn can be posted to
514 guint32
scheduled_date_get_post_max(void)
517 GDate
*today
, *maxdate
;
519 DB( g_print("\n[scheduled] date_get_post_max\n") );
521 //add until xx of the next month (excluded)
522 if(GLOBALS
->auto_smode
== 0)
524 DB( g_print(" - max is %d of next month\n", GLOBALS
->auto_weekday
) );
526 today
= g_date_new_julian(GLOBALS
->today
);
528 //we compute user xx weekday of next month
529 maxdate
= g_date_new_julian(GLOBALS
->today
);
530 g_date_set_day(maxdate
, GLOBALS
->auto_weekday
);
531 if(g_date_get_day (today
) >= GLOBALS
->auto_weekday
)
532 g_date_add_months(maxdate
, 1);
534 nbdays
= g_date_days_between(today
, maxdate
);
536 g_date_free(maxdate
);
541 nbdays
= GLOBALS
->auto_nbdays
;
544 DB( hb_print_date(GLOBALS
->today
, "today") );
545 DB( g_print(" - %d nbdays\n", nbdays
) );
546 DB( hb_print_date(GLOBALS
->today
+ nbdays
, "maxpostdate") );
548 return GLOBALS
->today
+ nbdays
;
552 gint
scheduled_post_all_pending(void)
559 DB( g_print("\n[scheduled] post_all_pending\n") );
563 maxpostdate
= scheduled_date_get_post_max();
565 txn
= da_transaction_malloc();
567 list
= g_list_first(GLOBALS
->arc_list
);
570 Archive
*arc
= list
->data
;
572 DB( g_print("\n eval %d for '%s'\n", scheduled_is_postable(arc
), arc
->memo
) );
574 if(scheduled_is_postable(arc
) == TRUE
)
576 DB( g_print(" - every %d limit %d (to %d)\n", arc
->every
, arc
->flags
& OF_LIMIT
, arc
->limit
) );
577 DB( hb_print_date(arc
->nextdate
, "next post") );
579 if(arc
->nextdate
< maxpostdate
)
581 guint32 mydate
= arc
->nextdate
;
583 while(mydate
< maxpostdate
)
585 DB( hb_print_date(mydate
, arc
->memo
) );
587 da_transaction_init_from_template(txn
, arc
);
588 txn
->date
= scheduled_get_postdate(arc
, mydate
);
589 /* todo: ? fill in cheque number */
591 transaction_add(NULL
, txn
);
592 GLOBALS
->changes_count
++;
595 da_transaction_clean(txn
);
597 mydate
= scheduled_date_advance(arc
);
599 //DB( hb_print_date(mydate, "next on") );
608 list
= g_list_next(list
);
611 da_transaction_free (txn
);