/* HomeBank -- Free, easy, personal accounting for everyone. * Copyright (C) 1995-2019 Maxime DOYEN * * This file is part of HomeBank. * * HomeBank is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * HomeBank is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "homebank.h" #include "hb-import.h" /****************************************************************************/ /* Debug macros */ /****************************************************************************/ #define MYDEBUG 0 #if MYDEBUG #define DB(x) (x); #else #define DB(x); #endif /* our global datas */ extern struct HomeBank *GLOBALS; extern struct Preferences *PREFS; /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ static void da_import_context_gen_txn_destroy(ImportContext *context) { GList *list; DB( g_print("\n[import] free gen txn list\n") ); list = g_list_first(context->gen_lst_txn); while (list != NULL) { GenTxn *gentxn = list->data; da_gen_txn_free(gentxn); list = g_list_next(list); } g_list_free(context->gen_lst_txn); context->gen_lst_txn = NULL; } static void da_import_context_gen_acc_destroy(ImportContext *context) { GList *list; DB( g_print("\n[import] free gen acc list\n") ); list = g_list_first(context->gen_lst_acc); while (list != NULL) { GenAcc *genacc = list->data; da_gen_acc_free(genacc); list = g_list_next(list); } g_list_free(context->gen_lst_acc); context->gen_lst_acc = NULL; } static void da_import_context_clear(ImportContext *context) { DB( g_print("\n[import] context clear\n") ); da_import_context_gen_txn_destroy(context); da_import_context_gen_acc_destroy(context); context->gen_next_acckey = 1; } void da_import_context_destroy(ImportContext *context) { GList *list; DB( g_print("\n[import] context destroy\n") ); da_import_context_gen_txn_destroy(context); da_import_context_gen_acc_destroy(context); DB( g_print(" free gen file list\n") ); list = g_list_first(context->gen_lst_file); while (list != NULL) { GenFile *genfile = list->data; da_gen_file_free(genfile); list = g_list_next(list); } g_list_free(context->gen_lst_file); context->gen_lst_file = NULL; } void da_import_context_new(ImportContext *context) { context->gen_lst_file = NULL; context->gen_lst_acc = NULL; context->gen_lst_txn = NULL; context->gen_next_acckey = 1; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ GenFile * da_gen_file_malloc(void) { return g_malloc0(sizeof(GenFile)); } void da_gen_file_free(GenFile *genfile) { if(genfile != NULL) { if(genfile->filepath != NULL) g_free(genfile->filepath); g_free(genfile); } } GenFile * da_gen_file_get(GList *lst_file, guint32 key) { GenFile *existfile = NULL; GList *list; list = g_list_first(lst_file); while (list != NULL) { GenFile *genfile = list->data; if( key == genfile->key ) { existfile = genfile; break; } list = g_list_next(list); } return existfile; } static GenFile * da_gen_file_get_by_name(GList *lst_file, gchar *filepath) { GenFile *existfile = NULL; GList *list; DB( g_print("da_gen_file_get_by_name\n") ); list = g_list_first(lst_file); while (list != NULL) { GenFile *genfile = list->data; DB( g_print(" strcasecmp '%s' '%s'\n", filepath, genfile->filepath) ); if(!strcasecmp(filepath, genfile->filepath)) { existfile = genfile; DB( g_print(" found\n") ); break; } list = g_list_next(list); } return existfile; } GenFile * da_gen_file_append_from_filename(ImportContext *ictx, gchar *filename) { GenFile *genfile = NULL; gint filetype; //todo: should check if its a file !! filetype = homebank_alienfile_recognize(filename); DB( g_print(" - filename '%s', type is %d\n", filename, filetype ) ); // we keep everything here //if( (filetype == FILETYPE_OFX) || (filetype == FILETYPE_QIF) || (filetype == FILETYPE_CSV_HB) ) //{ GenFile *existgenfile; existgenfile = da_gen_file_get_by_name(ictx->gen_lst_file, filename); if(existgenfile == NULL) { genfile = da_gen_file_malloc(); genfile->filepath = g_strdup(filename); genfile->filetype = filetype; //append to list genfile->key = g_list_length (ictx->gen_lst_file) + 1; ictx->gen_lst_file = g_list_append(ictx->gen_lst_file, genfile); } //} return genfile; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ GenAcc * da_gen_acc_malloc(void) { return g_malloc0(sizeof(GenAcc)); } void da_gen_acc_free(GenAcc *genacc) { if(genacc != NULL) { if(genacc->name != NULL) g_free(genacc->name); if(genacc->number != NULL) g_free(genacc->number); g_free(genacc); } } GenAcc * da_gen_acc_get_by_key(GList *lst_acc, guint32 key) { GenAcc *existacc = NULL; GList *list; list = g_list_first(lst_acc); while (list != NULL) { GenAcc *genacc = list->data; if( key == genacc->key ) { existacc = genacc; break; } list = g_list_next(list); } return existacc; } static GenAcc * da_gen_acc_get_by_name(GList *lst_acc, gchar *name) { GenAcc *existacc = NULL; GList *list; //DB( g_print("da_gen_acc_get_by_name\n") ); list = g_list_first(lst_acc); while (list != NULL) { GenAcc *genacc = list->data; //DB( g_print(" strcasecmp '%s' '%s'\n", name, genacc->name) ); if(!strcasecmp(name, genacc->name)) { existacc = genacc; //DB( g_print(" found\n") ); break; } list = g_list_next(list); } return existacc; } Account * hb_import_acc_find_existing(gchar *name, gchar *number) { Account *retacc = NULL; GList *lacc, *list; DB( g_print("\n[import] acc_find_existing\n") ); DB( g_print(" - search number '%s'\n", number) ); lacc = list = g_hash_table_get_values(GLOBALS->h_acc); while (list != NULL) { Account *acc = list->data; //DB( g_print(" - eval acc '%s' or '%s'\n", acc->name, acc->number) ); if(number != NULL && acc->number && strlen(acc->number) ) { //prefer identifying with number & search number into acc->number if(g_strstr_len(number, -1, acc->number) != NULL) { DB( g_print(" - match number '%s'\n", acc->number) ); retacc = acc; break; } } list = g_list_next(list); } //# 1815964 only test name if all number test failed //if not found try with name if(retacc == NULL) { DB( g_print(" - search name '%s'\n", name) ); list = g_list_first(lacc); while (list != NULL) { Account *acc = list->data; //DB( g_print(" - eval acc '%s' or '%s'\n", acc->name, acc->number) ); if(retacc == NULL && name != NULL) { if(g_strstr_len(name, -1, acc->name) != NULL) { DB( g_print(" - match name '%s'\n", acc->name) ); retacc = acc; break; } } list = g_list_next(list); } } g_list_free(lacc); return retacc; } GenAcc * hb_import_gen_acc_get_next(ImportContext *ictx, gint filetype, gchar *name, gchar *number) { GenAcc *newacc; DB( g_print("\n[import] acc_get_next\n") ); DB( g_print(" - type='%d', name='%s', number='%s'\n", filetype, name, number) ); // try to find a same name account if( name != NULL ) { newacc = da_gen_acc_get_by_name(ictx->gen_lst_acc, name); if(newacc != NULL) { DB( g_print(" - found existing '%s'\n", name) ); goto end; } } newacc = da_gen_acc_malloc(); if(newacc) { newacc->kfile = ictx->curr_kfile; newacc->key = ictx->gen_next_acckey++; newacc->kacc = DST_ACC_GLOBAL; if(name != NULL) { newacc->is_unamed = FALSE; newacc->name = g_strdup(name); } else { GenFile *genfile; gchar *basename; newacc->is_unamed = TRUE; genfile = da_gen_file_get (ictx->gen_lst_file, newacc->kfile); basename = g_path_get_basename(genfile->filepath); newacc->name = g_strdup_printf("%s %d", basename, newacc->key); g_free(basename); } if(number != NULL) newacc->number = g_strdup(number); ictx->gen_lst_acc = g_list_append(ictx->gen_lst_acc, newacc); } DB( g_print(" - create new '%s'\n", newacc->name) ); end: newacc->filetype = filetype; ictx->curr_kacc = newacc->key; return newacc; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =*/ GenTxn * da_gen_txn_malloc(void) { return g_malloc0(sizeof(GenTxn)); } void da_gen_txn_free(GenTxn *gentxn) { gint i; if(gentxn != NULL) { if(gentxn->account != NULL) g_free(gentxn->account); if(gentxn->rawinfo != NULL) g_free(gentxn->rawinfo); if(gentxn->rawpayee != NULL) g_free(gentxn->rawpayee); if(gentxn->rawmemo != NULL) g_free(gentxn->rawmemo); if(gentxn->date != NULL) g_free(gentxn->date); if(gentxn->info != NULL) g_free(gentxn->info); if(gentxn->payee != NULL) g_free(gentxn->payee); if(gentxn->memo != NULL) g_free(gentxn->memo); if(gentxn->category != NULL) g_free(gentxn->category); if(gentxn->tags != NULL) g_free(gentxn->tags); for(i=0;isplits[i]; if(s->memo != NULL) g_free(s->memo); if(s->category != NULL) g_free(s->category); } if(gentxn->lst_existing != NULL) { g_list_free(gentxn->lst_existing); gentxn->lst_existing = NULL; } g_free(gentxn); } } static gint da_gen_txn_compare_func(GenTxn *a, GenTxn *b) { gint retval = (gint)(a->julian - b->julian); if(!retval) retval = (ABS(a->amount) - ABS(b->amount)) > 0 ? 1 : -1; return (retval); } GList * da_gen_txn_sort(GList *list) { return( g_list_sort(list, (GCompareFunc)da_gen_txn_compare_func)); } void da_gen_txn_move(GenTxn *sgentxn, GenTxn *dgentxn) { if(sgentxn != NULL && dgentxn != NULL) { memcpy(dgentxn, sgentxn, sizeof(GenTxn)); memset(sgentxn, 0, sizeof(GenTxn)); } } void da_gen_txn_append(ImportContext *ctx, GenTxn *gentxn) { gentxn->kfile = ctx->curr_kfile; gentxn->kacc = ctx->curr_kacc; gentxn->to_import = TRUE; ctx->gen_lst_txn = g_list_append(ctx->gen_lst_txn, gentxn); } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ static void _string_utf8_ucfirst(gchar **str) { gint str_len; gchar *first, *lc; if( *str == NULL ) return; str_len = strlen(*str); if( str_len <= 1 ) return; first = g_utf8_strup(*str, 1); lc = g_utf8_strdown( g_utf8_next_char(*str), -1 ); g_free(*str); *str = g_strjoin(NULL, first, lc, NULL); g_free(first); g_free(lc); } static gchar * _string_concat(gchar *str, gchar *addon) { gchar *retval; DB( g_print(" - concat '%s' + '%s'\n", str, addon) ); if(str == NULL) retval = g_strdup(addon); else { retval = g_strjoin(" ", str, addon, NULL); g_free(str); } DB( g_print(" - retval='%s'\n", retval) ); return retval; } /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ gchar *hb_import_filetype_char_get(GenAcc *genacc) { gchar *retval = ""; switch(genacc->filetype) { #ifndef NOOFX case FILETYPE_OFX: retval = "OFX/QFX"; break; #endif case FILETYPE_QIF: retval = "QIF"; break; case FILETYPE_CSV_HB: retval = "CSV"; break; } return retval; } void hb_import_load_all(ImportContext *ictx) { GList *list; DB( g_print("\n[import] load all\n") ); da_import_context_clear (ictx); list = g_list_first(ictx->gen_lst_file); while (list != NULL) { GenFile *genfile = list->data; if(genfile->filetype != FILETYPE_UNKNOWN) { //todo: move this to alien analysis genfile->encoding = homebank_file_getencoding(genfile->filepath); ictx->curr_kfile = genfile->key; DB( g_print(" -> key = '%d'\n", genfile->key) ); DB( g_print(" -> filepath = '%s'\n", genfile->filepath) ); DB( g_print(" -> encoding = '%s'\n", genfile->encoding) ); genfile->loaded = FALSE; genfile->invaliddatefmt = FALSE; switch(genfile->filetype) { #ifndef NOOFX case FILETYPE_OFX: homebank_ofx_import(ictx, genfile); break; #endif case FILETYPE_QIF: homebank_qif_import(ictx, genfile); break; case FILETYPE_CSV_HB: homebank_csv_import(ictx, genfile); break; } genfile->loaded = TRUE; } list = g_list_next(list); } // sort by date ictx->gen_lst_txn = da_gen_txn_sort(ictx->gen_lst_txn); } gint hb_import_gen_acc_count_txn(ImportContext *ictx, GenAcc *genacc) { GList *list; gint count = 0; DB( g_print("\n[import] gen_acc_count_txn\n") ); genacc->n_txnall = 0; genacc->n_txnimp = 0; list = g_list_first(ictx->gen_lst_txn); while (list != NULL) { GenTxn *gentxn = list->data; if(gentxn->kacc == genacc->key) { genacc->n_txnall++; count++; DB( g_print(" count %03d: gentxn in=%d dup=%d '%s'\n", count, gentxn->to_import, gentxn->is_dst_similar, gentxn->memo) ); if(gentxn->to_import) genacc->n_txnimp++; } list = g_list_next(list); } return count; } /** * uncheck duplicate within the import context files */ gint hb_import_gen_txn_check_duplicate(ImportContext *ictx, GenAcc *genacc) { GList *list1, *list2; gint count = 0; DB( g_print("\n[import] gen_txn_check_duplicate\n") ); list1 = g_list_first(ictx->gen_lst_txn); while (list1 != NULL) { GenTxn *gentxn1 = list1->data; if( (genacc->key == gentxn1->kacc) && (gentxn1->julian != 0) ) //same account, valid date { list2 = g_list_next(list1); while (list2 != NULL) { GenTxn *gentxn2 = list2->data; if( (gentxn2->julian > gentxn1->julian) ) break; //todo: maybe reinforce controls here if( (gentxn2->kacc == gentxn1->kacc) && (gentxn2->julian == gentxn1->julian) && (gentxn2->amount == gentxn1->amount) && (hb_string_compare(gentxn2->memo, gentxn1->memo) == 0) && (hb_string_compare(gentxn2->payee, gentxn1->payee) == 0) ) { gentxn1->to_import = FALSE; gentxn1->is_imp_similar = TRUE; count++; DB( g_print(" found import dup %d=%d %.2f %.2f in=%d dup=%d\n", gentxn1->julian, gentxn2->julian, gentxn2->amount, gentxn1->amount, gentxn1->to_import, gentxn1->is_imp_similar) ); } list2 = g_list_next(list2); } } list1 = g_list_next(list1); } return count; } /** * uncheck existing txn into target account * */ gint hb_import_gen_txn_check_target_similar(ImportContext *ictx, GenAcc *genacc) { GList *list1, *list2; gint count = 0; DB( g_print("\n[import] gen_txn_check_target_similar\n") ); list1 = g_list_first(ictx->gen_lst_txn); while (list1 != NULL) { GenTxn *gentxn = list1->data; if(genacc->key == gentxn->kacc) { gentxn->to_import = TRUE; gentxn->is_dst_similar = FALSE; if(genacc->kacc == DST_ACC_SKIP) { gentxn->to_import = FALSE; } else { Account *acc = da_acc_get(genacc->kacc); if(acc != NULL) { //clear previous existing if(gentxn->lst_existing != NULL) { g_list_free(gentxn->lst_existing); gentxn->lst_existing = NULL; } // try to find existing transaction list2 = g_queue_peek_tail_link(acc->txn_queue); while (list2 != NULL) { Transaction *txn = list2->data; //break if the date goes below the gentxn date + gap if( txn->date < (gentxn->julian - ictx->opt_daygap) ) break; //#1586211 add of date tolerance //todo: maybe reinforce controls here if( ( txn->kacc == genacc->kacc ) && ( gentxn->julian <= (txn->date + ictx->opt_daygap) ) && ( gentxn->julian >= (txn->date - ictx->opt_daygap) ) && ( txn->amount == gentxn->amount ) ) { gentxn->lst_existing = g_list_append(gentxn->lst_existing, txn); gentxn->to_import = FALSE; gentxn->is_dst_similar = TRUE; count++; DB( g_print(" found dst acc dup %d %.2f '%s' in=%d, dup=%d\n", gentxn->julian, gentxn->amount, gentxn->memo, gentxn->to_import, gentxn->is_dst_similar) ); } list2 = g_list_previous(list2); } } } } list1 = g_list_next(list1); } return count; } /** * try to indentify xfer for OFX * */ static gint hb_import_gen_xfer_eval(ImportContext *ictx, GList *list) { GList *root, *list1, *list2; GList *match = NULL; gint count = 0; DB( g_print("\n[import] gen xfer eval\n") ); root = list1 = g_list_first(list); while (list1 != NULL) { Transaction *txn1 = list1->data; GenAcc *acc; acc = da_gen_acc_get_by_key(ictx->gen_lst_acc, txn1->kacc); DB( g_print(" src: kacc:%d dat:%d amt:%.2f %s kfxacc:%d\n", txn1->kacc, txn1->date, txn1->amount, txn1->memo, txn1->kxferacc) ); if( (acc != NULL) && (acc->filetype == FILETYPE_OFX) ) { match = NULL; count = 0; list2 = g_list_next(root); while (list2 != NULL) { Transaction *txn2 = list2->data; //DB( g_print(" -- chk: kacc:%d dat:%d amt:%.2f %s\n", txn2->kacc, txn2->date, txn2->amount, txn2->memo) ); if( (txn2->date > txn1->date) ) break; if( (txn2 == txn1) || (txn2->paymode == PAYMODE_INTXFER) ) goto next; //todo: maybe reinforce controls here if( (txn2->kacc != txn1->kacc) && (txn2->date == txn1->date) && (txn2->amount == -txn1->amount) && (hb_string_compare(txn2->memo, txn1->memo) == 0) ) { DB( g_print(" match: kacc:%d dat:%d amt:%.2f %s kfxacc:%d\n", txn2->kacc, txn2->date, txn2->amount, txn2->memo, txn2->kxferacc) ); match = g_list_append(match, txn2); count++; } next: list2 = g_list_next(list2); } if(count == 1) //we found a single potential xfer, transform it { Transaction *txn2 ; DB( g_print(" single found => convert both\n") ); list2 = g_list_first(match); txn2 = list2->data; txn1->paymode = PAYMODE_INTXFER; transaction_xfer_change_to_child(txn1, txn2); /*list2 = g_list_first(match); txn2 = list2->data; txn1->paymode = PAYMODE_INTXFER; txn1->kxferacc = txn2->kacc; txn2->paymode = PAYMODE_INTXFER; txn2->kxferacc = txn1->kacc; */ } // if more than one, we cannot be sure g_list_free(match); } list1 = g_list_next(list1); } return count; } /** * apply the user option: date format, payee/memo/info mapping * */ gboolean hb_import_option_apply(ImportContext *ictx, GenAcc *genacc) { GList *list; DB( g_print("\n[import] option apply\n") ); DB( g_print(" - type=%d\n", genacc->filetype) ); genacc->n_txnbaddate = 0; list = g_list_first(ictx->gen_lst_txn); while (list != NULL) { GenTxn *gentxn = list->data; if(gentxn->kacc == genacc->key) { if(genacc->filetype != FILETYPE_OFX) { gentxn->julian = hb_date_get_julian(gentxn->date, ictx->opt_dateorder); if( gentxn->julian == 0 ) { genacc->n_txnbaddate++; } } if(genacc->filetype == FILETYPE_OFX) { DB( g_print(" - ofx option apply\n") ); g_free(gentxn->payee); g_free(gentxn->memo); g_free(gentxn->info); gentxn->payee = NULL; gentxn->memo = NULL; gentxn->info = NULL; // OFX:check_number gentxn->info = g_strdup(gentxn->rawinfo); //#1791482 map name to info (concat only) switch(ictx->opt_ofxname) { //ofxname is stored into rawpayee case 1: gentxn->memo = g_strdup(gentxn->rawpayee); break; case 2: gentxn->payee = g_strdup(gentxn->rawpayee); break; case 3: g_free(gentxn->info); gentxn->info = _string_concat(gentxn->rawinfo, gentxn->rawpayee); break; } if(gentxn->rawmemo != NULL) { switch(ictx->opt_ofxmemo) { //case 0: ignore case 1: //add to info gentxn->info = _string_concat(gentxn->info, gentxn->rawmemo); break; case 2: //add to memo gentxn->memo = _string_concat(gentxn->memo, gentxn->rawmemo); break; case 3: //add to payee gentxn->payee = _string_concat(gentxn->payee, gentxn->rawmemo); break; } } DB( g_print(" - payee is '%s'\n", gentxn->payee) ); DB( g_print(" - memo is '%s'\n", gentxn->memo) ); DB( g_print(" - info is '%s'\n", gentxn->info) ); DB( g_print("\n") ); } else if(genacc->filetype == FILETYPE_QIF) { DB( g_print(" - qif option apply\n") ); g_free(gentxn->payee); g_free(gentxn->memo); gentxn->payee = NULL; gentxn->memo = NULL; if(!ictx->opt_qifswap) { gentxn->payee = g_strdup(gentxn->rawpayee); if(ictx->opt_qifmemo) gentxn->memo = g_strdup(gentxn->rawmemo); } else { gentxn->payee = g_strdup(gentxn->rawmemo); if(ictx->opt_qifmemo) gentxn->memo = g_strdup(gentxn->rawpayee); } DB( g_print(" - payee is '%s'\n", gentxn->payee) ); DB( g_print(" - memo is '%s'\n", gentxn->memo) ); } else if(genacc->filetype == FILETYPE_CSV_HB) { DB( g_print(" - csv option apply\n") ); //#1791656 missing: info, payee and tagsg_freg_free(gentxn->payee); g_free(gentxn->payee); g_free(gentxn->memo); g_free(gentxn->info); gentxn->payee = g_strdup(gentxn->rawpayee); gentxn->memo = g_strdup(gentxn->rawmemo); gentxn->info = g_strdup(gentxn->rawinfo); } //at last do ucfirst if( (ictx->opt_ucfirst == TRUE) ) { _string_utf8_ucfirst(&gentxn->memo); _string_utf8_ucfirst(&gentxn->payee); //category ? } } list = g_list_next(list); } DB( g_print(" - nb_err=%d\n", genacc->n_txnbaddate) ); return genacc->n_txnbaddate == 0 ? TRUE : FALSE; } /** * convert a GenTxn to a Transaction * */ Transaction * hb_import_convert_txn(GenAcc *genacc, GenTxn *gentxn) { Transaction *newope; Account *accitem; Payee *payitem; Category *catitem; gint nsplit; DB( g_print("\n[import] convert txn\n") ); newope = NULL; DB( g_print(" - gentxt %s %s %s\n", gentxn->account, gentxn->date, gentxn->memo) ); DB( g_print(" - genacc '%s' '%p'\n", gentxn->account, genacc) ); if( genacc != NULL) { newope = da_transaction_malloc(); newope->kacc = genacc->kacc; newope->date = gentxn->julian; newope->paymode = gentxn->paymode; newope->info = g_strdup(gentxn->info); newope->memo = g_strdup(gentxn->memo); newope->amount = gentxn->amount; if(newope->amount > 0) newope->flags |= OF_INCOME; //#773282 invert amount for ccard accounts //todo: manage this (qif), it is not set to true anywhere //if(ictx->is_ccard) // gentxn->amount *= -1; // payee + append if( gentxn->payee != NULL ) { payitem = da_pay_get_by_name(gentxn->payee); if(payitem == NULL) { //DB( g_print(" -> append pay: '%s'\n", item->payee ) ); payitem = da_pay_malloc(); payitem->name = g_strdup(gentxn->payee); //payitem->imported = TRUE; da_pay_append(payitem); //ictx->cnt_new_pay += 1; } newope->kpay = payitem->key; } // LCategory of transaction // L[Transfer account name] // LCategory of transaction/Class of transaction // L[Transfer account]/Class of transaction if( gentxn->category != NULL ) { if(g_str_has_prefix(gentxn->category, "[")) // this is a transfer account name { gchar *accname; //DB ( g_print(" -> transfer to: '%s'\n", item->category) ); //remove brackets accname = hb_strdup_nobrackets(gentxn->category); // search target account + append if not exixts accitem = da_acc_get_by_name(accname); if(accitem == NULL) { DB( g_print(" -> append int xfer dest acc: '%s'\n", accname ) ); accitem = da_acc_malloc(); accitem->name = g_strdup(accname); //accitem->imported = TRUE; //accitem->imp_name = g_strdup(accname); da_acc_append(accitem); } newope->kxferacc = accitem->key; newope->paymode = PAYMODE_INTXFER; g_free(accname); } else { //DB ( g_print(" -> append cat: '%s'\n", item->category) ); catitem = da_cat_append_ifnew_by_fullname(gentxn->category); if( catitem != NULL ) { //ictx->cnt_new_cat += 1; newope->kcat = catitem->key; } } } //#1791656 miss tags also... if( gentxn->tags != NULL ) { g_free(newope->tags); newope->tags = tags_parse(gentxn->tags); } // splits, if not a xfer if( gentxn->paymode != PAYMODE_INTXFER ) { if( gentxn->nb_splits > 0 ) { newope->splits = da_split_new(); for(nsplit=0;nsplitnb_splits;nsplit++) { GenSplit *s = &gentxn->splits[nsplit]; Split *hbs; guint32 kcat = 0; DB( g_print(" -> append split %d: '%s' '%.2f' '%s'\n", nsplit, s->category, s->amount, s->memo) ); if( s->category != NULL ) { catitem = da_cat_append_ifnew_by_fullname(s->category); if( catitem != NULL ) { kcat = catitem->key; } } //todo: remove this when no more use || hb_string_replace_char('|', s->memo); hbs = da_split_malloc (); hbs->kcat = kcat; hbs->memo = g_strdup(s->memo); hbs->amount = s->amount; da_splits_append(newope->splits, hbs); hbs = NULL; } } } newope->flags |= OF_ADDED; if( newope->amount > 0 ) newope->flags |= OF_INCOME; if( gentxn->reconciled ) newope->status = TXN_STATUS_RECONCILED; else if( gentxn->cleared ) newope->status = TXN_STATUS_CLEARED; } return newope; } void hb_import_apply(ImportContext *ictx) { GList *list, *lacc; GList *txnlist; guint32 kcommon = 0; guint changes = 0; DB( g_print("\n[import] apply\n") ); //create accounts list = g_list_first(ictx->gen_lst_acc); while (list != NULL) { GenAcc *genacc = list->data; DB( g_print(" #1 genacc: %d %s %s => %d\n", genacc->key, genacc->name, genacc->number, genacc->kacc) ); //we do create the common account once if( (genacc->kacc == DST_ACC_GLOBAL) ) { if( kcommon == 0 ) { Account *acc = da_acc_malloc (); acc->name = g_strdup(_("imported account")); if( da_acc_append(acc) ) { kcommon = acc->key; changes++; } } genacc->kacc = kcommon; } else if( (genacc->kacc == DST_ACC_NEW) ) { Account *acc = da_acc_malloc (); acc->name = g_strdup(genacc->name); if( da_acc_append(acc) ) { acc->number = g_strdup(genacc->number); acc->initial = genacc->initial; //store the target acc key genacc->kacc = acc->key; changes++; } } list = g_list_next(list); } // insert every transactions into a temporary list // we do this to keep a finished real txn list for detect xfer below DB( g_print(" #2 insert txn\n") ); txnlist = NULL; lacc = g_list_first(ictx->gen_lst_acc); while (lacc != NULL) { GenAcc *genacc = lacc->data; if(genacc->kacc != DST_ACC_SKIP) { list = g_list_first(ictx->gen_lst_txn); while (list != NULL) { GenTxn *gentxn = list->data; if(gentxn->kacc == genacc->key && gentxn->to_import == TRUE) { Transaction *txn, *dtxn; txn = hb_import_convert_txn(genacc, gentxn); if( txn ) { dtxn = transaction_add(NULL, txn); txnlist = g_list_append(txnlist, dtxn); da_transaction_free(txn); //#1820618 forgot to report changes count changes++; } } list = g_list_next(list); } } lacc = g_list_next(lacc); } //auto assign DB( g_print(" call auto assign\n") ); transaction_auto_assign(txnlist, 0); //check for ofx internal xfer DB( g_print(" call hb_import_gen_xfer_eval\n") ); hb_import_gen_xfer_eval(ictx, txnlist); g_list_free(txnlist); GLOBALS->changes_count += changes; } #if MYDEBUG void _import_context_debug_file_list(ImportContext *ctx) { GList *list; g_print("\n--debug-- file list %d\n", g_list_length(ctx->gen_lst_file) ); list = g_list_first(ctx->gen_lst_file); while (list != NULL) { GenFile *item = list->data; g_print(" genfile: %d '%s' '%s'\ndf=%d invalid=%d\n", item->key, item->filepath, item->encoding, item->datefmt, item->invaliddatefmt); list = g_list_next(list); } } void _import_context_debug_acc_list(ImportContext *ctx) { GList *list; g_print("\n--debug-- acc list %d\n", g_list_length(ctx->gen_lst_acc) ); list = g_list_first(ctx->gen_lst_acc); while (list != NULL) { GenAcc *item = list->data; g_print(" genacc: %d %s %s => %d\n", item->key, item->name, item->number, item->kacc); list = g_list_next(list); } } void _import_context_debug_txn_list(ImportContext *ctx) { GList *list; g_print("\n--debug-- txn list %d\n", g_list_length(ctx->gen_lst_txn) ); list = g_list_first(ctx->gen_lst_txn); while (list != NULL) { GenTxn *item = list->data; g_print(" gentxn: (%d) %s %s (%d) %s %.2f\n", item->kfile, item->account, item->date, item->julian, item->memo, item->amount); list = g_list_next(list); } } #endif