From 6da33cbe3573c81f284fdd71bf71e4b1466ece46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Soutad=C3=A9?= Date: Sun, 20 Mar 2011 19:08:24 +0100 Subject: [PATCH] First pass for imports (with OFX imports) --- Makefile | 14 -- src/controller/KissCount.cpp | 23 +-- src/controller/KissCount.h | 3 +- src/model/Account.cpp | 34 ++++ src/model/Account.h | 2 + src/model/Database.h | 1 + src/model/Operation.cpp | 48 +++++ src/model/Operation.h | 6 + src/model/User.h | 3 + src/model/import/ImportEngine.cpp | 269 +++++++++++++++++++++++++++ src/model/import/ImportEngine.h | 75 ++++++++ src/model/import/OFXImportEngine.cpp | 153 +++++++++++++++ src/model/import/OFXImportEngine.h | 44 +++++ src/view/ImportPanel.cpp | 246 ++++++++++++++++++++++++ src/view/ImportPanel.h | 66 +++++++ src/view/grid/GridAccount.cpp | 51 ++--- src/view/grid/GridAccount.h | 2 + 17 files changed, 992 insertions(+), 48 deletions(-) create mode 100644 src/model/Account.cpp create mode 100644 src/model/Operation.cpp create mode 100644 src/model/import/ImportEngine.cpp create mode 100644 src/model/import/ImportEngine.h create mode 100644 src/model/import/OFXImportEngine.cpp create mode 100644 src/model/import/OFXImportEngine.h create mode 100644 src/view/ImportPanel.cpp create mode 100644 src/view/ImportPanel.h diff --git a/Makefile b/Makefile index 333d0e7..1833740 100644 --- a/Makefile +++ b/Makefile @@ -23,21 +23,7 @@ endif CXX=$(PREFIX)g++ -# SOURCES=$(wildcard src/model/*.cpp) -# SOURCES+=$(wildcard src/model/import/*.cpp) -# SOURCES+=$(wildcard src/model/export/*.cpp) -# SOURCES+=$(wildcard src/view/*.cpp) -# SOURCES+=$(wildcard src/view/grid/*.cpp) -# SOURCES+=$(wildcard src/controller/*.cpp) -# SOURCES+=$(wildcard src/*.cpp) SOURCES=$(shell find src -name '*.cpp' -type f | tr '\n' ' ') -# HEADERS=$(wildcard src/model/*.h) -# HEADERS+=$(wildcard src/model/import/*.h) -# HEADERS+=$(wildcard src/model/export/*.h) -# HEADERS+=$(wildcard src/view/*.h) -# HEADERS+=$(wildcard src/view/grid/*.h) -# HEADERS+=$(wildcard src/controller/*.h) -# HEADERS+=$(wildcard src/*.h) HEADERS=$(shell find src -name '*.h' -type f) OBJS=$(SOURCES:.cpp=.o) diff --git a/src/controller/KissCount.cpp b/src/controller/KissCount.cpp index f2a0449..e62b21c 100644 --- a/src/controller/KissCount.cpp +++ b/src/controller/KissCount.cpp @@ -174,14 +174,6 @@ wxString KissCount::AddAccount(Account& ac) return ac.id; } -bool sortAccounts(Account ac1, Account ac2) -{ - if (ac1._default) return true; - if (ac2._default) return false; - - return (ac1.name.Cmp(ac2.name) < 0); -} - void KissCount::UpdateAccount(Account& ac) { std::vector::iterator it; @@ -427,6 +419,11 @@ void KissCount::SetOperationOrder(const wxString& order) _db->UpdatePreference(_user, wxT("operation_order")); } +const wxString& KissCount::GetOperationOrder() +{ + return _user->_preferences[wxT("operation_order")] ; +} + std::vector* KissCount::Search(wxString* description, wxDateTime* dateFrom, wxDateTime* dateTo, wxString* amountFrom, wxString* amountTo, std::vector categories, int types, std::vector accounts) @@ -435,7 +432,7 @@ std::vector* KissCount::Search(wxString* description, wxDateTime* dat return _db->Search(_user, description, dateFrom, dateTo, amountFrom, amountTo, categories, types, accounts, true); } -bool KissCount::SearchPreviousOperation(Operation* res, Operation& op, int month, int year) +bool KissCount::SearchPreviousOperation(Operation* res, Operation& op, int month, int year, bool limitToType) { std::vector* operations; wxDateTime* date ; @@ -452,7 +449,11 @@ bool KissCount::SearchPreviousOperation(Operation* res, Operation& op, int month date = new wxDateTime(0, (wxDateTime::Month)month, year); - operations = _db->Search(_user, &op.description, date, NULL, NULL, NULL, v, op.fix_cost ? FIX_OP : NON_FIX_OP, v, false); + if (limitToType) + operations = _db->Search(_user, &op.description, date, NULL, NULL, NULL, v, op.fix_cost ? FIX_OP : NON_FIX_OP, v, false); + else + operations = _db->Search(_user, &op.description, date, NULL, NULL, NULL, v, ALL_OP, v, false); + delete date; @@ -601,7 +602,7 @@ ImportEngine* KissCount::GetImportEngine(wxString path) std::vector::iterator it; for(it=_importEngines.begin(); it!=_importEngines.end(); it++) - if ((*it)->HandleFile(path, _user, _db)) + if ((*it)->HandleFile(path, _user, _db, this)) return *it; return NULL; diff --git a/src/controller/KissCount.h b/src/controller/KissCount.h index 5c8b241..dd52399 100644 --- a/src/controller/KissCount.h +++ b/src/controller/KissCount.h @@ -83,12 +83,13 @@ public: void SetLanguage(wxLanguage language); void SetOperationOrder(const wxString& order); + const wxString& GetOperationOrder(); std::vector* Search(wxString* description, wxDateTime* dateFrom, wxDateTime* dateTo, wxString* amountFrom, wxString* amountTo, std::vector categories, int types, std::vector accounts); - bool SearchPreviousOperation(Operation* res, Operation& op, int month, int year); + bool SearchPreviousOperation(Operation* res, Operation& op, int month, int year, bool limitToType); void GetStats(int monthFrom, int yearFrom, int monthTo, int yearTo, std::map > >* accountAmounts, diff --git a/src/model/Account.cpp b/src/model/Account.cpp new file mode 100644 index 0000000..b207df9 --- /dev/null +++ b/src/model/Account.cpp @@ -0,0 +1,34 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#include "Account.h" + +bool sortAccounts(Account ac1, Account ac2) +{ + if (ac1._default) return true; + if (ac2._default) return false; + + if (!ac1.blocked && ac2.blocked) return true; + if (ac1.blocked && !ac2.blocked) return false; + + if (!ac1._virtual && ac2._virtual) return true; + if (ac1._virtual && !ac2._virtual) return false; + + return (ac1.name.Cmp(ac2.name) < 0); +} diff --git a/src/model/Account.h b/src/model/Account.h index 211759a..3cf953c 100644 --- a/src/model/Account.h +++ b/src/model/Account.h @@ -36,4 +36,6 @@ public: bool _virtual; }; +bool sortAccounts(Account ac1, Account ac2); + #endif diff --git a/src/model/Database.h b/src/model/Database.h index 0d64f1f..cef09a3 100644 --- a/src/model/Database.h +++ b/src/model/Database.h @@ -42,6 +42,7 @@ #define NON_FIX_OP (1 << 1) #define CHECKED_OP (1 << 2) #define NOT_CHECKED_OP (1 << 3) +#define ALL_OP (~0) // if (!_db.CheckSyntax(req)) // { diff --git a/src/model/Operation.cpp b/src/model/Operation.cpp new file mode 100644 index 0000000..6617318 --- /dev/null +++ b/src/model/Operation.cpp @@ -0,0 +1,48 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#include "Operation.h" + +bool sortOperations(Operation op1, Operation op2) +{ + if (!op1.fix_cost && op2.fix_cost) return false; + if (op1.fix_cost && !op2.fix_cost) return true; + + if (op1.year < op2.year) + return true; + else if (op1.year == op2.year) + { + if (op1.month < op2.month) + return true; + else if (op1.month == op2.month) + { + if (op1.day < op2.day) + return true; + else if (op1.day == op2.day) + return (op1.description.Cmp(op2.description) < 0); + } + } + + return false; +} + +bool reverseSortOperations(Operation op1, Operation op2) +{ + return !sortOperations(op1, op2); +} diff --git a/src/model/Operation.h b/src/model/Operation.h index a0f22b5..842ca47 100644 --- a/src/model/Operation.h +++ b/src/model/Operation.h @@ -20,6 +20,9 @@ #ifndef OPERATION_H #define OPERATION_H +#include +#include + class Operation { public: wxString id; @@ -40,4 +43,7 @@ public: std::vector childs; } ; +bool sortOperations(Operation op1, Operation op2); +bool reverseSortOperations(Operation op1, Operation op2); + #endif diff --git a/src/model/User.h b/src/model/User.h index c650dc3..b2847f3 100644 --- a/src/model/User.h +++ b/src/model/User.h @@ -29,8 +29,10 @@ #include "Account.h" #include "Operation.h" #include "Database.h" +#include "import/ImportEngine.h" class Database; +class ImportPattern; class User { @@ -47,6 +49,7 @@ public: std::vector _categories; std::vector _categoriesFonts; std::map _preferences; + std::map _importPatterns; Category GetCategory(const wxString& catId); wxString GetCategoryName(const wxString& catId); diff --git a/src/model/import/ImportEngine.cpp b/src/model/import/ImportEngine.cpp new file mode 100644 index 0000000..134aafd --- /dev/null +++ b/src/model/import/ImportEngine.cpp @@ -0,0 +1,269 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#include "ImportEngine.h" + +ImportEngine::ImportEngine() +{ +} + +ImportEngine::~ImportEngine() +{ +} + +bool ImportEngine::HandleFile(const wxString& path, User* user, Database* db, KissCount* kiss) +{ + _path = path; + _user = user; + _db = db; + _kiss = kiss; + + _accounts.clear(); + _unresolvedAccounts.clear(); + _operations.clear(); + _descriptions.clear(); + + return path.EndsWith(_shortExt) || path.EndsWith(_shortExt.Upper()); +} + +wxString ImportEngine::GetFileExt() +{ + return wxGetTranslation(_longExt); +} + +std::vector ExplodeString(wxString& s) +{ + wxString tmp = s, cur = wxT(""); + std::vector res; + + while (tmp.Len()) + { + if (tmp.StartsWith(wxT(" ")) || + tmp.StartsWith(wxT("\t")) || + tmp.StartsWith(wxT("\n")) || + tmp.StartsWith(wxT("\r"))) + { + if (cur.Len()) + { + res.push_back(cur); + cur = wxT(""); + } + tmp = tmp.Mid(1); + continue; + } + cur += tmp.SubString(0, 0); + tmp = tmp.Mid(1); + } + + if (cur.Len()) + res.push_back(cur); + + return res; +} + +/* + Remove : + - head spaces + - tail spaces + - every word starting by a number + */ +wxString ImportEngine::RemoveUnused(wxString& s) +{ + wxString tmp = s, tmp2; + wxString res; + + tmp = tmp.Trim(true); + tmp = tmp.Trim(false); + + while (tmp.Len()) + { + tmp2 = tmp.SubString(0, 0); + if (tmp2.IsNumber()) + { + do + { + tmp = tmp.Mid(1); + tmp2 = tmp.SubString(0, 0); + } while (tmp.Len() && tmp2 != wxT(" ") && + tmp2 != wxT("\t") && + tmp2 != wxT("\r") && + tmp2 != wxT("\n")); + } + res += tmp2; + tmp = tmp.Mid(1); + } + + return res; +} + +/* + Find a pattern between the two strings: + %mX : lower string of orig[X] + %MX : upper string of orig[X] + %fX : First case in upper case of orig[X] + %wX : word orig[X] + other : constants + */ +wxString ImportEngine::FindPattern(wxString& orig, wxString& dest) +{ + wxString pattern, cur_pat; + int i, a; + std::vector tok1 = ExplodeString(orig); + std::vector tok2 = ExplodeString(dest); + int size1 = tok1.size(), size2 = tok2.size(); + + for(i=0; i tok1 = ExplodeString(pattern.filter); + std::vector tok2 = ExplodeString(op.description); + int size1 = tok1.size(), i; + long pos; + + op.description = wxT(""); + + for(i=0; i_importPatterns[key1] = pattern; + + for(i=pos+1; i<(int)_operations.size(); i++) + { + key2 = RemoveUnused(_descriptions[_operations[i].id]); + + if (key1 == key2) + { + ApplyPattern(_user->_importPatterns[key2], _operations[i]); + nbOpUpdated++; + } + } + + return nbOpUpdated; +} + +void ImportEngine::MatchPattern(wxString& originalKey, Operation& op) +{ + wxString key1; + ImportPattern pattern; + + if (!_user) return; + + key1 = RemoveUnused(originalKey); + + if (!_user->_importPatterns.count(key1)) + { + pattern.filter = FindPattern(originalKey, op.description); + pattern.account = op.account; + pattern.category = op.category; + + _user->_importPatterns[key1] = pattern; + + // std::cout << "New pattern " << key1.mb_str() << "\t" << pattern.filter.mb_str() << std::endl; + } + else + { + op.description = _descriptions[op.id]; + ApplyPattern(_user->_importPatterns[key1], op); + } +} diff --git a/src/model/import/ImportEngine.h b/src/model/import/ImportEngine.h new file mode 100644 index 0000000..af18bf3 --- /dev/null +++ b/src/model/import/ImportEngine.h @@ -0,0 +1,75 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#ifndef IMPORTENGINE_H +#define IMPORTENGINE_H + +#include +#include + +class KissCount; + +class ImportPattern { +public: + wxString filter; + wxString account; + wxString category; +} ; + +class ImportEngine { +public: + ImportEngine(); + ~ImportEngine(); + + // Get supported file extension. Example : + // "OFX files (*.ofx)|*.ofx" + virtual wxString GetFileExt(); + + // Handle the file + virtual bool HandleFile(const wxString& path, User* user, Database* db, KissCount* kiss); + + // Parse the file and return accounts that doesn't match + virtual std::vector ParseFile()=0; + + // Final Step + virtual std::vector* GetOperations(std::map& accounts)=0; + + void MatchPattern(wxString& key, Operation& op); + int UpdatePattern(int pos); + +protected: + Database* _db; + User* _user; + wxString _path; + KissCount* _kiss; + + wxString _shortExt; + wxString _longExt; + + std::map _accounts; + std::vector _unresolvedAccounts; + std::vector _operations; + std::map _descriptions; + + wxString RemoveUnused(wxString& s); + void ApplyPattern(ImportPattern& pattern, Operation& op); + wxString FindPattern(wxString& s1, wxString& s2); +}; + +#endif diff --git a/src/model/import/OFXImportEngine.cpp b/src/model/import/OFXImportEngine.cpp new file mode 100644 index 0000000..e6a3c30 --- /dev/null +++ b/src/model/import/OFXImportEngine.cpp @@ -0,0 +1,153 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#include "OFXImportEngine.h" + +static OFXImportEngine ofxImportEngine; + +int OFXImportEngine::account_cb(const struct OfxAccountData data, void * account_data) +{ + int i; + OFXImportEngine* _this = (OFXImportEngine*) account_data; + wxString account_number = wxString(data.account_number, wxConvUTF8); + + _this->_curAccount = wxT(""); + + for (i=0; i<(int)_this->_user->_accounts.size(); i++) + { + if (_this->_user->_accounts[i].number == account_number) + { + _this->_accounts[account_number] = _this->_user->_accounts[i].id; + _this->_curAccount = _this->_user->_accounts[i].id; + // std::cout << "Account " << data.account_number << " is " << i << std::endl; + //_this->_unresolvedAccounts.push_back(account_number); + break; + } + } + + if (!_this->_curAccount.Len()) + { + _this->_accounts[account_number] = wxT("unknown-") + account_number; + _this->_unresolvedAccounts.push_back(account_number); + } + + return 0; +} + +int OFXImportEngine::transaction_cb(const struct OfxTransactionData data, void * transaction_data) +{ + static int id=0; + OFXImportEngine* _this = (OFXImportEngine*) transaction_data; + Operation op; + struct tm t; + + if (!data.amount_valid || + (!data.date_posted_valid && !data.date_initiated_valid)) return 1; + + op.id = wxString::Format(wxT("%d"), ++id); + op.parent = wxT(""); + op.category = wxT("0"); + op.fix_cost = false; + op.account = _this->_curAccount; + op.checked = false; + op.transfert = wxT(""); + op.formula = wxT(""); + op.meta = false; + op._virtual = false; + + op.amount = data.amount; + + if (data.date_initiated_valid) + gmtime_r(&data.date_initiated, &t); + else + gmtime_r(&data.date_posted, &t); + + op.day = t.tm_mday; + op.month = t.tm_mon; + op.year = t.tm_year+1900; + + if (data.name_valid) + op.description = wxString(data.name, wxConvUTF8); + + if (data.memo_valid) + { + if (op.description.Len()) + op.description += wxT(" "); + op.description += wxString(data.memo, wxConvUTF8); + } + + _this->_operations.push_back(op); + _this->_descriptions[op.id] = op.description; + + _this->MatchPattern(op.description, op); + + return 0; +} + +int OFXImportEngine::account_balance_cb(const struct OfxStatementData data, void * statement_data) +{ + OFXImportEngine* _this = (OFXImportEngine*) statement_data; + + return 0; +} + +OFXImportEngine::OFXImportEngine() +{ + KissCount::RegisterImportEngine(this); + + _ctx = libofx_get_new_context(); + + if (!_ctx) + throw std::string("Unable to initialize libofx"); + + ofx_set_account_cb(_ctx, account_cb, this); + ofx_set_transaction_cb(_ctx, transaction_cb, this); + ofx_set_statement_cb(_ctx, account_balance_cb, this); + + _shortExt = wxT("ofx"); + _longExt = _("OFX files (*.ofx)|*.ofx"); +} + +OFXImportEngine::~OFXImportEngine() +{ + if (_ctx) + libofx_free_context(_ctx); +} + +bool OFXImportEngine::HandleFile(const wxString& path, User* user, Database* db, KissCount* kiss) +{ + if (!ImportEngine::HandleFile(path, user, db, kiss)) return false; + + return !libofx_proc_file(_ctx, path.mb_str(), AUTODETECT); +} + +std::vector OFXImportEngine::ParseFile() +{ + return _unresolvedAccounts; +} + +std::vector* OFXImportEngine::GetOperations(std::map& accounts) +{ + if (_kiss->GetOperationOrder() == wxT("ASC")) + std::sort(_operations.begin(), _operations.end(), sortOperations); + else + std::sort(_operations.begin(), _operations.end(), reverseSortOperations); + + return &_operations; +} diff --git a/src/model/import/OFXImportEngine.h b/src/model/import/OFXImportEngine.h new file mode 100644 index 0000000..803505a --- /dev/null +++ b/src/model/import/OFXImportEngine.h @@ -0,0 +1,44 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#ifndef OFXIMPORTENGINE_H +#define OFXIMPORTENGINE_H + +#include "ImportEngine.h" +#include + +class OFXImportEngine : public ImportEngine { +public: + OFXImportEngine(); + ~OFXImportEngine(); + + virtual bool HandleFile(const wxString& path, User* user, Database* db, KissCount* kiss); + virtual std::vector ParseFile(); + virtual std::vector* GetOperations(std::map& accounts); + +private: + LibofxContextPtr _ctx; + wxString _curAccount; + + static int account_cb(const struct OfxAccountData data, void * account_data); + static int transaction_cb(const struct OfxTransactionData data, void * transaction_data); + static int account_balance_cb(const struct OfxStatementData data, void * statement_data); +}; + +#endif diff --git a/src/view/ImportPanel.cpp b/src/view/ImportPanel.cpp new file mode 100644 index 0000000..2dc8f2a --- /dev/null +++ b/src/view/ImportPanel.cpp @@ -0,0 +1,246 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#include "ImportPanel.h" + +enum {OPEN_FILE_ID=1, BUTTON_OPEN_ID, NAME_ID, BUTTON_LOAD_ID, BUTTON_INTEGRATE_ID, OPS_GRID_ID}; + +BEGIN_EVENT_TABLE(ImportPanel, wxPanel) +EVT_GRID_CMD_CELL_CHANGE(OPS_GRID_ID, ImportPanel::OnOperationModified) +EVT_BUTTON(BUTTON_OPEN_ID, ImportPanel::OnFile) +EVT_TEXT_ENTER(OPEN_FILE_ID, ImportPanel::OnFileEnter) +EVT_BUTTON(BUTTON_LOAD_ID, ImportPanel::OnLoadOperations) +EVT_SHOW(ImportPanel::OnShow) +END_EVENT_TABLE() + +ImportPanel::ImportPanel(KissCount* kiss, wxUI *parent) : KissPanel(kiss, parent) +{ + wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL); + wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL); + wxButton* buttonOpen; + wxRect rect = wxDisplay().GetGeometry(); + int w, h; + wxStaticBox* staticAccount = new wxStaticBox(this, wxID_ANY, _("Unresolved accounts")); + + SetSizer(vbox); + + _fileTxt = new wxTextCtrl(this, OPEN_FILE_ID); + _fileTxt->SetWindowStyle(_fileTxt->GetWindowStyle() | wxTE_PROCESS_ENTER); + _fileTxt->GetSize(&w, &h); + wxSize size(rect.width/3, h); + _fileTxt->SetMinSize(size); + buttonOpen = new wxButton(this, BUTTON_OPEN_ID, wxT("...")); + + _buttonLoadOperations = new wxButton(this, BUTTON_LOAD_ID, wxT("Load operations")); + _buttonLoadOperations->Disable(); + + _buttonIntegrate = new wxButton(this, BUTTON_INTEGRATE_ID, wxT("Integrate operations")); + _buttonIntegrate->Disable(); + + hbox->Add(_fileTxt, 0, wxGROW|wxALL, 5); + hbox->Add(buttonOpen, 0, wxALL, 5); + hbox->Add(_buttonLoadOperations, 0, wxALL, 5); + hbox->Add(_buttonIntegrate, 0, wxALL, 5); + + vbox->Add(hbox, 0); + + _accountsGrid = new wxGrid(this, wxID_ANY); + _accountsGrid->CreateGrid(0, 2); + _accountsGrid->SetRowLabelSize(0); + _accountsGrid->SetColLabelValue(0, _("File account")); + _accountsGrid->SetColLabelValue(1, _("Internal account")); + _accountsGrid->Fit(); + + wxStaticBoxSizer* staticBoxSizer = new wxStaticBoxSizer (staticAccount, wxVERTICAL); + staticBoxSizer->Add(_accountsGrid, 0, wxGROW|wxALL, 2); + + _operationsGrid = new GridAccount(kiss, this, OPS_GRID_ID, false, false, false); + + hbox2->Add(staticBoxSizer, 0, wxGROW|wxALL, 15); + hbox2->Add(_operationsGrid, 0, wxGROW|wxALL, 15); + + vbox->Add(hbox2, wxGROW); + + Fit(); + + SetMinSize(wxSize(rect.width-rect.x-15, rect.height-rect.y-128-25)); + SetMaxSize(wxSize(rect.width-rect.x-15, rect.height-rect.y-128-25)); + SetScrollbars(10, 10, 100/10, 100/10); +} + +KissPanel* ImportPanel::CreatePanel() +{ + return new ImportPanel(_kiss, _wxUI); +} + +wxBitmapButton* ImportPanel::GetButton(int id) +{ + if (!_KissButton) + _KissButton = new wxBitmapButton(_wxUI, id, wxBitmap(wxT(PREFS_ICON), wxBITMAP_TYPE_PNG), wxDefaultPosition, wxSize(128, 128)); + + return _KissButton; +} + +wxString ImportPanel::GetToolTip() +{ + return _("Import"); +} + +void ImportPanel::OnShow(wxShowEvent& event) +{ + _wxUI->SetTitle(_("KissCount - Import")); +} + +void ImportPanel::OnFile(wxCommandEvent& WXUNUSED(event)) +{ + wxFileDialog openFileDialog(this, _("Choose a database to open"), wxT(""), wxT(""), + _kiss->GetImportEngineExtensions(), wxFD_OPEN|wxFD_FILE_MUST_EXIST); + + if (openFileDialog.ShowModal() == wxID_CANCEL) + return; + + _fileTxt->Clear(); + + *_fileTxt << openFileDialog.GetPath(); + + ProcessFile(); +} + +void ImportPanel::OnFileEnter(wxCommandEvent& WXUNUSED(event)) +{ + ProcessFile(); +} + +void ImportPanel::ProcessFile() +{ + std::vector accounts; + User* user = _kiss->GetUser(); + int i; + wxGridCellChoiceEditor* accountEditor; + wxString* userAccounts; + std::map resolvedAccounts; + wxCommandEvent event; + + wxString path = _fileTxt->GetLineText(0); + + _buttonLoadOperations->Disable(); + _buttonIntegrate->Disable(); + _accountsGrid->ClearGrid(); + _operationsGrid->ClearGrid(); + + _importEngine = _kiss->GetImportEngine(path); + + if (!_importEngine) + { + wxMessageBox(_("Any engine can process this file !"), wxT("KissCount"), wxICON_INFORMATION | wxOK); + + return ; + } + + accounts = _importEngine->ParseFile(); + + if (accounts.size()) + { + int nb_accounts = user->GetAccountsNumber(); + userAccounts = new wxString[nb_accounts+1]; + + userAccounts[0] = _("Create one"); + + for(i=0; i_accounts[i].name; + + accountEditor = new wxGridCellChoiceEditor(nb_accounts+1, userAccounts, false); + + _buttonLoadOperations->Enable(); + + // std::cout << "Insert " << accounts.size() << " rows\n"; + _accountsGrid->AppendRows(accounts.size()); + + for (i=0; i<(int)accounts.size(); i++) + { + _accountsGrid->SetCellValue(i, 0, accounts[i]); + _accountsGrid->SetReadOnly(i, 0); + _accountsGrid->SetCellValue(i, 1, userAccounts[0]); + + _accountsGrid->SetCellEditor(i, 1, accountEditor); + } + + _accountsGrid->AutoSize(); + _accountsGrid->Layout(); + } + else + { + OnLoadOperations(event); + } + Layout(); +} + +void ImportPanel::OnLoadOperations(wxCommandEvent& WXUNUSED(event)) +{ + std::map resolvedAccounts; + int i; + User* user = _kiss->GetUser(); + + for(i=0; i<_accountsGrid->GetNumberRows(); i++) + { + resolvedAccounts[_accountsGrid->GetCellValue(i, 0)] = + user->GetAccountId(_accountsGrid->GetCellValue(i, 1)); + } + + _operations = _importEngine->GetOperations(resolvedAccounts); + + if (_operations->size()) + { + _operationsGrid->LoadOperations(_operations, 0, 0); + _buttonIntegrate->Enable(); + + Fit(); + } + else + { + wxMessageBox(_("No operation found into this file"), wxT("KissCount"), wxICON_INFORMATION | wxOK); + } +} + +void ImportPanel::OnOperationModified(wxGridEvent& event) +{ + int col = event.GetCol(); + int row; + static bool update = false; + + if (col != DESCRIPTION && col != CATEGORY && col != ACCOUNT) return ; + + if (update) return; + + update = true; + + row = event.GetRow(); + + _operationsGrid->ClearGrid(); + + if (_importEngine->UpdatePattern(row-1) > 1) + _operationsGrid->LoadOperations(_operations, 0, 0); + + // sleep(1); + + Fit(); + + update = false; +} diff --git a/src/view/ImportPanel.h b/src/view/ImportPanel.h new file mode 100644 index 0000000..545b7e2 --- /dev/null +++ b/src/view/ImportPanel.h @@ -0,0 +1,66 @@ +/* + Copyright 2010-2011 Grégory Soutadé + + This file is part of KissCount. + + KissCount 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 3 of the License, or + (at your option) any later version. + + KissCount 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 KissCount. If not, see . +*/ + +#ifndef IMPORTPANEL_H +#define IMPORTPANEL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "view.h" +#include +#include + +class ImportPanel: public KissPanel +{ +public: + ImportPanel(KissCount* kiss, wxUI *parent); + + KissPanel* CreatePanel(); + wxBitmapButton* GetButton(int id); + wxString GetToolTip(); + void OnShow(wxShowEvent& event); + + void OnFile(wxCommandEvent& WXUNUSED(event)); + void OnFileEnter(wxCommandEvent& WXUNUSED(event)); + void OnLoadOperations(wxCommandEvent& WXUNUSED(event)); + void OnOperationModified(wxGridEvent& event); + +private: + wxGrid* _accountsGrid; + wxTextCtrl* _fileTxt; + GridAccount* _operationsGrid; + ImportEngine* _importEngine; + wxButton* _buttonLoadOperations, *_buttonIntegrate; + std::vector* _operations; + + void ProcessFile(); + + DECLARE_EVENT_TABLE(); +}; + +#endif diff --git a/src/view/grid/GridAccount.cpp b/src/view/grid/GridAccount.cpp index bd89b5f..f65c1ce 100644 --- a/src/view/grid/GridAccount.cpp +++ b/src/view/grid/GridAccount.cpp @@ -39,8 +39,6 @@ BEGIN_EVENT_TABLE(GridAccount, wxGrid) EVT_GRID_CELL_LEFT_CLICK(GridAccount::OnCellLeftClick ) END_EVENT_TABLE() -enum {TREE, DESCRIPTION, OP_DATE, DEBIT, CREDIT, CATEGORY, ACCOUNT, OP_DELETE, CHECKED, NUMBER_COLS_OPS}; - enum {GRID_ID}; GridAccount::GridAccount(KissCount* kiss, wxWindow *parent, wxWindowID id, @@ -201,9 +199,11 @@ void GridAccount::LoadOperations(std::vector* operations, int month, _displayedOperations.clear(); _displayedOperations.push_back(NULLop); // Header _fixCosts = 0; - it = _operations->begin(); + if (GetNumberRows () > 1) + DeleteRows(1, GetNumberRows ()-1); + if (_canAddOperation) { for (;it != _operations->end() && it->fix_cost; it++) @@ -331,7 +331,13 @@ void GridAccount::InsertOperation(User* user, Operation& op, int line, bool fix, SetCellEditor(line, CREDIT, new wxGridCellFloatEditor(wxID_ANY, 2)); wxGridCellChoiceEditor* accountEditor = new wxGridCellChoiceEditor(user->GetAccountsNumber(), _accounts, false); SetCellEditor(line, ACCOUNT, accountEditor); - wxGridCellChoiceEditor* categoryEditor = new wxGridCellChoiceEditor(user->GetCategoriesNumber()-1, _categories+1, false); + wxGridCellChoiceEditor* categoryEditor ; + + if (_canAddOperation) + categoryEditor = new wxGridCellChoiceEditor(user->GetCategoriesNumber()-1, _categories+1, false); + else + categoryEditor = new wxGridCellChoiceEditor(user->GetCategoriesNumber(), _categories, false); + SetCellEditor(line, CATEGORY, categoryEditor); if (fix) @@ -669,7 +675,7 @@ void GridAccount::OnOperationModified(wxGridEvent& event) Category cat ; // Avoid recursives calls - if (inModification) return; + if (inModification || _loadOperations) return; inModification = true ; @@ -722,6 +728,23 @@ void GridAccount::OnOperationModified(wxGridEvent& event) op_complete--; } + if (col == DESCRIPTION && + (!GetCellValue(row, CATEGORY).Length() || + !GetCellValue(row, ACCOUNT).Length() || + !_canAddOperation)) + { + if (_kiss->SearchPreviousOperation(&op_tmp, new_op, new_op.month, new_op.year, _canAddOperation)) + { + new_op.category = op_tmp.category; + new_op.account = op_tmp.account; + SetCellValue(row, CATEGORY, wxGetTranslation(user->GetCategoryName(new_op.category))); + SetCellValue(row, ACCOUNT, user->GetAccountName(new_op.account)); + op_complete -= 2; + col = CATEGORY; + new_op.fix_cost = (new_op.category == user->GetCategoryId(wxT("Fix"))); + } + } + value = GetCellValue(row, DEBIT); if (value.Length()) { @@ -753,7 +776,6 @@ void GridAccount::OnOperationModified(wxGridEvent& event) pEditor->DecRef(); } - value = GetCellValue(row, CATEGORY); if (value.Length()) { @@ -775,21 +797,6 @@ void GridAccount::OnOperationModified(wxGridEvent& event) new_op.checked = false; op_complete--; - if (col == DESCRIPTION && - (!GetCellValue(row, CATEGORY).Length() || - !GetCellValue(row, ACCOUNT).Length())) - { - new_op.fix_cost = row <= _fixCosts; - if (_kiss->SearchPreviousOperation(&op_tmp, new_op, _curMonth, _curYear)) - { - new_op.category = op_tmp.category; - new_op.account = op_tmp.account; - SetCellValue(row, CATEGORY, wxGetTranslation(user->GetCategoryName(new_op.category))); - SetCellValue(row, ACCOUNT, user->GetAccountName(new_op.account)); - op_complete -= 2; - } - } - cur_op = (_displayedOperations)[row] ; if (col == CHECKED || col == CATEGORY) @@ -1539,7 +1546,7 @@ void GridAccount::MassUpdate(std::vector& rows, updateOperationFunc func, v } } - DeleteRows(1, GetNumberRows()-1); + ClearGrid(); LoadOperations(_operations, 0, 0); diff --git a/src/view/grid/GridAccount.h b/src/view/grid/GridAccount.h index 72c6bdf..c1c6926 100644 --- a/src/view/grid/GridAccount.h +++ b/src/view/grid/GridAccount.h @@ -36,6 +36,8 @@ class KissCount; +enum {TREE, DESCRIPTION, OP_DATE, DEBIT, CREDIT, CATEGORY, ACCOUNT, OP_DELETE, CHECKED, NUMBER_COLS_OPS}; + typedef void (*updateOperationFunc)(Operation* op, void** params); class GridAccount : public wxGrid