GnuCash c935c2f+
Loading...
Searching...
No Matches
dialog-bi-import.c
1/*
2 * dialog-bi-import.c -- Invoice importer Core functions
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of
7 * the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, contact:
16 *
17 * Free Software Foundation Voice: +1-617-542-5942
18 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
19 * Boston, MA 02110-1301, USA gnu@gnu.org
20 */
21
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
33
34#include <glib/gi18n.h>
35#include <regex.h>
36#include <glib.h>
37#include <glib/gstdio.h>
38
39#include "gnc-glib-utils.h"
40#include "gnc-date.h"
41#include "gnc-ui.h"
42#include "gnc-ui-util.h"
43#include "gnc-gui-query.h"
44#include "gncAddress.h"
45#include "gncVendorP.h"
46#include "gncVendor.h"
47#include "gncEntry.h"
48#include "gnc-prefs.h"
49
50#include "gnc-exp-parser.h"
51
52// query
53#include "Query.h"
54#include "qof.h"
55#include "gncIDSearch.h"
56#include "dialog-bi-import.h"
57#include "dialog-bi-import-helper.h"
58
59// To open the invoices for editing
61#include "dialog-invoice.h"
62#include "business-gnome-utils.h"
63
64// this helper macro takes a regexp match and fills the model
65#define FILL_IN_HELPER(match_name,column) \
66 temp = g_match_info_fetch_named (match_info, match_name); \
67 if (temp) \
68 { \
69 g_strstrip( temp ); \
70 gtk_list_store_set (store, &iter, column, temp, -1); \
71 g_free (temp); \
72 } else gtk_list_store_set (store, &iter, column, "", -1);
73
74static QofLogModule log_module = G_LOG_DOMAIN; //G_LOG_BUSINESS;
75static char * un_escape(char *str);
76
97bi_import_result
98gnc_bi_import_read_file (const gchar * filename, const gchar * parser_regexp,
99 GtkListStore * store, guint max_rows,
100 bi_import_stats * stats)
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}
243
244
275void
276gnc_bi_import_fix_bis (GtkListStore * store, guint * n_rows_fixed, guint * n_rows_ignored,
277 GString * info, gchar *type)
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}
581
582
604void
605gnc_bi_import_create_bis (GtkListStore * store, QofBook * book,
606 guint * n_invoices_created,
607 guint * n_invoices_updated,
608 guint * n_rows_ignored,
609 gchar * type, gchar * open_mode, GString * info,
610 GtkWindow *parent)
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}
951
952/* Change any escaped quotes ("") to (")
953 * @param char* String to be modified
954 * @return char* Modified string.
955*/
956static char*
957un_escape(char *str)
958{
959 gchar quote = '"';
960 gchar *newStr = NULL, *tmpstr = str;
961 int n = strlen (str), i;
962 newStr = g_malloc (n + 1);
963 memset (newStr, 0, n + 1);
964
965 for (i = 0; *tmpstr != '\0'; ++i, ++tmpstr)
966 {
967 newStr[i] = *tmpstr == quote ? *(++tmpstr) : *(tmpstr);
968 if (*tmpstr == '\0')
969 break;
970 }
971 g_free (str);
972 return newStr;
973}
core import functions for invoice import plugin
Date and Time handling routines.
GLib helper routines.
utility functions for the GnuCash UI
Generic api to store and retrieve preferences.
utility functions for the GnuCash UI
an Address object
Business Entry Interface.
Vendor Interface.
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
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account's account type.
Definition Account.cpp:3267
@ ACCT_TYPE_PAYABLE
A/P account type.
Definition Account.h:151
@ ACCT_TYPE_RECEIVABLE
A/R account type.
Definition Account.h:149
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
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
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
void gnc_gdate_set_today(GDate *gd)
Set a GDate to the current day.
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
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
void gncEntrySetDateGDate(GncEntry *entry, const GDate *date)
Set the date of this entry.
Definition gncEntry.c:504
void gncEntrySetQuantity(GncEntry *entry, gnc_numeric quantity)
Set the internal quantity without any conversion.
Definition gncEntry.c:551
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.
void gnc_utf8_strip_invalid(gchar *str)
Strip any non-UTF-8 characters from a string.
GncPluginPage * gnc_plugin_page_invoice_new(InvoiceWindow *iw)
Create a new "invoice" plugin page, given a pointer to an InvoiceWindow data structure.
GHashTable * gncInvoiceGetForeignCurrencies(const GncInvoice *invoice)
Return an overview of amounts on this invoice that will be posted to accounts in currencies that are ...
void gncBillAddEntry(GncInvoice *bill, GncEntry *entry)
Call this function when adding an entry to a bill instead of an invoice.
Definition gncInvoice.c:719
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.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
GncOwner * gncOwnerNew(void)
These two functions are mainly for the convenience of scheme code.
Definition gncOwner.c:57
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
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.
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.
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.
STRUCTS.
QofBook reference.
Definition qofbook-p.hpp:47