Line data Source code
1 : /********************************************************************\
2 : * Transaction.c -- transaction implementation *
3 : * Copyright (C) 1997 Robin D. Clark *
4 : * Copyright (C) 1997-2003 Linas Vepstas <linas@linas.org> *
5 : * Copyright (C) 2000 Bill Gribble <grib@billgribble.com> *
6 : * Copyright (c) 2006 David Hampton <hampton@employees.org> *
7 : * *
8 : * This program is free software; you can redistribute it and/or *
9 : * modify it under the terms of the GNU General Public License as *
10 : * published by the Free Software Foundation; either version 2 of *
11 : * the License, or (at your option) any later version. *
12 : * *
13 : * This program is distributed in the hope that it will be useful, *
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 : * GNU General Public License for more details. *
17 : * *
18 : * You should have received a copy of the GNU General Public License*
19 : * along with this program; if not, contact: *
20 : * *
21 : * Free Software Foundation Voice: +1-617-542-5942 *
22 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23 : * Boston, MA 02110-1301, USA gnu@gnu.org *
24 : * *
25 : \********************************************************************/
26 :
27 : #include "qofinstance.h"
28 : #include <config.h>
29 :
30 : #include <platform.h>
31 : #if PLATFORM(WINDOWS)
32 : #include <windows.h>
33 : #endif
34 :
35 : #include <glib.h>
36 : #include <glib/gi18n.h>
37 : #include <stdlib.h>
38 : #include <string.h>
39 : #include <stdint.h>
40 : #include <time.h>
41 : #ifdef HAVE_UNISTD_H
42 : # include <unistd.h>
43 : #endif
44 :
45 : #include "AccountP.hpp"
46 : #include "Scrub.h"
47 : #include "Scrub3.h"
48 : #include "TransactionP.hpp"
49 : #include "SplitP.hpp"
50 : #include "TransLog.h"
51 : #include "cap-gains.h"
52 : #include "gnc-commodity.h"
53 : #include "gnc-engine.h"
54 : #include "gnc-lot.h"
55 : #include "gnc-event.h"
56 : #include <gnc-date.h>
57 : #include "SchedXaction.h"
58 : #include "gncBusiness.h"
59 : #include <qofinstance-p.h>
60 : #include "gncInvoice.h"
61 : #include "gncOwner.h"
62 :
63 : /* Notes about xaccTransBeginEdit(), xaccTransCommitEdit(), and
64 : * xaccTransRollback():
65 : *
66 : * Why use it:
67 : *
68 : * Data consistency: Wrapping your changes to financial data inside
69 : * a BeginEdit/CommitEdit block allows the engine to verify that
70 : * your changes still leave the financial objects in an internally
71 : * consistent state. This is true even though you may make a series
72 : * of individual changes that are not consistent by themselves. In
73 : * this way, it's like telling the engine, "Okay, I've finished my
74 : * edits. Please check my work."
75 : *
76 : * Data integrity: The other benefit of the BeginEdit/CommitEdit
77 : * block is that it allows the engine (and the backend) to remember
78 : * the last known correct state of your data. This allows you to
79 : * undo any changes that you don't want to keep. In this way, it's
80 : * like telling the engine telling the back end, "Yes, I really mean
81 : * it. Remember this data." or "Nevermind, scratch that." The
82 : * important feature here is that if things go bad, for whatever
83 : * reason (e.g. the application crashed, you lost the backend), your
84 : * data remains in the state it was in just after the previous
85 : * xaccTransCommitEdit(). [assuming no nesting, which probably
86 : * isn't useful outside the engine.]
87 : *
88 : * Note that the backend doesn't care about data consistency -
89 : * that's the engine's job.
90 : *
91 : * Example Use:
92 : *
93 : * xaccTransBeginEdit(trans);
94 : *
95 : *
96 : * split = xaccMallocSplit(book);
97 : * xaccSplitSetAccount(split, acc);
98 : * xaccSplitSetParent(split, trans); // Adding a new split
99 : *
100 : * xaccSplitSetValue(split, val); // Changing a split
101 : *
102 : * xaccSplitDestroy(split); // Removing a split
103 : *
104 : * xaccTransSetNum(trans, "501"); // Changing the trans
105 : *
106 : * if (really_do_it)
107 : * xaccTransCommitEdit(trans);
108 : * else
109 : * xaccTransRollbackEdit(trans);
110 : *
111 : * How it works:
112 : *
113 : * Calling xaccTransBeginEdit() starts a BeginEdit/CommitEdit block.
114 : * Inside the block any changes to the transaction or any splits in
115 : * the transaction are considered "pending". What does that mean?
116 : *
117 : * In general that means that if you set and then get the
118 : * transaction's or split's parameters inside the
119 : * BeginEdit/CommitEdit block, you'll get the values you just set.
120 : * However, if you change an object's many-to-one relationship with
121 : * another object, you won't see the change from the "many" side
122 : * until the CommitEdit. For example, if you move a split from one
123 : * account into another, you can see the change with
124 : * xaccSplitGetAccount(), but both Accounts' split lists won't be
125 : * updated until the CommitEdit. Correspondingly, no signals
126 : * (events) will be generated for those "foreign" objects, or the
127 : * Transaction, until the CommitEdit.
128 : *
129 : * This behavior is important because, when we're finally ready to
130 : * commit to the backend, we can't be 100% sure that the backend
131 : * will still be available. We have to offer the backend all of the
132 : * new state as if it were already "true", but we need to save all of
133 : * the old state in case the backend won't accept our commit. If
134 : * the backend commit fails, we have to restore all the old state.
135 : * If the backend commit succeeds, and *only* after it succeeds, we
136 : * can advertise the new state to the rest of the engine (and gui).
137 : *
138 : * Q: Who owns the ref of an added split if the Transaction is rolled
139 : * back?
140 : *
141 : * A: This is a design decision. If the answer is 'the user',
142 : * then the burden is on the api user to check the transaction after
143 : * every commit to see if the added split is really in the
144 : * transaction. If they don't they risk leaking the split if the
145 : * commit was rolled back. Another design is to answer 'the engine'.
146 : * In that case the burden is on the engine to free a newly added
147 : * split if the commit is rolled back. Unfortunately the engine
148 : * objects aren't ref-counted, so this is tricky.
149 : *
150 : * In the current implementation, the answer is 'the engine', but
151 : * that means that you must not add the split to two different
152 : * transactions during the begin/commit block, because if one rolls
153 : * back, they will both think they own the split. This is one
154 : * specific example of the general problem that the outcome of two
155 : * parallel begin/commit edit blocks for two transactions where edits
156 : * for both transactions involve the same splits and one or more
157 : * edit-blocks is rolled-back, is poorly-defined.
158 : *
159 : *
160 : *
161 : * Design notes on event-generation: transaction-modified-events
162 : * should not be generated until transaction commit or rollback
163 : * time. They should not be generated as each field is tweaked.
164 : * This for two reasons:
165 : * 1) Most editing events make multiple changes to a transaction,
166 : * which would generate a flurry of (needless) events, if they
167 : * weren't saved up till the commit.
168 : * 2) Technically, its incorrect to use transaction data
169 : * until the transaction is committed. The GUI element that
170 : * is changing the data can look at it, but all of the rest
171 : * of the GUI should ignore the data until its committed.
172 : */
173 :
174 : const char *trans_notes_str = "notes";
175 : const char *void_reason_str = "void-reason";
176 : const char *void_time_str = "void-time";
177 : const char *void_former_notes_str = "void-former-notes";
178 : const char *trans_is_closing_str = "book_closing";
179 : const char *doclink_uri_str = "assoc_uri"; // this is the old name for the document link, kept for compatibility
180 :
181 : /* KVP entry for date-due value */
182 : #define TRANS_DATE_DUE_KVP "trans-date-due"
183 : #define TRANS_TXN_TYPE_KVP "trans-txn-type"
184 : #define TRANS_READ_ONLY_REASON "trans-read-only"
185 : #define TRANS_REVERSED_BY "reversed-by"
186 : #define GNC_SX_FROM "from-sched-xaction"
187 :
188 : #define ISO_DATELENGTH 32 /* length of an iso 8601 date string. */
189 :
190 : /* This static indicates the debugging module that this .o belongs to. */
191 : static QofLogModule log_module = GNC_MOD_ENGINE;
192 :
193 : enum
194 : {
195 : PROP_0,
196 : PROP_CURRENCY, /* Table */
197 : PROP_NUM, /* Table */
198 : PROP_POST_DATE, /* Table */
199 : PROP_ENTER_DATE, /* Table */
200 : PROP_DESCRIPTION, /* Table */
201 : PROP_INVOICE, /* KVP */
202 : PROP_SX_TXN, /* KVP */
203 : };
204 :
205 : void
206 15 : check_open (const Transaction *trans)
207 : {
208 15 : if (trans && 0 >= qof_instance_get_editlevel(trans))
209 1 : PERR ("transaction %p not open for editing", trans);
210 15 : }
211 : /********************************************************************\
212 : \********************************************************************/
213 : gboolean
214 24270 : xaccTransStillHasSplit(const Transaction *trans, const Split *s)
215 : {
216 24270 : return (s && s->parent == trans && !qof_instance_get_destroying(s));
217 : }
218 :
219 : /* Executes 'cmd_block' for each split currently in the transaction,
220 : * using the in-edit state. Use the variable 's' for each split. */
221 : #define FOR_EACH_SPLIT(trans, cmd_block) if (trans->splits) { \
222 : GList *splits; \
223 : for (splits = (trans)->splits; splits; splits = splits->next) { \
224 : Split *s = GNC_SPLIT(splits->data); \
225 : if (xaccTransStillHasSplit(trans, s)) { \
226 : cmd_block; \
227 : } \
228 : } \
229 : }
230 :
231 : static inline void mark_trans (Transaction *trans);
232 8929 : void mark_trans (Transaction *trans)
233 : {
234 9711 : FOR_EACH_SPLIT(trans, mark_split(s));
235 8929 : }
236 :
237 : static inline void gen_event_trans (Transaction *trans);
238 4645 : void gen_event_trans (Transaction *trans)
239 : {
240 : GList *node;
241 :
242 14155 : for (node = trans->splits; node; node = node->next)
243 : {
244 9510 : Split *s = GNC_SPLIT(node->data);
245 9510 : Account *account = s->acc;
246 9510 : GNCLot *lot = s->lot;
247 9510 : if (account)
248 9492 : qof_event_gen (&account->inst, GNC_EVENT_ITEM_CHANGED, s);
249 :
250 9510 : if (lot)
251 : {
252 : /* A change of transaction date might affect opening date of lot */
253 322 : qof_event_gen (QOF_INSTANCE(lot), QOF_EVENT_MODIFY, nullptr);
254 : }
255 : }
256 4645 : }
257 :
258 : /* GObject Initialization */
259 28230 : G_DEFINE_TYPE(Transaction, gnc_transaction, QOF_TYPE_INSTANCE)
260 :
261 : static void
262 8392 : gnc_transaction_init(Transaction* trans)
263 : {
264 8392 : ENTER ("trans=%p", trans);
265 : /* Fill in some sane defaults */
266 8392 : trans->num = CACHE_INSERT("");
267 8392 : trans->description = CACHE_INSERT("");
268 8392 : trans->common_currency = nullptr;
269 8392 : trans->splits = nullptr;
270 8392 : trans->date_entered = 0;
271 8392 : trans->date_posted = 0;
272 8392 : trans->marker = 0;
273 8392 : trans->orig = nullptr;
274 8392 : trans->txn_type = TXN_TYPE_UNCACHED;
275 8392 : LEAVE (" ");
276 8392 : }
277 :
278 : static void
279 7262 : gnc_transaction_dispose(GObject *txnp)
280 : {
281 7262 : G_OBJECT_CLASS(gnc_transaction_parent_class)->dispose(txnp);
282 7262 : }
283 :
284 : static void
285 7261 : gnc_transaction_finalize(GObject* txnp)
286 : {
287 7261 : G_OBJECT_CLASS(gnc_transaction_parent_class)->finalize(txnp);
288 7261 : }
289 :
290 : /* Note that g_value_set_object() refs the object, as does
291 : * g_object_get(). But g_object_get() only unrefs once when it disgorges
292 : * the object, leaving an unbalanced ref, which leaks. So instead of
293 : * using g_value_set_object(), use g_value_take_object() which doesn't
294 : * ref the object when used in get_property().
295 : */
296 : static void
297 248 : gnc_transaction_get_property(GObject* object,
298 : guint prop_id,
299 : GValue* value,
300 : GParamSpec* pspec)
301 : {
302 : Transaction* tx;
303 : Time64 time;
304 :
305 248 : g_return_if_fail(GNC_IS_TRANSACTION(object));
306 :
307 248 : tx = GNC_TRANSACTION(object);
308 248 : switch (prop_id)
309 : {
310 12 : case PROP_NUM:
311 12 : g_value_set_string(value, tx->num);
312 12 : break;
313 19 : case PROP_DESCRIPTION:
314 19 : g_value_set_string(value, tx->description);
315 19 : break;
316 12 : case PROP_CURRENCY:
317 12 : g_value_take_object(value, tx->common_currency);
318 12 : break;
319 12 : case PROP_POST_DATE:
320 12 : time.t = tx->date_posted;
321 12 : g_value_set_boxed(value, &time);
322 12 : break;
323 12 : case PROP_ENTER_DATE:
324 12 : time.t = tx->date_entered;
325 12 : g_value_set_boxed(value, &time);
326 12 : break;
327 180 : case PROP_INVOICE:
328 180 : qof_instance_get_kvp (QOF_INSTANCE (tx), value, 2, GNC_INVOICE_ID, GNC_INVOICE_GUID);
329 180 : break;
330 1 : case PROP_SX_TXN:
331 1 : qof_instance_get_kvp (QOF_INSTANCE (tx), value, 1, GNC_SX_FROM);
332 1 : break;
333 0 : default:
334 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
335 0 : break;
336 : }
337 : }
338 :
339 : static void
340 145 : gnc_transaction_set_property(GObject* object,
341 : guint prop_id,
342 : const GValue* value,
343 : GParamSpec* pspec)
344 : {
345 : Transaction* tx;
346 : Time64 *t;
347 :
348 145 : g_return_if_fail(GNC_IS_TRANSACTION(object));
349 :
350 145 : tx = GNC_TRANSACTION(object);
351 145 : g_assert (qof_instance_get_editlevel(tx));
352 :
353 145 : switch (prop_id)
354 : {
355 13 : case PROP_NUM:
356 13 : xaccTransSetNum( tx, g_value_get_string(value));
357 13 : break;
358 13 : case PROP_DESCRIPTION:
359 13 : xaccTransSetDescription(tx, g_value_get_string(value));
360 13 : break;
361 13 : case PROP_CURRENCY:
362 13 : xaccTransSetCurrency(tx, GNC_COMMODITY(g_value_get_object(value)));
363 13 : break;
364 13 : case PROP_POST_DATE:
365 13 : t = (Time64*)g_value_get_boxed(value);
366 13 : xaccTransSetDatePostedSecs(tx, t->t);
367 13 : break;
368 13 : case PROP_ENTER_DATE:
369 13 : t = (Time64*)g_value_get_boxed(value);
370 13 : xaccTransSetDateEnteredSecs(tx, t->t);
371 13 : break;
372 75 : case PROP_INVOICE:
373 75 : qof_instance_set_kvp (QOF_INSTANCE (tx), value, 2, GNC_INVOICE_ID, GNC_INVOICE_GUID);
374 75 : break;
375 5 : case PROP_SX_TXN:
376 5 : qof_instance_set_kvp (QOF_INSTANCE (tx), value, 1, GNC_SX_FROM);
377 5 : break;
378 0 : default:
379 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
380 0 : break;
381 : }
382 : }
383 :
384 : static void
385 42 : gnc_transaction_class_init(TransactionClass* klass)
386 : {
387 42 : GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
388 :
389 42 : gobject_class->dispose = gnc_transaction_dispose;
390 42 : gobject_class->finalize = gnc_transaction_finalize;
391 42 : gobject_class->set_property = gnc_transaction_set_property;
392 42 : gobject_class->get_property = gnc_transaction_get_property;
393 :
394 : g_object_class_install_property
395 42 : (gobject_class,
396 : PROP_NUM,
397 : g_param_spec_string("num",
398 : "Transaction Number",
399 : "The transactionNumber is an arbitrary string "
400 : "assigned by the user. It is intended to be "
401 : "a short 1-6 character string that is displayed "
402 : "by the register. For checks, it is usually the "
403 : "check number. For other types of transactions, "
404 : "it can be any string.",
405 : nullptr,
406 : G_PARAM_READWRITE));
407 :
408 : g_object_class_install_property
409 42 : (gobject_class,
410 : PROP_DESCRIPTION,
411 : g_param_spec_string("description",
412 : "Transaction Description",
413 : "The transaction description is an arbitrary string "
414 : "assigned by the user. It is usually the customer, "
415 : "vendor or other organization associated with the "
416 : "transaction.",
417 : nullptr,
418 : G_PARAM_READWRITE));
419 :
420 : g_object_class_install_property
421 42 : (gobject_class,
422 : PROP_CURRENCY,
423 : g_param_spec_object ("currency",
424 : "Currency",
425 : "The base currency for this transaction.",
426 : GNC_TYPE_COMMODITY,
427 : G_PARAM_READWRITE));
428 :
429 : g_object_class_install_property
430 42 : (gobject_class,
431 : PROP_POST_DATE,
432 : g_param_spec_boxed("post-date",
433 : "Post Date",
434 : "The date the transaction occurred.",
435 : GNC_TYPE_TIME64,
436 : G_PARAM_READWRITE));
437 :
438 : g_object_class_install_property
439 42 : (gobject_class,
440 : PROP_ENTER_DATE,
441 : g_param_spec_boxed("enter-date",
442 : "Enter Date",
443 : "The date the transaction was entered.",
444 : GNC_TYPE_TIME64,
445 : G_PARAM_READWRITE));
446 :
447 42 : g_object_class_install_property(
448 : gobject_class,
449 : PROP_INVOICE,
450 : g_param_spec_boxed("invoice",
451 : "Invoice attached to lot",
452 : "Used by GncInvoice",
453 : GNC_TYPE_GUID,
454 : G_PARAM_READWRITE));
455 :
456 42 : g_object_class_install_property(
457 : gobject_class,
458 : PROP_SX_TXN,
459 : g_param_spec_boxed("from-sched-xaction",
460 : "From Scheduled Transaction",
461 : "Used by Scheduled Transastions to record the "
462 : "originating template transaction for created "
463 : "transactions",
464 : GNC_TYPE_GUID,
465 : G_PARAM_READWRITE));
466 42 : }
467 :
468 : /********************************************************************\
469 : * xaccInitTransaction
470 : * Initialize a transaction structure
471 : \********************************************************************/
472 :
473 : static void
474 3497 : xaccInitTransaction (Transaction * trans, QofBook *book)
475 : {
476 3497 : ENTER ("trans=%p", trans);
477 3497 : qof_instance_init_data (&trans->inst, GNC_ID_TRANS, book);
478 3497 : LEAVE (" ");
479 3497 : }
480 :
481 : /********************************************************************\
482 : \********************************************************************/
483 :
484 : Transaction *
485 3498 : xaccMallocTransaction (QofBook *book)
486 : {
487 : Transaction *trans;
488 :
489 3498 : g_return_val_if_fail (book, nullptr);
490 :
491 3497 : trans = GNC_TRANSACTION(g_object_new(GNC_TYPE_TRANSACTION, nullptr));
492 3497 : xaccInitTransaction (trans, book);
493 3497 : qof_event_gen (&trans->inst, QOF_EVENT_CREATE, nullptr);
494 :
495 3497 : return trans;
496 : }
497 :
498 : #ifdef DUMP_FUNCTIONS
499 : /* Please don't delete this function. Although it is not called by
500 : any other code in GnuCash, it is useful when debugging. For example
501 : it can be called using the gdb "call" command when stopped at a
502 : breakpoint. */
503 : void
504 : xaccTransDump (const Transaction *trans, const char *tag)
505 : {
506 : GList *node;
507 : char datebuff[MAX_DATE_LENGTH + 1];
508 :
509 : printf("%s Trans %p", tag, trans);
510 : memset(datebuff, 0, sizeof(datebuff));
511 : qof_print_date_buff(datebuff, MAX_DATE_LENGTH, trans->date_entered);
512 : printf(" Entered: %s\n", datebuff);
513 : memset(datebuff, 0, sizeof(datebuff));
514 : qof_print_date_buff(datebuff, MAX_DATE_LENGTH, trans->date_posted);
515 : printf(" Posted: %s\n", datebuff);
516 : printf(" Num: %s\n", trans->num ? trans->num : "(null)");
517 : printf(" Description: %s\n",
518 : trans->description ? trans->description : "(null)");
519 : printf(" Currency: %s\n",
520 : gnc_commodity_get_printname(trans->common_currency));
521 : printf(" version: %x\n", qof_instance_get_version(trans));
522 : printf(" version_chk: %x\n", qof_instance_get_version_check(trans));
523 : printf(" editlevel: %x\n", qof_instance_get_editlevel(trans));
524 : printf(" orig: %p\n", trans->orig);
525 : printf(" idata: %x\n", qof_instance_get_idata(trans));
526 : printf(" splits: ");
527 : for (node = trans->splits; node; node = node->next)
528 : {
529 : printf("%p ", node->data);
530 : }
531 : printf("\n");
532 : for (node = trans->splits; node; node = node->next)
533 : {
534 : xaccSplitDump(GNC_SPLIT(node->data), tag);
535 : }
536 : printf("\n");
537 : }
538 : #endif
539 :
540 : static int
541 5017 : split_sign_cmp (gconstpointer a, gconstpointer b)
542 : {
543 5017 : bool a_neg = gnc_numeric_negative_p (xaccSplitGetValue (GNC_SPLIT (a)));
544 5017 : bool b_neg = gnc_numeric_negative_p (xaccSplitGetValue (GNC_SPLIT (b)));
545 5017 : return a_neg == b_neg ? 0 : a_neg ? 1 : -1;
546 : }
547 :
548 : void
549 4636 : xaccTransSortSplits (Transaction *trans)
550 : {
551 4636 : g_return_if_fail (trans);
552 4636 : trans->splits = g_list_sort (trans->splits, split_sign_cmp);
553 : }
554 :
555 :
556 : /********************************************************************\
557 : \********************************************************************/
558 : /* This routine is not exposed externally, since it does weird things,
559 : * like not really owning the splits correctly, and other weirdnesses.
560 : * This routine is prone to programmer snafu if not used correctly.
561 : * It is used only by the edit-rollback code.
562 : */
563 : static Transaction *
564 4880 : dupe_trans (const Transaction *from)
565 : {
566 : Transaction *to;
567 4880 : to = GNC_TRANSACTION(g_object_new (GNC_TYPE_TRANSACTION, nullptr));
568 :
569 4880 : CACHE_REPLACE (to->num, from->num);
570 4880 : CACHE_REPLACE (to->description, from->description);
571 :
572 4880 : to->splits = g_list_copy_deep (from->splits, (GCopyFunc)xaccDupeSplit, nullptr);
573 4880 : to->date_entered = from->date_entered;
574 4880 : to->date_posted = from->date_posted;
575 4880 : qof_instance_copy_version(to, from);
576 4880 : to->orig = nullptr;
577 :
578 4880 : to->common_currency = from->common_currency;
579 :
580 : /* Trash the guid and entity table. We don't want to mistake
581 : * the cloned transaction as something official. If we ever
582 : * use this transaction, we'll have to fix this up.
583 : */
584 4880 : to->inst.e_type = nullptr;
585 4880 : qof_instance_set_guid(to, guid_null());
586 4880 : qof_instance_copy_book(to, from);
587 4880 : qof_instance_copy_kvp (QOF_INSTANCE(to), QOF_INSTANCE(from));
588 :
589 4880 : return to;
590 : }
591 :
592 : /********************************************************************\
593 : * Use this routine to externally duplicate a transaction. It creates
594 : * a full fledged transaction with unique guid, splits, etc. and
595 : * writes it to the database.
596 : \********************************************************************/
597 : static gpointer
598 21 : copy_split (gconstpointer from_split, gpointer to)
599 : {
600 21 : auto split = xaccSplitCloneNoKvp(GNC_SPLIT(from_split));
601 21 : split->parent = GNC_TRANSACTION(to);
602 21 : return split;
603 : }
604 :
605 : Transaction *
606 11 : xaccTransCloneNoKvp (const Transaction *from)
607 : {
608 : Transaction *to;
609 :
610 11 : qof_event_suspend();
611 11 : to = GNC_TRANSACTION(g_object_new (GNC_TYPE_TRANSACTION, nullptr));
612 :
613 11 : to->date_entered = from->date_entered;
614 11 : to->date_posted = from->date_posted;
615 11 : CACHE_REPLACE (to->num, from->num);
616 11 : CACHE_REPLACE (to->description, from->description);
617 11 : to->common_currency = from->common_currency;
618 11 : qof_instance_copy_version(to, from);
619 11 : qof_instance_copy_version_check(to, from);
620 :
621 11 : to->orig = nullptr;
622 :
623 11 : qof_instance_init_data (&to->inst, GNC_ID_TRANS,
624 : qof_instance_get_book(from));
625 :
626 11 : xaccTransBeginEdit(to);
627 11 : to->splits = g_list_copy_deep (from->splits, copy_split, to);
628 11 : qof_instance_set_dirty(QOF_INSTANCE(to));
629 11 : xaccTransCommitEdit(to);
630 11 : qof_event_resume();
631 :
632 11 : return to;
633 : }
634 :
635 : Transaction *
636 7 : xaccTransClone (const Transaction *from)
637 : {
638 7 : Transaction *to = xaccTransCloneNoKvp (from);
639 :
640 7 : if (g_list_length (to->splits) != g_list_length (from->splits))
641 : {
642 0 : PERR ("Cloned transaction has different number of splits from original");
643 0 : xaccTransDestroy (to);
644 0 : return nullptr;
645 : }
646 :
647 7 : xaccTransBeginEdit (to);
648 7 : qof_instance_copy_kvp (QOF_INSTANCE (to), QOF_INSTANCE (from));
649 :
650 20 : for (GList* lfrom = from->splits, *lto = to->splits; lfrom && lto;
651 13 : lfrom = g_list_next (lfrom), lto = g_list_next (lto))
652 13 : xaccSplitCopyKvp (GNC_SPLIT(lfrom->data), GNC_SPLIT(lto->data));
653 :
654 7 : xaccTransCommitEdit (to);
655 7 : return to;
656 : }
657 :
658 : /*################## Added for Reg2 #################*/
659 :
660 : /********************************************************************\
661 : * Copy a transaction to the 'clipboard' transaction using
662 : * dupe_trans. The 'clipboard' transaction must never
663 : * be dereferenced.
664 : \********************************************************************/
665 0 : Transaction * xaccTransCopyToClipBoard(const Transaction *from_trans)
666 : {
667 : Transaction *to_trans;
668 :
669 0 : if (!from_trans)
670 0 : return nullptr;
671 :
672 0 : to_trans = dupe_trans(from_trans);
673 0 : return to_trans;
674 : }
675 :
676 : /********************************************************************\
677 : * Copy a transaction to another using the function below without
678 : * changing any account information.
679 : \********************************************************************/
680 : void
681 0 : xaccTransCopyOnto(const Transaction *from_trans, Transaction *to_trans)
682 : {
683 0 : xaccTransCopyFromClipBoard(from_trans, to_trans, nullptr, nullptr, TRUE);
684 0 : }
685 :
686 : /********************************************************************\
687 : * This function explicitly must robustly handle some unusual input.
688 : *
689 : * 'from_trans' may be a duped trans (see dupe_trans), so its
690 : * splits may not really belong to the accounts that they say they do.
691 : *
692 : * 'from_acc' need not be a valid account. It may be an already freed
693 : * Account. Therefore, it must not be dereferenced at all.
694 : *
695 : * Neither 'from_trans', nor 'from_acc', nor any of 'from's splits may
696 : * be modified in any way.
697 : *
698 : * 'no_date' if TRUE will not copy the date posted.
699 : *
700 : * The 'to_trans' transaction will end up with valid copies of from's
701 : * splits. In addition, the copies of any of from's splits that were
702 : * in from_acc (or at least claimed to be) will end up in to_acc.
703 : \********************************************************************/
704 : void
705 2 : xaccTransCopyFromClipBoard(const Transaction *from_trans, Transaction *to_trans,
706 : const Account *from_acc, Account *to_acc, gboolean no_date)
707 : {
708 2 : gboolean change_accounts = FALSE;
709 : GList *node;
710 :
711 2 : if (!from_trans || !to_trans)
712 0 : return;
713 :
714 2 : change_accounts = from_acc && GNC_IS_ACCOUNT(to_acc) && from_acc != to_acc;
715 2 : xaccTransBeginEdit(to_trans);
716 :
717 2 : xaccTransClearSplits(to_trans);
718 2 : xaccTransSetCurrency(to_trans, xaccTransGetCurrency(from_trans));
719 2 : xaccTransSetDescription(to_trans, xaccTransGetDescription(from_trans));
720 :
721 2 : if ((xaccTransGetNum(to_trans) == nullptr) || (g_strcmp0 (xaccTransGetNum(to_trans), "") == 0))
722 2 : xaccTransSetNum(to_trans, xaccTransGetNum(from_trans));
723 :
724 2 : xaccTransSetNotes(to_trans, xaccTransGetNotes(from_trans));
725 2 : xaccTransSetDocLink(to_trans, xaccTransGetDocLink (from_trans));
726 2 : if(!no_date)
727 : {
728 1 : xaccTransSetDatePostedSecs(to_trans, xaccTransRetDatePosted (from_trans));
729 : }
730 :
731 : /* Each new split will be parented to 'to' */
732 6 : for (node = from_trans->splits; node; node = node->next)
733 : {
734 4 : Split *new_split = xaccMallocSplit( qof_instance_get_book(QOF_INSTANCE(from_trans)));
735 4 : xaccSplitCopyOnto(GNC_SPLIT(node->data), new_split);
736 4 : if (change_accounts && xaccSplitGetAccount(GNC_SPLIT(node->data)) == from_acc)
737 2 : xaccSplitSetAccount(new_split, to_acc);
738 4 : xaccSplitSetParent(new_split, to_trans);
739 : }
740 2 : xaccTransCommitEdit(to_trans);
741 : }
742 :
743 : /*################## Added for Reg2 #################*/
744 :
745 : /********************************************************************\
746 : Free the transaction.
747 : \********************************************************************/
748 : static void
749 7176 : xaccFreeTransaction (Transaction *trans)
750 : {
751 7176 : if (!trans) return;
752 :
753 7176 : ENTER ("(addr=%p)", trans);
754 7176 : if (((char *) 1) == trans->num)
755 : {
756 0 : PERR ("double-free %p", trans);
757 0 : LEAVE (" ");
758 0 : return;
759 : }
760 :
761 : /* free up the destination splits */
762 7176 : g_list_free_full (trans->splits, (GDestroyNotify)xaccFreeSplit);
763 7176 : trans->splits = nullptr;
764 :
765 : /* free up transaction strings */
766 7176 : CACHE_REMOVE(trans->num);
767 7176 : CACHE_REMOVE(trans->description);
768 :
769 : /* Just in case someone looks up freed memory ... */
770 7176 : trans->num = (char *) 1;
771 7176 : trans->description = nullptr;
772 7176 : trans->date_entered = 0;
773 7176 : trans->date_posted = 0;
774 7176 : if (trans->orig)
775 : {
776 123 : xaccFreeTransaction (trans->orig);
777 123 : trans->orig = nullptr;
778 : }
779 :
780 : /* qof_instance_release (&trans->inst); */
781 7176 : g_object_unref(trans);
782 :
783 7176 : LEAVE ("(addr=%p)", trans);
784 : }
785 :
786 : /********************************************************************
787 : xaccTransEqual
788 :
789 : Compare two transactions for equality. We don't pay any attention to
790 : rollback issues here, and we only care about equality of "permanent
791 : fields", basically the things that would survive a file save/load
792 : cycle.
793 :
794 : ********************************************************************/
795 :
796 : /* return 0 when splits have equal guids */
797 : static gint
798 201 : compare_split_guids (gconstpointer a, gconstpointer b)
799 : {
800 201 : const Split *sa = GNC_SPLIT(a);
801 201 : const Split *sb = GNC_SPLIT(b);
802 :
803 201 : if (sa == sb) return 0;
804 201 : if (!sa || !sb) return 1;
805 :
806 201 : return guid_compare (xaccSplitGetGUID (sa), xaccSplitGetGUID (sb));
807 : }
808 :
809 : gboolean
810 268 : xaccTransEqual(const Transaction *ta, const Transaction *tb,
811 : gboolean check_guids,
812 : gboolean check_splits,
813 : gboolean check_balances,
814 : gboolean assume_ordered)
815 : {
816 : gboolean same_book;
817 :
818 268 : if (!ta && !tb) return TRUE; /* Arguable. FALSE may be better. */
819 :
820 267 : if (!ta || !tb)
821 : {
822 4 : PINFO ("one is nullptr");
823 4 : return FALSE;
824 : }
825 :
826 263 : if (ta == tb) return TRUE;
827 :
828 253 : same_book = qof_instance_get_book(QOF_INSTANCE(ta)) == qof_instance_get_book(QOF_INSTANCE(tb));
829 :
830 253 : if (check_guids)
831 : {
832 225 : if (qof_instance_guid_compare(ta, tb) != 0)
833 : {
834 3 : PINFO ("GUIDs differ");
835 3 : return FALSE;
836 : }
837 : }
838 :
839 250 : if (!gnc_commodity_equal(ta->common_currency, tb->common_currency))
840 : {
841 1 : PINFO ("commodities differ %s vs %s",
842 : gnc_commodity_get_unique_name (ta->common_currency),
843 : gnc_commodity_get_unique_name (tb->common_currency));
844 1 : return FALSE;
845 : }
846 :
847 249 : if (ta->date_entered != tb->date_entered)
848 : {
849 : char buf1[100];
850 : char buf2[100];
851 :
852 1 : (void)gnc_time64_to_iso8601_buff(ta->date_entered, buf1);
853 1 : (void)gnc_time64_to_iso8601_buff(tb->date_entered, buf2);
854 1 : PINFO ("date entered differs: '%s' vs '%s'", buf1, buf2);
855 1 : return FALSE;
856 : }
857 :
858 248 : if (ta->date_posted != tb->date_posted)
859 : {
860 : char buf1[100];
861 : char buf2[100];
862 :
863 1 : (void)gnc_time64_to_iso8601_buff(ta->date_posted, buf1);
864 1 : (void)gnc_time64_to_iso8601_buff(tb->date_posted, buf2);
865 1 : PINFO ("date posted differs: '%s' vs '%s'", buf1, buf2);
866 1 : return FALSE;
867 : }
868 :
869 : /* If the same book, since we use cached strings, we can just compare pointer
870 : * equality for num and description
871 : */
872 247 : if ((same_book && ta->num != tb->num) || (!same_book && g_strcmp0(ta->num, tb->num) != 0))
873 : {
874 2 : PINFO ("num differs: %s vs %s", ta->num, tb->num);
875 2 : return FALSE;
876 : }
877 :
878 169 : if ((same_book && ta->description != tb->description)
879 414 : || (!same_book && g_strcmp0(ta->description, tb->description)))
880 : {
881 2 : PINFO ("descriptions differ: %s vs %s", ta->description, tb->description);
882 2 : return FALSE;
883 : }
884 :
885 243 : if (qof_instance_compare_kvp (QOF_INSTANCE (ta), QOF_INSTANCE (tb)) != 0)
886 : {
887 : char *frame_a;
888 : char *frame_b;
889 :
890 1 : frame_a = qof_instance_kvp_as_string (QOF_INSTANCE (ta));
891 1 : frame_b = qof_instance_kvp_as_string (QOF_INSTANCE (tb));
892 :
893 :
894 1 : PINFO ("kvp frames differ:\n%s\n\nvs\n\n%s", frame_a, frame_b);
895 :
896 1 : g_free (frame_a);
897 1 : g_free (frame_b);
898 :
899 1 : return FALSE;
900 : }
901 :
902 242 : if (check_splits)
903 : {
904 75 : if ((!ta->splits && tb->splits) || (!tb->splits && ta->splits))
905 : {
906 0 : PINFO ("only one has splits");
907 0 : return FALSE;
908 : }
909 :
910 75 : if (ta->splits && tb->splits)
911 : {
912 : GList *node_a, *node_b;
913 :
914 75 : for (node_a = ta->splits, node_b = tb->splits;
915 219 : node_a;
916 144 : node_a = node_a->next, node_b = node_b->next)
917 : {
918 147 : Split *split_a = GNC_SPLIT(node_a->data);
919 : Split *split_b;
920 :
921 : /* don't presume that the splits are in the same order */
922 147 : if (!assume_ordered)
923 134 : node_b = g_list_find_custom (tb->splits, split_a,
924 : compare_split_guids);
925 :
926 147 : if (!node_b)
927 : {
928 : gchar guidstr[GUID_ENCODING_LENGTH+1];
929 0 : guid_to_string_buff (xaccSplitGetGUID (split_a),guidstr);
930 :
931 0 : PINFO ("first has split %s and second does not",guidstr);
932 0 : return FALSE;
933 : }
934 :
935 147 : split_b = GNC_SPLIT(node_b->data);
936 :
937 147 : if (!xaccSplitEqual (split_a, split_b, check_guids, check_balances,
938 : FALSE))
939 : {
940 : char str_a[GUID_ENCODING_LENGTH + 1];
941 : char str_b[GUID_ENCODING_LENGTH + 1];
942 :
943 3 : guid_to_string_buff (xaccSplitGetGUID (split_a), str_a);
944 3 : guid_to_string_buff (xaccSplitGetGUID (split_b), str_b);
945 :
946 3 : PINFO ("splits %s and %s differ", str_a, str_b);
947 3 : return FALSE;
948 : }
949 : }
950 :
951 72 : if (g_list_length (ta->splits) != g_list_length (tb->splits))
952 : {
953 0 : PINFO ("different number of splits");
954 0 : return FALSE;
955 : }
956 : }
957 : }
958 :
959 239 : return TRUE;
960 : }
961 :
962 : /********************************************************************\
963 : xaccTransUseTradingAccounts
964 :
965 : Returns true if the transaction should include trading account splits if
966 : it involves more than one commodity.
967 : \********************************************************************/
968 :
969 6153 : gboolean xaccTransUseTradingAccounts(const Transaction *trans)
970 : {
971 6153 : return qof_book_use_trading_accounts(qof_instance_get_book (trans));
972 : }
973 :
974 : /********************************************************************\
975 : \********************************************************************/
976 :
977 : Transaction *
978 72 : xaccTransLookup (const GncGUID *guid, QofBook *book)
979 : {
980 : QofCollection *col;
981 72 : if (!guid || !book) return nullptr;
982 72 : col = qof_book_get_collection (book, GNC_ID_TRANS);
983 72 : return (Transaction *) qof_collection_lookup_entity (col, guid);
984 : }
985 :
986 : /********************************************************************\
987 : \********************************************************************/
988 :
989 : gnc_numeric
990 3113 : xaccTransGetImbalanceValue (const Transaction * trans)
991 : {
992 3113 : gnc_numeric imbal = gnc_numeric_zero();
993 3113 : if (!trans) return imbal;
994 :
995 3113 : ENTER("(trans=%p)", trans);
996 : /* Could use xaccSplitsComputeValue, except that we want to use
997 : GNC_HOW_DENOM_EXACT */
998 9429 : FOR_EACH_SPLIT(trans, imbal =
999 : gnc_numeric_add(imbal, xaccSplitGetValue(s),
1000 : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT));
1001 3113 : LEAVE("(trans=%p) imbal=%s", trans, gnc_num_dbg_to_string(imbal));
1002 3113 : return imbal;
1003 : }
1004 :
1005 : MonetaryList *
1006 13 : xaccTransGetImbalance (const Transaction * trans)
1007 : {
1008 : /* imbal_value is used if either (1) the transaction has a non currency
1009 : split or (2) all the splits are in the same currency. If there are
1010 : no non-currency splits and not all splits are in the same currency then
1011 : imbal_list is used to compute the imbalance. */
1012 13 : MonetaryList *imbal_list = nullptr;
1013 13 : gnc_numeric imbal_value = gnc_numeric_zero();
1014 : gboolean trading_accts;
1015 :
1016 13 : if (!trans) return imbal_list;
1017 :
1018 12 : ENTER("(trans=%p)", trans);
1019 :
1020 12 : trading_accts = xaccTransUseTradingAccounts (trans);
1021 :
1022 : /* If using trading accounts and there is at least one split that is not
1023 : in the transaction currency or a split that has a price or exchange
1024 : rate other than 1, then compute the balance in each commodity in the
1025 : transaction. Otherwise (all splits are in the transaction's currency)
1026 : then compute the balance using the value fields.
1027 :
1028 : Optimize for the common case of only one currency and a balanced
1029 : transaction. */
1030 50 : FOR_EACH_SPLIT(trans,
1031 : {
1032 : gnc_commodity *commodity;
1033 : commodity = xaccAccountGetCommodity(xaccSplitGetAccount(s));
1034 : if (trading_accts &&
1035 : (imbal_list ||
1036 : ! gnc_commodity_equiv(commodity, trans->common_currency) ||
1037 : ! gnc_numeric_equal(xaccSplitGetAmount(s), xaccSplitGetValue(s))))
1038 : {
1039 : /* Need to use (or already are using) a list of imbalances in each of
1040 : the currencies used in the transaction. */
1041 : if (! imbal_list)
1042 : {
1043 : /* All previous splits have been in the transaction's common
1044 : currency, so imbal_value is in this currency. */
1045 : imbal_list = gnc_monetary_list_add_value(imbal_list,
1046 : trans->common_currency,
1047 : imbal_value);
1048 : }
1049 : imbal_list = gnc_monetary_list_add_value(imbal_list, commodity,
1050 : xaccSplitGetAmount(s));
1051 : }
1052 :
1053 : /* Add it to the value accumulator in case we need it. */
1054 : imbal_value = gnc_numeric_add(imbal_value, xaccSplitGetValue(s),
1055 : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
1056 : } );
1057 :
1058 :
1059 12 : if (!imbal_list && !gnc_numeric_zero_p(imbal_value))
1060 : {
1061 : /* Not balanced and no list, create one. If we found multiple currencies
1062 : and no non-currency commodity then imbal_list will already exist and
1063 : we won't get here. */
1064 1 : imbal_list = gnc_monetary_list_add_value(imbal_list,
1065 1 : trans->common_currency,
1066 : imbal_value);
1067 : }
1068 :
1069 : /* Delete all the zero entries from the list, perhaps leaving an
1070 : empty list */
1071 12 : imbal_list = gnc_monetary_list_delete_zeros(imbal_list);
1072 :
1073 12 : LEAVE("(trans=%p), imbal=%p", trans, imbal_list);
1074 12 : return imbal_list;
1075 : }
1076 :
1077 : gboolean
1078 3073 : xaccTransIsBalanced (const Transaction *trans)
1079 : {
1080 : MonetaryList *imbal_list;
1081 : gboolean result;
1082 3073 : gnc_numeric imbal = gnc_numeric_zero();
1083 3073 : gnc_numeric imbal_trading = gnc_numeric_zero();
1084 :
1085 3073 : if (trans == nullptr) return FALSE;
1086 :
1087 3072 : if (xaccTransUseTradingAccounts(trans))
1088 : {
1089 : /* Transaction is imbalanced if the value is imbalanced in either
1090 : trading or non-trading splits. One can't be used to balance
1091 : the other. */
1092 30 : FOR_EACH_SPLIT(trans,
1093 : {
1094 : Account *acc = xaccSplitGetAccount(s);
1095 : if (!acc || xaccAccountGetType(acc) != ACCT_TYPE_TRADING)
1096 : {
1097 : imbal = gnc_numeric_add(imbal, xaccSplitGetValue(s),
1098 : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
1099 : }
1100 : else
1101 : {
1102 : imbal_trading = gnc_numeric_add(imbal_trading, xaccSplitGetValue(s),
1103 : GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
1104 : }
1105 : }
1106 : );
1107 : }
1108 : else
1109 3065 : imbal = xaccTransGetImbalanceValue(trans);
1110 :
1111 3072 : if (! gnc_numeric_zero_p(imbal) || ! gnc_numeric_zero_p(imbal_trading))
1112 51 : return FALSE;
1113 :
1114 3021 : if (!xaccTransUseTradingAccounts (trans))
1115 3016 : return TRUE;
1116 :
1117 5 : imbal_list = xaccTransGetImbalance(trans);
1118 5 : result = imbal_list == nullptr;
1119 5 : gnc_monetary_list_free(imbal_list);
1120 5 : return result;
1121 : }
1122 :
1123 : gnc_numeric
1124 4 : xaccTransGetAccountValue (const Transaction *trans,
1125 : const Account *acc)
1126 : {
1127 4 : gnc_numeric total = gnc_numeric_zero ();
1128 4 : if (!trans || !acc) return total;
1129 :
1130 6 : FOR_EACH_SPLIT(trans, if (acc == xaccSplitGetAccount(s))
1131 : {
1132 : total = gnc_numeric_add (total, xaccSplitGetValue (s),
1133 : GNC_DENOM_AUTO,
1134 : GNC_HOW_DENOM_EXACT);
1135 : });
1136 2 : return total;
1137 : }
1138 :
1139 : gnc_numeric
1140 16 : xaccTransGetAccountAmount (const Transaction *trans, const Account *acc)
1141 : {
1142 16 : gnc_numeric total = gnc_numeric_zero ();
1143 16 : if (!trans || !acc) return total;
1144 :
1145 14 : total = gnc_numeric_convert (total, xaccAccountGetCommoditySCU (acc),
1146 : GNC_HOW_RND_ROUND_HALF_UP);
1147 42 : FOR_EACH_SPLIT(trans, if (acc == xaccSplitGetAccount(s))
1148 : total = gnc_numeric_add_fixed(
1149 : total, xaccSplitGetAmount(s)));
1150 14 : return total;
1151 : }
1152 :
1153 : gnc_numeric
1154 4 : xaccTransGetAccountConvRate(const Transaction *txn, const Account *acc)
1155 : {
1156 : gnc_numeric amount, value, convrate;
1157 : GList *splits;
1158 : Split *s;
1159 4 : gboolean found_acc_match = FALSE;
1160 4 : gnc_commodity *acc_commod = xaccAccountGetCommodity(acc);
1161 :
1162 : /* We need to compute the conversion rate into _this account_. So,
1163 : * find the first split into this account, compute the conversion
1164 : * rate (based on amount/value), and then return this conversion
1165 : * rate.
1166 : */
1167 4 : if (gnc_commodity_equal(acc_commod, xaccTransGetCurrency(txn)))
1168 1 : return gnc_numeric_create(1, 1);
1169 :
1170 4 : for (splits = txn->splits; splits; splits = splits->next)
1171 : {
1172 : Account *split_acc;
1173 : gnc_commodity *split_commod;
1174 :
1175 4 : s = GNC_SPLIT(splits->data);
1176 :
1177 4 : if (!xaccTransStillHasSplit(txn, s))
1178 0 : continue;
1179 4 : split_acc = xaccSplitGetAccount (s);
1180 4 : split_commod = xaccAccountGetCommodity (split_acc);
1181 5 : if (! (split_acc == acc ||
1182 1 : gnc_commodity_equal (split_commod, acc_commod)))
1183 1 : continue;
1184 :
1185 3 : found_acc_match = TRUE;
1186 3 : amount = xaccSplitGetAmount (s);
1187 :
1188 : /* Ignore splits with "zero" amount */
1189 3 : if (gnc_numeric_zero_p (amount))
1190 0 : continue;
1191 :
1192 3 : value = xaccSplitGetValue (s);
1193 3 : if (gnc_numeric_zero_p (value))
1194 1 : PWARN("How can amount be nonzero and value be zero?");
1195 :
1196 3 : convrate = gnc_numeric_div(amount, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
1197 3 : return convrate;
1198 : }
1199 :
1200 0 : if (acc)
1201 : {
1202 : /* If we did find a matching account but its amount was zero,
1203 : * then perhaps this is a "special" income/loss transaction
1204 : */
1205 0 : if (found_acc_match)
1206 0 : return gnc_numeric_zero();
1207 : else
1208 0 : PERR("Cannot convert transaction -- no splits with proper conversion ratio");
1209 : }
1210 0 : return gnc_numeric_create (100, 100);
1211 : }
1212 :
1213 : gnc_numeric
1214 4 : xaccTransGetAccountBalance (const Transaction *trans,
1215 : const Account *account)
1216 : {
1217 : GList *node;
1218 4 : Split *last_split = nullptr;
1219 :
1220 : // Not really the appropriate error value.
1221 4 : g_return_val_if_fail(account && trans, gnc_numeric_error(GNC_ERROR_ARG));
1222 :
1223 6 : for (node = trans->splits; node; node = node->next)
1224 : {
1225 4 : Split *split = GNC_SPLIT(node->data);
1226 :
1227 4 : if (!xaccTransStillHasSplit(trans, split))
1228 0 : continue;
1229 4 : if (xaccSplitGetAccount(split) != account)
1230 2 : continue;
1231 :
1232 2 : if (!last_split)
1233 : {
1234 2 : last_split = split;
1235 2 : continue;
1236 : }
1237 :
1238 : /* This test needs to correspond to the comparison function used when
1239 : sorting the splits for computing the running balance. */
1240 0 : if (xaccSplitOrder (last_split, split) < 0)
1241 0 : last_split = split;
1242 : }
1243 :
1244 2 : return xaccSplitGetBalance (last_split);
1245 : }
1246 :
1247 : /********************************************************************\
1248 : \********************************************************************/
1249 : /* The new routine for setting the common currency */
1250 :
1251 : gnc_commodity *
1252 61597 : xaccTransGetCurrency (const Transaction *trans)
1253 : {
1254 61597 : return trans ? trans->common_currency : nullptr;
1255 : }
1256 :
1257 : /* Helper functions for xaccTransSetCurrency */
1258 : static gnc_numeric
1259 201 : find_new_rate(Transaction *trans, gnc_commodity *curr)
1260 : {
1261 : GList *node;
1262 201 : gnc_numeric rate = gnc_numeric_zero();
1263 847 : for (node = trans->splits; node != nullptr; node = g_list_next (node))
1264 : {
1265 389 : Split *split = GNC_SPLIT(node->data);
1266 : gnc_commodity *split_com =
1267 389 : xaccAccountGetCommodity(xaccSplitGetAccount(split));
1268 389 : if (gnc_commodity_equal(curr, split_com))
1269 : {
1270 : /* This looks backwards, but the amount of the balancing transaction
1271 : * that we're going to use it on is in the value's currency. */
1272 66 : rate = gnc_numeric_div(xaccSplitGetAmount(split),
1273 : xaccSplitGetValue(split),
1274 : GNC_DENOM_AUTO, GNC_HOW_RND_NEVER);
1275 66 : break;
1276 : }
1277 : }
1278 201 : return rate;
1279 : }
1280 :
1281 : static void
1282 130 : split_set_new_value(Split* split, gnc_commodity *curr, gnc_commodity *old_curr,
1283 : gnc_numeric rate)
1284 : {
1285 : gnc_commodity *split_com =
1286 130 : xaccAccountGetCommodity(xaccSplitGetAccount(split));
1287 130 : if (gnc_commodity_equal(curr, split_com))
1288 67 : xaccSplitSetValue(split, xaccSplitGetAmount(split));
1289 63 : else if (gnc_commodity_equal(old_curr, split_com))
1290 63 : xaccSplitSetSharePrice(split, rate);
1291 : else
1292 : {
1293 0 : gnc_numeric old_rate = gnc_numeric_div(xaccSplitGetValue(split),
1294 : xaccSplitGetAmount(split),
1295 : GNC_DENOM_AUTO,
1296 : GNC_HOW_RND_NEVER);
1297 0 : gnc_numeric new_rate = gnc_numeric_div(old_rate, rate, GNC_DENOM_AUTO,
1298 : GNC_HOW_RND_NEVER);
1299 0 : xaccSplitSetSharePrice(split, new_rate);
1300 : }
1301 130 : }
1302 :
1303 : /**
1304 : * Set a new currency on a transaction.
1305 : * When we do that to a transaction with splits we need to re-value
1306 : * all of the splits in the new currency.
1307 : * @param trans: The transaction to change
1308 : * @param curr: The new currency to set.
1309 : */
1310 : void
1311 3639 : xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr)
1312 : {
1313 3639 : if (!trans || !curr || trans->common_currency == curr) return;
1314 :
1315 3441 : gnc_commodity *old_curr = trans->common_currency;
1316 3441 : xaccTransBeginEdit(trans);
1317 :
1318 3441 : trans->common_currency = curr;
1319 3441 : if (old_curr != nullptr && trans->splits != nullptr)
1320 : {
1321 201 : gnc_numeric rate = find_new_rate(trans, curr);
1322 201 : if (!gnc_numeric_zero_p (rate))
1323 : {
1324 193 : FOR_EACH_SPLIT(trans, split_set_new_value(s, curr, old_curr, rate));
1325 : }
1326 : else
1327 : {
1328 417 : FOR_EACH_SPLIT(trans, xaccSplitSetValue(s, xaccSplitGetValue(s)));
1329 : }
1330 : }
1331 :
1332 3441 : qof_instance_set_dirty(QOF_INSTANCE(trans));
1333 3441 : mark_trans(trans); /* Dirty balance of every account in trans */
1334 3441 : xaccTransCommitEdit(trans);
1335 : }
1336 :
1337 : /********************************************************************\
1338 : \********************************************************************/
1339 :
1340 : void
1341 83504 : xaccTransBeginEdit (Transaction *trans)
1342 : {
1343 83504 : if (!trans) return;
1344 68997 : if (!qof_begin_edit(&trans->inst)) return;
1345 :
1346 7162 : if (qof_book_shutting_down(qof_instance_get_book(trans))) return;
1347 :
1348 4878 : if (!qof_book_is_readonly(qof_instance_get_book(trans)))
1349 : {
1350 4875 : xaccOpenLog ();
1351 4875 : xaccTransWriteLog (trans, 'B');
1352 : }
1353 :
1354 : /* Make a clone of the transaction; we will use this
1355 : * in case we need to roll-back the edit. */
1356 4878 : trans->orig = dupe_trans (trans);
1357 : }
1358 :
1359 : /********************************************************************\
1360 : \********************************************************************/
1361 :
1362 : void
1363 2420 : xaccTransDestroy (Transaction *trans)
1364 : {
1365 2420 : if (!trans) return;
1366 :
1367 2521 : if (!xaccTransGetReadOnly (trans) ||
1368 101 : qof_book_shutting_down(qof_instance_get_book(trans)))
1369 : {
1370 2406 : xaccTransBeginEdit(trans);
1371 2406 : qof_instance_set_destroying(trans, TRUE);
1372 2406 : xaccTransCommitEdit(trans);
1373 : }
1374 : }
1375 :
1376 : static void
1377 123 : destroy_gains (Transaction *trans)
1378 : {
1379 : SplitList *node;
1380 359 : for (node = trans->splits; node; node = node->next)
1381 : {
1382 236 : Split *s = GNC_SPLIT(node->data);
1383 236 : if (!xaccTransStillHasSplit(trans, s))
1384 1 : continue;
1385 :
1386 235 : if (GAINS_STATUS_UNKNOWN == s->gains) xaccSplitDetermineGainStatus(s);
1387 235 : if (s->gains_split && (GAINS_STATUS_GAINS & s->gains_split->gains))
1388 : {
1389 2 : Transaction *t = s->gains_split->parent;
1390 2 : xaccTransDestroy (t);
1391 2 : s->gains_split = nullptr;
1392 : }
1393 : }
1394 123 : }
1395 :
1396 : static void
1397 2406 : do_destroy (QofInstance* inst)
1398 : {
1399 2406 : Transaction *trans{GNC_TRANSACTION (inst)};
1400 2406 : gboolean shutting_down = qof_book_shutting_down(qof_instance_get_book(trans));
1401 :
1402 : /* If there are capital-gains transactions associated with this,
1403 : * they need to be destroyed too unless we're shutting down in
1404 : * which case all transactions will be destroyed. */
1405 2406 : if (!shutting_down)
1406 122 : destroy_gains (trans);
1407 :
1408 : /* Make a log in the journal before destruction. */
1409 2406 : if (!shutting_down && !qof_book_is_readonly(qof_instance_get_book(trans)))
1410 121 : xaccTransWriteLog (trans, 'D');
1411 :
1412 2406 : qof_event_gen (&trans->inst, QOF_EVENT_DESTROY, nullptr);
1413 : /* xaccFreeTransaction will also clean up the splits but without
1414 : * emitting GNC_EVENT_ITEM_REMOVED.
1415 : */
1416 2406 : xaccTransClearSplits(trans);
1417 2406 : xaccFreeTransaction (trans);
1418 2406 : }
1419 :
1420 : /********************************************************************\
1421 : \********************************************************************/
1422 :
1423 : /* Temporary hack for data consistency */
1424 : static int scrub_data = 1;
1425 30 : void xaccEnableDataScrubbing(void)
1426 : {
1427 30 : scrub_data = 1;
1428 30 : }
1429 30 : void xaccDisableDataScrubbing(void)
1430 : {
1431 30 : scrub_data = 0;
1432 30 : }
1433 :
1434 : /* Check for an implicitly deleted transaction */
1435 7036 : static gboolean was_trans_emptied(Transaction *trans)
1436 : {
1437 7041 : FOR_EACH_SPLIT(trans, return FALSE);
1438 5 : return TRUE;
1439 : }
1440 :
1441 1 : static void trans_on_error(QofInstance *inst, QofBackendError errcode)
1442 : {
1443 1 : Transaction *trans{GNC_TRANSACTION(inst)};
1444 :
1445 : /* If the backend puked, then we must roll-back
1446 : * at this point, and let the user know that we failed.
1447 : * The GUI should check for error conditions ...
1448 : */
1449 1 : if (ERR_BACKEND_MODIFIED == errcode)
1450 : {
1451 1 : PWARN("Another user has modified this transaction\n"
1452 : "\tjust a moment ago. Please look at their changes,\n"
1453 : "\tand try again, if needed.\n");
1454 : }
1455 :
1456 1 : xaccTransRollbackEdit(trans);
1457 1 : gnc_engine_signal_commit_error( errcode );
1458 1 : }
1459 :
1460 4632 : static void trans_cleanup_commit(QofInstance *inst)
1461 : {
1462 4632 : Transaction *trans{GNC_TRANSACTION(inst)};
1463 : GList *slist, *node;
1464 :
1465 : /* ------------------------------------------------- */
1466 : /* Make sure all associated splits are in proper order
1467 : * in their accounts with the correct balances. */
1468 :
1469 : /* Iterate over existing splits */
1470 4632 : slist = g_list_copy(trans->splits);
1471 14143 : for (node = slist; node; node = node->next)
1472 : {
1473 9511 : Split *s = GNC_SPLIT(node->data);
1474 9511 : if (!qof_instance_is_dirty(QOF_INSTANCE(s)))
1475 1315 : continue;
1476 :
1477 8196 : if ((s->parent != trans) || qof_instance_get_destroying(s))
1478 : {
1479 : /* Existing split either moved to another transaction or
1480 : was destroyed, drop from list */
1481 : GncEventData ed;
1482 19 : ed.node = trans;
1483 19 : ed.idx = g_list_index(trans->splits, s);
1484 19 : trans->splits = g_list_remove(trans->splits, s);
1485 19 : qof_event_gen(&s->inst, QOF_EVENT_REMOVE, &ed);
1486 : }
1487 :
1488 8196 : if (s->parent == trans)
1489 : {
1490 : /* Split was either added, destroyed or just changed */
1491 8187 : if (qof_instance_get_destroying(s))
1492 10 : qof_event_gen(&s->inst, QOF_EVENT_DESTROY, nullptr);
1493 8177 : else qof_event_gen(&s->inst, QOF_EVENT_MODIFY, nullptr);
1494 8187 : xaccSplitCommitEdit(s);
1495 : }
1496 : }
1497 4632 : g_list_free(slist);
1498 :
1499 4632 : if (!qof_book_is_readonly(qof_instance_get_book(trans)))
1500 4632 : xaccTransWriteLog (trans, 'C');
1501 :
1502 : /* Get rid of the copy we made. We won't be rolling back,
1503 : * so we don't need it any more. */
1504 4632 : PINFO ("get rid of rollback trans=%p", trans->orig);
1505 4632 : xaccFreeTransaction (trans->orig);
1506 4632 : trans->orig = nullptr;
1507 :
1508 : /* Sort the splits. Why do we need to do this ?? */
1509 : /* Good question. Who knows? */
1510 4632 : xaccTransSortSplits(trans);
1511 :
1512 : /* Put back to zero. */
1513 4632 : qof_instance_decrease_editlevel(trans);
1514 4632 : g_assert(qof_instance_get_editlevel(trans) == 0);
1515 :
1516 4632 : gen_event_trans (trans); //TODO: could be conditional
1517 4632 : qof_event_gen (&trans->inst, QOF_EVENT_MODIFY, nullptr);
1518 4632 : }
1519 :
1520 : void
1521 83375 : xaccTransCommitEdit (Transaction *trans)
1522 : {
1523 83375 : if (!trans) return;
1524 68868 : ENTER ("(trans=%p)", trans);
1525 :
1526 68868 : if (!qof_commit_edit (QOF_INSTANCE(trans)))
1527 : {
1528 61834 : LEAVE("editlevel non-zero");
1529 61834 : return;
1530 : }
1531 :
1532 : /* We increment this for the duration of the call
1533 : * so other functions don't result in a recursive
1534 : * call to xaccTransCommitEdit. */
1535 7034 : qof_instance_increase_editlevel(trans);
1536 :
1537 7034 : if (was_trans_emptied(trans))
1538 4 : qof_instance_set_destroying(trans, TRUE);
1539 :
1540 : /* Before committing the transaction, we are going to enforce certain
1541 : * constraints. In particular, we want to enforce the cap-gains
1542 : * and the balanced lot constraints. These constraints might
1543 : * change the number of splits in this transaction, and the
1544 : * transaction itself might be deleted. This is also why
1545 : * we can't really enforce these constraints elsewhere: they
1546 : * can cause pointers to splits and transactions to disappear out
1547 : * from under the holder.
1548 : */
1549 10094 : if (!qof_instance_get_destroying(trans) && scrub_data &&
1550 3060 : !qof_book_shutting_down(xaccTransGetBook(trans)))
1551 : {
1552 : /* If scrubbing gains recurses through here, don't call it again. */
1553 3060 : scrub_data = 0;
1554 : /* The total value of the transaction should sum to zero.
1555 : * Call the trans scrub routine to fix it. Indirectly, this
1556 : * routine also performs a number of other transaction fixes too.
1557 : */
1558 3060 : xaccTransScrubImbalance (trans, nullptr, nullptr);
1559 : /* Get the cap gains into a consistent state as well. */
1560 :
1561 : /* Lot Scrubbing is temporarily disabled. */
1562 3060 : if (g_getenv("GNC_AUTO_SCRUB_LOTS") != nullptr)
1563 0 : xaccTransScrubGains (trans, nullptr);
1564 :
1565 : /* Allow scrubbing in transaction commit again */
1566 3060 : scrub_data = 1;
1567 : }
1568 :
1569 : /* Record the time of last modification */
1570 7034 : if (0 == trans->date_entered)
1571 : {
1572 2103 : trans->date_entered = gnc_time(nullptr);
1573 2103 : qof_instance_set_dirty(QOF_INSTANCE(trans));
1574 : }
1575 :
1576 7034 : trans->txn_type = TXN_TYPE_UNCACHED;
1577 7034 : qof_commit_edit_part2(QOF_INSTANCE(trans), trans_on_error,
1578 : trans_cleanup_commit, do_destroy);
1579 7034 : LEAVE ("(trans=%p)", trans);
1580 : }
1581 :
1582 : /* Ughhh. The Rollback function is terribly complex, and, what's worse,
1583 : * it only rolls back the basics. The TransCommit functions did a bunch
1584 : * of Lot/Cap-gains scrubbing that don't get addressed/undone here, and
1585 : * so the rollback can potentially leave a bit of a mess behind. We
1586 : * really need a more robust undo capability. Part of the problem is
1587 : * that the biggest user of the undo is the multi-user backend, which
1588 : * also adds complexity.
1589 : */
1590 : void
1591 16 : xaccTransRollbackEdit (Transaction *trans)
1592 : {
1593 : GList *node, *onode;
1594 : QofBackend *be;
1595 : Transaction *orig;
1596 : GList *slist;
1597 : int num_preexist, i;
1598 :
1599 : /* FIXME: This isn't quite the right way to handle nested edits --
1600 : * there should be a stack of transaction states that are popped off
1601 : * and restored at each level -- but it does prevent restoring to the
1602 : * editlevel 0 state until one is returning to editlevel 0, and
1603 : * thereby prevents a crash caused by trans->orig getting nullptred too
1604 : * soon.
1605 : */
1606 16 : if (!qof_instance_get_editlevel (QOF_INSTANCE (trans))) return;
1607 15 : if (qof_instance_get_editlevel (QOF_INSTANCE (trans)) > 1) {
1608 2 : qof_instance_decrease_editlevel (QOF_INSTANCE (trans));
1609 2 : return;
1610 : }
1611 :
1612 13 : ENTER ("trans addr=%p\n", trans);
1613 :
1614 13 : check_open(trans);
1615 :
1616 : /* copy the original values back in. */
1617 :
1618 13 : orig = trans->orig;
1619 13 : std::swap (trans->num, orig->num);
1620 13 : std::swap (trans->description, orig->description);
1621 13 : trans->date_entered = orig->date_entered;
1622 13 : trans->date_posted = orig->date_posted;
1623 13 : std::swap (trans->common_currency, orig->common_currency);
1624 13 : qof_instance_swap_kvp (QOF_INSTANCE (trans), QOF_INSTANCE (orig));
1625 :
1626 : /* The splits at the front of trans->splits are exactly the same
1627 : splits as in the original, but some of them may have changed, so
1628 : we restore only those. */
1629 : /* FIXME: Runs off the transaction's splits, so deleted splits are not
1630 : * restored!
1631 : */
1632 13 : num_preexist = g_list_length(orig->splits);
1633 13 : slist = g_list_copy(trans->splits);
1634 34 : for (i = 0, node = slist, onode = orig->splits; node;
1635 21 : i++, node = node->next, onode = onode ? onode->next : nullptr)
1636 : {
1637 21 : Split *s = GNC_SPLIT(node->data);
1638 :
1639 21 : if (!qof_instance_is_dirty(QOF_INSTANCE(s)))
1640 16 : continue;
1641 :
1642 5 : if (i < num_preexist && onode)
1643 : {
1644 2 : Split *so = GNC_SPLIT(onode->data);
1645 :
1646 2 : xaccSplitRollbackEdit(s);
1647 2 : std::swap (s->action, so->action);
1648 2 : std::swap (s->memo, so->memo);
1649 2 : qof_instance_copy_kvp (QOF_INSTANCE (s), QOF_INSTANCE (so));
1650 2 : s->reconciled = so->reconciled;
1651 2 : s->amount = so->amount;
1652 2 : s->value = so->value;
1653 2 : s->lot = so->lot;
1654 2 : s->gains_split = so->gains_split;
1655 : //SET_GAINS_A_VDIRTY(s);
1656 2 : s->date_reconciled = so->date_reconciled;
1657 2 : qof_instance_mark_clean(QOF_INSTANCE(s));
1658 2 : }
1659 : else
1660 : {
1661 : /* Potentially added splits */
1662 3 : if (trans != xaccSplitGetParent(s))
1663 : {
1664 0 : trans->splits = g_list_remove(trans->splits, s);
1665 : /* New split added, but then moved to another
1666 : transaction */
1667 0 : continue;
1668 : }
1669 3 : xaccSplitRollbackEdit(s);
1670 3 : trans->splits = g_list_remove(trans->splits, s);
1671 3 : g_assert(trans != xaccSplitGetParent(s));
1672 : /* NB: our memory management policy here is that a new split
1673 : added to the transaction which is then rolled-back still
1674 : belongs to the engine. Specifically, it's freed by the
1675 : transaction to which it was added. Don't add the Split to
1676 : more than one transaction during the begin/commit block! */
1677 3 : if (nullptr == xaccSplitGetParent(s))
1678 : {
1679 3 : xaccFreeSplit(s); // a newly malloc'd split
1680 : }
1681 : }
1682 : }
1683 13 : g_list_free(slist);
1684 :
1685 : // orig->splits may still have duped splits so free them
1686 13 : g_list_free_full (orig->splits, (GDestroyNotify)xaccFreeSplit);
1687 13 : orig->splits = nullptr;
1688 :
1689 : /* Now that the engine copy is back to its original version,
1690 : * get the backend to fix it in the database */
1691 13 : be = qof_book_get_backend(qof_instance_get_book(trans));
1692 : /** \todo Fix transrollbackedit in QOF so that rollback
1693 : is exposed via the API. */
1694 13 : if (qof_backend_can_rollback (be))
1695 : {
1696 : QofBackendError errcode;
1697 :
1698 : /* clear errors */
1699 : do
1700 : {
1701 9 : errcode = qof_backend_get_error (be);
1702 : }
1703 9 : while (ERR_BACKEND_NO_ERR != errcode);
1704 :
1705 8 : qof_backend_rollback_instance (be, &(trans->inst));
1706 :
1707 8 : errcode = qof_backend_get_error (be);
1708 8 : if (ERR_BACKEND_MOD_DESTROY == errcode)
1709 : {
1710 : /* The backend is asking us to delete this transaction.
1711 : * This typically happens because another (remote) user
1712 : * has deleted this transaction, and we haven't found
1713 : * out about it until this user tried to edit it.
1714 : */
1715 1 : xaccTransDestroy (trans);
1716 1 : do_destroy (QOF_INSTANCE(trans));
1717 :
1718 : /* push error back onto the stack */
1719 1 : qof_backend_set_error (be, errcode);
1720 1 : LEAVE ("deleted trans addr=%p\n", trans);
1721 1 : return;
1722 : }
1723 7 : if (ERR_BACKEND_NO_ERR != errcode)
1724 : {
1725 1 : PERR ("Rollback Failed. Ouch!");
1726 : /* push error back onto the stack */
1727 1 : qof_backend_set_error (be, errcode);
1728 : }
1729 : }
1730 :
1731 12 : if (!qof_book_is_readonly(qof_instance_get_book(trans)))
1732 10 : xaccTransWriteLog (trans, 'R');
1733 :
1734 12 : xaccFreeTransaction (trans->orig);
1735 :
1736 12 : trans->orig = nullptr;
1737 12 : qof_instance_set_destroying(trans, FALSE);
1738 :
1739 : /* Put back to zero. */
1740 12 : qof_instance_decrease_editlevel(trans);
1741 : /* FIXME: The register code seems to depend on the engine to
1742 : generate an event during rollback, even though the state is just
1743 : reverting to what it was. */
1744 12 : gen_event_trans (trans);
1745 :
1746 12 : LEAVE ("trans addr=%p\n", trans);
1747 : }
1748 :
1749 : gboolean
1750 92 : xaccTransIsOpen (const Transaction *trans)
1751 : {
1752 92 : return trans ? (0 < qof_instance_get_editlevel(trans)) : FALSE;
1753 : }
1754 :
1755 : #define SECS_PER_DAY 86400
1756 :
1757 : int
1758 679009 : xaccTransOrder (const Transaction *ta, const Transaction *tb)
1759 : {
1760 679009 : return xaccTransOrder_num_action (ta, nullptr, tb, nullptr);
1761 : }
1762 :
1763 : /* Order a pair of potentially numeric string as numbers if both
1764 : * strings begin with numbers, ordering the remainder of the string
1765 : * lexically if the numeric parts are equal, and the whole strings
1766 : * lexically otherwise.
1767 : *
1768 : * Note that this won't work well for numbers > 10^18 and that
1769 : * negative numbers are treated as strings and will cause the pair to
1770 : * be ordered lexically.
1771 : */
1772 :
1773 : static int
1774 46874 : order_by_int64_or_string (const char* a, const char* b)
1775 : {
1776 46874 : char *end_a = nullptr, *end_b = nullptr;
1777 46874 : int cmp = 0;
1778 46874 : uint64_t na = strtoull(a, &end_a, 10);
1779 46874 : uint64_t nb = strtoull(b, &end_b, 10);
1780 46874 : if (na && nb)
1781 : {
1782 507 : if (na != nb)
1783 480 : return na < nb ? -1 : 1;
1784 27 : cmp = g_utf8_collate(end_a, end_b);
1785 : }
1786 : else
1787 : {
1788 46367 : cmp = g_utf8_collate(a, b);
1789 : }
1790 46394 : return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
1791 : }
1792 :
1793 : int
1794 679024 : xaccTransOrder_num_action (const Transaction *ta, const char *actna,
1795 : const Transaction *tb, const char *actnb)
1796 : {
1797 : const char *da, *db;
1798 : int retval;
1799 :
1800 679024 : if (ta == tb) return 0;
1801 669897 : if (!tb) return -1;
1802 669895 : if (!ta) return +1;
1803 :
1804 669894 : if (ta->date_posted != tb->date_posted)
1805 623012 : return (ta->date_posted > tb->date_posted) - (ta->date_posted < tb->date_posted);
1806 :
1807 : /* Always sort closing transactions after normal transactions */
1808 : {
1809 46882 : gboolean ta_is_closing = xaccTransGetIsClosingTxn (ta);
1810 46882 : gboolean tb_is_closing = xaccTransGetIsClosingTxn (tb);
1811 46882 : if (ta_is_closing != tb_is_closing)
1812 8 : return (ta_is_closing - tb_is_closing);
1813 : }
1814 :
1815 : /* otherwise, sort on number string */
1816 46874 : if (actna && actnb) /* split action string, if not nullptr */
1817 : {
1818 3 : retval = order_by_int64_or_string (actna, actnb);
1819 : }
1820 : else /* else transaction num string */
1821 : {
1822 46871 : retval = order_by_int64_or_string (ta->num, tb->num);
1823 : }
1824 46874 : if (retval)
1825 3125 : return retval;
1826 :
1827 43749 : if (ta->date_entered != tb->date_entered)
1828 65 : return (ta->date_entered > tb->date_entered) - (ta->date_entered < tb->date_entered);
1829 :
1830 : /* otherwise, sort on description string */
1831 43684 : da = ta->description ? ta->description : "";
1832 43684 : db = tb->description ? tb->description : "";
1833 43684 : retval = g_utf8_collate (da, db);
1834 43684 : if (retval)
1835 43453 : return retval;
1836 :
1837 : /* else, sort on guid - keeps sort stable. */
1838 231 : return qof_instance_guid_compare(ta, tb);
1839 : }
1840 :
1841 : /********************************************************************\
1842 : \********************************************************************/
1843 :
1844 : static void
1845 718 : set_kvp_string_path (Transaction *txn, const Path& path, const char *value)
1846 : {
1847 718 : g_return_if_fail (GNC_IS_TRANSACTION(txn));
1848 718 : xaccTransBeginEdit(txn);
1849 1406 : auto val = value && *value ? std::make_optional<const char*>(g_strdup(value)) : std::nullopt;
1850 718 : qof_instance_set_path_kvp<const char*> (QOF_INSTANCE(txn), val, path);
1851 718 : qof_instance_set_dirty (QOF_INSTANCE(txn));
1852 718 : xaccTransCommitEdit(txn);
1853 : }
1854 :
1855 : static const char*
1856 14215 : get_kvp_string_path (const Transaction *txn, const Path& path)
1857 : {
1858 14215 : auto rv{qof_instance_get_path_kvp<const char*> (QOF_INSTANCE(txn), path)};
1859 28430 : return rv ? *rv : nullptr;
1860 : }
1861 :
1862 : static inline void
1863 4622 : xaccTransSetDateInternal(Transaction *trans, time64 *dadate, time64 val)
1864 : {
1865 4622 : xaccTransBeginEdit(trans);
1866 :
1867 : #if 0 /* gnc_ctime is expensive so change to 1 only if you need to debug setting
1868 : * dates. */
1869 : {
1870 : time64 secs = (time64) val.tv_sec;
1871 : gchar *tstr = gnc_ctime (&secs);
1872 : PINFO ("addr=%p set date to %" G_GUINT64_FORMAT ".%09ld %s\n",
1873 : trans, val.tv_sec, val.tv_nsec, tstr ? tstr : "(null)");
1874 : g_free(tstr);
1875 : }
1876 : #endif
1877 4622 : *dadate = val;
1878 4622 : qof_instance_set_dirty(QOF_INSTANCE(trans));
1879 4622 : mark_trans(trans);
1880 4622 : xaccTransCommitEdit(trans);
1881 :
1882 : /* Because the date has changed, we need to make sure that each of
1883 : * the splits is properly ordered in each of their accounts. We
1884 : * could do that here, simply by reinserting each split into its
1885 : * account. However, in some ways this is bad behaviour, and it
1886 : * seems much better/nicer to defer that until the commit phase,
1887 : * i.e. until the user has called the xaccTransCommitEdit()
1888 : * routine. So, for now, we are done. */
1889 4622 : }
1890 :
1891 : static inline void
1892 3382 : set_gains_date_dirty (Transaction *trans)
1893 : {
1894 3443 : FOR_EACH_SPLIT(trans, s->gains |= GAINS_STATUS_DATE_DIRTY);
1895 3382 : }
1896 :
1897 : void
1898 1179 : xaccTransSetDatePostedSecs (Transaction *trans, time64 secs)
1899 : {
1900 1179 : if (!trans) return;
1901 1179 : xaccTransSetDateInternal(trans, &trans->date_posted, secs);
1902 1179 : set_gains_date_dirty(trans);
1903 : }
1904 :
1905 : void
1906 110 : xaccTransSetDatePostedSecsNormalized (Transaction *trans, time64 time)
1907 : {
1908 : GDate date;
1909 110 : gnc_gdate_set_time64(&date, time);
1910 110 : xaccTransSetDatePostedGDate(trans, date);
1911 110 : }
1912 :
1913 : void
1914 2203 : xaccTransSetDatePostedGDate (Transaction *trans, GDate date)
1915 : {
1916 2203 : if (!trans) return;
1917 :
1918 : /* We additionally save this date into a kvp frame to ensure in
1919 : * the future a date which was set as *date* (without time) can
1920 : * clearly be distinguished from the time64. */
1921 4406 : qof_instance_set_path_kvp<GDate> (QOF_INSTANCE(trans), date, {TRANS_DATE_POSTED});
1922 2203 : qof_instance_set_dirty (QOF_INSTANCE(trans));
1923 : /* mark dirty and commit handled by SetDateInternal */
1924 2203 : xaccTransSetDateInternal(trans, &trans->date_posted,
1925 : gdate_to_time64(date));
1926 2203 : set_gains_date_dirty (trans);
1927 : }
1928 :
1929 : void
1930 1240 : xaccTransSetDateEnteredSecs (Transaction *trans, time64 secs)
1931 : {
1932 1240 : if (!trans) return;
1933 1240 : xaccTransSetDateInternal(trans, &trans->date_entered, secs);
1934 : }
1935 :
1936 : void
1937 1990 : xaccTransSetDate (Transaction *trans, int day, int mon, int year)
1938 : {
1939 1990 : if (!trans) return;
1940 : GDate date;
1941 1990 : g_date_clear (&date, 1);
1942 1990 : if (g_date_valid_dmy (day, static_cast<GDateMonth>(mon), year))
1943 1990 : g_date_set_dmy (&date, day, static_cast<GDateMonth>(mon), year);
1944 : else
1945 : {
1946 0 : PWARN("Attempted to set invalid date %d-%d-%d; set today's date instead.",
1947 : year, mon, day);
1948 0 : gnc_gdate_set_today (&date);
1949 : }
1950 1990 : xaccTransSetDatePostedGDate(trans, date);
1951 : }
1952 :
1953 : void
1954 76 : xaccTransSetDateDue (Transaction * trans, time64 time)
1955 : {
1956 76 : if (!trans) return;
1957 76 : xaccTransBeginEdit(trans);
1958 152 : qof_instance_set_path_kvp<Time64> (QOF_INSTANCE (trans), Time64{time}, {TRANS_DATE_DUE_KVP});
1959 76 : qof_instance_set_dirty(QOF_INSTANCE(trans));
1960 76 : xaccTransCommitEdit(trans);
1961 : }
1962 :
1963 : void
1964 100 : xaccTransSetTxnType (Transaction *trans, char type)
1965 : {
1966 100 : char s[2] = {type, '\0'};
1967 200 : set_kvp_string_path (trans, {TRANS_TXN_TYPE_KVP}, s);
1968 100 : }
1969 :
1970 16 : void xaccTransClearReadOnly (Transaction *trans)
1971 : {
1972 32 : set_kvp_string_path (trans, {TRANS_READ_ONLY_REASON}, nullptr);
1973 16 : }
1974 :
1975 : void
1976 181 : xaccTransSetReadOnly (Transaction *trans, const char *reason)
1977 : {
1978 181 : if (trans && reason)
1979 540 : set_kvp_string_path (trans, {TRANS_READ_ONLY_REASON}, reason);
1980 181 : }
1981 :
1982 : /********************************************************************\
1983 : \********************************************************************/
1984 :
1985 : /* QOF does not open the trans before setting a parameter,
1986 : but the call uses check_open so we cannot use the call directly. */
1987 : static void
1988 0 : qofTransSetNum (Transaction *trans, const char *xnum)
1989 : {
1990 0 : if (!qof_begin_edit(&trans->inst)) return;
1991 0 : xaccTransSetNum(trans, xnum);
1992 0 : qof_commit_edit(&trans->inst);
1993 : }
1994 :
1995 : void
1996 865 : xaccTransSetNum (Transaction *trans, const char *xnum)
1997 : {
1998 865 : if (!trans || !xnum) return;
1999 865 : xaccTransBeginEdit(trans);
2000 :
2001 865 : CACHE_REPLACE(trans->num, xnum);
2002 865 : qof_instance_set_dirty(QOF_INSTANCE(trans));
2003 865 : mark_trans(trans); /* Dirty balance of every account in trans */
2004 865 : xaccTransCommitEdit(trans);
2005 : }
2006 :
2007 : static void
2008 0 : qofTransSetDescription (Transaction *trans, const char *desc)
2009 : {
2010 0 : if (!qof_begin_edit(&trans->inst)) return;
2011 0 : xaccTransSetDescription(trans, desc);
2012 0 : qof_commit_edit(&trans->inst);
2013 : }
2014 :
2015 : void
2016 3373 : xaccTransSetDescription (Transaction *trans, const char *desc)
2017 : {
2018 3373 : if (!trans || !desc) return;
2019 3369 : xaccTransBeginEdit(trans);
2020 :
2021 3369 : CACHE_REPLACE(trans->description, desc);
2022 3369 : qof_instance_set_dirty(QOF_INSTANCE(trans));
2023 3369 : xaccTransCommitEdit(trans);
2024 : }
2025 :
2026 : void
2027 11 : xaccTransSetDocLink (Transaction *trans, const char *doclink)
2028 : {
2029 11 : if (!trans || !doclink) return;
2030 32 : set_kvp_string_path (trans, {doclink_uri_str}, doclink);
2031 16 : }
2032 :
2033 : static void
2034 0 : qofTransSetNotes (Transaction *trans, const char *notes)
2035 : {
2036 0 : if (!qof_begin_edit(&trans->inst)) return;
2037 0 : xaccTransSetNotes(trans, notes);
2038 0 : qof_commit_edit(&trans->inst);
2039 : }
2040 :
2041 : void
2042 100 : xaccTransSetNotes (Transaction *trans, const char *notes)
2043 : {
2044 100 : if (!trans || !notes) return;
2045 380 : set_kvp_string_path (trans, {trans_notes_str}, notes);
2046 190 : }
2047 :
2048 : void
2049 27 : xaccTransSetIsClosingTxn (Transaction *trans, gboolean is_closing)
2050 : {
2051 27 : xaccTransBeginEdit(trans);
2052 27 : auto val = is_closing ? std::make_optional<int64_t>(1) : std::nullopt;
2053 81 : qof_instance_set_path_kvp<int64_t> (QOF_INSTANCE(trans), val, {trans_is_closing_str});
2054 27 : xaccTransCommitEdit(trans);
2055 81 : }
2056 :
2057 :
2058 : /********************************************************************\
2059 : \********************************************************************/
2060 : void
2061 2415 : xaccTransClearSplits(Transaction* trans)
2062 : {
2063 2415 : xaccTransBeginEdit(trans);
2064 : /* We only own the splits that still think they belong to us. This is done
2065 : in 2 steps. In the first, the splits are marked as being destroyed, but they
2066 : are not destroyed yet. In the second, the destruction is committed which will
2067 : do the actual destruction. If both steps are done for a split before they are
2068 : done for the next split, then a split will still be on the split list after it
2069 : has been freed. This can cause other parts of the code (e.g. in xaccSplitDestroy())
2070 : to reference the split after it has been freed. */
2071 7346 : for (auto node = trans->splits; node; node = node->next)
2072 : {
2073 4931 : auto s = GNC_SPLIT(node->data);
2074 4931 : if (s && s->parent == trans)
2075 : {
2076 4930 : xaccSplitDestroy(s);
2077 : }
2078 : }
2079 7346 : for (auto node = trans->splits; node; node = node->next)
2080 : {
2081 4931 : auto s = GNC_SPLIT(node->data);
2082 4931 : if (s && s->parent == trans)
2083 : {
2084 4930 : xaccSplitCommitEdit(s);
2085 : }
2086 : }
2087 2415 : g_list_free (trans->splits);
2088 2415 : trans->splits = nullptr;
2089 :
2090 2415 : xaccTransCommitEdit(trans);
2091 2415 : }
2092 :
2093 : Split *
2094 383 : xaccTransGetSplit (const Transaction *trans, int i)
2095 : {
2096 383 : int j = 0;
2097 383 : if (!trans || i < 0) return nullptr;
2098 :
2099 548 : FOR_EACH_SPLIT(trans, { if (i == j) return s; j++; });
2100 50 : return nullptr;
2101 : }
2102 :
2103 : int
2104 4971 : xaccTransGetSplitIndex(const Transaction *trans, const Split *split)
2105 : {
2106 4971 : int j = 0;
2107 4971 : g_return_val_if_fail(trans && split, -1);
2108 :
2109 7709 : FOR_EACH_SPLIT(trans, { if (s == split) return j; j++; });
2110 1 : return -1;
2111 : }
2112 :
2113 : SplitList *
2114 5853 : xaccTransGetSplitList (const Transaction *trans)
2115 : {
2116 5853 : return trans ? trans->splits : nullptr;
2117 : }
2118 :
2119 : SplitList *
2120 0 : xaccTransGetPaymentAcctSplitList (const Transaction *trans)
2121 : {
2122 0 : GList *pay_splits = nullptr;
2123 0 : FOR_EACH_SPLIT (trans,
2124 : const Account *account = xaccSplitGetAccount(s);
2125 : if (account && gncBusinessIsPaymentAcctType(xaccAccountGetType(account)))
2126 : pay_splits = g_list_prepend (pay_splits, s);
2127 : );
2128 :
2129 0 : pay_splits = g_list_reverse (pay_splits);
2130 0 : return pay_splits;
2131 : }
2132 :
2133 : SplitList *
2134 9 : xaccTransGetAPARAcctSplitList (const Transaction *trans, gboolean strict)
2135 : {
2136 9 : GList *apar_splits = nullptr;
2137 9 : if (!trans) return nullptr;
2138 :
2139 28 : FOR_EACH_SPLIT (trans,
2140 : const Account *account = xaccSplitGetAccount(s);
2141 : if (account && xaccAccountIsAPARType(xaccAccountGetType(account)))
2142 : {
2143 :
2144 : if (!strict)
2145 : apar_splits = g_list_prepend (apar_splits, s);
2146 : else
2147 : {
2148 : GncOwner owner;
2149 : GNCLot *lot = xaccSplitGetLot(s);
2150 : if (lot &&
2151 : (gncInvoiceGetInvoiceFromLot (lot) ||
2152 : gncOwnerGetOwnerFromLot (lot, &owner)))
2153 : apar_splits = g_list_prepend (apar_splits, s);
2154 : }
2155 : }
2156 : );
2157 :
2158 9 : apar_splits = g_list_reverse (apar_splits);
2159 9 : return apar_splits;
2160 : }
2161 :
2162 0 : Split *xaccTransGetFirstPaymentAcctSplit(const Transaction *trans)
2163 : {
2164 0 : FOR_EACH_SPLIT (trans,
2165 : const Account *account = xaccSplitGetAccount(s);
2166 : if (account && gncBusinessIsPaymentAcctType(xaccAccountGetType(account)))
2167 : return s;
2168 : );
2169 :
2170 0 : return nullptr;
2171 : }
2172 :
2173 22 : Split *xaccTransGetFirstAPARAcctSplit (const Transaction *trans, gboolean strict)
2174 : {
2175 40 : FOR_EACH_SPLIT (trans,
2176 : const Account *account = xaccSplitGetAccount(s);
2177 : if (account && xaccAccountIsAPARType(xaccAccountGetType(account)))
2178 : {
2179 : GNCLot *lot;
2180 : GncOwner owner;
2181 :
2182 : if (!strict)
2183 : return s;
2184 :
2185 : lot = xaccSplitGetLot(s);
2186 : if (lot &&
2187 : (gncInvoiceGetInvoiceFromLot (lot) ||
2188 : gncOwnerGetOwnerFromLot (lot, &owner)))
2189 : return s;
2190 : }
2191 : );
2192 :
2193 0 : return nullptr;
2194 : }
2195 :
2196 : int
2197 206 : xaccTransCountSplits (const Transaction *trans)
2198 : {
2199 206 : gint i = 0;
2200 206 : g_return_val_if_fail (trans != nullptr, 0);
2201 618 : FOR_EACH_SPLIT(trans, i++);
2202 206 : return i;
2203 : }
2204 :
2205 : const char *
2206 2086 : xaccTransGetNum (const Transaction *trans)
2207 : {
2208 2086 : return trans ? trans->num : nullptr;
2209 : }
2210 :
2211 : const char *
2212 4735 : xaccTransGetDescription (const Transaction *trans)
2213 : {
2214 4735 : return trans ? trans->description : nullptr;
2215 : }
2216 :
2217 : const char *
2218 28 : xaccTransGetDocLink (const Transaction *trans)
2219 : {
2220 112 : return get_kvp_string_path (trans, {doclink_uri_str});
2221 56 : }
2222 :
2223 : const char *
2224 5663 : xaccTransGetNotes (const Transaction *trans)
2225 : {
2226 22652 : return get_kvp_string_path (trans, {trans_notes_str});
2227 11326 : }
2228 :
2229 : gboolean
2230 192544 : xaccTransGetIsClosingTxn (const Transaction *trans)
2231 : {
2232 577632 : auto rv{qof_instance_get_path_kvp<int64_t> (QOF_INSTANCE(trans), {trans_is_closing_str})};
2233 385088 : return rv ? *rv != 0 : FALSE;
2234 385088 : }
2235 :
2236 : /********************************************************************\
2237 : \********************************************************************/
2238 :
2239 : time64
2240 31630 : xaccTransGetDate (const Transaction *trans)
2241 : {
2242 31630 : return trans ? trans->date_posted : 0;
2243 : }
2244 :
2245 : /*################## Added for Reg2 #################*/
2246 : time64
2247 7 : xaccTransGetDateEntered (const Transaction *trans)
2248 : {
2249 7 : return trans ? trans->date_entered : 0;
2250 : }
2251 : /*################## Added for Reg2 #################*/
2252 :
2253 : time64
2254 11848 : xaccTransRetDatePosted (const Transaction *trans)
2255 : {
2256 11848 : return trans ? trans->date_posted : 0;
2257 : }
2258 :
2259 : GDate
2260 2 : xaccTransGetDatePostedGDate (const Transaction *trans)
2261 : {
2262 : GDate result;
2263 2 : g_date_clear (&result, 1);
2264 2 : if (trans)
2265 : {
2266 : /* Can we look up this value in the kvp slot? If yes, use it
2267 : * from there because it doesn't suffer from time zone
2268 : * shifts. */
2269 6 : if (auto res = qof_instance_get_path_kvp<GDate> (QOF_INSTANCE(trans), {TRANS_DATE_POSTED}))
2270 1 : result = *res;
2271 2 : if (! g_date_valid (&result) || gdate_to_time64 (result) == INT64_MAX)
2272 : {
2273 : /* Well, this txn doesn't have a valid GDate saved in a slot.
2274 : * time64_to_gdate() uses local time and we want UTC so we have
2275 : * to write it out.
2276 : */
2277 1 : time64 time = xaccTransGetDate(trans);
2278 1 : struct tm *stm = gnc_gmtime(&time);
2279 1 : if (stm)
2280 : {
2281 1 : g_date_set_dmy(&result, stm->tm_mday,
2282 1 : (GDateMonth)(stm->tm_mon + 1),
2283 1 : stm->tm_year + 1900);
2284 1 : free(stm);
2285 : }
2286 : }
2287 : }
2288 2 : return result;
2289 : }
2290 :
2291 : time64
2292 108 : xaccTransRetDateEntered (const Transaction *trans)
2293 : {
2294 108 : return trans ? trans->date_entered : 0;
2295 : }
2296 :
2297 : time64
2298 85 : xaccTransRetDateDue(const Transaction *trans)
2299 : {
2300 85 : if (!trans) return 0;
2301 170 : auto res = qof_instance_get_path_kvp<Time64> (QOF_INSTANCE (trans), {TRANS_DATE_DUE_KVP});
2302 85 : return res ? res->t : xaccTransRetDatePosted (trans);
2303 : }
2304 :
2305 : char
2306 343 : xaccTransGetTxnType (Transaction *trans)
2307 : {
2308 343 : gboolean has_nonAPAR_split = FALSE;
2309 :
2310 343 : if (!trans) return TXN_TYPE_NONE;
2311 :
2312 343 : if (trans->txn_type != TXN_TYPE_UNCACHED)
2313 268 : return trans->txn_type;
2314 :
2315 75 : trans->txn_type = TXN_TYPE_NONE;
2316 413 : for (GList *n = xaccTransGetSplitList (trans); n; n = g_list_next (n))
2317 : {
2318 169 : Account *acc = xaccSplitGetAccount (GNC_SPLIT(n->data));
2319 :
2320 169 : if (!acc)
2321 0 : continue;
2322 :
2323 169 : if (!xaccAccountIsAPARType (xaccAccountGetType (acc)))
2324 111 : has_nonAPAR_split = TRUE;
2325 58 : else if (trans->txn_type == TXN_TYPE_NONE)
2326 : {
2327 55 : GNCLot *lot = xaccSplitGetLot (GNC_SPLIT(n->data));
2328 55 : GncInvoice *invoice = gncInvoiceGetInvoiceFromLot (lot);
2329 : GncOwner owner;
2330 :
2331 55 : if (invoice && trans == gncInvoiceGetPostedTxn (invoice))
2332 22 : trans->txn_type = TXN_TYPE_INVOICE;
2333 33 : else if (invoice || gncOwnerGetOwnerFromLot (lot, &owner))
2334 30 : trans->txn_type = TXN_TYPE_PAYMENT;
2335 : }
2336 : }
2337 :
2338 75 : if (!has_nonAPAR_split && (trans->txn_type == TXN_TYPE_PAYMENT))
2339 2 : trans->txn_type = TXN_TYPE_LINK;
2340 :
2341 75 : return trans->txn_type;
2342 : }
2343 :
2344 : const char *
2345 2548 : xaccTransGetReadOnly (Transaction *trans)
2346 : {
2347 7644 : return get_kvp_string_path (trans, {TRANS_READ_ONLY_REASON});
2348 : }
2349 :
2350 : static gboolean
2351 0 : xaccTransIsSXTemplate (const Transaction * trans)
2352 : {
2353 0 : Split *split0 = xaccTransGetSplit (trans, 0);
2354 0 : if (split0 != nullptr)
2355 : {
2356 0 : char* formula = nullptr;
2357 0 : g_object_get (split0, "sx-debit-formula", &formula, nullptr);
2358 0 : if (formula != nullptr)
2359 : {
2360 0 : g_free (formula);
2361 0 : return TRUE;
2362 : }
2363 0 : g_object_get (split0, "sx-credit-formula", &formula, nullptr);
2364 0 : if (formula != nullptr)
2365 : {
2366 0 : g_free (formula);
2367 0 : return TRUE;
2368 : }
2369 : }
2370 0 : return FALSE;
2371 : }
2372 :
2373 0 : gboolean xaccTransIsReadonlyByPostedDate(const Transaction *trans)
2374 : {
2375 : GDate *threshold_date;
2376 : GDate trans_date;
2377 0 : const QofBook *book = xaccTransGetBook (trans);
2378 : gboolean result;
2379 0 : g_assert(trans);
2380 :
2381 0 : if (!qof_book_uses_autoreadonly(book))
2382 : {
2383 0 : return FALSE;
2384 : }
2385 :
2386 0 : if (xaccTransIsSXTemplate (trans))
2387 0 : return FALSE;
2388 :
2389 0 : threshold_date = qof_book_get_autoreadonly_gdate(book);
2390 0 : g_assert(threshold_date); // ok because we checked uses_autoreadonly before
2391 0 : trans_date = xaccTransGetDatePostedGDate(trans);
2392 :
2393 : // g_warning("there is auto-read-only with days=%d, trans_date_day=%d, threshold_date_day=%d",
2394 : // qof_book_get_num_days_autofreeze(book),
2395 : // g_date_get_day(&trans_date),
2396 : // g_date_get_day(threshold_date));
2397 :
2398 0 : if (g_date_compare(&trans_date, threshold_date) < 0)
2399 : {
2400 : //g_warning("we are auto-read-only");
2401 0 : result = TRUE;
2402 : }
2403 : else
2404 : {
2405 0 : result = FALSE;
2406 : }
2407 0 : g_date_free(threshold_date);
2408 0 : return result;
2409 : }
2410 :
2411 : gboolean
2412 0 : xaccTransHasReconciledSplitsByAccount (const Transaction *trans,
2413 : const Account *account)
2414 : {
2415 : GList *node;
2416 :
2417 0 : for (node = xaccTransGetSplitList (trans); node; node = node->next)
2418 : {
2419 0 : Split *split = GNC_SPLIT(node->data);
2420 :
2421 0 : if (!xaccTransStillHasSplit(trans, split))
2422 0 : continue;
2423 0 : if (account && (xaccSplitGetAccount(split) != account))
2424 0 : continue;
2425 :
2426 0 : switch (xaccSplitGetReconcile (split))
2427 : {
2428 0 : case YREC:
2429 : case FREC:
2430 0 : return TRUE;
2431 :
2432 0 : default:
2433 0 : break;
2434 : }
2435 : }
2436 :
2437 0 : return FALSE;
2438 : }
2439 :
2440 : gboolean
2441 0 : xaccTransHasReconciledSplits (const Transaction *trans)
2442 : {
2443 0 : return xaccTransHasReconciledSplitsByAccount (trans, nullptr);
2444 : }
2445 :
2446 :
2447 : gboolean
2448 0 : xaccTransHasSplitsInStateByAccount (const Transaction *trans,
2449 : const char state,
2450 : const Account *account)
2451 : {
2452 : GList *node;
2453 :
2454 0 : for (node = xaccTransGetSplitList (trans); node; node = node->next)
2455 : {
2456 0 : Split *split = GNC_SPLIT(node->data);
2457 :
2458 0 : if (!xaccTransStillHasSplit(trans, split))
2459 0 : continue;
2460 0 : if (account && (xaccSplitGetAccount(split) != account))
2461 0 : continue;
2462 :
2463 0 : if (split->reconciled == state)
2464 0 : return TRUE;
2465 : }
2466 :
2467 0 : return FALSE;
2468 : }
2469 :
2470 : gboolean
2471 0 : xaccTransHasSplitsInState (const Transaction *trans, const char state)
2472 : {
2473 0 : return xaccTransHasSplitsInStateByAccount (trans, state, nullptr);
2474 : }
2475 :
2476 :
2477 : /********************************************************************\
2478 : \********************************************************************/
2479 :
2480 :
2481 : /* ====================================================================== */
2482 :
2483 : static int
2484 8 : counter_thunk(Transaction *t, void *data)
2485 : {
2486 8 : (*((guint*)data))++;
2487 8 : return 0;
2488 : }
2489 :
2490 : guint
2491 8 : gnc_book_count_transactions(QofBook *book)
2492 : {
2493 8 : guint count = 0;
2494 8 : xaccAccountTreeForEachTransaction(gnc_book_get_root_account(book),
2495 : counter_thunk, (void*)&count);
2496 8 : return count;
2497 : }
2498 :
2499 : /********************************************************************\
2500 : \********************************************************************/
2501 :
2502 : void
2503 103 : xaccTransVoid(Transaction *trans, const char *reason)
2504 : {
2505 104 : g_return_if_fail(trans && reason);
2506 :
2507 : /* Prevent voiding transactions that are already marked
2508 : * read only, for example generated by the business features.
2509 : */
2510 103 : if (xaccTransGetReadOnly (trans))
2511 : {
2512 1 : PWARN ("Refusing to void a read-only transaction!");
2513 1 : return;
2514 : }
2515 102 : xaccTransBeginEdit(trans);
2516 :
2517 102 : char iso8601_str[ISO_DATELENGTH + 1] = "";
2518 102 : gnc_time64_to_iso8601_buff (gnc_time(nullptr), iso8601_str);
2519 :
2520 408 : if (auto s = get_kvp_string_path (trans, {trans_notes_str}))
2521 4 : set_kvp_string_path (trans, {void_former_notes_str}, s);
2522 408 : set_kvp_string_path (trans, {trans_notes_str}, _("Voided transaction"));
2523 306 : set_kvp_string_path (trans, {void_reason_str}, reason);
2524 306 : set_kvp_string_path (trans, {void_time_str}, iso8601_str);
2525 :
2526 306 : FOR_EACH_SPLIT(trans, xaccSplitVoid(s));
2527 :
2528 : /* Dirtying taken care of by SetReadOnly */
2529 102 : xaccTransSetReadOnly(trans, _("Transaction Voided"));
2530 102 : xaccTransCommitEdit(trans);
2531 818 : }
2532 :
2533 : gboolean
2534 5862 : xaccTransGetVoidStatus(const Transaction *trans)
2535 : {
2536 5862 : auto t = xaccTransGetVoidTime (trans);
2537 5862 : return t != INT64_MAX;
2538 : }
2539 :
2540 : const char *
2541 6 : xaccTransGetVoidReason(const Transaction *trans)
2542 : {
2543 24 : return get_kvp_string_path (trans, {void_reason_str});
2544 12 : }
2545 :
2546 : time64
2547 5865 : xaccTransGetVoidTime(const Transaction *tr)
2548 : {
2549 17595 : auto void_str{get_kvp_string_path (tr, {void_time_str})};
2550 5865 : return void_str ? gnc_iso8601_to_time64_gmt (void_str) : INT64_MAX;
2551 11730 : }
2552 :
2553 : void
2554 3 : xaccTransUnvoid (Transaction *trans)
2555 : {
2556 3 : g_return_if_fail(trans);
2557 :
2558 3 : if (!xaccTransGetVoidStatus (trans))
2559 0 : return; /* Transaction isn't voided. Bail. */
2560 :
2561 3 : xaccTransBeginEdit(trans);
2562 :
2563 21 : set_kvp_string_path (trans, {trans_notes_str}, get_kvp_string_path (trans, {void_former_notes_str}));
2564 9 : set_kvp_string_path (trans, {void_former_notes_str}, nullptr);
2565 9 : set_kvp_string_path (trans, {void_reason_str}, nullptr);
2566 9 : set_kvp_string_path (trans, {void_time_str}, nullptr);
2567 :
2568 9 : FOR_EACH_SPLIT(trans, xaccSplitUnvoid(s));
2569 :
2570 : /* Dirtying taken care of by ClearReadOnly */
2571 3 : xaccTransClearReadOnly(trans);
2572 3 : xaccTransCommitEdit(trans);
2573 30 : }
2574 :
2575 : Transaction *
2576 2 : xaccTransReverse (Transaction *orig)
2577 : {
2578 : Transaction *trans;
2579 2 : g_return_val_if_fail(orig, nullptr);
2580 :
2581 : /* First edit, dirty, and commit orig to ensure that any trading
2582 : * splits are correctly balanced.
2583 : */
2584 2 : xaccTransBeginEdit (orig);
2585 2 : qof_instance_set_dirty (QOF_INSTANCE (orig));
2586 2 : xaccTransCommitEdit (orig);
2587 :
2588 2 : trans = xaccTransClone(orig);
2589 2 : g_return_val_if_fail (trans, nullptr);
2590 2 : xaccTransBeginEdit(trans);
2591 :
2592 : /* Reverse the values on each split. Clear per-split info. */
2593 6 : FOR_EACH_SPLIT(trans,
2594 : {
2595 : xaccSplitSetAmount(s, gnc_numeric_neg(xaccSplitGetAmount(s)));
2596 : xaccSplitSetValue(s, gnc_numeric_neg(xaccSplitGetValue(s)));
2597 : xaccSplitSetReconcile(s, NREC);
2598 : });
2599 :
2600 : /* Now update the original with a pointer to the new one */
2601 4 : qof_instance_set_path_kvp<GncGUID*> (QOF_INSTANCE (orig), guid_copy(xaccTransGetGUID(trans)),
2602 : {TRANS_REVERSED_BY});
2603 :
2604 : /* Make sure the reverse transaction is not read-only */
2605 2 : xaccTransClearReadOnly(trans);
2606 :
2607 2 : qof_instance_set_dirty(QOF_INSTANCE(trans));
2608 2 : xaccTransCommitEdit(trans);
2609 2 : return trans;
2610 : }
2611 :
2612 : Transaction *
2613 1 : xaccTransGetReversedBy(const Transaction *trans)
2614 : {
2615 1 : g_return_val_if_fail(trans, nullptr);
2616 2 : auto g = qof_instance_get_path_kvp<GncGUID*> (QOF_INSTANCE(trans), {TRANS_REVERSED_BY});
2617 1 : return g ? xaccTransLookup (*g, qof_instance_get_book (trans)) : nullptr;
2618 : }
2619 :
2620 : /* ============================================================== */
2621 : /** The xaccTransScrubGainsDate() routine is used to keep the posted date
2622 : * of gains splits in sync with the posted date of the transaction
2623 : * that caused the gains.
2624 : *
2625 : * The posted date is kept in sync using a lazy-evaluation scheme.
2626 : * If xaccTransactionSetDatePosted() is called, the date change is
2627 : * accepted, and the split is marked date-dirty. If the posted date
2628 : * is queried for (using GetDatePosted()), then the transaction is
2629 : * evaluated. If it's a gains-transaction, then its date is copied
2630 : * from the source transaction that created the gains.
2631 : */
2632 :
2633 : static void
2634 3 : xaccTransScrubGainsDate (Transaction *trans)
2635 : {
2636 : SplitList *node;
2637 3 : SplitList *splits_copy = g_list_copy(trans->splits);
2638 9 : for (node = splits_copy; node; node = node->next)
2639 : {
2640 6 : Split *s = GNC_SPLIT(node->data);
2641 :
2642 6 : if (!xaccTransStillHasSplit(trans, s)) continue;
2643 6 : xaccSplitDetermineGainStatus(s);
2644 :
2645 6 : if ((GAINS_STATUS_GAINS & s->gains) &&
2646 3 : s->gains_split &&
2647 3 : ((s->gains_split->gains & GAINS_STATUS_DATE_DIRTY) ||
2648 2 : (s->gains & GAINS_STATUS_DATE_DIRTY)))
2649 : {
2650 2 : Transaction *source_trans = s->gains_split->parent;
2651 2 : s->gains &= ~GAINS_STATUS_DATE_DIRTY;
2652 2 : s->gains_split->gains &= ~GAINS_STATUS_DATE_DIRTY;
2653 2 : xaccTransSetDatePostedSecs(trans, source_trans->date_posted);
2654 6 : FOR_EACH_SPLIT(trans, s->gains &= ~GAINS_STATUS_DATE_DIRTY);
2655 : }
2656 : }
2657 3 : g_list_free(splits_copy);
2658 3 : }
2659 :
2660 : /* ============================================================== */
2661 :
2662 : void
2663 0 : xaccTransScrubGains (Transaction *trans, Account *gain_acc)
2664 : {
2665 : SplitList *node;
2666 :
2667 0 : ENTER("(trans=%p)", trans);
2668 : /* Lock down posted date, its to be synced to the posted date
2669 : * for the source of the cap gains. */
2670 0 : xaccTransScrubGainsDate(trans);
2671 :
2672 : /* Fix up the split amount */
2673 0 : restart:
2674 0 : for (node = trans->splits; node; node = node->next)
2675 : {
2676 0 : Split *s = GNC_SPLIT(node->data);
2677 :
2678 0 : if (!xaccTransStillHasSplit(trans, s)) continue;
2679 :
2680 0 : xaccSplitDetermineGainStatus(s);
2681 0 : if (s->gains & GAINS_STATUS_ADIRTY)
2682 : {
2683 0 : gboolean altered = FALSE;
2684 0 : s->gains &= ~GAINS_STATUS_ADIRTY;
2685 0 : if (s->lot)
2686 0 : altered = xaccScrubLot(s->lot);
2687 : else
2688 0 : altered = xaccSplitAssign(s);
2689 0 : if (altered) goto restart;
2690 : }
2691 : }
2692 :
2693 : /* Fix up gains split value */
2694 0 : FOR_EACH_SPLIT(trans,
2695 : if ((s->gains & GAINS_STATUS_VDIRTY) ||
2696 : (s->gains_split &&
2697 : (s->gains_split->gains & GAINS_STATUS_VDIRTY)))
2698 : xaccSplitComputeCapGains(s, gain_acc);
2699 : );
2700 :
2701 0 : LEAVE("(trans=%p)", trans);
2702 0 : }
2703 :
2704 : Split *
2705 79 : xaccTransFindSplitByAccount(const Transaction *trans, const Account *acc)
2706 : {
2707 79 : if (!trans || !acc) return nullptr;
2708 212 : FOR_EACH_SPLIT(trans, if (xaccSplitGetAccount(s) == acc) return s);
2709 56 : return nullptr;
2710 : }
2711 :
2712 : static void
2713 0 : record_price (Split *split,
2714 : PriceSource source)
2715 : {
2716 : Transaction *trans;
2717 : Account *account;
2718 : QofBook* book;
2719 : GNCPriceDB* pricedb;
2720 : gnc_commodity* comm;
2721 : gnc_commodity* curr;
2722 : GNCPrice* price;
2723 : gnc_numeric price_value, value, amount;
2724 : int scu;
2725 : time64 time;
2726 : gboolean swap;
2727 :
2728 0 : account = xaccSplitGetAccount (split);
2729 0 : if (!xaccAccountIsPriced (account))
2730 : {
2731 0 : return;
2732 : }
2733 0 : amount = xaccSplitGetAmount (split);
2734 0 : if (gnc_numeric_zero_p (amount))
2735 : {
2736 0 : return;
2737 : }
2738 0 : trans = xaccSplitGetParent (split);
2739 0 : value = gnc_numeric_div (xaccSplitGetValue (split), amount,
2740 : GNC_DENOM_AUTO,
2741 : GNC_HOW_DENOM_EXACT);
2742 0 : book = qof_instance_get_book (QOF_INSTANCE (account));
2743 0 : pricedb = gnc_pricedb_get_db (book);
2744 0 : comm = xaccAccountGetCommodity (account);
2745 0 : curr = xaccTransGetCurrency (trans);
2746 0 : scu = gnc_commodity_get_fraction (curr);
2747 0 : swap = FALSE;
2748 0 : time = xaccTransGetDate (trans);
2749 0 : price = gnc_pricedb_lookup_day_t64 (pricedb, comm, curr, time);
2750 0 : if (gnc_commodity_equiv (comm, gnc_price_get_currency (price)))
2751 0 : swap = TRUE;
2752 :
2753 0 : if (price)
2754 : {
2755 0 : PriceSource oldsource = gnc_price_get_source (price);
2756 0 : price_value = gnc_price_get_value (price);
2757 0 : if (gnc_numeric_equal (swap ? gnc_numeric_invert (value) : value,
2758 : price_value))
2759 : {
2760 0 : gnc_price_unref (price);
2761 0 : return;
2762 : }
2763 0 : if (oldsource < source &&
2764 0 : !(oldsource == PRICE_SOURCE_XFER_DLG_VAL &&
2765 : source == PRICE_SOURCE_SPLIT_REG))
2766 : {
2767 : /* Existing price is preferred over this one. */
2768 0 : gnc_price_unref (price);
2769 0 : return;
2770 : }
2771 0 : if (swap)
2772 : {
2773 0 : value = gnc_numeric_invert (value);
2774 0 : scu = gnc_commodity_get_fraction (comm);
2775 : }
2776 0 : value = gnc_numeric_convert (value, scu * COMMODITY_DENOM_MULT,
2777 : GNC_HOW_RND_ROUND_HALF_UP);
2778 0 : gnc_price_begin_edit (price);
2779 0 : gnc_price_set_time64 (price, time);
2780 0 : gnc_price_set_source (price, source);
2781 0 : gnc_price_set_typestr (price, PRICE_TYPE_TRN);
2782 0 : gnc_price_set_value (price, value);
2783 0 : gnc_price_commit_edit (price);
2784 0 : gnc_price_unref (price);
2785 0 : return;
2786 : }
2787 :
2788 0 : value = gnc_numeric_convert (value, scu * COMMODITY_DENOM_MULT,
2789 : GNC_HOW_RND_ROUND_HALF_UP);
2790 0 : price = gnc_price_create (book);
2791 0 : gnc_price_begin_edit (price);
2792 0 : gnc_price_set_commodity (price, comm);
2793 0 : gnc_price_set_currency (price, curr);
2794 0 : gnc_price_set_time64 (price, time);
2795 0 : gnc_price_set_source (price, source);
2796 0 : gnc_price_set_typestr (price, PRICE_TYPE_TRN);
2797 0 : gnc_price_set_value (price, value);
2798 0 : gnc_pricedb_add_price (pricedb, price);
2799 0 : gnc_price_commit_edit (price);
2800 : }
2801 :
2802 : void
2803 0 : xaccTransRecordPrice (Transaction *trans, PriceSource source)
2804 : {
2805 : /* XXX: This should have been part of xaccSplitCommitEdit. */
2806 0 : g_list_foreach (xaccTransGetSplitList (trans), (GFunc)record_price, (gpointer)source);
2807 0 : }
2808 :
2809 : /********************************************************************\
2810 : \********************************************************************/
2811 : /* QofObject function implementation */
2812 :
2813 : static void
2814 2284 : destroy_tx_on_book_close(QofInstance *ent, gpointer data)
2815 : {
2816 2284 : Transaction* tx = GNC_TRANSACTION(ent);
2817 :
2818 2284 : xaccTransDestroy(tx);
2819 2284 : }
2820 :
2821 : static int
2822 10509 : trans_reverse_order (const Transaction* a, const Transaction* b)
2823 : {
2824 10509 : return xaccTransOrder (b, a);
2825 : }
2826 :
2827 : /** Handles book end - frees all transactions from the book
2828 : *
2829 : * @param book Book being closed
2830 : */
2831 : static void
2832 179 : gnc_transaction_book_end(QofBook* book)
2833 : {
2834 : QofCollection *col;
2835 :
2836 179 : col = qof_book_get_collection(book, GNC_ID_TRANS);
2837 :
2838 : // destroy all transactions from latest to earliest, because
2839 : // accounts' splits are stored chronologically and removing from
2840 : // the end is faster than from the middle.
2841 179 : qof_collection_foreach_sorted (col, destroy_tx_on_book_close, nullptr,
2842 : (GCompareFunc)trans_reverse_order);
2843 179 : }
2844 :
2845 : #ifdef _MSC_VER
2846 : /* MSVC compiler doesn't have C99 "designated initializers"
2847 : * so we wrap them in a macro that is empty on MSVC. */
2848 : # define DI(x) /* */
2849 : #else
2850 : # define DI(x) x
2851 : #endif
2852 :
2853 : /* Hook into the QofObject registry */
2854 : static QofObject trans_object_def =
2855 : {
2856 : DI(.interface_version = ) QOF_OBJECT_VERSION,
2857 : DI(.e_type = ) GNC_ID_TRANS,
2858 : DI(.type_label = ) "Transaction",
2859 : DI(.create = ) (void* (*)(QofBook*))xaccMallocTransaction,
2860 : DI(.book_begin = ) nullptr,
2861 : DI(.book_end = ) gnc_transaction_book_end,
2862 : DI(.is_dirty = ) qof_collection_is_dirty,
2863 : DI(.mark_clean = ) qof_collection_mark_clean,
2864 : DI(.foreach = ) qof_collection_foreach,
2865 : DI(.printable = ) (const char * (*)(gpointer)) xaccTransGetDescription,
2866 : DI(.version_cmp = ) (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
2867 : };
2868 :
2869 : static gboolean
2870 0 : trans_is_balanced_p (const Transaction *trans)
2871 : {
2872 0 : return trans ? xaccTransIsBalanced(trans) : FALSE;
2873 : }
2874 :
2875 83 : gboolean xaccTransRegister (void)
2876 : {
2877 : static QofParam params[] =
2878 : {
2879 : {
2880 : TRANS_NUM, QOF_TYPE_STRING,
2881 : (QofAccessFunc)xaccTransGetNum,
2882 : (QofSetterFunc)qofTransSetNum,
2883 : qof_string_number_compare_func
2884 : },
2885 : {
2886 : TRANS_DESCRIPTION, QOF_TYPE_STRING,
2887 : (QofAccessFunc)xaccTransGetDescription,
2888 : (QofSetterFunc)qofTransSetDescription
2889 : },
2890 : {
2891 : TRANS_DATE_ENTERED, QOF_TYPE_DATE,
2892 : (QofAccessFunc)xaccTransRetDateEntered,
2893 : (QofSetterFunc)xaccTransSetDateEnteredSecs
2894 : },
2895 : {
2896 : TRANS_DATE_POSTED, QOF_TYPE_DATE,
2897 : (QofAccessFunc)xaccTransRetDatePosted,
2898 : (QofSetterFunc)xaccTransSetDatePostedSecs
2899 : },
2900 : {
2901 : TRANS_DATE_DUE, QOF_TYPE_DATE,
2902 : (QofAccessFunc)xaccTransRetDateDue, nullptr
2903 : },
2904 : {
2905 : TRANS_IMBALANCE, QOF_TYPE_NUMERIC,
2906 : (QofAccessFunc)xaccTransGetImbalanceValue, nullptr
2907 : },
2908 : {
2909 : TRANS_NOTES, QOF_TYPE_STRING,
2910 : (QofAccessFunc)xaccTransGetNotes,
2911 : (QofSetterFunc)qofTransSetNotes
2912 : },
2913 : {
2914 : TRANS_DOCLINK, QOF_TYPE_STRING,
2915 : (QofAccessFunc)xaccTransGetDocLink,
2916 : (QofSetterFunc)xaccTransSetDocLink
2917 : },
2918 : {
2919 : TRANS_IS_CLOSING, QOF_TYPE_BOOLEAN,
2920 : (QofAccessFunc)xaccTransGetIsClosingTxn, nullptr
2921 : },
2922 : {
2923 : TRANS_IS_BALANCED, QOF_TYPE_BOOLEAN,
2924 : (QofAccessFunc)trans_is_balanced_p, nullptr
2925 : },
2926 : {
2927 : TRANS_TYPE, QOF_TYPE_CHAR,
2928 : (QofAccessFunc)xaccTransGetTxnType,
2929 : (QofSetterFunc)xaccTransSetTxnType
2930 : },
2931 : {
2932 : TRANS_VOID_STATUS, QOF_TYPE_BOOLEAN,
2933 : (QofAccessFunc)xaccTransGetVoidStatus, nullptr
2934 : },
2935 : {
2936 : TRANS_VOID_REASON, QOF_TYPE_STRING,
2937 : (QofAccessFunc)xaccTransGetVoidReason, nullptr
2938 : },
2939 : {
2940 : TRANS_VOID_TIME, QOF_TYPE_DATE,
2941 : (QofAccessFunc)xaccTransGetVoidTime, nullptr
2942 : },
2943 : {
2944 : TRANS_SPLITLIST, GNC_ID_SPLIT,
2945 : (QofAccessFunc)xaccTransGetSplitList, nullptr
2946 : },
2947 : {
2948 : QOF_PARAM_BOOK, QOF_ID_BOOK,
2949 : (QofAccessFunc)qof_instance_get_book, nullptr
2950 : },
2951 : {
2952 : QOF_PARAM_GUID, QOF_TYPE_GUID,
2953 : (QofAccessFunc)qof_entity_get_guid, nullptr
2954 : },
2955 : { nullptr },
2956 : };
2957 :
2958 83 : qof_class_register (GNC_ID_TRANS, (QofSortFunc)xaccTransOrder, params);
2959 :
2960 83 : return qof_object_register (&trans_object_def);
2961 : }
2962 :
2963 : TransTestFunctions*
2964 43 : _utest_trans_fill_functions (void)
2965 : {
2966 43 : TransTestFunctions *func = g_new (TransTestFunctions, 1);
2967 :
2968 43 : func->mark_trans = mark_trans;
2969 43 : func->gen_event_trans = gen_event_trans;
2970 43 : func->xaccFreeTransaction = xaccFreeTransaction;
2971 43 : func->destroy_gains = destroy_gains;
2972 43 : func->do_destroy = do_destroy;
2973 43 : func->was_trans_emptied = was_trans_emptied;
2974 43 : func->trans_on_error = trans_on_error;
2975 43 : func->trans_cleanup_commit = trans_cleanup_commit;
2976 43 : func->xaccTransScrubGainsDate = xaccTransScrubGainsDate;
2977 43 : func->dupe_trans = dupe_trans;
2978 43 : return func;
2979 : }
2980 :
2981 : /************************ END OF ************************************\
2982 : \************************* FILE *************************************/
|