GnuCash 038f90a+
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 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}
582
583
605void
606gnc_bi_import_create_bis (GtkListStore * store, QofBook * book,
607 guint * n_invoices_created,
608 guint * n_invoices_updated,
609 guint * n_rows_ignored,
610 gchar * type, gchar * open_mode, GString * info,
611 GtkWindow *parent)
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}
952
953/* Change any escaped quotes ("") to (")
954 * @param char* String to be modified
955 * @return char* Modified string.
956*/
957static char*
958un_escape(char *str)
959{
960 gchar quote = '"';
961 gchar *newStr = NULL, *tmpstr = str;
962 int n = strlen (str), i;
963 newStr = g_malloc (n + 1);
964 memset (newStr, 0, n + 1);
965
966 for (i = 0; *tmpstr != '\0'; ++i, ++tmpstr)
967 {
968 newStr[i] = *tmpstr == quote ? *(++tmpstr) : *(tmpstr);
969 if (*tmpstr == '\0')
970 break;
971 }
972 g_free (str);
973 return newStr;
974}
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