void xaccTransScrubPostedDate (Transaction *trans) { time64 orig = xaccTransGetDate(trans); GDate date = xaccTransGetDatePostedGDate(trans); Timespec ts = gdate_to_timespec(date); if (orig && orig != ts.tv_sec) { /* xaccTransSetDatePostedTS handles committing the change. */ xaccTransSetDatePostedTS(trans, &ts); } }
void gncOwnerAutoApplyPaymentsWithLots (const GncOwner *owner, GList *lots) { GList *base_iter; /* General note: in the code below the term "payment" can * both mean a true payment or a document of * the opposite sign (invoice vs credit note) relative to * the lot being processed. In general this function will * perform a balancing action on a set of lots, so you * will also find frequent references to balancing instead. */ /* Payments can only be applied when at least an owner * and a list of lots to use are given */ if (!owner) return; if (!lots) return; for (base_iter = lots; base_iter; base_iter = base_iter->next) { GNCLot *base_lot = base_iter->data; QofBook *book; Account *acct; const gchar *name; GList *lot_list, *lot_iter; Transaction *txn = NULL; gnc_numeric base_lot_bal, val_to_pay, val_paid = { 0, 1 }; gboolean base_bal_is_pos; const gchar *action, *memo; /* Only attempt to apply payments to open lots. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ base_lot_bal = gnc_lot_get_balance (base_lot); if (gnc_numeric_zero_p (base_lot_bal)) continue; book = gnc_lot_get_book (base_lot); acct = gnc_lot_get_account (base_lot); name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); lot_list = base_iter->next; /* Strings used when creating splits later on. */ action = _("Lot Link"); memo = _("Internal link between invoice and payment lots"); /* Note: to balance the lot the payment to assign * must have the opposite sign of the existing lot balance */ val_to_pay = gnc_numeric_neg (base_lot_bal); base_bal_is_pos = gnc_numeric_positive_p (base_lot_bal); /* Create splits in a linking transaction between lots until * - either the invoice lot is balanced * - or there are no more balancing lots. */ for (lot_iter = lot_list; lot_iter; lot_iter = lot_iter->next) { gnc_numeric payment_lot_balance; Split *split; Account *bal_acct; gnc_numeric split_amt; GNCLot *balancing_lot = lot_iter->data; /* Only attempt to use open lots to balance the base lot. * Note that due to the iterative nature of this function lots * in the list may become closed before they are evaluated as * base lot, so we should check this for each lot. */ if (gnc_lot_is_closed (balancing_lot)) continue; /* Balancing transactions for invoice/payments can only happen * in the same account. */ bal_acct = gnc_lot_get_account (balancing_lot); if (acct != bal_acct) continue; payment_lot_balance = gnc_lot_get_balance (balancing_lot); /* Only attempt to balance if the base lot and balancing lot are * of the opposite sign. (Otherwise we would increase the balance * of the lot - Duh */ if (base_bal_is_pos == gnc_numeric_positive_p (payment_lot_balance)) continue; /* * If there is less to pay than there's open in the lot; we're done -- apply the base_lot_vale. * Note that payment_value and balance are opposite in sign, so we have to compare absolute values here * * Otherwise, apply the balance, subtract that from the payment_value, * and move on to the next one. */ if (gnc_numeric_compare (gnc_numeric_abs (val_to_pay), gnc_numeric_abs (payment_lot_balance)) <= 0) { /* abs(val_to_pay) <= abs(balance) */ split_amt = val_to_pay; } else { /* abs(val_to_pay) > abs(balance) * Remember payment_value and balance are opposite in sign, * and we want a payment to neutralize the current balance * so we need to negate here */ split_amt = payment_lot_balance; } /* If not created yet, create a new transaction linking * the base lot and the balancing lot(s) */ if (!txn) { Timespec ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (base_lot))); xaccAccountBeginEdit (acct); txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); xaccTransSetDescription (txn, name ? name : ""); xaccTransSetCurrency (txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &ts); xaccTransSetTxnType (txn, TXN_TYPE_LINK); } /* Create the split for this link in current balancing lot */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (split_amt), xaccAccountGetCommodity(acct)); gnc_lot_add_split (balancing_lot, split); /* If the balancing lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ { GncInvoice *this_invoice = gncInvoiceGetInvoiceFromLot(balancing_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } val_paid = gnc_numeric_add (val_paid, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); val_to_pay = gnc_numeric_sub (val_to_pay, split_amt, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); if (gnc_numeric_zero_p (val_to_pay)) break; } /* If the above loop managed to create a transaction and some balancing splits, * create the final split for the link transaction in the base lot */ if (txn) { GncInvoice *this_invoice; Split *split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set Action with utiltity function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, val_paid, xaccAccountGetCommodity(acct)); gnc_lot_add_split (base_lot, split); xaccTransCommitEdit (txn); xaccAccountCommitEdit (acct); /* If the base lot was linked to a document (invoice/credit note), * send an event for it as well so it gets potentially updated as paid */ this_invoice = gncInvoiceGetInvoiceFromLot(base_lot); if (this_invoice) qof_event_gen (QOF_INSTANCE(this_invoice), QOF_EVENT_MODIFY, NULL); } } }
static void gncOwnerCreateLotLink (GNCLot *from_lot, GNCLot *to_lot, const GncOwner *owner) { const gchar *action = _("Lot Link"); Account *acct = gnc_lot_get_account (from_lot); const gchar *name = gncOwnerGetName (gncOwnerGetEndOwner (owner)); Transaction *ll_txn = NULL; gnc_numeric from_lot_bal, to_lot_bal; Timespec from_ts, to_ts; time64 time_posted; Split *split; /* Sanity check */ if (!gncInvoiceGetInvoiceFromLot (from_lot) || !gncInvoiceGetInvoiceFromLot (to_lot)) return; /* Determine transaction date based on lot splits */ from_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (from_lot))); to_ts = xaccTransRetDatePostedTS (xaccSplitGetParent (gnc_lot_get_latest_split (to_lot))); if (timespecToTime64 (from_ts) >= timespecToTime64 (to_ts)) time_posted = timespecToTime64 (from_ts); else time_posted = timespecToTime64 (to_ts); /* Figure out how much we can offset between the lots */ from_lot_bal = gnc_lot_get_balance (from_lot); to_lot_bal = gnc_lot_get_balance (to_lot); if (gnc_numeric_compare (gnc_numeric_abs (from_lot_bal), gnc_numeric_abs (to_lot_bal)) > 0) from_lot_bal = gnc_numeric_neg (to_lot_bal); else to_lot_bal = gnc_numeric_neg (from_lot_bal); xaccAccountBeginEdit (acct); /* Look for a pre-existing lot link we can extend */ ll_txn = get_ll_transaction_from_lot (from_lot); if (!ll_txn) ll_txn = get_ll_transaction_from_lot (to_lot); if (!ll_txn) { /* No pre-existing lot link. Create one. */ Timespec ts; timespecFromTime64 (&ts, time_posted); ll_txn = xaccMallocTransaction (gnc_lot_get_book (from_lot)); xaccTransBeginEdit (ll_txn); xaccTransSetDescription (ll_txn, name ? name : "(Unknown)"); xaccTransSetCurrency (ll_txn, xaccAccountGetCommodity(acct)); xaccTransSetDateEnteredSecs (ll_txn, gnc_time (NULL)); xaccTransSetDatePostedTS (ll_txn, &ts); xaccTransSetTxnType (ll_txn, TXN_TYPE_LINK); } else { Timespec ts = xaccTransRetDatePostedTS (ll_txn); xaccTransBeginEdit (ll_txn); /* Maybe we need to update the post date of the transaction ? */ if (time_posted > timespecToTime64 (ts)) { timespecFromTime64 (&ts, time_posted); xaccTransSetDatePostedTS (ll_txn, &ts); } } /* Create a split for the from_lot */ split = xaccMallocSplit (gnc_lot_get_book (from_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (from_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (from_lot, split); /* Create a split for the to_lot */ split = xaccMallocSplit (gnc_lot_get_book (to_lot)); /* set Action using utility function */ gnc_set_num_action (NULL, split, NULL, action); xaccAccountInsertSplit (acct, split); xaccTransAppendSplit (ll_txn, split); /* To offset the lot balance, the split must be of the opposite sign */ xaccSplitSetBaseValue (split, gnc_numeric_neg (to_lot_bal), xaccAccountGetCommodity(acct)); gnc_lot_add_split (to_lot, split); xaccTransCommitEdit (ll_txn); /* Do some post-cleaning on the lots * The above actions may have created splits that are * in the same transaction and lot. These can be merged. */ xaccScrubMergeLotSubSplits (to_lot, FALSE); xaccScrubMergeLotSubSplits (from_lot, FALSE); /* And finally set the same memo for all remaining splits * It's a convenience for the users to identify all documents * involved in the link. */ gncOwnerSetLotLinkMemo (ll_txn); xaccAccountCommitEdit (acct); }
GNCLot * gncOwnerCreatePaymentLot (const GncOwner *owner, Transaction *txn, Account *posted_acc, Account *xfer_acc, gnc_numeric amount, gnc_numeric exch, Timespec date, const char *memo, const char *num) { QofBook *book; Split *split; const char *name; gnc_commodity *commodity; Split *xfer_split = NULL; GNCLot *payment_lot; /* Verify our arguments */ if (!owner || !posted_acc || !xfer_acc) return NULL; g_return_val_if_fail (owner->owner.undefined != NULL, NULL); /* Compute the ancillary data */ book = gnc_account_get_book (posted_acc); name = gncOwnerGetName (gncOwnerGetEndOwner ((GncOwner*)owner)); commodity = gncOwnerGetCurrency (owner); // reverse = use_reversed_payment_amounts(owner); if (txn) { /* Pre-existing transaction was specified. We completely clear it, * except for the split in the transfer account, unless the * transaction can't be reused (wrong currency, wrong transfer account). * In that case, the transaction is simply removed and an new * one created. */ xfer_split = xaccTransFindSplitByAccount(txn, xfer_acc); if (xaccTransGetCurrency(txn) != gncOwnerGetCurrency (owner)) { g_message("Uh oh, mismatching currency/commodity between selected transaction and owner. We fall back to manual creation of a new transaction."); xfer_split = NULL; } if (!xfer_split) { g_message("Huh? Asset account not found anymore. Fully deleting old txn and now creating a new one."); xaccTransBeginEdit (txn); xaccTransDestroy (txn); xaccTransCommitEdit (txn); txn = NULL; } else { int i = 0; xaccTransBeginEdit (txn); while (i < xaccTransCountSplits(txn)) { Split *split = xaccTransGetSplit (txn, i); if (split == xfer_split) { gnc_set_num_action (NULL, split, num, _("Payment")); ++i; } else { xaccSplitDestroy(split); } } /* Note: don't commit transaction now - that would insert an imbalance split.*/ } } /* Create the transaction if we don't have one yet */ if (!txn) { txn = xaccMallocTransaction (book); xaccTransBeginEdit (txn); } /* Insert a split for the transfer account if we don't have one yet */ if (!xfer_split) { /* Set up the transaction */ xaccTransSetDescription (txn, name ? name : ""); /* set per book option */ xaccTransSetCurrency (txn, commodity); xaccTransSetDateEnteredSecs (txn, gnc_time (NULL)); xaccTransSetDatePostedTS (txn, &date); /* The split for the transfer account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (xfer_acc); xaccAccountInsertSplit (xfer_acc, split); xaccAccountCommitEdit (xfer_acc); xaccTransAppendSplit (txn, split); if (gnc_commodity_equal(xaccAccountGetCommodity(xfer_acc), commodity)) { xaccSplitSetBaseValue (split, amount, commodity); } else { /* Need to value the payment in terms of the owner commodity */ gnc_numeric payment_value = gnc_numeric_mul(amount, exch, GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP); xaccSplitSetAmount(split, amount); xaccSplitSetValue(split, payment_value); } } /* Add a split in the post account */ split = xaccMallocSplit (book); xaccSplitSetMemo (split, memo); /* set per book option */ gnc_set_num_action (NULL, split, num, _("Payment")); xaccAccountBeginEdit (posted_acc); xaccAccountInsertSplit (posted_acc, split); xaccAccountCommitEdit (posted_acc); xaccTransAppendSplit (txn, split); xaccSplitSetBaseValue (split, gnc_numeric_neg (amount), commodity); /* Create a new lot for the payment */ payment_lot = gnc_lot_new (book); gncOwnerAttachToLot (owner, payment_lot); gnc_lot_add_split (payment_lot, split); /* Mark the transaction as a payment */ gnc_set_num_action (txn, NULL, num, _("Payment")); xaccTransSetTxnType (txn, TXN_TYPE_PAYMENT); /* Commit this new transaction */ xaccTransCommitEdit (txn); return payment_lot; }
/* File pointer must already be at the begining of a record */ static void process_trans_record( FILE *log_file) { char read_buf[2048]; char *read_retval; char * trans_ro = NULL; const char * record_end_str = "===== END"; int first_record = TRUE; int record_ended = FALSE; int split_num = 0; split_record record; Transaction * trans = NULL; Split * split = NULL; Account * acct = NULL; QofBook * book = gnc_get_current_book(); DEBUG("process_trans_record(): Begin...\n"); while ( record_ended == FALSE) { read_retval = fgets(read_buf, sizeof(read_buf), log_file); if (read_retval != NULL && strncmp(record_end_str, read_buf, strlen(record_end_str)) != 0) /* If we are not at the end of the record */ { split_num++; /*DEBUG("process_trans_record(): Line read: %s%s",read_buf ,"\n");*/ record = interpret_split_record( read_buf); dump_split_record( record); if (record.log_action_present) { switch (record.log_action) { case split_record::LOG_BEGIN_EDIT: DEBUG("process_trans_record():Ignoring log action: LOG_BEGIN_EDIT"); /*Do nothing, there is no point*/ break; case split_record::LOG_ROLLBACK: DEBUG("process_trans_record():Ignoring log action: LOG_ROLLBACK");/*Do nothing, since we didn't do the begin_edit either*/ break; case split_record::LOG_DELETE: DEBUG("process_trans_record(): Playing back LOG_DELETE"); if ((trans = xaccTransLookup (&(record.trans_guid), book)) != NULL && first_record == TRUE) { first_record = FALSE; if (xaccTransGetReadOnly(trans)) { PWARN("Destroying a read only transaction."); xaccTransClearReadOnly(trans); } xaccTransBeginEdit(trans); xaccTransDestroy(trans); } else if (first_record == TRUE) { PERR("The transaction to delete was not found!"); } else xaccTransDestroy(trans); break; case split_record::LOG_COMMIT: DEBUG("process_trans_record(): Playing back LOG_COMMIT"); if (record.trans_guid_present == TRUE && first_record == TRUE) { trans = xaccTransLookupDirect (record.trans_guid, book); if (trans != NULL) { DEBUG("process_trans_record(): Transaction to be edited was found"); xaccTransBeginEdit(trans); trans_ro = g_strdup(xaccTransGetReadOnly(trans)); if (trans_ro) { PWARN("Replaying a read only transaction."); xaccTransClearReadOnly(trans); } } else { DEBUG("process_trans_record(): Creating a new transaction"); trans = xaccMallocTransaction (book); xaccTransBeginEdit(trans); } xaccTransSetGUID (trans, &(record.trans_guid)); /*Fill the transaction info*/ if (record.date_entered_present) { xaccTransSetDateEnteredTS(trans, &(record.date_entered)); } if (record.date_posted_present) { xaccTransSetDatePostedTS(trans, &(record.date_posted)); } if (record.trans_num_present) { xaccTransSetNum(trans, record.trans_num); } if (record.trans_descr_present) { xaccTransSetDescription(trans, record.trans_descr); } if (record.trans_notes_present) { xaccTransSetNotes(trans, record.trans_notes); } } if (record.split_guid_present == TRUE) /*Fill the split info*/ { gboolean is_new_split; split = xaccSplitLookupDirect (record.split_guid, book); if (split != NULL) { DEBUG("process_trans_record(): Split to be edited was found"); is_new_split = FALSE; } else { DEBUG("process_trans_record(): Creating a new split"); split = xaccMallocSplit(book); is_new_split = TRUE; } xaccSplitSetGUID (split, &(record.split_guid)); if (record.acc_guid_present) { acct = xaccAccountLookupDirect(record.acc_guid, book); xaccAccountInsertSplit(acct, split); } if (is_new_split) xaccTransAppendSplit(trans, split); if (record.split_memo_present) { xaccSplitSetMemo(split, record.split_memo); } if (record.split_action_present) { xaccSplitSetAction(split, record.split_action); } if (record.date_reconciled_present) { xaccSplitSetDateReconciledTS (split, &(record.date_reconciled)); } if (record.split_reconcile_present) { xaccSplitSetReconcile(split, record.split_reconcile); } if (record.amount_present) { xaccSplitSetAmount(split, record.amount); } if (record.value_present) { xaccSplitSetValue(split, record.value); } } first_record = FALSE; break; } } else { PERR("Corrupted record"); } } else /* The record ended */ { record_ended = TRUE; DEBUG("process_trans_record(): Record ended\n"); if (trans != NULL) /*If we played with a transaction, commit it here*/ { xaccTransScrubCurrencyFromSplits(trans); xaccTransSetReadOnly(trans, trans_ro); xaccTransCommitEdit(trans); g_free(trans_ro); } } } }