GnuCash  5.6-133-gc519490283+
Files | Data Structures | Macros | Typedefs | Enumerations | Functions
Tools

Files

file  dialog-bi-import-gui.h
 GUI handling for bi-import plugin.
 
file  dialog-bi-import.h
 core import functions for invoice import plugin
 
file  gnc-plugin-bi-import.h
 Plugin registration of the bi-import module.
 
file  dialog-customer-import-gui.h
 GUI handling for customer import plugin.
 
file  dialog-customer-import.h
 core import functions for customer import plugin
 
file  gnc-plugin-customer-import.h
 Plugin registration of the customer_import module.
 

Data Structures

struct  bi_import_stats
 
struct  customer_import_stats
 

Macros

#define GNC_TYPE_PLUGIN_example   (gnc_plugin_example_get_type())
 
#define GNC_PLUGIN_EXAMPLE_NAME   "gnc-plugin-example"
 
#define GNC_TYPE_PLUGIN_BI_IMPORT   (gnc_plugin_bi_import_get_type())
 
#define GNC_PLUGIN_BI_IMPORT_NAME   "gnc-plugin-bi-import"
 
#define GNC_TYPE_PLUGIN_CUSTOMER_IMPORT   (gnc_plugin_customer_import_get_type())
 
#define GNC_PLUGIN_CUSTOMER_IMPORT_NAME   "gnc-plugin-customer-import"
 

Typedefs

typedef typedefG_BEGIN_DECLS struct _bi_import_gui BillImportGui
 
typedef enum _bi_import_result bi_import_result
 
typedef typedefG_BEGIN_DECLS struct _customer_import_gui CustomerImportGui
 
typedef enum _customer_import_result customer_import_result
 

Enumerations

enum  bi_import_model_columns {
  ID, DATE_OPENED, OWNER_ID, BILLING_ID,
  NOTES, DATE, DESC, ACTION,
  ACCOUNT, QUANTITY, PRICE, DISC_TYPE,
  DISC_HOW, DISCOUNT, TAXABLE, TAXINCLUDED,
  TAX_TABLE, DATE_POSTED, DUE_DATE, ACCOUNT_POSTED,
  MEMO_POSTED, ACCU_SPLITS, N_COLUMNS
}
 
enum  _bi_import_result { RESULT_OK, RESULT_OPEN_FAILED, RESULT_ERROR_IN_REGEXP }
 
enum  customer_import_model_columns {
  CI_ID, CI_COMPANY, CI_NAME, CI_ADDR1,
  CI_ADDR2, CI_ADDR3, CI_ADDR4, CI_PHONE,
  CI_FAX, CI_EMAIL, CI_NOTES, CI_SHIPNAME,
  CI_SHIPADDR1, CI_SHIPADDR2, CI_SHIPADDR3, CI_SHIPADDR4,
  CI_SHIPPHONE, CI_SHIPFAX, CI_SHIPEMAIL, CI_N_COLUMNS
}
 
enum  _customer_import_result { CI_RESULT_OK, CI_RESULT_OPEN_FAILED, CI_RESULT_ERROR_IN_REGEXP }
 

Functions

GncPlugin * gnc_plugin_example_new (void)
 
void gnc_plugin_example_create_plugin (void)
 Create a new GncPluginexample object and register it.
 
BillImportGui * gnc_plugin_bi_import_showGUI (GtkWindow *parent)
 File chooser.
 
bi_import_result gnc_bi_import_read_file (const gchar *filename, const gchar *parser_regexp, GtkListStore *store, guint max_rows, bi_import_stats *stats)
 Imports a csv file with invoice data into a GtkListStore. More...
 
void gnc_bi_import_fix_bis (GtkListStore *store, guint *fixed, guint *deleted, GString *info, gchar *type)
 Adjusts and validates invoice import data. More...
 
void gnc_bi_import_create_bis (GtkListStore *store, QofBook *book, guint *n_invoices_created, guint *n_invoices_updated, guint *n_rows_ignored, gchar *type, gchar *open_mode, GString *info, GtkWindow *parent)
 Creates and updates invoices from validated import data. More...
 
GncPlugin * gnc_plugin_bi_import_new (void)
 
void gnc_plugin_bi_import_create_plugin (void)
 Create a new GncPluginbi_import object and register it.
 
CustomerImportGui * gnc_plugin_customer_import_showGUI (GtkWindow *parent)
 File chooser.
 
customer_import_result gnc_customer_import_read_file (const gchar *filename, const gchar *parser_regexp, GtkListStore *store, guint max_rows, customer_import_stats *stats)
 
void gnc_customer_import_fix_customers (GtkListStore *store, guint *fixed, guint *deleted, gchar *type)
 
void gnc_customer_import_create_customers (GtkListStore *store, QofBook *book, guint *n_customers_created, guint *n_customers_updated, gchar *type)
 
GncPlugin * gnc_plugin_customer_import_new (void)
 
void gnc_plugin_customer_import_create_plugin (void)
 Create a new GncPlugincustomer_import object and register it.
 

Detailed Description

Function Documentation

◆ gnc_bi_import_create_bis()

void gnc_bi_import_create_bis ( GtkListStore *  store,
QofBook *  book,
guint *  n_invoices_created,
guint *  n_invoices_updated,
guint *  n_rows_ignored,
gchar *  type,
gchar *  open_mode,
GString *  info,
GtkWindow *  parent 
)

Creates and updates invoices from validated import data.

Loops through the import data to create and update invoices. The first data row for an invoice is assumed to hold the header data.

If an invoice already exists, the user is asked, once per import, to confirm that invoices should be updated. If not confirmed, any rows for existing invoices are ignored. If confirmed, entries are added to existing invoices. Posted invoices, however, are never updated.

If the field date_posted is set, the system will attempt to also post the invoice. The system will not post the invoice if the entries of the invoice hold different currencies, or if the currency of the invoice differs from the currency of the account_posted.

As per user selection, the system displays tabs for either all affected invoices, all affected invoices not yet posted, or no invoices at all.

Definition at line 598 of file dialog-bi-import.c.

604 {
605  gboolean valid, on_first_row_of_invoice, invoice_posted;
606  GtkTreeIter iter, first_row_of_invoice;
607  gchar *id = NULL, *date_opened = NULL, *owner_id = NULL, *billing_id = NULL, *notes = NULL;
608  gchar *date = NULL, *desc = NULL, *action = NULL, *account = NULL, *quantity = NULL,
609  *price = NULL, *disc_type = NULL, *disc_how = NULL, *discount = NULL, *taxable = NULL,
610  *taxincluded = NULL, *tax_table = NULL;
611  gchar *date_posted = NULL, *due_date = NULL, *account_posted = NULL, *memo_posted = NULL,
612  *accumulatesplits = NULL;
613  guint dummy;
614  GncInvoice *invoice;
615  GncEntry *entry;
616  gint day, month, year;
617  gnc_numeric value;
618  GncOwner *owner;
619  Account *acc = NULL;
620  enum update {YES = GTK_RESPONSE_YES, NO = GTK_RESPONSE_NO, NOT_ASKED = GTK_RESPONSE_NONE} update;
621  GtkWidget *dialog;
622  time64 today;
623  InvoiceWindow *iw;
624  GString *running_id;
625 
626  // these arguments are needed
627  g_return_if_fail (store && book);
628  // logic of this function only works for bills or invoices
629  g_return_if_fail ((g_ascii_strcasecmp (type, "INVOICE") == 0) ||
630  (g_ascii_strcasecmp (type, "BILL") == 0));
631 
632  // allow to call this function without statistics
633  if (!n_invoices_created)
634  n_invoices_created = &dummy;
635  if (!n_invoices_updated)
636  n_invoices_updated = &dummy;
637  *n_invoices_created = 0;
638  *n_invoices_updated = 0;
639 
640  invoice = NULL;
641  update = NOT_ASKED;
642  on_first_row_of_invoice = TRUE;
643  running_id = g_string_new("");
644 
645  g_string_append_printf (info, "\n%s\n", _("Processing…") );
646 
647  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
648  while (valid)
649  {
650  // Walk through the list, reading each row
651  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
652  ID, &id,
653  DATE_OPENED, &date_opened,
654  DATE_POSTED, &date_posted, // if autoposting requested
655  DUE_DATE, &due_date, // if autoposting requested
656  ACCOUNT_POSTED, &account_posted, // if autoposting requested
657  MEMO_POSTED, &memo_posted, // if autoposting requested
658  ACCU_SPLITS, &accumulatesplits, // if autoposting requested
659  OWNER_ID, &owner_id,
660  BILLING_ID, &billing_id,
661  NOTES, &notes,
662  DATE, &date,
663  DESC, &desc,
664  ACTION, &action,
665  ACCOUNT, &account,
666  QUANTITY, &quantity,
667  PRICE, &price,
668  DISC_TYPE, &disc_type,
669  DISC_HOW, &disc_how,
670  DISCOUNT, &discount,
671  TAXABLE, &taxable,
672  TAXINCLUDED, &taxincluded,
673  TAX_TABLE, &tax_table, -1);
674 
675  if (on_first_row_of_invoice)
676  {
677  g_string_assign(running_id, id);
678  first_row_of_invoice = iter;
679 
680  if (g_ascii_strcasecmp (type, "BILL") == 0)
681  invoice = gnc_search_bill_on_id (book, id);
682  else if (g_ascii_strcasecmp (type, "INVOICE") == 0)
683  invoice = gnc_search_invoice_on_id (book, id);
684  DEBUG( "Existing %s ID: %s\n", type, gncInvoiceGetID(invoice));
685 
686  // If the search is empty then there is no existing invoice so make a new one
687  if (invoice == NULL)
688  {
689  DEBUG( "Creating a new : %s\n", type );
690  // new invoice
691  invoice = gncInvoiceCreate (book);
692  /* Protect against thrashing the DB and trying to write the invoice
693  * record prematurely */
694  gncInvoiceBeginEdit (invoice);
695  gncInvoiceSetID (invoice, id);
696  owner = gncOwnerNew ();
697  if (g_ascii_strcasecmp (type, "BILL") == 0)
698  gncOwnerInitVendor (owner,
699  gnc_search_vendor_on_id (book, owner_id));
700  else if (g_ascii_strcasecmp (type, "INVOICE") == 0)
701  gncOwnerInitCustomer (owner,
702  gnc_search_customer_on_id (book, owner_id));
703  gncInvoiceSetOwner (invoice, owner);
704  gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (owner)); // Set the invoice currency based on the owner
705  qof_scan_date (date_opened, &day, &month, &year);
706  gncInvoiceSetDateOpened (invoice,
707  gnc_dmy2time64 (day, month, year));
708  gncInvoiceSetBillingID (invoice, billing_id ? billing_id : "");
709  notes = un_escape(notes);
710  gncInvoiceSetNotes (invoice, notes ? notes : "");
711  gncInvoiceSetActive (invoice, TRUE);
712  //if (g_ascii_strcasecmp(type,"INVOICE"))gncInvoiceSetBillTo( invoice, billto );
713  (*n_invoices_created)++;
714  g_string_append_printf (info, _("Invoice %s created.\n"),id);
715 
716  gncInvoiceCommitEdit (invoice);
717  }
718  else // Dealing with an existing invoice.
719  {
720  // For the first existing invoice in the import file,
721  // ask the user to confirm update of existing invoices.
722  if (update == NOT_ASKED)
723  {
724  dialog = gtk_message_dialog_new (parent,
725  GTK_DIALOG_MODAL,
726  GTK_MESSAGE_ERROR,
727  GTK_BUTTONS_YES_NO,
728  "%s",
729  _("Do you want to update existing bills/invoices?"));
730  update = gtk_dialog_run (GTK_DIALOG (dialog));
731  gtk_widget_destroy (dialog);
732  }
733 
734  if (update == NO)
735  {
736  // If the user does not want to update existing invoices, ignore all rows of the invoice.
737  g_string_append_printf (info,_("Invoice %s not updated because it already exists.\n"),id);
738  while (valid && g_strcmp0 (id, running_id->str) == 0)
739  {
740  (*n_rows_ignored)++;
741  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
742  if (valid)
743  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, -1);
744  }
745  on_first_row_of_invoice = TRUE;
746  continue;
747  }
748 
749  if (gncInvoiceIsPosted (invoice))
750  {
751  // If the invoice is already posted, ignore all rows of the invoice.
752  g_string_append_printf (info,_("Invoice %s not updated because it is already posted.\n"),id);
753  while (valid && g_strcmp0 (id, running_id->str) == 0)
754  {
755  (*n_rows_ignored)++;
756  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
757  if (valid)
758  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, -1);
759  }
760  on_first_row_of_invoice = TRUE;
761  continue;
762  }
763 
764  (*n_invoices_updated)++;
765  g_string_append_printf (info, _("Invoice %s updated.\n"),id);
766  }
767  }
768 
769  // Add entry to invoice/bill
770  entry = gncEntryCreate (book);
771  gncEntryBeginEdit(entry);
772  qof_scan_date (date, &day, &month, &year);
773  {
774  GDate *date = g_date_new_dmy(day, month, year);
775  gncEntrySetDateGDate (entry, date);
776  g_date_free (date);
777  }
778  today = gnc_time (NULL);
779  gncEntrySetDateEntered(entry, today);
780  // Remove escaped quotes
781  desc = un_escape(desc);
782  notes = un_escape(notes);
783  gncEntrySetDescription (entry, desc);
784  gncEntrySetAction (entry, action);
785  value = gnc_numeric_zero();
786  gnc_exp_parser_parse (quantity, &value, NULL);
787  gncEntrySetQuantity (entry, value);
788  acc = gnc_account_lookup_for_register (gnc_get_current_root_account (),
789  account);
790 
791  if (g_ascii_strcasecmp (type, "BILL") == 0)
792  {
793  gncEntrySetBillAccount (entry, acc);
794  value = gnc_numeric_zero();
795  gnc_exp_parser_parse (price, &value, NULL);
796  gncEntrySetBillPrice (entry, value);
797  gncEntrySetBillTaxable (entry, text2bool (taxable));
798  gncEntrySetBillTaxIncluded (entry, text2bool (taxincluded));
799  gncEntrySetBillTaxTable (entry, gncTaxTableLookupByName (book, tax_table));
800  gncBillAddEntry (invoice, entry);
801  }
802  else if (g_ascii_strcasecmp (type, "INVOICE") == 0)
803  {
804  gncEntrySetNotes (entry, notes);
805  gncEntrySetInvAccount (entry, acc);
806  value = gnc_numeric_zero();
807  gnc_exp_parser_parse (price, &value, NULL);
808  gncEntrySetInvPrice (entry, value);
809  gncEntrySetInvTaxable (entry, text2bool (taxable));
810  gncEntrySetInvTaxIncluded (entry, text2bool (taxincluded));
811  gncEntrySetInvTaxTable (entry, gncTaxTableLookupByName (book, tax_table));
812  value = gnc_numeric_zero();
813  gnc_exp_parser_parse (discount, &value, NULL);
814  gncEntrySetInvDiscount (entry, value);
815  gncEntrySetInvDiscountType (entry, text2disc_type (disc_type));
816  gncEntrySetInvDiscountHow (entry, text2disc_how (disc_how));
817  gncInvoiceAddEntry (invoice, entry);
818  }
819  gncEntryCommitEdit(entry);
820  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
821  // handle auto posting of invoices
822 
823  if (valid)
824  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, -1);
825  else
826  id = NULL;
827 
828  if (g_strcmp0 (id, running_id->str) == 0) // The next row is for the same invoice.
829  {
830  on_first_row_of_invoice = FALSE;
831  }
832  else // The next row is for a new invoice; try to post the invoice.
833  {
834  // Use posting values from the first row of this invoice.
835  gtk_tree_model_get (GTK_TREE_MODEL (store), &first_row_of_invoice,
836  ID, &id,
837  DATE_POSTED, &date_posted,
838  DUE_DATE, &due_date,
839  ACCOUNT_POSTED, &account_posted,
840  MEMO_POSTED, &memo_posted,
841  ACCU_SPLITS, &accumulatesplits, -1);
842  invoice_posted = FALSE;
843 
844  if (strlen(date_posted) != 0)
845  {
846  // autopost this invoice
847  GHashTable *foreign_currs;
848  gboolean auto_pay;
849  time64 p_date, d_date;
850  guint curr_count;
851  gboolean scan_date_r;
852  scan_date_r = qof_scan_date (date_posted, &day, &month, &year);
853  DEBUG("Invoice %s is marked to be posted because...", id);
854  DEBUG("qof_scan_date = %d", scan_date_r);
855  if (g_ascii_strcasecmp (type, "INVOICE") == 0)
856  auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_INVOICE, GNC_PREF_AUTO_PAY);
857  else
858  auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_BILL, GNC_PREF_AUTO_PAY);
859  // Do we have any foreign currencies to deal with?
860  foreign_currs = gncInvoiceGetForeignCurrencies (invoice);
861  curr_count = g_hash_table_size (foreign_currs);
862  DEBUG("curr_count = %d",curr_count);
863  // Only auto-post if there's a single currency involved
864  if(curr_count == 0)
865  {
867  (gnc_get_current_root_account (), account_posted);
868  // Check if the currencies match
869  if(gncInvoiceGetCurrency(invoice) == gnc_account_get_currency_or_parent(acc))
870  {
871  qof_scan_date (date_posted, &day, &month, &year);
872  p_date = gnc_dmy2time64 (day, month, year);
873  qof_scan_date (due_date, &day, &month, &year);
874  d_date = gnc_dmy2time64 (day, month, year);
875  gncInvoicePostToAccount (invoice, acc, p_date, d_date,
876  memo_posted,
877  text2bool (accumulatesplits),
878  auto_pay);
879  PWARN("Invoice %s posted",id);
880  invoice_posted = TRUE;
881  g_string_append_printf (info, _("Invoice %s posted.\n"),id);
882  }
883  else // No match! Don't post it.
884  {
885  PWARN("Invoice %s NOT posted because currencies don't match", id);
886  g_string_append_printf (info,_("Invoice %s NOT posted because currencies don't match.\n"), id);
887  }
888  }
889  else
890  {
891  PWARN("Invoice %s NOT posted because it requires currency conversion.",id);
892  g_string_append_printf (info,_("Invoice %s NOT posted because it requires currency conversion.\n"),id);
893  }
894  g_hash_table_unref (foreign_currs);
895  }
896  else
897  {
898  PWARN("Invoice %s is NOT marked for posting",id);
899  }
900 
901  // open new bill / invoice in a tab, if requested
902  if (g_ascii_strcasecmp(open_mode, "ALL") == 0
903  || (g_ascii_strcasecmp(open_mode, "NOT_POSTED") == 0
904  && !invoice_posted))
905  {
906  iw = gnc_ui_invoice_edit (parent, invoice);
908  }
909 
910  // The next row will be for a new invoice.
911  on_first_row_of_invoice = TRUE;
912  }
913  }
914 
915  if (*n_invoices_updated + *n_invoices_created == 0)
916  g_string_append_printf (info, _("Nothing to process.\n"));
917 
918  // cleanup
919  g_free (id);
920  g_free (date_opened);
921  g_free (owner_id);
922  g_free (billing_id);
923  g_free (notes);
924  g_free (date);
925  g_free (desc);
926  g_free (action);
927  g_free (account);
928  g_free (quantity);
929  g_free (price);
930  g_free (disc_type);
931  g_free (disc_how);
932  g_free (discount);
933  g_free (taxable);
934  g_free (taxincluded);
935  g_free (tax_table);
936  g_free (date_posted);
937  g_free (due_date);
938  g_free (account_posted);
939  g_free (memo_posted);
940  g_free (accumulatesplits);
941 
942  g_string_free (running_id, TRUE);
943 }
void gncEntrySetQuantity(GncEntry *entry, gnc_numeric quantity)
Set the internal quantity without any conversion.
Definition: gncEntry.c:551
STRUCTS.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
time64 gnc_dmy2time64(gint day, gint month, gint year)
Convert a day, month, and year to a time64, returning the first second of the day.
Transaction * gncInvoicePostToAccount(GncInvoice *invoice, Account *acc, time64 post_date, time64 due_date, const char *memo, gboolean accumulatesplits, gboolean autopay)
Post this invoice to an account.
Definition: gncInvoice.c:1497
GHashTable * gncInvoiceGetForeignCurrencies(const GncInvoice *invoice)
Return an overview of amounts on this invoice that will be posted to accounts in currencies that are ...
Definition: gncInvoice.c:1363
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
GncPluginPage * gnc_plugin_page_invoice_new(InvoiceWindow *iw)
Create a new "invoice" plugin page, given a pointer to an InvoiceWindow data structure.
void gncBillAddEntry(GncInvoice *bill, GncEntry *entry)
Call this function when adding an entry to a bill instead of an invoice.
Definition: gncInvoice.c:720
void gncEntrySetDateGDate(GncEntry *entry, const GDate *date)
Set the date of this entry.
Definition: gncEntry.c:504
gnc_commodity * gnc_account_get_currency_or_parent(const Account *account)
Returns a gnc_commodity that is a currency, suitable for being a Transaction's currency.
Definition: Account.cpp:3474
Account * gnc_account_lookup_for_register(const Account *base_account, const char *name)
Retrieve the account matching the given name starting from the descendants of base_account.
gboolean qof_scan_date(const char *buff, int *day, int *month, int *year)
qof_scan_date Convert a string into day / month / year integers according to the current dateFormat v...
Definition: gnc-date.cpp:917
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:261
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
GncOwner * gncOwnerNew(void)
These two functions are mainly for the convenience of scheme code.
Definition: gncOwner.c:57

◆ gnc_bi_import_fix_bis()

void gnc_bi_import_fix_bis ( GtkListStore *  store,
guint *  n_rows_fixed,
guint *  n_rows_ignored,
GString *  info,
gchar *  type 
)

Adjusts and validates invoice import data.

Replaces missing or invalid data with defaults:

  • if quantity is not set, default to 1
  • if date_opened is not set or invalid, default to today
  • if date is not set or invalid, default to date_opened
  • if due date is not set or invalid, default to date_posted

Validates the import data; any error causes all rows of the same invoice to be deleted from the import data:

  • id is not set, and there is no previous id
  • owner_id is not set, or customer/vendor does not exist
  • date_posted is not valid
  • account_posted does not exist
  • account posted is not the applicable type, A/P or A/R
  • price is not set
  • account does not exist

Adjustment and validation for header fields is only done for the first row of an invoice, which is assumed to hold the header data for all items of the same invoice. Currency related validation is done in subsqequent processing by gnc_bi_import_create_bis.

Parameters
storeHolds the rows of invoice import data
n_rows_fixedIncreased for every data row that is adjusted in this function
n_rows_ignoredIncreased for every data row that is deleted in this function
infoUpdated with the error messages from this function
typeThe type of the import data, BILL or INVOICE

Definition at line 269 of file dialog-bi-import.c.

271 {
272  GtkTreeIter iter, first_row_of_invoice;
273  gboolean valid, row_fixed, on_first_row_of_invoice, ignore_invoice;
274  gchar *id = NULL, *date_opened = NULL, *date_posted = NULL, *due_date = NULL, *account_posted = NULL,
275  *owner_id = NULL, *date = NULL, *account = NULL, *quantity = NULL, *price = NULL;
276  GString *running_id;
277  Account *acc = NULL;
278  guint dummy;
279  gint row = 1, fixed_for_invoice = 0, invoice_line = 0;
280  const gchar* date_format_string = qof_date_format_get_string (qof_date_format_get()); // Get the user set date format string
281 
282  DEBUG("date_format_string: %s",date_format_string);
283  // allow the call to this function with only GtkListeStore* specified
284  if (!n_rows_fixed)
285  n_rows_fixed = &dummy;
286  if (!n_rows_ignored)
287  n_rows_ignored = &dummy;
288 
289  *n_rows_fixed = 0;
290  *n_rows_ignored = 0;
291 
292  // Init control variables
293  running_id = g_string_new("");
294  ignore_invoice = FALSE;
295  on_first_row_of_invoice = TRUE;
296 
297  g_string_append_printf (info, _("Validation…\n") );
298 
299  // Walk through the list, reading each row.
300  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
301  while (valid)
302  {
303  ++invoice_line;
304  row_fixed = FALSE;
305 
306  // If this is a row for a new invoice id, validate header values.
307  if (on_first_row_of_invoice)
308  {
309  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
310  ID, &id,
311  DATE_OPENED, &date_opened,
312  DATE_POSTED, &date_posted,
313  DUE_DATE, &due_date,
314  ACCOUNT_POSTED, &account_posted,
315  OWNER_ID, &owner_id, -1);
316 
317  g_string_assign (running_id, id);
318  first_row_of_invoice = iter;
319 
320  // Validate the invoice id.
321  if (strlen (id) == 0)
322  {
323  // If there was an earlier valid id, then it replaces an empty id when the next row is read at the end of the loop.
324  // So an empty id error can only happen on the first row of an import file.
325  ignore_invoice = TRUE;
326  g_string_append_printf (info,
327  _("Row %d: no invoice ID in first row of import file.\n"), row);
328  }
329 
330  // Validate customer or vendor.
331  if (strlen (owner_id) == 0)
332  {
333  ignore_invoice = TRUE;
334  g_string_append_printf (info,
335  _("Row %d, invoice %s/%u: owner not set.\n"),
336  row, id, invoice_line);
337  }
338  // Verify that customer or vendor exists.
339  if (g_ascii_strcasecmp (type, "BILL") == 0)
340  {
341  if (!gnc_search_vendor_on_id
342  (gnc_get_current_book (), owner_id))
343  {
344  // Vendor not found.
345  ignore_invoice = TRUE;
346  g_string_append_printf (info,
347  _("Row %d, invoice %s/%u: vendor %s does not exist.\n"),
348  row, id, invoice_line, owner_id);
349  }
350  }
351  else if (g_ascii_strcasecmp (type, "INVOICE") == 0)
352  {
353  if (!gnc_search_customer_on_id
354  (gnc_get_current_book (), owner_id))
355  {
356  // Customer not found.
357  ignore_invoice = TRUE;
358  g_string_append_printf (info,
359  _("Row %d, invoice %s/%u: customer %s does not exist.\n"),
360  row, id, invoice_line, owner_id);
361  }
362  }
363 
364  if (strlen(date_posted) != 0)
365  {
366  // Validate the date posted and due date.
367  if (!isDateValid(date_posted))
368  {
369  // Invalid date posted in first row of invoice, ignore the invoice
370  ignore_invoice = TRUE;
371  g_string_append_printf (info,
372  _("Row %d, invoice %s/%u: %s is not a valid posting date.\n"),
373  row, id, invoice_line, date_posted);
374 
375  // Verify the due date.
376  if (!isDateValid(due_date))
377  {
378  // Invalid due date in first row of invoice, without valid posting date to substitute.
379  g_string_append_printf (info,
380  _("Row %d, invoice %s/%u: %s is not a valid due date.\n"),
381  row, id, invoice_line, due_date);
382  }
383  }
384  else
385  {
386  // Verify the due date.
387  if (!isDateValid(due_date))
388  {
389  // Fix this by using the date posted.
390  gtk_list_store_set (store, &iter, DUE_DATE,
391  date_posted, -1);
392  row_fixed = TRUE;
393  }
394  }
395 
396  // Validate account posted.
397  // Account should exists, and should be of type A/R for invoices, A/P for bills.
399  (gnc_get_current_root_account (), account_posted);
400  if (acc == NULL)
401  {
402  ignore_invoice = TRUE;
403  g_string_append_printf (info,
404  _("Row %d, invoice %s/%u: account %s does not exist.\n"),
405  row, id, invoice_line, account_posted);
406  }
407  else
408  {
409  if (g_ascii_strcasecmp (type, "BILL") == 0)
410  {
411 
413  {
414  ignore_invoice = TRUE;
415  g_string_append_printf (info,
416  _("Row %d, invoice %s/%u: account %s is not of type Accounts Payable.\n"),
417  row, id, invoice_line, account_posted);
418  }
419  }
420  else if (g_ascii_strcasecmp (type, "INVOICE") == 0)
421  {
423  {
424  ignore_invoice = TRUE;
425  g_string_append_printf (info,
426  _("Row %d, invoice %s/%u: account %s is not of type Accounts Receivable.\n"),
427  row, id, invoice_line, account_posted);
428  }
429  }
430  }
431  }
432 
433  // Verify the date opened.
434  if(!isDateValid(date_opened))
435  {
436  // Fix this by using the current date.
437  gchar temp[20];
438  GDate date;
439  g_date_clear (&date, 1);
440  gnc_gdate_set_today (&date);
441  g_date_strftime (temp, 20, date_format_string, &date); // Create a user specified date string.
442  gtk_list_store_set (store, &iter, DATE_OPENED,
443  temp, -1);
444  row_fixed = TRUE;
445  }
446  }
447 
448  // Validate and fix item data for each row.
449 
450  // Get item data.
451  gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
452  DATE, &date,
453  ACCOUNT, &account,
454  QUANTITY, &quantity,
455  PRICE, &price, -1);
456 
457 
458  // Validate the price.
459  if (strlen (price) == 0)
460  {
461  // No valid price, delete the row
462  ignore_invoice = TRUE;
463  g_string_append_printf (info,
464  _("Row %d, invoice %s/%u: price not set.\n"),
465  row, id, invoice_line);
466  }
467 
468  // Validate the account
469  acc = gnc_account_lookup_for_register (gnc_get_current_root_account (),
470  account);
471  if (acc == NULL)
472  {
473  ignore_invoice = TRUE;
474  g_string_append_printf (info,
475  _("Row %d, invoice %s/%u: account %s does not exist.\n"),
476  row, id, invoice_line, account);
477  }
478 
479  // Fix item data.
480  if (!ignore_invoice)
481  {
482 
483  // Verify the quantity.
484  if (strlen (quantity) == 0)
485  {
486  // The quantity is not set, default to 1.
487  gtk_list_store_set (store, &iter, QUANTITY, "1", -1);
488  row_fixed = TRUE;
489  }
490 
491  // Verify the item date
492  if(!isDateValid(date))
493  {
494  // Invalid item date, replace with date opened
495  gtk_list_store_set (store, &iter, DATE,
496  date_opened, -1);
497  row_fixed = TRUE;
498  }
499 
500  }
501  if (row_fixed) ++fixed_for_invoice;
502 
503  // Get the next row and its id.
504  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
505  if (valid) gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, -1);
506 
507 
508  // If the id of the next row is blank, it takes the id of the previous row.
509  if (valid && strlen(id) == 0)
510  {
511  strcpy( id, running_id->str);
512  gtk_list_store_set (store, &iter, ID, id, -1);
513  }
514 
515  // If this row was the last row of the invoice...
516  if (!valid || (valid && g_strcmp0 (id, running_id->str) != 0))
517  {
518  // If invoice should be ignored, remove all rows of this invoice.
519  if (ignore_invoice)
520  {
521  iter = first_row_of_invoice;
522  do
523  {
524  (*n_rows_ignored)++;
525  valid = gtk_list_store_remove (store, &iter);
526  if (valid) gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, -1);
527  }
528  while (valid && (g_strcmp0 (id, running_id->str) == 0));
529 
530  if (running_id->len != 0)
531  {
532  g_string_append_printf (info,
533  _("Error(s) in invoice %s, all rows of this invoice ignored.\n"),
534  running_id->str);
535  }
536  else
537  {
538  g_string_append_printf (info,
539  _("Error(s) in invoice without id, all rows of this invoice ignored.\n"));
540  }
541 
542  // Fixes for ignored invoices don't count in the statistics.
543  fixed_for_invoice = 0;
544 
545  ignore_invoice = FALSE;
546  }
547 
548  on_first_row_of_invoice = TRUE;
549  (*n_rows_fixed) += fixed_for_invoice;
550  fixed_for_invoice = 0;
551  invoice_line = 0;
552 
553  g_free (id);
554  g_free (date_opened);
555  g_free (date_posted);
556  g_free (due_date);
557  g_free (account_posted);
558  g_free (owner_id);
559  }
560  else on_first_row_of_invoice = FALSE;
561 
562  g_free (date);
563  g_free (account);
564  g_free (quantity);
565  g_free (price);
566 
567  row++;
568  }
569 
570  // Deallocate strings.
571  g_string_free (running_id, TRUE);
572 
573 }
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account's account type.
Definition: Account.cpp:3282
STRUCTS.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
void gnc_gdate_set_today(GDate *gd)
Set a GDate to the current day.
Definition: gnc-date.cpp:1235
QofDateFormat qof_date_format_get(void)
The qof_date_format_get routine returns the date format that the date printing will use when printing...
Definition: gnc-date.cpp:426
A/P account type.
Definition: Account.h:151
Account * gnc_account_lookup_for_register(const Account *base_account, const char *name)
Retrieve the account matching the given name starting from the descendants of base_account.
A/R account type.
Definition: Account.h:149
const gchar * qof_date_format_get_string(QofDateFormat df)
This function returns a strftime formatting string for printing an all numeric date (e...
Definition: gnc-date.cpp:500

◆ gnc_bi_import_read_file()

bi_import_result gnc_bi_import_read_file ( const gchar *  filename,
const gchar *  parser_regexp,
GtkListStore *  store,
guint  max_rows,
bi_import_stats *  stats 
)

Imports a csv file with invoice data into a GtkListStore.

Opens the csv file and attempts to match each row with the regular expression provided in parser_regexp. This is a regular expression that matches each field of the import row and the user selected field separators (, or ;), optionally with the fields enclosed in quotes.

If the match is successful, the fields of the import row are transferred to a row in the GtkListStore store. If the match is not successful, the row is ignored. Maintains information about number of rows imported, the number of rows ignored, and the actual ignored rows.

Parameters
filenameThe csv filename to read
parser_regexpThe regular expression with which to match the import rows
storeTo store the matched data
max_rowsThe maximum number of rows to import; use 0 for no maximum.
statsReturn information about matched and non-matched rows. Use NULL if the information is not required.

Definition at line 97 of file dialog-bi-import.c.

100 {
101  // some statistics
102  bi_import_stats stats_fallback;
103  FILE *f;
104 
105  // regexp
106  char *line = NULL;
107  gchar *line_utf8 = NULL;
108  gchar *temp = NULL;
109  GMatchInfo *match_info;
110  GError *err;
111  GRegex *regexpat;
112 
113  // model
114  GtkTreeIter iter;
115 
116  f = g_fopen (filename, "rt");
117  if (!f)
118  {
119  //gnc_error_dialog (NULL, _("File %s cannot be opened."), filename );
120  return RESULT_OPEN_FAILED;
121  }
122 
123  // set up statistics
124  if (!stats)
125  stats = &stats_fallback;
126 
127  // compile the regular expression and check for errors
128  err = NULL;
129  regexpat =
130  g_regex_new (parser_regexp, G_REGEX_EXTENDED | G_REGEX_OPTIMIZE | G_REGEX_DUPNAMES, 0, &err);
131  if (err != NULL)
132  {
133  GtkWidget *dialog;
134  gchar *errmsg;
135 
136  errmsg = g_strdup_printf (_("Error in regular expression '%s':\n%s"),
137  parser_regexp, err->message);
138  g_error_free (err);
139  err = NULL;
140 
141  dialog = gtk_message_dialog_new (NULL,
142  GTK_DIALOG_MODAL,
143  GTK_MESSAGE_ERROR,
144  GTK_BUTTONS_OK, "%s", errmsg);
145  gtk_dialog_run (GTK_DIALOG (dialog));
146  gtk_widget_destroy (dialog);
147  g_free (errmsg);
148  errmsg = 0;
149 
150  fclose (f);
151  return RESULT_ERROR_IN_REGEXP;
152  }
153 
154  // start the import
155  stats->n_imported = 0;
156  stats->n_ignored = 0;
157  stats->ignored_lines = g_string_new (NULL);
158 #define buffer_size 1000
159  line = g_malloc0 (buffer_size);
160  while (!feof (f)
161  && ((max_rows == 0)
162  || (stats->n_imported + stats->n_ignored < max_rows)))
163  {
164  int l;
165  // read one line
166  if (!fgets (line, buffer_size, f))
167  break; // eof
168  // now strip the '\n' from the end of the line
169  l = strlen (line);
170  if ((l > 0) && (line[l - 1] == '\n'))
171  line[l - 1] = 0;
172 
173  // convert line from locale into utf8
174  line_utf8 = g_locale_to_utf8 (line, -1, NULL, NULL, NULL);
175 
176  // parse the line
177  match_info = NULL; // it seems, that in contrast to documentation, match_info is not always set -> g_match_info_free will segfault
178  if (g_regex_match (regexpat, line_utf8, 0, &match_info))
179  {
180  // match found
181  stats->n_imported++;
182 
183  // fill in the values
184  gtk_list_store_append (store, &iter);
185  FILL_IN_HELPER ("id", ID); /* FIXME: Should "id" be translated? I don't think so. */
186  FILL_IN_HELPER ("date_opened", DATE_OPENED);
187  FILL_IN_HELPER ("owner_id", OWNER_ID);
188  FILL_IN_HELPER ("billing_id", BILLING_ID);
189  FILL_IN_HELPER ("notes", NOTES);
190 
191  FILL_IN_HELPER ("date", DATE);
192  FILL_IN_HELPER ("desc", DESC);
193  FILL_IN_HELPER ("action", ACTION);
194  FILL_IN_HELPER ("account", ACCOUNT);
195  FILL_IN_HELPER ("quantity", QUANTITY);
196  FILL_IN_HELPER ("price", PRICE);
197  FILL_IN_HELPER ("disc_type", DISC_TYPE);
198  FILL_IN_HELPER ("disc_how", DISC_HOW);
199  FILL_IN_HELPER ("discount", DISCOUNT);
200  FILL_IN_HELPER ("taxable", TAXABLE);
201  FILL_IN_HELPER ("taxincluded", TAXINCLUDED);
202  FILL_IN_HELPER ("tax_table", TAX_TABLE);
203 
204  FILL_IN_HELPER ("date_posted", DATE_POSTED);
205  FILL_IN_HELPER ("due_date", DUE_DATE);
206  FILL_IN_HELPER ("account_posted", ACCOUNT_POSTED);
207  FILL_IN_HELPER ("memo_posted", MEMO_POSTED);
208  FILL_IN_HELPER ("accu_splits", ACCU_SPLITS);
209  }
210  else
211  {
212  // ignore line
213  stats->n_ignored++;
214  g_string_append (stats->ignored_lines, line_utf8);
215  g_string_append_c (stats->ignored_lines, '\n');
216  }
217 
218  g_match_info_free (match_info);
219  match_info = 0;
220  g_free (line_utf8);
221  line_utf8 = 0;
222  }
223  g_free (line);
224  line = 0;
225 
226  g_regex_unref (regexpat);
227  regexpat = 0;
228  fclose (f);
229 
230  if (stats == &stats_fallback)
231  // stats are not requested -> free the string
232  g_string_free (stats->ignored_lines, TRUE);
233 
234  return RESULT_OK;
235 }

◆ gnc_plugin_bi_import_new()

GncPlugin* gnc_plugin_bi_import_new ( void  )
Returns
A new GncPluginbi_import object

Definition at line 80 of file gnc-plugin-bi-import.c.

81 {
82  return GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_BI_IMPORT, (gchar*) NULL));
83 }

◆ gnc_plugin_customer_import_new()

GncPlugin* gnc_plugin_customer_import_new ( void  )
Returns
A new GncPlugincustomer_import object

Definition at line 78 of file gnc-plugin-customer-import.c.

79 {
80  return GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_CUSTOMER_IMPORT, (gchar*) NULL));
81 }

◆ gnc_plugin_example_new()

GncPlugin* gnc_plugin_example_new ( void  )
Returns
A new GncPluginexample object

Definition at line 66 of file gnc-plugin.example.c.

67 {
68  return GNC_PLUGIN (g_object_new (GNC_TYPE_PLUGIN_EXAMPLE, (gchar*) NULL));
69 }