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 606 of file dialog-bi-import.c.

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

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