gnucash/gnucash/gnome/dialog-invoice.c
luz.paz 65bb60d621 Fix typos for gnuchash/ doc/ bindings/ and misc.
Typos found via `codespell -q 3 -D ~/Projects/codespell/codespell_lib/data/dictionary.txt -S *.po,./po,*.min.js,./ChangeLog*,./NEWS,./doc/README*,./AUTHORS,./libgnucash/tax/us/txf-de*,./data/accounts -L ans,cas,dragable,gae,iff,iif,mut,nd,numer,startd,stoll`
2019-09-13 20:26:03 -04:00

3520 lines
115 KiB
C

/*
* dialog-invoice.c -- Dialog for Invoice entry
* Copyright (C) 2001,2002,2006 Derek Atkins
* Author: Derek Atkins <warlord@MIT.EDU>
*
* Copyright (c) 2005,2006 David Hampton <hampton@employees.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact:
*
* Free Software Foundation Voice: +1-617-542-5942
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
* Boston, MA 02110-1301, USA gnu@gnu.org
*/
#include <config.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libguile.h>
#include "swig-runtime.h"
#include "qof.h"
#include "dialog-utils.h"
#include "gnc-component-manager.h"
#include "gnc-ui.h"
#include "gnc-gui-query.h"
#include "gnc-prefs.h"
#include "gnc-ui-util.h"
#include "gnc-date.h"
#include "gnc-date-edit.h"
#include "gnc-amount-edit.h"
#include "gnucash-sheet.h"
#include "gnucash-register.h"
#include "window-report.h"
#include "dialog-search.h"
#include "search-param.h"
#include "gnc-session.h"
#include "gncOwner.h"
#include "gncInvoice.h"
#include "gncInvoiceP.h"
#include "gncEntryLedger.h"
#include "gnc-plugin-page.h"
#include "gnc-general-search.h"
#include "dialog-date-close.h"
#include "dialog-invoice.h"
#include "dialog-job.h"
#include "business-gnome-utils.h"
#include "dialog-payment.h"
#include "dialog-tax-table.h"
#include "dialog-billterms.h"
#include "dialog-account.h"
#include "guile-mappings.h"
#include "dialog-dup-trans.h"
#include "dialog-query-view.h"
#include "gnc-plugin-business.h"
#include "gnc-plugin-page-invoice.h"
#include "gnc-main-window.h"
#include "dialog-transfer.h"
/* Disable -Waddress. GCC 4.2 warns (and fails to compile with -Werror) when
* passing the address of a guid on the stack to QOF_BOOK_LOOKUP_ENTITY via
* gncInvoiceLookup and friends. When the macro gets inlined, the compiler
* emits a warning that the guid null pointer test is always true.
*/
#if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
# pragma GCC diagnostic warning "-Waddress"
#endif
#define DIALOG_NEW_INVOICE_CM_CLASS "dialog-new-invoice"
#define DIALOG_VIEW_INVOICE_CM_CLASS "dialog-view-invoice"
#define GNC_PREFS_GROUP_SEARCH "dialogs.business.invoice-search"
#define GNC_PREF_NOTIFY_WHEN_DUE "notify-when-due"
#define GNC_PREF_ACCUM_SPLITS "accumulate-splits"
#define GNC_PREF_DAYS_IN_ADVANCE "days-in-advance"
void gnc_invoice_window_ok_cb (GtkWidget *widget, gpointer data);
void gnc_invoice_window_cancel_cb (GtkWidget *widget, gpointer data);
void gnc_invoice_window_help_cb (GtkWidget *widget, gpointer data);
void gnc_invoice_type_toggled_cb (GtkWidget *widget, gpointer data);
void gnc_invoice_id_changed_cb (GtkWidget *widget, gpointer data);
void gnc_invoice_terms_changed_cb (GtkWidget *widget, gpointer data);
#define ENUM_INVOICE_TYPE(_) \
_(NEW_INVOICE, ) \
_(MOD_INVOICE, ) \
_(DUP_INVOICE, ) \
_(EDIT_INVOICE, ) \
_(VIEW_INVOICE, )
DEFINE_ENUM(InvoiceDialogType, ENUM_INVOICE_TYPE)
AS_STRING_DEC(InvoiceDialogType, ENUM_INVOICE_TYPE)
FROM_STRING_DEC(InvoiceDialogType, ENUM_INVOICE_TYPE)
FROM_STRING_FUNC(InvoiceDialogType, ENUM_INVOICE_TYPE)
AS_STRING_FUNC(InvoiceDialogType, ENUM_INVOICE_TYPE)
typedef enum
{
DUE_FOR_VENDOR, // show bills due
DUE_FOR_CUSTOMER, // show invoices due
} GncWhichDueType;
struct _invoice_select_window
{
QofBook *book;
GncOwner *owner;
QofQuery *q;
GncOwner owner_def;
};
#define UNUSED_VAR __attribute__ ((unused))
static QofLogModule UNUSED_VAR log_module = G_LOG_DOMAIN; //G_LOG_BUSINESS;
/** This data structure does double duty. It is used to maintain
* information for the "New Invoice" dialog, and it is also used to
* maintain information for the "Invoice Entry" page that is embedded
* into a main window. Beware, as not all fields are used by both windows.
*/
struct _invoice_window
{
GtkBuilder * builder;
GtkWidget * dialog; /* Used by 'New Invoice Window' */
GncPluginPage *page; /* Used by 'Edit Invoice' Page */
/* Summary Bar Widgets */
GtkWidget * total_label;
GtkWidget * total_cash_label;
GtkWidget * total_charge_label;
GtkWidget * total_subtotal_label;
GtkWidget * total_tax_label;
/* Data Widgets */
GtkWidget * info_label; /*Default in glade is "Invoice Information"*/
GtkWidget * id_label; /* Default in glade is Invoice ID */
GtkWidget * type_label;
GtkWidget * type_hbox;
GtkWidget * type_choice;
GtkWidget * id_entry;
GtkWidget * notes_text;
GtkWidget * opened_date;
GtkWidget * posted_date_hbox;
GtkWidget * posted_date;
GtkWidget * active_check;
GtkWidget * paid_label;
GtkWidget * owner_box;
GtkWidget * owner_label;
GtkWidget * owner_choice;
GtkWidget * job_label;
GtkWidget * job_box;
GtkWidget * job_choice;
GtkWidget * billing_id_entry;
GtkWidget * terms_menu;
/* Project Widgets (used for Bills only) */
GtkWidget * proj_frame;
GtkWidget * proj_cust_box;
GtkWidget * proj_cust_choice;
GtkWidget * proj_job_box;
GtkWidget * proj_job_choice;
/* Expense Voucher Widgets */
GtkWidget * to_charge_frame;
GtkWidget * to_charge_edit;
gint width;
GncBillTerm * terms;
GnucashRegister * reg;
GncEntryLedger * ledger;
invoice_sort_type_t last_sort;
InvoiceDialogType dialog_type;
GncGUID invoice_guid;
gboolean is_credit_note;
gint component_id;
QofBook * book;
GncInvoice * created_invoice;
GncOwner owner;
GncOwner job;
GncOwner proj_cust;
GncOwner proj_job;
/* for Unposting */
gboolean reset_tax_tables;
};
/* Forward definitions for CB functions */
void gnc_invoice_window_active_toggled_cb (GtkWidget *widget, gpointer data);
gboolean gnc_invoice_window_leave_notes_cb (GtkWidget *widget, GdkEventFocus *event, gpointer data);
DialogQueryView *gnc_invoice_show_docs_due (GtkWindow *parent, QofBook *book, double days_in_advance, GncWhichDueType duetype);
#define INV_WIDTH_PREFIX "invoice_reg"
#define BILL_WIDTH_PREFIX "bill_reg"
#define VOUCHER_WIDTH_PREFIX "voucher_reg"
static void gnc_invoice_update_window (InvoiceWindow *iw, GtkWidget *widget);
static InvoiceWindow * gnc_ui_invoice_modify (GtkWindow *parent, GncInvoice *invoice);
/*******************************************************************************/
/* FUNCTIONS FOR ACCESSING DATA STRUCTURE FIELDS */
static GtkWidget *
iw_get_window (InvoiceWindow *iw)
{
if (iw->page)
return gnc_plugin_page_get_window(iw->page);
return iw->dialog;
}
GtkWidget *
gnc_invoice_get_register(InvoiceWindow *iw)
{
if (iw)
return (GtkWidget *)iw->reg;
return NULL;
}
GtkWidget *
gnc_invoice_get_notes(InvoiceWindow *iw)
{
if (iw)
return (GtkWidget *)iw->notes_text;
return NULL;
}
/*******************************************************************************/
/* FUNCTIONS FOR UNPOSTING */
static gboolean
iw_ask_unpost (InvoiceWindow *iw)
{
GtkWidget *dialog;
GtkToggleButton *toggle;
GtkBuilder *builder;
gint response;
builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "dialog-invoice.glade", "unpost_message_dialog");
dialog = GTK_WIDGET (gtk_builder_get_object (builder, "unpost_message_dialog"));
toggle = GTK_TOGGLE_BUTTON(gtk_builder_get_object (builder, "yes_tt_reset"));
// Set the style context for this dialog so it can be easily manipulated with css
gnc_widget_set_style_context (GTK_WIDGET(dialog), "GncInvoiceDialog");
gtk_window_set_transient_for (GTK_WINDOW(dialog),
GTK_WINDOW(iw_get_window(iw)));
iw->reset_tax_tables = FALSE;
gtk_widget_show_all(dialog);
response = gtk_dialog_run(GTK_DIALOG(dialog));
if (response == GTK_RESPONSE_OK)
iw->reset_tax_tables =
gtk_toggle_button_get_active(toggle);
gtk_widget_destroy(dialog);
g_object_unref(G_OBJECT(builder));
return (response == GTK_RESPONSE_OK);
}
/*******************************************************************************/
/* INVOICE WINDOW */
static GncInvoice *
iw_get_invoice (InvoiceWindow *iw)
{
if (!iw)
return NULL;
return gncInvoiceLookup (iw->book, &iw->invoice_guid);
}
static void
set_gncEntry_switch_type (gpointer data, gpointer user_data)
{
GncEntry *entry = data;
//g_warning("Modifying date for entry with desc=\"%s\"", gncEntryGetDescription(entry));
gncEntrySetQuantity (entry, gnc_numeric_neg (gncEntryGetQuantity (entry)));
}
static void
set_gncEntry_date(gpointer data, gpointer user_data)
{
GncEntry *entry = data;
time64 new_date = *(time64*) user_data;
//g_warning("Modifying date for entry with desc=\"%s\"", gncEntryGetDescription(entry));
gncEntrySetDate(entry, gnc_time64_get_day_neutral (new_date));
/*gncEntrySetDateEntered(entry, *new_date); - don't modify this
* because apparently it defines the ordering of the entries,
* which we don't want to change. */
}
static void gnc_ui_to_invoice (InvoiceWindow *iw, GncInvoice *invoice)
{
GtkTextBuffer* text_buffer;
GtkTextIter start, end;
gchar *text;
time64 time;
gboolean is_credit_note = gncInvoiceGetIsCreditNote (invoice);
if (iw->dialog_type == VIEW_INVOICE)
return;
gnc_suspend_gui_refresh ();
gncInvoiceBeginEdit (invoice);
if (iw->active_check)
gncInvoiceSetActive (invoice, gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON (iw->active_check)));
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(iw->notes_text));
gtk_text_buffer_get_bounds (text_buffer, &start, &end);
text = gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE);
gncInvoiceSetNotes (invoice, text);
if (iw->to_charge_edit)
gncInvoiceSetToChargeAmount (invoice,
gnc_amount_edit_get_amount
(GNC_AMOUNT_EDIT (iw->to_charge_edit)));
time = gnc_date_edit_get_date (GNC_DATE_EDIT (iw->opened_date));
/* Only set these values for NEW/MOD INVOICE types */
if (iw->dialog_type != EDIT_INVOICE)
{
gncInvoiceSetID (invoice, gtk_editable_get_chars
(GTK_EDITABLE (iw->id_entry), 0, -1));
gncInvoiceSetBillingID (invoice, gtk_editable_get_chars
(GTK_EDITABLE (iw->billing_id_entry), 0, -1));
gncInvoiceSetTerms (invoice, iw->terms);
gncInvoiceSetDateOpened (invoice, time);
gnc_owner_get_owner (iw->owner_choice, &(iw->owner));
if (iw->job_choice)
gnc_owner_get_owner (iw->job_choice, &(iw->job));
/* Only set the job if we've actually got one */
if (gncOwnerGetJob (&(iw->job)))
gncInvoiceSetOwner (invoice, &(iw->job));
else
gncInvoiceSetOwner (invoice, &(iw->owner));
/* Set the invoice currency based on the owner */
gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (&iw->owner));
/* Only set the BillTo if we've actually got one */
if (gncOwnerGetJob (&iw->proj_job))
gncInvoiceSetBillTo (invoice, &iw->proj_job);
else
gncInvoiceSetBillTo (invoice, &iw->proj_cust);
}
/* Document type can only be modified for a new or duplicated invoice/credit note */
if (iw->dialog_type == NEW_INVOICE || iw->dialog_type == DUP_INVOICE)
{
/* Update the entry dates to match the invoice date. This only really
* should happen for a duplicate invoice. However as a new invoice has
* no entries we can run this unconditionally. */
g_list_foreach(gncInvoiceGetEntries(invoice),
&set_gncEntry_date, &time);
gncInvoiceSetIsCreditNote (invoice, iw->is_credit_note);
}
/* If the document type changed on a duplicated invoice,
* its entries should be updated
*/
if (iw->dialog_type == DUP_INVOICE && iw->is_credit_note != is_credit_note)
{
g_list_foreach(gncInvoiceGetEntries(invoice),
&set_gncEntry_switch_type, NULL);
}
gncInvoiceCommitEdit (invoice);
gnc_resume_gui_refresh ();
}
static gboolean
gnc_invoice_window_verify_ok (InvoiceWindow *iw)
{
const char *res;
gchar *string;
/* save the current entry in the ledger? */
if (!gnc_entry_ledger_check_close (iw_get_window(iw), iw->ledger))
return FALSE;
/* Check the Owner */
gnc_owner_get_owner (iw->owner_choice, &(iw->owner));
res = gncOwnerGetName (&(iw->owner));
if (res == NULL || g_strcmp0 (res, "") == 0)
{
gnc_error_dialog (GTK_WINDOW (iw_get_window(iw)), "%s",
/* Translators: In this context,
* 'Billing information' maps to the
* label in the frame and means
* e.g. customer i.e. the company being
* invoiced. */
_("You need to supply Billing Information."));
return FALSE;
}
/* Check the ID; set one if necessary */
res = gtk_entry_get_text (GTK_ENTRY (iw->id_entry));
if (g_strcmp0 (res, "") == 0)
{
/* Invoices and bills have separate counters.
Therefore we pass the GncOwer to gncInvoiceNextID
so it knows whether we are creating a bill
or an invoice. */
string = gncInvoiceNextID(iw->book, &(iw->owner));
gtk_entry_set_text (GTK_ENTRY (iw->id_entry), string);
g_free(string);
}
return TRUE;
}
static gboolean
gnc_invoice_window_ok_save (InvoiceWindow *iw)
{
if (!gnc_invoice_window_verify_ok (iw))
return FALSE;
{
GncInvoice *invoice = iw_get_invoice (iw);
if (invoice)
{
gnc_ui_to_invoice (iw, invoice);
}
/* Save the invoice to return it later. */
iw->created_invoice = invoice;
}
return TRUE;
}
void
gnc_invoice_window_ok_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!gnc_invoice_window_ok_save (iw))
return;
/* Ok, we don't need this anymore */
iw->invoice_guid = *guid_null ();
/* if this is a new or duplicated invoice, and created_invoice is NON-NULL,
* then open up a new window with the invoice. This used to be done
* in gnc_ui_invoice_new() but cannot be done anymore
*/
if ((iw->dialog_type == NEW_INVOICE || iw->dialog_type == DUP_INVOICE)
&& iw->created_invoice)
gnc_ui_invoice_edit (gnc_ui_get_main_window (iw->dialog), iw->created_invoice);
gnc_close_gui_component (iw->component_id);
}
void
gnc_invoice_window_cancel_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
gnc_close_gui_component (iw->component_id);
}
void
gnc_invoice_window_help_cb (GtkWidget *widget, gpointer data)
{
gnc_gnome_help(HF_HELP, HL_USAGE_INVOICE);
}
void
gnc_invoice_window_destroy_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice (iw);
gnc_suspend_gui_refresh ();
if ((iw->dialog_type == NEW_INVOICE || iw->dialog_type == DUP_INVOICE)
&& invoice != NULL)
{
gncInvoiceRemoveEntries (invoice);
gncInvoiceBeginEdit (invoice);
gncInvoiceDestroy (invoice);
iw->invoice_guid = *guid_null ();
}
gnc_entry_ledger_destroy (iw->ledger);
gnc_unregister_gui_component (iw->component_id);
gtk_widget_destroy(widget);
gnc_resume_gui_refresh ();
g_free (iw);
}
void
gnc_invoice_window_editCB (GtkWindow *parent, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice (iw);
if (invoice)
gnc_ui_invoice_modify (parent, invoice);
}
void
gnc_invoice_window_duplicateInvoiceCB (GtkWindow *parent, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice (iw);
if (invoice)
gnc_ui_invoice_duplicate (parent, invoice, TRUE, NULL);
}
void gnc_invoice_window_entryUpCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
gnc_entry_ledger_move_current_entry_updown(iw->ledger, TRUE);
}
void gnc_invoice_window_entryDownCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
gnc_entry_ledger_move_current_entry_updown(iw->ledger, FALSE);
}
void
gnc_invoice_window_recordCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
if (!gnc_entry_ledger_commit_entry (iw->ledger))
return;
gnucash_register_goto_next_virt_row (iw->reg);
}
void
gnc_invoice_window_cancelCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
gnc_entry_ledger_cancel_cursor_changes (iw->ledger);
}
void
gnc_invoice_window_deleteCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncEntry *entry;
if (!iw || !iw->ledger)
return;
/* get the current entry based on cursor position */
entry = gnc_entry_ledger_get_current_entry (iw->ledger);
if (!entry)
{
gnc_entry_ledger_cancel_cursor_changes (iw->ledger);
return;
}
/* deleting the blank entry just cancels */
if (entry == gnc_entry_ledger_get_blank_entry (iw->ledger))
{
gnc_entry_ledger_cancel_cursor_changes (iw->ledger);
return;
}
/* Verify that the user really wants to delete this entry */
{
const char *message = _("Are you sure you want to delete the "
"selected entry?");
const char *order_warn = _("This entry is attached to an order and "
"will be deleted from that as well!");
char *msg;
gboolean result;
if (gncEntryGetOrder (entry))
msg = g_strconcat (message, "\n\n", order_warn, (char *)NULL);
else
msg = g_strdup (message);
result = gnc_verify_dialog (GTK_WINDOW (iw_get_window(iw)), FALSE, "%s", msg);
g_free (msg);
if (!result)
return;
}
/* Yep, let's delete */
gnc_entry_ledger_delete_current_entry (iw->ledger);
return;
}
void
gnc_invoice_window_duplicateCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
gnc_entry_ledger_duplicate_current_entry (iw->ledger);
}
void
gnc_invoice_window_blankCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw || !iw->ledger)
return;
if (!gnc_entry_ledger_commit_entry (iw->ledger))
return;
{
VirtualCellLocation vcell_loc;
GncEntry *blank;
blank = gnc_entry_ledger_get_blank_entry (iw->ledger);
if (blank == NULL)
return;
if (gnc_entry_ledger_get_entry_virt_loc (iw->ledger, blank, &vcell_loc))
gnucash_register_goto_virt_cell (iw->reg, vcell_loc);
}
}
static void
gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
{
SCM func, arg, arg2;
SCM args = SCM_EOL;
int report_id;
const char *reportname = gnc_plugin_business_get_invoice_printreport();
g_return_if_fail (invoice);
if (!reportname)
reportname = "5123a759ceb9483abf2182d01c140e8d"; // fallback if the option lookup failed
func = scm_c_eval_string ("gnc:invoice-report-create");
g_return_if_fail (scm_is_procedure (func));
arg = SWIG_NewPointerObj(invoice, SWIG_TypeQuery("_p__gncInvoice"), 0);
arg2 = scm_from_utf8_string(reportname);
args = scm_cons2 (arg, arg2, args);
/* scm_gc_protect_object(func); */
arg = scm_apply (func, args, SCM_EOL);
g_return_if_fail (scm_is_exact (arg));
report_id = scm_to_int (arg);
/* scm_gc_unprotect_object(func); */
if (report_id >= 0)
reportWindow (report_id, parent);
}
void
gnc_invoice_window_printCB (GtkWindow* parent, gpointer data)
{
InvoiceWindow *iw = data;
gnc_invoice_window_print_invoice (parent, iw_get_invoice (iw));
}
static gboolean
gnc_dialog_post_invoice(InvoiceWindow *iw, char *message,
time64 *ddue, time64 *postdate,
char **memo, Account **acc, gboolean *accumulate)
{
GncInvoice *invoice;
char *ddue_label, *post_label, *acct_label, *question_label;
GList * acct_types = NULL;
GList * acct_commodities = NULL;
QofInstance *owner_inst;
EntryList *entries, *entries_iter;
invoice = iw_get_invoice (iw);
if (!invoice)
return FALSE;
ddue_label = _("Due Date");
post_label = _("Post Date");
acct_label = _("Post to Account");
question_label = _("Accumulate Splits?");
/* Determine the type of account to post to */
acct_types = gncOwnerGetAccountTypesList (&(iw->owner));
/* Determine which commodity we're working with */
acct_commodities = gncOwnerGetCommoditiesList(&(iw->owner));
/* Get the invoice entries */
entries = gncInvoiceGetEntries (invoice);
/* Find the most suitable post date.
* For Customer Invoices that would be today.
* For Vendor Bills and Employee Vouchers
* that would be the date of the most recent invoice entry.
* Failing that, today is used as a fallback */
*postdate = gnc_time(NULL);
if (entries && ((gncInvoiceGetOwnerType (invoice) == GNC_OWNER_VENDOR) ||
(gncInvoiceGetOwnerType (invoice) == GNC_OWNER_EMPLOYEE)))
{
*postdate = gncEntryGetDate ((GncEntry*)entries->data);
for (entries_iter = entries; entries_iter != NULL; entries_iter = g_list_next(entries_iter))
{
time64 entrydate = gncEntryGetDate ((GncEntry*)entries_iter->data);
if (entrydate > *postdate)
*postdate = entrydate;
}
}
/* Get the due date and posted account */
*ddue = *postdate;
*memo = NULL;
{
GncGUID *guid = NULL;
owner_inst = qofOwnerGetOwner (gncOwnerGetEndOwner (&(iw->owner)));
qof_instance_get (owner_inst,
"invoice-last-posted-account", &guid,
NULL);
*acc = xaccAccountLookup (guid, iw->book);
}
/* Get the default for the accumulate option */
*accumulate = gnc_prefs_get_bool(GNC_PREFS_GROUP_INVOICE, GNC_PREF_ACCUM_SPLITS);
if (!gnc_dialog_dates_acct_question_parented (iw_get_window(iw), message, ddue_label,
post_label, acct_label, question_label, TRUE, TRUE,
acct_types, acct_commodities, iw->book, iw->terms,
ddue, postdate, memo, acc, accumulate))
return FALSE;
return TRUE;
}
struct post_invoice_params
{
time64 ddue; /* Due date */
time64 postdate; /* Date posted */
char *memo; /* Memo for posting transaction */
Account *acc; /* Account to post to */
gboolean accumulate; /* Whether to accumulate splits */
GtkWindow *parent;
};
static void
gnc_invoice_post(InvoiceWindow *iw, struct post_invoice_params *post_params)
{
GncInvoice *invoice;
char *message, *memo;
Account *acc = NULL;
time64 ddue, postdate;
gboolean accumulate;
QofInstance *owner_inst;
const char *text;
GHashTable *foreign_currs;
GHashTableIter foreign_currs_iter;
gpointer key,value;
gboolean is_cust_doc, auto_pay;
gboolean show_dialog = TRUE;
gboolean post_ok = TRUE;
/* Make sure the invoice is ok */
if (!gnc_invoice_window_verify_ok (iw))
return;
invoice = iw_get_invoice (iw);
if (!invoice)
return;
/* Check that there is at least one Entry */
if (gncInvoiceGetEntries (invoice) == NULL)
{
gnc_error_dialog (GTK_WINDOW (iw_get_window(iw)), "%s",
_("The Invoice must have at least one Entry."));
return;
}
is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER);
/* Ok, we can post this invoice. Ask for verification, set the due date,
* post date, and posted account
*/
if (post_params)
{
ddue = post_params->ddue;
postdate = post_params->postdate;
// Dup it since it will free it below
memo = g_strdup (post_params->memo);
acc = post_params->acc;
accumulate = post_params->accumulate;
}
else
{
message = _("Do you really want to post the invoice?");
if (!gnc_dialog_post_invoice(iw, message,
&ddue, &postdate, &memo, &acc, &accumulate))
return;
}
/* Yep, we're posting. So, save the invoice...
* Note that we can safely ignore the return value; we checked
* the verify_ok earlier, so we know it's ok.
* Additionally make sure the invoice has the owner's currency
* refer to https://bugs.gnucash.org/show_bug.cgi?id=728074
*/
gnc_suspend_gui_refresh ();
gncInvoiceBeginEdit (invoice);
gnc_invoice_window_ok_save (iw);
gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (gncInvoiceGetOwner (invoice)));
/* Fill in the conversion prices with feedback from the user */
text = _("One or more of the entries are for accounts different from the invoice/bill currency. You will be asked a conversion rate for each.");
/* Ask the user for conversion rates for all foreign currencies
* (relative to the invoice currency) */
foreign_currs = gncInvoiceGetForeignCurrencies (invoice);
g_hash_table_iter_init (&foreign_currs_iter, foreign_currs);
while (g_hash_table_iter_next (&foreign_currs_iter, &key, &value))
{
GNCPrice *convprice;
gnc_commodity *account_currency = (gnc_commodity*)key;
gnc_numeric *amount = (gnc_numeric*)value;
XferDialog *xfer;
gnc_numeric exch_rate;
/* Explain to the user we're about to ask for an exchange rate.
* Only show this dialog once, right before the first xfer dialog pops up.
*/
if (show_dialog)
{
gnc_info_dialog(GTK_WINDOW (iw_get_window(iw)), "%s", text);
show_dialog = FALSE;
}
/* Note some twisted logic here:
* We ask the exchange rate
* FROM invoice currency
* TO other account currency
* Because that's what happens logically.
* But the internal posting logic works backwards:
* It searches for an exchange rate
* FROM other account currency
* TO invoice currency
* So we will store the inverted exchange rate
*/
/* create the exchange-rate dialog */
xfer = gnc_xfer_dialog (iw_get_window(iw), acc);
gnc_xfer_dialog_is_exchange_dialog(xfer, &exch_rate);
gnc_xfer_dialog_select_to_currency(xfer, account_currency);
gnc_xfer_dialog_set_date (xfer, postdate);
/* Even if amount is 0 ask for an exchange rate. It's required
* for the transaction generating code. Use an amount of 1 in
* that case as the dialog won't allow to specify an exchange
* rate for 0. */
gnc_xfer_dialog_set_amount(xfer, gnc_numeric_zero_p (*amount) ?
(gnc_numeric){1, 1} : *amount);
/* If we already had an exchange rate from a previous post operation,
* set it here */
convprice = gncInvoiceGetPrice (invoice, account_currency);
if (convprice)
{
exch_rate = gnc_price_get_value (convprice);
/* Invert the exchange rate as explained above */
if (!gnc_numeric_zero_p (exch_rate))
{
exch_rate = gnc_numeric_div ((gnc_numeric){1, 1}, exch_rate,
GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
gnc_xfer_dialog_set_price_edit (xfer, exch_rate);
}
}
/* All we want is the exchange rate so prevent the user from thinking
it makes sense to mess with other stuff */
gnc_xfer_dialog_set_from_show_button_active(xfer, FALSE);
gnc_xfer_dialog_set_to_show_button_active(xfer, FALSE);
gnc_xfer_dialog_hide_from_account_tree(xfer);
gnc_xfer_dialog_hide_to_account_tree(xfer);
if (gnc_xfer_dialog_run_until_done(xfer))
{
/* User finished the transfer dialog successfully */
/* Invert the exchange rate as explained above */
if (!gnc_numeric_zero_p (exch_rate))
exch_rate = gnc_numeric_div ((gnc_numeric){1, 1}, exch_rate,
GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
convprice = gnc_price_create(iw->book);
gnc_price_begin_edit (convprice);
gnc_price_set_commodity (convprice, account_currency);
gnc_price_set_currency (convprice, gncInvoiceGetCurrency (invoice));
gnc_price_set_time64 (convprice, postdate);
gnc_price_set_source (convprice, PRICE_SOURCE_TEMP);
gnc_price_set_typestr (convprice, PRICE_TYPE_LAST);
gnc_price_set_value (convprice, exch_rate);
gncInvoiceAddPrice(invoice, convprice);
gnc_price_commit_edit (convprice);
}
else
{
/* User canceled the transfer dialog, abort posting */
post_ok = FALSE;
goto cleanup;
}
}
/* Save account as last used account in the owner's
* invoice-last-posted-account property.
*/
owner_inst = qofOwnerGetOwner (gncOwnerGetEndOwner (&(iw->owner)));
{
const GncGUID *guid = qof_instance_get_guid (QOF_INSTANCE (acc));
qof_begin_edit (owner_inst);
qof_instance_set (owner_inst,
"invoice-last-posted-account", guid,
NULL);
qof_commit_edit (owner_inst);
}
/* ... post it ... */
if (is_cust_doc)
auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_INVOICE, GNC_PREF_AUTO_PAY);
else
auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_BILL, GNC_PREF_AUTO_PAY);
gncInvoicePostToAccount (invoice, acc, postdate, ddue, memo, accumulate, auto_pay);
cleanup:
gncInvoiceCommitEdit (invoice);
g_hash_table_unref (foreign_currs);
gnc_resume_gui_refresh ();
if (memo)
g_free (memo);
if (post_ok)
{
/* Reset the type; change to read-only! */
iw->dialog_type = VIEW_INVOICE;
gnc_entry_ledger_set_readonly (iw->ledger, TRUE);
}
else
{
text = _("The post action was canceled because not all exchange rates were given.");
gnc_info_dialog(GTK_WINDOW (iw_get_window(iw)), "%s", text);
}
/* ... and redisplay here. */
gnc_invoice_update_window (iw, NULL);
gnc_table_refresh_gui (gnc_entry_ledger_get_table (iw->ledger), FALSE);
}
void
gnc_invoice_window_postCB (GtkWidget *unused_widget, gpointer data)
{
InvoiceWindow *iw =data;
gnc_invoice_post(iw, NULL);
}
void
gnc_invoice_window_unpostCB (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice;
gboolean result;
invoice = iw_get_invoice (iw);
if (!invoice)
return;
/* make sure the user REALLY wants to do this! */
result = iw_ask_unpost(iw);
if (!result) return;
/* Attempt to unpost the invoice */
gnc_suspend_gui_refresh ();
result = gncInvoiceUnpost (invoice, iw->reset_tax_tables);
gnc_resume_gui_refresh ();
if (!result) return;
/* if we get here, we succeeded in unposting -- reset the ledger and redisplay */
iw->dialog_type = EDIT_INVOICE;
gnc_entry_ledger_set_readonly (iw->ledger, FALSE);
gnc_invoice_update_window (iw, NULL);
gnc_table_refresh_gui (gnc_entry_ledger_get_table (iw->ledger), FALSE);
}
void gnc_invoice_window_cut_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
gnucash_register_cut_clipboard (iw->reg);
}
void gnc_invoice_window_copy_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
gnucash_register_copy_clipboard (iw->reg);
}
void gnc_invoice_window_paste_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
gnucash_register_paste_clipboard (iw->reg);
}
void gnc_invoice_window_new_invoice_cb (GtkWindow *parent, gpointer data)
{
InvoiceWindow *iw = data;
if (gncOwnerGetJob (&iw->job))
{
gnc_ui_invoice_new (parent, &iw->job, iw->book);
}
else
{
gnc_ui_invoice_new (parent, &iw->owner, iw->book);
}
}
void gnc_business_call_owner_report (GtkWindow *parent, GncOwner *owner, Account *acc)
{
int id;
SCM args;
SCM func;
SCM arg;
g_return_if_fail (owner);
args = SCM_EOL;
func = scm_c_eval_string ("gnc:owner-report-create");
g_return_if_fail (scm_is_procedure (func));
if (acc)
{
swig_type_info * qtype = SWIG_TypeQuery("_p_Account");
g_return_if_fail (qtype);
arg = SWIG_NewPointerObj(acc, qtype, 0);
g_return_if_fail (arg != SCM_UNDEFINED);
args = scm_cons (arg, args);
}
else
{
args = scm_cons (SCM_BOOL_F, args);
}
arg = SWIG_NewPointerObj(owner, SWIG_TypeQuery("_p__gncOwner"), 0);
g_return_if_fail (arg != SCM_UNDEFINED);
args = scm_cons (arg, args);
/* Apply the function to the args */
arg = scm_apply (func, args, SCM_EOL);
g_return_if_fail (scm_is_exact (arg));
id = scm_to_int (arg);
if (id >= 0)
reportWindow (id, parent);
}
void gnc_invoice_window_report_owner_cb (GtkWindow *parent, gpointer data)
{
InvoiceWindow *iw = data;
gnc_business_call_owner_report (parent, &iw->owner, NULL);
}
void gnc_invoice_window_payment_cb (GtkWindow *parent, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice(iw);
if (gncOwnerGetJob (&iw->job))
gnc_ui_payment_new_with_invoice (parent, &iw->job, iw->book, invoice);
else
gnc_ui_payment_new_with_invoice (parent, &iw->owner, iw->book, invoice);
}
/* Sorting callbacks */
void
gnc_invoice_window_sort (InvoiceWindow *iw, invoice_sort_type_t sort_code)
{
QofQuery *query = gnc_entry_ledger_get_query (iw->ledger);
GSList *p1 = NULL, *p2 = NULL, *p3 = NULL, *standard;
if (iw->last_sort == sort_code)
return;
standard = g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
switch (sort_code)
{
case INVSORT_BY_STANDARD:
p1 = standard;
break;
case INVSORT_BY_DATE:
p1 = g_slist_prepend (p1, ENTRY_DATE);
p2 = standard;
break;
case INVSORT_BY_DATE_ENTERED:
p1 = g_slist_prepend (p1, ENTRY_DATE_ENTERED);
p2 = standard;
break;
case INVSORT_BY_DESC:
p1 = g_slist_prepend (p1, ENTRY_DESC);
p2 = standard;
break;
case INVSORT_BY_QTY:
p1 = g_slist_prepend (p1, ENTRY_QTY);
p2 = standard;
break;
case INVSORT_BY_PRICE:
p1 = g_slist_prepend (p1, ((iw->owner.type == GNC_OWNER_CUSTOMER) ?
ENTRY_IPRICE : ENTRY_BPRICE));
p2 = standard;
break;
default:
g_slist_free (standard);
g_return_if_fail (FALSE);
break;
}
qof_query_set_sort_order (query, p1, p2, p3);
iw->last_sort = sort_code;
gnc_entry_ledger_display_refresh (iw->ledger);
}
/* Window configuration callbacks */
void
gnc_invoice_window_active_toggled_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice(iw);
if (!invoice) return;
gncInvoiceSetActive (invoice,
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
}
gboolean
gnc_invoice_window_leave_notes_cb (GtkWidget *widget, GdkEventFocus *event,
gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice(iw);
GtkTextBuffer* text_buffer;
GtkTextIter start, end;
gchar *text;
if (!invoice) return FALSE;
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(iw->notes_text));
gtk_text_buffer_get_bounds (text_buffer, &start, &end);
text = gtk_text_buffer_get_text (text_buffer, &start, &end, FALSE);
gncInvoiceSetNotes (invoice, text);
return FALSE;
}
static gboolean
gnc_invoice_window_leave_to_charge_cb (GtkWidget *widget, GdkEventFocus *event,
gpointer data)
{
gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (widget));
return FALSE;
}
static void
gnc_invoice_window_changed_to_charge_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice *invoice = iw_get_invoice(iw);
if (!invoice) return;
gncInvoiceSetToChargeAmount (invoice, gnc_amount_edit_get_amount
(GNC_AMOUNT_EDIT (widget)));
}
static GtkWidget *
add_summary_label (GtkWidget *summarybar, const char *label_str)
{
GtkWidget *hbox;
GtkWidget *label;
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
gtk_box_pack_start (GTK_BOX(summarybar), hbox, FALSE, FALSE, 5);
label = gtk_label_new (label_str);
gnc_label_set_alignment (label, 1.0, 0.5);
gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
label = gtk_label_new ("");
gnc_label_set_alignment (label, 1.0, 0.5);
gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
return label;
}
GtkWidget *
gnc_invoice_window_create_summary_bar (InvoiceWindow *iw)
{
GtkWidget *summarybar;
iw->total_label = NULL;
iw->total_cash_label = NULL;
iw->total_charge_label = NULL;
iw->total_subtotal_label = NULL;
iw->total_tax_label = NULL;
summarybar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
gtk_box_set_homogeneous (GTK_BOX (summarybar), FALSE);
iw->total_label = add_summary_label (summarybar, _("Total:"));
switch (gncOwnerGetType (&iw->owner))
{
case GNC_OWNER_CUSTOMER:
case GNC_OWNER_VENDOR:
iw->total_subtotal_label = add_summary_label (summarybar, _("Subtotal:"));
iw->total_tax_label = add_summary_label (summarybar, _("Tax:"));
break;
case GNC_OWNER_EMPLOYEE:
iw->total_cash_label = add_summary_label (summarybar, _("Total Cash:"));
iw->total_charge_label = add_summary_label (summarybar, _("Total Charge:"));
break;
default:
break;
}
gtk_widget_show_all(summarybar);
return summarybar;
}
static int
gnc_invoice_job_changed_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
char const *msg = "";
if (!iw)
return FALSE;
if (iw->dialog_type == VIEW_INVOICE)
return FALSE;
gnc_owner_get_owner (iw->job_choice, &(iw->job));
if (iw->dialog_type == EDIT_INVOICE)
return FALSE;
msg = gncJobGetReference (gncOwnerGetJob (&(iw->job)));
gtk_entry_set_text (GTK_ENTRY (iw->billing_id_entry), msg ? msg : "");
return FALSE;
}
static GNCSearchWindow *
gnc_invoice_select_job_cb (GtkWindow *parent, gpointer jobp, gpointer user_data)
{
GncJob *j = jobp;
InvoiceWindow *iw = user_data;
GncOwner owner, *ownerp;
if (!iw) return NULL;
if (j)
{
ownerp = gncJobGetOwner (j);
gncOwnerCopy (ownerp, &owner);
}
else
gncOwnerCopy (&(iw->owner), &owner);
return gnc_job_search (parent, j, &owner, iw->book);
}
static void
gnc_invoice_update_job_choice (InvoiceWindow *iw)
{
if (iw->job_choice)
gtk_container_remove (GTK_CONTAINER (iw->job_box), iw->job_choice);
/* If we don't have a real owner, then we obviously can't have a job */
if (iw->owner.owner.undefined == NULL)
{
iw->job_choice = NULL;
}
else
switch (iw->dialog_type)
{
case VIEW_INVOICE:
case EDIT_INVOICE:
iw->job_choice =
gnc_owner_edit_create (NULL, iw->job_box, iw->book, &(iw->job));
break;
case NEW_INVOICE:
case MOD_INVOICE:
case DUP_INVOICE:
iw->job_choice =
gnc_general_search_new (GNC_JOB_MODULE_NAME, _("Select..."), TRUE,
gnc_invoice_select_job_cb, iw, iw->book);
gnc_general_search_set_selected (GNC_GENERAL_SEARCH (iw->job_choice),
gncOwnerGetJob (&iw->job));
gnc_general_search_allow_clear (GNC_GENERAL_SEARCH (iw->job_choice),
TRUE);
gtk_box_pack_start (GTK_BOX (iw->job_box), iw->job_choice,
TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (iw->job_choice), "changed",
G_CALLBACK (gnc_invoice_job_changed_cb), iw);
break;
}
if (iw->job_choice)
gtk_widget_show_all (iw->job_choice);
}
static GNCSearchWindow *
gnc_invoice_select_proj_job_cb (GtkWindow *parent, gpointer jobp, gpointer user_data)
{
GncJob *j = jobp;
InvoiceWindow *iw = user_data;
GncOwner owner, *ownerp;
if (!iw) return NULL;
if (j)
{
ownerp = gncJobGetOwner (j);
gncOwnerCopy (ownerp, &owner);
}
else
gncOwnerCopy (&(iw->proj_cust), &owner);
return gnc_job_search (parent, j, &owner, iw->book);
}
static int
gnc_invoice_proj_job_changed_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw)
return FALSE;
if (iw->dialog_type == VIEW_INVOICE)
return FALSE;
gnc_owner_get_owner (iw->proj_job_choice, &(iw->proj_job));
return FALSE;
}
static void
gnc_invoice_update_proj_job (InvoiceWindow *iw)
{
if (iw->proj_job_choice)
gtk_container_remove (GTK_CONTAINER (iw->proj_job_box),
iw->proj_job_choice);
switch (iw->dialog_type)
{
case VIEW_INVOICE:
case EDIT_INVOICE:
iw->proj_job_choice =
gnc_owner_edit_create (NULL, iw->proj_job_box, iw->book, &(iw->proj_job));
break;
case NEW_INVOICE:
case MOD_INVOICE:
case DUP_INVOICE:
if (iw->proj_cust.owner.undefined == NULL)
{
iw->proj_job_choice = NULL;
}
else
{
iw->proj_job_choice =
gnc_general_search_new (GNC_JOB_MODULE_NAME, _("Select..."), TRUE,
gnc_invoice_select_proj_job_cb, iw, iw->book);
gnc_general_search_set_selected (GNC_GENERAL_SEARCH(iw->proj_job_choice),
gncOwnerGetJob (&iw->proj_job));
gnc_general_search_allow_clear (GNC_GENERAL_SEARCH (iw->proj_job_choice),
TRUE);
gtk_box_pack_start (GTK_BOX (iw->proj_job_box), iw->proj_job_choice,
TRUE, TRUE, 0);
g_signal_connect (G_OBJECT (iw->proj_job_choice), "changed",
G_CALLBACK (gnc_invoice_proj_job_changed_cb), iw);
}
break;
}
if (iw->proj_job_choice)
gtk_widget_show_all (iw->proj_job_choice);
}
static int
gnc_invoice_owner_changed_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncBillTerm *term = NULL;
GncOwner owner;
if (!iw)
return FALSE;
if (iw->dialog_type == VIEW_INVOICE)
return FALSE;
gncOwnerCopy (&(iw->owner), &owner);
gnc_owner_get_owner (iw->owner_choice, &owner);
/* If this owner really changed, then reset ourselves */
if (!gncOwnerEqual (&owner, &(iw->owner)))
{
gncOwnerCopy (&owner, &(iw->owner));
gncOwnerInitJob (&(iw->job), NULL);
gnc_entry_ledger_reset_query (iw->ledger);
}
if (iw->dialog_type == EDIT_INVOICE)
return FALSE;
switch (gncOwnerGetType (&(iw->owner)))
{
case GNC_OWNER_CUSTOMER:
term = gncCustomerGetTerms (gncOwnerGetCustomer (&(iw->owner)));
break;
case GNC_OWNER_VENDOR:
term = gncVendorGetTerms (gncOwnerGetVendor (&(iw->owner)));
break;
case GNC_OWNER_EMPLOYEE:
term = NULL;
break;
default:
g_warning ("Unknown owner type: %d\n", gncOwnerGetType (&(iw->owner)));
break;
}
/* XXX: I'm not sure -- should we change the terms if this happens? */
iw->terms = term;
gnc_simple_combo_set_value (GTK_COMBO_BOX(iw->terms_menu), iw->terms);
gnc_invoice_update_job_choice (iw);
return FALSE;
}
static int
gnc_invoice_proj_cust_changed_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
GncOwner owner;
if (!iw)
return FALSE;
if (iw->dialog_type == VIEW_INVOICE)
return FALSE;
gncOwnerCopy (&(iw->proj_cust), &owner);
gnc_owner_get_owner (iw->proj_cust_choice, &owner);
/* If this owner really changed, then reset ourselves */
if (!gncOwnerEqual (&owner, &(iw->proj_cust)))
{
gncOwnerCopy (&owner, &(iw->proj_cust));
gncOwnerInitJob (&(iw->proj_job), NULL);
}
if (iw->dialog_type == EDIT_INVOICE)
return FALSE;
gnc_invoice_update_proj_job (iw);
return FALSE;
}
static void
gnc_invoice_dialog_close_handler (gpointer user_data)
{
InvoiceWindow *iw = user_data;
if (iw)
{
gtk_widget_destroy (iw->dialog);
}
}
static void
gnc_invoice_window_close_handler (gpointer user_data)
{
InvoiceWindow *iw = user_data;
if (iw)
{
gnc_main_window_close_page(iw->page);
iw->page = NULL;
}
}
static void
gnc_invoice_reset_total_label (GtkLabel *label, gnc_numeric amt, gnc_commodity *com)
{
char string[256];
amt = gnc_numeric_convert (amt, gnc_commodity_get_fraction(com), GNC_HOW_RND_ROUND_HALF_UP);
xaccSPrintAmount (string, amt, gnc_commodity_print_info (com, TRUE));
gtk_label_set_text (label, string);
}
static void
gnc_invoice_redraw_all_cb (GnucashRegister *g_reg, gpointer data)
{
InvoiceWindow *iw = data;
GncInvoice * invoice;
gnc_commodity * currency;
gnc_numeric amount, to_charge_amt = gnc_numeric_zero();
if (!iw)
return;
// if (iw)
// gnc_invoice_update_window (iw, NULL);
invoice = iw_get_invoice (iw);
if (!invoice)
return;
currency = gncInvoiceGetCurrency (invoice);
if (iw->total_label)
{
amount = gncInvoiceGetTotal (invoice);
gnc_invoice_reset_total_label (GTK_LABEL (iw->total_label), amount, currency);
}
if (iw->total_subtotal_label)
{
amount = gncInvoiceGetTotalSubtotal (invoice);
gnc_invoice_reset_total_label (GTK_LABEL (iw->total_subtotal_label), amount, currency);
}
if (iw->total_tax_label)
{
amount = gncInvoiceGetTotalTax (invoice);
gnc_invoice_reset_total_label (GTK_LABEL (iw->total_tax_label), amount, currency);
}
/* Deal with extra items for the expense voucher */
if (iw->to_charge_edit)
{
gnc_amount_edit_evaluate (GNC_AMOUNT_EDIT (iw->to_charge_edit));
to_charge_amt = gnc_amount_edit_get_amount(GNC_AMOUNT_EDIT(iw->to_charge_edit));
}
if (iw->total_cash_label)
{
amount = gncInvoiceGetTotalOf (invoice, GNC_PAYMENT_CASH);
amount = gnc_numeric_sub (amount, to_charge_amt,
gnc_commodity_get_fraction (currency), GNC_HOW_RND_ROUND_HALF_UP);
gnc_invoice_reset_total_label (GTK_LABEL (iw->total_cash_label), amount, currency);
}
if (iw->total_charge_label)
{
amount = gncInvoiceGetTotalOf (invoice, GNC_PAYMENT_CARD);
amount = gnc_numeric_add (amount, to_charge_amt,
gnc_commodity_get_fraction (currency), GNC_HOW_RND_ROUND_HALF_UP);
gnc_invoice_reset_total_label (GTK_LABEL (iw->total_charge_label), amount, currency);
}
}
void
gnc_invoice_window_changed (InvoiceWindow *iw, GtkWidget *window)
{
gnc_entry_ledger_set_parent(iw->ledger, window);
}
gchar *
gnc_invoice_get_help (InvoiceWindow *iw)
{
if (!iw)
return NULL;
return gnc_table_get_help (gnc_entry_ledger_get_table (iw->ledger));
}
static void
gnc_invoice_window_refresh_handler (GHashTable *changes, gpointer user_data)
{
InvoiceWindow *iw = user_data;
const EventInfo *info;
GncInvoice *invoice = iw_get_invoice (iw);
const GncOwner *owner;
/* If there isn't an invoice behind us, close down */
if (!invoice)
{
gnc_close_gui_component (iw->component_id);
return;
}
/* Next, close if this is a destroy event */
if (changes)
{
info = gnc_gui_get_entity_events (changes, &iw->invoice_guid);
if (info && (info->event_mask & QOF_EVENT_DESTROY))
{
gnc_close_gui_component (iw->component_id);
return;
}
}
/* Check the owners, and see if they have changed */
owner = gncInvoiceGetOwner (invoice);
/* Copy the owner information into our window */
gncOwnerCopy (gncOwnerGetEndOwner (owner), &(iw->owner));
gncOwnerInitJob (&(iw->job), gncOwnerGetJob (owner));
/* Copy the billto information into our window */
owner = gncInvoiceGetBillTo (invoice);
gncOwnerCopy (gncOwnerGetEndOwner (owner), &iw->proj_cust);
gncOwnerInitJob (&iw->proj_job, gncOwnerGetJob (owner));
/* Ok, NOW let's refresh ourselves */
gnc_invoice_update_window (iw, NULL);
}
/** Update the various widgets in the window/page based upon the data
* in the InvoiceWindow data structure.
*
* @param iw A pointer to the InvoiceWindow data structure.
*
* @param widget If set, this is the widget that will be used for the
* call to gtk_widget_show_all(). This is needed at window/page
* creation time when all of the iw/page linkages haven't been set up
* yet.
*/
static void
gnc_invoice_update_window (InvoiceWindow *iw, GtkWidget *widget)
{
GtkWidget *acct_entry;
GncInvoice *invoice;
gboolean is_posted = FALSE;
gboolean can_unpost = FALSE;
invoice = iw_get_invoice (iw);
if (iw->owner_choice)
gtk_container_remove (GTK_CONTAINER (iw->owner_box), iw->owner_choice);
if (iw->proj_cust_choice)
gtk_container_remove (GTK_CONTAINER (iw->proj_cust_box),
iw->proj_cust_choice);
switch (iw->dialog_type)
{
case VIEW_INVOICE:
case EDIT_INVOICE:
iw->owner_choice =
gnc_owner_edit_create (iw->owner_label, iw->owner_box, iw->book,
&(iw->owner));
iw->proj_cust_choice =
gnc_owner_edit_create (NULL, iw->proj_cust_box, iw->book,
&(iw->proj_cust));
break;
case NEW_INVOICE:
case MOD_INVOICE:
case DUP_INVOICE:
iw->owner_choice =
gnc_owner_select_create (iw->owner_label, iw->owner_box, iw->book,
&(iw->owner));
iw->proj_cust_choice =
gnc_owner_select_create (NULL, iw->proj_cust_box, iw->book,
&(iw->proj_cust));
g_signal_connect (G_OBJECT (iw->owner_choice), "changed",
G_CALLBACK (gnc_invoice_owner_changed_cb), iw);
g_signal_connect (G_OBJECT (iw->proj_cust_choice), "changed",
G_CALLBACK (gnc_invoice_proj_cust_changed_cb), iw);
break;
}
/* Set the type label */
gtk_label_set_text (GTK_LABEL(iw->type_label), iw->is_credit_note ? _("Credit Note")
: gtk_label_get_text (GTK_LABEL(iw->type_label)));
if (iw->owner_choice)
gtk_widget_show_all (iw->owner_choice);
if (iw->proj_cust_choice)
gtk_widget_show_all (iw->proj_cust_choice);
gnc_invoice_update_job_choice (iw);
gnc_invoice_update_proj_job (iw);
/* Hide the project frame for customer invoices */
if (iw->owner.type == GNC_OWNER_CUSTOMER)
gtk_widget_hide (iw->proj_frame);
/* Hide the "job" label and entry for employee invoices */
if (iw->owner.type == GNC_OWNER_EMPLOYEE)
{
gtk_widget_hide (iw->job_label);
gtk_widget_hide (iw->job_box);
}
acct_entry = GTK_WIDGET (gtk_builder_get_object (iw->builder, "acct_entry"));
/* We know that "invoice" (and "owner") exist now */
{
GtkTextBuffer* text_buffer;
const char *string;
gchar * tmp_string;
time64 time;
gtk_entry_set_text (GTK_ENTRY (iw->id_entry), gncInvoiceGetID (invoice));
gtk_entry_set_text (GTK_ENTRY (iw->billing_id_entry),
gncInvoiceGetBillingID (invoice));
string = gncInvoiceGetNotes (invoice);
text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(iw->notes_text));
gtk_text_buffer_set_text (text_buffer, string, -1);
if (iw->active_check)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (iw->active_check),
gncInvoiceGetActive (invoice));
time = gncInvoiceGetDateOpened (invoice);
if (time == INT64_MAX)
{
gnc_date_edit_set_time (GNC_DATE_EDIT (iw->opened_date),
gnc_time (NULL));
}
else
{
gnc_date_edit_set_time (GNC_DATE_EDIT (iw->opened_date), time);
}
/* fill in the terms text */
iw->terms = gncInvoiceGetTerms (invoice);
//DEBUG("iw->dialog_type: %d",iw->dialog_type);
switch (iw->dialog_type)
{
case NEW_INVOICE:
case MOD_INVOICE:
case DUP_INVOICE: //??
gnc_simple_combo_set_value (GTK_COMBO_BOX(iw->terms_menu), iw->terms);
break;
case EDIT_INVOICE:
case VIEW_INVOICE:
// Fill in the invoice view version
if(gncBillTermGetName (iw->terms) != NULL)
gtk_entry_set_text (GTK_ENTRY (iw->terms_menu),gncBillTermGetName (iw->terms));
else
gtk_entry_set_text (GTK_ENTRY (iw->terms_menu),"None");
break;
default:
break;
}
/*
* Next, figure out if we've been posted, and if so set the appropriate
* bits of information... Then work on hiding or showing as
* necessary.
*/
is_posted = gncInvoiceIsPosted (invoice);
if (is_posted)
{
Account *acct = gncInvoiceGetPostedAcc (invoice);
/* Can we unpost this invoice?
* XXX: right now we always can, but there
* may be times in the future when we cannot.
*/
can_unpost = TRUE;
time = gncInvoiceGetDatePosted (invoice);
gnc_date_edit_set_time (GNC_DATE_EDIT (iw->posted_date), time);
tmp_string = gnc_account_get_full_name (acct);
gtk_entry_set_text (GTK_ENTRY (acct_entry), tmp_string);
g_free(tmp_string);
}
}
gnc_invoice_id_changed_cb(NULL, iw);
if (iw->dialog_type == NEW_INVOICE ||
iw->dialog_type == DUP_INVOICE ||
iw->dialog_type == MOD_INVOICE)
{
if (widget)
gtk_widget_show (widget);
else
gtk_widget_show (iw_get_window(iw));
return;
}
/* Fill in the to_charge amount (only in VIEW/EDIT modes) */
{
gnc_numeric amount;
amount = gncInvoiceGetToChargeAmount (invoice);
gnc_amount_edit_set_amount (GNC_AMOUNT_EDIT (iw->to_charge_edit), amount);
}
/* Hide/show the appropriate widgets based on our posted/paid state */
{
GtkWidget *hide, *show;
if (is_posted)
{
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "posted_label"));
gtk_widget_show (show);
gtk_widget_show (iw->posted_date_hbox);
show = GTK_WIDGET (gtk_builder_get_object (iw->builder, "acct_label"));
gtk_widget_show (show);
gtk_widget_show (acct_entry);
}
else /* ! posted */
{
hide = GTK_WIDGET (gtk_builder_get_object (iw->builder, "posted_label"));
gtk_widget_hide (hide);
gtk_widget_hide (iw->posted_date_hbox);
hide = GTK_WIDGET (gtk_builder_get_object (iw->builder, "acct_label"));
gtk_widget_hide (hide);
gtk_widget_hide (acct_entry);
}
}
/* Set the toolbar widgets sensitivity */
if (iw->page)
gnc_plugin_page_invoice_update_menus(iw->page, is_posted, can_unpost);
/* Set the to_charge widget */
gtk_widget_set_sensitive (iw->to_charge_edit, !is_posted);
/* Hide the to_charge frame for all non-employee invoices,
* or set insensitive if the employee does not have a charge card
*/
if (iw->owner.type == GNC_OWNER_EMPLOYEE)
{
if (!gncEmployeeGetCCard (gncOwnerGetEmployee(&iw->owner)))
gtk_widget_set_sensitive (iw->to_charge_edit, FALSE);
}
else
{
gtk_widget_hide (iw->to_charge_frame);
}
if (is_posted)
{
// GtkWidget *hide;
/* Setup viewer for read-only access */
gtk_widget_set_sensitive (acct_entry, FALSE);
gtk_widget_set_sensitive (iw->id_entry, FALSE);
gtk_widget_set_sensitive (iw->id_entry, TRUE);
gtk_widget_set_sensitive (iw->terms_menu, FALSE);
gtk_widget_set_sensitive (iw->owner_box, FALSE);
gtk_widget_set_sensitive (iw->job_box, FALSE);
gtk_widget_set_sensitive (iw->billing_id_entry, FALSE);
gtk_widget_set_sensitive (iw->notes_text, FALSE); /* XXX: should notes remain writable?*/
}
else /* ! posted */
{
gtk_widget_set_sensitive (acct_entry, TRUE);
gtk_widget_set_sensitive (iw->terms_menu, TRUE);
gtk_widget_set_sensitive (iw->owner_box, TRUE);
gtk_widget_set_sensitive (iw->job_box, TRUE);
gtk_widget_set_sensitive (iw->billing_id_entry, TRUE);
gtk_widget_set_sensitive (iw->notes_text, TRUE);
}
/* Translators: This is a label to show whether the invoice is paid or not. */
if(gncInvoiceIsPaid (invoice))
gtk_label_set_text(GTK_LABEL(iw->paid_label), _("PAID"));
else
gtk_label_set_text(GTK_LABEL(iw->paid_label), _("UNPAID"));
if (widget)
gtk_widget_show (widget);
else
gtk_widget_show (iw_get_window(iw));
}
gchar *
gnc_invoice_get_title (InvoiceWindow *iw)
{
char *wintitle = NULL;
const char *id = NULL;
if (!iw) return NULL;
switch (gncOwnerGetType (&iw->owner))
{
case GNC_OWNER_CUSTOMER:
switch (iw->dialog_type)
{
case NEW_INVOICE:
wintitle = iw->is_credit_note ? _("New Credit Note")
: _("New Invoice");
break;
case MOD_INVOICE:
case DUP_INVOICE:
case EDIT_INVOICE:
wintitle = iw->is_credit_note ? _("Edit Credit Note")
: _("Edit Invoice");
break;
case VIEW_INVOICE:
wintitle = iw->is_credit_note ? _("View Credit Note")
: _("View Invoice");
break;
}
break;
case GNC_OWNER_VENDOR:
switch (iw->dialog_type)
{
case NEW_INVOICE:
wintitle = iw->is_credit_note ? _("New Credit Note")
: _("New Bill");
break;
case MOD_INVOICE:
case DUP_INVOICE:
case EDIT_INVOICE:
wintitle = iw->is_credit_note ? _("Edit Credit Note")
: _("Edit Bill");
break;
case VIEW_INVOICE:
wintitle = iw->is_credit_note ? _("View Credit Note")
: _("View Bill");
break;
}
break;
case GNC_OWNER_EMPLOYEE:
switch (iw->dialog_type)
{
case NEW_INVOICE:
wintitle = iw->is_credit_note ? _("New Credit Note")
: _("New Expense Voucher");
break;
case MOD_INVOICE:
case DUP_INVOICE:
case EDIT_INVOICE:
wintitle = iw->is_credit_note ? _("Edit Credit Note")
: _("Edit Expense Voucher");
break;
case VIEW_INVOICE:
wintitle = iw->is_credit_note ? _("View Credit Note")
: _("View Expense Voucher");
break;
}
break;
default:
break;
}
if (iw->id_entry)
id = gtk_entry_get_text (GTK_ENTRY (iw->id_entry));
if (id && *id)
return g_strconcat (wintitle, " - ", id, (char *)NULL);
return g_strdup (wintitle);
}
void
gnc_invoice_type_toggled_cb (GtkWidget *widget, gpointer data)
{
InvoiceWindow *iw = data;
if (!iw) return;
iw->is_credit_note = !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
}
void
gnc_invoice_id_changed_cb (GtkWidget *unused, gpointer data)
{
InvoiceWindow *iw = data;
gchar *title;
if (!iw) return;
if (iw->page)
{
gnc_plugin_page_invoice_update_title (iw->page);
}
else
{
title = gnc_invoice_get_title (iw);
gtk_window_set_title (GTK_WINDOW (iw->dialog), title);
g_free (title);
}
}
void
gnc_invoice_terms_changed_cb (GtkWidget *widget, gpointer data)
{
GtkComboBox *cbox = GTK_COMBO_BOX (widget);
InvoiceWindow *iw = data;
if (!iw) return;
if (!cbox) return;
iw->terms = gnc_simple_combo_get_value (cbox);
}
static gboolean
find_handler (gpointer find_data, gpointer user_data)
{
const GncGUID *invoice_guid = find_data;
InvoiceWindow *iw = user_data;
return(iw && guid_equal(&iw->invoice_guid, invoice_guid));
}
static InvoiceWindow *
gnc_invoice_new_page (QofBook *bookp, InvoiceDialogType type,
GncInvoice *invoice, const GncOwner *owner,
GncMainWindow *window)
{
InvoiceWindow *iw;
GncOwner *billto;
GncPluginPage *new_page;
g_assert (type != NEW_INVOICE && type != MOD_INVOICE && type != DUP_INVOICE);
g_assert (invoice != NULL);
/*
* Find an existing window for this invoice. If found, bring it to
* the front.
*/
if (invoice)
{
GncGUID invoice_guid;
invoice_guid = *gncInvoiceGetGUID (invoice);
iw = gnc_find_first_gui_component (DIALOG_VIEW_INVOICE_CM_CLASS,
find_handler, &invoice_guid);
if (iw)
{
gnc_main_window_display_page(iw->page);
return(iw);
}
}
/*
* No existing invoice window found. Build a new one.
*/
iw = g_new0 (InvoiceWindow, 1);
iw->book = bookp;
iw->dialog_type = type;
iw->invoice_guid = *gncInvoiceGetGUID (invoice);
iw->is_credit_note = gncInvoiceGetIsCreditNote (invoice);
iw->width = -1;
/* Save this for later */
gncOwnerCopy (gncOwnerGetEndOwner (owner), &(iw->owner));
gncOwnerInitJob (&(iw->job), gncOwnerGetJob (owner));
billto = gncInvoiceGetBillTo (invoice);
gncOwnerCopy (gncOwnerGetEndOwner (billto), &(iw->proj_cust));
gncOwnerInitJob (&iw->proj_job, gncOwnerGetJob (billto));
/* Now create the plugin page for this invoice and display it. */
new_page = gnc_plugin_page_invoice_new (iw);
if (window)
gnc_plugin_page_set_use_new_window (new_page, FALSE);
else
window = gnc_plugin_business_get_window ();
gnc_main_window_open_page (window, new_page);
/* Initialize the summary bar */
gnc_invoice_redraw_all_cb(iw->reg, iw);
return iw;
}
#define KEY_INVOICE_TYPE "InvoiceType"
#define KEY_INVOICE_GUID "InvoiceGUID"
#define KEY_OWNER_TYPE "OwnerType"
#define KEY_OWNER_GUID "OwnerGUID"
GncPluginPage *
gnc_invoice_recreate_page (GncMainWindow *window,
GKeyFile *key_file,
const gchar *group_name)
{
InvoiceWindow *iw;
GError *error = NULL;
char *tmp_string = NULL, *owner_type = NULL;
InvoiceDialogType type;
GncInvoice *invoice;
GncGUID guid;
QofBook *book;
GncOwner owner = { 0 };
/* Get Invoice Type */
tmp_string = g_key_file_get_string(key_file, group_name,
KEY_INVOICE_TYPE, &error);
if (error)
{
g_warning("Error reading group %s key %s: %s.",
group_name, KEY_INVOICE_TYPE, error->message);
goto give_up;
}
type = InvoiceDialogTypefromString(tmp_string);
g_free(tmp_string);
/* Get Invoice GncGUID */
tmp_string = g_key_file_get_string(key_file, group_name,
KEY_INVOICE_GUID, &error);
if (error)
{
g_warning("Error reading group %s key %s: %s.",
group_name, KEY_INVOICE_GUID, error->message);
goto give_up;
}
if (!string_to_guid(tmp_string, &guid))
{
g_warning("Invalid invoice guid: %s.", tmp_string);
goto give_up;
}
book = gnc_get_current_book();
invoice = gncInvoiceLookup(gnc_get_current_book(), &guid);
if (invoice == NULL)
{
g_warning("Can't find invoice %s in current book.", tmp_string);
goto give_up;
}
g_free(tmp_string);
tmp_string = NULL;
/* Get Owner Type */
owner_type = g_key_file_get_string(key_file, group_name,
KEY_OWNER_TYPE, &error);
if (error)
{
g_warning("Error reading group %s key %s: %s.",
group_name, KEY_OWNER_TYPE, error->message);
goto give_up;
}
/* Get Owner GncGUID */
tmp_string = g_key_file_get_string(key_file, group_name,
KEY_OWNER_GUID, &error);
if (error)
{
g_warning("Error reading group %s key %s: %s.",
group_name, KEY_OWNER_GUID, error->message);
goto give_up;
}
if (!string_to_guid(tmp_string, &guid))
{
g_warning("Invalid owner guid: %s.", tmp_string);
goto give_up;
}
if (!gncOwnerGetOwnerFromTypeGuid(book, &owner, owner_type, &guid))
{
g_warning("Can't find owner %s in current book.", tmp_string);
goto give_up;
}
g_free(tmp_string);
g_free(owner_type);
iw = gnc_invoice_new_page (book, type, invoice, &owner, window);
return iw->page;
give_up:
g_warning("Giving up on restoring '%s'.", group_name);
if (error)
g_error_free(error);
if (tmp_string)
g_free(tmp_string);
if (owner_type)
g_free(owner_type);
return NULL;
}
void
gnc_invoice_save_page (InvoiceWindow *iw,
GKeyFile *key_file,
const gchar *group_name)
{
gchar guidstr[GUID_ENCODING_LENGTH+1];
guid_to_string_buff(&iw->invoice_guid, guidstr);
g_key_file_set_string(key_file, group_name, KEY_INVOICE_TYPE,
InvoiceDialogTypeasString(iw->dialog_type));
g_key_file_set_string(key_file, group_name, KEY_INVOICE_GUID, guidstr);
if (gncOwnerGetJob (&(iw->job)))
{
g_key_file_set_string(key_file, group_name, KEY_OWNER_TYPE,
qofOwnerGetType(&iw->job));
guid_to_string_buff(gncOwnerGetGUID(&iw->job), guidstr);
g_key_file_set_string(key_file, group_name, KEY_OWNER_GUID, guidstr);
}
else
{
g_key_file_set_string(key_file, group_name, KEY_OWNER_TYPE,
qofOwnerGetType(&iw->owner));
guid_to_string_buff(gncOwnerGetGUID(&iw->owner), guidstr);
g_key_file_set_string(key_file, group_name, KEY_OWNER_GUID, guidstr);
}
}
GtkWidget *
gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
{
GncInvoice *invoice;
GtkBuilder *builder;
GtkWidget *dialog, *hbox;
GncEntryLedger *entry_ledger = NULL;
GncOwnerType owner_type;
GncEntryLedgerType ledger_type;
const gchar *prefs_group = NULL;
gboolean is_credit_note = FALSE;
invoice = gncInvoiceLookup (iw->book, &iw->invoice_guid);
is_credit_note = gncInvoiceGetIsCreditNote (invoice);
iw->page = page;
/* Find the dialog */
iw->builder = builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "dialog-invoice.glade", "terms_store");
gnc_builder_add_from_file (builder, "dialog-invoice.glade", "invoice_entry_vbox");
dialog = GTK_WIDGET (gtk_builder_get_object (builder, "invoice_entry_vbox"));
// Set the style context for this dialog so it can be easily manipulated with css
gnc_widget_set_style_context (GTK_WIDGET(dialog), "GncInvoiceDialog");
/* Autoconnect all the signals */
gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, iw);
/* Grab the widgets */
iw->id_label = GTK_WIDGET (gtk_builder_get_object (builder, "label3"));
iw->type_label = GTK_WIDGET (gtk_builder_get_object (builder, "page_type_label"));
iw->info_label = GTK_WIDGET (gtk_builder_get_object (builder, "label25"));
iw->id_entry = GTK_WIDGET (gtk_builder_get_object (builder, "page_id_entry"));
iw->billing_id_entry = GTK_WIDGET (gtk_builder_get_object (builder, "page_billing_id_entry"));
iw->terms_menu = GTK_WIDGET (gtk_builder_get_object (builder, "page_terms_menu"));
iw->notes_text = GTK_WIDGET (gtk_builder_get_object (builder, "page_notes_text"));
iw->active_check = GTK_WIDGET (gtk_builder_get_object (builder, "active_check"));
iw->owner_box = GTK_WIDGET (gtk_builder_get_object (builder, "page_owner_hbox"));
iw->owner_label = GTK_WIDGET (gtk_builder_get_object (builder, "page_owner_label"));
iw->job_label = GTK_WIDGET (gtk_builder_get_object (builder, "page_job_label"));
iw->job_box = GTK_WIDGET (gtk_builder_get_object (builder, "page_job_hbox"));
iw->paid_label = GTK_WIDGET (gtk_builder_get_object (builder, "paid_label"));
// Set the style context for this label so it can be easily manipulated with css
gnc_widget_style_context_add_class (GTK_WIDGET(iw->paid_label), "gnc-class-highlight");
/* grab the project widgets */
iw->proj_frame = GTK_WIDGET (gtk_builder_get_object (builder, "page_proj_frame"));
iw->proj_cust_box = GTK_WIDGET (gtk_builder_get_object (builder, "page_proj_cust_hbox"));
iw->proj_job_box = GTK_WIDGET (gtk_builder_get_object (builder, "page_proj_job_hbox"));
/* grab the to_charge widgets */
{
GtkWidget *edit;
gnc_commodity *currency = gncInvoiceGetCurrency (invoice);
GNCPrintAmountInfo print_info;
iw->to_charge_frame = GTK_WIDGET (gtk_builder_get_object (builder, "to_charge_frame"));
edit = gnc_amount_edit_new();
print_info = gnc_commodity_print_info (currency, FALSE);
gnc_amount_edit_set_evaluate_on_enter (GNC_AMOUNT_EDIT (edit), TRUE);
gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT (edit), print_info);
gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT (edit),
gnc_commodity_get_fraction (currency));
iw->to_charge_edit = edit;
gtk_widget_show (edit);
hbox = GTK_WIDGET (gtk_builder_get_object (builder, "to_charge_box"));
gtk_box_pack_start (GTK_BOX (hbox), edit, TRUE, TRUE, 0);
g_signal_connect(G_OBJECT(gnc_amount_edit_gtk_entry(GNC_AMOUNT_EDIT(edit))),
"focus-out-event",
G_CALLBACK(gnc_invoice_window_leave_to_charge_cb), iw);
g_signal_connect(G_OBJECT(edit), "amount_changed",
G_CALLBACK(gnc_invoice_window_changed_to_charge_cb), iw);
}
hbox = GTK_WIDGET (gtk_builder_get_object (builder, "page_date_opened_hbox"));
iw->opened_date = gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE);
gtk_widget_show(iw->opened_date);
gtk_box_pack_start (GTK_BOX(hbox), iw->opened_date, TRUE, TRUE, 0);
iw->posted_date_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "date_posted_hbox"));
iw->posted_date = gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE);
gtk_widget_show(iw->posted_date);
gtk_box_pack_start (GTK_BOX(iw->posted_date_hbox), iw->posted_date,
TRUE, TRUE, 0);
/* Make the opened and posted dates insensitive in this window */
gtk_widget_set_sensitive (iw->opened_date, FALSE);
gtk_widget_set_sensitive (iw->posted_date, FALSE);
/* Also the invoice ID */
gtk_widget_set_sensitive (iw->id_entry, FALSE);
/* Build the ledger */
ledger_type = GNCENTRY_INVOICE_VIEWER;
owner_type = gncOwnerGetType (&iw->owner);
switch (iw->dialog_type)
{
case EDIT_INVOICE:
switch (owner_type)
{
case GNC_OWNER_CUSTOMER:
ledger_type = is_credit_note ? GNCENTRY_CUST_CREDIT_NOTE_ENTRY
: GNCENTRY_INVOICE_ENTRY;
break;
case GNC_OWNER_VENDOR:
ledger_type = is_credit_note ? GNCENTRY_VEND_CREDIT_NOTE_ENTRY
: GNCENTRY_BILL_ENTRY;
break;
case GNC_OWNER_EMPLOYEE:
ledger_type = is_credit_note ? GNCENTRY_EMPL_CREDIT_NOTE_ENTRY
: GNCENTRY_EXPVOUCHER_ENTRY;
break;
default:
g_warning ("Invalid owner type");
break;
}
break;
case VIEW_INVOICE:
default:
switch (owner_type)
{
case GNC_OWNER_CUSTOMER:
ledger_type = is_credit_note ? GNCENTRY_CUST_CREDIT_NOTE_VIEWER
: GNCENTRY_INVOICE_VIEWER;
prefs_group = GNC_PREFS_GROUP_INVOICE;
break;
case GNC_OWNER_VENDOR:
ledger_type = is_credit_note ? GNCENTRY_VEND_CREDIT_NOTE_VIEWER
: GNCENTRY_BILL_VIEWER;
prefs_group = GNC_PREFS_GROUP_BILL;
break;
case GNC_OWNER_EMPLOYEE:
ledger_type = is_credit_note ? GNCENTRY_EMPL_CREDIT_NOTE_VIEWER
: GNCENTRY_EXPVOUCHER_VIEWER;
prefs_group = GNC_PREFS_GROUP_BILL;
break;
default:
g_warning ("Invalid owner type");
break;
}
break;
}
/* Default labels are for invoices, change them if they are anything else. */
switch (owner_type)
{
case GNC_OWNER_VENDOR:
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Bill Information"));
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Bill ID"));
break;
case GNC_OWNER_EMPLOYEE:
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Voucher Information"));
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Voucher"));
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Voucher ID"));
default:
break;
}
entry_ledger = gnc_entry_ledger_new (iw->book, ledger_type);
/* Save the ledger... */
iw->ledger = entry_ledger;
/* window will be updated in a callback */
/* Set the entry_ledger's invoice */
gnc_entry_ledger_set_default_invoice (entry_ledger, invoice);
/* Set the preferences group */
gnc_entry_ledger_set_prefs_group (entry_ledger, prefs_group);
/* Setup initial values */
iw->component_id =
gnc_register_gui_component (DIALOG_VIEW_INVOICE_CM_CLASS,
gnc_invoice_window_refresh_handler,
gnc_invoice_window_close_handler,
iw);
gnc_gui_component_watch_entity_type (iw->component_id,
GNC_INVOICE_MODULE_NAME,
QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
/* Create the register */
{
GtkWidget *regWidget, *frame, *window;
/* Watch the order of operations, here... */
regWidget = gnucash_register_new (gnc_entry_ledger_get_table
(entry_ledger), NULL);
gtk_widget_show(regWidget);
frame = GTK_WIDGET (gtk_builder_get_object (builder, "ledger_frame"));
gtk_container_add (GTK_CONTAINER (frame), regWidget);
iw->reg = GNUCASH_REGISTER (regWidget);
window = gnc_plugin_page_get_window(iw->page);
gnucash_sheet_set_window (gnucash_register_get_sheet (iw->reg), window);
g_signal_connect (G_OBJECT (regWidget), "activate_cursor",
G_CALLBACK (gnc_invoice_window_recordCB), iw);
g_signal_connect (G_OBJECT (regWidget), "redraw_all",
G_CALLBACK (gnc_invoice_redraw_all_cb), iw);
}
gnc_table_realize_gui (gnc_entry_ledger_get_table (entry_ledger));
/* Now fill in a lot of the pieces and display properly */
gnc_invoice_update_window (iw, dialog);
gnc_table_refresh_gui (gnc_entry_ledger_get_table (iw->ledger), TRUE);
/* Show the dialog */
// gtk_widget_show_all (dialog);
return dialog;
}
static InvoiceWindow *
gnc_invoice_window_new_invoice (GtkWindow *parent, InvoiceDialogType dialog_type, QofBook *bookp,
const GncOwner *owner, GncInvoice *invoice)
{
InvoiceWindow *iw;
GtkBuilder *builder;
GtkWidget *hbox;
GtkWidget *invoice_radio;
GncOwner *billto;
const GncOwner *start_owner;
GncBillTerm *owner_terms = NULL;
GncOwnerType owner_type;
g_assert (dialog_type == NEW_INVOICE || dialog_type == MOD_INVOICE || dialog_type == DUP_INVOICE);
if (invoice)
{
/*
* Try to find an existing window for this invoice. If found,
* bring it to the front.
*/
GncGUID invoice_guid;
invoice_guid = *gncInvoiceGetGUID (invoice);
iw = gnc_find_first_gui_component (DIALOG_NEW_INVOICE_CM_CLASS,
find_handler, &invoice_guid);
if (iw)
{
gtk_window_set_transient_for (GTK_WINDOW(iw->dialog), parent);
gtk_window_present (GTK_WINDOW(iw->dialog));
return(iw);
}
}
/*
* No existing invoice window found. Build a new one.
*/
iw = g_new0 (InvoiceWindow, 1);
iw->dialog_type = dialog_type;
switch (dialog_type)
{
case NEW_INVOICE:
g_assert (bookp);
invoice = gncInvoiceCreate (bookp);
gncInvoiceSetCurrency (invoice, gnc_default_currency ());
iw->book = bookp;
start_owner = owner;
switch (gncOwnerGetType (gncOwnerGetEndOwner (owner)))
{
case GNC_OWNER_CUSTOMER:
owner_terms = gncCustomerGetTerms (gncOwnerGetCustomer (gncOwnerGetEndOwner (owner)));
break;
case GNC_OWNER_VENDOR:
owner_terms = gncVendorGetTerms (gncOwnerGetVendor (gncOwnerGetEndOwner (owner)));
break;
default:
break;
}
if (owner_terms)
gncInvoiceSetTerms (invoice, owner_terms);
break;
case MOD_INVOICE:
case DUP_INVOICE:
start_owner = gncInvoiceGetOwner (invoice);
iw->book = gncInvoiceGetBook (invoice);
break;
default:
/* The assert at the beginning of this function should prevent this switch case ! */
return NULL;
}
/* Save this for later */
gncOwnerCopy (gncOwnerGetEndOwner(start_owner), &(iw->owner));
gncOwnerInitJob (&(iw->job), gncOwnerGetJob (start_owner));
billto = gncInvoiceGetBillTo (invoice);
gncOwnerCopy (gncOwnerGetEndOwner (billto), &(iw->proj_cust));
gncOwnerInitJob (&iw->proj_job, gncOwnerGetJob (billto));
/* Find the glade page layout */
iw->builder = builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "dialog-invoice.glade", "terms_store");
gnc_builder_add_from_file (builder, "dialog-invoice.glade", "new_invoice_dialog");
iw->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "new_invoice_dialog"));
gtk_window_set_transient_for (GTK_WINDOW(iw->dialog), parent);
// Set the style context for this dialog so it can be easily manipulated with css
gnc_widget_set_style_context (GTK_WIDGET(iw->dialog), "GncInvoiceDialog");
g_object_set_data (G_OBJECT (iw->dialog), "dialog_info", iw);
/* Grab the widgets */
iw->type_label = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_type_label"));
iw->id_label = GTK_WIDGET (gtk_builder_get_object (builder, "label14"));
iw->info_label = GTK_WIDGET (gtk_builder_get_object (builder, "label1"));
invoice_radio = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_invoice_type"));
iw->type_hbox = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_type_choice_hbox"));
iw->type_choice = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_type_invoice"));
/* The default GUI labels are for invoices, so change them if it isn't. */
owner_type = gncOwnerGetType (&iw->owner);
switch(owner_type)
{
case GNC_OWNER_VENDOR:
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Bill Information"));
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Bill"));
gtk_button_set_label (GTK_BUTTON(invoice_radio), _("Bill"));
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Bill ID"));
break;
case GNC_OWNER_EMPLOYEE:
gtk_label_set_text (GTK_LABEL(iw->info_label), _("Voucher Information"));
gtk_label_set_text (GTK_LABEL(iw->type_label), _("Voucher"));
gtk_button_set_label (GTK_BUTTON(invoice_radio), _("Voucher"));
gtk_label_set_text (GTK_LABEL(iw->id_label), _("Voucher ID"));
default:
break;
}
/* configure the type related widgets based on dialog type and invoice type */
switch (dialog_type)
{
case NEW_INVOICE:
case DUP_INVOICE:
gtk_widget_show_all (iw->type_hbox);
gtk_widget_hide (iw->type_label);
break;
case MOD_INVOICE:
gtk_widget_hide (iw->type_hbox);
gtk_widget_show (iw->type_label);
break;
default:
break;
}
if (dialog_type == DUP_INVOICE)
{
GtkWidget *cn_radio = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_creditnote_type"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(cn_radio), gncInvoiceGetIsCreditNote (invoice));
}
iw->id_entry = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_id_entry"));
iw->billing_id_entry = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_billing_id_entry"));
iw->terms_menu = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_terms_menu"));
iw->notes_text = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_notes_text"));
iw->owner_box = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_owner_hbox"));
iw->owner_label = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_owner_label"));
iw->job_label = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_job_label"));
iw->job_box = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_job_hbox"));
/* Grab the project widgets */
iw->proj_frame = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_proj_frame"));
iw->proj_cust_box = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_proj_cust_hbox"));
iw->proj_job_box = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_proj_job_hbox"));
hbox = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_date_opened_hbox"));
iw->opened_date = gnc_date_edit_new (gnc_time (NULL), FALSE, FALSE);
gtk_widget_show(iw->opened_date);
gtk_box_pack_start (GTK_BOX(hbox), iw->opened_date, TRUE, TRUE, 0);
/* If this is a New Invoice, reset the Notes file to read/write */
gtk_widget_set_sensitive (iw->notes_text,
(iw->dialog_type == NEW_INVOICE) ||
(iw->dialog_type == DUP_INVOICE));
/* Setup signals */
gtk_builder_connect_signals_full( builder,
gnc_builder_connect_full_func,
iw);
/* Setup initial values */
iw->invoice_guid = *gncInvoiceGetGUID (invoice);
iw->is_credit_note = gncInvoiceGetIsCreditNote (invoice);
iw->component_id =
gnc_register_gui_component (DIALOG_NEW_INVOICE_CM_CLASS,
gnc_invoice_window_refresh_handler,
gnc_invoice_dialog_close_handler,
iw);
gnc_gui_component_watch_entity_type (iw->component_id,
GNC_INVOICE_MODULE_NAME,
QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
/* Now fill in a lot of the pieces and display properly */
switch(dialog_type)
{
case NEW_INVOICE:
case MOD_INVOICE:
case DUP_INVOICE:
gnc_billterms_combo (GTK_COMBO_BOX(iw->terms_menu), iw->book, TRUE, iw->terms);
break;
case EDIT_INVOICE:
case VIEW_INVOICE:
// Fill in the invoice view version
if(gncBillTermGetName (iw->terms) != NULL)
gtk_entry_set_text (GTK_ENTRY (iw->terms_menu),gncBillTermGetName (iw->terms));
else
gtk_entry_set_text (GTK_ENTRY (iw->terms_menu),"None");
break;
}
gnc_invoice_update_window (iw, iw->dialog);
gnc_table_refresh_gui (gnc_entry_ledger_get_table (iw->ledger), TRUE);
// The customer choice widget should have keyboard focus
if (GNC_IS_GENERAL_SEARCH(iw->owner_choice))
{
gnc_general_search_grab_focus(GNC_GENERAL_SEARCH(iw->owner_choice));
}
return iw;
}
InvoiceWindow *
gnc_ui_invoice_edit (GtkWindow *parent, GncInvoice *invoice)
{
InvoiceWindow *iw;
InvoiceDialogType type;
if (!invoice) return NULL;
/* Immutable once we've been posted */
if (gncInvoiceGetPostedAcc (invoice))
type = VIEW_INVOICE;
else
type = EDIT_INVOICE;
iw = gnc_invoice_new_page (gncInvoiceGetBook(invoice), type,
invoice, gncInvoiceGetOwner (invoice),
GNC_MAIN_WINDOW(gnc_ui_get_main_window (GTK_WIDGET (parent))));
return iw;
}
static InvoiceWindow *
gnc_ui_invoice_modify (GtkWindow *parent, GncInvoice *invoice)
{
InvoiceWindow *iw;
if (!invoice) return NULL;
iw = gnc_invoice_window_new_invoice (parent, MOD_INVOICE, NULL, NULL, invoice);
return iw;
}
InvoiceWindow * gnc_ui_invoice_duplicate (GtkWindow *parent, GncInvoice *old_invoice, gboolean open_properties, const GDate *new_date)
{
InvoiceWindow *iw = NULL;
GncInvoice *new_invoice = NULL;
time64 entry_date;
g_assert(old_invoice);
// Create a deep copy of the old invoice
new_invoice = gncInvoiceCopy(old_invoice);
// The new invoice is for sure active
gncInvoiceSetActive(new_invoice, TRUE);
// and unposted
if (gncInvoiceIsPosted (new_invoice))
{
gboolean result = gncInvoiceUnpost(new_invoice, TRUE);
if (!result)
{
g_warning("Oops, error when unposting the copied invoice; ignoring.");
}
}
// Unset the invoice ID, let it get allocated later
gncInvoiceSetID(new_invoice, "");
// Modify the date to today
if (new_date)
entry_date = gnc_time64_get_day_neutral (gdate_to_time64 (*new_date));
else
entry_date = gnc_time64_get_day_neutral (gnc_time (NULL));
gncInvoiceSetDateOpened(new_invoice, entry_date);
// Also modify the date of all entries to today
//g_warning("We have %d entries", g_list_length(gncInvoiceGetEntries(new_invoice)));
g_list_foreach(gncInvoiceGetEntries(new_invoice),
&set_gncEntry_date, &entry_date);
if (open_properties)
{
// Open the "properties" pop-up for the invoice...
iw = gnc_invoice_window_new_invoice (parent, DUP_INVOICE, NULL, NULL, new_invoice);
}
else
{
// Open the newly created invoice in the "edit" window
iw = gnc_ui_invoice_edit (parent, new_invoice);
// Check the ID; set one if necessary
if (g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (iw->id_entry)), "") == 0)
{
gncInvoiceSetID (new_invoice, gncInvoiceNextID(iw->book, &(iw->owner)));
}
}
return iw;
}
InvoiceWindow *
gnc_ui_invoice_new (GtkWindow *parent, GncOwner *owner, QofBook *book)
{
InvoiceWindow *iw;
GncOwner inv_owner;
if (owner)
{
gncOwnerCopy (owner, &inv_owner);
}
else
gncOwnerInitCustomer (&inv_owner, NULL); /* XXX: pass in the owner type? */
/* Make sure required options exist */
if (!book) return NULL;
iw = gnc_invoice_window_new_invoice (parent, NEW_INVOICE, book, &inv_owner, NULL);
return iw;
}
/* Functions for invoice selection widgets */
static void
edit_invoice_direct (GtkWindow *dialog, gpointer invoice, gpointer user_data)
{
g_return_if_fail (invoice);
gnc_ui_invoice_edit (gnc_ui_get_main_window (GTK_WIDGET (dialog)), invoice);
}
static void
edit_invoice_cb (GtkWindow *dialog, gpointer inv, gpointer user_data)
{
GncInvoice *invoice = inv;
g_return_if_fail (invoice && user_data);
edit_invoice_direct (dialog, invoice, user_data);
}
struct multi_edit_invoice_data
{
gpointer user_data;
GtkWindow *parent;
};
static void
multi_edit_invoice_one (gpointer inv, gpointer user_data)
{
struct multi_edit_invoice_data *meid = user_data;
edit_invoice_cb (meid->parent, inv, meid->user_data);
}
static void
multi_edit_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_data)
{
struct multi_edit_invoice_data meid;
meid.user_data = user_data;
meid.parent = dialog;
g_list_foreach (invoice_list, multi_edit_invoice_one, &meid);
}
static void
pay_invoice_direct (GtkWindow *dialog, gpointer inv, gpointer user_data)
{
GncInvoice *invoice = inv;
g_return_if_fail (invoice);
gnc_ui_payment_new_with_invoice (dialog, gncInvoiceGetOwner (invoice),
gncInvoiceGetBook (invoice), invoice);
}
static void
pay_invoice_cb (GtkWindow *dialog, gpointer *invoice_p, gpointer user_data)
{
g_return_if_fail (invoice_p && user_data);
if (! *invoice_p)
return;
pay_invoice_direct (dialog, *invoice_p, user_data);
}
struct multi_duplicate_invoice_data
{
GDate date;
GtkWindow *parent;
};
static void multi_duplicate_invoice_one(gpointer data, gpointer user_data)
{
GncInvoice *old_invoice = data;
struct multi_duplicate_invoice_data *dup_user_data = user_data;
g_assert(dup_user_data);
if (old_invoice)
{
GncInvoice *new_invoice;
// In this simplest form, we just use the existing duplication
// algorithm, only without opening the "edit invoice" window for editing
// the number etc. for each of the invoices.
InvoiceWindow *iw = gnc_ui_invoice_duplicate(dup_user_data->parent, old_invoice, FALSE, &dup_user_data->date);
// FIXME: Now we could use this invoice and manipulate further data.
g_assert(iw);
new_invoice = iw_get_invoice(iw);
g_assert(new_invoice);
}
}
static void
multi_duplicate_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_data)
{
g_return_if_fail (invoice_list);
switch (g_list_length(invoice_list))
{
case 0:
return;
case 1:
{
// Duplicate exactly one invoice
GncInvoice *old_invoice = invoice_list->data;
gnc_ui_invoice_duplicate(dialog, old_invoice, TRUE, NULL);
return;
}
default:
{
// Duplicate multiple invoices. We ask for a date first.
struct multi_duplicate_invoice_data dup_user_data;
gboolean dialog_ok;
// Default date: Today
gnc_gdate_set_time64(&dup_user_data.date, gnc_time (NULL));
dup_user_data.parent = dialog;
dialog_ok = gnc_dup_date_dialog (GTK_WIDGET(dialog), _("Date of duplicated entries"), &dup_user_data.date);
if (!dialog_ok)
{
// User pressed cancel, so don't duplicate anything here.
return;
}
// Note: If we want to have a more sophisticated duplication, we might want
// to ask for particular data right here, then insert this data upon
// duplication.
g_list_foreach(invoice_list, multi_duplicate_invoice_one, &dup_user_data);
return;
}
}
}
static void post_one_invoice_cb(gpointer data, gpointer user_data)
{
GncInvoice *invoice = data;
struct post_invoice_params *post_params = user_data;
InvoiceWindow *iw = gnc_ui_invoice_edit(post_params->parent, invoice);
gnc_invoice_post(iw, post_params);
}
static void gnc_invoice_is_posted(gpointer inv, gpointer test_value)
{
GncInvoice *invoice = inv;
gboolean *test = (gboolean*)test_value;
if (gncInvoiceIsPosted (invoice))
{
*test = TRUE;
}
}
static void
multi_post_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_data)
{
struct post_invoice_params post_params;
gboolean test;
InvoiceWindow *iw;
if (g_list_length(invoice_list) == 0)
return;
// Get the posting parameters for these invoices
iw = gnc_ui_invoice_edit(dialog, invoice_list->data);
test = FALSE;
gnc_suspend_gui_refresh (); // Turn off GUI refresh for the duration.
// Check if any of the selected invoices have already been posted.
g_list_foreach(invoice_list, gnc_invoice_is_posted, &test);
gnc_resume_gui_refresh ();
if (test)
{
gnc_error_dialog (GTK_WINDOW (iw_get_window(iw)), "%s",
_("One or more selected invoices have already been posted.\nRe-check your selection."));
return;
}
if (!gnc_dialog_post_invoice(iw, _("Do you really want to post these invoices?"),
&post_params.ddue, &post_params.postdate,
&post_params.memo, &post_params.acc,
&post_params.accumulate))
return;
post_params.parent = dialog;
// Turn off GUI refresh for the duration. This is more than just an
// optimization. If the search that got us here is based on the "posted"
// status of an invoice, the updating the GUI will change the list we're
// working on which leads to bad things happening.
gnc_suspend_gui_refresh ();
g_list_foreach(invoice_list, post_one_invoice_cb, &post_params);
gnc_resume_gui_refresh ();
}
static void print_one_invoice_cb(GtkWindow *dialog, gpointer data, gpointer user_data)
{
GncInvoice *invoice = data;
gnc_invoice_window_print_invoice (dialog, invoice);
}
static void
multi_print_invoice_one (gpointer data, gpointer user_data)
{
struct multi_edit_invoice_data *meid = user_data;
print_one_invoice_cb (gnc_ui_get_main_window (GTK_WIDGET(meid->parent)), data, meid->user_data);
}
static void
multi_print_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_data)
{
struct multi_edit_invoice_data meid;
if (g_list_length(invoice_list) == 0)
return;
meid.user_data = user_data;
meid.parent = dialog;
g_list_foreach (invoice_list, multi_print_invoice_one, &meid);
}
static gpointer
new_invoice_cb (GtkWindow *dialog, gpointer user_data)
{
struct _invoice_select_window *sw = user_data;
InvoiceWindow *iw;
g_return_val_if_fail (user_data, NULL);
iw = gnc_ui_invoice_new (dialog, sw->owner, sw->book);
return iw_get_invoice (iw);
}
static void
free_invoice_cb (gpointer user_data)
{
struct _invoice_select_window *sw = user_data;
g_return_if_fail (sw);
qof_query_destroy (sw->q);
g_free (sw);
}
GNCSearchWindow *
gnc_invoice_search (GtkWindow *parent, GncInvoice *start, GncOwner *owner, QofBook *book)
{
QofIdType type = GNC_INVOICE_MODULE_NAME;
struct _invoice_select_window *sw;
QofQuery *q, *q2 = NULL;
GncOwnerType owner_type = GNC_OWNER_CUSTOMER;
static GList *inv_params = NULL, *bill_params = NULL, *emp_params = NULL, *params;
static GList *columns = NULL;
const gchar *title, *label, *style_class;
static GNCSearchCallbackButton *buttons;
static GNCSearchCallbackButton inv_buttons[] =
{
{ N_("View/Edit Invoice"), NULL, multi_edit_invoice_cb, TRUE},
{ N_("Process Payment"), pay_invoice_cb, NULL, FALSE},
{ N_("Duplicate"), NULL, multi_duplicate_invoice_cb, FALSE},
{ N_("Post"), NULL, multi_post_invoice_cb, FALSE},
{ N_("Printable Report"), NULL, multi_print_invoice_cb, TRUE},
{ NULL },
};
static GNCSearchCallbackButton bill_buttons[] =
{
{ N_("View/Edit Bill"), NULL, multi_edit_invoice_cb, TRUE},
{ N_("Process Payment"), pay_invoice_cb, NULL, FALSE},
{ N_("Duplicate"), NULL, multi_duplicate_invoice_cb, FALSE},
{ N_("Post"), NULL, multi_post_invoice_cb, FALSE},
{ N_("Printable Report"), NULL, multi_print_invoice_cb, TRUE},
{ NULL },
};
static GNCSearchCallbackButton emp_buttons[] =
{
/* Translators: The terms 'Voucher' and 'Expense Voucher' are used
interchangeably in gnucash and mean the same thing. */
{ N_("View/Edit Voucher"), NULL, multi_edit_invoice_cb, TRUE},
{ N_("Process Payment"), pay_invoice_cb, NULL, FALSE},
{ N_("Duplicate"), NULL, multi_duplicate_invoice_cb, FALSE},
{ N_("Post"), NULL, multi_post_invoice_cb, FALSE},
{ N_("Printable Report"), NULL, multi_print_invoice_cb, TRUE},
{ NULL },
};
g_return_val_if_fail (book, NULL);
/* Build parameter list in reverse order */
if (inv_params == NULL)
{
inv_params = gnc_search_param_prepend (inv_params,
_("Invoice Owner"), NULL, type,
INVOICE_OWNER, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Invoice Notes"), NULL, type,
INVOICE_NOTES, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Billing ID"), NULL, type,
INVOICE_BILLINGID, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Is Paid?"), NULL, type,
INVOICE_IS_PAID, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Date Posted"), NULL, type,
INVOICE_POSTED, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Is Posted?"), NULL, type,
INVOICE_IS_POSTED, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Date Opened"), NULL, type,
INVOICE_OPENED, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Due Date"), NULL, type,
INVOICE_DUE, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Company Name "), NULL, type,
INVOICE_OWNER, OWNER_PARENT,
OWNER_NAME, NULL);
inv_params = gnc_search_param_prepend (inv_params,
_("Invoice ID"), NULL, type,
INVOICE_ID, NULL);
}
if (bill_params == NULL)
{
bill_params = gnc_search_param_prepend (bill_params,
_("Bill Owner"), NULL, type,
INVOICE_OWNER, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Bill Notes"), NULL, type,
INVOICE_NOTES, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Billing ID"), NULL, type,
INVOICE_BILLINGID, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Is Paid?"), NULL, type,
INVOICE_IS_PAID, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Date Posted"), NULL, type,
INVOICE_POSTED, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Is Posted?"), NULL, type,
INVOICE_IS_POSTED, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Date Opened"), NULL, type,
INVOICE_OPENED, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Due Date"), NULL, type,
INVOICE_DUE, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Company Name "), NULL, type,
INVOICE_OWNER, OWNER_PARENT,
OWNER_NAME, NULL);
bill_params = gnc_search_param_prepend (bill_params,
_("Bill ID"), NULL, type,
INVOICE_ID, NULL);
}
if (emp_params == NULL)
{
emp_params = gnc_search_param_prepend (emp_params,
_("Voucher Owner"), NULL, type,
INVOICE_OWNER, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Voucher Notes"), NULL, type,
INVOICE_NOTES, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Billing ID"), NULL, type,
INVOICE_BILLINGID, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Is Paid?"), NULL, type,
INVOICE_IS_PAID, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Date Posted"), NULL, type,
INVOICE_POSTED, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Is Posted?"), NULL, type,
INVOICE_IS_POSTED, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Date Opened"), NULL, type,
INVOICE_OPENED, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Due Date"), NULL, type,
INVOICE_DUE, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Employee Name"), NULL, type,
INVOICE_OWNER, OWNER_PARENT,
OWNER_NAME, NULL);
emp_params = gnc_search_param_prepend (emp_params,
_("Voucher ID"), NULL, type,
INVOICE_ID, NULL);
}
/* Build the column list in reverse order */
if (columns == NULL)
{
columns = gnc_search_param_prepend (columns, _("Billing ID"), NULL, type,
INVOICE_BILLINGID, NULL);
columns = gnc_search_param_prepend (columns, _("Type"), NULL, type,
INVOICE_TYPE_STRING, NULL);
columns = gnc_search_param_prepend_with_justify (columns, _("Paid"),
GTK_JUSTIFY_CENTER, NULL, type,
INVOICE_IS_PAID, NULL);
columns = gnc_search_param_prepend (columns, _("Posted"), NULL, type,
INVOICE_POSTED, NULL);
columns = gnc_search_param_prepend (columns, _("Company"), NULL, type,
INVOICE_OWNER, OWNER_PARENT,
OWNER_NAME, NULL);
columns = gnc_search_param_prepend (columns, _("Due"), NULL, type,
INVOICE_DUE, NULL);
columns = gnc_search_param_prepend (columns, _("Opened"), NULL, type,
INVOICE_OPENED, NULL);
columns = gnc_search_param_prepend (columns, _("Num"), NULL, type,
INVOICE_ID, NULL);
}
/* Build the queries */
q = qof_query_create_for (type);
qof_query_set_book (q, book);
/* If owner is supplied, limit all searches to invoices who's owner
* or end-owner is the supplied owner! Show all invoices by this
* owner. If a Job is supplied, search for all invoices for that
* job, but if a Customer is supplied, search for all invoices owned
* by that Customer or any of that Customer's Jobs. In other words,
* match on <supplied-owner's guid> == Invoice->Owner->GncGUID or
* Invoice->owner->parentGUID.
*/
if (owner)
{
/* First, figure out the type of owner here.. */
owner_type = gncOwnerGetType (gncOwnerGetEndOwner (owner));
/* Then if there's an actual owner add it to the query
* and limit the search to this owner
* If there's only a type, limit the search to this type.
*/
if (gncOwnerGetGUID (owner))
{
q2 = qof_query_create ();
qof_query_add_guid_match (q2, g_slist_prepend
(g_slist_prepend (NULL, QOF_PARAM_GUID),
INVOICE_OWNER),
gncOwnerGetGUID (owner), QOF_QUERY_OR);
qof_query_add_guid_match (q2, g_slist_prepend
(g_slist_prepend (NULL, OWNER_PARENTG),
INVOICE_OWNER),
gncOwnerGetGUID (owner), QOF_QUERY_OR);
qof_query_merge_in_place (q, q2, QOF_QUERY_AND);
qof_query_destroy (q2);
/* Use this base query as pre-fill query.
* This will pre-fill the search dialog with the query results
*/
q2 = qof_query_copy (q);
}
else
{
QofQuery *q3 = qof_query_create ();
QofQueryPredData *inv_type_pred = NULL;
GList *type_list = NULL, *node = NULL;
type_list = gncInvoiceGetTypeListForOwnerType(owner_type);
for (node = type_list; node; node = node->next)
{
inv_type_pred = qof_query_int32_predicate(QOF_COMPARE_EQUAL,
GPOINTER_TO_INT(node->data));
qof_query_add_term (q3, g_slist_prepend (NULL, INVOICE_TYPE), inv_type_pred, QOF_QUERY_OR);
}
qof_query_merge_in_place (q, q3, QOF_QUERY_AND);
qof_query_destroy (q3);
/* Don't set a pre-fill query in this case, the result set would be too long */
q2 = NULL;
}
}
/* Launch select dialog and return the result */
sw = g_new0 (struct _invoice_select_window, 1);
if (owner)
{
gncOwnerCopy (owner, &(sw->owner_def));
sw->owner = &(sw->owner_def);
}
sw->book = book;
sw->q = q;
switch (owner_type)
{
case GNC_OWNER_VENDOR:
title = _("Find Bill");
label = _("Bill");
style_class = "GncFindBillDialog";
params = bill_params;
buttons = bill_buttons;
break;
case GNC_OWNER_EMPLOYEE:
title = _("Find Expense Voucher");
label = _("Expense Voucher");
style_class = "GncFindExpenseVoucherDialog";
params = emp_params;
buttons = emp_buttons;
break;
default:
title = _("Find Invoice");
label = _("Invoice");
style_class = "GncFindInvoiceDialog";
params = inv_params;
buttons = inv_buttons;
break;
}
return gnc_search_dialog_create (parent, type, title, params, columns, q, q2,
buttons, NULL, new_invoice_cb,
sw, free_invoice_cb, GNC_PREFS_GROUP_SEARCH,
label, style_class);
}
DialogQueryView *
gnc_invoice_show_docs_due (GtkWindow *parent, QofBook *book, double days_in_advance, GncWhichDueType duetype)
{
QofIdType type = GNC_INVOICE_MODULE_NAME;
Query *q;
QofQueryPredData* pred_data;
time64 end_date;
GList *res;
gchar *message, *title;
DialogQueryView *dialog;
gint len;
static GList *param_list = NULL;
static GNCDisplayViewButton vendorbuttons[] =
{
{ N_("View/Edit Bill"), edit_invoice_direct },
{ N_("Process Payment"), pay_invoice_direct },
{ NULL },
};
static GNCDisplayViewButton customerbuttons[] =
{
{ N_("View/Edit Invoice"), edit_invoice_direct },
{ N_("Process Payment"), pay_invoice_direct },
{ NULL },
};
/* Create the param list (in reverse order) */
if (param_list == NULL)
{
/* Translators: This abbreviation is the column heading for
the condition "Is this invoice a Credit Note?" */
param_list = gnc_search_param_prepend (param_list, _("CN?"), NULL, type,
INVOICE_IS_CN, NULL);
param_list = gnc_search_param_prepend (param_list, _("Amount"), NULL, type,
INVOICE_POST_LOT, LOT_BALANCE, NULL);
param_list = gnc_search_param_prepend (param_list, _("Company"), NULL, type,
INVOICE_OWNER, OWNER_NAME, NULL);
param_list = gnc_search_param_prepend (param_list, _("Due"), NULL, type,
INVOICE_DUE, NULL);
}
/* Create the query to search for invoices; set the book */
q = qof_query_create();
qof_query_search_for(q, GNC_INVOICE_MODULE_NAME);
qof_query_set_book (q, book);
/* For vendor bills we want to find all invoices where:
* invoice -> is_posted == TRUE
* AND invoice -> lot -> is_closed? == FALSE
* AND invoice -> type != customer invoice
* AND invoice -> type != customer credit note
* AND invoice -> due <= (today + days_in_advance)
*/
/* For customer invoices we want to find all invoices where:
* invoice -> is_posted == TRUE
* AND invoice -> lot -> is_closed? == FALSE
* AND invoice -> type != vendor bill
* AND invoice -> type != vendor credit note
* AND invoice -> type != employee voucher
* AND invoice -> type != employee credit note
* AND invoice -> due <= (today + days_in_advance)
* This could probably also be done by searching for customer invoices OR customer credit notes
* but that would make a more complicated query to compose.
*/
qof_query_add_boolean_match (q, g_slist_prepend(NULL, INVOICE_IS_POSTED), TRUE,
QOF_QUERY_AND);
qof_query_add_boolean_match (q, g_slist_prepend(g_slist_prepend(NULL, LOT_IS_CLOSED),
INVOICE_POST_LOT), FALSE, QOF_QUERY_AND);
if (duetype == DUE_FOR_VENDOR)
{
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_CUST_INVOICE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_CUST_CREDIT_NOTE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
}
else
{
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_VEND_INVOICE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_VEND_CREDIT_NOTE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_EMPL_INVOICE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
pred_data = qof_query_int32_predicate (QOF_COMPARE_NEQ, GNC_INVOICE_EMPL_CREDIT_NOTE);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_TYPE), pred_data, QOF_QUERY_AND);
}
end_date = gnc_time (NULL);
if (days_in_advance < 0)
days_in_advance = 0;
end_date += days_in_advance * 60 * 60 * 24;
pred_data = qof_query_date_predicate (QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, end_date);
qof_query_add_term (q, g_slist_prepend(NULL, INVOICE_DUE), pred_data, QOF_QUERY_AND);
res = qof_query_run(q);
len = g_list_length (res);
if (!res || len <= 0)
{
qof_query_destroy(q);
return NULL;
}
if (duetype == DUE_FOR_VENDOR)
{
message = g_strdup_printf
(/* Translators: %d is the number of bills/credit notes due. This is a
ngettext(3) message. */
ngettext("The following vendor document is due:",
"The following %d vendor documents are due:",
len),
len);
title = _("Due Bills Reminder");
}
else
{
message = g_strdup_printf
(/* Translators: %d is the number of invoices/credit notes due. This is a
ngettext(3) message. */
ngettext("The following customer document is due:",
"The following %d customer documents are due:",
len),
len);
title = _("Due Invoices Reminder");
}
dialog = gnc_dialog_query_view_create(parent, param_list, q,
title,
message,
TRUE, FALSE,
1, GTK_SORT_ASCENDING,
duetype == DUE_FOR_VENDOR ?
vendorbuttons :
customerbuttons, NULL);
g_free(message);
qof_query_destroy(q);
return dialog;
}
void
gnc_invoice_remind_bills_due (GtkWindow *parent)
{
QofBook *book;
gint days;
if (!gnc_current_session_exist()) return;
book = qof_session_get_book(gnc_get_current_session());
days = gnc_prefs_get_float(GNC_PREFS_GROUP_BILL, GNC_PREF_DAYS_IN_ADVANCE);
gnc_invoice_show_docs_due (parent, book, days, DUE_FOR_VENDOR);
}
void
gnc_invoice_remind_invoices_due (GtkWindow *parent)
{
QofBook *book;
gint days;
if (!gnc_current_session_exist()) return;
book = qof_session_get_book(gnc_get_current_session());
days = gnc_prefs_get_float(GNC_PREFS_GROUP_INVOICE, GNC_PREF_DAYS_IN_ADVANCE);
gnc_invoice_show_docs_due (parent, book, days, DUE_FOR_CUSTOMER);
}
void
gnc_invoice_remind_bills_due_cb (void)
{
if (!gnc_prefs_get_bool(GNC_PREFS_GROUP_BILL, GNC_PREF_NOTIFY_WHEN_DUE))
return;
gnc_invoice_remind_bills_due (GTK_WINDOW(gnc_ui_get_main_window (NULL)));
}
void
gnc_invoice_remind_invoices_due_cb (void)
{
if (!gnc_prefs_get_bool(GNC_PREFS_GROUP_INVOICE, GNC_PREF_NOTIFY_WHEN_DUE))
return;
gnc_invoice_remind_invoices_due (GTK_WINDOW(gnc_ui_get_main_window (NULL)));
}