Line data Source code
1 : /********************************************************************\
2 : * gncOwner.c -- Business Interface: Object OWNERs *
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 : * Copyright (c) 2005 Neil Williams <linux@codehelp.co.uk>
27 : * Copyright (c) 2006 David Hampton <hampton@employees.org>
28 : * Copyright (c) 2011 Geert Janssens <geert@kobaltwit.be>
29 : * Author: Derek Atkins <warlord@MIT.EDU>
30 : */
31 :
32 : #include <config.h>
33 :
34 : #include <glib.h>
35 : #include <glib/gi18n.h>
36 : #include <qofinstance-p.h>
37 :
38 : #include "gncCustomerP.h"
39 : #include "gncEmployeeP.h"
40 : #include "gncJobP.h"
41 : #include "gncOwner.h"
42 : #include "gncOwnerP.h"
43 : #include "gncVendorP.h"
44 : #include "gncInvoice.h"
45 : #include "gnc-commodity.h"
46 : #include "Scrub2.h"
47 : #include "Split.h"
48 : #include "Transaction.h"
49 : #include "engine-helpers.h"
50 :
51 : #define _GNC_MOD_NAME GNC_ID_OWNER
52 :
53 : #define GNC_OWNER_ID "gncOwner"
54 :
55 : static QofLogModule log_module = GNC_MOD_ENGINE;
56 :
57 82 : GncOwner * gncOwnerNew (void)
58 : {
59 : GncOwner *o;
60 :
61 82 : o = g_new0 (GncOwner, 1);
62 82 : o->type = GNC_OWNER_NONE;
63 82 : return o;
64 : }
65 :
66 17 : void gncOwnerFree (GncOwner *owner)
67 : {
68 17 : if (!owner) return;
69 17 : g_free (owner);
70 : }
71 :
72 0 : void gncOwnerBeginEdit (GncOwner *owner)
73 : {
74 0 : if (!owner) return;
75 0 : switch (owner->type)
76 : {
77 0 : case GNC_OWNER_NONE :
78 : case GNC_OWNER_UNDEFINED :
79 0 : break;
80 0 : case GNC_OWNER_CUSTOMER :
81 : {
82 0 : gncCustomerBeginEdit(owner->owner.customer);
83 0 : break;
84 : }
85 0 : case GNC_OWNER_JOB :
86 : {
87 0 : gncJobBeginEdit(owner->owner.job);
88 0 : break;
89 : }
90 0 : case GNC_OWNER_VENDOR :
91 : {
92 0 : gncVendorBeginEdit(owner->owner.vendor);
93 0 : break;
94 : }
95 0 : case GNC_OWNER_EMPLOYEE :
96 : {
97 0 : gncEmployeeBeginEdit(owner->owner.employee);
98 0 : break;
99 : }
100 : }
101 : }
102 :
103 0 : void gncOwnerCommitEdit (GncOwner *owner)
104 : {
105 0 : if (!owner) return;
106 0 : switch (owner->type)
107 : {
108 0 : case GNC_OWNER_NONE :
109 : case GNC_OWNER_UNDEFINED :
110 0 : break;
111 0 : case GNC_OWNER_CUSTOMER :
112 : {
113 0 : gncCustomerCommitEdit(owner->owner.customer);
114 0 : break;
115 : }
116 0 : case GNC_OWNER_JOB :
117 : {
118 0 : gncJobCommitEdit(owner->owner.job);
119 0 : break;
120 : }
121 0 : case GNC_OWNER_VENDOR :
122 : {
123 0 : gncVendorCommitEdit(owner->owner.vendor);
124 0 : break;
125 : }
126 0 : case GNC_OWNER_EMPLOYEE :
127 : {
128 0 : gncEmployeeCommitEdit(owner->owner.employee);
129 0 : break;
130 : }
131 : }
132 : }
133 :
134 0 : void gncOwnerDestroy (GncOwner *owner)
135 : {
136 0 : if (!owner) return;
137 0 : switch (owner->type)
138 : {
139 0 : case GNC_OWNER_NONE :
140 : case GNC_OWNER_UNDEFINED :
141 0 : break;
142 0 : case GNC_OWNER_CUSTOMER :
143 : {
144 0 : gncCustomerDestroy(owner->owner.customer);
145 0 : break;
146 : }
147 0 : case GNC_OWNER_JOB :
148 : {
149 0 : gncJobDestroy(owner->owner.job);
150 0 : break;
151 : }
152 0 : case GNC_OWNER_VENDOR :
153 : {
154 0 : gncVendorDestroy(owner->owner.vendor);
155 0 : break;
156 : }
157 0 : case GNC_OWNER_EMPLOYEE :
158 : {
159 0 : gncEmployeeDestroy(owner->owner.employee);
160 0 : break;
161 : }
162 : }
163 : }
164 :
165 0 : void gncOwnerInitUndefined (GncOwner *owner, gpointer obj)
166 : {
167 0 : if (!owner) return;
168 0 : owner->type = GNC_OWNER_UNDEFINED;
169 0 : owner->owner.undefined = obj;
170 : }
171 :
172 248 : void gncOwnerInitCustomer (GncOwner *owner, GncCustomer *customer)
173 : {
174 248 : if (!owner) return;
175 248 : owner->type = GNC_OWNER_CUSTOMER;
176 248 : owner->owner.customer = customer;
177 : }
178 :
179 10 : void gncOwnerInitJob (GncOwner *owner, GncJob *job)
180 : {
181 10 : if (!owner) return;
182 10 : owner->type = GNC_OWNER_JOB;
183 10 : owner->owner.job = job;
184 : }
185 :
186 13 : void gncOwnerInitVendor (GncOwner *owner, GncVendor *vendor)
187 : {
188 13 : if (!owner) return;
189 13 : owner->type = GNC_OWNER_VENDOR;
190 13 : owner->owner.vendor = vendor;
191 : }
192 :
193 5 : void gncOwnerInitEmployee (GncOwner *owner, GncEmployee *employee)
194 : {
195 5 : if (!owner) return;
196 5 : owner->type = GNC_OWNER_EMPLOYEE;
197 5 : owner->owner.employee = employee;
198 : }
199 :
200 2623 : GncOwnerType gncOwnerGetType (const GncOwner *owner)
201 : {
202 2623 : if (!owner) return GNC_OWNER_NONE;
203 2169 : return owner->type;
204 : }
205 :
206 0 : const char * gncOwnerGetTypeString (const GncOwner *owner)
207 : {
208 0 : GncOwnerType type = gncOwnerGetType(owner);
209 0 : switch (type)
210 : {
211 0 : case GNC_OWNER_NONE:
212 0 : return N_("None");
213 0 : case GNC_OWNER_UNDEFINED:
214 0 : return N_("Undefined");
215 0 : case GNC_OWNER_CUSTOMER:
216 0 : return N_("Customer");
217 0 : case GNC_OWNER_JOB:
218 0 : return N_("Job");
219 0 : case GNC_OWNER_VENDOR:
220 0 : return N_("Vendor");
221 0 : case GNC_OWNER_EMPLOYEE:
222 0 : return N_("Employee");
223 0 : default:
224 0 : PWARN ("Unknown owner type");
225 0 : return NULL;
226 : }
227 : }
228 :
229 : QofIdTypeConst
230 0 : qofOwnerGetType(const GncOwner *owner)
231 : {
232 0 : return gncOwnerTypeToQofIdType(owner->type);
233 : }
234 :
235 0 : QofIdTypeConst gncOwnerTypeToQofIdType(GncOwnerType t)
236 : {
237 0 : QofIdTypeConst type = NULL;
238 0 : switch (t)
239 : {
240 0 : case GNC_OWNER_NONE :
241 : {
242 0 : type = NULL;
243 0 : break;
244 : }
245 0 : case GNC_OWNER_UNDEFINED :
246 : {
247 0 : type = NULL;
248 0 : break;
249 : }
250 0 : case GNC_OWNER_CUSTOMER :
251 : {
252 0 : type = GNC_ID_CUSTOMER;
253 0 : break;
254 : }
255 0 : case GNC_OWNER_JOB :
256 : {
257 0 : type = GNC_ID_JOB;
258 0 : break;
259 : }
260 0 : case GNC_OWNER_VENDOR :
261 : {
262 0 : type = GNC_ID_VENDOR;
263 0 : break;
264 : }
265 0 : case GNC_OWNER_EMPLOYEE :
266 : {
267 0 : type = GNC_ID_EMPLOYEE;
268 0 : break;
269 : }
270 : }
271 0 : return type;
272 : }
273 :
274 : QofInstance*
275 1 : qofOwnerGetOwner (const GncOwner *owner)
276 : {
277 : QofInstance *ent;
278 :
279 1 : if (!owner)
280 : {
281 0 : return NULL;
282 : }
283 1 : ent = NULL;
284 1 : switch (owner->type)
285 : {
286 0 : case GNC_OWNER_NONE :
287 : {
288 0 : break;
289 : }
290 0 : case GNC_OWNER_UNDEFINED :
291 : {
292 0 : break;
293 : }
294 1 : case GNC_OWNER_CUSTOMER :
295 : {
296 1 : ent = QOF_INSTANCE(owner->owner.customer);
297 1 : break;
298 : }
299 0 : case GNC_OWNER_JOB :
300 : {
301 0 : ent = QOF_INSTANCE(owner->owner.job);
302 0 : break;
303 : }
304 0 : case GNC_OWNER_VENDOR :
305 : {
306 0 : ent = QOF_INSTANCE(owner->owner.vendor);
307 0 : break;
308 : }
309 0 : case GNC_OWNER_EMPLOYEE :
310 : {
311 0 : ent = QOF_INSTANCE(owner->owner.employee);
312 0 : break;
313 : }
314 : }
315 1 : return ent;
316 : }
317 :
318 : void
319 0 : qofOwnerSetEntity (GncOwner *owner, QofInstance *ent)
320 : {
321 0 : if (!owner || !ent)
322 : {
323 0 : return;
324 : }
325 0 : if (0 == g_strcmp0(ent->e_type, GNC_ID_CUSTOMER))
326 : {
327 0 : owner->type = GNC_OWNER_CUSTOMER;
328 0 : gncOwnerInitCustomer(owner, (GncCustomer*)ent);
329 : }
330 0 : else if (0 == g_strcmp0(ent->e_type, GNC_ID_JOB))
331 : {
332 0 : owner->type = GNC_OWNER_JOB;
333 0 : gncOwnerInitJob(owner, (GncJob*)ent);
334 : }
335 0 : else if (0 == g_strcmp0(ent->e_type, GNC_ID_VENDOR))
336 : {
337 0 : owner->type = GNC_OWNER_VENDOR;
338 0 : gncOwnerInitVendor(owner, (GncVendor*)ent);
339 : }
340 0 : else if (0 == g_strcmp0(ent->e_type, GNC_ID_EMPLOYEE))
341 : {
342 0 : owner->type = GNC_OWNER_EMPLOYEE;
343 0 : gncOwnerInitEmployee(owner, (GncEmployee*)ent);
344 : }
345 : else
346 : {
347 0 : owner->type = GNC_OWNER_NONE;
348 0 : owner->owner.undefined = NULL;
349 : }
350 : }
351 :
352 0 : gboolean GNC_IS_OWNER (QofInstance *ent)
353 : {
354 0 : if (!ent)
355 0 : return FALSE;
356 :
357 0 : return (GNC_IS_VENDOR(ent) ||
358 0 : GNC_IS_CUSTOMER(ent) ||
359 0 : GNC_IS_EMPLOYEE(ent) ||
360 0 : GNC_IS_JOB(ent));
361 : }
362 0 : gpointer gncOwnerGetUndefined (const GncOwner *owner)
363 : {
364 0 : if (!owner) return NULL;
365 0 : if (owner->type != GNC_OWNER_UNDEFINED) return NULL;
366 0 : return owner->owner.undefined;
367 : }
368 :
369 486 : GncCustomer * gncOwnerGetCustomer (const GncOwner *owner)
370 : {
371 486 : if (!owner) return NULL;
372 486 : if (owner->type != GNC_OWNER_CUSTOMER) return NULL;
373 486 : return owner->owner.customer;
374 : }
375 :
376 61 : GncJob * gncOwnerGetJob (const GncOwner *owner)
377 : {
378 61 : if (!owner) return NULL;
379 61 : if (owner->type != GNC_OWNER_JOB) return NULL;
380 13 : return owner->owner.job;
381 : }
382 :
383 58 : GncVendor * gncOwnerGetVendor (const GncOwner *owner)
384 : {
385 58 : if (!owner) return NULL;
386 58 : if (owner->type != GNC_OWNER_VENDOR) return NULL;
387 58 : return owner->owner.vendor;
388 : }
389 :
390 58 : GncEmployee * gncOwnerGetEmployee (const GncOwner *owner)
391 : {
392 58 : if (!owner) return NULL;
393 58 : if (owner->type != GNC_OWNER_EMPLOYEE) return NULL;
394 58 : return owner->owner.employee;
395 : }
396 :
397 179 : void gncOwnerCopy (const GncOwner *src, GncOwner *dest)
398 : {
399 179 : if (!src || !dest) return;
400 179 : if (src == dest) return;
401 179 : *dest = *src;
402 : }
403 :
404 116 : gboolean gncOwnerEqual (const GncOwner *a, const GncOwner *b)
405 : {
406 116 : if (!a || !b) return FALSE;
407 116 : if (gncOwnerGetType (a) != gncOwnerGetType (b)) return FALSE;
408 36 : return (a->owner.undefined == b->owner.undefined);
409 : }
410 :
411 0 : int gncOwnerGCompareFunc (const GncOwner *a, const GncOwner *b)
412 : {
413 0 : if (gncOwnerEqual (a, b))
414 0 : return 0;
415 : else
416 0 : return 1;
417 : }
418 :
419 0 : const char * gncOwnerGetID (const GncOwner *owner)
420 : {
421 0 : if (!owner) return NULL;
422 0 : switch (owner->type)
423 : {
424 0 : case GNC_OWNER_NONE:
425 : case GNC_OWNER_UNDEFINED:
426 : default:
427 0 : return NULL;
428 0 : case GNC_OWNER_CUSTOMER:
429 0 : return gncCustomerGetID (owner->owner.customer);
430 0 : case GNC_OWNER_JOB:
431 0 : return gncJobGetID (owner->owner.job);
432 0 : case GNC_OWNER_VENDOR:
433 0 : return gncVendorGetID (owner->owner.vendor);
434 0 : case GNC_OWNER_EMPLOYEE:
435 0 : return gncEmployeeGetID (owner->owner.employee);
436 : }
437 : }
438 :
439 185 : const char * gncOwnerGetName (const GncOwner *owner)
440 : {
441 185 : if (!owner) return NULL;
442 185 : switch (owner->type)
443 : {
444 0 : case GNC_OWNER_NONE:
445 : case GNC_OWNER_UNDEFINED:
446 : default:
447 0 : return NULL;
448 120 : case GNC_OWNER_CUSTOMER:
449 120 : return gncCustomerGetName (owner->owner.customer);
450 5 : case GNC_OWNER_JOB:
451 5 : return gncJobGetName (owner->owner.job);
452 30 : case GNC_OWNER_VENDOR:
453 30 : return gncVendorGetName (owner->owner.vendor);
454 30 : case GNC_OWNER_EMPLOYEE:
455 30 : return gncEmployeeGetName (owner->owner.employee);
456 : }
457 : }
458 :
459 7 : GncAddress * gncOwnerGetAddr (const GncOwner *owner)
460 : {
461 7 : if (!owner) return NULL;
462 7 : switch (owner->type)
463 : {
464 0 : case GNC_OWNER_NONE:
465 : case GNC_OWNER_UNDEFINED:
466 : case GNC_OWNER_JOB:
467 : default:
468 0 : return NULL;
469 3 : case GNC_OWNER_CUSTOMER:
470 3 : return gncCustomerGetAddr (owner->owner.customer);
471 2 : case GNC_OWNER_VENDOR:
472 2 : return gncVendorGetAddr (owner->owner.vendor);
473 2 : case GNC_OWNER_EMPLOYEE:
474 2 : return gncEmployeeGetAddr (owner->owner.employee);
475 : }
476 : }
477 :
478 2 : gnc_commodity * gncOwnerGetCurrency (const GncOwner *owner)
479 : {
480 2 : if (!owner) return NULL;
481 2 : switch (owner->type)
482 : {
483 0 : case GNC_OWNER_NONE:
484 : case GNC_OWNER_UNDEFINED:
485 : default:
486 0 : return NULL;
487 2 : case GNC_OWNER_CUSTOMER:
488 2 : return gncCustomerGetCurrency (owner->owner.customer);
489 0 : case GNC_OWNER_VENDOR:
490 0 : return gncVendorGetCurrency (owner->owner.vendor);
491 0 : case GNC_OWNER_EMPLOYEE:
492 0 : return gncEmployeeGetCurrency (owner->owner.employee);
493 0 : case GNC_OWNER_JOB:
494 0 : return gncOwnerGetCurrency (gncJobGetOwner (owner->owner.job));
495 : }
496 : }
497 :
498 14 : gboolean gncOwnerGetActive (const GncOwner *owner)
499 : {
500 14 : if (!owner) return FALSE;
501 14 : switch (owner->type)
502 : {
503 0 : case GNC_OWNER_NONE:
504 : case GNC_OWNER_UNDEFINED:
505 : default:
506 0 : return FALSE;
507 6 : case GNC_OWNER_CUSTOMER:
508 6 : return gncCustomerGetActive (owner->owner.customer);
509 4 : case GNC_OWNER_VENDOR:
510 4 : return gncVendorGetActive (owner->owner.vendor);
511 4 : case GNC_OWNER_EMPLOYEE:
512 4 : return gncEmployeeGetActive (owner->owner.employee);
513 0 : case GNC_OWNER_JOB:
514 0 : return gncJobGetActive (owner->owner.job);
515 : }
516 : }
517 :
518 74 : const GncGUID * gncOwnerGetGUID (const GncOwner *owner)
519 : {
520 74 : if (!owner) return NULL;
521 :
522 73 : switch (owner->type)
523 : {
524 0 : case GNC_OWNER_NONE:
525 : case GNC_OWNER_UNDEFINED:
526 : default:
527 0 : return NULL;
528 67 : case GNC_OWNER_CUSTOMER:
529 67 : return qof_instance_get_guid (QOF_INSTANCE(owner->owner.customer));
530 0 : case GNC_OWNER_JOB:
531 0 : return qof_instance_get_guid (QOF_INSTANCE(owner->owner.job));
532 4 : case GNC_OWNER_VENDOR:
533 4 : return qof_instance_get_guid (QOF_INSTANCE(owner->owner.vendor));
534 2 : case GNC_OWNER_EMPLOYEE:
535 2 : return qof_instance_get_guid (QOF_INSTANCE(owner->owner.employee));
536 : }
537 : }
538 :
539 : void
540 0 : gncOwnerSetActive (const GncOwner *owner, gboolean active)
541 : {
542 0 : if (!owner) return;
543 0 : switch (owner->type)
544 : {
545 0 : case GNC_OWNER_CUSTOMER:
546 0 : gncCustomerSetActive (owner->owner.customer, active);
547 0 : break;
548 0 : case GNC_OWNER_VENDOR:
549 0 : gncVendorSetActive (owner->owner.vendor, active);
550 0 : break;
551 0 : case GNC_OWNER_EMPLOYEE:
552 0 : gncEmployeeSetActive (owner->owner.employee, active);
553 0 : break;
554 0 : case GNC_OWNER_JOB:
555 0 : gncJobSetActive (owner->owner.job, active);
556 0 : break;
557 0 : case GNC_OWNER_NONE:
558 : case GNC_OWNER_UNDEFINED:
559 : default:
560 0 : break;
561 : }
562 : }
563 :
564 12 : GncGUID gncOwnerRetGUID (GncOwner *owner)
565 : {
566 12 : const GncGUID *guid = gncOwnerGetGUID (owner);
567 12 : if (guid)
568 11 : return *guid;
569 1 : return *guid_null ();
570 : }
571 :
572 2035 : const GncOwner * gncOwnerGetEndOwner (const GncOwner *owner)
573 : {
574 2035 : if (!owner) return NULL;
575 2003 : switch (owner->type)
576 : {
577 0 : case GNC_OWNER_NONE:
578 : case GNC_OWNER_UNDEFINED:
579 : default:
580 0 : return NULL;
581 1875 : case GNC_OWNER_CUSTOMER:
582 : case GNC_OWNER_VENDOR:
583 : case GNC_OWNER_EMPLOYEE:
584 1875 : return owner;
585 128 : case GNC_OWNER_JOB:
586 128 : return gncJobGetOwner (owner->owner.job);
587 : }
588 : }
589 :
590 0 : int gncOwnerCompare (const GncOwner *a, const GncOwner *b)
591 : {
592 0 : if (!a && !b) return 0;
593 0 : if (!a && b) return 1;
594 0 : if (a && !b) return -1;
595 :
596 0 : if (a->type != b->type)
597 0 : return (a->type - b->type);
598 :
599 0 : switch (a->type)
600 : {
601 0 : case GNC_OWNER_NONE:
602 : case GNC_OWNER_UNDEFINED:
603 : default:
604 0 : return 0;
605 0 : case GNC_OWNER_CUSTOMER:
606 0 : return gncCustomerCompare (a->owner.customer, b->owner.customer);
607 0 : case GNC_OWNER_VENDOR:
608 0 : return gncVendorCompare (a->owner.vendor, b->owner.vendor);
609 0 : case GNC_OWNER_EMPLOYEE:
610 0 : return gncEmployeeCompare (a->owner.employee, b->owner.employee);
611 0 : case GNC_OWNER_JOB:
612 0 : return gncJobCompare (a->owner.job, b->owner.job);
613 : }
614 : }
615 :
616 107 : const GncGUID * gncOwnerGetEndGUID (const GncOwner *owner)
617 : {
618 107 : if (!owner) return NULL;
619 36 : return gncOwnerGetGUID (gncOwnerGetEndOwner (owner));
620 : }
621 :
622 24 : void gncOwnerAttachToLot (const GncOwner *owner, GNCLot *lot)
623 : {
624 24 : if (!owner || !lot)
625 0 : return;
626 :
627 24 : gnc_lot_begin_edit (lot);
628 :
629 48 : qof_instance_set (QOF_INSTANCE (lot),
630 24 : GNC_OWNER_TYPE, (gint64)gncOwnerGetType (owner),
631 : GNC_OWNER_GUID, gncOwnerGetGUID (owner),
632 : NULL);
633 24 : gnc_lot_commit_edit (lot);
634 : }
635 :
636 681 : gboolean gncOwnerGetOwnerFromLot (GNCLot *lot, GncOwner *owner)
637 : {
638 681 : GncGUID *guid = NULL;
639 : QofBook *book;
640 681 : GncOwnerType type = GNC_OWNER_NONE;
641 681 : guint64 type64 = 0;
642 :
643 681 : if (!lot || !owner) return FALSE;
644 :
645 678 : book = gnc_lot_get_book (lot);
646 678 : qof_instance_get (QOF_INSTANCE (lot),
647 : GNC_OWNER_TYPE, &type64,
648 : GNC_OWNER_GUID, &guid,
649 : NULL);
650 678 : type = (GncOwnerType) type64;
651 678 : switch (type)
652 : {
653 212 : case GNC_OWNER_CUSTOMER:
654 212 : gncOwnerInitCustomer (owner, gncCustomerLookup (book, guid));
655 212 : break;
656 0 : case GNC_OWNER_VENDOR:
657 0 : gncOwnerInitVendor (owner, gncVendorLookup (book, guid));
658 0 : break;
659 0 : case GNC_OWNER_EMPLOYEE:
660 0 : gncOwnerInitEmployee (owner, gncEmployeeLookup (book, guid));
661 0 : break;
662 0 : case GNC_OWNER_JOB:
663 0 : gncOwnerInitJob (owner, gncJobLookup (book, guid));
664 0 : break;
665 466 : default:
666 466 : guid_free (guid);
667 466 : return FALSE;
668 : }
669 :
670 212 : guid_free (guid);
671 212 : return (owner->owner.undefined != NULL);
672 : }
673 :
674 0 : gboolean gncOwnerGetOwnerFromTxn (Transaction *txn, GncOwner *owner)
675 : {
676 0 : Split *apar_split = NULL;
677 :
678 0 : if (!txn || !owner) return FALSE;
679 :
680 0 : if (xaccTransGetTxnType (txn) == TXN_TYPE_NONE)
681 0 : return FALSE;
682 :
683 0 : apar_split = xaccTransGetFirstAPARAcctSplit (txn, TRUE);
684 0 : if (apar_split)
685 : {
686 0 : GNCLot *lot = xaccSplitGetLot (apar_split);
687 0 : GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
688 0 : if (invoice)
689 0 : gncOwnerCopy (gncInvoiceGetOwner (invoice), owner);
690 0 : else if (!gncOwnerGetOwnerFromLot (lot, owner))
691 0 : return FALSE;
692 :
693 0 : return TRUE; // Got owner from either invoice or lot
694 : }
695 :
696 0 : return FALSE;
697 : }
698 :
699 46 : gboolean gncOwnerIsValid (const GncOwner *owner)
700 : {
701 46 : if (!owner) return FALSE;
702 14 : return (owner->owner.undefined != NULL);
703 : }
704 :
705 : gboolean
706 0 : gncOwnerLotMatchOwnerFunc (GNCLot *lot, gpointer user_data)
707 : {
708 0 : const GncOwner *req_owner = user_data;
709 : GncOwner lot_owner;
710 : const GncOwner *end_owner;
711 0 : GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
712 :
713 : /* Determine the owner associated to the lot */
714 0 : if (invoice)
715 : /* Invoice lots */
716 0 : end_owner = gncOwnerGetEndOwner (gncInvoiceGetOwner (invoice));
717 0 : else if (gncOwnerGetOwnerFromLot (lot, &lot_owner))
718 : /* Pre-payment lots */
719 0 : end_owner = gncOwnerGetEndOwner (&lot_owner);
720 : else
721 0 : return FALSE;
722 :
723 : /* Is this a lot for the requested owner ? */
724 0 : return gncOwnerEqual (end_owner, req_owner);
725 : }
726 :
727 : gint
728 0 : gncOwnerLotsSortFunc (GNCLot *lotA, GNCLot *lotB)
729 : {
730 : GncInvoice *ia, *ib;
731 : time64 da, db;
732 :
733 0 : ia = gncInvoiceGetInvoiceFromLot (lotA);
734 0 : ib = gncInvoiceGetInvoiceFromLot (lotB);
735 :
736 0 : if (ia)
737 0 : da = gncInvoiceGetDateDue (ia);
738 : else
739 0 : da = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_earliest_split (lotA)));
740 :
741 0 : if (ib)
742 0 : db = gncInvoiceGetDateDue (ib);
743 : else
744 0 : db = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_earliest_split (lotB)));
745 :
746 0 : return (da > db) - (da < db);
747 : }
748 :
749 : GNCLot *
750 14 : gncOwnerCreatePaymentLotSecs (const GncOwner *owner, Transaction **preset_txn,
751 : Account *posted_acc, Account *xfer_acc,
752 : gnc_numeric amount, gnc_numeric exch, time64 date,
753 : const char *memo, const char *num)
754 : {
755 : QofBook *book;
756 : Split *split;
757 : const char *name;
758 : gnc_commodity *post_comm, *xfer_comm;
759 14 : Split *xfer_split = NULL;
760 14 : Transaction *txn = NULL;
761 : GNCLot *payment_lot;
762 14 : gnc_numeric xfer_amount = gnc_numeric_zero();
763 14 : gnc_numeric txn_value = gnc_numeric_zero();
764 :
765 : /* Verify our arguments */
766 14 : if (!owner || !posted_acc || !xfer_acc) return NULL;
767 14 : g_return_val_if_fail (owner->owner.undefined != NULL, NULL);
768 :
769 : /* Compute the ancillary data */
770 14 : book = gnc_account_get_book (posted_acc);
771 14 : name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner));
772 14 : post_comm = xaccAccountGetCommodity (posted_acc);
773 14 : xfer_comm = xaccAccountGetCommodity (xfer_acc);
774 :
775 : // reverse = use_reversed_payment_amounts(owner);
776 :
777 14 : if (preset_txn && *preset_txn)
778 0 : txn = *preset_txn;
779 :
780 14 : if (txn)
781 : {
782 0 : int i = 0;
783 :
784 : /* Pre-existing transaction was specified. We completely clear it,
785 : * except for a pre-existing transfer split. We're very conservative
786 : * in preserving that one as it may have been reconciled already. */
787 0 : xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc);
788 0 : xaccTransBeginEdit (txn);
789 0 : while (i < xaccTransCountSplits(txn))
790 : {
791 0 : Split *split = xaccTransGetSplit (txn, i);
792 0 : if (split == xfer_split)
793 0 : ++i;
794 : else
795 0 : xaccSplitDestroy(split);
796 : }
797 : /* Note: don't commit transaction now - that would insert an imbalance split.*/
798 : }
799 : else
800 : {
801 14 : txn = xaccMallocTransaction (book);
802 14 : xaccTransBeginEdit (txn);
803 : }
804 :
805 : /* Complete transaction setup */
806 14 : xaccTransSetDescription (txn, name ? name : "");
807 28 : if (!gnc_commodity_equal(xaccTransGetCurrency (txn), post_comm) &&
808 14 : !gnc_commodity_equal (xaccTransGetCurrency (txn), xfer_comm))
809 14 : xaccTransSetCurrency (txn, xfer_comm);
810 :
811 : /* With all commodities involved known, define split amounts and txn value.
812 : * - post amount (amount passed in as parameter) is always in post_acct commodity,
813 : * - xfer amount requires conversion if the xfer account has a different
814 : * commodity than the post account.
815 : * - txn value requires conversion if the post account has a different
816 : * commodity than the transaction */
817 14 : if (gnc_commodity_equal(post_comm, xfer_comm))
818 14 : xfer_amount = amount;
819 : else
820 0 : xfer_amount = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
821 : GNC_HOW_RND_ROUND_HALF_UP);
822 :
823 14 : if (gnc_commodity_equal(post_comm, xaccTransGetCurrency (txn)))
824 14 : txn_value = amount;
825 : else
826 0 : txn_value = gnc_numeric_mul (amount, exch, GNC_DENOM_AUTO,
827 : GNC_HOW_RND_ROUND_HALF_UP);
828 :
829 : /* Insert a split for the transfer account if we don't have one yet */
830 14 : if (!xfer_split)
831 : {
832 : /* The split for the transfer account */
833 14 : xfer_split = xaccMallocSplit (book);
834 14 : xaccSplitSetMemo (xfer_split, memo);
835 : /* set per book option */
836 14 : gnc_set_num_action (NULL, xfer_split, num, _("Payment"));
837 14 : xaccAccountBeginEdit (xfer_acc);
838 14 : xaccAccountInsertSplit (xfer_acc, xfer_split);
839 14 : xaccAccountCommitEdit (xfer_acc);
840 14 : xaccTransAppendSplit (txn, xfer_split);
841 :
842 14 : xaccSplitSetAmount(xfer_split, xfer_amount); /* Payment in xfer account currency */
843 14 : xaccSplitSetValue(xfer_split, txn_value); /* Payment in transaction currency */
844 : }
845 : /* For a pre-existing xfer split, let's check if the amount and value
846 : * have changed. If so, update them and unreconcile. */
847 0 : else if (!gnc_numeric_equal (xaccSplitGetAmount (xfer_split), xfer_amount) ||
848 0 : !gnc_numeric_equal (xaccSplitGetValue (xfer_split), txn_value))
849 : {
850 0 : xaccSplitSetAmount (xfer_split, xfer_amount);
851 0 : xaccSplitSetValue (xfer_split, txn_value);
852 0 : xaccSplitSetReconcile (xfer_split, NREC);
853 : }
854 :
855 : /* Add a split in the post account */
856 14 : split = xaccMallocSplit (book);
857 14 : xaccSplitSetMemo (split, memo);
858 : /* set per book option */
859 14 : xaccSplitSetAction (split, _("Payment"));
860 14 : xaccAccountBeginEdit (posted_acc);
861 14 : xaccAccountInsertSplit (posted_acc, split);
862 14 : xaccAccountCommitEdit (posted_acc);
863 14 : xaccTransAppendSplit (txn, split);
864 14 : xaccSplitSetAmount (split, gnc_numeric_neg (amount));
865 14 : xaccSplitSetValue (split, gnc_numeric_neg (txn_value));
866 :
867 : /* Create a new lot for the payment */
868 14 : payment_lot = gnc_lot_new (book);
869 14 : gncOwnerAttachToLot (owner, payment_lot);
870 14 : gnc_lot_add_split (payment_lot, split);
871 :
872 : /* Mark the transaction as a payment */
873 14 : xaccTransSetNum (txn, num);
874 14 : xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT);
875 :
876 : /* Set date for transaction */
877 14 : xaccTransSetDateEnteredSecs (txn, gnc_time (NULL));
878 14 : xaccTransSetDatePostedSecs (txn, date);
879 :
880 : /* Commit this new transaction */
881 14 : xaccTransCommitEdit (txn);
882 14 : if (preset_txn)
883 13 : *preset_txn = txn;
884 :
885 14 : return payment_lot;
886 : }
887 :
888 : typedef enum
889 : {
890 : is_equal = 8,
891 : is_more = 4,
892 : is_less = 2,
893 : is_pay_split = 1
894 : } split_flags;
895 :
896 14 : Split *gncOwnerFindOffsettingSplit (GNCLot *lot, gnc_numeric target_amount)
897 : {
898 14 : SplitList *ls_iter = NULL;
899 14 : Split *best_split = NULL;
900 14 : gnc_numeric best_amt = { 0, 1};
901 14 : gint best_flags = 0;
902 :
903 14 : if (!lot)
904 0 : return NULL;
905 :
906 28 : for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
907 : {
908 14 : Split *split = ls_iter->data;
909 :
910 14 : if (!split)
911 0 : continue;
912 :
913 :
914 14 : Transaction *txn = xaccSplitGetParent (split);
915 14 : if (!txn)
916 : {
917 : // Ooops - the split doesn't belong to any transaction !
918 : // This is not expected so issue a warning and continue with next split
919 0 : PWARN("Encountered a split in a payment lot that's not part of any transaction. "
920 : "This is unexpected! Skipping split %p.", split);
921 0 : continue;
922 : }
923 :
924 : // Check if this split has the opposite sign of the target amount we want to offset
925 14 : gnc_numeric split_amount = xaccSplitGetAmount (split);
926 14 : if (gnc_numeric_positive_p (target_amount) == gnc_numeric_positive_p (split_amount))
927 0 : continue;
928 :
929 : // Ok we have found a split that potentially can offset the target value
930 : // Let's see if it's better than what we have found already.
931 14 : gint amt_cmp = gnc_numeric_compare (gnc_numeric_abs (split_amount),
932 : gnc_numeric_abs (target_amount));
933 14 : gint new_flags = 0;
934 14 : if (amt_cmp == 0)
935 8 : new_flags += is_equal;
936 6 : else if (amt_cmp > 0)
937 0 : new_flags += is_more;
938 : else
939 6 : new_flags += is_less;
940 :
941 14 : if (xaccTransGetTxnType (txn) != TXN_TYPE_LINK)
942 14 : new_flags += is_pay_split;
943 :
944 28 : if ((new_flags >= best_flags) &&
945 14 : (gnc_numeric_compare (gnc_numeric_abs (split_amount),
946 : gnc_numeric_abs (best_amt)) > 0))
947 : {
948 : // The new split is a better match than what we found so far
949 14 : best_split = split;
950 14 : best_flags = new_flags;
951 14 : best_amt = split_amount;
952 : }
953 : }
954 :
955 14 : return best_split;
956 : }
957 :
958 : gboolean
959 0 : gncOwnerReduceSplitTo (Split *split, gnc_numeric target_amount)
960 : {
961 0 : gnc_numeric split_amt = xaccSplitGetAmount (split);
962 0 : if (gnc_numeric_positive_p (split_amt) != gnc_numeric_positive_p (target_amount))
963 0 : return FALSE; // Split and target amount have to be of the same sign
964 :
965 0 : if (gnc_numeric_equal (split_amt, target_amount))
966 0 : return FALSE; // Split already has the target amount
967 :
968 0 : if (gnc_numeric_zero_p (split_amt))
969 0 : return FALSE; // We can't reduce a split that already has zero amount
970 :
971 0 : Transaction *txn = xaccSplitGetParent (split);
972 0 : xaccTransBeginEdit (txn);
973 :
974 : /* Calculate new value for reduced split. This can be different from
975 : * the reduced split's new amount (when account and transaction
976 : * commodity differ) */
977 0 : gnc_numeric split_val = xaccSplitGetValue (split);
978 0 : gnc_numeric exch = gnc_numeric_div (split_val, split_amt,
979 : GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
980 :
981 0 : gint64 txn_comm_fraction = gnc_commodity_get_fraction (xaccTransGetCurrency (txn));
982 0 : gnc_numeric target_val = gnc_numeric_mul (target_amount, exch,
983 : txn_comm_fraction,
984 : GNC_HOW_RND_ROUND_HALF_UP);
985 0 : xaccSplitSetAmount (split, target_amount);
986 0 : xaccSplitSetValue (split, target_val);
987 :
988 : /* Calculate amount and value for remainder split.
989 : * Note we calculate the remaining value by subtracting the new target value
990 : * from the original split's value rather than multiplying the remaining
991 : * amount with the exchange rate to avoid imbalances due to rounding errors. */
992 0 : gnc_numeric rem_amt = gnc_numeric_sub (split_amt, target_amount, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
993 0 : gnc_numeric rem_val = gnc_numeric_sub (split_val, target_val, GNC_DENOM_AUTO, GNC_HOW_DENOM_FIXED);
994 :
995 0 : Split *rem_split = xaccMallocSplit (xaccSplitGetBook (split));
996 0 : xaccSplitCopyOnto (split, rem_split);
997 0 : xaccSplitSetAmount (rem_split, rem_amt);
998 0 : xaccSplitSetValue (rem_split, rem_val);
999 0 : xaccSplitSetParent (rem_split, txn);
1000 :
1001 0 : xaccTransCommitEdit (txn);
1002 :
1003 0 : GNCLot *lot = xaccSplitGetLot (split);
1004 0 : gnc_lot_add_split (lot, rem_split);
1005 :
1006 0 : return TRUE;
1007 : }
1008 :
1009 : void
1010 0 : gncOwnerSetLotLinkMemo (Transaction *ll_txn)
1011 : {
1012 0 : gchar *memo_prefix = _("Offset between business items: ");
1013 : gchar *new_memo;
1014 : SplitList *lts_iter;
1015 0 : SplitList *splits = NULL, *siter;
1016 0 : GList *titles = NULL, *titer;
1017 :
1018 0 : if (!ll_txn)
1019 0 : return;
1020 :
1021 0 : if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
1022 0 : return;
1023 :
1024 : // Find all splits in the lot link transaction that are also in a document lot
1025 0 : for (lts_iter = xaccTransGetSplitList (ll_txn); lts_iter; lts_iter = lts_iter->next)
1026 : {
1027 0 : Split *split = lts_iter->data;
1028 : GNCLot *lot;
1029 : GncInvoice *invoice;
1030 : gchar *title;
1031 :
1032 0 : if (!split)
1033 0 : continue;
1034 :
1035 0 : lot = xaccSplitGetLot (split);
1036 0 : if (!lot)
1037 0 : continue;
1038 :
1039 0 : invoice = gncInvoiceGetInvoiceFromLot (lot);
1040 0 : if (!invoice)
1041 0 : continue;
1042 :
1043 0 : title = g_strdup_printf ("%s %s", gncInvoiceGetTypeString (invoice), gncInvoiceGetID (invoice));
1044 :
1045 0 : titles = g_list_prepend (titles, title);
1046 0 : splits = g_list_prepend (splits, split); // splits don't need to be sorted
1047 : }
1048 :
1049 0 : if (!titles)
1050 0 : return; // We didn't find document lots
1051 :
1052 0 : titles = g_list_sort (titles, (GCompareFunc)g_strcmp0);
1053 :
1054 : // Create the memo as we'd want it to be
1055 0 : new_memo = g_strconcat (memo_prefix, titles->data, NULL);
1056 0 : for (titer = titles->next; titer; titer = titer->next)
1057 : {
1058 0 : gchar *tmp_memo = g_strconcat (new_memo, " - ", titer->data, NULL);
1059 0 : g_free (new_memo);
1060 0 : new_memo = tmp_memo;
1061 : }
1062 0 : g_list_free_full (titles, g_free);
1063 :
1064 : // Update the memos of all the splits we found previously (if needed)
1065 0 : for (siter = splits; siter; siter = siter->next)
1066 : {
1067 0 : if (g_strcmp0 (xaccSplitGetMemo (siter->data), new_memo) != 0)
1068 0 : xaccSplitSetMemo (siter->data, new_memo);
1069 : }
1070 :
1071 0 : g_list_free (splits);
1072 0 : g_free (new_memo);
1073 : }
1074 :
1075 : /* Find an existing lot link transaction in the given lot
1076 : * Only use a lot link that already links at least two
1077 : * documents (to avoid perpetuating the lot link proliferation
1078 : * that happened in 2.6.0-2.6.3).
1079 : */
1080 : static Transaction *
1081 0 : get_ll_transaction_from_lot (GNCLot *lot)
1082 : {
1083 : SplitList *ls_iter;
1084 :
1085 : /* This should really only be called on a document lot */
1086 0 : if (!gncInvoiceGetInvoiceFromLot (lot))
1087 0 : return NULL;
1088 :
1089 : /* The given lot is a valid document lot. Now iterate over all
1090 : * other lot links in this lot to find one more document lot.
1091 : */
1092 0 : for (ls_iter = gnc_lot_get_split_list (lot); ls_iter; ls_iter = ls_iter->next)
1093 : {
1094 0 : Split *ls = ls_iter->data;
1095 0 : Transaction *ll_txn = xaccSplitGetParent (ls);
1096 : SplitList *ts_iter;
1097 :
1098 0 : if (xaccTransGetTxnType (ll_txn) != TXN_TYPE_LINK)
1099 0 : continue;
1100 :
1101 0 : for (ts_iter = xaccTransGetSplitList (ll_txn); ts_iter; ts_iter = ts_iter->next)
1102 : {
1103 0 : Split *ts = ts_iter->data;
1104 0 : GNCLot *tslot = xaccSplitGetLot (ts);
1105 :
1106 0 : if (!tslot)
1107 0 : continue;
1108 :
1109 0 : if (tslot == lot)
1110 0 : continue;
1111 :
1112 0 : if (gncInvoiceGetInvoiceFromLot (lot))
1113 0 : return ll_txn; /* Got one more document lot - mission accomplished */
1114 : }
1115 : }
1116 :
1117 : /* The lot doesn't have an ll_txn with the requested criteria... */
1118 0 : return NULL;
1119 : }
1120 :
1121 : static void
1122 0 : gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner)
1123 : {
1124 0 : const gchar *action = _("Lot Link");
1125 0 : Account *acct = gnc_lot_get_account (from_lot);
1126 0 : const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner));
1127 0 : Transaction *ll_txn = NULL;
1128 : gnc_numeric from_lot_bal, to_lot_bal;
1129 : time64 from_time, to_time;
1130 : time64 time_posted;
1131 : Split *split;
1132 :
1133 : /* Sanity check */
1134 0 : if (!gncInvoiceGetInvoiceFromLot (from_lot) ||
1135 0 : !gncInvoiceGetInvoiceFromLot (to_lot))
1136 0 : return;
1137 :
1138 : /* Determine transaction date based on lot splits */
1139 0 : from_time = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot)));
1140 0 : to_time = xaccTransRetDatePosted (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot)));
1141 0 : if (from_time >= to_time)
1142 0 : time_posted = from_time;
1143 : else
1144 0 : time_posted = to_time;
1145 :
1146 : /* Figure out how much we can offset between the lots */
1147 0 : from_lot_bal = gnc_lot_get_balance (from_lot);
1148 0 : to_lot_bal = gnc_lot_get_balance (to_lot);
1149 0 : if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal),
1150 : gnc_numeric_abs (to_lot_bal)) > 0)
1151 0 : from_lot_bal = gnc_numeric_neg (to_lot_bal);
1152 : else
1153 0 : to_lot_bal = gnc_numeric_neg (from_lot_bal);
1154 :
1155 0 : xaccAccountBeginEdit (acct);
1156 :
1157 : /* Look for a pre-existing lot link we can extend */
1158 0 : ll_txn = get_ll_transaction_from_lot (from_lot);
1159 :
1160 0 : if (!ll_txn)
1161 0 : ll_txn = get_ll_transaction_from_lot (to_lot);
1162 :
1163 0 : if (!ll_txn)
1164 : {
1165 : /* No pre-existing lot link. Create one. */
1166 0 : ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot));
1167 0 : xaccTransBeginEdit (ll_txn);
1168 0 : xaccTransSetDescription (ll_txn, name ? name : "(Unknown)");
1169 0 : xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct));
1170 0 : xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL));
1171 0 : xaccTransSetDatePostedSecs (ll_txn, time_posted);
1172 0 : xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK);
1173 : }
1174 : else
1175 : {
1176 0 : time64 time = xaccTransRetDatePosted (ll_txn);
1177 0 : xaccTransBeginEdit (ll_txn);
1178 :
1179 : /* Maybe we need to update the post date of the transaction ? */
1180 0 : if (time_posted > time)
1181 0 : xaccTransSetDatePostedSecs (ll_txn, time_posted);
1182 : }
1183 :
1184 : /* Create a split for the from_lot */
1185 0 : split = xaccMallocSplit (gnc_lot_get_book (from_lot));
1186 : /* set Action using utility function */
1187 0 : gnc_set_num_action (NULL, split, NULL, action);
1188 0 : xaccAccountInsertSplit (acct, split);
1189 0 : xaccTransAppendSplit (ll_txn, split);
1190 : /* To offset the lot balance, the split must be of the opposite sign */
1191 0 : xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct));
1192 0 : gnc_lot_add_split (from_lot, split);
1193 :
1194 : /* Create a split for the to_lot */
1195 0 : split = xaccMallocSplit (gnc_lot_get_book (to_lot));
1196 : /* set Action using utility function */
1197 0 : gnc_set_num_action (NULL, split, NULL, action);
1198 0 : xaccAccountInsertSplit (acct, split);
1199 0 : xaccTransAppendSplit (ll_txn, split);
1200 : /* To offset the lot balance, the split must be of the opposite sign */
1201 0 : xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct));
1202 0 : gnc_lot_add_split (to_lot, split);
1203 :
1204 0 : xaccTransCommitEdit (ll_txn);
1205 :
1206 :
1207 : /* Do some post-cleaning on the lots
1208 : * The above actions may have created splits that are
1209 : * in the same transaction and lot. These can be merged.
1210 : */
1211 0 : xaccScrubMergeLotSubSplits (to_lot, FALSE);
1212 0 : xaccScrubMergeLotSubSplits (from_lot, FALSE);
1213 : /* And finally set the same memo for all remaining splits
1214 : * It's a convenience for the users to identify all documents
1215 : * involved in the link.
1216 : */
1217 0 : gncOwnerSetLotLinkMemo (ll_txn);
1218 0 : xaccAccountCommitEdit (acct);
1219 : }
1220 :
1221 14 : static void gncOwnerOffsetLots (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner)
1222 : {
1223 : gnc_numeric target_offset;
1224 : Split *split;
1225 :
1226 : /* from lot should not be a document lot because we're removing a split from there ! */
1227 14 : if (gncInvoiceGetInvoiceFromLot (from_lot))
1228 : {
1229 0 : PWARN ("from_lot %p is a document lot. That is not allowed in gncOwnerOffsetLots", from_lot);
1230 0 : return;
1231 : }
1232 :
1233 : /* Get best matching split from from_lot to offset to_lot */
1234 14 : target_offset = gnc_lot_get_balance (to_lot);
1235 14 : if (gnc_numeric_zero_p (target_offset))
1236 0 : return; // to_lot is already balanced, nothing more to do
1237 :
1238 14 : split = gncOwnerFindOffsettingSplit (from_lot, target_offset);
1239 14 : if (!split)
1240 0 : return; // No suitable offsetting split found, nothing more to do
1241 :
1242 : /* If the offsetting split is bigger than the amount needed to balance
1243 : * to_lot, reduce the split so its reduced amount closes to_lot exactly.
1244 : * Note the negation in the reduction function. The split must be of
1245 : * opposite sign of to_lot's balance in order to be able to close it.
1246 : */
1247 14 : if (gnc_numeric_compare (gnc_numeric_abs (xaccSplitGetAmount (split)),
1248 : gnc_numeric_abs (target_offset)) > 0)
1249 0 : gncOwnerReduceSplitTo (split, gnc_numeric_neg (target_offset));
1250 :
1251 : /* Move the reduced split from from_lot to to_lot */
1252 14 : gnc_lot_add_split (to_lot, split);
1253 :
1254 : }
1255 :
1256 15 : void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots)
1257 : {
1258 : GList *left_iter;
1259 :
1260 : /* General note: in the code below the term "payment" can
1261 : * both mean a true payment or a document of
1262 : * the opposite sign (invoice vs credit note) relative to
1263 : * the lot being processed. In general this function will
1264 : * perform a balancing action on a set of lots, so you
1265 : * will also find frequent references to balancing instead. */
1266 :
1267 : /* Payments can only be applied when at least an owner
1268 : * and a list of lots to use are given */
1269 15 : if (!owner) return;
1270 15 : if (!lots) return;
1271 :
1272 44 : for (left_iter = lots; left_iter; left_iter = left_iter->next)
1273 : {
1274 29 : GNCLot *left_lot = left_iter->data;
1275 : gnc_numeric left_lot_bal;
1276 : gboolean left_lot_has_doc;
1277 29 : gboolean left_modified = FALSE;
1278 : Account *acct;
1279 : GList *right_iter;
1280 :
1281 : /* Only attempt to apply payments to open lots.
1282 : * Note that due to the iterative nature of this function lots
1283 : * in the list may become empty/closed before they are evaluated as
1284 : * base lot, so we should check this for each lot. */
1285 29 : if (!left_lot)
1286 0 : continue;
1287 29 : if (gnc_lot_count_splits (left_lot) == 0)
1288 : {
1289 0 : gnc_lot_destroy (left_lot);
1290 0 : left_iter->data = NULL;
1291 0 : continue;
1292 : }
1293 29 : if (gnc_lot_is_closed (left_lot))
1294 9 : continue;
1295 :
1296 20 : acct = gnc_lot_get_account (left_lot);
1297 20 : xaccAccountBeginEdit (acct);
1298 :
1299 20 : left_lot_bal = gnc_lot_get_balance (left_lot);
1300 20 : left_lot_has_doc = (gncInvoiceGetInvoiceFromLot (left_lot) != NULL);
1301 :
1302 : /* Attempt to offset left_lot with any of the remaining lots. To do so
1303 : * iterate over the remaining lots adding lot links or moving payments
1304 : * around.
1305 : */
1306 34 : for (right_iter = left_iter->next; right_iter; right_iter = right_iter->next)
1307 : {
1308 14 : GNCLot *right_lot = right_iter->data;
1309 : gnc_numeric right_lot_bal;
1310 : gboolean right_lot_has_doc;
1311 :
1312 : /* Only attempt to use open lots to balance the base lot.
1313 : * Note that due to the iterative nature of this function lots
1314 : * in the list may become empty/closed before they are evaluated as
1315 : * base lot, so we should check this for each lot. */
1316 14 : if (!right_lot)
1317 0 : continue;
1318 14 : if (gnc_lot_count_splits (right_lot) == 0)
1319 : {
1320 0 : gnc_lot_destroy (right_lot);
1321 0 : right_iter->data = NULL;
1322 0 : continue;
1323 : }
1324 14 : if (gnc_lot_is_closed (right_lot))
1325 0 : continue;
1326 :
1327 : /* Balancing transactions for invoice/payments can only happen
1328 : * in the same account. */
1329 14 : if (acct != gnc_lot_get_account (right_lot))
1330 0 : continue;
1331 :
1332 :
1333 : /* Only attempt to balance if the base lot and balancing lot are
1334 : * of the opposite sign. (Otherwise we would increase the balance
1335 : * of the lot - Duh */
1336 14 : right_lot_bal = gnc_lot_get_balance (right_lot);
1337 14 : if (gnc_numeric_positive_p (left_lot_bal) == gnc_numeric_positive_p (right_lot_bal))
1338 0 : continue;
1339 :
1340 : /* Ok we found two lots than can (partly) offset each other.
1341 : * Depending on the lot types, a different action is needed to accomplish this.
1342 : * 1. Both lots are document lots (invoices/credit notes)
1343 : * -> Create a lot linking transaction between the lots
1344 : * 2. Both lots are payment lots (lots without a document attached)
1345 : * -> Use part of the bigger lot to the close the smaller lot
1346 : * 3. One document lot with one payment lot
1347 : * -> Use (part of) the payment to offset (part of) the document lot,
1348 : * Which one will be closed depends on which is the bigger one
1349 : */
1350 14 : right_lot_has_doc = (gncInvoiceGetInvoiceFromLot (right_lot) != NULL);
1351 14 : if (left_lot_has_doc && right_lot_has_doc)
1352 0 : gncOwnerCreateLotLink (left_lot, right_lot, owner);
1353 14 : else if (!left_lot_has_doc && !right_lot_has_doc)
1354 0 : {
1355 0 : gint cmp = gnc_numeric_compare (gnc_numeric_abs (left_lot_bal),
1356 : gnc_numeric_abs (right_lot_bal));
1357 0 : if (cmp >= 0)
1358 0 : gncOwnerOffsetLots (left_lot, right_lot, owner);
1359 : else
1360 0 : gncOwnerOffsetLots (right_lot, left_lot, owner);
1361 : }
1362 : else
1363 : {
1364 14 : GNCLot *doc_lot = left_lot_has_doc ? left_lot : right_lot;
1365 14 : GNCLot *pay_lot = left_lot_has_doc ? right_lot : left_lot;
1366 : // Ok, let's try to move a payment from pay_lot to doc_lot
1367 14 : gncOwnerOffsetLots (pay_lot, doc_lot, owner);
1368 : }
1369 :
1370 : /* If we get here, then right_lot was modified
1371 : * If the lot has a document, send an event for send an event for it as well
1372 : * so it gets potentially updated as paid */
1373 :
1374 : {
1375 14 : GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(right_lot);
1376 14 : if (this_invoice)
1377 14 : qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
1378 : }
1379 14 : left_modified = TRUE;
1380 : }
1381 :
1382 : /* If left_lot was modified and the lot has a document,
1383 : * send an event for send an event for it as well
1384 : * so it gets potentially updated as paid */
1385 20 : if (left_modified)
1386 : {
1387 14 : GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(left_lot);
1388 14 : if (this_invoice)
1389 0 : qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL);
1390 : }
1391 20 : xaccAccountCommitEdit (acct);
1392 :
1393 : }
1394 : }
1395 :
1396 : /*
1397 : * Create a payment of "amount" for the owner and match it with
1398 : * the set of lots passed in.
1399 : * If
1400 : * - no lots were given
1401 : * - auto_pay is true
1402 : * then all open lots for the owner are considered.
1403 : */
1404 : void
1405 1 : gncOwnerApplyPaymentSecs (const GncOwner *owner, Transaction **preset_txn,
1406 : GList *lots, Account *posted_acc, Account *xfer_acc,
1407 : gnc_numeric amount, gnc_numeric exch, time64 date,
1408 : const char *memo, const char *num, gboolean auto_pay)
1409 : {
1410 1 : GNCLot *payment_lot = NULL;
1411 1 : GList *selected_lots = NULL;
1412 :
1413 : /* Verify our arguments */
1414 1 : if (!owner || !posted_acc
1415 1 : || (!xfer_acc && !gnc_numeric_zero_p (amount)) ) return;
1416 1 : g_return_if_fail (owner->owner.undefined);
1417 :
1418 1 : if (lots)
1419 1 : selected_lots = lots;
1420 0 : else if (auto_pay)
1421 0 : selected_lots = xaccAccountFindOpenLots (posted_acc, gncOwnerLotMatchOwnerFunc,
1422 : (gpointer)owner, NULL);
1423 :
1424 : /* If there's a real amount to transfer create a lot for this payment */
1425 1 : if (!gnc_numeric_zero_p (amount))
1426 1 : payment_lot = gncOwnerCreatePaymentLotSecs (owner, preset_txn,
1427 : posted_acc, xfer_acc,
1428 : amount, exch, date, memo,
1429 : num);
1430 :
1431 : /* And link the selected lots and the payment lot together as well
1432 : * as possible. If the payment was bigger than the selected
1433 : * documents/overpayments, only part of the payment will be
1434 : * used. Similarly if more documents were selected than the
1435 : * payment value set, not all documents will be marked as paid. */
1436 1 : if (payment_lot)
1437 1 : selected_lots = g_list_prepend (selected_lots, payment_lot);
1438 :
1439 1 : gncOwnerAutoApplyPaymentsWithLots (owner, selected_lots);
1440 1 : g_list_free (selected_lots);
1441 : }
1442 :
1443 : GList *
1444 0 : gncOwnerGetAccountTypesList (const GncOwner *owner)
1445 : {
1446 0 : g_return_val_if_fail (owner, NULL);
1447 :
1448 0 : switch (gncOwnerGetType (owner))
1449 : {
1450 0 : case GNC_OWNER_CUSTOMER:
1451 0 : return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_RECEIVABLE));
1452 0 : case GNC_OWNER_VENDOR:
1453 : case GNC_OWNER_EMPLOYEE:
1454 0 : return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_PAYABLE));
1455 : break;
1456 0 : default:
1457 0 : return (g_list_prepend (NULL, (gpointer)ACCT_TYPE_NONE));
1458 : }
1459 : }
1460 :
1461 : GList *
1462 1 : gncOwnerGetCommoditiesList (const GncOwner *owner)
1463 : {
1464 1 : g_return_val_if_fail (owner, NULL);
1465 1 : g_return_val_if_fail (gncOwnerGetCurrency(owner), NULL);
1466 :
1467 1 : return (g_list_prepend (NULL, gncOwnerGetCurrency(owner)));
1468 : }
1469 :
1470 : /*********************************************************************/
1471 : /* Owner balance calculation routines */
1472 :
1473 : /*
1474 : * Given an owner, extract the open balance from the owner and then
1475 : * convert it to the desired currency.
1476 : */
1477 : gnc_numeric
1478 0 : gncOwnerGetBalanceInCurrency (const GncOwner *owner,
1479 : const gnc_commodity *report_currency)
1480 : {
1481 0 : gnc_numeric balance = gnc_numeric_zero ();
1482 : QofBook *book;
1483 : gnc_commodity *owner_currency;
1484 : GNCPriceDB *pdb;
1485 0 : const gnc_numeric *cached_balance = NULL;
1486 :
1487 0 : g_return_val_if_fail (owner, gnc_numeric_zero ());
1488 :
1489 0 : book = qof_instance_get_book (qofOwnerGetOwner (owner));
1490 0 : owner_currency = gncOwnerGetCurrency (owner);
1491 :
1492 0 : cached_balance = gncOwnerGetCachedBalance (owner);
1493 0 : if (cached_balance)
1494 0 : balance = *cached_balance;
1495 : else
1496 : {
1497 : /* No valid cache value found for balance. Let's recalculate */
1498 0 : GList *acct_list = gnc_account_get_descendants (gnc_book_get_root_account (book));
1499 0 : GList *acct_types = gncOwnerGetAccountTypesList (owner);
1500 : GList *acct_node;
1501 :
1502 : /* For each account */
1503 0 : for (acct_node = acct_list; acct_node; acct_node = acct_node->next)
1504 : {
1505 0 : Account *account = acct_node->data;
1506 0 : GList *lot_list = NULL, *lot_node;
1507 :
1508 : /* Check if this account can have lots for the owner, otherwise skip to next */
1509 0 : if (g_list_index (acct_types, (gpointer)xaccAccountGetType (account))
1510 : == -1)
1511 0 : continue;
1512 :
1513 :
1514 0 : if (!gnc_commodity_equal (owner_currency, xaccAccountGetCommodity (account)))
1515 0 : continue;
1516 :
1517 : /* Get a list of open lots for this owner and account */
1518 0 : lot_list = xaccAccountFindOpenLots (account, gncOwnerLotMatchOwnerFunc,
1519 : (gpointer)owner, NULL);
1520 : /* For each lot */
1521 0 : for (lot_node = lot_list; lot_node; lot_node = lot_node->next)
1522 : {
1523 0 : GNCLot *lot = lot_node->data;
1524 0 : gnc_numeric lot_balance = gnc_lot_get_balance (lot);
1525 0 : GncInvoice *invoice = gncInvoiceGetInvoiceFromLot(lot);
1526 0 : if (invoice)
1527 0 : balance = gnc_numeric_add (balance, lot_balance,
1528 0 : gnc_commodity_get_fraction (owner_currency), GNC_HOW_RND_ROUND_HALF_UP);
1529 : }
1530 0 : g_list_free (lot_list);
1531 : }
1532 0 : g_list_free (acct_list);
1533 0 : g_list_free (acct_types);
1534 :
1535 0 : gncOwnerSetCachedBalance (owner, &balance);
1536 : }
1537 :
1538 0 : pdb = gnc_pricedb_get_db (book);
1539 :
1540 0 : if (report_currency)
1541 0 : balance = gnc_pricedb_convert_balance_latest_price (
1542 : pdb, balance, owner_currency, report_currency);
1543 :
1544 0 : return balance;
1545 : }
1546 :
1547 :
1548 : /* XXX: Yea, this is broken, but it should work fine for Queries.
1549 : * We're single-threaded, right?
1550 : */
1551 : static GncOwner *
1552 33 : owner_from_lot (GNCLot *lot)
1553 : {
1554 : static GncOwner owner;
1555 :
1556 33 : if (!lot) return NULL;
1557 12 : if (gncOwnerGetOwnerFromLot (lot, &owner))
1558 4 : return &owner;
1559 :
1560 8 : return NULL;
1561 : }
1562 :
1563 : static void
1564 83 : reg_lot (void)
1565 : {
1566 : static QofParam params[] =
1567 : {
1568 : { OWNER_FROM_LOT, _GNC_MOD_NAME, (QofAccessFunc)owner_from_lot, NULL },
1569 : { NULL },
1570 : };
1571 :
1572 83 : qof_class_register (GNC_ID_LOT, NULL, params);
1573 83 : }
1574 :
1575 0 : gboolean gncOwnerGetOwnerFromTypeGuid (QofBook *book, GncOwner *owner, QofIdType type, GncGUID *guid)
1576 : {
1577 0 : if (!book || !owner || !type || !guid) return FALSE;
1578 :
1579 0 : if (0 == g_strcmp0(type, GNC_ID_CUSTOMER))
1580 : {
1581 0 : GncCustomer *customer = gncCustomerLookup(book, guid);
1582 0 : gncOwnerInitCustomer(owner, customer);
1583 0 : return (NULL != customer);
1584 : }
1585 0 : else if (0 == g_strcmp0(type, GNC_ID_JOB))
1586 : {
1587 0 : GncJob *job = gncJobLookup(book, guid);
1588 0 : gncOwnerInitJob(owner, job);
1589 0 : return (NULL != job);
1590 : }
1591 0 : else if (0 == g_strcmp0(type, GNC_ID_VENDOR))
1592 : {
1593 0 : GncVendor *vendor = gncVendorLookup(book, guid);
1594 0 : gncOwnerInitVendor(owner, vendor);
1595 0 : return (NULL != vendor);
1596 : }
1597 0 : else if (0 == g_strcmp0(type, GNC_ID_EMPLOYEE))
1598 : {
1599 0 : GncEmployee *employee = gncEmployeeLookup(book, guid);
1600 0 : gncOwnerInitEmployee(owner, employee);
1601 0 : return (NULL != employee);
1602 : }
1603 0 : return 0;
1604 : }
1605 :
1606 83 : gboolean gncOwnerRegister (void)
1607 : {
1608 : static QofParam params[] =
1609 : {
1610 : { OWNER_TYPE, QOF_TYPE_INT64, (QofAccessFunc)gncOwnerGetType, NULL },
1611 : { OWNER_CUSTOMER, GNC_ID_CUSTOMER, (QofAccessFunc)gncOwnerGetCustomer, NULL },
1612 : { OWNER_JOB, GNC_ID_JOB, (QofAccessFunc)gncOwnerGetJob, NULL },
1613 : { OWNER_VENDOR, GNC_ID_VENDOR, (QofAccessFunc)gncOwnerGetVendor, NULL },
1614 : { OWNER_EMPLOYEE, GNC_ID_EMPLOYEE, (QofAccessFunc)gncOwnerGetEmployee, NULL },
1615 : { OWNER_PARENT, GNC_ID_OWNER, (QofAccessFunc)gncOwnerGetEndOwner, NULL },
1616 : { OWNER_PARENTG, QOF_TYPE_GUID, (QofAccessFunc)gncOwnerGetEndGUID, NULL },
1617 : { OWNER_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncOwnerGetName, NULL },
1618 : { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)gncOwnerGetGUID, NULL },
1619 : { NULL },
1620 : };
1621 :
1622 83 : qof_class_register (GNC_ID_OWNER, (QofSortFunc)gncOwnerCompare, params);
1623 83 : reg_lot ();
1624 :
1625 83 : return TRUE;
1626 : }
1627 :
1628 : const gnc_numeric*
1629 0 : gncOwnerGetCachedBalance (const GncOwner *owner)
1630 : {
1631 0 : if (!owner) return NULL;
1632 :
1633 0 : if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
1634 0 : return gncCustomerGetCachedBalance (gncOwnerGetCustomer (owner));
1635 0 : else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
1636 0 : return gncVendorGetCachedBalance (gncOwnerGetVendor (owner));
1637 0 : else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
1638 0 : return gncEmployeeGetCachedBalance (gncOwnerGetEmployee (owner));
1639 :
1640 0 : return NULL;
1641 : }
1642 :
1643 0 : void gncOwnerSetCachedBalance (const GncOwner *owner, const gnc_numeric *new_bal)
1644 : {
1645 0 : if (!owner) return;
1646 :
1647 0 : if (gncOwnerGetType (owner) == GNC_OWNER_CUSTOMER)
1648 0 : gncCustomerSetCachedBalance (gncOwnerGetCustomer (owner), new_bal);
1649 0 : else if (gncOwnerGetType (owner) == GNC_OWNER_VENDOR)
1650 0 : gncVendorSetCachedBalance (gncOwnerGetVendor (owner), new_bal);
1651 0 : else if (gncOwnerGetType (owner) == GNC_OWNER_EMPLOYEE)
1652 0 : gncEmployeeSetCachedBalance (gncOwnerGetEmployee (owner), new_bal);
1653 : }
|