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