//! \brief try to fix some common errors in the csv representation of invoices //! * corrects the date format //! * corrects ambigous values in multi line invoices //! * ensures customer exists //! * if quantity is unset, set to 1 //! * if price is unset, delete row void gnc_bi_import_fix_bis (GtkListStore * store, guint * fixed, guint * deleted, GString * info, gchar *type) { GtkTreeIter iter; gboolean valid, row_deleted, row_fixed; gchar *id, *date_opened, *date_posted, *due_date, *owner_id, *date, *quantity, *price; GString *prev_id, *prev_date_opened, *prev_date_posted, *prev_owner_id, *prev_date; // needed to fix multi line invoices guint dummy; gint row = 1; const gchar* date_format_string = qof_date_format_get_string (qof_date_format_get()); // Get the user set date format string //date_format_string = qof_date_format_get_string (qof_date_format_get()); DEBUG("date_format_string: %s",date_format_string); // allow the call to this function with only GtkListeStore* specified if (!fixed) fixed = &dummy; if (!deleted) deleted = &dummy; *fixed = 0; *deleted = 0; // init strings prev_id = g_string_new (""); prev_date_opened = g_string_new (""); prev_date_posted = g_string_new (""); prev_owner_id = g_string_new (""); prev_date = g_string_new (""); valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); while (valid) { row_deleted = FALSE; row_fixed = FALSE; // Walk through the list, reading each row gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, DATE_OPENED, &date_opened, DATE_POSTED, &date_posted, DUE_DATE, &due_date, OWNER_ID, &owner_id, DATE, &date, QUANTITY, &quantity, PRICE, &price, -1); if (strlen (price) == 0) { // invalid row (no price given) // no fix possible -> delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, PRICE_NOT_SET: id=%s\n"), row, id); } // TODO: QTY get set to 1 later if field is empty. Delete this section? else if (strlen (quantity) == 0) { // invalid row (no quantity given) // no fix possible -> delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, QTY_NOT_SET: id=%s\n"), row, id); } else { // TODO: If id is empty get the next one in the series. Bug 731105 if (strlen (id) == 0) { // no invoice id specified if (prev_id->len == 0) { // cannot fix -> delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, ID_NOT_SET\n"), row); } else { // this is a fixable multi line invoice gtk_list_store_set (store, &iter, ID, prev_id->str, -1); row_fixed = TRUE; } } else { // remember invoice id (to be able to fix multi line invoices) g_string_assign (prev_id, id); // new invoice => reset all other fixable entries g_string_assign (prev_date_opened, ""); g_string_assign (prev_date_posted, ""); g_string_assign (prev_owner_id, ""); g_string_assign (prev_date, ""); } } if (!row_deleted) { // the row is valid (price and id are valid) if(!isDateValid(date_opened)) { if (prev_date_opened->len == 0) { // fix this by using the current date gchar temp[20]; GDate date; g_date_clear (&date, 1); gnc_gdate_set_today (&date); g_date_strftime (temp, 20, date_format_string, &date); // Create a user specified date string. g_string_assign (prev_date_opened, temp); } // fix this by using the previous date_opened value (multi line invoice) gtk_list_store_set (store, &iter, DATE_OPENED, prev_date_opened->str, -1); row_fixed = TRUE; } else { // remember date_opened (to be able to fix multi line invoices) g_string_assign (prev_date_opened, date_opened); } // date_opened is valid if(!isDateValid(date_posted)) { if (prev_date_posted->len == 0) { // this invoice will have to get posted manually } else { // multi line invoice => fix it gtk_list_store_set (store, &iter, DATE_POSTED, prev_date_posted->str, -1); row_fixed = TRUE; } } else { // remember date_opened (to be able to fix multi line invoices) g_string_assign (prev_date_posted, date_posted); } // date_posted is valid /* // Check if due date is valid. Set it to date_posted if not valid or missing. if(!isDateValid(due_date)) { gtk_list_store_set (store, &iter, DUE_DATE, date_posted, -1); row_fixed = TRUE; } // due_date is valid */ if (strlen (quantity) == 0) { // quantity is unset => set to 1 gtk_list_store_set (store, &iter, QUANTITY, "1", -1); row_fixed = TRUE; } // quantity is valid if (strlen (owner_id) == 0) { if (prev_owner_id->len == 0) { // no customer given and not fixable => delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, OWNER_NOT_SET: id=%s\n"), row, id); } else { gtk_list_store_set (store, &iter, owner_id, prev_owner_id->str, -1); row_fixed = TRUE; } } else { // remember owner_id g_string_assign (prev_owner_id, owner_id); } if (g_ascii_strcasecmp (type, "BILL") == 0) { // BILL: check, if vendor exists if (!gnc_search_vendor_on_id (gnc_get_current_book (), prev_owner_id->str)) { // vendor not found => delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, VENDOR_DOES_NOT_EXIST: id=%s\n"), row, id); } } else if (g_ascii_strcasecmp (type, "INVOICE") == 0) { // INVOICE: check, if customer exists if (!gnc_search_customer_on_id (gnc_get_current_book (), prev_owner_id->str)) { // customer not found => delete row valid = gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW %d DELETED, CUSTOMER_DOES_NOT_EXIST: id=%s\n"), row, id); } } // owner_id is valid } g_free (id); g_free (date_opened); g_free (date_posted); g_free (owner_id); g_free (date); g_free (quantity); g_free (price); if (row_deleted) { (*deleted)++; // reset all remembered values g_string_assign (prev_id, ""); g_string_assign (prev_date_opened, ""); g_string_assign (prev_date_posted, ""); g_string_assign (prev_owner_id, ""); g_string_assign (prev_date, ""); } else if (row_fixed) (*fixed)++; if (!row_deleted) valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); row++; } // deallocate strings g_string_free (prev_id, TRUE); g_string_free (prev_date_opened, TRUE); g_string_free (prev_date_posted, TRUE); g_string_free (prev_owner_id, TRUE); g_string_free (prev_date, TRUE); if (info && (info->len > 0)) { g_string_prepend (info, "\n\n"); g_string_prepend (info, _("These rows were deleted:")); } }
/*********************************************************************** * @todo Maybe invoice checking should be done in gnc_bi_import_fix_bis (...) * rather than in here? But that is more concerned with ensuring the csv is consistent. * @param GtkListStore *store * @param guint *n_invoices_created * @param guint *n_invoices_updated * @return void ***********************************************************************/ void gnc_bi_import_create_bis (GtkListStore * store, QofBook * book, guint * n_invoices_created, guint * n_invoices_updated, gchar * type, gchar * open_mode, GString * info) { gboolean valid; GtkTreeIter iter; gchar *id, *date_opened, *owner_id, *billing_id, *notes; gchar *date, *desc, *action, *account, *quantity, *price, *disc_type, *disc_how, *discount, *taxable, *taxincluded, *tax_table; gchar *date_posted, *due_date, *account_posted, *memo_posted, *accumulatesplits; guint dummy; GncInvoice *invoice; GncEntry *entry; gint day, month, year; gnc_numeric value; GncOwner *owner; Account *acc; enum update {YES = GTK_RESPONSE_YES, NO = GTK_RESPONSE_NO} update; GtkWidget *dialog; Timespec today; InvoiceWindow *iw; gchar *new_id = NULL; gint64 denom = 0; gnc_commodity *currency; // these arguments are needed g_return_if_fail (store && book); // logic of this function only works for bills or invoices g_return_if_fail ((g_ascii_strcasecmp (type, "INVOICE") == 0) || (g_ascii_strcasecmp (type, "BILL") == 0)); // allow to call this function without statistics if (!n_invoices_created) n_invoices_created = &dummy; if (!n_invoices_updated) n_invoices_updated = &dummy; *n_invoices_created = 0; *n_invoices_updated = 0; invoice = NULL; update = NO; valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); while (valid) { // Walk through the list, reading each row gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, DATE_OPENED, &date_opened, DATE_POSTED, &date_posted, // if autoposting requested DUE_DATE, &due_date, // if autoposting requested ACCOUNT_POSTED, &account_posted, // if autoposting requested MEMO_POSTED, &memo_posted, // if autoposting requested ACCU_SPLITS, &accumulatesplits, // if autoposting requested OWNER_ID, &owner_id, BILLING_ID, &billing_id, NOTES, ¬es, DATE, &date, DESC, &desc, ACTION, &action, ACCOUNT, &account, QUANTITY, &quantity, PRICE, &price, DISC_TYPE, &disc_type, DISC_HOW, &disc_how, DISCOUNT, &discount, TAXABLE, &taxable, TAXINCLUDED, &taxincluded, TAX_TABLE, &tax_table, -1); // TODO: Assign a new invoice number if one is absent. BUT we don't want to assign a new invoice for every line!! // so we'd have to flag this up somehow or add an option in the import GUI. The former implies that we make // an assumption about what the importer (person) wants to do. It seems reasonable that a CSV file full of items with // If an invoice exists then we add to it in this current schema. // no predefined invoice number is a new invoice that's in need of a new number. // This was not designed to satisfy the need for repeat invoices however, so maybe we need a another method for this, after all // It should be easier to copy an invoice with a new ID than to go through all this malarky. if (g_ascii_strcasecmp (type, "BILL") == 0) invoice = gnc_search_bill_on_id (book, id); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) invoice = gnc_search_invoice_on_id (book, id); DEBUG( "Existing %s ID: %s\n", type, gncInvoiceGetID(invoice)); // If the search is empty then there is no existing invoice so make a new one if (invoice == NULL) { DEBUG( "Creating a new : %s\n", type ); // new invoice invoice = gncInvoiceCreate (book); /* Protect against thrashing the DB and trying to write the invoice * record prematurely */ gncInvoiceBeginEdit (invoice); gncInvoiceSetID (invoice, id); owner = gncOwnerNew (); if (g_ascii_strcasecmp (type, "BILL") == 0) gncOwnerInitVendor (owner, gnc_search_vendor_on_id (book, owner_id)); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) gncOwnerInitCustomer (owner, gnc_search_customer_on_id (book, owner_id)); gncInvoiceSetOwner (invoice, owner); gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (owner)); // Set the invoice currency based on the owner if (strlen (date_opened) != 0) // If a date is specified in CSV { // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date_opened, &day, &month, &year); gncInvoiceSetDateOpened (invoice, gnc_dmy2timespec (day, month, year)); } else // If no date in CSV { time64 now = gnc_time (NULL); Timespec now_timespec; timespecFromTime64 (&now_timespec, now); gncInvoiceSetDateOpened (invoice, now_timespec); } gncInvoiceSetBillingID (invoice, billing_id ? billing_id : ""); gncInvoiceSetNotes (invoice, notes ? notes : ""); gncInvoiceSetActive (invoice, TRUE); //if (g_ascii_strcasecmp(type,"INVOICE"))gncInvoiceSetBillTo( invoice, billto ); (*n_invoices_created)++; update = YES; // open new bill / invoice in a tab, if requested if (g_ascii_strcasecmp(open_mode, "ALL") == 0 || (g_ascii_strcasecmp(open_mode, "NOT_POSTED") == 0 && strlen(date_posted) == 0)) { iw = gnc_ui_invoice_edit (invoice); gnc_plugin_page_invoice_new (iw); } gncInvoiceCommitEdit (invoice); } // I want to warn the user that an existing billvoice exists, but not every // time. // An import can contain many lines usually referring to the same invoice. // NB: Posted invoices are NEVER updated. else // if invoice exists { if (gncInvoiceIsPosted (invoice)) // Is it already posted? { valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); continue; // If already posted then never import } if (update != YES) // Pop up a dialog to ask if updates are the expected action { dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, "%s", _("Are you sure you have bills/invoices to update?")); update = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (update == NO) { // Cleanup and leave g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); return; } } (*n_invoices_updated)++; } // add entry to invoice/bill entry = gncEntryCreate (book); gncEntryBeginEdit(entry); currency = gncInvoiceGetCurrency(invoice); if (currency) denom = gnc_commodity_get_fraction(currency); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date, &day, &month, &year); { GDate *date = g_date_new_dmy(day, month, year); gncEntrySetDateGDate (entry, date); g_date_free (date); } timespecFromTime64 (&today, gnc_time (NULL)); // set today to the current date gncEntrySetDateEntered (entry, today); gncEntrySetDescription (entry, desc); gncEntrySetAction (entry, action); value = gnc_numeric_zero(); gnc_exp_parser_parse (quantity, &value, NULL); gncEntrySetQuantity (entry, value); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account); if (g_ascii_strcasecmp (type, "BILL") == 0) { gncEntrySetBillAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetBillPrice (entry, value); gncEntrySetBillTaxable (entry, text2bool (taxable)); gncEntrySetBillTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetBillTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); gncEntryCommitEdit(entry); gncBillAddEntry (invoice, entry); } else if (g_ascii_strcasecmp (type, "INVOICE") == 0) { gncEntrySetNotes (entry, notes); gncEntrySetInvAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetInvPrice (entry, value); gncEntrySetInvTaxable (entry, text2bool (taxable)); gncEntrySetInvTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetInvTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); value = gnc_numeric_zero(); gnc_exp_parser_parse (discount, &value, NULL); gncEntrySetInvDiscount (entry, value); gncEntrySetInvDiscountType (entry, text2disc_type (disc_type)); gncEntrySetInvDiscountHow (entry, text2disc_how (disc_how)); gncEntryCommitEdit(entry); gncInvoiceAddEntry (invoice, entry); } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); // handle auto posting of invoices if (valid) gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &new_id, -1); if (g_strcmp0 (id, new_id) != 0) { // the next invoice id is different => try to autopost this invoice if (qof_scan_date (date_posted, &day, &month, &year)) { // autopost this invoice gboolean auto_pay; Timespec d1, d2; if (g_ascii_strcasecmp (type, "INVOICE") == 0) auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_INVOICE, GNC_PREF_AUTO_PAY); else auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_BILL, GNC_PREF_AUTO_PAY); d1 = gnc_dmy2timespec (day, month, year); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (due_date, &day, &month, &year); // obtains the due date, or leaves it at date_posted d2 = gnc_dmy2timespec (day, month, year); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account_posted); gncInvoicePostToAccount (invoice, acc, &d1, &d2, memo_posted, text2bool (accumulatesplits), auto_pay); } } } // cleanup g_free (new_id); g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); }
//! \brief try to fix some common errors in the csv representation of invoices //! * corrects the date format //! * corrects ambigous values in multi line invoices //! * ensures customer exists //! * if quantity is unset, set to 1 //! * if price is unset, delete row void gnc_bi_import_fix_bis (GtkListStore * store, guint * fixed, guint * deleted, GString * info, gchar *type) { GtkTreeIter iter; gboolean valid, row_deleted, row_fixed; gchar *id, *date_opened, *date_posted, *owner_id, *date, *quantity, *price; GString *prev_id, *prev_date_opened, *prev_date_posted, *prev_owner_id, *prev_date; // needed to fix multi line invoices guint dummy; // allow the call to this function with only GtkListeStore* specified if (!fixed) fixed = &dummy; if (!deleted) deleted = &dummy; *fixed = 0; *deleted = 0; // init strings prev_id = g_string_new (""); prev_date_opened = g_string_new (""); prev_date_posted = g_string_new (""); prev_owner_id = g_string_new (""); prev_date = g_string_new (""); valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); while (valid) { row_deleted = FALSE; row_fixed = FALSE; // Walk through the list, reading each row gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, DATE_OPENED, &date_opened, DATE_POSTED, &date_posted, OWNER_ID, &owner_id, DATE, &date, QUANTITY, &quantity, PRICE, &price, -1); if (strlen (price) == 0) { // invalid row (no price given) // no fix possible -> delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, PRICE_NOT_SET: id=%s\n"), id); } else if (strlen (quantity) == 0) { // invalid row (no quantity given) // no fix possible -> delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, QTY_NOT_SET: id=%s\n"), id); } else { if (strlen (id) == 0) { // no invoice id specified if (prev_id->len == 0) { // cannot fix -> delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, ID_NOT_SET\n")); } else { // this is a fixable multi line invoice gtk_list_store_set (store, &iter, ID, prev_id->str, -1); row_fixed = TRUE; } } else { // remember invoice id (to be able to fix multi line invoices) g_string_assign (prev_id, id); // new invoice => reset all other fixable entries g_string_assign (prev_date_opened, ""); g_string_assign (prev_date_posted, ""); g_string_assign (prev_owner_id, ""); g_string_assign (prev_date, ""); } } if (!row_deleted) { // the row is valid (price and id are valid) if (strlen (date_opened) == 0) { if (prev_date_opened->len == 0) { // fix this by using the current date (why is this so complicated?) gchar temp[20]; GDate *date; time_t secs; struct tm now; time (&secs); localtime_r (&secs, &now); date = g_date_new_dmy (now.tm_mday, now.tm_mon + 1, now.tm_year + 1900); g_date_strftime (temp, 20, "%x", date); // create a locale specific date string g_string_assign (prev_date_opened, temp); g_date_free (date); } // fix this by using the previous date_opened value (multi line invoice) gtk_list_store_set (store, &iter, DATE_OPENED, prev_date_opened->str, -1); row_fixed = TRUE; } else { // remember date_opened (to be able to fix multi line invoices) g_string_assign (prev_date_opened, date_opened); } // date_opened is valid if (strlen (date_posted) == 0) { if (prev_date_posted->len == 0) { // this invoice will have to get posted manually } else { // multi line invoice => fix it gtk_list_store_set (store, &iter, DATE_POSTED, prev_date_posted->str, -1); row_fixed = TRUE; } } else { // remember date_opened (to be able to fix multi line invoices) g_string_assign (prev_date_posted, date_posted); } // date_posted is valid if (strlen (quantity) == 0) { // quantity is unset => set to 1 gtk_list_store_set (store, &iter, QUANTITY, "1", -1); row_fixed = TRUE; } // quantity is valid if (strlen (owner_id) == 0) { if (prev_owner_id->len == 0) { // no customer given and not fixable => delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, OWNER_NOT_SET: id=%s\n"), id); } else { gtk_list_store_set (store, &iter, owner_id, prev_owner_id->str, -1); row_fixed = TRUE; } } else { // remember owner_id g_string_assign (prev_owner_id, owner_id); } if (g_ascii_strcasecmp (type, "BILL") == 0) { // BILL: check, if vendor exists if (!gnc_search_vendor_on_id (gnc_get_current_book (), prev_owner_id->str)) { // vendor not found => delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, VENDOR_DOES_NOT_EXIST: id=%s\n"), id); } } else if (g_ascii_strcasecmp (type, "INVOICE") == 0) { // INVOICE: check, if customer exists if (!gnc_search_customer_on_id (gnc_get_current_book (), prev_owner_id->str)) { // customer not found => delete row gtk_list_store_remove (store, &iter); row_deleted = TRUE; g_string_append_printf (info, _("ROW DELETED, CUSTOMER_DOES_NOT_EXIST: id=%s\n"), id); } } // owner_id is valid } g_free (id); g_free (date_opened); g_free (date_posted); g_free (owner_id); g_free (date); g_free (quantity); g_free (price); if (row_deleted) { (*deleted)++; // reset all remembered values g_string_assign (prev_id, ""); g_string_assign (prev_date_opened, ""); g_string_assign (prev_date_posted, ""); g_string_assign (prev_owner_id, ""); g_string_assign (prev_date, ""); } else if (row_fixed) (*fixed)++; valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); } // deallocate strings g_string_free (prev_id, TRUE); g_string_free (prev_date_opened, TRUE); g_string_free (prev_date_posted, TRUE); g_string_free (prev_owner_id, TRUE); g_string_free (prev_date, TRUE); if (info && (info->len > 0)) { g_string_prepend (info, "\n\n"); g_string_prepend (info, _("These rows were deleted:")); } }