1 /********************************************************************\
2  * gncCustomer.c -- the Core Customer 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) 2001,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 <string.h>
33 #include <qofinstance-p.h>
34 
35 #include "gnc-commodity.h"
36 
37 #include "gncAddressP.h"
38 #include "gncBillTermP.h"
39 #include "gncInvoice.h"
40 #include "gncBusiness.h"
41 
42 #include "gncCustomer.h"
43 #include "gncCustomerP.h"
44 #include "gncJobP.h"
45 #include "gncTaxTableP.h"
46 
47 static gint cust_qof_event_handler_id = 0;
48 static void cust_handle_qof_events (QofInstance *entity, QofEventId event_type,
49                                     gpointer user_data, gpointer event_data);
50 
51 struct _gncCustomer
52 {
53     QofInstance     inst;
54 
55     /* The following fields are identical to 'vendor' */
56     const char *    id;
57     const char *    name;
58     const char *    notes;
59     GncBillTerm *   terms;
60     GncAddress *    addr;
61     gnc_commodity * currency;
62     GncTaxTable*    taxtable;
63     gboolean        taxtable_override;
64     GncTaxIncluded  taxincluded;
65     gboolean        active;
66     GList *         jobs;
67     gnc_numeric *   balance; /* cached customer balance, will not be stored */
68 
69     /* The following fields are unique to 'customer' */
70     gnc_numeric     credit;
71     gnc_numeric     discount;
72     GncAddress *    shipaddr;
73 };
74 
75 struct _gncCustomerClass
76 {
77     QofInstanceClass parent_class;
78 };
79 
80 static QofLogModule log_module = GNC_MOD_BUSINESS;
81 
82 #define _GNC_MOD_NAME        GNC_ID_CUSTOMER
83 
84 /* ============================================================== */
85 /* misc inline funcs */
86 
87 static inline void mark_customer (GncCustomer *customer);
mark_customer(GncCustomer * customer)88 void mark_customer (GncCustomer *customer)
89 {
90     qof_instance_set_dirty(&customer->inst);
91     qof_event_gen (&customer->inst, QOF_EVENT_MODIFY, NULL);
92 }
93 
94 /* ============================================================== */
95 
96 enum
97 {
98     PROP_0,
99     PROP_NAME,
100     PROP_PDF_DIRNAME,
101     PROP_LAST_POSTED,
102     PROP_PAYMENT_LAST_ACCT,
103 };
104 
105 /* GObject Initialization */
106 G_DEFINE_TYPE(GncCustomer, gnc_customer, QOF_TYPE_INSTANCE);
107 
108 static void
gnc_customer_init(GncCustomer * cust)109 gnc_customer_init(GncCustomer* cust)
110 {
111 }
112 
113 static void
gnc_customer_dispose(GObject * custp)114 gnc_customer_dispose(GObject *custp)
115 {
116     G_OBJECT_CLASS(gnc_customer_parent_class)->dispose(custp);
117 }
118 
119 static void
gnc_customer_finalize(GObject * custp)120 gnc_customer_finalize(GObject* custp)
121 {
122     G_OBJECT_CLASS(gnc_customer_parent_class)->finalize(custp);
123 }
124 
125 static void
gnc_customer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)126 gnc_customer_get_property (GObject         *object,
127                            guint            prop_id,
128                            GValue          *value,
129                            GParamSpec      *pspec)
130 {
131     GncCustomer *cust;
132     gchar *key;
133     g_return_if_fail(GNC_IS_CUSTOMER(object));
134 
135     cust = GNC_CUSTOMER(object);
136     switch (prop_id)
137     {
138     case PROP_NAME:
139         g_value_set_string(value, cust->name);
140         break;
141     case PROP_PDF_DIRNAME:
142         qof_instance_get_kvp (QOF_INSTANCE (cust), value, 1, OWNER_EXPORT_PDF_DIRNAME);
143         break;
144     case PROP_LAST_POSTED:
145         qof_instance_get_kvp (QOF_INSTANCE (cust), value, 1, LAST_POSTED_TO_ACCT);
146         break;
147     case PROP_PAYMENT_LAST_ACCT:
148         qof_instance_get_kvp (QOF_INSTANCE (cust), value, 2, GNC_PAYMENT, GNC_LAST_ACCOUNT);
149         break;
150     default:
151         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
152         break;
153     }
154 }
155 
156 static void
gnc_customer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)157 gnc_customer_set_property (GObject         *object,
158                            guint            prop_id,
159                            const GValue    *value,
160                            GParamSpec      *pspec)
161 {
162     GncCustomer *cust;
163     gchar *key;
164 
165     g_return_if_fail(GNC_IS_CUSTOMER(object));
166 
167     cust = GNC_CUSTOMER(object);
168     g_assert (qof_instance_get_editlevel(cust));
169 
170     switch (prop_id)
171     {
172     case PROP_NAME:
173         gncCustomerSetName(cust, g_value_get_string(value));
174         break;
175     case PROP_PDF_DIRNAME:
176         qof_instance_set_kvp (QOF_INSTANCE (cust), value, 1, OWNER_EXPORT_PDF_DIRNAME);
177         break;
178     case PROP_LAST_POSTED:
179         qof_instance_set_kvp (QOF_INSTANCE (cust), value, 1, LAST_POSTED_TO_ACCT);
180         break;
181     case PROP_PAYMENT_LAST_ACCT:
182         qof_instance_set_kvp (QOF_INSTANCE (cust), value, 2, GNC_PAYMENT, GNC_LAST_ACCOUNT);
183         break;
184     default:
185         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
186         break;
187     }
188 }
189 
190 /** Return display name for this object */
191 static gchar*
impl_get_display_name(const QofInstance * inst)192 impl_get_display_name(const QofInstance* inst)
193 {
194     GncCustomer* cust;
195 
196     g_return_val_if_fail(inst != NULL, FALSE);
197     g_return_val_if_fail(GNC_IS_CUSTOMER(inst), FALSE);
198 
199     cust = GNC_CUSTOMER(inst);
200     /* XXX internationalization of "Customer" */
201     return g_strdup_printf("Customer %s", cust->name);
202 }
203 
204 /** Does this object refer to a specific object */
205 static gboolean
impl_refers_to_object(const QofInstance * inst,const QofInstance * ref)206 impl_refers_to_object(const QofInstance* inst, const QofInstance* ref)
207 {
208     GncCustomer* cust;
209 
210     g_return_val_if_fail(inst != NULL, FALSE);
211     g_return_val_if_fail(GNC_IS_CUSTOMER(inst), FALSE);
212 
213     cust = GNC_CUSTOMER(inst);
214 
215     if (GNC_IS_BILLTERM(ref))
216     {
217         return (cust->terms == GNC_BILLTERM(ref));
218     }
219     else if (GNC_IS_TAXTABLE(ref))
220     {
221         return (cust->taxtable == GNC_TAXTABLE(ref));
222     }
223 
224     return FALSE;
225 }
226 
227 /** Returns a list of my type of object which refers to an object.  For example, when called as
228         qof_instance_get_typed_referring_object_list(taxtable, account);
229     it will return the list of taxtables which refer to a specific account.  The result should be the
230     same regardless of which taxtable object is used.  The list must be freed by the caller but the
231     objects on the list must not.
232  */
233 static GList*
impl_get_typed_referring_object_list(const QofInstance * inst,const QofInstance * ref)234 impl_get_typed_referring_object_list(const QofInstance* inst, const QofInstance* ref)
235 {
236     if (!GNC_IS_BILLTERM(ref) && !GNC_IS_TAXTABLE(ref))
237     {
238         return NULL;
239     }
240 
241     return qof_instance_get_referring_object_list_from_collection(qof_instance_get_collection(inst), ref);
242 }
243 
244 static void
gnc_customer_class_init(GncCustomerClass * klass)245 gnc_customer_class_init (GncCustomerClass *klass)
246 {
247     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
248     QofInstanceClass* qof_class = QOF_INSTANCE_CLASS(klass);
249 
250     gobject_class->dispose = gnc_customer_dispose;
251     gobject_class->finalize = gnc_customer_finalize;
252     gobject_class->set_property = gnc_customer_set_property;
253     gobject_class->get_property = gnc_customer_get_property;
254 
255     qof_class->get_display_name = impl_get_display_name;
256     qof_class->refers_to_object = impl_refers_to_object;
257     qof_class->get_typed_referring_object_list = impl_get_typed_referring_object_list;
258 
259     g_object_class_install_property
260     (gobject_class,
261      PROP_NAME,
262      g_param_spec_string ("name",
263                           "Customer Name",
264                           "The customer is an arbitrary string "
265                           "assigned by the user which provides the "
266                           "customer name.",
267                           NULL,
268                           G_PARAM_READWRITE));
269 
270     g_object_class_install_property
271     (gobject_class,
272      PROP_PDF_DIRNAME,
273      g_param_spec_string ("export-pdf-dir",
274                           "Export PDF Directory Name",
275                           "A subdirectory for exporting PDF reports which is "
276 			  "appended to the target directory when writing them "
277 			  "out. It is retrieved from preferences and stored on "
278 			  "each 'Owner' object which prints items after "
279 			  "printing.",
280                           NULL,
281                           G_PARAM_READWRITE));
282 
283     g_object_class_install_property(
284        gobject_class,
285        PROP_LAST_POSTED,
286        g_param_spec_boxed("invoice-last-posted-account",
287 			  "Invoice Last Posted Account",
288 			  "The last account to which an invoice belonging to "
289 			  "this owner was posted.",
290 			  GNC_TYPE_GUID,
291 			  G_PARAM_READWRITE));
292 
293     g_object_class_install_property(
294        gobject_class,
295        PROP_PAYMENT_LAST_ACCT,
296        g_param_spec_boxed("payment-last-account",
297 			  "Payment Last Account",
298 			  "The last account to which an payment belonging to "
299 			  "this owner was posted.",
300 			  GNC_TYPE_GUID,
301 			  G_PARAM_READWRITE));
302 }
303 
304 /* Create/Destroy Functions */
gncCustomerCreate(QofBook * book)305 GncCustomer *gncCustomerCreate (QofBook *book)
306 {
307     GncCustomer *cust;
308 
309     if (!book) return NULL;
310 
311     cust = g_object_new (GNC_TYPE_CUSTOMER, NULL);
312     qof_instance_init_data (&cust->inst, _GNC_MOD_NAME, book);
313 
314     cust->id = CACHE_INSERT ("");
315     cust->name = CACHE_INSERT ("");
316     cust->notes = CACHE_INSERT ("");
317     cust->addr = gncAddressCreate (book, &cust->inst);
318     cust->taxincluded = GNC_TAXINCLUDED_USEGLOBAL;
319     cust->active = TRUE;
320     cust->jobs = NULL;
321     cust->balance = NULL;
322 
323     cust->discount = gnc_numeric_zero();
324     cust->credit = gnc_numeric_zero();
325     cust->shipaddr = gncAddressCreate (book, &cust->inst);
326 
327     if (cust_qof_event_handler_id == 0)
328         cust_qof_event_handler_id = qof_event_register_handler (cust_handle_qof_events, NULL);
329 
330     qof_event_gen (&cust->inst, QOF_EVENT_CREATE, NULL);
331 
332     return cust;
333 }
334 
gncCustomerDestroy(GncCustomer * cust)335 void gncCustomerDestroy (GncCustomer *cust)
336 {
337     if (!cust) return;
338     qof_instance_set_destroying(cust, TRUE);
339     qof_instance_set_dirty (&cust->inst);
340     gncCustomerCommitEdit (cust);
341 }
342 
gncCustomerFree(GncCustomer * cust)343 static void gncCustomerFree (GncCustomer *cust)
344 {
345     if (!cust) return;
346 
347     qof_event_gen (&cust->inst, QOF_EVENT_DESTROY, NULL);
348 
349     CACHE_REMOVE (cust->id);
350     CACHE_REMOVE (cust->name);
351     CACHE_REMOVE (cust->notes);
352     gncAddressBeginEdit (cust->addr);
353     gncAddressDestroy (cust->addr);
354     gncAddressBeginEdit (cust->shipaddr);
355     gncAddressDestroy (cust->shipaddr);
356     g_list_free (cust->jobs);
357     g_free (cust->balance);
358 
359     if (cust->terms)
360         gncBillTermDecRef (cust->terms);
361     if (cust->taxtable)
362     {
363         gncTaxTableDecRef (cust->taxtable);
364     }
365 
366     /* qof_instance_release (&cust->inst); */
367     g_object_unref (cust);
368 }
369 
370 /* ============================================================== */
371 /* Set Functions */
372 
373 #define SET_STR(obj, member, str) { \
374         if (!g_strcmp0 (member, str)) return; \
375         gncCustomerBeginEdit (obj); \
376         CACHE_REPLACE(member, str); \
377         }
378 
gncCustomerSetID(GncCustomer * cust,const char * id)379 void gncCustomerSetID (GncCustomer *cust, const char *id)
380 {
381     if (!cust) return;
382     if (!id) return;
383     SET_STR(cust, cust->id, id);
384     mark_customer (cust);
385     gncCustomerCommitEdit (cust);
386 }
387 
gncCustomerSetName(GncCustomer * cust,const char * name)388 void gncCustomerSetName (GncCustomer *cust, const char *name)
389 {
390     if (!cust) return;
391     if (!name) return;
392     SET_STR(cust, cust->name, name);
393     mark_customer (cust);
394     gncCustomerCommitEdit (cust);
395 }
396 
gncCustomerSetNotes(GncCustomer * cust,const char * notes)397 void gncCustomerSetNotes (GncCustomer *cust, const char *notes)
398 {
399     if (!cust) return;
400     if (!notes) return;
401     SET_STR(cust, cust->notes, notes);
402     mark_customer (cust);
403     gncCustomerCommitEdit (cust);
404 }
405 
gncCustomerSetTerms(GncCustomer * cust,GncBillTerm * terms)406 void gncCustomerSetTerms (GncCustomer *cust, GncBillTerm *terms)
407 {
408     if (!cust) return;
409     if (cust->terms == terms) return;
410 
411     gncCustomerBeginEdit (cust);
412     if (cust->terms)
413         gncBillTermDecRef (cust->terms);
414     cust->terms = terms;
415     if (cust->terms)
416         gncBillTermIncRef (cust->terms);
417     mark_customer (cust);
418     gncCustomerCommitEdit (cust);
419 }
420 
gncCustomerSetTaxIncluded(GncCustomer * cust,GncTaxIncluded taxincl)421 void gncCustomerSetTaxIncluded (GncCustomer *cust, GncTaxIncluded taxincl)
422 {
423     if (!cust) return;
424     if (taxincl == cust->taxincluded) return;
425     gncCustomerBeginEdit (cust);
426     cust->taxincluded = taxincl;
427     mark_customer (cust);
428     gncCustomerCommitEdit (cust);
429 }
430 
gncCustomerSetActive(GncCustomer * cust,gboolean active)431 void gncCustomerSetActive (GncCustomer *cust, gboolean active)
432 {
433     if (!cust) return;
434     if (active == cust->active) return;
435     gncCustomerBeginEdit (cust);
436     cust->active = active;
437     mark_customer (cust);
438     gncCustomerCommitEdit (cust);
439 }
440 
gncCustomerSetDiscount(GncCustomer * cust,gnc_numeric discount)441 void gncCustomerSetDiscount (GncCustomer *cust, gnc_numeric discount)
442 {
443     if (!cust) return;
444     if (gnc_numeric_equal (discount, cust->discount)) return;
445     gncCustomerBeginEdit (cust);
446     cust->discount = discount;
447     mark_customer (cust);
448     gncCustomerCommitEdit (cust);
449 }
450 
gncCustomerSetCredit(GncCustomer * cust,gnc_numeric credit)451 void gncCustomerSetCredit (GncCustomer *cust, gnc_numeric credit)
452 {
453     if (!cust) return;
454     if (gnc_numeric_equal (credit, cust->credit)) return;
455     gncCustomerBeginEdit (cust);
456     cust->credit = credit;
457     mark_customer (cust);
458     gncCustomerCommitEdit (cust);
459 }
460 
gncCustomerSetCurrency(GncCustomer * cust,gnc_commodity * currency)461 void gncCustomerSetCurrency (GncCustomer *cust, gnc_commodity *currency)
462 {
463     if (!cust || !currency) return;
464     if (cust->currency && gnc_commodity_equal (cust->currency, currency)) return;
465     gncCustomerBeginEdit (cust);
466     cust->currency = currency;
467     mark_customer (cust);
468     gncCustomerCommitEdit (cust);
469 }
470 
gncCustomerSetTaxTableOverride(GncCustomer * customer,gboolean override)471 void gncCustomerSetTaxTableOverride (GncCustomer *customer, gboolean override)
472 {
473     if (!customer) return;
474     if (customer->taxtable_override == override) return;
475     gncCustomerBeginEdit (customer);
476     customer->taxtable_override = override;
477     mark_customer (customer);
478     gncCustomerCommitEdit (customer);
479 }
480 
gncCustomerSetTaxTable(GncCustomer * customer,GncTaxTable * table)481 void gncCustomerSetTaxTable (GncCustomer *customer, GncTaxTable *table)
482 {
483     if (!customer) return;
484     if (customer->taxtable == table) return;
485 
486     gncCustomerBeginEdit (customer);
487     if (customer->taxtable)
488         gncTaxTableDecRef (customer->taxtable);
489     if (table)
490         gncTaxTableIncRef (table);
491     customer->taxtable = table;
492     mark_customer (customer);
493     gncCustomerCommitEdit (customer);
494 }
495 
496 /* Note that JobList changes do not affect the "dirtiness" of the customer */
gncCustomerAddJob(GncCustomer * cust,GncJob * job)497 void gncCustomerAddJob (GncCustomer *cust, GncJob *job)
498 {
499     if (!cust) return;
500     if (!job) return;
501 
502     if (g_list_index(cust->jobs, job) == -1)
503         cust->jobs = g_list_insert_sorted (cust->jobs, job,
504                                            (GCompareFunc)gncJobCompare);
505 
506     qof_event_gen (&cust->inst, QOF_EVENT_MODIFY, NULL);
507 }
508 
gncCustomerRemoveJob(GncCustomer * cust,GncJob * job)509 void gncCustomerRemoveJob (GncCustomer *cust, GncJob *job)
510 {
511     GList *node;
512 
513     if (!cust) return;
514     if (!job) return;
515 
516     node = g_list_find (cust->jobs, job);
517     if (!node)
518     {
519         /*    PERR ("split not in account"); */
520     }
521     else
522     {
523         cust->jobs = g_list_remove_link (cust->jobs, node);
524         g_list_free_1 (node);
525     }
526     qof_event_gen (&cust->inst, QOF_EVENT_MODIFY, NULL);
527 }
528 
gncCustomerBeginEdit(GncCustomer * cust)529 void gncCustomerBeginEdit (GncCustomer *cust)
530 {
531     qof_begin_edit (&cust->inst);
532 }
533 
gncCustomerOnError(QofInstance * inst,QofBackendError errcode)534 static void gncCustomerOnError (QofInstance *inst, QofBackendError errcode)
535 {
536     PERR("Customer QofBackend Failure: %d", errcode);
537     gnc_engine_signal_commit_error( errcode );
538 }
539 
gncCustomerOnDone(QofInstance * inst)540 static void gncCustomerOnDone (QofInstance *inst)
541 {
542     GncCustomer *cust = (GncCustomer *) inst;
543     gncAddressClearDirty (cust->addr);
544     gncAddressClearDirty (cust->shipaddr);
545 }
546 
cust_free(QofInstance * inst)547 static void cust_free (QofInstance *inst)
548 {
549     GncCustomer *cust = (GncCustomer *) inst;
550     gncCustomerFree (cust);
551 }
552 
gncCustomerCommitEdit(GncCustomer * cust)553 void gncCustomerCommitEdit (GncCustomer *cust)
554 {
555     if (!qof_commit_edit (QOF_INSTANCE(cust))) return;
556     qof_commit_edit_part2 (&cust->inst, gncCustomerOnError,
557                            gncCustomerOnDone, cust_free);
558 }
559 
560 /* ============================================================== */
561 /* Get Functions */
562 
gncCustomerGetID(const GncCustomer * cust)563 const char * gncCustomerGetID (const GncCustomer *cust)
564 {
565     if (!cust) return NULL;
566     return cust->id;
567 }
568 
gncCustomerGetName(const GncCustomer * cust)569 const char * gncCustomerGetName (const GncCustomer *cust)
570 {
571     if (!cust) return NULL;
572     return cust->name;
573 }
574 
gncCustomerGetAddr(const GncCustomer * cust)575 GncAddress * gncCustomerGetAddr (const GncCustomer *cust)
576 {
577     if (!cust) return NULL;
578     return cust->addr;
579 }
580 
581 static void
qofCustomerSetAddr(GncCustomer * cust,QofInstance * addr_ent)582 qofCustomerSetAddr (GncCustomer *cust, QofInstance *addr_ent)
583 {
584     GncAddress *addr;
585 
586     if (!cust || !addr_ent)
587     {
588         return;
589     }
590     addr = (GncAddress*)addr_ent;
591     if (addr == cust->addr)
592     {
593         return;
594     }
595     if (cust->addr != NULL)
596     {
597         gncAddressBeginEdit(cust->addr);
598         gncAddressDestroy(cust->addr);
599     }
600     gncCustomerBeginEdit(cust);
601     cust->addr = addr;
602     gncCustomerCommitEdit(cust);
603 }
604 
605 static void
qofCustomerSetShipAddr(GncCustomer * cust,QofInstance * ship_addr_ent)606 qofCustomerSetShipAddr (GncCustomer *cust, QofInstance *ship_addr_ent)
607 {
608     GncAddress *ship_addr;
609 
610     if (!cust || !ship_addr_ent)
611     {
612         return;
613     }
614     ship_addr = (GncAddress*)ship_addr_ent;
615     if (ship_addr == cust->shipaddr)
616     {
617         return;
618     }
619     if (cust->shipaddr != NULL)
620     {
621         gncAddressBeginEdit(cust->shipaddr);
622         gncAddressDestroy(cust->shipaddr);
623     }
624     gncCustomerBeginEdit(cust);
625     cust->shipaddr = ship_addr;
626     gncCustomerCommitEdit(cust);
627 }
628 
gncCustomerGetShipAddr(const GncCustomer * cust)629 GncAddress * gncCustomerGetShipAddr (const GncCustomer *cust)
630 {
631     if (!cust) return NULL;
632     return cust->shipaddr;
633 }
634 
gncCustomerGetNotes(const GncCustomer * cust)635 const char * gncCustomerGetNotes (const GncCustomer *cust)
636 {
637     if (!cust) return NULL;
638     return cust->notes;
639 }
640 
gncCustomerGetTerms(const GncCustomer * cust)641 GncBillTerm * gncCustomerGetTerms (const GncCustomer *cust)
642 {
643     if (!cust) return NULL;
644     return cust->terms;
645 }
646 
gncCustomerGetTaxIncluded(const GncCustomer * cust)647 GncTaxIncluded gncCustomerGetTaxIncluded (const GncCustomer *cust)
648 {
649     if (!cust) return GNC_TAXINCLUDED_USEGLOBAL;
650     return cust->taxincluded;
651 }
652 
gncCustomerGetCurrency(const GncCustomer * cust)653 gnc_commodity * gncCustomerGetCurrency (const GncCustomer *cust)
654 {
655     if (!cust) return NULL;
656     return cust->currency;
657 }
658 
gncCustomerGetActive(const GncCustomer * cust)659 gboolean gncCustomerGetActive (const GncCustomer *cust)
660 {
661     if (!cust) return FALSE;
662     return cust->active;
663 }
664 
gncCustomerGetDiscount(const GncCustomer * cust)665 gnc_numeric gncCustomerGetDiscount (const GncCustomer *cust)
666 {
667     if (!cust) return gnc_numeric_zero();
668     return cust->discount;
669 }
670 
gncCustomerGetCredit(const GncCustomer * cust)671 gnc_numeric gncCustomerGetCredit (const GncCustomer *cust)
672 {
673     if (!cust) return gnc_numeric_zero();
674     return cust->credit;
675 }
676 
gncCustomerGetTaxTableOverride(const GncCustomer * customer)677 gboolean gncCustomerGetTaxTableOverride (const GncCustomer *customer)
678 {
679     if (!customer) return FALSE;
680     return customer->taxtable_override;
681 }
682 
gncCustomerGetTaxTable(const GncCustomer * customer)683 GncTaxTable* gncCustomerGetTaxTable (const GncCustomer *customer)
684 {
685     if (!customer) return NULL;
686     return customer->taxtable;
687 }
688 
gncCustomerGetJoblist(const GncCustomer * cust,gboolean show_all)689 GList * gncCustomerGetJoblist (const GncCustomer *cust, gboolean show_all)
690 {
691     if (!cust) return NULL;
692 
693     if (show_all)
694     {
695         return (g_list_copy (cust->jobs));
696     }
697     else
698     {
699         GList *list = NULL, *iterator;
700         for (iterator = cust->jobs; iterator; iterator = iterator->next)
701         {
702             GncJob *j = iterator->data;
703             if (gncJobGetActive (j))
704                 list = g_list_prepend (list, j);
705         }
706         return g_list_reverse (list);
707     }
708 }
709 
gncCustomerIsDirty(GncCustomer * cust)710 gboolean gncCustomerIsDirty (GncCustomer *cust)
711 {
712     if (!cust) return FALSE;
713     return (qof_instance_is_dirty(&cust->inst) ||
714             gncAddressIsDirty (cust->addr) ||
715             gncAddressIsDirty (cust->shipaddr));
716 }
717 
718 /* Other functions */
719 
gncCustomerCompare(const GncCustomer * a,const GncCustomer * b)720 int gncCustomerCompare (const GncCustomer *a, const GncCustomer *b)
721 {
722     if (!a && !b) return 0;
723     if (!a && b) return 1;
724     if (a && !b) return -1;
725 
726     return(strcmp(a->name, b->name));
727 }
728 
729 gboolean
gncCustomerEqual(const GncCustomer * a,const GncCustomer * b)730 gncCustomerEqual(const GncCustomer *a, const GncCustomer *b)
731 {
732     if (a == NULL && b == NULL) return TRUE;
733     if (a == NULL || b == NULL) return FALSE;
734 
735     g_return_val_if_fail(GNC_IS_CUSTOMER(a), FALSE);
736     g_return_val_if_fail(GNC_IS_CUSTOMER(b), FALSE);
737 
738     if (g_strcmp0(a->id, b->id) != 0)
739     {
740         PWARN("IDs differ: %s vs %s", a->id, b->id);
741         return FALSE;
742     }
743 
744     if (g_strcmp0(a->name, b->name) != 0)
745     {
746         PWARN("Names differ: %s vs %s", a->name, b->name);
747         return FALSE;
748     }
749 
750     if (g_strcmp0(a->notes, b->notes) != 0)
751     {
752         PWARN("Notes differ: %s vs %s", a->notes, b->notes);
753         return FALSE;
754     }
755 
756     if (!gncBillTermEqual(a->terms, b->terms))
757     {
758         PWARN("Bill terms differ");
759         return FALSE;
760     }
761 
762     if (!gnc_commodity_equal(a->currency, b->currency))
763     {
764         PWARN("currencies differ");
765         return FALSE;
766     }
767 
768     if (!gncTaxTableEqual(a->taxtable, b->taxtable))
769     {
770         PWARN("tax tables differ");
771         return FALSE;
772     }
773 
774     if (a->taxtable_override != b->taxtable_override)
775     {
776         PWARN("Tax table override flags differ");
777         return FALSE;
778     }
779 
780     if (a->taxincluded != b->taxincluded)
781     {
782         PWARN("Tax included flags differ");
783         return FALSE;
784     }
785 
786     if (a->active != b->active)
787     {
788         PWARN("Active flags differ");
789         return FALSE;
790     }
791 
792     if (!gncAddressEqual(a->addr, b->addr))
793     {
794         PWARN("addresses differ");
795         return FALSE;
796     }
797     if (!gncAddressEqual(a->shipaddr, b->shipaddr))
798     {
799         PWARN("addresses differ");
800         return FALSE;
801     }
802 
803     if (!gnc_numeric_equal(a->credit, b->credit))
804     {
805         PWARN("Credit amounts differ");
806         return FALSE;
807     }
808 
809     if (!gnc_numeric_equal(a->discount, b->discount))
810     {
811         PWARN("Discount amounts differ");
812         return FALSE;
813     }
814 
815     /* FIXME: Need to check jobs list
816     GList *         jobs;
817     */
818 
819     return TRUE;
820 }
821 
822 /**
823  * Listen for qof events.
824  *
825  * - If the address of a customer has changed, mark the customer as dirty.
826  * - If a lot related to a customer has changed, clear the customer's
827  *   cached balance as it likely has become invalid.
828  *
829  * @param entity Entity for the event
830  * @param event_type Event type
831  * @param user_data User data registered with the handler
832  * @param event_data Event data passed with the event.
833  */
834 static void
cust_handle_qof_events(QofInstance * entity,QofEventId event_type,gpointer user_data,gpointer event_data)835 cust_handle_qof_events (QofInstance *entity, QofEventId event_type,
836                         gpointer user_data, gpointer event_data)
837 {
838     /* Handle address change events */
839     if ((GNC_IS_ADDRESS (entity) &&
840         (event_type & QOF_EVENT_MODIFY) != 0))
841     {
842         if (GNC_IS_CUSTOMER (event_data))
843         {
844             GncCustomer* cust = GNC_CUSTOMER (event_data);
845             gncCustomerBeginEdit (cust);
846             mark_customer (cust);
847             gncCustomerCommitEdit (cust);
848         }
849         return;
850     }
851 
852     /* Handle lot change events */
853     if (GNC_IS_LOT (entity))
854     {
855         GNCLot *lot = GNC_LOT (entity);
856         GncOwner lot_owner;
857         const GncOwner *end_owner = NULL;
858         GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
859 
860         /* Determine the owner associated with the lot */
861         if (invoice)
862             /* Invoice lots */
863             end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
864         else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
865             /* Pre-payment lots */
866             end_owner = gncOwnerGetEndOwner (&lot_owner);
867 
868         if (gncOwnerGetType (end_owner) == GNC_OWNER_CUSTOMER)
869         {
870             /* Clear the cached balance */
871             GncCustomer* cust = gncOwnerGetCustomer (end_owner);
872             g_free (cust->balance);
873             cust->balance = NULL;
874         }
875         return;
876     }
877 }
878 
879 /* ============================================================== */
880 /* Package-Private functions */
_gncCustomerPrintable(gpointer item)881 static const char * _gncCustomerPrintable (gpointer item)
882 {
883 //  GncCustomer *c = item;
884     if (!item) return "failed";
885     return gncCustomerGetName((GncCustomer*)item);
886 }
887 
888 static void
destroy_customer_on_book_close(QofInstance * ent,gpointer data)889 destroy_customer_on_book_close(QofInstance *ent, gpointer data)
890 {
891     GncCustomer* c = GNC_CUSTOMER(ent);
892 
893     gncCustomerBeginEdit(c);
894     gncCustomerDestroy(c);
895 }
896 
897 /** Handles book end - frees all customers from the book
898  *
899  * @param book Book being closed
900  */
901 static void
gnc_customer_book_end(QofBook * book)902 gnc_customer_book_end(QofBook* book)
903 {
904     QofCollection *col;
905 
906     col = qof_book_get_collection(book, GNC_ID_CUSTOMER);
907     qof_collection_foreach(col, destroy_customer_on_book_close, NULL);
908 }
909 
910 static QofObject gncCustomerDesc =
911 {
912     DI(.interface_version = ) QOF_OBJECT_VERSION,
913     DI(.e_type            = ) _GNC_MOD_NAME,
914     DI(.type_label        = ) "Customer",
915     DI(.create            = ) (gpointer)gncCustomerCreate,
916     DI(.book_begin        = ) NULL,
917     DI(.book_end          = ) gnc_customer_book_end,
918     DI(.is_dirty          = ) qof_collection_is_dirty,
919     DI(.mark_clean        = ) qof_collection_mark_clean,
920     DI(.foreach           = ) qof_collection_foreach,
921     DI(.printable         = ) (const char * (*)(gpointer))gncCustomerGetName,
922     DI(.version_cmp       = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
923 };
924 
gncCustomerRegister(void)925 gboolean gncCustomerRegister (void)
926 {
927     static QofParam params[] =
928     {
929         { CUSTOMER_ID, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetID, (QofSetterFunc)gncCustomerSetID },
930         { CUSTOMER_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetName, (QofSetterFunc)gncCustomerSetName },
931         { CUSTOMER_NOTES, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetNotes, (QofSetterFunc)gncCustomerSetNotes },
932         {
933             CUSTOMER_DISCOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncCustomerGetDiscount,
934             (QofSetterFunc)gncCustomerSetDiscount
935         },
936         {
937             CUSTOMER_CREDIT, QOF_TYPE_NUMERIC, (QofAccessFunc)gncCustomerGetCredit,
938             (QofSetterFunc)gncCustomerSetCredit
939         },
940         { CUSTOMER_ADDR, GNC_ID_ADDRESS, (QofAccessFunc)gncCustomerGetAddr, (QofSetterFunc)qofCustomerSetAddr },
941         { CUSTOMER_SHIPADDR, GNC_ID_ADDRESS, (QofAccessFunc)gncCustomerGetShipAddr, (QofSetterFunc)qofCustomerSetShipAddr },
942         {
943             CUSTOMER_TT_OVER, QOF_TYPE_BOOLEAN, (QofAccessFunc)gncCustomerGetTaxTableOverride,
944             (QofSetterFunc)gncCustomerSetTaxTableOverride
945         },
946         { CUSTOMER_TERMS, GNC_ID_BILLTERM, (QofAccessFunc)gncCustomerGetTerms, (QofSetterFunc)gncCustomerSetTerms },
947         { QOF_PARAM_ACTIVE, QOF_TYPE_BOOLEAN, (QofAccessFunc)gncCustomerGetActive, (QofSetterFunc)gncCustomerSetActive },
948         { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL },
949         { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL },
950         { NULL },
951     };
952 
953     if (!qof_choice_add_class(GNC_ID_INVOICE, GNC_ID_CUSTOMER, INVOICE_OWNER))
954     {
955         return FALSE;
956     }
957     if (!qof_choice_add_class(GNC_ID_JOB, GNC_ID_CUSTOMER, JOB_OWNER))
958     {
959         return FALSE;
960     }
961     qof_class_register (_GNC_MOD_NAME, (QofSortFunc)gncCustomerCompare, params);
962     if (!qof_choice_create(GNC_ID_CUSTOMER))
963     {
964         return FALSE;
965     }
966     /* temp */
967     _gncCustomerPrintable(NULL);
968     return qof_object_register (&gncCustomerDesc);
969 }
970 
gncCustomerNextID(QofBook * book)971 gchar *gncCustomerNextID (QofBook *book)
972 {
973     return qof_book_increment_and_format_counter (book, _GNC_MOD_NAME);
974 }
975 
976 const gnc_numeric*
gncCustomerGetCachedBalance(GncCustomer * cust)977 gncCustomerGetCachedBalance (GncCustomer *cust)
978 {
979     return cust->balance;
980 }
981 
gncCustomerSetCachedBalance(GncCustomer * cust,const gnc_numeric * new_bal)982 void gncCustomerSetCachedBalance (GncCustomer *cust, const gnc_numeric *new_bal)
983 {
984     if (!new_bal)
985     {
986         if (cust->balance)
987         {
988             g_free (cust->balance);
989             cust->balance = NULL;
990         }
991         return;
992     }
993 
994     if (!cust->balance)
995         cust->balance = g_new0 (gnc_numeric, 1);
996 
997     *cust->balance = *new_bal;
998 }
999