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