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