GnuCash  5.6-150-g038405b370+
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 605 of file dialog-bi-import.c.

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

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

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

◆ 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 }