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