From 41d1fc8512bf06a1b3ce3cc4a5d30054a0ea1ddc Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 28 May 2015 16:43:23 +0200 Subject: [PATCH 1/8] [ADD] migration script and a module that takes care of saving imported files (+migration) --- .../migrations/8.0.1.0/post-migrate.py | 33 +++++++ .../README.rst | 30 ++++++ .../__init__.py | 22 +++++ .../__openerp__.py | 45 +++++++++ .../hooks.py | 92 ++++++++++++++++++ .../models/__init__.py | 22 +++++ .../models/account_bank_statement.py | 34 +++++++ .../models/account_bank_statement_import.py | 60 ++++++++++++ .../static/description/icon.png | Bin 0 -> 6907 bytes .../views/account_bank_statement.xml | 25 +++++ 10 files changed, 363 insertions(+) create mode 100644 account_bank_statement_import/migrations/8.0.1.0/post-migrate.py create mode 100644 account_bank_statement_import_save_file/README.rst create mode 100644 account_bank_statement_import_save_file/__init__.py create mode 100644 account_bank_statement_import_save_file/__openerp__.py create mode 100644 account_bank_statement_import_save_file/hooks.py create mode 100644 account_bank_statement_import_save_file/models/__init__.py create mode 100644 account_bank_statement_import_save_file/models/account_bank_statement.py create mode 100644 account_bank_statement_import_save_file/models/account_bank_statement_import.py create mode 100644 account_bank_statement_import_save_file/static/description/icon.png create mode 100644 account_bank_statement_import_save_file/views/account_bank_statement.xml diff --git a/account_bank_statement_import/migrations/8.0.1.0/post-migrate.py b/account_bank_statement_import/migrations/8.0.1.0/post-migrate.py new file mode 100644 index 00000000..ff2ee0f5 --- /dev/null +++ b/account_bank_statement_import/migrations/8.0.1.0/post-migrate.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +def migrate(cr, version): + # if we end up here, we migrate from 7.0's account_banking + # set transaction ids, taking care to enforce uniqueness + cr.execute( + """update account_bank_statement_line l set unique_import_id=l1.trans + from ( + select distinct + first_value(id) over (partition by trans) id, trans + from account_bank_statement_line + ) l1 + where l.id=l1.id""") diff --git a/account_bank_statement_import_save_file/README.rst b/account_bank_statement_import_save_file/README.rst new file mode 100644 index 00000000..694c956c --- /dev/null +++ b/account_bank_statement_import_save_file/README.rst @@ -0,0 +1,30 @@ +Save imported statement file +============================ + +This module saves the original file of an imported bank statement for further reference/processing and maintains a link between bank statements and those imported files. + +Usage +===== + +On a successful import, the generated statement(s) link to an attachment containing the original file. + +Credits +======= + +Contributors +------------ + +* Holger Brunn + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/account_bank_statement_import_save_file/__init__.py b/account_bank_statement_import_save_file/__init__.py new file mode 100644 index 00000000..a1813e65 --- /dev/null +++ b/account_bank_statement_import_save_file/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import models +from .hooks import _post_init_hook diff --git a/account_bank_statement_import_save_file/__openerp__.py b/account_bank_statement_import_save_file/__openerp__.py new file mode 100644 index 00000000..57b40975 --- /dev/null +++ b/account_bank_statement_import_save_file/__openerp__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + "name": "Save imported bank statements", + "version": "1.0", + "author": "Therp BV", + "license": "AGPL-3", + "category": "Accounting & Finance", + "summary": "Keep imported bank statements as raw data", + "depends": [ + 'account_bank_statement_import', + ], + "data": [ + "views/account_bank_statement.xml", + ], + "qweb": [ + ], + "test": [ + ], + "post_init_hook": '_post_init_hook', + "auto_install": False, + "installable": True, + "application": False, + "external_dependencies": { + 'python': [], + }, +} diff --git a/account_bank_statement_import_save_file/hooks.py b/account_bank_statement_import_save_file/hooks.py new file mode 100644 index 00000000..f35b069e --- /dev/null +++ b/account_bank_statement_import_save_file/hooks.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import SUPERUSER_ID, api + + +def _post_init_hook(cr, pool): + # if we install this module on a database with remains of account_banking, + # migrate account.banking.imported.file + cr.execute( + "select 1 from pg_catalog.pg_class c " + "join pg_catalog.pg_namespace n ON n.oid = c.relnamespace " + "where n.nspname = 'public' and " + "c.relname = 'account_banking_imported_file' and " + "c.relkind = 'r'") + if cr.fetchall(): + _post_init_hook_migrate_account_banking_imported_file(cr, pool) + + +def _post_init_hook_migrate_account_banking_imported_file(cr, pool): + # create attachments + cr.execute( + """insert into ir_attachment + ( + name, create_uid, create_date, datas_fname, description, + company_id, res_model, type, + res_id + ) + select + coalesce(file_name, ''), user_id, date, file_name, log, + company_id, 'account.bank.statement', 'binary', + ( + select id from account_bank_statement + where banking_id=f.id + limit 1 + ) + from account_banking_imported_file f + returning id""") + + attachment_ids = [attachment_id for attachment_id, in cr.fetchall()] + + # assign respective attachment to all statements pointing to an imported + # banking file + cr.execute( + """with banking_id2attachment as ( + select distinct b.id banking_id, a.id attachment_id + from account_banking_imported_file b + join account_bank_statement s + on s.banking_id=b.id + join ir_attachment a + on a.id in %s and s.id=a.res_id + ) + update account_bank_statement s + set import_file=b2a.attachment_id + from banking_id2attachment b2a + where b2a.banking_id=s.banking_id""", + (tuple(attachment_ids),) + ) + + # now we just have to write the file's content via the orm + # (to support non-db storage) + cr.execute( + """select distinct a.id, b.file + from account_banking_imported_file b + join account_bank_statement s + on s.banking_id=b.id + join ir_attachment a + on a.id in %s and s.id=a.res_id""", + (tuple(attachment_ids),) + ) + for attachment_id, content in cr.fetchall(): + pool['ir.attachment'].write( + cr, SUPERUSER_ID, + [attachment_id], + {'datas': str(content)}) diff --git a/account_bank_statement_import_save_file/models/__init__.py b/account_bank_statement_import_save_file/models/__init__.py new file mode 100644 index 00000000..84bff13e --- /dev/null +++ b/account_bank_statement_import_save_file/models/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import account_bank_statement +from . import account_bank_statement_import diff --git a/account_bank_statement_import_save_file/models/account_bank_statement.py b/account_bank_statement_import_save_file/models/account_bank_statement.py new file mode 100644 index 00000000..fe67d3c9 --- /dev/null +++ b/account_bank_statement_import_save_file/models/account_bank_statement.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import models, fields, api + + +class AccountBankStatement(models.Model): + _inherit = 'account.bank.statement' + + import_file = fields.Many2one( + 'ir.attachment', 'Import file', readonly=True) + import_date = fields.Datetime( + related=['import_file', 'create_date'], readonly=True) + import_user = fields.Many2one( + related=['import_file', 'create_uid'], readonly=True) + import_log = fields.Text( + related=['import_file', 'description'], readonly=True) diff --git a/account_bank_statement_import_save_file/models/account_bank_statement_import.py b/account_bank_statement_import_save_file/models/account_bank_statement_import.py new file mode 100644 index 00000000..15b8d86a --- /dev/null +++ b/account_bank_statement_import_save_file/models/account_bank_statement_import.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import base64 +import inspect +from openerp import models, fields, api + + +class AccountBankStatementImport(models.Model): + _inherit = 'account.bank.statement.import' + + @api.model + def _import_statement(self, statement): + (statement_id, notifications) = \ + super(AccountBankStatementImport, self)._import_statement( + statement) + if statement_id: + # get raw file data from the stack + def get_data_file(frame): + if 'data_file' in frame.f_locals: + return frame.f_locals['data_file'] + if frame.f_back: + return get_data_file(frame.f_back) + return None + data_file = get_data_file(inspect.currentframe()) + self.env['account.bank.statement'].browse([statement_id]).write({ + 'import_file': self.env['ir.attachment'].create( + self._create_import_file_attachment_data( + data_file, statement_id, notifications)).id, + }) + return (statement_id, notifications) + + @api.model + def _create_import_file_attachment_data(self, data_file, statement_id, + notifications): + return { + 'name': '', + 'res_model': 'account.bank.statement', + 'res_id': statement_id, + 'type': 'binary', + 'datas': base64.b64encode(data_file), + 'description': notifications, + } diff --git a/account_bank_statement_import_save_file/static/description/icon.png b/account_bank_statement_import_save_file/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a22a285caee435c1257b688f5b45cc3c6682a33 GIT binary patch literal 6907 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 X(w!Fx-Uru-DuB-2` + + + + account.bank.statement + + + + + + + + + + + + + + + + + + + + From be07831202c2691e98416394e6e24b284246c134 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 29 May 2015 08:26:52 +0200 Subject: [PATCH 2/8] [ADD] test --- .../tests/__init__.py | 21 +++++++ .../tests/test_save_file.py | 61 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 account_bank_statement_import_save_file/tests/__init__.py create mode 100644 account_bank_statement_import_save_file/tests/test_save_file.py diff --git a/account_bank_statement_import_save_file/tests/__init__.py b/account_bank_statement_import_save_file/tests/__init__.py new file mode 100644 index 00000000..f06dd214 --- /dev/null +++ b/account_bank_statement_import_save_file/tests/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import test_save_file diff --git a/account_bank_statement_import_save_file/tests/test_save_file.py b/account_bank_statement_import_save_file/tests/test_save_file.py new file mode 100644 index 00000000..8c3f3417 --- /dev/null +++ b/account_bank_statement_import_save_file/tests/test_save_file.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# This module copyright (C) 2015 Therp BV . +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import base64 +from openerp import models +from openerp.tests.common import TransactionCase + + +class HelloWorldParser(models.TransientModel): + _inherit = 'account.bank.statement.import' + + def _parse_file(self, cr, uid, data_file, context=None): + return 'EUR', 'BE1234567890', [{ + 'name': '000000123', + 'date': '2013-06-26', + 'transactions': [{ + 'name': 'KBC-INVESTERINGSKREDIET 787-5562831-01', + 'date': '2013-06-26', + 'amount': 42, + 'unique_import_id': 'hello', + }], + }] + + +class TestSaveFile(TransactionCase): + def test_SaveFile(self): + HelloWorldParser._build_model(self.registry, self.cr) + testmodel = self.env['account.bank.statement.import'] + testmodel._prepare_setup() + testmodel._setup_base(False) + testmodel._setup_fields() + testmodel._setup_complete() + testmodel._auto_init() + action = self.env['account.bank.statement.import']\ + .with_context( + journal_id=self.env['account.journal'] + .search([('currency.name', '=', 'EUR')]).ids[0])\ + .create({'data_file': base64.b64encode('hello world')})\ + .import_file() + for statement in self.env['account.bank.statement'].browse( + action['context']['statement_ids']): + self.assertEqual( + base64.b64decode(statement.import_file.datas), + 'hello world') From e43290e25f6876d3f8c892797843afa73aedda8a Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 29 May 2015 08:46:07 +0200 Subject: [PATCH 3/8] [IMP] make test work with and without demo data --- .../tests/test_save_file.py | 37 ++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/account_bank_statement_import_save_file/tests/test_save_file.py b/account_bank_statement_import_save_file/tests/test_save_file.py index 8c3f3417..dca847c9 100644 --- a/account_bank_statement_import_save_file/tests/test_save_file.py +++ b/account_bank_statement_import_save_file/tests/test_save_file.py @@ -19,15 +19,21 @@ # ############################################################################## import base64 -from openerp import models +from openerp import api, models from openerp.tests.common import TransactionCase +acc_number = 'BE1234567890' + + class HelloWorldParser(models.TransientModel): _inherit = 'account.bank.statement.import' - def _parse_file(self, cr, uid, data_file, context=None): - return 'EUR', 'BE1234567890', [{ + @api.model + def _parse_file(self, data_file): + return [{ + 'currency_code': 'EUR', + 'account_number': acc_number, 'name': '000000123', 'date': '2013-06-26', 'transactions': [{ @@ -42,16 +48,21 @@ class HelloWorldParser(models.TransientModel): class TestSaveFile(TransactionCase): def test_SaveFile(self): HelloWorldParser._build_model(self.registry, self.cr) - testmodel = self.env['account.bank.statement.import'] - testmodel._prepare_setup() - testmodel._setup_base(False) - testmodel._setup_fields() - testmodel._setup_complete() - testmodel._auto_init() - action = self.env['account.bank.statement.import']\ - .with_context( - journal_id=self.env['account.journal'] - .search([('currency.name', '=', 'EUR')]).ids[0])\ + import_wizard = self.env['account.bank.statement.import'] + import_wizard._prepare_setup() + import_wizard._setup_base(False) + import_wizard._setup_fields() + import_wizard._setup_complete() + import_wizard._auto_init() + journal_id = self.env['res.partner.bank'].search([ + ('acc_number', '=', acc_number), + ]).journal_id.id + if not journal_id: + account = import_wizard._create_bank_account(acc_number) + journal_id = self.env['account.journal']\ + .search([('currency.name', '=', 'EUR')]).ids[0] + account.journal_id = journal_id + action = import_wizard.with_context(journal_id=journal_id)\ .create({'data_file': base64.b64encode('hello world')})\ .import_file() for statement in self.env['account.bank.statement'].browse( From 8a41968d4332e02ef0ef9f9500b6d43ca85be84f Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 4 Jun 2015 10:54:01 +0200 Subject: [PATCH 4/8] [FIX] account.bank.statement.import is transient --- .../models/account_bank_statement_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_bank_statement_import_save_file/models/account_bank_statement_import.py b/account_bank_statement_import_save_file/models/account_bank_statement_import.py index 15b8d86a..f0646f98 100644 --- a/account_bank_statement_import_save_file/models/account_bank_statement_import.py +++ b/account_bank_statement_import_save_file/models/account_bank_statement_import.py @@ -23,7 +23,7 @@ import inspect from openerp import models, fields, api -class AccountBankStatementImport(models.Model): +class AccountBankStatementImport(models.TransientModel): _inherit = 'account.bank.statement.import' @api.model From 1ca4c6e9d84d76fde0cb974014f4ab30db995fe7 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Mon, 8 Jun 2015 12:37:48 +0200 Subject: [PATCH 5/8] [FIX] unused imports --- account_bank_statement_import_save_file/hooks.py | 2 +- .../models/account_bank_statement.py | 2 +- .../models/account_bank_statement_import.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/account_bank_statement_import_save_file/hooks.py b/account_bank_statement_import_save_file/hooks.py index f35b069e..908c2258 100644 --- a/account_bank_statement_import_save_file/hooks.py +++ b/account_bank_statement_import_save_file/hooks.py @@ -18,7 +18,7 @@ # along with this program. If not, see . # ############################################################################## -from openerp import SUPERUSER_ID, api +from openerp import SUPERUSER_ID def _post_init_hook(cr, pool): diff --git a/account_bank_statement_import_save_file/models/account_bank_statement.py b/account_bank_statement_import_save_file/models/account_bank_statement.py index fe67d3c9..9b7073b4 100644 --- a/account_bank_statement_import_save_file/models/account_bank_statement.py +++ b/account_bank_statement_import_save_file/models/account_bank_statement.py @@ -18,7 +18,7 @@ # along with this program. If not, see . # ############################################################################## -from openerp import models, fields, api +from openerp import models, fields class AccountBankStatement(models.Model): diff --git a/account_bank_statement_import_save_file/models/account_bank_statement_import.py b/account_bank_statement_import_save_file/models/account_bank_statement_import.py index f0646f98..9ef8d1b9 100644 --- a/account_bank_statement_import_save_file/models/account_bank_statement_import.py +++ b/account_bank_statement_import_save_file/models/account_bank_statement_import.py @@ -20,7 +20,7 @@ ############################################################################## import base64 import inspect -from openerp import models, fields, api +from openerp import models, api class AccountBankStatementImport(models.TransientModel): From 59e27cd17e0fb069fddf420a61ef51a96be09fa6 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 25 Jun 2015 15:09:32 +0200 Subject: [PATCH 6/8] [UPD] adapt to upstream changes --- .../models/account_bank_statement_import.py | 23 +++++---------- .../tests/test_save_file.py | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/account_bank_statement_import_save_file/models/account_bank_statement_import.py b/account_bank_statement_import_save_file/models/account_bank_statement_import.py index 9ef8d1b9..3e3ab9d8 100644 --- a/account_bank_statement_import_save_file/models/account_bank_statement_import.py +++ b/account_bank_statement_import_save_file/models/account_bank_statement_import.py @@ -27,25 +27,16 @@ class AccountBankStatementImport(models.TransientModel): _inherit = 'account.bank.statement.import' @api.model - def _import_statement(self, statement): - (statement_id, notifications) = \ - super(AccountBankStatementImport, self)._import_statement( - statement) - if statement_id: - # get raw file data from the stack - def get_data_file(frame): - if 'data_file' in frame.f_locals: - return frame.f_locals['data_file'] - if frame.f_back: - return get_data_file(frame.f_back) - return None - data_file = get_data_file(inspect.currentframe()) - self.env['account.bank.statement'].browse([statement_id]).write({ + def _import_file(self, data_file): + (statement_ids, notifications) = \ + super(AccountBankStatementImport, self)._import_file(data_file) + if statement_ids: + self.env['account.bank.statement'].browse(statement_ids).write({ 'import_file': self.env['ir.attachment'].create( self._create_import_file_attachment_data( - data_file, statement_id, notifications)).id, + data_file, statement_ids[0], notifications)).id, }) - return (statement_id, notifications) + return (statement_ids, notifications) @api.model def _create_import_file_attachment_data(self, data_file, statement_id, diff --git a/account_bank_statement_import_save_file/tests/test_save_file.py b/account_bank_statement_import_save_file/tests/test_save_file.py index dca847c9..7e5b1867 100644 --- a/account_bank_statement_import_save_file/tests/test_save_file.py +++ b/account_bank_statement_import_save_file/tests/test_save_file.py @@ -31,18 +31,20 @@ class HelloWorldParser(models.TransientModel): @api.model def _parse_file(self, data_file): - return [{ - 'currency_code': 'EUR', - 'account_number': acc_number, - 'name': '000000123', - 'date': '2013-06-26', - 'transactions': [{ - 'name': 'KBC-INVESTERINGSKREDIET 787-5562831-01', + return ( + 'EUR', + acc_number, + [{ + 'name': '000000123', 'date': '2013-06-26', - 'amount': 42, - 'unique_import_id': 'hello', + 'transactions': [{ + 'name': 'KBC-INVESTERINGSKREDIET 787-5562831-01', + 'date': '2013-06-26', + 'amount': 42, + 'unique_import_id': 'hello', + }], }], - }] + ) class TestSaveFile(TransactionCase): @@ -60,7 +62,11 @@ class TestSaveFile(TransactionCase): if not journal_id: account = import_wizard._create_bank_account(acc_number) journal_id = self.env['account.journal']\ - .search([('currency.name', '=', 'EUR')]).ids[0] + .search([ + '|', + ('currency.name', '=', 'EUR'), + ('currency', '=', False) + ]).ids[0] account.journal_id = journal_id action = import_wizard.with_context(journal_id=journal_id)\ .create({'data_file': base64.b64encode('hello world')})\ From 1376b19f27b9717ac5f9e3ab1a5e322e11b9676e Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 25 Jun 2015 15:29:46 +0200 Subject: [PATCH 7/8] [FIX] unused import --- .../models/account_bank_statement_import.py | 1 - 1 file changed, 1 deletion(-) diff --git a/account_bank_statement_import_save_file/models/account_bank_statement_import.py b/account_bank_statement_import_save_file/models/account_bank_statement_import.py index 3e3ab9d8..875e2c7f 100644 --- a/account_bank_statement_import_save_file/models/account_bank_statement_import.py +++ b/account_bank_statement_import_save_file/models/account_bank_statement_import.py @@ -19,7 +19,6 @@ # ############################################################################## import base64 -import inspect from openerp import models, api From 86b37fba52307ad7b5e1ba10a89f6720a77788f9 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Thu, 30 Jul 2015 11:49:10 +0200 Subject: [PATCH 8/8] [IMP] better icon --- .../README.rst | 5 +++++ .../static/description/icon.png | Bin 6907 -> 7226 bytes 2 files changed, 5 insertions(+) diff --git a/account_bank_statement_import_save_file/README.rst b/account_bank_statement_import_save_file/README.rst index 694c956c..37ff0bf5 100644 --- a/account_bank_statement_import_save_file/README.rst +++ b/account_bank_statement_import_save_file/README.rst @@ -16,6 +16,11 @@ Contributors * Holger Brunn +Icon +---- + +* https://commons.wikimedia.org/wiki/File:Paper_clip_font_awesome.svg + Maintainer ---------- diff --git a/account_bank_statement_import_save_file/static/description/icon.png b/account_bank_statement_import_save_file/static/description/icon.png index 1a22a285caee435c1257b688f5b45cc3c6682a33..49dedcf455ba2861afbd03daacacb70e235100a7 100644 GIT binary patch literal 7226 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 BnbH6N literal 6907 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 X(w!Fx-Uru-DuB-2`