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