diff --git a/.travis.yml b/.travis.yml index fd8dfb25..d0f63084 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ install: - git clone --depth=1 https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} - travis_install_nightly + - pip install xlrd script: - travis_run_tests diff --git a/account_move_bankaccount_import/README.rst b/account_move_bankaccount_import/README.rst new file mode 100644 index 00000000..ce9a8d73 --- /dev/null +++ b/account_move_bankaccount_import/README.rst @@ -0,0 +1,57 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================================================= +Journal Entry completion from bank account number +================================================= + +This module extends the functionality of account_move_base_import +to add a completion method based on the partner bank account number +provided by the bank/office. + +Completion will look in the partner with that bank account number +to match the partner, then it will fill in the journal item with +it to ease the reconciliation. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/98/9.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of +trouble, please check there if your issue has already been reported. If you +spotted it first, help us smashing it by providing a detailed and welcomed +feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Laurent Mignon +* Matthieu Dietrich + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://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 https://odoo-community.org. diff --git a/account_move_bankaccount_import/__init__.py b/account_move_bankaccount_import/__init__.py new file mode 100644 index 00000000..126485cb --- /dev/null +++ b/account_move_bankaccount_import/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import models diff --git a/account_move_bankaccount_import/__openerp__.py b/account_move_bankaccount_import/__openerp__.py new file mode 100644 index 00000000..2b591b37 --- /dev/null +++ b/account_move_bankaccount_import/__openerp__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +{ + 'name': "Journal Entry completion from bank account number", + 'version': '9.0.1.0.0', + 'author': "ACSONE SA/NV,Odoo Community Association (OCA)", + 'maintainer': 'ACSONE SA/NV', + 'category': 'Finance', + 'complexity': 'normal', + 'depends': [ + 'account_move_base_import', + ], + 'website': 'http://www.acsone.eu', + 'data': [ + "data/completion_rule_data.xml", + ], + 'demo': [], + 'installable': True, + 'auto_install': False, + 'license': 'AGPL-3', +} diff --git a/account_move_bankaccount_import/data/completion_rule_data.xml b/account_move_bankaccount_import/data/completion_rule_data.xml new file mode 100644 index 00000000..e4069b5f --- /dev/null +++ b/account_move_bankaccount_import/data/completion_rule_data.xml @@ -0,0 +1,10 @@ + + + + + Match from bank account number (Normal or IBAN)) + 10 + get_from_bank_account + + + diff --git a/account_statement_bankaccount_completion/i18n/account_statement_bankaccount_completion.pot b/account_move_bankaccount_import/i18n/account_statement_bankaccount_completion.pot similarity index 100% rename from account_statement_bankaccount_completion/i18n/account_statement_bankaccount_completion.pot rename to account_move_bankaccount_import/i18n/account_statement_bankaccount_completion.pot diff --git a/account_statement_bankaccount_completion/i18n/es.po b/account_move_bankaccount_import/i18n/es.po similarity index 71% rename from account_statement_bankaccount_completion/i18n/es.po rename to account_move_bankaccount_import/i18n/es.po index c42aa57c..d3d2ceb6 100644 --- a/account_statement_bankaccount_completion/i18n/es.po +++ b/account_move_bankaccount_import/i18n/es.po @@ -17,18 +17,18 @@ msgstr "" "X-Launchpad-Export-Date: 2014-06-06 06:36+0000\n" "X-Generator: Launchpad (build 17031)\n" -#. module: account_statement_bankaccount_completion +#. module: account_move_bankaccount_import #: help:account.bank.statement.line,partner_acc_number:0 msgid "Account number of the partner" msgstr "Número de cuenta de la empresa" -#. module: account_statement_bankaccount_completion -#: model:ir.model,name:account_statement_bankaccount_completion.model_account_bank_statement_line +#. module: account_move_bankaccount_import +#: model:ir.model,name:account_move_bankaccount_import.model_account_bank_statement_line msgid "Bank Statement Line" msgstr "Línea de extracto bancario" -#. module: account_statement_bankaccount_completion -#: code:addons/account_statement_bankaccount_completion/statement.py:68 +#. module: account_move_bankaccount_import +#: code:addons/account_move_bankaccount_import/statement.py:68 #, python-format msgid "" "Line named \"%s\" (Ref:%s) was matched by more than one partner for account " @@ -37,12 +37,12 @@ msgstr "" "La línea llamada \"%s\" (Ref: %s) fue casada con más de una empresa al " "buscar el nº de cuenta \"%s\"." -#. module: account_statement_bankaccount_completion +#. module: account_move_bankaccount_import #: field:account.bank.statement.line,partner_acc_number:0 msgid "Account Number" msgstr "Número de cuenta" -#. module: account_statement_bankaccount_completion -#: model:ir.model,name:account_statement_bankaccount_completion.model_account_statement_completion_rule +#. module: account_move_bankaccount_import +#: model:ir.model,name:account_move_bankaccount_import.model_account_statement_completion_rule msgid "account.statement.completion.rule" msgstr "account.statement.completion.rule" diff --git a/account_statement_bankaccount_completion/i18n/fr.po b/account_move_bankaccount_import/i18n/fr.po similarity index 68% rename from account_statement_bankaccount_completion/i18n/fr.po rename to account_move_bankaccount_import/i18n/fr.po index d084efae..7e1e31b4 100644 --- a/account_statement_bankaccount_completion/i18n/fr.po +++ b/account_move_bankaccount_import/i18n/fr.po @@ -17,30 +17,30 @@ msgstr "" "X-Launchpad-Export-Date: 2014-06-20 06:09+0000\n" "X-Generator: Launchpad (build 17058)\n" -#. module: account_statement_bankaccount_completion +#. module: account_move_bankaccount_import #: help:account.bank.statement.line,partner_acc_number:0 msgid "Account number of the partner" msgstr "" -#. module: account_statement_bankaccount_completion -#: model:ir.model,name:account_statement_bankaccount_completion.model_account_bank_statement_line +#. module: account_move_bankaccount_import +#: model:ir.model,name:account_move_bankaccount_import.model_account_bank_statement_line msgid "Bank Statement Line" msgstr "Ligne de relevé bancaire" -#. module: account_statement_bankaccount_completion -#: code:addons/account_statement_bankaccount_completion/statement.py:68 +#. module: account_move_bankaccount_import +#: code:addons/account_move_bankaccount_import/statement.py:68 #, python-format msgid "" "Line named \"%s\" (Ref:%s) was matched by more than one partner for account " "number \"%s\"." msgstr "" -#. module: account_statement_bankaccount_completion +#. module: account_move_bankaccount_import #: field:account.bank.statement.line,partner_acc_number:0 msgid "Account Number" msgstr "Numéro de compte" -#. module: account_statement_bankaccount_completion -#: model:ir.model,name:account_statement_bankaccount_completion.model_account_statement_completion_rule +#. module: account_move_bankaccount_import +#: model:ir.model,name:account_move_bankaccount_import.model_account_statement_completion_rule msgid "account.statement.completion.rule" msgstr "" diff --git a/account_move_bankaccount_import/models/__init__.py b/account_move_bankaccount_import/models/__init__.py new file mode 100644 index 00000000..61ce482b --- /dev/null +++ b/account_move_bankaccount_import/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import account_move +from . import res_partner_bank diff --git a/account_move_bankaccount_import/models/account_move.py b/account_move_bankaccount_import/models/account_move.py new file mode 100644 index 00000000..ba0aeb6f --- /dev/null +++ b/account_move_bankaccount_import/models/account_move.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openerp import _, fields, models +from openerp.addons.account_move_base_import.models.account_move \ + import ErrorTooManyPartner + + +class AccountMoveCompletionRule(models.Model): + """Add a rule based on transaction ID""" + + _inherit = "account.move.completion.rule" + + function_to_call = fields.Selection( + selection_add=[ + ('get_from_bank_account', + 'From bank account number (Normal or IBAN)') + ]) + + def get_from_bank_account(self, line): + """ + Match the partner based on the partner account number field + Then, call the generic st_line method to complete other values. + :param dict st_line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id' : value, + ...} + """ + partner_acc_number = line.partner_acc_number + if not partner_acc_number: + return {} + res = {} + res_bank_obj = self.env['res.partner.bank'] + banks = res_bank_obj.search_by_acc_number(partner_acc_number) + if len(banks) > 1: + raise ErrorTooManyPartner(_('Line named "%s" was matched ' + 'by more than one partner for account ' + 'number "%s".') % + (line.name, + partner_acc_number)) + if len(banks) == 1: + partner = banks[0].partner_id + res['partner_id'] = partner.id + return res + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + partner_acc_number = fields.Char( + string='Account Number', + size=64, + help="Account number of the partner") diff --git a/account_move_bankaccount_import/models/res_partner_bank.py b/account_move_bankaccount_import/models/res_partner_bank.py new file mode 100644 index 00000000..eadab7db --- /dev/null +++ b/account_move_bankaccount_import/models/res_partner_bank.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openerp import api, models + + +class ResPartnerBank(models.Model): + _inherit = 'res.partner.bank' + + @api.multi + def search_by_acc_number(self, acc_number): + ''' + Try to find the Account Number using a 'like' operator to avoid + problems with the input mask used to store the value. + ''' + # first try with an exact match + banks = self.search([('acc_number', '=', acc_number)]) + if banks: + return banks + + self.env.cr.execute(""" + SELECT + id + FROM + res_partner_bank + WHERE + regexp_replace(acc_number,'([^[:alnum:]])', '','g') + ilike + regexp_replace(%s,'([^[:alnum:]])', '','g') + """, (acc_number,)) + # apply security constraints by using the orm + return self.search( + [('id', 'in', [r[0] for r in self.env.cr.fetchall()])]) diff --git a/account_move_bankaccount_import/tests/__init__.py b/account_move_bankaccount_import/tests/__init__.py new file mode 100644 index 00000000..17337560 --- /dev/null +++ b/account_move_bankaccount_import/tests/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import test_bankaccount_completion diff --git a/account_move_bankaccount_import/tests/test_bankaccount_completion.py b/account_move_bankaccount_import/tests/test_bankaccount_completion.py new file mode 100644 index 00000000..d89a887f --- /dev/null +++ b/account_move_bankaccount_import/tests/test_bankaccount_completion.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# © 2013 ACSONE SA/NV +# © 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openerp.tests import common +from openerp import fields, tools +from openerp.modules import get_module_resource + +ACC_NUMBER = " BE38 7330 4038 5372 " + + +class BankAccountCompletion(common.TransactionCase): + + def setUp(self): + super(BankAccountCompletion, self).setUp() + tools.convert_file(self.cr, 'account', + get_module_resource('account', 'test', + 'account_minimal_test.xml'), + {}, 'init', False, 'test') + self.account_move_obj = self.env["account.move"] + self.account_move_line_obj = \ + self.env["account.move.line"].with_context( + check_move_validity=False + ) + self.company_a = self.browse_ref('base.main_company') + self.completion_rule_id = \ + self.ref('account_move_bankaccount_import.' + 'bank_statement_completion_rule_10') + self.journal = self.browse_ref("account.bank_journal") + self.partner = self.browse_ref('base.main_partner') + self.account_id = self.ref("account.a_recv") + + # Create the profile + self.journal.write({ + 'used_for_completion': True, + 'rule_ids': [(6, 0, [self.completion_rule_id])] + }) + # Create a bank statement + self.move = self.account_move_obj.create({ + "date": fields.Date.today(), + "journal_id": self.journal.id + }) + + # Add a bank account number to the partner + self.res_partner_bank_obj = self.env['res.partner.bank'] + vals = {"state": "bank", + "company_id": self.company_a.id, + "partner_id": self.partner.id, + "acc_number": ACC_NUMBER, + "footer": True, + "bank_name": "Reserve", + } + self.partner_bank = self.res_partner_bank_obj.create(vals) + + def test_00(self): + """Test complete partner_id from bank account number + Test the automatic completion of the partner_id based on the account + number associated to the statement line + """ + for bank_acc_number in [ACC_NUMBER, ACC_NUMBER.replace(" ", ""), + ACC_NUMBER.replace(" ", "-")]: + # check the completion for well formatted and not well + # formatted account number + self.partner_bank.write({"acc_number": bank_acc_number}) + for acc_number in [ACC_NUMBER, ACC_NUMBER.replace(" ", ""), + ACC_NUMBER.replace(" ", "-"), + " BE38-7330 4038-5372 "]: + self.move_line = self.account_move_line_obj.create({ + 'account_id': self.account_id, + 'credit': 1000.0, + 'name': 'EXT001', + 'move_id': self.move.id, + 'partner_acc_number': acc_number + }) + self.assertFalse( + self.move_line.partner_id, + 'Partner_id must be blank before completion') + self.move.button_auto_completion() + self.assertEquals( + self.partner, self.move_line.partner_id, + "Missing expected partner id after completion") + + self.move_line = self.account_move_line_obj.create({ + 'account_id': self.account_id, + 'credit': 1000.0, + 'name': 'EXT001', + 'move_id': self.move.id, + 'partner_acc_number': 'BE38a7330.4038-5372.', + }) + self.assertFalse(self.move_line.partner_id, + 'Partner_id must be blank before completion') + self.move.button_auto_completion() + self.assertFalse(self.move_line.partner_id) diff --git a/account_move_base_import/README.rst b/account_move_base_import/README.rst new file mode 100644 index 00000000..4f223928 --- /dev/null +++ b/account_move_base_import/README.rst @@ -0,0 +1,103 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +======================== +Account move base import +======================== + +This module is a grouping of 7.0/8.0 modules, used to import accounting files +and completing them automatically: + +* account_statement_base_completion +* account_statement_base_import +* account_statement_commission +* account_statement_ext + +The main change is that, in order to import financial data, this information +is now imported directly as a Journal Entry. + +Most of the information present in the "statement profile" is now located in +the account journal (with 2 boolean parameters which allows to use +this journal for importation and/or auto-completion). + +Financial data can be imported using a standard .csv or .xls file (you'll find +it in the 'data' folder). It respects the journal to pass the entries. + +This module can handle a commission taken by the payment office and has the +following format: +* __date__: date of the payment +* __amount__: amount paid in the currency of the journal used in the +importation +* __label__: the comunication given by the payment office, used as +communication in the generated entries. + +Another column which can be used is __commission_amount__, representing +the amount for the commission taken by line. + +Afterwards, the goal is to populate the journal items with information that +the bank or office gave you. For this, completion rules can be specified by +journal. + +Some basic rules are provided in this module: + +1) Match from statement line label (based on partner field 'Bank Statement +Label') +2) Match from statement line label (based on partner name) +3) Match from statement line label (based on Invoice reference) + +Feel free to extend either the importation method, the completion method, or +both. + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/{repo_id}/{branch} + +.. repo_id is available in https://github.com/OCA/maintainer-tools/blob/master/tools/repos_with_ids.txt +.. branch is "8.0" for example + +Known issues / Roadmap +====================== + +* As for now, the module does not handle multicurrency imports. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Joël Grand-Guillaume +* Nicolas Bessi +* Laurent Mignon +* Sébastien Beau +* Matthieu Dietrich + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://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 https://odoo-community.org. diff --git a/account_move_base_import/__init__.py b/account_move_base_import/__init__.py new file mode 100644 index 00000000..8bbeba75 --- /dev/null +++ b/account_move_base_import/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import parser +from . import wizard +from . import models diff --git a/account_move_base_import/__openerp__.py b/account_move_base_import/__openerp__.py new file mode 100644 index 00000000..e356aa0f --- /dev/null +++ b/account_move_base_import/__openerp__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +{ + 'name': "Journal Entry base import", + 'version': '9.0.1.0.0', + 'author': "Akretion,Camptocamp,Odoo Community Association (OCA)", + 'category': 'Finance', + 'depends': ['account'], + 'website': 'http://www.camptocamp.com', + 'data': [ + "security/ir.model.access.csv", + "data/completion_rule_data.xml", + "wizard/import_statement_view.xml", + "views/account_move_view.xml", + "views/journal_view.xml", + "views/partner_view.xml", + ], + 'test': [ + 'test/partner.yml', + 'test/invoice.yml', + 'test/supplier_invoice.yml', + 'test/refund.yml', + 'test/completion_test.yml' + ], + 'installable': True, + 'auto_install': False, + 'license': 'AGPL-3', +} diff --git a/account_move_base_import/data/completion_rule_data.xml b/account_move_base_import/data/completion_rule_data.xml new file mode 100644 index 00000000..ae1ba858 --- /dev/null +++ b/account_move_base_import/data/completion_rule_data.xml @@ -0,0 +1,28 @@ + + + + + Match from line label (based on partner field 'Bank Statement Label') + 60 + get_from_name_and_partner_field + + + + Match from line label (based on partner name) + 70 + get_from_name_and_partner_name + + + + Match from line label (based on Invoice number) + 40 + get_from_name_and_invoice + + + + Match from line label (based on Invoice Supplier number) + 45 + get_from_name_and_supplier_invoice + + + diff --git a/account_move_base_import/data/statement.csv b/account_move_base_import/data/statement.csv new file mode 100644 index 00000000..21dead31 --- /dev/null +++ b/account_move_base_import/data/statement.csv @@ -0,0 +1,4 @@ +"date";"amount";"commission_amount";"label" +2011-03-07 13:45:14;118.4;-11.84;"label a" +2011-03-02 13:45:14;189;-15.12;"label b" +2011-03-02 17:45:14;189;-15.12;"label c" diff --git a/account_move_base_import/data/statement.xls b/account_move_base_import/data/statement.xls new file mode 100644 index 00000000..5f5a57b0 Binary files /dev/null and b/account_move_base_import/data/statement.xls differ diff --git a/account_statement_base_import/i18n/account_statement_base_import.pot b/account_move_base_import/i18n/account_statement_base_import.pot similarity index 100% rename from account_statement_base_import/i18n/account_statement_base_import.pot rename to account_move_base_import/i18n/account_statement_base_import.pot diff --git a/account_statement_base_import/i18n/es.po b/account_move_base_import/i18n/es.po similarity index 62% rename from account_statement_base_import/i18n/es.po rename to account_move_base_import/i18n/es.po index b990338f..41dc3b2b 100644 --- a/account_statement_base_import/i18n/es.po +++ b/account_move_base_import/i18n/es.po @@ -17,29 +17,29 @@ msgstr "" "X-Launchpad-Export-Date: 2014-06-06 06:36+0000\n" "X-Generator: Launchpad (build 17031)\n" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 -#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action +#: model:ir.actions.act_window,name:account_move_base_import.statement_importer_action msgid "Import statement" msgstr "Importar extracto" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 msgid "Historical Import Logs" msgstr "Registro histórico de importaciones" -#. module: account_statement_base_import -#: model:ir.model,name:account_statement_base_import.model_credit_statement_import +#. module: account_move_base_import +#: model:ir.model,name:account_move_base_import.model_credit_statement_import msgid "credit.statement.import" msgstr "credit.statement.import" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,input_statement:0 msgid "Statement file" msgstr "Archivo de extracto" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:168 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:168 #, python-format msgid "" "Column %s you try to import is not present in the bank statement line!" @@ -47,91 +47,91 @@ msgstr "" "La columna %s que intenta importar no está presente en la línea del extracto " "bancario." -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:162 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:162 #, python-format msgid "Nothing to import" msgstr "Nada que importar" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,journal_id:0 msgid "Financial journal to use transaction" msgstr "Diario contable a usar" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:102 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:102 #, python-format msgid "Column %s not present in file" msgstr "La columna %s no está presente en el archivo" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 -#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu +#: model:ir.ui.menu,name:account_move_base_import.statement_importer_menu msgid "Import Bank Statement" msgstr "Importar extracto bancario" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:54 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:54 #, python-format msgid "User Error" msgstr "Error de usuario" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:223 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:223 #, python-format msgid "The statement cannot be created: %s" msgstr "El extracto no puede ser creado: %s" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:167 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:167 #, python-format msgid "Missing column!" msgstr "Columna ausente" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/parser.py:166 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/parser.py:166 #, python-format msgid "No buffer file given." msgstr "No se ha proporcionado ningún búfer de archivo." -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:107 -#: code:addons/account_statement_base_import/parser/file_parser.py:171 -#: code:addons/account_statement_base_import/parser/file_parser.py:205 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:107 +#: code:addons/account_move_base_import/parser/file_parser.py:171 +#: code:addons/account_move_base_import/parser/file_parser.py:205 #, python-format msgid "Invalid data" msgstr "Datos inválidos" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,launch_import_completion:0 msgid "Launch completion after import" msgstr "Lanzar el completado después de la importación" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,partner_id:0 msgid "Credit insitute partner" msgstr "Empresa para el agente financiero" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 msgid "Import related infos" msgstr "Importar información relacionada" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:163 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:163 #, python-format msgid "The file is empty" msgstr "El archivo está vacío" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/wizard/import_statement.py:93 +#. module: account_move_base_import +#: code:addons/account_move_base_import/wizard/import_statement.py:93 #, python-format msgid "Please use a file with an extention" msgstr "Use por favor un archivo con extensión" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:172 -#: code:addons/account_statement_base_import/parser/file_parser.py:206 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:172 +#: code:addons/account_move_base_import/parser/file_parser.py:206 #, python-format msgid "" "Value %s of column %s is not valid.\n" @@ -145,15 +145,15 @@ msgstr "" " \n" "Detalles: %s" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:29 -#: code:addons/account_statement_base_import/parser/generic_file_parser.py:31 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:29 +#: code:addons/account_move_base_import/parser/generic_file_parser.py:31 #, python-format msgid "Please install python lib xlrd" msgstr "Por favor instale la librería de Python xlrd" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:160 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:160 #, python-format msgid "" " It should be YYYY-MM-DD for column: %s value: %s \n" @@ -169,24 +169,24 @@ msgstr "" " \n" "Detalles: %s" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,last_import_date:0 msgid "Last Import Date" msgstr "Última fecha de importación" -#. module: account_statement_base_import -#: model:ir.model,name:account_statement_base_import.model_account_statement_profile +#. module: account_move_base_import +#: model:ir.model,name:account_move_base_import.model_account_statement_profile msgid "Statement Profile" msgstr "Perfil de extracto" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:234 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:234 #, python-format msgid "Statement import error" msgstr "Error de importación del extracto" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:193 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:193 #, python-format msgid "" "Please modify the cell formatting to date format for column: %s value: %s\n" @@ -200,18 +200,18 @@ msgstr "" " \n" "Detalles: %s" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:192 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:192 #, python-format msgid "Date format is not valid" msgstr "El formato de fecha no es válido" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,import_type:0 msgid "Type of import" msgstr "Tipo de importación" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:account.statement.profile,launch_import_completion:0 msgid "" "Tic that box to automatically launch the completion on each imported file " @@ -220,7 +220,7 @@ msgstr "" "Marque esta casilla para lanzar automáticamente el completado en cada " "archivo importado usando este perfil." -#. module: account_statement_base_import +#. module: account_move_base_import #: help:credit.statement.import,balance_check:0 msgid "" "Tic that box if you want OpenERP to control the start/end balance before " @@ -231,81 +231,81 @@ msgstr "" "antes de confirmar un extracto bancaria. Si no está marcada, no se realizará " "ningún control de saldo." -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:154 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:154 #, python-format msgid "No Profile!" msgstr "Sin perfil" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:159 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:159 #, python-format msgid "Date format is not valid." msgstr "El formato de fecha no es válido." -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,profile_id:0 msgid "Import configuration parameter" msgstr "Parámetros de configuración de la importación" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,rec_log:0 msgid "log" msgstr "registro" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 msgid "Import Parameters Summary" msgstr "Resumen de parámetros de importación" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,balance_check:0 msgid "Balance check" msgstr "Comprobar saldo" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,force_partner_on_bank:0 msgid "Force partner on bank move" msgstr "Forzar empresa en el apunte bancario" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,file_name:0 msgid "File Name" msgstr "Nombre del archivo" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:55 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:55 #, python-format msgid "Invalid file type %s. Please use csv or xls" msgstr "Tipo de archivo %s no válido. Utilice por favor CSV o XLS." -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:155 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:155 #, python-format msgid "You must provide a valid profile to import a bank statement!" msgstr "Debe introducir un perfil válido para importar un extracto bancario" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:83 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:83 #, python-format msgid "Statement ID %s have been imported with %s lines." msgstr "El extracto con ID %s ha sido importado con %s líneas." -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,receivable_account_id:0 msgid "Force Receivable/Payable Account" msgstr "Forzar cuenta a cobrar/a pagar" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:164 -#: code:addons/account_statement_base_import/parser/file_parser.py:174 -#: code:addons/account_statement_base_import/parser/file_parser.py:198 -#: code:addons/account_statement_base_import/parser/file_parser.py:208 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:164 +#: code:addons/account_move_base_import/parser/file_parser.py:174 +#: code:addons/account_move_base_import/parser/file_parser.py:198 +#: code:addons/account_move_base_import/parser/file_parser.py:208 #, python-format msgid "Missing" msgstr "Ausente" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:account.statement.profile,import_type:0 msgid "" "Choose here the method by which you want to import bank statement for this " @@ -314,12 +314,12 @@ msgstr "" "Escoja aquí el método con el que quiere importar el extracto bancario para " "este perfil." -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 msgid "Cancel" msgstr "Cancelar" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:credit.statement.import,force_partner_on_bank:0 msgid "" "Tic that box if you want to use the credit insitute partner in the " diff --git a/account_statement_base_import/i18n/fr.po b/account_move_base_import/i18n/fr.po similarity index 56% rename from account_statement_base_import/i18n/fr.po rename to account_move_base_import/i18n/fr.po index a1418535..15407a9f 100644 --- a/account_statement_base_import/i18n/fr.po +++ b/account_move_base_import/i18n/fr.po @@ -17,119 +17,119 @@ msgstr "" "X-Launchpad-Export-Date: 2014-05-22 06:49+0000\n" "X-Generator: Launchpad (build 17017)\n" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 -#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action +#: model:ir.actions.act_window,name:account_move_base_import.statement_importer_action msgid "Import statement" msgstr "Import de relevé" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 msgid "Historical Import Logs" msgstr "" -#. module: account_statement_base_import -#: model:ir.model,name:account_statement_base_import.model_credit_statement_import +#. module: account_move_base_import +#: model:ir.model,name:account_move_base_import.model_credit_statement_import msgid "credit.statement.import" msgstr "credit.statement.import" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,input_statement:0 msgid "Statement file" msgstr "Fichier à importer" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:168 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:168 #, python-format msgid "" "Column %s you try to import is not present in the bank statement line!" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:162 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:162 #, python-format msgid "Nothing to import" msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,journal_id:0 msgid "Financial journal to use transaction" msgstr "Journal" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:102 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:102 #, python-format msgid "Column %s not present in file" msgstr "Colonne %s non présente dans le fichier" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 -#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu +#: model:ir.ui.menu,name:account_move_base_import.statement_importer_menu msgid "Import Bank Statement" msgstr "Importation de relevé" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:54 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:54 #, python-format msgid "User Error" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:223 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:223 #, python-format msgid "The statement cannot be created: %s" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:167 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:167 #, python-format msgid "Missing column!" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/parser.py:166 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/parser.py:166 #, python-format msgid "No buffer file given." msgstr "Pas de fichier tampon donné." -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:107 -#: code:addons/account_statement_base_import/parser/file_parser.py:171 -#: code:addons/account_statement_base_import/parser/file_parser.py:205 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:107 +#: code:addons/account_move_base_import/parser/file_parser.py:171 +#: code:addons/account_move_base_import/parser/file_parser.py:205 #, python-format msgid "Invalid data" msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,launch_import_completion:0 msgid "Launch completion after import" msgstr "Lancer l'auto-complétion après import" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,partner_id:0 msgid "Credit insitute partner" msgstr "Organisme bancaire" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:account.statement.profile:0 msgid "Import related infos" msgstr "Importation des informations liées" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:163 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:163 #, python-format msgid "The file is empty" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/wizard/import_statement.py:93 +#. module: account_move_base_import +#: code:addons/account_move_base_import/wizard/import_statement.py:93 #, python-format msgid "Please use a file with an extention" msgstr "Veuillez sélectionner un fichier avec une extension" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:172 -#: code:addons/account_statement_base_import/parser/file_parser.py:206 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:172 +#: code:addons/account_move_base_import/parser/file_parser.py:206 #, python-format msgid "" "Value %s of column %s is not valid.\n" @@ -138,15 +138,15 @@ msgid "" " Detail: %s" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:29 -#: code:addons/account_statement_base_import/parser/generic_file_parser.py:31 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:29 +#: code:addons/account_move_base_import/parser/generic_file_parser.py:31 #, python-format msgid "Please install python lib xlrd" msgstr "Veuillez installer la bibliothèque python xlrd" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:160 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:160 #, python-format msgid "" " It should be YYYY-MM-DD for column: %s value: %s \n" @@ -157,24 +157,24 @@ msgid "" " Detail: %s" msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,last_import_date:0 msgid "Last Import Date" msgstr "Date de dernier import" -#. module: account_statement_base_import -#: model:ir.model,name:account_statement_base_import.model_account_statement_profile +#. module: account_move_base_import +#: model:ir.model,name:account_move_base_import.model_account_statement_profile msgid "Statement Profile" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:234 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:234 #, python-format msgid "Statement import error" msgstr "Erreur d'import de relevé" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:193 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:193 #, python-format msgid "" "Please modify the cell formatting to date format for column: %s value: %s\n" @@ -183,25 +183,25 @@ msgid "" " Detail: %s" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:192 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:192 #, python-format msgid "Date format is not valid" msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,import_type:0 msgid "Type of import" msgstr "Type d'import" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:account.statement.profile,launch_import_completion:0 msgid "" "Tic that box to automatically launch the completion on each imported file " "using this profile." msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:credit.statement.import,balance_check:0 msgid "" "Tic that box if you want OpenERP to control the start/end balance before " @@ -209,93 +209,93 @@ msgid "" "done." msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:154 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:154 #, python-format msgid "No Profile!" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:159 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:159 #, python-format msgid "Date format is not valid." msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,profile_id:0 msgid "Import configuration parameter" msgstr "Paramètres de configuration d'import" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:account.statement.profile,rec_log:0 msgid "log" msgstr "journal" -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 msgid "Import Parameters Summary" msgstr "Résumé des paramètres d'import" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,balance_check:0 msgid "Balance check" msgstr "Vérification des soldes" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,force_partner_on_bank:0 msgid "Force partner on bank move" msgstr "Forcer un partenaire sur la ligne du compte de banque" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,file_name:0 msgid "File Name" msgstr "Nom du fichier" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:55 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:55 #, python-format msgid "Invalid file type %s. Please use csv or xls" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:155 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:155 #, python-format msgid "You must provide a valid profile to import a bank statement!" msgstr "" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/statement.py:83 +#. module: account_move_base_import +#: code:addons/account_move_base_import/statement.py:83 #, python-format msgid "Statement ID %s have been imported with %s lines." msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: field:credit.statement.import,receivable_account_id:0 msgid "Force Receivable/Payable Account" msgstr "Forcer le compte Client/Fournisseur" -#. module: account_statement_base_import -#: code:addons/account_statement_base_import/parser/file_parser.py:164 -#: code:addons/account_statement_base_import/parser/file_parser.py:174 -#: code:addons/account_statement_base_import/parser/file_parser.py:198 -#: code:addons/account_statement_base_import/parser/file_parser.py:208 +#. module: account_move_base_import +#: code:addons/account_move_base_import/parser/file_parser.py:164 +#: code:addons/account_move_base_import/parser/file_parser.py:174 +#: code:addons/account_move_base_import/parser/file_parser.py:198 +#: code:addons/account_move_base_import/parser/file_parser.py:208 #, python-format msgid "Missing" msgstr "" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:account.statement.profile,import_type:0 msgid "" "Choose here the method by which you want to import bank statement for this " "profile." msgstr "Choisissez la méthode d'import de relevé pour ce profil." -#. module: account_statement_base_import +#. module: account_move_base_import #: view:credit.statement.import:0 msgid "Cancel" msgstr "Annulation" -#. module: account_statement_base_import +#. module: account_move_base_import #: help:credit.statement.import,force_partner_on_bank:0 msgid "" "Tic that box if you want to use the credit insitute partner in the " diff --git a/account_move_base_import/models/__init__.py b/account_move_base_import/models/__init__.py new file mode 100644 index 00000000..e945d363 --- /dev/null +++ b/account_move_base_import/models/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import account_journal +from . import account_move +from . import partner diff --git a/account_move_base_import/models/account_journal.py b/account_move_base_import/models/account_journal.py new file mode 100644 index 00000000..029800d7 --- /dev/null +++ b/account_move_base_import/models/account_journal.py @@ -0,0 +1,334 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import sys +import traceback +import os +from openerp import _, api, fields, models +from ..parser.parser import new_move_parser +from openerp.exceptions import UserError, ValidationError +from operator import attrgetter + + +class AccountJournal(models.Model): + _name = 'account.journal' + _inherit = ['account.journal', 'mail.thread'] + + used_for_import = fields.Boolean( + string="Journal used for import") + + commission_account_id = fields.Many2one( + comodel_name='account.account', + string='Commission account') + + import_type = fields.Selection( + [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')], + string='Type of import', + default='generic_csvxls_so', + required=True, + help="Choose here the method by which you want to import account " + "moves for this journal.") + + last_import_date = fields.Datetime( + string="Last Import Date") + + partner_id = fields.Many2one( + comodel_name='res.partner', + string='Bank/Payment Office partner', + help="Put a partner if you want to have it on the commission move " + "(and optionaly on the counterpart of the intermediate/" + "banking move if you tick the corresponding checkbox).") + + receivable_account_id = fields.Many2one( + comodel_name='account.account', + string='Receivable/Payable Account', + help="Choose a receivable/payable account to use as the default " + "debit/credit account.") + + used_for_completion = fields.Boolean( + string="Journal used for completion") + + rule_ids = fields.Many2many( + comodel_name='account.move.completion.rule', + string='Auto-completion rules', + relation='account_journal_completion_rule_rel') + + launch_import_completion = fields.Boolean( + string="Launch completion after import", + help="Tic that box to automatically launch the completion " + "on each imported file using this journal.") + + create_counterpart = fields.Boolean( + string="Create Counterpart", + help="Tick that box to automatically create the move counterpart", + default=True) + + split_counterpart = fields.Boolean( + string="Split Counterpart", + help="Two counterparts will be automatically created : one for " + "the refunds and one for the payments") + + def _get_rules(self): + # We need to respect the sequence order + return sorted(self.rule_ids, key=attrgetter('sequence')) + + def _find_values_from_rules(self, calls, line): + """This method will execute all related rules, in their sequence order, + to retrieve all the values returned by the first rules that will match. + :param calls: list of lookup function name available in rules + :param dict line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id: value, + ...} + """ + if not calls: + calls = self._get_rules() + rule_obj = self.env['account.move.completion.rule'] + for call in calls: + method_to_call = getattr(rule_obj, call.function_to_call) + result = method_to_call(line) + if result: + result['already_completed'] = True + return result + return None + + @api.multi + def _prepare_counterpart_line(self, move, amount, date): + if amount > 0.0: + account_id = self.default_debit_account_id.id + credit = 0.0 + debit = amount + else: + account_id = self.default_credit_account_id.id + credit = -amount + debit = 0.0 + counterpart_values = { + 'name': _('/'), + 'date_maturity': date, + 'credit': credit, + 'debit': debit, + 'partner_id': self.partner_id.id, + 'move_id': move.id, + 'account_id': account_id, + 'already_completed': True, + 'journal_id': self.id, + 'company_id': self.company_id.id, + 'currency_id': self.currency_id.id, + 'company_currency_id': self.company_id.currency_id.id, + 'amount_residual': amount, + } + return counterpart_values + + @api.multi + def _create_counterpart(self, parser, move): + move_line_obj = self.env['account.move.line'] + refund = 0.0 + payment = 0.0 + transfer_lines = [] + for move_line in move.line_ids: + refund -= move_line.debit + payment += move_line.credit + if self.split_counterpart: + if refund: + transfer_lines.append(refund) + if payment: + transfer_lines.append(payment) + else: + total_amount = refund + payment + if total_amount: + transfer_lines.append(total_amount) + counterpart_date = parser.get_move_vals().get('date') or \ + fields.Date.today() + transfer_line_count = len(transfer_lines) + check_move_validity = False + for amount in transfer_lines: + transfer_line_count -= 1 + if not transfer_line_count: + check_move_validity = True + vals = self._prepare_counterpart_line(move, amount, + counterpart_date) + move_line_obj.with_context( + check_move_validity=check_move_validity + ).create(vals) + + @api.multi + def _write_extra_move_lines(self, parser, move): + """Insert extra lines after the main statement lines. + + After the main statement lines have been created, you can override this + method to create extra statement lines. + + :param: browse_record of the current parser + :param: result_row_list: [{'key':value}] + :param: profile: browserecord of account.statement.profile + :param: statement_id: int/long of the current importing + statement ID + :param: context: global context + """ + move_line_obj = self.env['account.move.line'] + global_commission_amount = 0 + for row in parser.result_row_list: + global_commission_amount += float( + row.get('commission_amount', '0.0')) + partner_id = self.partner_id.id + # Commission line + if global_commission_amount > 0.0: + raise UserError(_('Commission amount should not be positive.')) + elif global_commission_amount < 0.0: + if not self.commission_account_id: + raise UserError( + _('No commission account is set on the journal.')) + else: + commission_account_id = self.commission_account_id.id + comm_values = { + 'name': _('Commission line'), + 'date_maturity': parser.get_move_vals().get('date') or + fields.Date.today(), + 'debit': -global_commission_amount, + 'partner_id': partner_id, + 'move_id': move.id, + 'account_id': commission_account_id, + 'already_completed': True, + } + move_line_obj.with_context( + check_move_validity=False + ).create(comm_values) + + @api.multi + def write_logs_after_import(self, move, num_lines): + """Write the log in the logger + + :param int/long statement_id: ID of the concerned + account.bank.statement + :param int/long num_lines: Number of line that have been parsed + :return: True + """ + self.message_post( + body=_('Move %s have been imported with %s ' + 'lines.') % (move.name, num_lines)) + return True + + def prepare_move_line_vals(self, parser_vals, move): + """Hook to build the values of a line from the parser returned values. + At least it fullfill the basic values. Overide it to add your own + completion if needed. + + :param dict of vals from parser for account.bank.statement.line + (called by parser.get_st_line_vals) + :param int/long statement_id: ID of the concerned + account.bank.statement + :return: dict of vals that will be passed to create method of + statement line. + """ + move_line_obj = self.env['account.move.line'] + values = parser_vals + values['company_id'] = self.company_id.id + values['currency_id'] = self.currency_id.id + values['company_currency_id'] = self.company_id.currency_id.id + values['journal_id'] = self.id + values['move_id'] = move.id + if not values.get('account_id', False): + values['account_id'] = self.receivable_account_id.id + values = move_line_obj._add_missing_default_values(values) + return values + + def prepare_move_vals(self, result_row_list, parser): + """Hook to build the values of the statement from the parser and + the profile. + """ + vals = {'journal_id': self.id, + 'currency_id': self.currency_id.id} + vals.update(parser.get_move_vals()) + return vals + + def multi_move_import(self, file_stream, ftype="csv"): + """Create multiple bank statements from values given by the parser for + the given profile. + + :param int/long profile_id: ID of the profile used to import the file + :param filebuffer file_stream: binary of the providen file + :param char: ftype represent the file exstension (csv by default) + :return: list: list of ids of the created account.bank.statemênt + """ + filename = self._context.get('file_name', None) + if filename: + (filename, __) = os.path.splitext(filename) + parser = new_move_parser(self, ftype=ftype, move_ref=filename) + res = self.env['account.move'] + for result_row_list in parser.parse(file_stream): + move = self._move_import(parser, file_stream, ftype=ftype) + res |= move + return res + + def _move_import(self, parser, file_stream, ftype="csv"): + """Create a bank statement with the given profile and parser. It will + fullfill the bank statement with the values of the file providen, but + will not complete data (like finding the partner, or the right + account). This will be done in a second step with the completion rules. + + :param prof : The profile used to import the file + :param parser: the parser + :param filebuffer file_stream: binary of the providen file + :param char: ftype represent the file exstension (csv by default) + :return: ID of the created account.bank.statemênt + """ + move_obj = self.env['account.move'] + move_line_obj = self.env['account.move.line'] + attachment_obj = self.env['ir.attachment'] + result_row_list = parser.result_row_list + # Check all key are present in account.bank.statement.line!! + if not result_row_list: + raise UserError(_("Nothing to import: " + "The file is empty")) + parsed_cols = parser.get_move_line_vals(result_row_list[0]).keys() + for col in parsed_cols: + if col not in move_line_obj._columns: + raise UserError( + _("Missing column! Column %s you try to import is not " + "present in the bank statement line!") % col) + move_vals = self.prepare_move_vals(result_row_list, parser) + move = move_obj.create(move_vals) + try: + # Record every line in the bank statement + move_store = [] + for line in result_row_list: + parser_vals = parser.get_move_line_vals(line) + values = self.prepare_move_line_vals(parser_vals, move) + move_store.append(values) + # Hack to bypass ORM poor perfomance. Sob... + move_line_obj._insert_lines(move_store) + self.env.invalidate_all() + self._write_extra_move_lines(parser, move) + if self.create_counterpart: + self._create_counterpart(parser, move) + attachment_data = { + 'name': 'statement file', + 'datas': file_stream, + 'datas_fname': "%s.%s" % (fields.Date.today(), ftype), + 'res_model': 'account.move', + 'res_id': move.id, + } + attachment_obj.create(attachment_data) + # If user ask to launch completion at end of import, do it! + if self.launch_import_completion: + move.button_auto_completion() + # Write the needed log infos on profile + self.write_logs_after_import(move, len(result_row_list)) + except UserError: + # "Clean" exception, raise as such + raise + except Exception: + error_type, error_value, trbk = sys.exc_info() + st = "Error: %s\nDescription: %s\nTraceback:" % ( + error_type.__name__, error_value) + st += ''.join(traceback.format_tb(trbk, 30)) + raise ValidationError( + _("Statement import error" + "The statement cannot be created: %s") % st) + return move diff --git a/account_move_base_import/models/account_move.py b/account_move_base_import/models/account_move.py new file mode 100644 index 00000000..c0e77e49 --- /dev/null +++ b/account_move_base_import/models/account_move.py @@ -0,0 +1,407 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import traceback +import sys +import logging + +import psycopg2 + +from openerp import _, api, fields, models +from openerp.exceptions import ValidationError + + +_logger = logging.getLogger(__name__) + + +class ErrorTooManyPartner(Exception): + """ New Exception definition that is raised when more than one partner is + matched by the completion rule. + """ + + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + def __repr__(self): + return repr(self.value) + + +class AccountMoveCompletionRule(models.Model): + """This will represent all the completion method that we can have to + fullfill the bank statement lines. You'll be able to extend them in you own + module and choose those to apply for every statement profile. + The goal of a rule is to fullfill at least the partner of the line, but + if possible also the reference because we'll use it in the reconciliation + process. The reference should contain the invoice number or the SO number + or any reference that will be matched by the invoice accounting move. + """ + _name = "account.move.completion.rule" + _order = "sequence asc" + + sequence = fields.Integer( + string='Sequence', + help="Lower means parsed first.") + name = fields.Char( + string='Name') + journal_ids = fields.Many2many( + comodel_name='account.journal', + relation='account_journal_completion_rule_rel', + string='Related journals') + function_to_call = fields.Selection([ + ('get_from_name_and_invoice', + 'From line name (based on customer invoice number)'), + ('get_from_name_and_supplier_invoice', + 'From line name (based on supplier invoice number)'), + ('get_from_name_and_partner_field', + 'From line name (based on partner field)'), + ('get_from_name_and_partner_name', + 'From line name (based on partner name)') + ], string='Method') + + def _find_invoice(self, line, inv_type): + """Find invoice related to statement line""" + inv_obj = self.env['account.invoice'] + if inv_type == 'supplier': + type_domain = ('in_invoice', 'in_refund') + number_field = 'reference' + elif inv_type == 'customer': + type_domain = ('out_invoice', 'out_refund') + number_field = 'number' + else: + raise ValidationError( + _('Invalid invoice type for completion: %') % inv_type) + + invoices = inv_obj.search([(number_field, '=', line.name.strip()), + ('type', 'in', type_domain)]) + if invoices: + if len(invoices) == 1: + return invoices + else: + raise ErrorTooManyPartner( + _('Line named "%s" was matched by more than one ' + 'partner while looking on %s invoices') % + (line.name, inv_type)) + return False + + def _from_invoice(self, line, inv_type): + """Populate statement line values""" + if inv_type not in ('supplier', 'customer'): + raise ValidationError( + _('Invalid invoice type for completion: %') % + inv_type) + res = {} + invoice = self._find_invoice(line, inv_type) + if invoice: + partner_id = invoice.commercial_partner_id.id + res = {'partner_id': partner_id, + 'account_id': invoice.account_id.id} + return res + + # Should be private but data are initialised with no update XML + def get_from_name_and_supplier_invoice(self, line): + """Match the partner based on the invoice number and the reference of + the statement line. Then, call the generic get_values_for_line method + to complete other values. If more than one partner matched, raise the + ErrorTooManyPartner error. + + :param dict line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id': value, + ...} + """ + return self._from_invoice(line, 'supplier') + + # Should be private but data are initialised with no update XML + def get_from_name_and_invoice(self, line): + """Match the partner based on the invoice number and the reference of + the statement line. Then, call the generic get_values_for_line method + to complete other values. If more than one partner matched, raise the + ErrorTooManyPartner error. + + :param dict line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id': value, + ...} + """ + return self._from_invoice(line, 'customer') + + # Should be private but data are initialised with no update XML + def get_from_name_and_partner_field(self, line): + """ + Match the partner based on the label field of the statement line and + the text defined in the 'bank_statement_label' field of the partner. + Remember that we can have values separated with ; Then, call the + generic get_values_for_line method to complete other values. If more + than one partner matched, raise the ErrorTooManyPartner error. + + :param dict line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id': value, + + ...} + """ + res = {} + partner_obj = self.env['res.partner'] + or_regex = ".*;? *%s *;?.*" % line.name + sql = ("SELECT id from res_partner" + " WHERE bank_statement_label ~* %s") + self.env.cr.execute(sql, (or_regex, )) + partner_ids = self.env.cr.fetchall() + partners = partner_obj.browse([x[0] for x in partner_ids]) + if partners: + if len(partners) > 1: + msg = (_('Line named "%s" was matched by more than ' + 'one partner while looking on partner label: %s') % + (line.name, + ','.join([x.name for x in partners]))) + raise ErrorTooManyPartner(msg) + res['partner_id'] = partners[0].id + return res + + def get_from_name_and_partner_name(self, line): + """Match the partner based on the label field of the statement line and + the name of the partner. Then, call the generic get_values_for_line + method to complete other values. If more than one partner matched, + raise the ErrorTooManyPartner error. + + :param dict st_line: read of the concerned account.bank.statement.line + :return: + A dict of value that can be passed directly to the write method of + the statement line or {} + {'partner_id': value, + 'account_id': value, + + ...} + """ + res = {} + # The regexp_replace() escapes the name to avoid false positive + # example: 'John J. Doe (No 1)' is escaped to 'John J\. Doe \(No 1\)' + # See http://stackoverflow.com/a/400316/1504003 for a list of + # chars to escape. Postgres is POSIX-ARE, compatible with + # POSIX-ERE excepted that '\' must be escaped inside brackets according + # to: + # http://www.postgresql.org/docs/9.0/static/functions-matching.html + # in chapter 9.7.3.6. Limits and Compatibility + sql = r""" + SELECT id FROM ( + SELECT id, + regexp_matches(%s, + regexp_replace(name,'([\.\^\$\*\+\?\(\)\[\{\\\|])', %s, + 'g'), 'i') AS name_match + FROM res_partner) + AS res_partner_matcher + WHERE name_match IS NOT NULL""" + self.env.cr.execute(sql, (line.name, r"\\\1")) + result = self.env.cr.fetchall() + if result: + if len(result) > 1: + raise ErrorTooManyPartner( + _('Line named "%s" was matched by more than one ' + 'partner while looking on partner by name') % + line.name) + res['partner_id'] = result[0][0] + return res + + +class AccountMoveLine(models.Model): + """ + Add sparse field on the statement line to allow to store all the bank infos + that are given by a bank/office. You can then add you own in your module. + The idea here is to store all bank/office infos in the + additionnal_bank_fields serialized field when importing the file. If many + values, add a tab in the bank statement line to store your specific one. + Have a look in account_move_base_import module to see how we've done + it. + """ + _inherit = "account.move.line" + _order = "already_completed desc, date asc" + + already_completed = fields.Boolean( + string="Auto-Completed", + default=False, + help="When this checkbox is ticked, the auto-completion " + "process/button will ignore this line.") + + def _get_line_values_from_rules(self, rules): + """We'll try to find out the values related to the line based on rules + setted on the profile.. We will ignore line for which already_completed + is ticked. + + :return: + A dict of dict value that can be passed directly to the write + method of the statement line or {}. The first dict has statement + line ID as a key: {117009: {'partner_id': 100997, + 'account_id': 489L}} + """ + journal_obj = self.env['account.journal'] + for line in self: + if not line.already_completed: + # Ask the rule + vals = journal_obj._find_values_from_rules(rules, line) + if vals: + vals['id'] = line['id'] + return vals + return {} + + def _get_available_columns(self, move_store): + """Return writeable by SQL columns""" + model_cols = self._columns + avail = [ + k for k, col in model_cols.iteritems() if not hasattr(col, '_fnct') + ] + keys = [k for k in move_store[0].keys() if k in avail] + keys.sort() + return keys + + def _prepare_insert(self, move, cols): + """ Apply column formating to prepare data for SQL inserting + Return a copy of statement + """ + move_copy = move + for k, col in move_copy.iteritems(): + if k in cols: + move_copy[k] = self._columns[k]._symbol_set[1](col) + return move_copy + + def _prepare_manyinsert(self, move_store, cols): + """ Apply column formating to prepare multiple SQL inserts + Return a copy of statement_store + """ + values = [] + for move in move_store: + values.append(self._prepare_insert(move, cols)) + return values + + def _insert_lines(self, move_store): + """ Do raw insert into database because ORM is awfully slow + when doing batch write. It is a shame that batch function + does not exist""" + self.check_access_rule('create') + self.check_access_rights('create', raise_exception=True) + cols = self._get_available_columns(move_store) + move_store = self._prepare_manyinsert(move_store, cols) + tmp_vals = (', '.join(cols), ', '.join(['%%(%s)s' % i for i in cols])) + sql = "INSERT INTO account_move_line (%s) " \ + "VALUES (%s);" % tmp_vals + try: + self.env.cr.executemany(sql, tuple(move_store)) + except psycopg2.Error as sql_err: + self.env.cr.rollback() + raise ValidationError(_("ORM bypass error"), + sql_err.pgerror) + + def _update_line(self, vals): + """ Do raw update into database because ORM is awfully slow + when cheking security. + """ + cols = self._get_available_columns([vals]) + vals = self._prepare_insert(vals, cols) + tmp_vals = (', '.join(['%s = %%(%s)s' % (i, i) for i in cols])) + sql = "UPDATE account_move_line " \ + "SET %s where id = %%(id)s;" % tmp_vals + try: + self.env.cr.execute(sql, vals) + except psycopg2.Error as sql_err: + self.env.cr.rollback() + raise ValidationError(_("ORM bypass error"), + sql_err.pgerror) + + +class AccountMove(models.Model): + """We add a basic button and stuff to support the auto-completion + of the bank statement once line have been imported or manually fullfill. + """ + _name = 'account.move' + _inherit = ['account.move', 'mail.thread'] + + used_for_completion = fields.Boolean( + related='journal_id.used_for_completion', + readonly=True) + completion_logs = fields.Text(string='Completion Log', readonly=True) + + def write_completion_log(self, error_msg, number_imported): + """Write the log in the completion_logs field of the bank statement to + let the user know what have been done. This is an append mode, so we + don't overwrite what already recoded. + + :param int/long stat_id: ID of the account.bank.statement + :param char error_msg: Message to add + :number_imported int/long: Number of lines that have been completed + :return True + """ + user_name = self.env.user.name + number_line = len(self.line_ids) + log = self.completion_logs or "" + completion_date = fields.Datetime.now() + message = (_("%s Account Move %s has %s/%s lines completed by " + "%s \n%s\n%s\n") % (completion_date, self.name, + number_imported, number_line, + user_name, error_msg, log)) + self.write({'completion_logs': message}) + + body = (_('Statement ID %s auto-completed for %s/%s lines completed') % + (self.name, number_imported, number_line)), + self.message_post(body=body) + return True + + @api.multi + def button_auto_completion(self): + """Complete line with values given by rules and tic the + already_completed checkbox so we won't compute them again unless the + user untick them! + """ + move_line_obj = self.env['account.move.line'] + compl_lines = 0 + move_line_obj.check_access_rule('create') + move_line_obj.check_access_rights('create', raise_exception=True) + for move in self: + msg_lines = [] + journal = move.journal_id + rules = journal._get_rules() + res = False + for line in move.line_ids: + try: + res = line._get_line_values_from_rules(rules) + if res: + compl_lines += 1 + except ErrorTooManyPartner, exc: + msg_lines.append(repr(exc)) + except Exception, exc: + msg_lines.append(repr(exc)) + error_type, error_value, trbk = sys.exc_info() + st = "Error: %s\nDescription: %s\nTraceback:" % ( + error_type.__name__, error_value) + st += ''.join(traceback.format_tb(trbk, 30)) + _logger.error(st) + if res: + try: + move_line_obj._update_line(res) + except Exception as exc: + msg_lines.append(repr(exc)) + error_type, error_value, trbk = sys.exc_info() + st = "Error: %s\nDescription: %s\nTraceback:" % ( + error_type.__name__, error_value) + st += ''.join(traceback.format_tb(trbk, 30)) + _logger.error(st) + # we can commit as it is not needed to be atomic + # commiting here adds a nice perfo boost + if not compl_lines % 500: + self.env.cr.commit() + msg = u'\n'.join(msg_lines) + self.write_completion_log(msg, compl_lines) + return True diff --git a/account_move_base_import/models/partner.py b/account_move_base_import/models/partner.py new file mode 100644 index 00000000..818868bc --- /dev/null +++ b/account_move_base_import/models/partner.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openerp import fields, models + + +class ResPartner(models.Model): + """Add a bank label on the partner so that we can use it to match + this partner when we found this in a statement line. + """ + _inherit = 'res.partner' + + bank_statement_label = fields.Char( + string='Bank Statement Label', + help="Enter the various label found on your bank statement " + "separated by a ; If one of this label is include in the " + "bank statement line, the partner will be automatically " + "filled (as long as you use this method/rules in your " + "statement profile).") diff --git a/account_move_base_import/parser/__init__.py b/account_move_base_import/parser/__init__.py new file mode 100644 index 00000000..1233bf9d --- /dev/null +++ b/account_move_base_import/parser/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from .parser import new_move_parser +from .parser import AccountMoveImportParser +from . import file_parser +from . import generic_file_parser diff --git a/account_statement_base_import/parser/file_parser.py b/account_move_base_import/parser/file_parser.py similarity index 77% rename from account_statement_base_import/parser/file_parser.py rename to account_move_base_import/parser/file_parser.py index 706e0b2d..6b3f48fb 100644 --- a/account_statement_base_import/parser/file_parser.py +++ b/account_move_base_import/parser/file_parser.py @@ -1,28 +1,14 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# Copyright Camptocamp SA -# Author Nicolas Bessi, Joel Grand-Guillaume -# This program 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. -# -# 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 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 . -# -############################################################################## +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) from openerp.tools.translate import _ -from openerp.osv.orm import except_orm +from openerp.exceptions import UserError import tempfile import datetime -from .parser import BankStatementImportParser -from .parser import UnicodeDictReader +from .parser import AccountMoveImportParser, UnicodeDictReader try: import xlrd except: @@ -35,13 +21,13 @@ def float_or_zero(val): return float(val) if val else 0.0 -class FileParser(BankStatementImportParser): +class FileParser(AccountMoveImportParser): """Generic abstract class for defining parser for .csv, .xls or .xlsx file format. """ - def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, - dialect=None, **kwargs): + def __init__(self, journal, ftype='csv', extra_fields=None, header=None, + dialect=None, move_ref=None, **kwargs): """ :param char: parse_name: The name of the parser :param char: ftype: extension of the file (could be csv, xls or @@ -51,12 +37,11 @@ class FileParser(BankStatementImportParser): :param list: header : specify header fields if the csv file has no header """ - super(FileParser, self).__init__(parse_name, **kwargs) + super(FileParser, self).__init__(journal, **kwargs) if ftype in ('csv', 'xls', 'xlsx'): self.ftype = ftype[0:3] else: - raise except_orm( - _('User Error'), + raise UserError( _('Invalid file type %s. Please use csv, xls or xlsx') % ftype) self.conversion_dict = extra_fields self.keys_to_validate = self.conversion_dict.keys() @@ -65,6 +50,7 @@ class FileParser(BankStatementImportParser): # 0 means Windows mode (1900 based dates). # Set in _parse_xls, from the contents of the file self.dialect = dialect + self.move_ref = move_ref def _custom_format(self, *args, **kwargs): """No other work on data are needed in this parser.""" @@ -96,8 +82,7 @@ class FileParser(BankStatementImportParser): parsed_cols = self.result_row_list[0].keys() for col in self.keys_to_validate: if col not in parsed_cols: - raise except_orm(_('Invalid data'), - _('Column %s not present in file') % col) + raise UserError(_('Column %s not present in file') % col) return True def _post(self, *args, **kwargs): @@ -143,9 +128,9 @@ class FileParser(BankStatementImportParser): line[rule] = datetime.datetime.strptime(date_string, '%Y-%m-%d') except ValueError as err: - raise except_orm( - _("Date format is not valid."), - _(" It should be YYYY-MM-DD for column: %s" + raise UserError( + _("Date format is not valid." + " It should be YYYY-MM-DD for column: %s" " value: %s \n \n \n Please check the line with " "ref: %s \n \n Detail: %s") % (rule, line.get(rule, _('Missing')), @@ -154,8 +139,7 @@ class FileParser(BankStatementImportParser): try: line[rule] = conversion_rules[rule](line[rule]) except Exception as err: - raise except_orm( - _('Invalid data'), + raise UserError( _("Value %s of column %s is not valid.\n Please " "check the line with ref %s:\n \n Detail: %s") % (line.get(rule, _('Missing')), rule, @@ -174,9 +158,9 @@ class FileParser(BankStatementImportParser): self._datemode) line[rule] = datetime.datetime(*t_tuple) except Exception as err: - raise except_orm( - _("Date format is not valid"), - _("Please modify the cell formatting to date " + raise UserError( + _("Date format is not valid. " + "Please modify the cell formatting to date " "format for column: %s value: %s\n Please check " "the line with ref: %s\n \n Detail: %s") % (rule, line.get(rule, _('Missing')), @@ -185,8 +169,7 @@ class FileParser(BankStatementImportParser): try: line[rule] = conversion_rules[rule](line[rule]) except Exception as err: - raise except_orm( - _('Invalid data'), + raise UserError( _("Value %s of column %s is not valid.\n Please " "check the line with ref %s:\n \n Detail: %s") % (line.get(rule, _('Missing')), rule, diff --git a/account_move_base_import/parser/generic_file_parser.py b/account_move_base_import/parser/generic_file_parser.py new file mode 100644 index 00000000..d6f466b1 --- /dev/null +++ b/account_move_base_import/parser/generic_file_parser.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import datetime +from .file_parser import FileParser +from openerp.addons.account_move_base_import.parser.file_parser import ( + float_or_zero +) +from openerp.tools import ustr + + +class GenericFileParser(FileParser): + """Standard parser that use a define format in csv or xls to import into a + bank statement. This is mostely an example of how to proceed to create a + new parser, but will also be useful as it allow to import a basic flat + file. + """ + + def __init__(self, journal, ftype='csv', **kwargs): + conversion_dict = { + 'label': ustr, + 'date': datetime.datetime, + 'amount': float_or_zero, + } + # set self.env for later ORM searches + self.env = journal.env + super(GenericFileParser, self).__init__( + journal, ftype=ftype, + extra_fields=conversion_dict, + **kwargs) + + @classmethod + def parser_for(cls, parser_name): + """Used by the new_bank_statement_parser class factory. Return true if + the providen name is generic_csvxls_so + """ + return parser_name == 'generic_csvxls_so' + + def get_move_line_vals(self, line, *args, **kwargs): + """ + This method must return a dict of vals that can be passed to create + method of statement line in order to record it. It is the + responsibility of every parser to give this dict of vals, so each one + can implement his own way of recording the lines. + :param: line: a dict of vals that represent a line of + result_row_list + :return: dict of values to give to the create method of statement + line, it MUST contain at least: + { + 'name':value, + 'date_maturity':value, + 'credit':value, + 'debit':value + } + """ + account_obj = self.env['account.account'] + partner_obj = self.env['res.partner'] + account_id = False + partner_id = False + + if line.get('account'): + accounts = account_obj.search([('code', '=', line['account'])]) + if len(accounts) == 1: + account_id = accounts[0].id + + if line.get('partner'): + partners = partner_obj.search([('name', '=', line['partner'])]) + if len(partners) == 1: + partner_id = partners[0].id + + amount = line.get('amount', 0.0) + return { + 'name': line.get('label', '/'), + 'date_maturity': line.get('date', datetime.datetime.now().date()), + 'credit': amount > 0.0 and amount or 0.0, + 'debit': amount < 0.0 and -amount or 0.0, + 'account_id': account_id, + 'partner_id': partner_id, + } diff --git a/account_statement_base_import/parser/parser.py b/account_move_base_import/parser/parser.py similarity index 77% rename from account_statement_base_import/parser/parser.py rename to account_move_base_import/parser/parser.py index 999be4b0..e20d5613 100644 --- a/account_statement_base_import/parser/parser.py +++ b/account_move_base_import/parser/parser.py @@ -1,27 +1,12 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# Author: Joel Grand-Guillaume -# Copyright 2011-2012 Camptocamp SA -# -# 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 . -# -############################################################################## +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) import base64 import csv -from datetime import datetime -from openerp.tools.translate import _ +from openerp import _, fields def UnicodeDictReader(utf8_data, **kwargs): @@ -41,7 +26,7 @@ def UnicodeDictReader(utf8_data, **kwargs): for key, value in row.iteritems()]) -class BankStatementImportParser(object): +class AccountMoveImportParser(object): """ Generic abstract class for defining parser for different files and @@ -50,21 +35,20 @@ class BankStatementImportParser(object): from the FileParser instead. """ - def __init__(self, profile, *args, **kwargs): + def __init__(self, journal, *args, **kwargs): # The name of the parser as it will be called - self.parser_name = profile.import_type + self.parser_name = journal.import_type # The result as a list of row. One row per line of data in the file, # but not the commission one! self.result_row_list = None # The file buffer on which to work on self.filebuffer = None # The profile record to access its parameters in any parser method - self.profile = profile - self.balance_start = None - self.balance_end = None - self.statement_name = None - self.statement_date = None - self.support_multi_statements = False + self.journal = journal + self.move_date = None + self.move_name = None + self.move_ref = None + self.support_multi_moves = None @classmethod def parser_for(cls, parser_name): @@ -119,19 +103,18 @@ class BankStatementImportParser(object): """ return NotImplementedError - def get_st_vals(self): + def get_move_vals(self): """This method return a dict of vals that ca be passed to create method of statement. :return: dict of vals that represent additional infos for the statement """ return { - 'name': self.statement_name or '/', - 'balance_start': self.balance_start, - 'balance_end_real': self.balance_end, - 'date': self.statement_date or datetime.now() + 'name': self.move_name or '/', + 'date': self.move_date or fields.Datetime.now(), + 'ref': self.move_ref or '/' } - def get_st_line_vals(self, line, *args, **kwargs): + def get_move_line_vals(self, line, *args, **kwargs): """Implement a method in your parser that must return a dict of vals that can be passed to create method of statement line in order to record it. It is the responsibility of every parser to give this dict @@ -165,7 +148,7 @@ class BankStatementImportParser(object): raise Exception(_('No buffer file given.')) self._format(*args, **kwargs) self._pre(*args, **kwargs) - if self.support_multi_statements: + if self.support_multi_moves: while self._parse(*args, **kwargs): self._validate(*args, **kwargs) self._post(*args, **kwargs) @@ -218,13 +201,13 @@ def itersubclasses(cls, _seen=None): yield sub -def new_bank_statement_parser(profile, *args, **kwargs): +def new_move_parser(journal, *args, **kwargs): """Return an instance of the good parser class based on the given profile. :param profile: browse_record of import profile. :return: class instance for given profile import type. """ - for cls in itersubclasses(BankStatementImportParser): - if cls.parser_for(profile.import_type): - return cls(profile, *args, **kwargs) + for cls in itersubclasses(AccountMoveImportParser): + if cls.parser_for(journal.import_type): + return cls(journal, *args, **kwargs) raise ValueError diff --git a/account_move_base_import/security/ir.model.access.csv b/account_move_base_import/security/ir.model.access.csv new file mode 100644 index 00000000..f1bdb6c1 --- /dev/null +++ b/account_move_base_import/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_bank_st_cmpl_user,account.move.completion.rule.user,model_account_move_completion_rule,account.group_account_user,1,0,0,0 +access_account_bank_st_cmpl_manager,account.move.completion.rule.manager,model_account_move_completion_rule,account.group_account_manager,1,1,1,1 diff --git a/account_move_base_import/test/completion_test.yml b/account_move_base_import/test/completion_test.yml new file mode 100644 index 00000000..8dd265b6 --- /dev/null +++ b/account_move_base_import/test/completion_test.yml @@ -0,0 +1,122 @@ +- + In order to test the banking framework, I first need to create a journal +- + !record {model: account.journal, id: account.bank_journal}: + used_for_completion: True + rule_ids: + - bank_statement_completion_rule_4 + - bank_statement_completion_rule_2 + - bank_statement_completion_rule_3 + - bank_statement_completion_rule_5 +- + Now I create a statement. I create statment lines separately because I need + to find each one by XML id +- + !record {model: account.move, id: move_test1}: + name: Move 2 + journal_id: account.bank_journal + company_id: base.main_company +- + I create a move line for a CI +- + !record {model: account.move.line, id: move_line_ci}: + name: \ + account_id: account.a_sale + move_id: move_test1 + date_maturity: '2013-12-20' + credit: 0.0 +- + I create a move line for a SI +- + !record {model: account.move.line, id: move_line_si}: + name: \ + account_id: account.a_expense + move_id: move_test1 + date_maturity: '2013-12-19' + debit: 0.0 +- + I create a move line for a CR +- + !record {model: account.move.line, id: move_line_cr}: + name: \ + account_id: account.a_expense + move_id: move_test1 + date_maturity: '2013-12-19' + debit: 0.0 +- + I create a move line for the Partner Name +- + !record {model: account.move.line, id: move_line_partner_name}: + name: Test autocompletion based on Partner Name Camptocamp + account_id: account.a_sale + move_id: move_test1 + date_maturity: '2013-12-17' + credit: 0.0 +- + I create a move line for the Partner Label +- + !record {model: account.move.line, id: move_line_partner_label}: + name: XXX66Z + account_id: account.a_sale + move_id: move_test1 + date_maturity: '2013-12-24' + debit: 0.0 +- + and add the correct name +- + !python {model: account.move.line}: | + import datetime as dt + context['check_move_validity'] = False + model.write(cr, uid, [ref('move_line_ci')], + {'name': dt.date.today().strftime('TBNK/%Y/0001'), + 'credit': 210.0}, + context) + model.write(cr, uid, [ref('move_line_si')], + {'name': 'T2S12345', + 'debit': 65.0}, + context) + model.write(cr, uid, [ref('move_line_cr')], + {'name': dt.date.today().strftime('RTEXJ/%Y/0001'), + 'debit': 210.0}, + context) + model.write(cr, uid, [ref('move_line_partner_name')], + {'credit': 600.0}, + context) + model.write(cr, uid, [ref('move_line_partner_label')], + {'debit': 932.4}, + context) +- + I run the auto complete +- + !python {model: account.move}: | + result = self.button_auto_completion(cr, uid, [ref("move_test1")]) +- + Now I can check that all is nice and shiny, line 1. I expect the Customer + Invoice Number to be recognised. + I Use _ref, because ref conflicts with the field ref of the statement line +- + !assert {model: account.move.line, id: move_line_ci, string: Check completion by CI number}: + - partner_id.id == _ref("base.res_partner_12") +- + Line 2. I expect the Supplier invoice number to be recognised. The supplier + invoice was created by the account module demo data, and we confirmed it + here. +- + !assert {model: account.move.line, id: move_line_si, string: Check completion by SI number}: + - partner_id.id == _ref("base.res_partner_12") +- + Line 3. I expect the Customer refund number to be recognised. It should be + the commercial partner, and not the regular partner. +- + !assert {model: account.move.line, id: move_line_cr, string: Check completion by CR number and commercial partner}: + - partner_id.id == _ref("base.res_partner_12") +- + Line 4. I check that the partner name has been recognised. +- + !assert {model: account.move.line, id: move_line_partner_name, string: Check completion by partner name}: + - partner_id.name == 'Camptocamp' +- + Line 5. I check that the partner special label has been recognised. +- + !assert {model: account.move.line, id: move_line_partner_label, string: Check completion by partner label}: + - partner_id.id == _ref("base.res_partner_4") diff --git a/account_move_base_import/test/invoice.yml b/account_move_base_import/test/invoice.yml new file mode 100644 index 00000000..5379f670 --- /dev/null +++ b/account_move_base_import/test/invoice.yml @@ -0,0 +1,42 @@ +- + I import account minimal data +- + !python {model: account.invoice}: | + openerp.tools.convert_file(cr, + 'account', + openerp.modules.get_module_resource( + 'account', + 'test', + 'account_minimal_test.xml'), + {}, 'init', False, 'test') +- + I create a customer Invoice to be found by the completion. +- + !record {model: account.invoice, id: invoice_for_completion_1}: + company_id: base.main_company + currency_id: base.EUR + invoice_line_ids: + - name: '[PCSC234] PC Assemble SC234' + price_unit: 210.0 + quantity: 1.0 + product_id: product.product_product_3 + uom_id: product.product_uom_unit + journal_id: account.bank_journal + partner_id: base.res_partner_12 + reference_type: none +- + I confirm the Invoice +- + !workflow {model: account.invoice, action: invoice_open, ref: invoice_for_completion_1} +- + I check that the invoice state is "Open" +- + !assert {model: account.invoice, id: invoice_for_completion_1}: + - state == 'open' +- + I check that it is given the number "TBNK/%Y/0001" +- + !python {model: account.invoice}: | + import datetime as dt + invoice = model.browse(cr, uid, ref('invoice_for_completion_1'), context) + assert invoice.number == dt.date.today().strftime('TBNK/%Y/0001') diff --git a/account_statement_base_completion/test/partner.yml b/account_move_base_import/test/partner.yml similarity index 62% rename from account_statement_base_completion/test/partner.yml rename to account_move_base_import/test/partner.yml index bc8fca6c..5e207100 100644 --- a/account_statement_base_completion/test/partner.yml +++ b/account_move_base_import/test/partner.yml @@ -1,5 +1,5 @@ - I fill in the field Bank Statement Label in a Partner - - !record {model: res.partner, id: base.res_partner_6}: + !record {model: res.partner, id: base.res_partner_4}: bank_statement_label: XXX66Z diff --git a/account_statement_base_completion/test/refund.yml b/account_move_base_import/test/refund.yml similarity index 71% rename from account_statement_base_completion/test/refund.yml rename to account_move_base_import/test/refund.yml index f8c07909..2779ba73 100644 --- a/account_statement_base_completion/test/refund.yml +++ b/account_move_base_import/test/refund.yml @@ -12,17 +12,14 @@ I create a customer refund to be found by the completion. - !record {model: account.invoice, id: refund_for_completion_1}: - account_id: account.a_pay company_id: base.main_company currency_id: base.EUR - internal_number: CR0001 - invoice_line: - - account_id: account.a_expense - name: '[PCSC234] PC Assemble SC234' + invoice_line_ids: + - name: '[PCSC234] PC Assemble SC234' price_unit: 210.0 quantity: 1.0 product_id: product.product_product_3 - uos_id: product.product_uom_unit + uom_id: product.product_uom_unit journal_id: account.expenses_journal partner_id: res_partner_12_child type: 'out_refund' @@ -37,7 +34,9 @@ !assert {model: account.invoice, id: refund_for_completion_1}: - state == 'open' - - I check that it is given the number "CR0001" + I check that it is given the number "RTEXJ/%Y/0001" - - !assert {model: account.invoice, id: refund_for_completion_1, string: Check CI number}: - - number == 'CR0001' + !python {model: account.invoice}: | + import datetime as dt + invoice = model.browse(cr, uid, ref('refund_for_completion_1'), context) + assert invoice.number == dt.date.today().strftime('RTEXJ/%Y/0001') diff --git a/account_statement_base_completion/test/supplier_invoice.yml b/account_move_base_import/test/supplier_invoice.yml similarity index 66% rename from account_statement_base_completion/test/supplier_invoice.yml rename to account_move_base_import/test/supplier_invoice.yml index 1bd826f9..4ac8e87d 100644 --- a/account_statement_base_completion/test/supplier_invoice.yml +++ b/account_move_base_import/test/supplier_invoice.yml @@ -1,3 +1,14 @@ +- + I import account minimal data +- + !python {model: account.invoice}: | + openerp.tools.convert_file(cr, + 'account', + openerp.modules.get_module_resource( + 'account', + 'demo', + 'account_invoice_demo.yml'), + {}, 'init', False, 'test') - I check that my invoice is a supplier invoice - @@ -8,7 +19,7 @@ - !python {model: account.invoice}: | self.write(cr, uid, ref('account.demo_invoice_0'), { - 'supplier_invoice_number': 'T2S12345' + 'reference': 'T2S12345' }) - I check a second time that my invoice is still a supplier invoice @@ -23,7 +34,7 @@ I check that the supplier number is there - !assert {model: account.invoice, id: account.demo_invoice_0, string: Check supplier number}: - - supplier_invoice_number == 'T2S12345' + - reference == 'T2S12345' - I check a third time that my invoice is still a supplier invoice - diff --git a/account_move_base_import/tests/__init__.py b/account_move_base_import/tests/__init__.py new file mode 100644 index 00000000..2a150908 --- /dev/null +++ b/account_move_base_import/tests/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from . import test_base_completion +from . import test_base_import diff --git a/account_move_base_import/tests/test_base_completion.py b/account_move_base_import/tests/test_base_completion.py new file mode 100644 index 00000000..8e36ee10 --- /dev/null +++ b/account_move_base_import/tests/test_base_completion.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openerp import fields, tools +from openerp.modules import get_module_resource +from openerp.tests import common +from collections import namedtuple + +name_completion_case = namedtuple( + "name_completion_case", ["partner_name", "line_label", "should_match"]) +NAMES_COMPLETION_CASES = [ + name_completion_case("Acsone", "Line for Acsone SA", True), + name_completion_case("Acsone", "Line for Acsone", True), + name_completion_case("Acsone", "Acsone for line", True), + name_completion_case("acsone", "Acsone for line", True), + name_completion_case("Acsone SA", "Line for Acsone SA test", True), + name_completion_case("Ac..ne", "Acsone for line", False), + name_completion_case("é@|r{}", "Acsone é@|r{} for line", True), + name_completion_case("Acsone", "A..one for line", False), + name_completion_case("A.one SA", "A.one SA for line", True), + name_completion_case( + "Acsone SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA test", False), + name_completion_case( + "Acsone ([^a-zA-Z0-9 -]) SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA " + "test", True), + name_completion_case( + r"Acsone (.^$*+?()[{\| -]\) SA", r"Line for Acsone (.^$*+?()[{\| -]\) " + r"SA test", True), + name_completion_case("Acšone SA", "Line for Acšone SA test", True), +] + + +class BaseCompletion(common.TransactionCase): + + def setUp(self): + super(BaseCompletion, self).setUp() + tools.convert_file(self.cr, 'account', + get_module_resource('account', 'test', + 'account_minimal_test.xml'), + {}, 'init', False, 'test') + self.account_move_obj = self.env["account.move"] + self.account_move_line_obj = \ + self.env["account.move.line"] + self.company_a = self.browse_ref('base.main_company') + self.journal = self.browse_ref("account.bank_journal") + self.partner = self.browse_ref("base.res_partner_12") + self.account_id = self.ref("account.a_recv") + + def test_name_completion(self): + """Test complete partner_id from statement line label + Test the automatic completion of the partner_id based if the name of + the partner appears in the statement line label + """ + self.completion_rule_id = self.ref( + 'account_move_base_import.bank_statement_completion_rule_3') + # Create the profile + self.journal.write({ + 'used_for_completion': True, + 'rule_ids': [(6, 0, [self.completion_rule_id])] + }) + # Create a bank statement + self.move = self.account_move_obj.create({ + "date": fields.Date.today(), + "journal_id": self.journal.id + }) + + for case in NAMES_COMPLETION_CASES: + self.partner.write({'name': case.partner_name}) + self.move_line = self.account_move_line_obj.with_context( + check_move_validity=False + ).create({ + 'account_id': self.account_id, + 'credit': 1000.0, + 'name': case.line_label, + 'move_id': self.move.id, + }) + self.assertFalse( + self.move_line.partner_id, + "Partner_id must be blank before completion") + self.move.button_auto_completion() + if case.should_match: + self.assertEquals( + self.partner, self.move_line.partner_id, + "Missing expected partner id after completion " + "(partner_name: %s, line_name: %s)" % + (case.partner_name, case.line_label)) + else: + self.assertNotEquals( + self.partner, self.move_line.partner_id, + "Partner id should be empty after completion " + "(partner_name: %s, line_name: %s)" + % (case.partner_name, case.line_label)) diff --git a/account_move_base_import/tests/test_base_import.py b/account_move_base_import/tests/test_base_import.py new file mode 100644 index 00000000..4f62edb7 --- /dev/null +++ b/account_move_base_import/tests/test_base_import.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# © 2011 Akretion +# © 2011-2016 Camptocamp SA +# © 2013 Savoir-faire Linux +# © 2014 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +import base64 +import inspect +import os +from operator import attrgetter +from openerp.tests import common +from openerp import tools +from openerp.modules import get_module_resource + + +class TestCodaImport(common.TransactionCase): + + def setUp(self): + super(TestCodaImport, self).setUp() + self.company_a = self.browse_ref('base.main_company') + tools.convert_file(self.cr, 'account', + get_module_resource('account', 'test', + 'account_minimal_test.xml'), + {}, 'init', False, 'test') + self.account_move_obj = self.env["account.move"] + self.account_move_line_obj = self.env["account.move.line"] + self.account_id = self.ref("account.a_recv") + self.journal = self.browse_ref("account.bank_journal") + self.import_wizard_obj = self.env['credit.statement.import'] + self.partner = self.browse_ref("base.res_partner_12") + self.journal.write({ + 'used_for_import': True, + "import_type": "generic_csvxls_so", + 'partner_id': self.partner.id, + 'commission_account_id': self.account_id, + 'receivable_account_id': self.account_id, + 'create_counterpart': True, + }) + + def _filename_to_abs_filename(self, file_name): + dir_name = os.path.dirname(inspect.getfile(self.__class__)) + return os.path.join(dir_name, file_name) + + def _import_file(self, file_name): + """ import a file using the wizard + return the create account.bank.statement object + """ + with open(file_name) as f: + content = f.read() + self.wizard = self.import_wizard_obj.create({ + "journal_id": self.journal.id, + 'input_statement': base64.b64encode(content), + 'file_name': os.path.basename(file_name), + }) + res = self.wizard.import_statement() + return self.account_move_obj.browse(res['res_id']) + + def test_simple_xls(self): + """Test import from xls + """ + file_name = self._filename_to_abs_filename( + os.path.join("..", "data", "statement.xls")) + move = self._import_file(file_name) + self._validate_imported_move(move) + + def test_simple_csv(self): + """Test import from csv + """ + file_name = self._filename_to_abs_filename( + os.path.join("..", "data", "statement.csv")) + move = self._import_file(file_name) + self._validate_imported_move(move) + + def _validate_imported_move(self, move): + self.assertEqual("/", move.name) + self.assertEqual(5, len(move.line_ids)) + move_line = sorted(move.line_ids, + key=attrgetter('date_maturity'))[2] + # common infos + self.assertEqual(move_line.date_maturity, "2011-03-07") + self.assertEqual(move_line.credit, 118.4) + self.assertEqual(move_line.name, "label a") diff --git a/account_move_base_import/views/account_move_view.xml b/account_move_base_import/views/account_move_view.xml new file mode 100644 index 00000000..e16e225d --- /dev/null +++ b/account_move_base_import/views/account_move_view.xml @@ -0,0 +1,67 @@ + + + account.move.view + account.move + + + + + + + + + + + + + + + + + + + account.move.completion.rule.view + account.move.completion.rule + +
+ + + + + + + + + +
+ + + account.move.completion.rule.view + account.move.completion.rule + + + + + + + + + + + + Move Completion Rule + account.move.completion.rule + form + tree,form + + + +
diff --git a/account_move_base_import/views/journal_view.xml b/account_move_base_import/views/journal_view.xml new file mode 100644 index 00000000..e5f95bde --- /dev/null +++ b/account_move_base_import/views/journal_view.xml @@ -0,0 +1,44 @@ + + + + account.journal.view + account.journal + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - account_bank_statement_import_base.bank_statement.auto_cmpl - account.bank.statement - - - - - - - - - - - - account.statement.profile.view - account.statement.profile - - - - - - - - - - - - account.statement.completion.rule.view - account.statement.completion.rule - -
- - - - - - - -
- - - account.statement.completion.rule.view - account.statement.completion.rule - - - - - - - - - - - Statement Completion Rule - account.statement.completion.rule - form - tree,form - - - - - - diff --git a/account_statement_base_completion/test/completion_test.yml b/account_statement_base_completion/test/completion_test.yml deleted file mode 100644 index 5f5cdbad..00000000 --- a/account_statement_base_completion/test/completion_test.yml +++ /dev/null @@ -1,102 +0,0 @@ -- - In order to test the banking framework, I first need to create a profile -- - !record {model: account.statement.profile, id: profile_test1}: - name: Bank EUR Profile - journal_id: account.bank_journal - commission_account_id: account.a_expense - company_id: base.main_company - balance_check: True - rule_ids: - - bank_statement_completion_rule_4 - - bank_statement_completion_rule_5 - - bank_statement_completion_rule_2 - - bank_statement_completion_rule_3 -- - Now I create a statement. I create statment lines separately because I need - to find each one by XML id -- - !record {model: account.bank.statement, id: statement_test1}: - name: Statement 2 - profile_id: profile_test1 - company_id: base.main_company -- - I create a statement line for a CI -- - !record {model: account.bank.statement.line, id: statement_line_ci}: - name: Test autocompletion based on Customer Invoice Number - statement_id: statement_test1 - ref: CI0001 - date: '2013-12-20' - amount: 210.0 -- - I create a statement line for a SI -- - !record {model: account.bank.statement.line, id: statement_line_si}: - name: Test autocompletion based on Supplier Invoice Number - statement_id: statement_test1 - ref: T2S12345 - date: '2013-12-19' - amount: -65.0 -- - I create a statement line for a CR -- - !record {model: account.bank.statement.line, id: statement_line_cr}: - name: Test autocompletion based on Customer Refund Number - statement_id: statement_test1 - ref: CR0001 - date: '2013-12-19' - amount: -210.0 -- - I create a statement line for the Partner Name -- - !record {model: account.bank.statement.line, id: statement_line_partner_name}: - name: Test autocompletion based on Partner Name Vauxoo - statement_id: statement_test1 - ref: / - date: '2013-12-17' - amount: 600.0 -- - I create a statement line for the Partner Label -- - !record {model: account.bank.statement.line, id: statement_line_partner_label}: - name: test autocompletion based on text (XXX66Z) matching with partner form information (note that Ref does not exist) - statement_id: statement_test1 - ref: ZU788 - date: '2013-12-24' - amount: -932.4 -- - I run the auto complete -- - !python {model: account.bank.statement}: | - result = self.button_auto_completion(cr, uid, [ref("statement_test1")]) -- - Now I can check that all is nice and shiny, line 1. I expect the Customer - Invoice Number to be recognised. - I Use _ref, because ref conflicts with the field ref of the statement line -- - !assert {model: account.bank.statement.line, id: statement_line_ci, string: Check completion by CI number}: - - partner_id.id == _ref("base.res_partner_12") -- - Line 2. I expect the Supplier invoice number to be recognised. The supplier - invoice was created by the account module demo data, and we confirmed it - here. -- - !assert {model: account.bank.statement.line, id: statement_line_si, string: Check completion by SI number}: - - partner_id.id == _ref("base.res_partner_17") -- - Line 3. I expect the Customer refund number to be recognised. It should be - the commercial partner, and not the regular partner. -- - !assert {model: account.bank.statement.line, id: statement_line_cr, string: Check completion by CR number and commercial partner}: - - partner_id.id == _ref("base.res_partner_12") -- - Line 4. I check that the partner name has been recognised. -- - !assert {model: account.bank.statement.line, id: statement_line_partner_name, string: Check completion by partner name}: - - partner_id.name == 'Vauxoo' -- - Line 5. I check that the partner special label has been recognised. -- - !assert {model: account.bank.statement.line, id: statement_line_partner_label, string: Check completion by partner label}: - - partner_id.id == _ref("base.res_partner_6") diff --git a/account_statement_base_completion/test/invoice.yml b/account_statement_base_completion/test/invoice.yml deleted file mode 100644 index 5619f0cd..00000000 --- a/account_statement_base_completion/test/invoice.yml +++ /dev/null @@ -1,32 +0,0 @@ -- - I create a customer Invoice to be found by the completion. -- - !record {model: account.invoice, id: invoice_for_completion_1}: - account_id: account.a_recv - company_id: base.main_company - currency_id: base.EUR - internal_number: CI0001 - invoice_line: - - account_id: account.a_sale - name: '[PCSC234] PC Assemble SC234' - price_unit: 210.0 - quantity: 1.0 - product_id: product.product_product_3 - uos_id: product.product_uom_unit - journal_id: account.bank_journal - partner_id: base.res_partner_12 - reference_type: none -- - I confirm the Invoice -- - !workflow {model: account.invoice, action: invoice_open, ref: invoice_for_completion_1} -- - I check that the invoice state is "Open" -- - !assert {model: account.invoice, id: invoice_for_completion_1}: - - state == 'open' -- - I check that it is given the number "CI0001" -- - !assert {model: account.invoice, id: invoice_for_completion_1, string: Check CI number}: - - number == 'CI0001' diff --git a/account_statement_base_completion/tests/__init__.py b/account_statement_base_completion/tests/__init__.py deleted file mode 100644 index 6f9e09ea..00000000 --- a/account_statement_base_completion/tests/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Authors: Laurent Mignon -# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) -# All Rights Reserved -# -# 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_base_completion - -checks = [ - test_base_completion -] diff --git a/account_statement_base_completion/tests/test_base_completion.py b/account_statement_base_completion/tests/test_base_completion.py deleted file mode 100644 index 9fd9adb5..00000000 --- a/account_statement_base_completion/tests/test_base_completion.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -# -# -# Authors: Laurent Mignon -# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) -# All Rights Reserved -# -# 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.tests import common -import time -from collections import namedtuple - -name_completion_case = namedtuple( - "name_completion_case", ["partner_name", "line_label", "should_match"]) -NAMES_COMPLETION_CASES = [ - name_completion_case("Acsone", "Line for Acsone SA", True), - name_completion_case("Acsone", "Line for Acsone", True), - name_completion_case("Acsone", "Acsone for line", True), - name_completion_case("acsone", "Acsone for line", True), - name_completion_case("Acsone SA", "Line for Acsone SA test", True), - name_completion_case("Ac..ne", "Acsone for line", False), - name_completion_case("é@|r{}", "Acsone é@|r{} for line", True), - name_completion_case("Acsone", "A..one for line", False), - name_completion_case("A.one SA", "A.one SA for line", True), - name_completion_case( - "Acsone SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA test", False), - name_completion_case( - "Acsone ([^a-zA-Z0-9 -]) SA", "Line for Acsone ([^a-zA-Z0-9 -]) SA " - "test", True), - name_completion_case( - r"Acsone (.^$*+?()[{\| -]\) SA", r"Line for Acsone (.^$*+?()[{\| -]\) " - r"SA test", True), - name_completion_case("Acšone SA", "Line for Acšone SA test", True), -] - - -class base_completion(common.TransactionCase): - - def setUp(self): - super(base_completion, self).setUp() - self.company_a = self.browse_ref('base.main_company') - self.profile_obj = self.registry("account.statement.profile") - self.partner_obj = self.registry("res.partner") - self.account_bank_statement_obj = self.registry( - "account.bank.statement") - self.account_bank_statement_line_obj = self.registry( - "account.bank.statement.line") - self.journal_id = self.ref("account.bank_journal") - self.partner_id = self.ref('base.main_partner') - self.account_id = self.ref("account.a_recv") - self.partner_id = self.ref("base.res_partner_12") - - def test_name_completion(self): - """Test complete partner_id from statement line label - Test the automatic completion of the partner_id based if the name of - the partner appears in the statement line label - """ - self.completion_rule_id = self.ref( - 'account_statement_base_completion.' - 'bank_statement_completion_rule_3') - # Create the profile - self.profile_id = self.profile_obj.create(self.cr, self.uid, { - "name": "TEST", - "commission_account_id": self.account_id, - "journal_id": self.journal_id, - "rule_ids": [(6, 0, [self.completion_rule_id])]}) - # Create a bank statement - self.statement_id = self.account_bank_statement_obj.create( - self.cr, self.uid, { - "balance_end_real": 0.0, - "balance_start": 0.0, - "date": time.strftime('%Y-%m-%d'), - "journal_id": self.journal_id, - "profile_id": self.profile_id - }) - - for case in NAMES_COMPLETION_CASES: - self.partner_obj.write( - self.cr, self.uid, self.partner_id, {'name': case.partner_name} - ) - statement_line_id = self.account_bank_statement_line_obj.create( - self.cr, self.uid, { - 'amount': 1000.0, - 'name': case.line_label, - 'ref': 'My ref', - 'statement_id': self.statement_id, - }) - statement_line = self.account_bank_statement_line_obj.browse( - self.cr, self.uid, statement_line_id) - self.assertFalse( - statement_line.partner_id, - "Partner_id must be blank before completion") - statement_obj = self.account_bank_statement_obj.browse( - self.cr, self.uid, self.statement_id) - statement_obj.button_auto_completion() - statement_line = self.account_bank_statement_line_obj.browse( - self.cr, self.uid, statement_line_id) - if case.should_match: - self.assertEquals( - self.partner_id, statement_line.partner_id['id'], - "Missing expected partner id after completion " - "(partner_name: %s, line_name: %s)" % - (case.partner_name, case.line_label)) - else: - self.assertNotEquals( - self.partner_id, statement_line.partner_id['id'], - "Partner id should be empty after completion " - "(partner_name: %s, line_name: %s)" - % (case.partner_name, case.line_label)) diff --git a/account_statement_base_import/__init__.py b/account_statement_base_import/__init__.py deleted file mode 100644 index 2fe60a3e..00000000 --- a/account_statement_base_import/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Joel Grand-Guillaume -# Copyright 2011-2012 Camptocamp SA -# -# 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 parser -from . import wizard -from . import statement diff --git a/account_statement_base_import/__openerp__.py b/account_statement_base_import/__openerp__.py deleted file mode 100644 index 3a920daf..00000000 --- a/account_statement_base_import/__openerp__.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Joel Grand-Guillaume -# Copyright 2011-2012 Camptocamp SA -# -# 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': "Bank statement base import", - 'version': '1.2', - 'author': "Camptocamp,Odoo Community Association (OCA)", - 'maintainer': 'Camptocamp', - 'category': 'Finance', - 'complexity': 'normal', - 'depends': [ - 'account_statement_ext', - 'account_statement_base_completion' - ], - 'description': """ - This module brings basic methods and fields on bank statement to deal with - the importation of different bank and offices. A generic abstract method is - defined and an example that gives you a basic way of importing bank statement - through a standard file is provided. - - This module improves the bank statement and allows you to import your bank - transactions with a standard .csv or .xls file (you'll find it in the 'data' - folder). It respects the profile (provided by the accouhnt_statement_ext - module) to pass the entries. That means, you'll have to choose a file format - for each profile. - In order to achieve this it uses the `xlrd` Python module which you will need - to install separately in your environment. - - This module can handle a commission taken by the payment office and has the - following format: - - * __ref__: the SO number, INV number or any matching ref found. It'll be used - as reference in the generated entries and will be useful for reconciliation - process - * __date__: date of the payment - * __amount__: amount paid in the currency of the journal used in the - importation profile - * __label__: the comunication given by the payment office, used as - communication in the generated entries. - - The goal is here to populate the statement lines of a bank statement with the - infos that the bank or office give you. Fell free to inherit from this module - to add your own format. Then, if you need to complete data from there, add - your own account_statement_*_completion module and implement the needed rules. - """, - 'website': 'http://www.camptocamp.com', - 'data': [ - "wizard/import_statement_view.xml", - "statement_view.xml", - ], - 'test': [], - 'installable': False, - 'images': [], - 'auto_install': False, - 'license': 'AGPL-3', - } diff --git a/account_statement_base_import/data/statement.csv b/account_statement_base_import/data/statement.csv deleted file mode 100644 index fa059787..00000000 --- a/account_statement_base_import/data/statement.csv +++ /dev/null @@ -1,4 +0,0 @@ -"ref";"date";"amount";"commission_amount";"label" -50969286;2011-03-07 13:45:14;118.4;-11.84;"label a" -51065326;2011-03-02 13:45:14;189;-15.12;"label b" -51179306;2011-03-02 17:45:14;189;-15.12;"label c" diff --git a/account_statement_base_import/data/statement.xls b/account_statement_base_import/data/statement.xls deleted file mode 100644 index 53ba58a5..00000000 Binary files a/account_statement_base_import/data/statement.xls and /dev/null differ diff --git a/account_statement_base_import/parser/__init__.py b/account_statement_base_import/parser/__init__.py deleted file mode 100644 index cb73080b..00000000 --- a/account_statement_base_import/parser/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Nicolas Bessi, Joel Grand-Guillaume -# Copyright 2011-2012 Camptocamp SA -# -# 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 .parser import new_bank_statement_parser -from .parser import BankStatementImportParser -from . import file_parser -from . import generic_file_parser diff --git a/account_statement_base_import/parser/generic_file_parser.py b/account_statement_base_import/parser/generic_file_parser.py deleted file mode 100644 index 47e98445..00000000 --- a/account_statement_base_import/parser/generic_file_parser.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright Camptocamp SA -# Author Joel Grand-Guillaume -# This program 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. -# -# 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 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 . -# -############################################################################## - -import datetime -from .file_parser import FileParser -from openerp.addons.account_statement_base_import.parser.file_parser import ( - float_or_zero -) -from openerp.tools import ustr - - -class GenericFileParser(FileParser): - """Standard parser that use a define format in csv or xls to import into a - bank statement. This is mostely an example of how to proceed to create a - new parser, but will also be useful as it allow to import a basic flat - file. - """ - - def __init__(self, parse_name, ftype='csv', **kwargs): - conversion_dict = { - 'ref': ustr, - 'label': ustr, - 'date': datetime.datetime, - 'amount': float_or_zero, - } - super(GenericFileParser, self).__init__( - parse_name, ftype=ftype, - extra_fields=conversion_dict, - **kwargs) - - @classmethod - def parser_for(cls, parser_name): - """Used by the new_bank_statement_parser class factory. Return true if - the providen name is generic_csvxls_so - """ - return parser_name == 'generic_csvxls_so' - - def get_st_line_vals(self, line, *args, **kwargs): - """ - This method must return a dict of vals that can be passed to create - method of statement line in order to record it. It is the - responsibility of every parser to give this dict of vals, so each one - can implement his own way of recording the lines. - :param: line: a dict of vals that represent a line of - result_row_list - :return: dict of values to give to the create method of statement - line, it MUST contain at least: - { - 'name':value, - 'date':value, - 'amount':value, - 'ref':value, - 'label':value, - } - """ - return { - 'name': line.get('label', line.get('ref', '/')), - 'date': line.get('date', datetime.datetime.now().date()), - 'amount': line.get('amount', 0.0), - 'ref': line.get('ref', '/'), - 'label': line.get('label', ''), - } diff --git a/account_statement_base_import/statement.py b/account_statement_base_import/statement.py deleted file mode 100644 index 83e250f3..00000000 --- a/account_statement_base_import/statement.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Joel Grand-Guillaume -# Copyright 2011-2012 Camptocamp SA -# -# 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 sys -import traceback -from openerp.tools.translate import _ -import datetime -from openerp.osv import fields, orm -from .parser import new_bank_statement_parser -from openerp.tools.config import config - - -class AccountStatementProfil(orm.Model): - _inherit = "account.statement.profile" - - def _get_import_type_selection(self, cr, uid, context=None): - """This is the method to be inherited for adding the parser""" - return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')] - - def __get_import_type_selection(self, cr, uid, context=None): - """ Call method which can be inherited """ - return self._get_import_type_selection(cr, uid, context=context) - - _columns = { - 'launch_import_completion': fields.boolean( - "Launch completion after import", - help="Tic that box to automatically launch the completion " - "on each imported file using this profile."), - 'last_import_date': fields.datetime("Last Import Date"), - # we remove deprecated as it floods logs in standard/warning level - # sob... - 'rec_log': fields.text('log', readonly=True), # Deprecated - 'import_type': fields.selection( - __get_import_type_selection, - 'Type of import', - required=True, - help="Choose here the method by which you want to import bank" - "statement for this profile."), - } - - _defaults = { - 'import_type': 'generic_csvxls_so' - } - - def _write_extra_statement_lines( - self, cr, uid, parser, result_row_list, profile, statement_id, - context): - """Insert extra lines after the main statement lines. - - After the main statement lines have been created, you can override this - method to create extra statement lines. - - :param: browse_record of the current parser - :param: result_row_list: [{'key':value}] - :param: profile: browserecord of account.statement.profile - :param: statement_id: int/long of the current importing - statement ID - :param: context: global context - """ - - def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, - context): - """Write the log in the logger - - :param int/long statement_id: ID of the concerned - account.bank.statement - :param int/long num_lines: Number of line that have been parsed - :return: True - """ - self.message_post( - cr, uid, ids, - body=_('Statement ID %s have been imported with %s ' - 'lines.') % (statement_id, num_lines), context=context) - return True - - # Deprecated remove on V8 - def prepare_statetement_lines_vals(self, *args, **kwargs): - return self.prepare_statement_lines_vals(*args, **kwargs) - - def prepare_statement_lines_vals(self, cr, uid, parser_vals, - statement_id, context): - """Hook to build the values of a line from the parser returned values. - At least it fullfill the statement_id. Overide it to add your own - completion if needed. - - :param dict of vals from parser for account.bank.statement.line - (called by parser.get_st_line_vals) - :param int/long statement_id: ID of the concerned - account.bank.statement - :return: dict of vals that will be passed to create method of - statement line. - """ - statement_line_obj = self.pool['account.bank.statement.line'] - values = parser_vals - values['statement_id'] = statement_id - date = values.get('date') - period_memoizer = context.get('period_memoizer') - if not period_memoizer: - period_memoizer = {} - context['period_memoizer'] = period_memoizer - if period_memoizer.get(date): - values['period_id'] = period_memoizer[date] - else: - # This is awfully slow... - periods = self.pool.get('account.period').find( - cr, uid, dt=values.get('date'), context=context) - values['period_id'] = periods[0] - period_memoizer[date] = periods[0] - values = statement_line_obj._add_missing_default_values( - cr, uid, values, context) - return values - - def prepare_statement_vals(self, cr, uid, profile_id, result_row_list, - parser, context=None): - """Hook to build the values of the statement from the parser and - the profile. - """ - vals = {'profile_id': profile_id} - vals.update(parser.get_st_vals()) - if vals.get('balance_start') is None: - # Get starting balance from journal balance if parser doesn't - # fill this data, simulating the manual flow - statement_obj = self.pool['account.bank.statement'] - profile = self.browse(cr, uid, profile_id, context=context) - temp = statement_obj.onchange_journal_id( - cr, uid, None, profile.journal_id.id, context=context) - vals['balance_start'] = temp['value'].get('balance_start', False) - return vals - - def multi_statement_import(self, cr, uid, ids, profile_id, file_stream, - ftype="csv", context=None): - """Create multiple bank statements from values given by the parser for - the given profile. - - :param int/long profile_id: ID of the profile used to import the file - :param filebuffer file_stream: binary of the providen file - :param char: ftype represent the file exstension (csv by default) - :return: list: list of ids of the created account.bank.statemênt - """ - prof_obj = self.pool['account.statement.profile'] - if not profile_id: - raise orm.except_orm( - _("No Profile!"), - _("You must provide a valid profile to import a bank " - "statement!")) - prof = prof_obj.browse(cr, uid, profile_id, context=context) - parser = new_bank_statement_parser(prof, ftype=ftype) - res = [] - for result_row_list in parser.parse(file_stream): - statement_id = self._statement_import( - cr, uid, ids, prof, parser, file_stream, ftype=ftype, - context=context) - res.append(statement_id) - return res - - def _statement_import(self, cr, uid, ids, prof, parser, file_stream, - ftype="csv", context=None): - """Create a bank statement with the given profile and parser. It will - fullfill the bank statement with the values of the file providen, but - will not complete data (like finding the partner, or the right - account). This will be done in a second step with the completion rules. - - :param prof : The profile used to import the file - :param parser: the parser - :param filebuffer file_stream: binary of the providen file - :param char: ftype represent the file exstension (csv by default) - :return: ID of the created account.bank.statemênt - """ - statement_obj = self.pool['account.bank.statement'] - statement_line_obj = self.pool['account.bank.statement.line'] - attachment_obj = self.pool['ir.attachment'] - result_row_list = parser.result_row_list - # Check all key are present in account.bank.statement.line!! - if not result_row_list: - raise orm.except_orm(_("Nothing to import"), - _("The file is empty")) - parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys() - for col in parsed_cols: - if col not in statement_line_obj._columns: - raise orm.except_orm( - _("Missing column!"), - _("Column %s you try to import is not present in the bank " - "statement line!") % col) - statement_vals = self.prepare_statement_vals( - cr, uid, prof.id, result_row_list, parser, context) - statement_id = statement_obj.create( - cr, uid, statement_vals, context=context) - try: - # Record every line in the bank statement - statement_store = [] - for line in result_row_list: - parser_vals = parser.get_st_line_vals(line) - values = self.prepare_statement_lines_vals( - cr, uid, parser_vals, statement_id, - context) - statement_store.append(values) - # Hack to bypass ORM poor perfomance. Sob... - statement_line_obj._insert_lines( - cr, uid, statement_store, context=context) - self._write_extra_statement_lines( - cr, uid, parser, result_row_list, prof, statement_id, context) - # Trigger store field computation if someone has better idea - start_bal = statement_obj.read( - cr, uid, statement_id, ['balance_start'], context=context) - start_bal = start_bal['balance_start'] - statement_obj.write( - cr, uid, [statement_id], {'balance_start': start_bal}) - attachment_data = { - 'name': 'statement file', - 'datas': file_stream, - 'datas_fname': "%s.%s" % (datetime.datetime.now().date(), - ftype), - 'res_model': 'account.bank.statement', - 'res_id': statement_id, - } - attachment_obj.create(cr, uid, attachment_data, context=context) - # If user ask to launch completion at end of import, do it! - if prof.launch_import_completion: - statement_obj.button_auto_completion( - cr, uid, [statement_id], context) - # Write the needed log infos on profile - self.write_logs_after_import(cr, uid, prof.id, - statement_id, - len(result_row_list), - context) - except Exception: - error_type, error_value, trbk = sys.exc_info() - st = "Error: %s\nDescription: %s\nTraceback:" % ( - error_type.__name__, error_value) - st += ''.join(traceback.format_tb(trbk, 30)) - # TODO we should catch correctly the exception with a python - # Exception and only re-catch some special exception. - # For now we avoid re-catching error in debug mode - if config['debug_mode']: - raise - raise orm.except_orm(_("Statement import error"), - _("The statement cannot be created: %s") % st) - return statement_id diff --git a/account_statement_base_import/statement_view.xml b/account_statement_base_import/statement_view.xml deleted file mode 100644 index b73e0dc7..00000000 --- a/account_statement_base_import/statement_view.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - account.statement.profile.view - account.statement.profile - - - - - - - -