1 /********************************************************************\
2 * gncBillTerm.c -- the Gnucash Billing Terms interface *
3 * *
4 * This program is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU General Public License as *
6 * published by the Free Software Foundation; either version 2 of *
7 * the License, or (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License*
15 * along with this program; if not, contact: *
16 * *
17 * Free Software Foundation Voice: +1-617-542-5942 *
18 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19 * Boston, MA 02110-1301, USA gnu@gnu.org *
20 * *
21 \********************************************************************/
22
23 /*
24 * Copyright (C) 2002 Derek Atkins
25 * Copyright (C) 2003 Linas Vepstas <linas@linas.org>
26 * Author: Derek Atkins <warlord@MIT.EDU>
27 */
28
29 #include <config.h>
30
31 #include <glib.h>
32 #include <qofinstance-p.h>
33
34 #include "gnc-engine.h"
35 #include "gncBillTermP.h"
36
37 struct _gncBillTerm
38 {
39 QofInstance inst;
40
41 /* 'visible' data fields directly manipulated by user */
42 const char * name;
43 const char * desc;
44 GncBillTermType type;
45 gint due_days;
46 gint disc_days;
47 gnc_numeric discount;
48 gint cutoff;
49
50 /* Internal management fields */
51 /* See src/doc/business.txt for an explanation of the following */
52 /* Code that handles this is *identical* to that in gncTaxTable */
53 gint64 refcount;
54 GncBillTerm * parent; /* if non-null, we are an immutable child */
55 GncBillTerm * child; /* if non-null, we have not changed */
56 gboolean invisible;
57 GList * children; /* list of children for disconnection */
58 };
59
60 struct _gncBillTermClass
61 {
62 QofInstanceClass parent_class;
63 };
64
65 struct _book_info
66 {
67 GList * terms; /* visible terms */
68 };
69
70 static QofLogModule log_module = GNC_MOD_BUSINESS;
71
72 #define _GNC_MOD_NAME GNC_ID_BILLTERM
73
74 #define SET_STR(obj, member, str) { \
75 if (!g_strcmp0 (member, str)) return; \
76 gncBillTermBeginEdit (obj); \
77 CACHE_REPLACE(member, str); \
78 }
79
AS_STRING_DEC(GncBillTermType,ENUM_TERMS_TYPE)80 AS_STRING_DEC(GncBillTermType, ENUM_TERMS_TYPE)
81 FROM_STRING_DEC(GncBillTermType, ENUM_TERMS_TYPE)
82
83 /* ============================================================== */
84 /* Misc inline utilities */
85
86 static inline void
87 mark_term (GncBillTerm *term)
88 {
89 qof_instance_set_dirty(&term->inst);
90 qof_event_gen (&term->inst, QOF_EVENT_MODIFY, NULL);
91 }
92
maybe_resort_list(GncBillTerm * term)93 static inline void maybe_resort_list (GncBillTerm *term)
94 {
95 struct _book_info *bi;
96
97 if (term->parent || term->invisible) return;
98 bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
99 bi->terms = g_list_sort (bi->terms, (GCompareFunc)gncBillTermCompare);
100 }
101
addObj(GncBillTerm * term)102 static inline void addObj (GncBillTerm *term)
103 {
104 struct _book_info *bi;
105 bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
106 bi->terms = g_list_insert_sorted (bi->terms, term,
107 (GCompareFunc)gncBillTermCompare);
108 }
109
remObj(GncBillTerm * term)110 static inline void remObj (GncBillTerm *term)
111 {
112 struct _book_info *bi;
113 bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
114 bi->terms = g_list_remove (bi->terms, term);
115 }
116
117 static inline void
gncBillTermAddChild(GncBillTerm * table,GncBillTerm * child)118 gncBillTermAddChild (GncBillTerm *table, GncBillTerm *child)
119 {
120 g_return_if_fail(qof_instance_get_destroying(table) == FALSE);
121 table->children = g_list_prepend(table->children, child);
122 }
123
124 static inline void
gncBillTermRemoveChild(GncBillTerm * table,GncBillTerm * child)125 gncBillTermRemoveChild (GncBillTerm *table, GncBillTerm *child)
126 {
127 if (qof_instance_get_destroying(table)) return;
128 table->children = g_list_remove(table->children, child);
129 }
130
131 /* ============================================================== */
132
133 enum
134 {
135 PROP_0,
136 PROP_NAME
137 };
138
139 /* GObject Initialization */
140 G_DEFINE_TYPE(GncBillTerm, gnc_billterm, QOF_TYPE_INSTANCE);
141
142 static void
gnc_billterm_init(GncBillTerm * bt)143 gnc_billterm_init(GncBillTerm* bt)
144 {
145 }
146
147 static void
gnc_billterm_dispose(GObject * btp)148 gnc_billterm_dispose(GObject *btp)
149 {
150 G_OBJECT_CLASS(gnc_billterm_parent_class)->dispose(btp);
151 }
152
153 static void
gnc_billterm_finalize(GObject * btp)154 gnc_billterm_finalize(GObject* btp)
155 {
156 G_OBJECT_CLASS(gnc_billterm_parent_class)->finalize(btp);
157 }
158
159 static void
gnc_billterm_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)160 gnc_billterm_get_property (GObject *object,
161 guint prop_id,
162 GValue *value,
163 GParamSpec *pspec)
164 {
165 GncBillTerm *bt;
166
167 g_return_if_fail(GNC_IS_BILLTERM(object));
168
169 bt = GNC_BILLTERM(object);
170 switch (prop_id)
171 {
172 case PROP_NAME:
173 g_value_set_string(value, bt->name);
174 break;
175 default:
176 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
177 break;
178 }
179 }
180
181 static void
gnc_billterm_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)182 gnc_billterm_set_property (GObject *object,
183 guint prop_id,
184 const GValue *value,
185 GParamSpec *pspec)
186 {
187 GncBillTerm *bt;
188
189 g_return_if_fail(GNC_IS_BILLTERM(object));
190
191 bt = GNC_BILLTERM(object);
192 g_assert (qof_instance_get_editlevel(bt));
193
194 switch (prop_id)
195 {
196 case PROP_NAME:
197 gncBillTermSetName(bt, g_value_get_string(value));
198 break;
199 default:
200 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
201 break;
202 }
203 }
204
205 /** Returns a list of my type of object which refers to an object. For example, when called as
206 qof_instance_get_typed_referring_object_list(taxtable, account);
207 it will return the list of taxtables which refer to a specific account. The result should be the
208 same regardless of which taxtable object is used. The list must be freed by the caller but the
209 objects on the list must not.
210 */
211 static GList*
impl_get_typed_referring_object_list(const QofInstance * inst,const QofInstance * ref)212 impl_get_typed_referring_object_list(const QofInstance* inst, const QofInstance* ref)
213 {
214 /* Bill term doesn't refer to anything except other billterms */
215 return NULL;
216 }
217
218 static void
gnc_billterm_class_init(GncBillTermClass * klass)219 gnc_billterm_class_init (GncBillTermClass *klass)
220 {
221 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
222 QofInstanceClass* qof_class = QOF_INSTANCE_CLASS(klass);
223
224 gobject_class->dispose = gnc_billterm_dispose;
225 gobject_class->finalize = gnc_billterm_finalize;
226 gobject_class->set_property = gnc_billterm_set_property;
227 gobject_class->get_property = gnc_billterm_get_property;
228
229 qof_class->get_display_name = NULL;
230 qof_class->refers_to_object = NULL;
231 qof_class->get_typed_referring_object_list = impl_get_typed_referring_object_list;
232
233 g_object_class_install_property
234 (gobject_class,
235 PROP_NAME,
236 g_param_spec_string ("name",
237 "BillTerm Name",
238 "The bill term name is an arbitrary string "
239 "assigned by the user. It is intended to "
240 "a short, 10 to 30 character long string "
241 "that is displayed by the GUI as the "
242 "billterm mnemonic.",
243 NULL,
244 G_PARAM_READWRITE));
245 }
246
247 /* Create/Destroy Functions */
gncBillTermCreate(QofBook * book)248 GncBillTerm * gncBillTermCreate (QofBook *book)
249 {
250 GncBillTerm *term;
251 if (!book) return NULL;
252
253 term = g_object_new (GNC_TYPE_BILLTERM, NULL);
254 qof_instance_init_data(&term->inst, _GNC_MOD_NAME, book);
255 term->name = CACHE_INSERT ("");
256 term->desc = CACHE_INSERT ("");
257 term->discount = gnc_numeric_zero ();
258 addObj (term);
259 qof_event_gen (&term->inst, QOF_EVENT_CREATE, NULL);
260 return term;
261 }
262
gncBillTermDestroy(GncBillTerm * term)263 void gncBillTermDestroy (GncBillTerm *term)
264 {
265 gchar guidstr[GUID_ENCODING_LENGTH+1];
266 if (!term) return;
267 guid_to_string_buff(qof_instance_get_guid(&term->inst),guidstr);
268 DEBUG("destroying bill term %s (%p)", guidstr, term);
269 qof_instance_set_destroying(term, TRUE);
270 qof_instance_set_dirty (&term->inst);
271 gncBillTermCommitEdit (term);
272 }
273
gncBillTermFree(GncBillTerm * term)274 static void gncBillTermFree (GncBillTerm *term)
275 {
276 GncBillTerm *child;
277 GList *list;
278
279 if (!term) return;
280
281 qof_event_gen (&term->inst, QOF_EVENT_DESTROY, NULL);
282 CACHE_REMOVE (term->name);
283 CACHE_REMOVE (term->desc);
284 remObj (term);
285
286 if (!qof_instance_get_destroying(term))
287 PERR("free a billterm without do_free set!");
288
289 /* disconnect from parent */
290 if (term->parent)
291 gncBillTermRemoveChild(term->parent, term);
292
293 /* disconnect from the children */
294 for (list = term->children; list; list = list->next)
295 {
296 child = list->data;
297 gncBillTermSetParent(child, NULL);
298 }
299 g_list_free(term->children);
300
301 /* qof_instance_release(&term->inst); */
302 g_object_unref (term);
303 }
304
305 /* ============================================================== */
306 /* Set Functions */
307
gncBillTermSetName(GncBillTerm * term,const char * name)308 void gncBillTermSetName (GncBillTerm *term, const char *name)
309 {
310 if (!term || !name) return;
311 SET_STR (term, term->name, name);
312 mark_term (term);
313 maybe_resort_list (term);
314 gncBillTermCommitEdit (term);
315 }
316
gncBillTermSetDescription(GncBillTerm * term,const char * desc)317 void gncBillTermSetDescription (GncBillTerm *term, const char *desc)
318 {
319 if (!term || !desc) return;
320 SET_STR (term, term->desc, desc);
321 mark_term (term);
322 maybe_resort_list (term);
323 gncBillTermCommitEdit (term);
324 }
325
gncBillTermSetType(GncBillTerm * term,GncBillTermType type)326 void gncBillTermSetType (GncBillTerm *term, GncBillTermType type)
327 {
328 if (!term) return;
329 if (term->type == type) return;
330 gncBillTermBeginEdit (term);
331 term->type = type;
332 mark_term (term);
333 gncBillTermCommitEdit (term);
334 }
335
336 /** \brief Convert bill term types from text. */
FROM_STRING_FUNC(GncBillTermType,ENUM_TERMS_TYPE)337 FROM_STRING_FUNC(GncBillTermType, ENUM_TERMS_TYPE)
338
339 static
340 void qofBillTermSetType (GncBillTerm *term, const char *type_label)
341 {
342 GncBillTermType type;
343
344 type = GncBillTermTypefromString(type_label);
345 gncBillTermSetType(term, type);
346 }
347
gncBillTermSetDueDays(GncBillTerm * term,gint days)348 void gncBillTermSetDueDays (GncBillTerm *term, gint days)
349 {
350 if (!term) return;
351 if (term->due_days == days) return;
352 gncBillTermBeginEdit (term);
353 term->due_days = days;
354 mark_term (term);
355 gncBillTermCommitEdit (term);
356 }
357
gncBillTermSetDiscountDays(GncBillTerm * term,gint days)358 void gncBillTermSetDiscountDays (GncBillTerm *term, gint days)
359 {
360 if (!term) return;
361 if (term->disc_days == days) return;
362 gncBillTermBeginEdit (term);
363 term->disc_days = days;
364 mark_term (term);
365 gncBillTermCommitEdit (term);
366 }
367
gncBillTermSetDiscount(GncBillTerm * term,gnc_numeric discount)368 void gncBillTermSetDiscount (GncBillTerm *term, gnc_numeric discount)
369 {
370 if (!term) return;
371 if (gnc_numeric_eq (term->discount, discount)) return;
372 gncBillTermBeginEdit (term);
373 term->discount = discount;
374 mark_term (term);
375 gncBillTermCommitEdit (term);
376 }
377
gncBillTermSetCutoff(GncBillTerm * term,gint cutoff)378 void gncBillTermSetCutoff (GncBillTerm *term, gint cutoff)
379 {
380 if (!term) return;
381 if (term->cutoff == cutoff) return;
382 gncBillTermBeginEdit (term);
383 term->cutoff = cutoff;
384 mark_term (term);
385 gncBillTermCommitEdit (term);
386 }
387
388 /* XXX this doesn't seem right. If the parent/child relationship
389 * is a doubly-linked list, then there shouldn't be separate set-parent,
390 * set-child routines, else misuse of the routines will goof up
391 * relationships. These ops should be atomic, I think.
392 */
gncBillTermSetParent(GncBillTerm * term,GncBillTerm * parent)393 void gncBillTermSetParent (GncBillTerm *term, GncBillTerm *parent)
394 {
395 if (!term) return;
396 gncBillTermBeginEdit (term);
397 if (term->parent)
398 gncBillTermRemoveChild(term->parent, term);
399 term->parent = parent;
400 if (parent)
401 gncBillTermAddChild(parent, term);
402 term->refcount = 0;
403 if ( parent != NULL )
404 {
405 gncBillTermMakeInvisible (term);
406 }
407 mark_term (term);
408 gncBillTermCommitEdit (term);
409 }
410
gncBillTermSetChild(GncBillTerm * term,GncBillTerm * child)411 void gncBillTermSetChild (GncBillTerm *term, GncBillTerm *child)
412 {
413 if (!term) return;
414 gncBillTermBeginEdit (term);
415 term->child = child;
416 mark_term (term);
417 gncBillTermCommitEdit (term);
418 }
419
gncBillTermIncRef(GncBillTerm * term)420 void gncBillTermIncRef (GncBillTerm *term)
421 {
422 if (!term) return;
423 if (term->parent || term->invisible) return; /* children dont need refcounts */
424 gncBillTermBeginEdit (term);
425 term->refcount++;
426 mark_term (term);
427 gncBillTermCommitEdit (term);
428 }
429
gncBillTermDecRef(GncBillTerm * term)430 void gncBillTermDecRef (GncBillTerm *term)
431 {
432 if (!term) return;
433 if (term->parent || term->invisible) return; /* children dont need refcounts */
434 g_return_if_fail (term->refcount >= 1);
435 gncBillTermBeginEdit (term);
436 term->refcount--;
437 mark_term (term);
438 gncBillTermCommitEdit (term);
439 }
440
gncBillTermSetRefcount(GncBillTerm * term,gint64 refcount)441 void gncBillTermSetRefcount (GncBillTerm *term, gint64 refcount)
442 {
443 if (!term) return;
444 gncBillTermBeginEdit (term);
445 term->refcount = refcount;
446 mark_term (term);
447 gncBillTermCommitEdit (term);
448 }
449
gncBillTermMakeInvisible(GncBillTerm * term)450 void gncBillTermMakeInvisible (GncBillTerm *term)
451 {
452 if (!term) return;
453 gncBillTermBeginEdit (term);
454 term->invisible = TRUE;
455 remObj (term);
456 mark_term (term);
457 gncBillTermCommitEdit (term);
458 }
459
gncBillTermChanged(GncBillTerm * term)460 void gncBillTermChanged (GncBillTerm *term)
461 {
462 if (!term) return;
463 term->child = NULL;
464 }
465
gncBillTermBeginEdit(GncBillTerm * term)466 void gncBillTermBeginEdit (GncBillTerm *term)
467 {
468 qof_begin_edit(&term->inst);
469 }
470
gncBillTermOnError(QofInstance * inst,QofBackendError errcode)471 static void gncBillTermOnError (QofInstance *inst, QofBackendError errcode)
472 {
473 PERR("BillTerm QofBackend Failure: %d", errcode);
474 gnc_engine_signal_commit_error( errcode );
475 }
476
bill_free(QofInstance * inst)477 static void bill_free (QofInstance *inst)
478 {
479 GncBillTerm *term = (GncBillTerm *) inst;
480 gncBillTermFree(term);
481 }
482
on_done(QofInstance * inst)483 static void on_done (QofInstance *inst) {}
484
gncBillTermCommitEdit(GncBillTerm * term)485 void gncBillTermCommitEdit (GncBillTerm *term)
486 {
487 if (!qof_commit_edit (QOF_INSTANCE(term))) return;
488 qof_commit_edit_part2 (&term->inst, gncBillTermOnError,
489 on_done, bill_free);
490 }
491
492 /* Get Functions */
493
gncBillTermLookupByName(QofBook * book,const char * name)494 GncBillTerm *gncBillTermLookupByName (QofBook *book, const char *name)
495 {
496 GList *list = gncBillTermGetTerms (book);
497
498 for ( ; list; list = list->next)
499 {
500 GncBillTerm *term = list->data;
501 if (!g_strcmp0 (term->name, name))
502 return list->data;
503 }
504 return NULL;
505 }
506
gncBillTermGetTerms(QofBook * book)507 GList * gncBillTermGetTerms (QofBook *book)
508 {
509 struct _book_info *bi;
510 if (!book) return NULL;
511
512 bi = qof_book_get_data (book, _GNC_MOD_NAME);
513 return bi->terms;
514 }
515
gncBillTermGetName(const GncBillTerm * term)516 const char *gncBillTermGetName (const GncBillTerm *term)
517 {
518 if (!term) return NULL;
519 return term->name;
520 }
521
gncBillTermGetDescription(const GncBillTerm * term)522 const char *gncBillTermGetDescription (const GncBillTerm *term)
523 {
524 if (!term) return NULL;
525 return term->desc;
526 }
527
gncBillTermGetType(const GncBillTerm * term)528 GncBillTermType gncBillTermGetType (const GncBillTerm *term)
529 {
530 if (!term) return 0;
531 return term->type;
532 }
533
534 /** \brief Convert bill term types to text. */
AS_STRING_FUNC(GncBillTermType,ENUM_TERMS_TYPE)535 AS_STRING_FUNC(GncBillTermType, ENUM_TERMS_TYPE)
536
537 static
538 const char* qofBillTermGetType (const GncBillTerm *term)
539 {
540 if (!term)
541 {
542 return NULL;
543 }
544 return GncBillTermTypeasString(term->type);
545 }
546
gncBillTermGetDueDays(const GncBillTerm * term)547 gint gncBillTermGetDueDays (const GncBillTerm *term)
548 {
549 if (!term) return 0;
550 return term->due_days;
551 }
552
gncBillTermGetDiscountDays(const GncBillTerm * term)553 gint gncBillTermGetDiscountDays (const GncBillTerm *term)
554 {
555 if (!term) return 0;
556 return term->disc_days;
557 }
558
gncBillTermGetDiscount(const GncBillTerm * term)559 gnc_numeric gncBillTermGetDiscount (const GncBillTerm *term)
560 {
561 if (!term) return gnc_numeric_zero ();
562 return term->discount;
563 }
564
gncBillTermGetCutoff(const GncBillTerm * term)565 gint gncBillTermGetCutoff (const GncBillTerm *term)
566 {
567 if (!term) return 0;
568 return term->cutoff;
569 }
570
gncBillTermCopy(const GncBillTerm * term)571 static GncBillTerm *gncBillTermCopy (const GncBillTerm *term)
572 {
573 GncBillTerm *t;
574
575 if (!term) return NULL;
576 t = gncBillTermCreate (qof_instance_get_book(term));
577
578 gncBillTermBeginEdit(t);
579
580 gncBillTermSetName (t, term->name);
581 gncBillTermSetDescription (t, term->desc);
582
583 t->type = term->type;
584 t->due_days = term->due_days;
585 t->disc_days = term->disc_days;
586 t->discount = term->discount;
587 t->cutoff = term->cutoff;
588
589 mark_term (t);
590 gncBillTermCommitEdit(t);
591
592 return t;
593 }
594
gncBillTermReturnChild(GncBillTerm * term,gboolean make_new)595 GncBillTerm *gncBillTermReturnChild (GncBillTerm *term, gboolean make_new)
596 {
597 GncBillTerm *child = NULL;
598
599 if (!term) return NULL;
600 if (term->child) return term->child;
601 if (term->parent || term->invisible) return term;
602 if (make_new)
603 {
604 child = gncBillTermCopy (term);
605 gncBillTermSetChild (term, child);
606 gncBillTermSetParent (child, term);
607 }
608 return child;
609 }
610
gncBillTermGetParent(const GncBillTerm * term)611 GncBillTerm *gncBillTermGetParent (const GncBillTerm *term)
612 {
613 if (!term) return NULL;
614 return term->parent;
615 }
616
gncBillTermGetRefcount(const GncBillTerm * term)617 gint64 gncBillTermGetRefcount (const GncBillTerm *term)
618 {
619 if (!term) return 0;
620 return term->refcount;
621 }
622
gncBillTermGetInvisible(const GncBillTerm * term)623 gboolean gncBillTermGetInvisible (const GncBillTerm *term)
624 {
625 if (!term) return FALSE;
626 return term->invisible;
627 }
628
gncBillTermCompare(const GncBillTerm * a,const GncBillTerm * b)629 int gncBillTermCompare (const GncBillTerm *a, const GncBillTerm *b)
630 {
631 int ret;
632
633 if (!a && !b) return 0;
634 if (!a) return -1;
635 if (!b) return 1;
636
637 ret = g_strcmp0 (a->name, b->name);
638 if (ret) return ret;
639
640 return g_strcmp0 (a->desc, b->desc);
641 }
642
gncBillTermEqual(const GncBillTerm * a,const GncBillTerm * b)643 gboolean gncBillTermEqual(const GncBillTerm *a, const GncBillTerm *b)
644 {
645 if (a == NULL && b == NULL) return TRUE;
646 if (a == NULL || b == NULL) return FALSE;
647
648 g_return_val_if_fail(GNC_IS_BILLTERM(a), FALSE);
649 g_return_val_if_fail(GNC_IS_BILLTERM(b), FALSE);
650
651 if (g_strcmp0(a->name, b->name) != 0)
652 {
653 PWARN("Names differ: %s vs %s", a->name, b->name);
654 return FALSE;
655 }
656
657 if (g_strcmp0(a->desc, b->desc) != 0)
658 {
659 PWARN("Descriptions differ: %s vs %s", a->desc, b->desc);
660 return FALSE;
661 }
662
663 if (a->type != b->type)
664 {
665 PWARN("Types differ");
666 return FALSE;
667 }
668
669 if (a->due_days != b->due_days)
670 {
671 PWARN("Due days differ: %d vs %d", a->due_days, b->due_days);
672 return FALSE;
673 }
674
675 if (a->disc_days != b->disc_days)
676 {
677 PWARN("Discount days differ: %d vs %d", a->disc_days, b->disc_days);
678 return FALSE;
679 }
680
681 if (!gnc_numeric_equal(a->discount, b->discount))
682 {
683 PWARN("Discounts differ");
684 return FALSE;
685 }
686
687 if (a->cutoff != b->cutoff)
688 {
689 PWARN("Cutoffs differ: %d vs %d", a->cutoff, b->cutoff);
690 return FALSE;
691 }
692
693 if (a->invisible != b->invisible)
694 {
695 PWARN("Invisible flags differ");
696 return FALSE;
697 }
698
699 // gint64 refcount;
700 // GncBillTerm * parent; /* if non-null, we are an immutable child */
701 // GncBillTerm * child; /* if non-null, we have not changed */
702 // GList * children; /* list of children for disconnection */
703
704 return TRUE;
705 }
706
gncBillTermIsFamily(const GncBillTerm * a,const GncBillTerm * b)707 gboolean gncBillTermIsFamily (const GncBillTerm *a, const GncBillTerm *b)
708 {
709 if (!gncBillTermCompare (a, b))
710 return TRUE;
711 else
712 return FALSE;
713 }
714
gncBillTermIsDirty(const GncBillTerm * term)715 gboolean gncBillTermIsDirty (const GncBillTerm *term)
716 {
717 if (!term) return FALSE;
718 return qof_instance_get_dirty_flag(term);
719 }
720
721 /********************************************************/
722 /* functions to compute dates from Bill Terms */
723
724 #define SECS_PER_DAY 86400
725
726 /* Based on the post date and a proximo type, compute the month and
727 * year this is due. The actual day is filled in below.
728 *
729 * A proximo billing term has multiple parameters:
730 * * due day: day of the month the invoice/bill will be due
731 * * cutoff: day of the month used to decide if the due date will be
732 * in the next month or in the month thereafter. This can be
733 * a negative number in which case the cutoff date is relative
734 * to the end of the month and counting backwards.
735 * Eg: cutoff = -3 would mean 25 in February or 28 in June
736 *
737 * How does it work:
738 * Assume cutoff = 19 and due day = 20
739 *
740 * * Example 1 post date = 14-06-2010 (European date format)
741 * 14 is less than the cutoff of 19, so the due date will be in the next
742 * month. Since the due day is set to 20, the due date will be
743 * 20-07-2010
744 *
745 * * Example 2 post date = 22-06-2010 (European date format)
746 * 22 is more than the cutoff of 19, so the due date will be in the month
747 * after next month. Since the due day is set to 20, the due date will be
748 * 20-02-2010
749 *
750 */
751 static void
compute_monthyear(const GncBillTerm * term,time64 post_date,int * month,int * year)752 compute_monthyear (const GncBillTerm *term, time64 post_date,
753 int *month, int *year)
754 {
755 int iday, imonth, iyear;
756 struct tm tm;
757 int cutoff = term->cutoff;
758
759 g_return_if_fail (term->type == GNC_TERM_TYPE_PROXIMO);
760 gnc_localtime_r (&post_date, &tm);
761 iday = tm.tm_mday;
762 imonth = tm.tm_mon + 1;
763 iyear = tm.tm_year + 1900;
764
765 if (cutoff <= 0)
766 cutoff += gnc_date_get_last_mday (imonth - 1, iyear);
767
768 if (iday <= cutoff)
769 {
770 /* We apply this to next month */
771 imonth++;
772 }
773 else
774 {
775 /* We apply to the following month */
776 imonth += 2;
777 }
778
779 if (imonth > 12)
780 {
781 iyear++;
782 imonth -= 12;
783 }
784
785 if (month) *month = imonth;
786 if (year) *year = iyear;
787 }
788
789 /* There are two types of billing terms:
790 *
791 * Type DAYS defines a due date to be a fixed number of days passed the post
792 * date. This is a straightforward calculation.
793 *
794 * The other type PROXIMO defines the due date as a fixed day of the month
795 * (like always the 15th of the month). The proximo algorithm determines which
796 * month based on the cutoff day and the post date. See above for a more
797 * detailed explanation of proximo.
798 */
799
800 static time64
compute_time(const GncBillTerm * term,time64 post_date,int days)801 compute_time (const GncBillTerm *term, time64 post_date, int days)
802 {
803 time64 res = post_date;
804 int day, month, year;
805
806 switch (term->type)
807 {
808 case GNC_TERM_TYPE_DAYS:
809 res += (SECS_PER_DAY * days);
810 break;
811 case GNC_TERM_TYPE_PROXIMO:
812 compute_monthyear (term, post_date, &month, &year);
813 day = gnc_date_get_last_mday (month - 1, year);
814 if (days < day)
815 day = days;
816 res = gnc_dmy2time64 (day, month, year);
817 break;
818 }
819 return res;
820 }
821
822 time64
gncBillTermComputeDueDate(const GncBillTerm * term,time64 post_date)823 gncBillTermComputeDueDate (const GncBillTerm *term, time64 post_date)
824 {
825 if (!term) return post_date;
826 return compute_time (term, post_date, term->due_days);
827 }
828
829 /* Package-Private functions */
830
_gncBillTermCreate(QofBook * book)831 static void _gncBillTermCreate (QofBook *book)
832 {
833 struct _book_info *bi;
834
835 if (!book) return;
836
837 bi = g_new0 (struct _book_info, 1);
838 qof_book_set_data (book, _GNC_MOD_NAME, bi);
839 }
840
_gncBillTermDestroy(QofBook * book)841 static void _gncBillTermDestroy (QofBook *book)
842 {
843 struct _book_info *bi;
844
845 if (!book) return;
846
847 bi = qof_book_get_data (book, _GNC_MOD_NAME);
848
849 g_list_free (bi->terms);
850 g_free (bi);
851 }
852
853 static QofObject gncBillTermDesc =
854 {
855 DI(.interface_version = ) QOF_OBJECT_VERSION,
856 DI(.e_type = ) _GNC_MOD_NAME,
857 DI(.type_label = ) "Billing Term",
858 DI(.create = ) (gpointer)gncBillTermCreate,
859 DI(.book_begin = ) _gncBillTermCreate,
860 DI(.book_end = ) _gncBillTermDestroy,
861 DI(.is_dirty = ) qof_collection_is_dirty,
862 DI(.mark_clean = ) qof_collection_mark_clean,
863 DI(.foreach = ) qof_collection_foreach,
864 DI(.printable = ) NULL,
865 DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
866 };
867
gncBillTermRegister(void)868 gboolean gncBillTermRegister (void)
869 {
870 static QofParam params[] =
871 {
872 { GNC_BILLTERM_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetName, (QofSetterFunc)gncBillTermSetName },
873 { GNC_BILLTERM_DESC, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetDescription, (QofSetterFunc)gncBillTermSetDescription },
874 { GNC_BILLTERM_TYPE, QOF_TYPE_STRING, (QofAccessFunc)qofBillTermGetType, (QofSetterFunc)qofBillTermSetType },
875 { GNC_BILLTERM_DUEDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDueDays, (QofSetterFunc)gncBillTermSetDueDays },
876 { GNC_BILLTERM_DISCDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDiscountDays, (QofSetterFunc)gncBillTermSetDiscountDays },
877 { GNC_BILLTERM_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount, (QofSetterFunc)gncBillTermSetDiscount },
878 { GNC_BILLTERM_CUTOFF, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetCutoff, (QofSetterFunc)gncBillTermSetCutoff },
879 { GNC_BILLTERM_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncBillTermGetRefcount, NULL },
880 { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
881 { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
882 { NULL },
883 };
884
885 qof_class_register (_GNC_MOD_NAME, (QofSortFunc)gncBillTermCompare, params);
886
887 return qof_object_register (&gncBillTermDesc);
888 }
889