/* * gncEntry.c -- the Core Business Entry Interface * Copyright (C) 2001,2002 Derek Atkins * Author: Derek Atkins */ #include "config.h" #include #include "messages.h" #include "gnc-book-p.h" #include "gnc-commodity.h" #include "gnc-engine-util.h" #include "gnc-event-p.h" #include "gnc-numeric.h" #include "GNCIdP.h" #include "QueryObject.h" #include "gnc-be-utils.h" #include "gncBusiness.h" #include "gncEntry.h" #include "gncEntryP.h" #include "gncInvoice.h" #include "gncOrder.h" struct _gncEntry { GNCBook * book; GUID guid; Timespec date; Timespec date_entered; char * desc; char * action; char * notes; gnc_numeric quantity; /* customer invoice data */ Account * i_account; gnc_numeric i_price; gboolean i_taxable; gboolean i_taxincluded; GncTaxTable * i_tax_table; gnc_numeric i_discount; GncAmountType i_disc_type; GncDiscountHow i_disc_how; /* vendor bill data */ Account * b_account; gnc_numeric b_price; gboolean b_taxable; gboolean b_taxincluded; GncTaxTable * b_tax_table; gboolean billable; GncOwner billto; /* employee bill data */ GncEntryPaymentType b_payment; /* my parent(s) */ GncOrder * order; GncInvoice * invoice; GncInvoice * bill; int editlevel; gboolean do_free; gboolean dirty; /* CACHED VALUES */ gboolean values_dirty; /* customer invoice */ gnc_numeric i_value; gnc_numeric i_value_rounded; GList * i_tax_values; gnc_numeric i_tax_value; gnc_numeric i_tax_value_rounded; gnc_numeric i_disc_value; gnc_numeric i_disc_value_rounded; Timespec i_taxtable_modtime; /* vendor bill */ gnc_numeric b_value; gnc_numeric b_value_rounded; GList * b_tax_values; gnc_numeric b_tax_value; gnc_numeric b_tax_value_rounded; Timespec b_taxtable_modtime; }; static short module = MOD_BUSINESS; /* You must edit the functions in this block in tandem. KEEP THEM IN SYNC! */ #define GNC_RETURN_ENUM_AS_STRING(x,s) case (x): return (s); const char * gncEntryDiscountHowToString (GncDiscountHow how) { switch(how) { GNC_RETURN_ENUM_AS_STRING(GNC_DISC_PRETAX, "PRETAX"); GNC_RETURN_ENUM_AS_STRING(GNC_DISC_SAMETIME, "SAMETIME"); GNC_RETURN_ENUM_AS_STRING(GNC_DISC_POSTTAX, "POSTTAX"); default: g_warning ("asked to translate unknown discount-how %d.\n", how); break; } return(NULL); } const char * gncEntryPaymentTypeToString (GncEntryPaymentType type) { switch(type) { GNC_RETURN_ENUM_AS_STRING(GNC_PAYMENT_CASH, "CASH"); GNC_RETURN_ENUM_AS_STRING(GNC_PAYMENT_CARD, "CARD"); default: g_warning ("asked to translate unknown payment type %d.\n", type); break; } return(NULL); } #undef GNC_RETURN_ENUM_AS_STRING #define GNC_RETURN_ON_MATCH(s,x,r) \ if(safe_strcmp((s), (str)) == 0) { *(r) = x; return(TRUE); } gboolean gncEntryDiscountStringToHow (const char *str, GncDiscountHow *how) { GNC_RETURN_ON_MATCH ("PRETAX", GNC_DISC_PRETAX, how); GNC_RETURN_ON_MATCH ("SAMETIME", GNC_DISC_SAMETIME, how); GNC_RETURN_ON_MATCH ("POSTTAX", GNC_DISC_POSTTAX, how); g_warning ("asked to translate unknown discount-how string %s.\n", str ? str : "(null)"); return(FALSE); } gboolean gncEntryPaymentStringToType (const char *str, GncEntryPaymentType *type) { GNC_RETURN_ON_MATCH ("CASH", GNC_PAYMENT_CASH, type); GNC_RETURN_ON_MATCH ("CARD", GNC_PAYMENT_CARD, type); g_warning ("asked to translate unknown discount-how string %s.\n", str ? str : "(null)"); return(FALSE); } #undef GNC_RETURN_ON_MATCH #define _GNC_MOD_NAME GNC_ENTRY_MODULE_NAME #define CACHE_INSERT(str) g_cache_insert(gnc_engine_get_string_cache(), (gpointer)(str)); #define CACHE_REMOVE(str) g_cache_remove(gnc_engine_get_string_cache(), (str)); #define SET_STR(obj, member, str) { \ char * tmp; \ \ if (!safe_strcmp (member, str)) return; \ gncEntryBeginEdit (obj); \ tmp = CACHE_INSERT (str); \ CACHE_REMOVE (member); \ member = tmp; \ } static void addObj (GncEntry *entry); static void remObj (GncEntry *entry); G_INLINE_FUNC void mark_entry (GncEntry *entry); G_INLINE_FUNC void mark_entry (GncEntry *entry) { entry->dirty = TRUE; gncBusinessSetDirtyFlag (entry->book, _GNC_MOD_NAME, TRUE); gnc_engine_generate_event (&entry->guid, GNC_EVENT_MODIFY); } /* Create/Destroy Functions */ GncEntry *gncEntryCreate (GNCBook *book) { GncEntry *entry; gnc_numeric zero = gnc_numeric_zero (); if (!book) return NULL; entry = g_new0 (GncEntry, 1); entry->book = book; entry->desc = CACHE_INSERT (""); entry->action = CACHE_INSERT (""); entry->notes = CACHE_INSERT (""); entry->quantity = zero; entry->i_price = zero; entry->i_taxable = TRUE; entry->i_discount = zero; entry->i_disc_type = GNC_AMT_TYPE_PERCENT; entry->i_disc_how = GNC_DISC_PRETAX; entry->b_price = zero; entry->b_taxable = TRUE; entry->billto.type = GNC_OWNER_CUSTOMER; entry->b_payment = GNC_PAYMENT_CASH; entry->values_dirty = TRUE; xaccGUIDNew (&entry->guid, book); addObj (entry); gnc_engine_generate_event (&entry->guid, GNC_EVENT_CREATE); return entry; } void gncEntryDestroy (GncEntry *entry) { if (!entry) return; entry->do_free = TRUE; gncEntryCommitEdit(entry); } static void gncEntryFree (GncEntry *entry) { if (!entry) return; gnc_engine_generate_event (&entry->guid, GNC_EVENT_DESTROY); CACHE_REMOVE (entry->desc); CACHE_REMOVE (entry->action); CACHE_REMOVE (entry->notes); if (entry->i_tax_values) gncAccountValueDestroy (entry->i_tax_values); if (entry->b_tax_values) gncAccountValueDestroy (entry->b_tax_values); remObj (entry); g_free (entry); } /* Set Functions */ void gncEntrySetGUID (GncEntry *entry, const GUID *guid) { if (!entry || !guid) return; if (guid_equal (guid, &entry->guid)) return; gncEntryBeginEdit (entry); remObj (entry); entry->guid = *guid; addObj (entry); gncEntryCommitEdit (entry); } void gncEntrySetDate (GncEntry *entry, Timespec date) { if (!entry) return; if (timespec_equal (&entry->date, &date)) return; gncEntryBeginEdit (entry); entry->date = date; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetDateEntered (GncEntry *entry, Timespec date) { if (!entry) return; if (timespec_equal (&entry->date_entered, &date)) return; gncEntryBeginEdit (entry); entry->date_entered = date; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetDescription (GncEntry *entry, const char *desc) { if (!entry || !desc) return; SET_STR (entry, entry->desc, desc); mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetAction (GncEntry *entry, const char *action) { if (!entry || !action) return; SET_STR (entry,entry->action, action); mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetNotes (GncEntry *entry, const char *notes) { if (!entry || !notes) return; SET_STR (entry, entry->notes, notes); mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetQuantity (GncEntry *entry, gnc_numeric quantity) { if (!entry) return; if (gnc_numeric_eq (entry->quantity, quantity)) return; gncEntryBeginEdit (entry); entry->quantity = quantity; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } /* Customer Invoices */ void gncEntrySetInvAccount (GncEntry *entry, Account *acc) { if (!entry) return; if (entry->i_account == acc) return; gncEntryBeginEdit (entry); entry->i_account = acc; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvPrice (GncEntry *entry, gnc_numeric price) { if (!entry) return; if (gnc_numeric_eq (entry->i_price, price)) return; gncEntryBeginEdit (entry); entry->i_price = price; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvTaxable (GncEntry *entry, gboolean taxable) { if (!entry) return; if (entry->i_taxable == taxable) return; gncEntryBeginEdit (entry); entry->i_taxable = taxable; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvTaxIncluded (GncEntry *entry, gboolean taxincluded) { if (!entry) return; if (entry->i_taxincluded == taxincluded) return; gncEntryBeginEdit (entry); entry->i_taxincluded = taxincluded; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvTaxTable (GncEntry *entry, GncTaxTable *table) { if (!entry) return; if (entry->i_tax_table == table) return; gncEntryBeginEdit (entry); if (entry->i_tax_table) gncTaxTableDecRef (entry->i_tax_table); if (table) gncTaxTableIncRef (table); entry->i_tax_table = table; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvDiscount (GncEntry *entry, gnc_numeric discount) { if (!entry) return; if (gnc_numeric_eq (entry->i_discount, discount)) return; gncEntryBeginEdit (entry); entry->i_discount = discount; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvDiscountType (GncEntry *entry, GncAmountType type) { if (!entry) return; if (entry->i_disc_type == type) return; gncEntryBeginEdit (entry); entry->i_disc_type = type; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetInvDiscountHow (GncEntry *entry, GncDiscountHow how) { if (!entry) return; if (entry->i_disc_how == how) return; gncEntryBeginEdit (entry); entry->i_disc_how = how; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } /* Vendor Bills */ void gncEntrySetBillAccount (GncEntry *entry, Account *acc) { if (!entry) return; if (entry->b_account == acc) return; gncEntryBeginEdit (entry); entry->b_account = acc; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillPrice (GncEntry *entry, gnc_numeric price) { if (!entry) return; if (gnc_numeric_eq (entry->b_price, price)) return; gncEntryBeginEdit (entry); entry->b_price = price; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillTaxable (GncEntry *entry, gboolean taxable) { if (!entry) return; if (entry->b_taxable == taxable) return; gncEntryBeginEdit (entry); entry->b_taxable = taxable; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillTaxIncluded (GncEntry *entry, gboolean taxincluded) { if (!entry) return; if (entry->b_taxincluded == taxincluded) return; gncEntryBeginEdit (entry); entry->b_taxincluded = taxincluded; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillTaxTable (GncEntry *entry, GncTaxTable *table) { if (!entry) return; if (entry->b_tax_table == table) return; gncEntryBeginEdit (entry); if (entry->b_tax_table) gncTaxTableDecRef (entry->b_tax_table); if (table) gncTaxTableIncRef (table); entry->b_tax_table = table; entry->values_dirty = TRUE; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillable (GncEntry *entry, gboolean billable) { if (!entry) return; if (entry->billable == billable) return; gncEntryBeginEdit (entry); entry->billable = billable; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillTo (GncEntry *entry, GncOwner *billto) { if (!entry || !billto) return; if (gncOwnerEqual (&entry->billto, billto)) return; gncEntryBeginEdit (entry); gncOwnerCopy (billto, &entry->billto); mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetBillPayment (GncEntry *entry, GncEntryPaymentType type) { if (!entry) return; if (entry->b_payment == type) return; gncEntryBeginEdit (entry); entry->b_payment = type; mark_entry (entry); gncEntryCommitEdit (entry); } /* Called from gncOrder when we're added to the Order */ void gncEntrySetOrder (GncEntry *entry, GncOrder *order) { if (!entry) return; if (entry->order == order) return; gncEntryBeginEdit (entry); entry->order = order; mark_entry (entry); gncEntryCommitEdit (entry); /* Generate an event modifying the Order's end-owner */ gnc_engine_generate_event (gncOwnerGetEndGUID (gncOrderGetOwner (order)), GNC_EVENT_MODIFY); } /* called from gncInvoice when we're added to the Invoice */ void gncEntrySetInvoice (GncEntry *entry, GncInvoice *invoice) { if (!entry) return; if (entry->invoice == invoice) return; gncEntryBeginEdit (entry); entry->invoice = invoice; mark_entry (entry); gncEntryCommitEdit (entry); } /* called from gncInvoice when we're added to the Invoice/Bill */ void gncEntrySetBill (GncEntry *entry, GncInvoice *bill) { if (!entry) return; if (entry->bill == bill) return; gncEntryBeginEdit (entry); entry->bill = bill; mark_entry (entry); gncEntryCommitEdit (entry); } void gncEntrySetDirty (GncEntry *entry, gboolean dirty) { if (!entry) return; entry->dirty = dirty; } void gncEntryCopy (const GncEntry *src, GncEntry *dest) { if (!src || !dest) return; gncEntryBeginEdit (dest); dest->date = src->date; dest->date_entered = src->date_entered; /* ??? */ gncEntrySetDescription (dest, src->desc); gncEntrySetAction (dest, src->action); gncEntrySetNotes (dest, src->notes); dest->quantity = src->quantity; dest->i_account = src->i_account; dest->i_price = src->i_price; dest->i_taxable = src->i_taxable; dest->i_taxincluded = src->i_taxincluded; dest->i_discount = src->i_discount; dest->i_disc_type = src->i_disc_type; dest->i_disc_how = src->i_disc_how; /* vendor bill data */ dest->b_account = src->b_account; dest->b_price = src->b_price; dest->b_taxable = src->b_taxable; dest->b_taxincluded = src->b_taxincluded; dest->billable = src->billable; dest->billto = src->billto; if (src->i_tax_table) gncEntrySetInvTaxTable (dest, src->i_tax_table); if (src->b_tax_table) gncEntrySetBillTaxTable (dest, src->b_tax_table); if (src->order) gncOrderAddEntry (src->order, dest); if (src->invoice) gncInvoiceAddEntry (src->invoice, dest); if (src->bill) gncBillAddEntry (src->bill, dest); dest->values_dirty = TRUE; gncEntryCommitEdit (dest); } /* Get Functions */ GNCBook * gncEntryGetBook (GncEntry *entry) { if (!entry) return NULL; return entry->book; } const GUID * gncEntryGetGUID (GncEntry *entry) { if (!entry) return NULL; return &(entry->guid); } Timespec gncEntryGetDate (GncEntry *entry) { Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; if (!entry) return ts; return entry->date; } Timespec gncEntryGetDateEntered (GncEntry *entry) { Timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; if (!entry) return ts; return entry->date_entered; } const char * gncEntryGetDescription (GncEntry *entry) { if (!entry) return NULL; return entry->desc; } const char * gncEntryGetAction (GncEntry *entry) { if (!entry) return NULL; return entry->action; } const char * gncEntryGetNotes (GncEntry *entry) { if (!entry) return NULL; return entry->notes; } gnc_numeric gncEntryGetQuantity (GncEntry *entry) { if (!entry) return gnc_numeric_zero(); return entry->quantity; } /* Customer Invoice */ Account * gncEntryGetInvAccount (GncEntry *entry) { if (!entry) return NULL; return entry->i_account; } gnc_numeric gncEntryGetInvPrice (GncEntry *entry) { if (!entry) return gnc_numeric_zero(); return entry->i_price; } gnc_numeric gncEntryGetInvDiscount (GncEntry *entry) { if (!entry) return gnc_numeric_zero(); return entry->i_discount; } GncAmountType gncEntryGetInvDiscountType (GncEntry *entry) { if (!entry) return 0; return entry->i_disc_type; } GncDiscountHow gncEntryGetInvDiscountHow (GncEntry *entry) { if (!entry) return 0; return entry->i_disc_how; } gboolean gncEntryGetInvTaxable (GncEntry *entry) { if (!entry) return FALSE; return entry->i_taxable; } gboolean gncEntryGetInvTaxIncluded (GncEntry *entry) { if (!entry) return FALSE; return entry->i_taxincluded; } GncTaxTable * gncEntryGetInvTaxTable (GncEntry *entry) { if (!entry) return NULL; return entry->i_tax_table; } /* vendor bills */ Account * gncEntryGetBillAccount (GncEntry *entry) { if (!entry) return NULL; return entry->b_account; } gnc_numeric gncEntryGetBillPrice (GncEntry *entry) { if (!entry) return gnc_numeric_zero(); return entry->b_price; } gboolean gncEntryGetBillTaxable (GncEntry *entry) { if (!entry) return FALSE; return entry->b_taxable; } gboolean gncEntryGetBillTaxIncluded (GncEntry *entry) { if (!entry) return FALSE; return entry->b_taxincluded; } GncTaxTable * gncEntryGetBillTaxTable (GncEntry *entry) { if (!entry) return NULL; return entry->b_tax_table; } gboolean gncEntryGetBillable (GncEntry *entry) { if (!entry) return FALSE; return entry->billable; } GncOwner * gncEntryGetBillTo (GncEntry *entry) { if (!entry) return NULL; return &entry->billto; } GncEntryPaymentType gncEntryGetBillPayment (GncEntry* entry) { if (!entry) return 0; return entry->b_payment; } GncInvoice * gncEntryGetInvoice (GncEntry *entry) { if (!entry) return NULL; return entry->invoice; } GncInvoice * gncEntryGetBill (GncEntry *entry) { if (!entry) return NULL; return entry->bill; } GncOrder * gncEntryGetOrder (GncEntry *entry) { if (!entry) return NULL; return entry->order; } GncEntry * gncEntryLookup (GNCBook *book, const GUID *guid) { if (!book || !guid) return NULL; return xaccLookupEntity (gnc_book_get_entity_table (book), guid, _GNC_MOD_NAME); } /* * This is the logic of computing the total for an Entry, so you know * what values to put into various Splits or to display in the ledger. * In other words, we combine the quantity, unit-price, discount and * taxes together, depending on various flags. * * There are four potental ways to combine these numbers: * Discount: Pre-Tax Post-Tax * Tax : Included Not-Included * * The process is relatively simple: * * 1) compute the agregate price (price*qty) * 2) if taxincluded, then back-compute the agregate pre-tax price * 3) apply discount and taxes in the appropriate order * 4) return the requested results. * * step 2 can be done with agregate taxes; no need to compute them all * unless the caller asked for the tax_value. * * Note that the returned "value" is such that value + tax == "total * to pay," which means in the case of tax-included that the returned * "value" may be less than the agregate price, even without a * discount. If you want to display the tax-included value, you need * to add the value and taxes together. In other words, the value is * the amount the merchant gets; the taxes are the amount the gov't * gets, and the customer pays the sum or value + taxes. * * The discount return value is just for entertainment -- you may way * to let a consumer know how much they saved. */ void gncEntryComputeValue (gnc_numeric qty, gnc_numeric price, GncTaxTable *tax_table, gboolean tax_included, gnc_numeric discount, GncAmountType discount_type, GncDiscountHow discount_how, gnc_numeric *value, gnc_numeric *discount_value, GList **tax_value) { gnc_numeric aggregate; gnc_numeric pretax; gnc_numeric result; gnc_numeric tax; gnc_numeric percent = gnc_numeric_create (100, 1); gnc_numeric tpercent = gnc_numeric_zero (); gnc_numeric tvalue = gnc_numeric_zero (); GList * entries = gncTaxTableGetEntries (tax_table); GList * node; /* Step 1: compute the aggregate price */ aggregate = gnc_numeric_mul (qty, price, GNC_DENOM_AUTO, GNC_DENOM_LCD); /* Step 2: compute the pre-tax aggregate */ /* First, compute the aggregate tpercent and tvalue numbers */ for (node = entries; node; node = node->next) { GncTaxTableEntry *entry = node->data; gnc_numeric amount = gncTaxTableEntryGetAmount (entry); switch (gncTaxTableEntryGetType (entry)) { case GNC_AMT_TYPE_VALUE: tvalue = gnc_numeric_add (tvalue, amount, GNC_DENOM_AUTO, GNC_DENOM_LCD); break; case GNC_AMT_TYPE_PERCENT: tpercent = gnc_numeric_add (tpercent, amount, GNC_DENOM_AUTO, GNC_DENOM_LCD); break; default: g_warning ("Unknown tax type: %d", gncTaxTableEntryGetType (entry)); } } /* now we need to convert from 5% -> .05 */ tpercent = gnc_numeric_div (tpercent, percent, GNC_DENOM_AUTO, GNC_DENOM_LCD); /* Next, actually compute the pre-tax aggregate value based on the * taxincluded flag. */ if (tax_table && tax_included) { /* Back-compute the pre-tax aggregate value. * We know that aggregate = pretax + pretax*tpercent + tvalue, so * pretax = (aggregate-tvalue)/(1+tpercent) */ pretax = gnc_numeric_sub (aggregate, tvalue, GNC_DENOM_AUTO, GNC_DENOM_LCD); pretax = gnc_numeric_div (pretax, gnc_numeric_add (tpercent, gnc_numeric_create (1, 1), GNC_DENOM_AUTO, GNC_DENOM_LCD), GNC_DENOM_AUTO, GNC_DENOM_LCD); } else { pretax = aggregate; } /* Step 3: apply discount and taxes in the appropriate order */ /* * There are two ways to apply discounts and taxes. In one way, you * always compute the discount off the pretax number, and compute * the taxes off of either the pretax value or "pretax-discount" * value. In the other way, you always compute the tax on "pretax", * and compute the discount on either "pretax" or "pretax+taxes". * * I don't know which is the "correct" way. */ /* * Type: discount tax * PRETAX pretax pretax-discount * SAMETIME pretax pretax * POSTTAX pretax+tax pretax */ switch (discount_how) { case GNC_DISC_PRETAX: case GNC_DISC_SAMETIME: /* compute the discount from pretax */ if (discount_type == GNC_AMT_TYPE_PERCENT) { discount = gnc_numeric_div (discount, percent, GNC_DENOM_AUTO, GNC_DENOM_LCD); discount = gnc_numeric_mul (pretax, discount, GNC_DENOM_AUTO, GNC_DENOM_LCD); } result = gnc_numeric_sub (pretax, discount, GNC_DENOM_AUTO, GNC_DENOM_LCD); /* Figure out when to apply the tax, pretax or pretax-discount */ if (discount_how == GNC_DISC_PRETAX) pretax = result; break; case GNC_DISC_POSTTAX: /* compute discount on pretax+taxes */ if (discount_type == GNC_AMT_TYPE_PERCENT) { gnc_numeric after_tax; tax = gnc_numeric_mul (pretax, tpercent, GNC_DENOM_AUTO, GNC_DENOM_LCD); after_tax = gnc_numeric_add (pretax, tax, GNC_DENOM_AUTO, GNC_DENOM_LCD); after_tax = gnc_numeric_add (after_tax, tvalue, GNC_DENOM_AUTO, GNC_DENOM_LCD); discount = gnc_numeric_div (discount, percent, GNC_DENOM_AUTO, GNC_DENOM_LCD); discount = gnc_numeric_mul (after_tax, discount, GNC_DENOM_AUTO, GNC_DENOM_LCD); } result = gnc_numeric_sub (pretax, discount, GNC_DENOM_AUTO, GNC_DENOM_LCD); break; default: g_warning ("unknown DiscountHow value: %d", discount_how); } /* Step 4: return the requested results. */ /* result == amount merchant gets * discount == amount of discount * need to compute taxes (based on 'pretax') if the caller wants it. */ if (discount_value != NULL) *discount_value = discount; if (value != NULL) *value = result; /* Now... Compute the list of tax values (if the caller wants it) */ if (tax_value != NULL) { GList * taxes = NULL; for (node = entries; node; node = node->next) { GncTaxTableEntry *entry = node->data; Account *acc = gncTaxTableEntryGetAccount (entry); gnc_numeric amount = gncTaxTableEntryGetAmount (entry); g_return_if_fail (acc); switch (gncTaxTableEntryGetType (entry)) { case GNC_AMT_TYPE_VALUE: taxes = gncAccountValueAdd (taxes, acc, amount); break; case GNC_AMT_TYPE_PERCENT: amount = gnc_numeric_div (amount, percent, GNC_DENOM_AUTO, GNC_DENOM_LCD); tax = gnc_numeric_mul (pretax, amount, GNC_DENOM_AUTO, GNC_DENOM_LCD); taxes = gncAccountValueAdd (taxes, acc, tax); break; default: break; } } *tax_value = taxes; } return; } static int get_commodity_denom (GncEntry *entry) { gnc_commodity *c; if (!entry) return 0; if (entry->invoice) { c = gncInvoiceGetCurrency (entry->invoice); if (c) return (gnc_commodity_get_fraction (c)); } if (entry->bill) { c = gncInvoiceGetCurrency (entry->bill); if (c) return (gnc_commodity_get_fraction (c)); } return 100000; } static void gncEntryRecomputeValues (GncEntry *entry) { int denom; /* See if either tax table changed since we last computed values */ if (entry->i_tax_table) { Timespec modtime = gncTaxTableLastModified (entry->i_tax_table); if (timespec_cmp (&entry->i_taxtable_modtime, &modtime)) { entry->values_dirty = TRUE; entry->i_taxtable_modtime = modtime; } } if (entry->b_tax_table) { Timespec modtime = gncTaxTableLastModified (entry->b_tax_table); if (timespec_cmp (&entry->b_taxtable_modtime, &modtime)) { entry->values_dirty = TRUE; entry->b_taxtable_modtime = modtime; } } if (!entry->values_dirty) return; /* Clear the last-computed tax values */ if (entry->i_tax_values) { gncAccountValueDestroy (entry->i_tax_values); entry->i_tax_values = NULL; } if (entry->b_tax_values) { gncAccountValueDestroy (entry->b_tax_values); entry->b_tax_values = NULL; } /* Compute the invoice values */ gncEntryComputeValue (entry->quantity, entry->i_price, (entry->i_taxable ? entry->i_tax_table : NULL), entry->i_taxincluded, entry->i_discount, entry->i_disc_type, entry->i_disc_how, &(entry->i_value), &(entry->i_disc_value), &(entry->i_tax_values)); /* Compute the bill values */ gncEntryComputeValue (entry->quantity, entry->b_price, (entry->b_taxable ? entry->b_tax_table : NULL), entry->b_taxincluded, gnc_numeric_zero(), GNC_AMT_TYPE_VALUE, GNC_DISC_PRETAX, &(entry->b_value), NULL, &(entry->b_tax_values)); denom = get_commodity_denom (entry); entry->i_value_rounded = gnc_numeric_convert (entry->i_value, denom, GNC_RND_ROUND); entry->i_disc_value_rounded = gnc_numeric_convert (entry->i_disc_value, denom, GNC_RND_ROUND); entry->i_tax_value = gncAccountValueTotal (entry->i_tax_values); entry->i_tax_value_rounded = gnc_numeric_convert (entry->i_tax_value, denom, GNC_RND_ROUND); entry->b_value_rounded = gnc_numeric_convert (entry->b_value, denom, GNC_RND_ROUND); entry->b_tax_value = gncAccountValueTotal (entry->b_tax_values); entry->b_tax_value_rounded = gnc_numeric_convert (entry->b_tax_value, denom, GNC_RND_ROUND); entry->values_dirty = FALSE; } void gncEntryGetValue (GncEntry *entry, gboolean is_inv, gnc_numeric *value, gnc_numeric *discount_value, gnc_numeric *tax_value, GList **tax_values) { if (!entry) return; gncEntryRecomputeValues (entry); if (value) *value = (is_inv ? entry->i_value : entry->b_value); if (discount_value) *discount_value = (is_inv ? entry->i_disc_value : gnc_numeric_zero()); if (tax_value) *tax_value = (is_inv ? entry->i_tax_value : entry->b_tax_value); if (tax_values) *tax_values = (is_inv ? entry->i_tax_values : entry->b_tax_values); } gnc_numeric gncEntryReturnValue (GncEntry *entry, gboolean is_inv) { if (!entry) return gnc_numeric_zero(); gncEntryRecomputeValues (entry); return (is_inv ? entry->i_value_rounded : entry->b_value_rounded); } gnc_numeric gncEntryReturnTaxValue (GncEntry *entry, gboolean is_inv) { if (!entry) return gnc_numeric_zero(); gncEntryRecomputeValues (entry); return (is_inv ? entry->i_tax_value_rounded : entry->b_tax_value_rounded); } GList * gncEntryReturnTaxValues (GncEntry *entry, gboolean is_inv) { if (!entry) return NULL; gncEntryRecomputeValues (entry); return (is_inv ? entry->i_tax_values : entry->b_tax_values); } gnc_numeric gncEntryReturnDiscountValue (GncEntry *entry, gboolean is_inv) { if (!entry) return gnc_numeric_zero(); gncEntryRecomputeValues (entry); return (is_inv ? entry->i_disc_value_rounded : gnc_numeric_zero()); } gboolean gncEntryIsOpen (GncEntry *entry) { if (!entry) return FALSE; return (entry->editlevel > 0); } void gncEntryBeginEdit (GncEntry *entry) { GNC_BEGIN_EDIT (entry, _GNC_MOD_NAME); } static void gncEntryOnError (GncEntry *entry, GNCBackendError errcode) { PERR("Entry Backend Failure: %d", errcode); } static void gncEntryOnDone (GncEntry *entry) { entry->dirty = FALSE; } void gncEntryCommitEdit (GncEntry *entry) { GNC_COMMIT_EDIT_PART1 (entry); GNC_COMMIT_EDIT_PART2 (entry, _GNC_MOD_NAME, gncEntryOnError, gncEntryOnDone, gncEntryFree); } int gncEntryCompare (GncEntry *a, GncEntry *b) { int compare; if (a == b) return 0; if (!a && b) return -1; if (a && !b) return 1; compare = timespec_cmp (&(a->date), &(b->date)); if (compare) return compare; compare = timespec_cmp (&(a->date_entered), &(b->date_entered)); if (compare) return compare; compare = safe_strcmp (a->desc, b->desc); if (compare) return compare; compare = safe_strcmp (a->action, b->action); if (compare) return compare; return guid_compare (&(a->guid), &(b->guid)); } /* Package-Private functions */ static void addObj (GncEntry *entry) { gncBusinessAddObject (entry->book, _GNC_MOD_NAME, entry, &entry->guid); } static void remObj (GncEntry *entry) { gncBusinessRemoveObject (entry->book, _GNC_MOD_NAME, &entry->guid); } static void _gncEntryCreate (GNCBook *book) { gncBusinessCreate (book, _GNC_MOD_NAME); } static void _gncEntryDestroy (GNCBook *book) { gncBusinessDestroy (book, _GNC_MOD_NAME); } static gboolean _gncEntryIsDirty (GNCBook *book) { return gncBusinessIsDirty (book, _GNC_MOD_NAME); } static void _gncEntryMarkClean (GNCBook *book) { gncBusinessSetDirtyFlag (book, _GNC_MOD_NAME, FALSE); } static void _gncEntryForeach (GNCBook *book, foreachObjectCB cb, gpointer user_data) { gncBusinessForeach (book, _GNC_MOD_NAME, cb, user_data); } static GncObject_t gncEntryDesc = { GNC_OBJECT_VERSION, _GNC_MOD_NAME, "Order/Invoice/Bill Entry", _gncEntryCreate, _gncEntryDestroy, _gncEntryIsDirty, _gncEntryMarkClean, _gncEntryForeach, NULL /* printable */ }; gboolean gncEntryRegister (void) { static QueryObjectDef params[] = { { ENTRY_DATE, QUERYCORE_DATE, (QueryAccess)gncEntryGetDate }, { ENTRY_DATE_ENTERED, QUERYCORE_DATE, (QueryAccess)gncEntryGetDateEntered }, { ENTRY_DESC, QUERYCORE_STRING, (QueryAccess)gncEntryGetDescription }, { ENTRY_ACTION, QUERYCORE_STRING, (QueryAccess)gncEntryGetAction }, { ENTRY_NOTES, QUERYCORE_STRING, (QueryAccess)gncEntryGetNotes }, { ENTRY_QTY, QUERYCORE_NUMERIC, (QueryAccess)gncEntryGetQuantity }, { ENTRY_IPRICE, QUERYCORE_NUMERIC, (QueryAccess)gncEntryGetInvPrice }, { ENTRY_BPRICE, QUERYCORE_NUMERIC, (QueryAccess)gncEntryGetBillPrice }, { ENTRY_INVOICE, GNC_INVOICE_MODULE_NAME, (QueryAccess)gncEntryGetInvoice }, { ENTRY_BILL, GNC_INVOICE_MODULE_NAME, (QueryAccess)gncEntryGetBill }, { ENTRY_BILLABLE, QUERYCORE_BOOLEAN, (QueryAccess)gncEntryGetBillable }, { ENTRY_BILLTO, GNC_OWNER_MODULE_NAME, (QueryAccess)gncEntryGetBillTo }, { ENTRY_ORDER, GNC_ORDER_MODULE_NAME, (QueryAccess)gncEntryGetOrder }, { QUERY_PARAM_BOOK, GNC_ID_BOOK, (QueryAccess)gncEntryGetBook }, { QUERY_PARAM_GUID, QUERYCORE_GUID, (QueryAccess)gncEntryGetGUID }, { NULL }, }; gncQueryObjectRegister (_GNC_MOD_NAME, (QuerySort)gncEntryCompare, params); return gncObjectRegister (&gncEntryDesc); }