Line data Source code
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 libgnucash/engine/TaxTableBillTermImmutability.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 :
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 60 : mark_term (GncBillTerm *term)
88 : {
89 60 : qof_instance_set_dirty(&term->inst);
90 60 : qof_event_gen (&term->inst, QOF_EVENT_MODIFY, NULL);
91 60 : }
92 :
93 20 : static inline void maybe_resort_list (GncBillTerm *term)
94 : {
95 : struct _book_info *bi;
96 :
97 20 : if (term->parent || term->invisible) return;
98 20 : bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
99 20 : bi->terms = g_list_sort (bi->terms, (GCompareFunc)gncBillTermCompare);
100 : }
101 :
102 10 : static inline void addObj (GncBillTerm *term)
103 : {
104 : struct _book_info *bi;
105 10 : bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
106 10 : bi->terms = g_list_insert_sorted (bi->terms, term,
107 : (GCompareFunc)gncBillTermCompare);
108 10 : }
109 :
110 5 : static inline void remObj (GncBillTerm *term)
111 : {
112 : struct _book_info *bi;
113 5 : bi = qof_book_get_data (qof_instance_get_book(term), _GNC_MOD_NAME);
114 5 : bi->terms = g_list_remove (bi->terms, term);
115 5 : }
116 :
117 : static inline void
118 5 : gncBillTermAddChild (GncBillTerm *table, GncBillTerm *child)
119 : {
120 5 : g_return_if_fail(qof_instance_get_destroying(table) == FALSE);
121 5 : table->children = g_list_prepend(table->children, child);
122 : }
123 :
124 : static inline void
125 0 : gncBillTermRemoveChild (GncBillTerm *table, GncBillTerm *child)
126 : {
127 0 : if (qof_instance_get_destroying(table)) return;
128 0 : 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 29 : G_DEFINE_TYPE(GncBillTerm, gnc_billterm, QOF_TYPE_INSTANCE)
141 :
142 : static void
143 10 : gnc_billterm_init(GncBillTerm* bt)
144 : {
145 10 : }
146 :
147 : static void
148 0 : gnc_billterm_dispose(GObject *btp)
149 : {
150 0 : G_OBJECT_CLASS(gnc_billterm_parent_class)->dispose(btp);
151 0 : }
152 :
153 : static void
154 0 : gnc_billterm_finalize(GObject* btp)
155 : {
156 0 : G_OBJECT_CLASS(gnc_billterm_parent_class)->finalize(btp);
157 0 : }
158 :
159 : static void
160 0 : gnc_billterm_get_property (GObject *object,
161 : guint prop_id,
162 : GValue *value,
163 : GParamSpec *pspec)
164 : {
165 : GncBillTerm *bt;
166 :
167 0 : g_return_if_fail(GNC_IS_BILLTERM(object));
168 :
169 0 : bt = GNC_BILLTERM(object);
170 0 : switch (prop_id)
171 : {
172 0 : case PROP_NAME:
173 0 : g_value_set_string(value, bt->name);
174 0 : break;
175 0 : default:
176 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
177 0 : break;
178 : }
179 : }
180 :
181 : static void
182 0 : gnc_billterm_set_property (GObject *object,
183 : guint prop_id,
184 : const GValue *value,
185 : GParamSpec *pspec)
186 : {
187 : GncBillTerm *bt;
188 :
189 0 : g_return_if_fail(GNC_IS_BILLTERM(object));
190 :
191 0 : bt = GNC_BILLTERM(object);
192 0 : g_assert (qof_instance_get_editlevel(bt));
193 :
194 0 : switch (prop_id)
195 : {
196 0 : case PROP_NAME:
197 0 : gncBillTermSetName(bt, g_value_get_string(value));
198 0 : break;
199 0 : default:
200 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
201 0 : 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*
212 0 : 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 0 : return NULL;
216 : }
217 :
218 : static void
219 3 : gnc_billterm_class_init (GncBillTermClass *klass)
220 : {
221 3 : GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
222 3 : QofInstanceClass* qof_class = QOF_INSTANCE_CLASS(klass);
223 :
224 3 : gobject_class->dispose = gnc_billterm_dispose;
225 3 : gobject_class->finalize = gnc_billterm_finalize;
226 3 : gobject_class->set_property = gnc_billterm_set_property;
227 3 : gobject_class->get_property = gnc_billterm_get_property;
228 :
229 3 : qof_class->get_display_name = NULL;
230 3 : qof_class->refers_to_object = NULL;
231 3 : qof_class->get_typed_referring_object_list = impl_get_typed_referring_object_list;
232 :
233 3 : 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 3 : }
246 :
247 : /* Create/Destroy Functions */
248 10 : GncBillTerm * gncBillTermCreate (QofBook *book)
249 : {
250 : GncBillTerm *term;
251 10 : if (!book) return NULL;
252 :
253 10 : term = g_object_new (GNC_TYPE_BILLTERM, NULL);
254 10 : qof_instance_init_data(&term->inst, _GNC_MOD_NAME, book);
255 10 : term->name = CACHE_INSERT ("");
256 10 : term->desc = CACHE_INSERT ("");
257 10 : term->discount = gnc_numeric_zero ();
258 10 : addObj (term);
259 10 : qof_event_gen (&term->inst, QOF_EVENT_CREATE, NULL);
260 10 : return term;
261 : }
262 :
263 0 : void gncBillTermDestroy (GncBillTerm *term)
264 : {
265 : gchar guidstr[GUID_ENCODING_LENGTH+1];
266 0 : if (!term) return;
267 0 : guid_to_string_buff(qof_instance_get_guid(&term->inst),guidstr);
268 0 : DEBUG("destroying bill term %s (%p)", guidstr, term);
269 0 : qof_instance_set_destroying(term, TRUE);
270 0 : qof_instance_set_dirty (&term->inst);
271 0 : gncBillTermCommitEdit (term);
272 : }
273 :
274 0 : static void gncBillTermFree (GncBillTerm *term)
275 : {
276 : GncBillTerm *child;
277 : GList *list;
278 :
279 0 : if (!term) return;
280 :
281 0 : qof_event_gen (&term->inst, QOF_EVENT_DESTROY, NULL);
282 0 : CACHE_REMOVE (term->name);
283 0 : CACHE_REMOVE (term->desc);
284 0 : remObj (term);
285 :
286 0 : if (!qof_instance_get_destroying(term))
287 0 : PERR("free a billterm without do_free set!");
288 :
289 : /* disconnect from parent */
290 0 : if (term->parent)
291 0 : gncBillTermRemoveChild(term->parent, term);
292 :
293 : /* disconnect from the children */
294 0 : for (list = term->children; list; list = list->next)
295 : {
296 0 : child = list->data;
297 0 : gncBillTermSetParent(child, NULL);
298 : }
299 0 : g_list_free(term->children);
300 :
301 : /* qof_instance_release(&term->inst); */
302 0 : g_object_unref (term);
303 : }
304 :
305 : /* ============================================================== */
306 : /* Set Functions */
307 :
308 10 : void gncBillTermSetName (GncBillTerm *term, const char *name)
309 : {
310 10 : if (!term || !name) return;
311 10 : SET_STR (term, term->name, name);
312 10 : mark_term (term);
313 10 : maybe_resort_list (term);
314 10 : gncBillTermCommitEdit (term);
315 : }
316 :
317 10 : void gncBillTermSetDescription (GncBillTerm *term, const char *desc)
318 : {
319 10 : if (!term || !desc) return;
320 10 : SET_STR (term, term->desc, desc);
321 10 : mark_term (term);
322 10 : maybe_resort_list (term);
323 10 : gncBillTermCommitEdit (term);
324 : }
325 :
326 5 : void gncBillTermSetType (GncBillTerm *term, GncBillTermType type)
327 : {
328 5 : if (!term) return;
329 5 : if (term->type == type) return;
330 5 : gncBillTermBeginEdit (term);
331 5 : term->type = type;
332 5 : mark_term (term);
333 5 : gncBillTermCommitEdit (term);
334 : }
335 :
336 : /** \brief Convert bill term types from text. */
337 0 : FROM_STRING_FUNC(GncBillTermType, ENUM_TERMS_TYPE)
338 :
339 : static
340 0 : void qofBillTermSetType (GncBillTerm *term, const char *type_label)
341 : {
342 : GncBillTermType type;
343 :
344 0 : type = GncBillTermTypefromString(type_label);
345 0 : gncBillTermSetType(term, type);
346 0 : }
347 :
348 5 : void gncBillTermSetDueDays (GncBillTerm *term, gint days)
349 : {
350 5 : if (!term) return;
351 5 : if (term->due_days == days) return;
352 5 : gncBillTermBeginEdit (term);
353 5 : term->due_days = days;
354 5 : mark_term (term);
355 5 : gncBillTermCommitEdit (term);
356 : }
357 :
358 0 : void gncBillTermSetDiscountDays (GncBillTerm *term, gint days)
359 : {
360 0 : if (!term) return;
361 0 : if (term->disc_days == days) return;
362 0 : gncBillTermBeginEdit (term);
363 0 : term->disc_days = days;
364 0 : mark_term (term);
365 0 : gncBillTermCommitEdit (term);
366 : }
367 :
368 0 : void gncBillTermSetDiscount (GncBillTerm *term, gnc_numeric discount)
369 : {
370 0 : if (!term) return;
371 0 : if (gnc_numeric_eq (term->discount, discount)) return;
372 0 : gncBillTermBeginEdit (term);
373 0 : term->discount = discount;
374 0 : mark_term (term);
375 0 : gncBillTermCommitEdit (term);
376 : }
377 :
378 0 : void gncBillTermSetCutoff (GncBillTerm *term, gint cutoff)
379 : {
380 0 : if (!term) return;
381 0 : if (term->cutoff == cutoff) return;
382 0 : gncBillTermBeginEdit (term);
383 0 : term->cutoff = cutoff;
384 0 : mark_term (term);
385 0 : 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 : */
393 5 : void gncBillTermSetParent (GncBillTerm *term, GncBillTerm *parent)
394 : {
395 5 : if (!term) return;
396 5 : gncBillTermBeginEdit (term);
397 5 : if (term->parent)
398 0 : gncBillTermRemoveChild(term->parent, term);
399 5 : term->parent = parent;
400 5 : if (parent)
401 5 : gncBillTermAddChild(parent, term);
402 5 : term->refcount = 0;
403 5 : if ( parent != NULL )
404 : {
405 5 : gncBillTermMakeInvisible (term);
406 : }
407 5 : mark_term (term);
408 5 : gncBillTermCommitEdit (term);
409 : }
410 :
411 5 : void gncBillTermSetChild (GncBillTerm *term, GncBillTerm *child)
412 : {
413 5 : if (!term) return;
414 5 : gncBillTermBeginEdit (term);
415 5 : term->child = child;
416 5 : mark_term (term);
417 5 : gncBillTermCommitEdit (term);
418 : }
419 :
420 44 : void gncBillTermIncRef (GncBillTerm *term)
421 : {
422 44 : if (!term) return;
423 10 : if (term->parent || term->invisible) return; /* children dont need refcounts */
424 5 : gncBillTermBeginEdit (term);
425 5 : term->refcount++;
426 5 : mark_term (term);
427 5 : gncBillTermCommitEdit (term);
428 : }
429 :
430 5 : void gncBillTermDecRef (GncBillTerm *term)
431 : {
432 5 : if (!term) return;
433 5 : if (term->parent || term->invisible) return; /* children dont need refcounts */
434 5 : g_return_if_fail (term->refcount >= 1);
435 5 : gncBillTermBeginEdit (term);
436 5 : term->refcount--;
437 5 : mark_term (term);
438 5 : gncBillTermCommitEdit (term);
439 : }
440 :
441 0 : void gncBillTermSetRefcount (GncBillTerm *term, gint64 refcount)
442 : {
443 0 : if (!term) return;
444 0 : gncBillTermBeginEdit (term);
445 0 : term->refcount = refcount;
446 0 : mark_term (term);
447 0 : gncBillTermCommitEdit (term);
448 : }
449 :
450 5 : void gncBillTermMakeInvisible (GncBillTerm *term)
451 : {
452 5 : if (!term) return;
453 5 : gncBillTermBeginEdit (term);
454 5 : term->invisible = TRUE;
455 5 : remObj (term);
456 5 : mark_term (term);
457 5 : gncBillTermCommitEdit (term);
458 : }
459 :
460 0 : void gncBillTermChanged (GncBillTerm *term)
461 : {
462 0 : if (!term) return;
463 0 : term->child = NULL;
464 : }
465 :
466 60 : void gncBillTermBeginEdit (GncBillTerm *term)
467 : {
468 60 : qof_begin_edit(&term->inst);
469 60 : }
470 :
471 0 : static void gncBillTermOnError (QofInstance *inst, QofBackendError errcode)
472 : {
473 0 : PERR("BillTerm QofBackend Failure: %d", errcode);
474 0 : gnc_engine_signal_commit_error( errcode );
475 0 : }
476 :
477 0 : static void bill_free (QofInstance *inst)
478 : {
479 0 : GncBillTerm *term = (GncBillTerm *) inst;
480 0 : gncBillTermFree(term);
481 0 : }
482 :
483 45 : static void on_done (QofInstance *inst) {}
484 :
485 60 : void gncBillTermCommitEdit (GncBillTerm *term)
486 : {
487 60 : if (!qof_commit_edit (QOF_INSTANCE(term))) return;
488 45 : qof_commit_edit_part2 (&term->inst, gncBillTermOnError,
489 : on_done, bill_free);
490 : }
491 :
492 : /* Get Functions */
493 :
494 0 : GncBillTerm *gncBillTermLookupByName (QofBook *book, const char *name)
495 : {
496 0 : GList *list = gncBillTermGetTerms (book);
497 :
498 0 : for ( ; list; list = list->next)
499 : {
500 0 : GncBillTerm *term = list->data;
501 0 : if (!g_strcmp0 (term->name, name))
502 0 : return list->data;
503 : }
504 0 : return NULL;
505 : }
506 :
507 0 : GList * gncBillTermGetTerms (QofBook *book)
508 : {
509 : struct _book_info *bi;
510 0 : if (!book) return NULL;
511 :
512 0 : bi = qof_book_get_data (book, _GNC_MOD_NAME);
513 0 : return bi->terms;
514 : }
515 :
516 0 : const char *gncBillTermGetName (const GncBillTerm *term)
517 : {
518 0 : if (!term) return NULL;
519 0 : return term->name;
520 : }
521 :
522 24 : const char *gncBillTermGetDescription (const GncBillTerm *term)
523 : {
524 24 : if (!term) return NULL;
525 3 : return term->desc;
526 : }
527 :
528 0 : GncBillTermType gncBillTermGetType (const GncBillTerm *term)
529 : {
530 0 : if (!term) return 0;
531 0 : return term->type;
532 : }
533 :
534 : /** \brief Convert bill term types to text. */
535 0 : AS_STRING_FUNC(GncBillTermType, ENUM_TERMS_TYPE)
536 :
537 : static
538 0 : const char* qofBillTermGetType (const GncBillTerm *term)
539 : {
540 0 : if (!term)
541 : {
542 0 : return NULL;
543 : }
544 0 : return GncBillTermTypeasString(term->type);
545 : }
546 :
547 0 : gint gncBillTermGetDueDays (const GncBillTerm *term)
548 : {
549 0 : if (!term) return 0;
550 0 : return term->due_days;
551 : }
552 :
553 0 : gint gncBillTermGetDiscountDays (const GncBillTerm *term)
554 : {
555 0 : if (!term) return 0;
556 0 : return term->disc_days;
557 : }
558 :
559 0 : gnc_numeric gncBillTermGetDiscount (const GncBillTerm *term)
560 : {
561 0 : if (!term) return gnc_numeric_zero ();
562 0 : return term->discount;
563 : }
564 :
565 0 : gint gncBillTermGetCutoff (const GncBillTerm *term)
566 : {
567 0 : if (!term) return 0;
568 0 : return term->cutoff;
569 : }
570 :
571 5 : static GncBillTerm *gncBillTermCopy (const GncBillTerm *term)
572 : {
573 : GncBillTerm *t;
574 :
575 5 : if (!term) return NULL;
576 5 : t = gncBillTermCreate (qof_instance_get_book(term));
577 :
578 5 : gncBillTermBeginEdit(t);
579 :
580 5 : gncBillTermSetName (t, term->name);
581 5 : gncBillTermSetDescription (t, term->desc);
582 :
583 5 : t->type = term->type;
584 5 : t->due_days = term->due_days;
585 5 : t->disc_days = term->disc_days;
586 5 : t->discount = term->discount;
587 5 : t->cutoff = term->cutoff;
588 :
589 5 : mark_term (t);
590 5 : gncBillTermCommitEdit(t);
591 :
592 5 : return t;
593 : }
594 :
595 5 : GncBillTerm *gncBillTermReturnChild (GncBillTerm *term, gboolean make_new)
596 : {
597 5 : GncBillTerm *child = NULL;
598 :
599 5 : if (!term) return NULL;
600 5 : if (term->child) return term->child;
601 5 : if (term->parent || term->invisible) return term;
602 5 : if (make_new)
603 : {
604 5 : child = gncBillTermCopy (term);
605 5 : gncBillTermSetChild (term, child);
606 5 : gncBillTermSetParent (child, term);
607 : }
608 5 : return child;
609 : }
610 :
611 0 : GncBillTerm *gncBillTermGetParent (const GncBillTerm *term)
612 : {
613 0 : if (!term) return NULL;
614 0 : return term->parent;
615 : }
616 :
617 0 : gint64 gncBillTermGetRefcount (const GncBillTerm *term)
618 : {
619 0 : if (!term) return 0;
620 0 : return term->refcount;
621 : }
622 :
623 0 : gboolean gncBillTermGetInvisible (const GncBillTerm *term)
624 : {
625 0 : if (!term) return FALSE;
626 0 : return term->invisible;
627 : }
628 :
629 31 : int gncBillTermCompare (const GncBillTerm *a, const GncBillTerm *b)
630 : {
631 : int ret;
632 :
633 31 : if (!a && !b) return 0;
634 31 : if (!a) return -1;
635 31 : if (!b) return 1;
636 :
637 31 : ret = g_strcmp0 (a->name, b->name);
638 31 : if (ret) return ret;
639 :
640 24 : return g_strcmp0 (a->desc, b->desc);
641 : }
642 :
643 5 : gboolean gncBillTermEqual(const GncBillTerm *a, const GncBillTerm *b)
644 : {
645 5 : if (a == NULL && b == NULL) return TRUE;
646 0 : if (a == NULL || b == NULL) return FALSE;
647 :
648 0 : g_return_val_if_fail(GNC_IS_BILLTERM(a), FALSE);
649 0 : g_return_val_if_fail(GNC_IS_BILLTERM(b), FALSE);
650 :
651 0 : if (g_strcmp0(a->name, b->name) != 0)
652 : {
653 0 : PWARN("Names differ: %s vs %s", a->name, b->name);
654 0 : return FALSE;
655 : }
656 :
657 0 : if (g_strcmp0(a->desc, b->desc) != 0)
658 : {
659 0 : PWARN("Descriptions differ: %s vs %s", a->desc, b->desc);
660 0 : return FALSE;
661 : }
662 :
663 0 : if (a->type != b->type)
664 : {
665 0 : PWARN("Types differ");
666 0 : return FALSE;
667 : }
668 :
669 0 : if (a->due_days != b->due_days)
670 : {
671 0 : PWARN("Due days differ: %d vs %d", a->due_days, b->due_days);
672 0 : return FALSE;
673 : }
674 :
675 0 : if (a->disc_days != b->disc_days)
676 : {
677 0 : PWARN("Discount days differ: %d vs %d", a->disc_days, b->disc_days);
678 0 : return FALSE;
679 : }
680 :
681 0 : if (!gnc_numeric_equal(a->discount, b->discount))
682 : {
683 0 : PWARN("Discounts differ");
684 0 : return FALSE;
685 : }
686 :
687 0 : if (a->cutoff != b->cutoff)
688 : {
689 0 : PWARN("Cutoffs differ: %d vs %d", a->cutoff, b->cutoff);
690 0 : return FALSE;
691 : }
692 :
693 0 : if (a->invisible != b->invisible)
694 : {
695 0 : PWARN("Invisible flags differ");
696 0 : 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 0 : return TRUE;
705 : }
706 :
707 0 : gboolean gncBillTermIsFamily (const GncBillTerm *a, const GncBillTerm *b)
708 : {
709 0 : if (!gncBillTermCompare (a, b))
710 0 : return TRUE;
711 : else
712 0 : return FALSE;
713 : }
714 :
715 0 : gboolean gncBillTermIsDirty (const GncBillTerm *term)
716 : {
717 0 : if (!term) return FALSE;
718 0 : 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-08-2010
749 : *
750 : */
751 : static void
752 0 : compute_monthyear (const GncBillTerm *term, time64 post_date,
753 : int *month, int *year)
754 : {
755 : int iday, imonth, iyear;
756 : struct tm tm;
757 0 : int cutoff = term->cutoff;
758 :
759 0 : g_return_if_fail (term->type == GNC_TERM_TYPE_PROXIMO);
760 0 : gnc_localtime_r (&post_date, &tm);
761 0 : iday = tm.tm_mday;
762 0 : imonth = tm.tm_mon + 1;
763 0 : iyear = tm.tm_year + 1900;
764 :
765 0 : if (cutoff <= 0)
766 0 : cutoff += gnc_date_get_last_mday (imonth - 1, iyear);
767 :
768 0 : if (iday <= cutoff)
769 : {
770 : /* We apply this to next month */
771 0 : imonth++;
772 : }
773 : else
774 : {
775 : /* We apply to the following month */
776 0 : imonth += 2;
777 : }
778 :
779 0 : if (imonth > 12)
780 : {
781 0 : iyear++;
782 0 : imonth -= 12;
783 : }
784 :
785 0 : if (month) *month = imonth;
786 0 : 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
801 0 : compute_time (const GncBillTerm *term, time64 post_date, int days)
802 : {
803 0 : time64 res = gnc_time64_get_day_neutral (post_date);
804 : int day, month, year;
805 :
806 0 : switch (term->type)
807 : {
808 0 : case GNC_TERM_TYPE_DAYS:
809 0 : res += (SECS_PER_DAY * days);
810 0 : res = gnc_time64_get_day_neutral (res);
811 0 : break;
812 0 : case GNC_TERM_TYPE_PROXIMO:
813 0 : compute_monthyear (term, post_date, &month, &year);
814 0 : day = gnc_date_get_last_mday (month - 1, year);
815 0 : if (days < day)
816 0 : day = days;
817 0 : res = gnc_dmy2time64_neutral (day, month, year);
818 0 : break;
819 : }
820 0 : return res;
821 : }
822 :
823 : time64
824 0 : gncBillTermComputeDueDate (const GncBillTerm *term, time64 post_date)
825 : {
826 0 : if (!term) return post_date;
827 0 : return compute_time (term, post_date, term->due_days);
828 : }
829 :
830 : /* Package-Private functions */
831 :
832 304 : static void _gncBillTermCreate (QofBook *book)
833 : {
834 : struct _book_info *bi;
835 :
836 304 : if (!book) return;
837 :
838 304 : bi = g_new0 (struct _book_info, 1);
839 304 : qof_book_set_data (book, _GNC_MOD_NAME, bi);
840 : }
841 :
842 :
843 : static void
844 0 : destroy_billterm_on_book_close (QofInstance *ent, gpointer data)
845 : {
846 0 : GncBillTerm *term = GNC_BILLTERM(ent);
847 :
848 0 : gncBillTermBeginEdit (term);
849 0 : gncBillTermDestroy (term);
850 0 : }
851 :
852 179 : static void _gncBillTermDestroy (QofBook *book)
853 : {
854 : struct _book_info *bi;
855 : QofCollection *col;
856 :
857 179 : if (!book) return;
858 :
859 179 : bi = qof_book_get_data (book, _GNC_MOD_NAME);
860 :
861 179 : col = qof_book_get_collection (book, GNC_ID_BILLTERM);
862 179 : qof_collection_foreach (col, destroy_billterm_on_book_close, NULL);
863 :
864 179 : g_list_free (bi->terms);
865 179 : g_free (bi);
866 : }
867 :
868 : static QofObject gncBillTermDesc =
869 : {
870 : DI(.interface_version = ) QOF_OBJECT_VERSION,
871 : DI(.e_type = ) _GNC_MOD_NAME,
872 : DI(.type_label = ) "Billing Term",
873 : DI(.create = ) (gpointer)gncBillTermCreate,
874 : DI(.book_begin = ) _gncBillTermCreate,
875 : DI(.book_end = ) _gncBillTermDestroy,
876 : DI(.is_dirty = ) qof_collection_is_dirty,
877 : DI(.mark_clean = ) qof_collection_mark_clean,
878 : DI(.foreach = ) qof_collection_foreach,
879 : DI(.printable = ) NULL,
880 : DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
881 : };
882 :
883 83 : gboolean gncBillTermRegister (void)
884 : {
885 : static QofParam params[] =
886 : {
887 : { GNC_BILLTERM_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetName, (QofSetterFunc)gncBillTermSetName },
888 : { GNC_BILLTERM_DESC, QOF_TYPE_STRING, (QofAccessFunc)gncBillTermGetDescription, (QofSetterFunc)gncBillTermSetDescription },
889 : { GNC_BILLTERM_TYPE, QOF_TYPE_STRING, (QofAccessFunc)qofBillTermGetType, (QofSetterFunc)qofBillTermSetType },
890 : { GNC_BILLTERM_DUEDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDueDays, (QofSetterFunc)gncBillTermSetDueDays },
891 : { GNC_BILLTERM_DISCDAYS, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetDiscountDays, (QofSetterFunc)gncBillTermSetDiscountDays },
892 : { GNC_BILLTERM_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncBillTermGetDiscount, (QofSetterFunc)gncBillTermSetDiscount },
893 : { GNC_BILLTERM_CUTOFF, QOF_TYPE_INT32, (QofAccessFunc)gncBillTermGetCutoff, (QofSetterFunc)gncBillTermSetCutoff },
894 : { GNC_BILLTERM_REFCOUNT, QOF_TYPE_INT64, (QofAccessFunc)gncBillTermGetRefcount, NULL },
895 : { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
896 : { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
897 : { NULL },
898 : };
899 :
900 83 : qof_class_register (_GNC_MOD_NAME, (QofSortFunc)gncBillTermCompare, params);
901 :
902 83 : return qof_object_register (&gncBillTermDesc);
903 : }
|