gboolean gncOwnerGetOwnerFromLot (GNCLot *lot, GncOwner *owner) { GncGUID *guid = NULL; QofBook *book; GncOwnerType type = GNC_OWNER_NONE; if (!lot || !owner) return FALSE; book = gnc_lot_get_book (lot); qof_instance_get (QOF_INSTANCE (lot), "owner-type", &type, "owner-guid", &guid, NULL); switch (type) { case GNC_OWNER_CUSTOMER: gncOwnerInitCustomer (owner, gncCustomerLookup (book, guid)); break; case GNC_OWNER_VENDOR: gncOwnerInitVendor (owner, gncVendorLookup (book, guid)); break; case GNC_OWNER_EMPLOYEE: gncOwnerInitEmployee (owner, gncEmployeeLookup (book, guid)); break; case GNC_OWNER_JOB: gncOwnerInitJob (owner, gncJobLookup (book, guid)); break; default: return FALSE; } return (owner->owner.undefined != NULL); }
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); }