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