]> Dogcows Code - chaz/homebank/blob - src/hb-archive.c
Merge branch 'master' into ext-perl
[chaz/homebank] / src / hb-archive.c
1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2019 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 #include "hb-archive.h"
22 #include "hb-split.h"
23
24 #include "ext.h"
25 #include "refcount.h"
26
27 /****************************************************************************/
28 /* Debug macros */
29 /****************************************************************************/
30 #define MYDEBUG 0
31
32 #if MYDEBUG
33 #define DB(x) (x);
34 #else
35 #define DB(x);
36 #endif
37
38 /* our global datas */
39 extern struct HomeBank *GLOBALS;
40
41
42 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
43
44
45 static void
46 da_archive_clean(Archive *item)
47 {
48 if(item != NULL)
49 {
50 if(item->memo != NULL)
51 {
52 g_free(item->memo);
53 item->memo = NULL;
54 }
55
56
57
58
59
60 //5.3 added as it was a leak
61 if(item->tags != NULL)
62 {
63 g_free(item->tags);
64 item->tags = NULL;
65 }
66 if(item->splits != NULL)
67 {
68 da_split_destroy(item->splits);
69 item->splits = NULL;
70 item->flags &= ~(OF_SPLIT); //Flag that Splits are cleared
71 }
72 }
73 }
74
75
76 void da_archive_free(Archive *item)
77 {
78 if(rc_unref(item))
79 {
80 da_archive_clean(item);
81 rc_free(item);
82 }
83 }
84
85
86 Archive *da_archive_malloc(void)
87 {
88 Archive *item;
89
90 item = rc_alloc(sizeof(Archive));
91 item->key = 1;
92 return item;
93 }
94
95
96 Archive *da_archive_clone(Archive *src_item)
97 {
98 Archive *new_item = rc_dup(src_item, sizeof(Archive));
99
100 if(new_item)
101 {
102 //duplicate the string
103 new_item->memo = g_strdup(src_item->memo);
104
105 //duplicate tags
106 //no g_free here to avoid free the src tags (memdup copie dthe ptr)
107 new_item->tags = tags_clone(src_item->tags);
108
109 //duplicate splits
110 //no g_free here to avoid free the src tags (memdup copie dthe ptr)
111 new_item->splits = da_splits_clone(src_item->splits);
112 if( da_splits_length (new_item->splits) > 0 )
113 new_item->flags |= OF_SPLIT; //Flag that Splits are active
114 }
115 return new_item;
116 }
117
118
119 void da_archive_destroy(GList *list)
120 {
121 GList *tmplist = g_list_first(list);
122
123 while (tmplist != NULL)
124 {
125 Archive *item = tmplist->data;
126 da_archive_free(item);
127 tmplist = g_list_next(tmplist);
128 }
129 g_list_free(list);
130 }
131
132
133 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
134
135
136 static gint da_archive_glist_compare_func(Archive *a, Archive *b)
137 {
138 return hb_string_utf8_compare(a->memo, b->memo);
139 }
140
141
142 GList *da_archive_sort(GList *list)
143 {
144 return g_list_sort(list, (GCompareFunc)da_archive_glist_compare_func);
145 }
146
147
148 guint da_archive_length(void)
149 {
150 return g_list_length(GLOBALS->arc_list);
151 }
152
153
154 /* append a fav with an existing key (from xml file only) */
155 gboolean
156 da_archive_append(Archive *item)
157 {
158 GLOBALS->arc_list = g_list_append(GLOBALS->arc_list, item);
159 return TRUE;
160 }
161
162
163 gboolean
164 da_archive_append_new(Archive *item)
165 {
166 item->key = da_archive_get_max_key() + 1;
167 GLOBALS->arc_list = g_list_append(GLOBALS->arc_list, item);
168 return TRUE;
169 }
170
171
172 guint32
173 da_archive_get_max_key(void)
174 {
175 GList *tmplist = g_list_first(GLOBALS->arc_list);
176 guint32 max_key = 0;
177
178 while (tmplist != NULL)
179 {
180 Archive *item = tmplist->data;
181
182 max_key = MAX(item->key, max_key);
183 tmplist = g_list_next(tmplist);
184 }
185
186 return max_key;
187 }
188
189
190 Archive *
191 da_archive_get(guint32 key)
192 {
193 GList *tmplist;
194 Archive *retval = NULL;
195
196 tmplist = g_list_first(GLOBALS->arc_list);
197 while (tmplist != NULL)
198 {
199 Archive *item = tmplist->data;
200
201 if(item->key == key)
202 {
203 retval = item;
204 break;
205 }
206 tmplist = g_list_next(tmplist);
207 }
208 return retval;
209 }
210
211
212 void da_archive_consistency(Archive *item)
213 {
214 Account *acc;
215 Category *cat;
216 Payee *pay;
217 guint nbsplit;
218
219 // check category exists
220 cat = da_cat_get(item->kcat);
221 if(cat == NULL)
222 {
223 g_warning("arc consistency: fixed invalid cat %d", item->kcat);
224 item->kcat = 0;
225 GLOBALS->changes_count++;
226 }
227
228 //#1340142 check split category
229 if( item->splits != NULL )
230 {
231 nbsplit = da_splits_consistency(item->splits);
232 //# 1416624 empty category when split
233 if(nbsplit > 0 && item->kcat > 0)
234 {
235 g_warning("txn consistency: fixed invalid cat on split txn");
236 item->kcat = 0;
237 GLOBALS->changes_count++;
238 }
239 }
240
241 // check payee exists
242 pay = da_pay_get(item->kpay);
243 if(pay == NULL)
244 {
245 g_warning("arc consistency: fixed invalid pay %d", item->kpay);
246 item->kpay = 0;
247 GLOBALS->changes_count++;
248 }
249
250 // reset dst acc for non xfer transaction
251 if( item->paymode != PAYMODE_INTXFER )
252 item->kxferacc = 0;
253
254 // delete automation if dst_acc not exists
255 if(item->paymode == PAYMODE_INTXFER)
256 {
257 acc = da_acc_get(item->kxferacc);
258 if(acc == NULL)
259 {
260 item->flags &= ~(OF_AUTO); //delete flag
261 }
262 }
263
264 }
265
266 /* = = = = = = = = = = = = = = = = = = = = */
267
268 Archive *da_archive_init_from_transaction(Archive *arc, Transaction *txn)
269 {
270 DB( g_print("\n[scheduled] init from txn\n") );
271
272 //fill it
273 arc->amount = txn->amount;
274 arc->kacc = txn->kacc;
275 arc->kxferacc = txn->kxferacc;
276 arc->paymode = txn->paymode;
277 arc->flags = txn->flags & (OF_INCOME);
278 arc->status = txn->status;
279 arc->kpay = txn->kpay;
280 arc->kcat = txn->kcat;
281 if(txn->memo != NULL)
282 arc->memo = g_strdup(txn->memo);
283 else
284 arc->memo = g_strdup(_("(new archive)"));
285
286 arc->tags = tags_clone(txn->tags);
287 arc->splits = da_splits_clone(txn->splits);
288 if( da_splits_length (arc->splits) > 0 )
289 arc->flags |= OF_SPLIT; //Flag that Splits are active
290
291 return arc;
292 }
293
294
295 static guint32 _sched_date_get_next_post(GDate *tmpdate, Archive *arc, guint32 nextdate)
296 {
297 guint32 nextpostdate = nextdate;
298
299 //DB( g_print("\n[scheduled] date_get_next_post\n") );
300
301 g_date_set_julian(tmpdate, nextpostdate);
302
303 //DB( g_print("in : %2d-%2d-%4d\n", g_date_get_day(tmpdate), g_date_get_month (tmpdate), g_date_get_year(tmpdate) ) );
304
305 switch(arc->unit)
306 {
307 case AUTO_UNIT_DAY:
308 g_date_add_days(tmpdate, arc->every);
309 break;
310 case AUTO_UNIT_WEEK:
311 g_date_add_days(tmpdate, 7 * arc->every);
312 break;
313 case AUTO_UNIT_MONTH:
314 g_date_add_months(tmpdate, arc->every);
315 break;
316 case AUTO_UNIT_YEAR:
317 g_date_add_years(tmpdate, arc->every);
318 break;
319 }
320
321 //DB( g_print("out: %2d-%2d-%4d\n", g_date_get_day(tmpdate), g_date_get_month (tmpdate), g_date_get_year(tmpdate) ) );
322
323
324 /* get the final post date and free */
325 nextpostdate = g_date_get_julian(tmpdate);
326
327 return nextpostdate;
328 }
329
330
331 gboolean scheduled_is_postable(Archive *arc)
332 {
333 gdouble value;
334
335 value = hb_amount_round(arc->amount, 2);
336 if( (arc->flags & OF_AUTO) && (arc->kacc > 0) && (value != 0.0) )
337 return TRUE;
338
339 return FALSE;
340 }
341
342
343 guint32 scheduled_get_postdate(Archive *arc, guint32 postdate)
344 {
345 GDate *tmpdate;
346 GDateWeekday wday;
347 guint32 finalpostdate;
348 gint shift;
349
350 DB( g_print("\n[scheduled] get_postdate\n") );
351
352
353 finalpostdate = postdate;
354
355 tmpdate = g_date_new_julian(finalpostdate);
356 /* manage weekend exception */
357 if( arc->weekend > 0 )
358 {
359 wday = g_date_get_weekday(tmpdate);
360
361 DB( g_print(" %s wday=%d\n", arc->memo, wday) );
362
363 if( wday >= G_DATE_SATURDAY )
364 {
365 switch(arc->weekend)
366 {
367 case 1: /* shift before : sun 7-5=+2 , sat 6-5=+1 */
368 shift = wday - G_DATE_FRIDAY;
369 DB( g_print("sub=%d\n", shift) );
370 g_date_subtract_days (tmpdate, shift);
371 break;
372
373 case 2: /* shift after : sun 8-7=1 , sat 8-6=2 */
374 shift = 8 - wday;
375 DB( g_print("add=%d\n", shift) );
376 g_date_add_days (tmpdate, shift);
377 break;
378 }
379 }
380 }
381
382 /* get the final post date and free */
383 finalpostdate = g_date_get_julian(tmpdate);
384 g_date_free(tmpdate);
385
386 return finalpostdate;
387 }
388
389
390 guint32 scheduled_get_latepost_count(Archive *arc, guint32 jrefdate)
391 {
392 GDate *post_date;
393 guint32 curdate;
394 guint32 nblate = 0;
395
396 //DB( g_print("\n[scheduled] get_latepost_count\n") );
397
398 /*
399 curdate = jrefdate - arc->nextdate;
400 switch(arc->unit)
401 {
402 case AUTO_UNIT_DAY:
403 nbpost = (curdate / arc->every);
404 g_print("debug d: %d => %f\n", curdate, nbpost);
405 break;
406
407 case AUTO_UNIT_WEEK:
408 nbpost = (curdate / ( 7 * arc->every));
409 g_print("debug w: %d => %f\n", curdate, nbpost);
410 break;
411
412 case AUTO_UNIT_MONTH:
413 //approximate is sufficient
414 nbpost = (curdate / (( 365.2425 / 12) * arc->every));
415 g_print("debug m: %d => %f\n", curdate, nbpost);
416 break;
417
418 case AUTO_UNIT_YEAR:
419 //approximate is sufficient
420 nbpost = (curdate / ( 365.2425 * arc->every));
421 g_print("debug y: %d => %f\n", curdate, nbpost);
422 break;
423 }
424
425 nblate = floor(nbpost);
426
427 if(arc->flags & OF_LIMIT)
428 nblate = MIN(nblate, arc->limit);
429
430 nblate = MIN(nblate, 11);
431 */
432
433
434 // pre 5.1 way
435 post_date = g_date_new();
436 curdate = arc->nextdate;
437 while(curdate <= jrefdate)
438 {
439 curdate = _sched_date_get_next_post(post_date, arc, curdate);
440 nblate++;
441 // break if over limit or at 11 max (to display +10)
442 if( nblate >= 11 || ( (arc->flags & OF_LIMIT) && (nblate >= arc->limit) ) )
443 break;
444 }
445
446 //DB( g_print(" nblate=%d\n", nblate) );
447
448 g_date_free(post_date);
449
450 return nblate;
451 }
452
453
454 /* return 0 is max number of post is reached */
455 guint32 scheduled_date_advance(Archive *arc)
456 {
457 GDate *post_date;
458 gushort lastday;
459
460 DB( g_print("\n[scheduled] date_advance\n") );
461
462 DB( g_print(" arc: '%s'\n", arc->memo ) );
463
464 post_date = g_date_new();
465 g_date_set_julian(post_date, arc->nextdate);
466 // saved the current day number
467 lastday = g_date_get_day(post_date);
468
469 arc->nextdate = _sched_date_get_next_post(post_date, arc, arc->nextdate);
470
471 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) ) );
472
473 //for day > 28 we might have a gap to compensate later
474 if( (arc->unit==AUTO_UNIT_MONTH) || (arc->unit==AUTO_UNIT_YEAR) )
475 {
476 if( lastday >= 28 )
477 {
478 DB( g_print(" lastday:%d, daygap:%d\n", lastday, arc->daygap) );
479 if( arc->daygap > 0 )
480 {
481 g_date_add_days (post_date, arc->daygap);
482 arc->nextdate = g_date_get_julian (post_date);
483 lastday += arc->daygap;
484 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) ) );
485 }
486
487 arc->daygap = CLAMP(lastday - g_date_get_day(post_date), 0, 3);
488
489 DB( g_print(" daygap is %d\n", arc->daygap) );
490 }
491 else
492 arc->daygap = 0;
493 }
494
495
496 //#1556289
497 /* check limit, update and maybe break */
498 if(arc->flags & OF_LIMIT)
499 {
500 arc->limit--;
501 if(arc->limit <= 0)
502 {
503 arc->flags ^= (OF_LIMIT | OF_AUTO); // invert flags
504 arc->nextdate = 0;
505 }
506 }
507
508 g_date_free(post_date);
509
510 return arc->nextdate;
511 }
512
513
514 /*
515 * return the maximum date a scheduled txn can be posted to
516 */
517 guint32 scheduled_date_get_post_max(void)
518 {
519 guint nbdays;
520 GDate *today, *maxdate;
521
522 DB( g_print("\n[scheduled] date_get_post_max\n") );
523
524 //add until xx of the next month (excluded)
525 if(GLOBALS->auto_smode == 0)
526 {
527 DB( g_print(" - max is %d of next month\n", GLOBALS->auto_weekday) );
528
529 today = g_date_new_julian(GLOBALS->today);
530
531 //we compute user xx weekday of next month
532 maxdate = g_date_new_julian(GLOBALS->today);
533 g_date_set_day(maxdate, GLOBALS->auto_weekday);
534 if(g_date_get_day (today) >= GLOBALS->auto_weekday)
535 g_date_add_months(maxdate, 1);
536
537 nbdays = g_date_days_between(today, maxdate);
538
539 g_date_free(maxdate);
540 g_date_free(today);
541 }
542 else
543 {
544 nbdays = GLOBALS->auto_nbdays;
545 }
546
547 DB( hb_print_date(GLOBALS->today, "today") );
548 DB( g_print(" - %d nbdays\n", nbdays) );
549 DB( hb_print_date(GLOBALS->today + nbdays, "maxpostdate") );
550
551 return GLOBALS->today + nbdays;
552 }
553
554
555 gint scheduled_post_all_pending(void)
556 {
557 GList *list;
558 gint count;
559 guint32 maxpostdate;
560 Transaction *txn;
561
562 DB( g_print("\n[scheduled] post_all_pending\n") );
563
564 count = 0;
565
566 maxpostdate = scheduled_date_get_post_max();
567
568 txn = da_transaction_malloc();
569
570 list = g_list_first(GLOBALS->arc_list);
571 while (list != NULL)
572 {
573 Archive *arc = list->data;
574
575 DB( g_print("\n eval %d for '%s'\n", scheduled_is_postable(arc), arc->memo) );
576
577 if(scheduled_is_postable(arc) == TRUE)
578 {
579 DB( g_print(" - every %d limit %d (to %d)\n", arc->every, arc->flags & OF_LIMIT, arc->limit) );
580 DB( hb_print_date(arc->nextdate, "next post") );
581
582 if(arc->nextdate < maxpostdate)
583 {
584 guint32 mydate = arc->nextdate;
585
586 while(mydate < maxpostdate)
587 {
588 DB( hb_print_date(mydate, arc->memo) );
589
590 da_transaction_init_from_template(txn, arc);
591 txn->date = scheduled_get_postdate(arc, mydate);
592 /* todo: ? fill in cheque number */
593
594 transaction_add(NULL, txn);
595 GLOBALS->changes_count++;
596 count++;
597
598 da_transaction_clean(txn);
599
600 mydate = scheduled_date_advance(arc);
601
602 //DB( hb_print_date(mydate, "next on") );
603
604 if(mydate == 0)
605 goto nextarchive;
606 }
607
608 }
609 }
610 nextarchive:
611 list = g_list_next(list);
612 }
613
614 da_transaction_free (txn);
615
616 return count;
617 }
618
This page took 0.058459 seconds and 4 git commands to generate.