From 02326358e03ca6fb20ccee108d66d32f65711f66 Mon Sep 17 00:00:00 2001 From: Matthieu Dietrich Date: Tue, 24 May 2016 11:51:51 +0200 Subject: [PATCH 01/77] Remane import modules --- account_move_base_import/README.rst | 101 +++++ account_move_base_import/__init__.py | 9 + account_move_base_import/__openerp__.py | 32 ++ .../data/completion_rule_data.xml | 28 ++ account_move_base_import/data/statement.csv | 4 + account_move_base_import/data/statement.xls | Bin 0 -> 7168 bytes .../i18n/account_statement_base_import.pot | 289 ++++++++++++ account_move_base_import/i18n/es.po | 329 ++++++++++++++ account_move_base_import/i18n/fr.po | 303 +++++++++++++ account_move_base_import/models/__init__.py | 9 + .../models/account_journal.py | 282 ++++++++++++ .../models/account_move.py | 418 ++++++++++++++++++ account_move_base_import/models/partner.py | 22 + account_move_base_import/parser/__init__.py | 10 + .../parser/file_parser.py | 186 ++++++++ .../parser/generic_file_parser.py | 82 ++++ account_move_base_import/parser/parser.py | 206 +++++++++ .../security/ir.model.access.csv | 3 + .../test/completion_test.yml | 122 +++++ account_move_base_import/test/invoice.yml | 42 ++ account_move_base_import/test/partner.yml | 5 + account_move_base_import/test/refund.yml | 42 ++ .../test/supplier_invoice.yml | 42 ++ account_move_base_import/tests/__init__.py | 8 + .../tests/test_base_completion.py | 95 ++++ .../tests/test_base_import.py | 81 ++++ .../views/account_move_view.xml | 67 +++ .../views/journal_view.xml | 42 ++ .../views/partner_view.xml | 15 + account_move_base_import/wizard/__init__.py | 7 + .../wizard/import_statement.py | 91 ++++ .../wizard/import_statement_view.xml | 35 ++ 32 files changed, 3007 insertions(+) create mode 100644 account_move_base_import/README.rst create mode 100644 account_move_base_import/__init__.py create mode 100644 account_move_base_import/__openerp__.py create mode 100644 account_move_base_import/data/completion_rule_data.xml create mode 100644 account_move_base_import/data/statement.csv create mode 100644 account_move_base_import/data/statement.xls create mode 100644 account_move_base_import/i18n/account_statement_base_import.pot create mode 100644 account_move_base_import/i18n/es.po create mode 100644 account_move_base_import/i18n/fr.po create mode 100644 account_move_base_import/models/__init__.py create mode 100644 account_move_base_import/models/account_journal.py create mode 100644 account_move_base_import/models/account_move.py create mode 100644 account_move_base_import/models/partner.py create mode 100644 account_move_base_import/parser/__init__.py create mode 100644 account_move_base_import/parser/file_parser.py create mode 100644 account_move_base_import/parser/generic_file_parser.py create mode 100644 account_move_base_import/parser/parser.py create mode 100644 account_move_base_import/security/ir.model.access.csv create mode 100644 account_move_base_import/test/completion_test.yml create mode 100644 account_move_base_import/test/invoice.yml create mode 100644 account_move_base_import/test/partner.yml create mode 100644 account_move_base_import/test/refund.yml create mode 100644 account_move_base_import/test/supplier_invoice.yml create mode 100644 account_move_base_import/tests/__init__.py create mode 100644 account_move_base_import/tests/test_base_completion.py create mode 100644 account_move_base_import/tests/test_base_import.py create mode 100644 account_move_base_import/views/account_move_view.xml create mode 100644 account_move_base_import/views/journal_view.xml create mode 100644 account_move_base_import/views/partner_view.xml create mode 100644 account_move_base_import/wizard/__init__.py create mode 100644 account_move_base_import/wizard/import_statement.py create mode 100644 account_move_base_import/wizard/import_statement_view.xml diff --git a/account_move_base_import/README.rst b/account_move_base_import/README.rst new file mode 100644 index 0000000000..a4434d6d2c --- /dev/null +++ b/account_move_base_import/README.rst @@ -0,0 +1,101 @@ +.. 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 statement 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 +====================== + +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 0000000000..8bbeba75e3 --- /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 0000000000..8b7c20cc5e --- /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': "Bank statement 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 0000000000..ae1ba8582c --- /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 0000000000..21dead3172 --- /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 0000000000000000000000000000000000000000..5f5a57b061a3ad1b1879524ffb0f8aeb3013df4f GIT binary patch literal 7168 zcmeHLYiv|S6h3!%TW)Eg+fqskQZ5Awr7iTK@)B5RizRWPsh}|-!R@jw3)?Pfw~8^w z^7?3x}8i@XRLwI*`@H{?fXP5EfG0rmfP7#IPykE?3V~@r z5l{?F2W9~G05gFSU>0Bivw>1z4loy(2b2NxfdxQ0un?#KDuF6s5wIAj25Nv>pbp@^ z?z2kPm!iH5SPrZJ9so#RWt9o7-MzIe9jBO?As!k4w_N9xUHkyr7THDH zsP~2Z#($O8sx|mD+GZb&bfuPwSi=E3J&Js?ys3K5sC=ceKn(PV!82!6avBZt9#na$ z$|ob&OJZ(CU@U zptLIaC9WJf!B_af#tn@vYaS|b70N5TZ4C{Jz23!-A#E|5nwERLC9YZWD(N7~^z7_q~)aYFAaO0EFIqPua|G)GT(5b=g zSt+jP3R4f!q4e@3^vWdkg-PgH!_qTgMjBo4(!4_5rQ;r(>q*dkgXjV?Q|q6bmuDX@ zFtQQpmA--ThaC{pD>0VtUZ*PadNWm--|JH4l->!dEa=UMJ+7|g0X!(5GXBK1SQqFC zhC+cF(q=FQP0(sd`-?GYf>!q&9F&INIZX#dHXYRDC)vSgD%#4Sk{RW5!Z!?SQ_mfr zJX$|N>tz|I)k$`g&)P?jq01f(OVIl?4|0!`T;s!shfv34IvzUx54?ppr9}nJ^rDck z%@+wEj`nqgdpaYyw*GKON3gp)81CF^(y-BxuQd?DcvTv{fm-W8%^ySXt;=Q%=Hu(v zrvj*0iH|LlgW51XxqFZ&&%wsFK?(GGd1{kxOn39-wXeOc&8xR6dM_yPG>-=-^Rzwa zIpNnlsd%^X zijOj`+1DO0%9{5n0sB>6hQ-vWbanN7TYUlQ`>HPNt-26hU2dx%hmXs`7J2rX?3GHb zQj7D)OHDRo?reER=@4N+5rY>|ZE`&^CDC?ZvJwi6cd$?QZM>d{`zHrwK0AY|H; znd=T(2J1~UCXCG$(%5i8t#%qMZYYh`XQ$m#pI&BAC1SQTgjk%&Y&p>4OlHf8(2H|8 zu`-UCCL795K2rPbkDjx5BDvQhalX&MY~L@jm_u=C;3$SvlZ$hiTrYfMa?x~(<}o@-Dw!#lsBuk?9z^%Ml{Z!iQCSL@7C%55Fn{s*72Lw8}xUDyTfe}qj9%C5Hgl6 zsoKP6I}}iUY;CZu&HUsrdD70|^h}{?bD9kOa96yiLld3pr2{Wrzp-(<`_%KgRLnnp zkxsP;9sGu8|1N->y8*h!{Qw_=LjaHBC_sPu8o;pT9f0n#4`58jFBm$?D**o=;1>kL z6i!YVIVd^*)m!E_KV>rU`~P<849vW~NMw63&|@?N1Cg$vf4em=<#C@LAf7z&3d*7N zKf`A~^dI{wz8u@g)T+t!&viJ?e}4Yxq8aYPN1*@88*em#mNA631+*f73n1J8d8xBx zbaand8q5Fx)cS|qq2U%vTkEr8s7LYdW0&m2S1!*pUkBpfQxs{ZKa3pw1+XdqH1s^=XhBl0cG?qmw5>zA4$vN9Pc^ zAA<_4ff@3!^kDxT`1gVT{jSHqz-{VGNQvN|kT598(*jFA{Y9$#UxKk#fDAv=011Az lhJgA9I=%HQ{TKUR3Hg!aTS~hpUH#kte@$E1FbuaN|8JkruzUaj literal 0 HcmV?d00001 diff --git a/account_move_base_import/i18n/account_statement_base_import.pot b/account_move_base_import/i18n/account_statement_base_import.pot new file mode 100644 index 0000000000..f6bfbeea47 --- /dev/null +++ b/account_move_base_import/i18n/account_statement_base_import.pot @@ -0,0 +1,289 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * account_statement_base_import +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-01-21 11:58+0000\n" +"PO-Revision-Date: 2014-01-21 11:58+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action +msgid "Import statement" +msgstr "" + +#. module: account_statement_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 +msgid "credit.statement.import" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,input_statement:0 +msgid "Statement file" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Nothing to import" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,journal_id:0 +msgid "Financial journal to use transaction" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/parser/file_parser.py:108 +#, python-format +msgid "Column %s not present in file" +msgstr "" + +#. module: account_statement_base_import +#: view:account.statement.profile:0 +#: model:ir.ui.menu,name:account_statement_base_import.statement_importer_menu +msgid "Import Bank Statement" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "The statement cannot be created: %s" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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:150 +#, python-format +msgid "No buffer file given." +msgstr "" + +#. 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 +#, python-format +msgid "Invalid data" +msgstr "" + +#. module: account_statement_base_import +#: field:account.statement.profile,launch_import_completion:0 +msgid "Launch completion after import" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,partner_id:0 +msgid "Credit insitute partner" +msgstr "" + +#. module: account_statement_base_import +#: view:account.statement.profile:0 +msgid "Import related infos" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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:90 +#, python-format +msgid "Please use a file with an extention" +msgstr "" + +#. 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 +#, python-format +msgid "Value %s of column %s is not valid.\n" +" Please check the line with ref %s:\n" +" \n" +" 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:30 +#, python-format +msgid "Please install python lib xlrd" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/parser/file_parser.py:160 +#, python-format +msgid " 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" +msgstr "" + +#. module: account_statement_base_import +#: field:account.statement.profile,last_import_date:0 +msgid "Last Import Date" +msgstr "" + +#. module: account_statement_base_import +#: model:ir.model,name:account_statement_base_import.model_account_statement_profile +msgid "Statement Profile" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/statement.py:222 +#, python-format +msgid "Statement import error" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/parser/file_parser.py:193 +#, python-format +msgid "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" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/parser/file_parser.py:192 +#, python-format +msgid "Date format is not valid" +msgstr "" + +#. module: account_statement_base_import +#: field:account.statement.profile,import_type:0 +msgid "Type of import" +msgstr "" + +#. module: account_statement_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 +#: help:credit.statement.import,balance_check:0 +msgid "Tic that box if you want OpenERP to control the start/end balance before confirming a bank statement. If don't ticked, no balance control will be done." +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Date format is not valid." +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,profile_id:0 +msgid "Import configuration parameter" +msgstr "" + +#. module: account_statement_base_import +#: field:account.statement.profile,rec_log:0 +msgid "log" +msgstr "" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +msgid "Import Parameters Summary" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,balance_check:0 +msgid "Balance check" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,force_partner_on_bank:0 +msgid "Force partner on bank move" +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,file_name:0 +msgid "File Name" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, 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 +#, python-format +msgid "Statement ID %s have been imported with %s lines." +msgstr "" + +#. module: account_statement_base_import +#: field:credit.statement.import,receivable_account_id:0 +msgid "Force Receivable/Payable Account" +msgstr "" + +#. 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 +#, python-format +msgid "Missing" +msgstr "" + +#. module: account_statement_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 "" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +msgid "Cancel" +msgstr "" + +#. module: account_statement_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 counterpart of the treasury/banking move." +msgstr "" + diff --git a/account_move_base_import/i18n/es.po b/account_move_base_import/i18n/es.po new file mode 100644 index 0000000000..b990338fc0 --- /dev/null +++ b/account_move_base_import/i18n/es.po @@ -0,0 +1,329 @@ +# Spanish translation for banking-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the banking-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: banking-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2014-01-21 11:58+0000\n" +"PO-Revision-Date: 2014-06-05 22:11+0000\n" +"Last-Translator: Pedro Manuel Baeza \n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-06-06 06:36+0000\n" +"X-Generator: Launchpad (build 17031)\n" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action +msgid "Import statement" +msgstr "Importar extracto" + +#. module: account_statement_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 +msgid "credit.statement.import" +msgstr "credit.statement.import" + +#. module: account_statement_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 +#, python-format +msgid "" +"Column %s you try to import is not present in the bank statement line!" +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 +#, python-format +msgid "Nothing to import" +msgstr "Nada que importar" + +#. module: account_statement_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 +#, python-format +msgid "Column %s not present in file" +msgstr "La columna %s no está presente en el archivo" + +#. module: account_statement_base_import +#: view:account.statement.profile:0 +#: model:ir.ui.menu,name:account_statement_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 +#, python-format +msgid "User Error" +msgstr "Error de usuario" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Missing column!" +msgstr "Columna ausente" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Invalid data" +msgstr "Datos inválidos" + +#. module: account_statement_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 +#: field:credit.statement.import,partner_id:0 +msgid "Credit insitute partner" +msgstr "Empresa para el agente financiero" + +#. module: account_statement_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 +#, 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 +#, 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 +#, python-format +msgid "" +"Value %s of column %s is not valid.\n" +" Please check the line with ref %s:\n" +" \n" +" Detail: %s" +msgstr "" +"El valor %s de la columna %s no es válido.\n" +"\n" +"Compruebe por favor la línea con referencia %s\n" +" \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 +#, 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 +#, python-format +msgid "" +" 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" +msgstr "" +" La columna %s debería tener el formato YYYY-MM-DD y es: %s \n" +" \n" +"Compruebe por favor la línea con referencia %s \n" +" \n" +"Detalles: %s" + +#. module: account_statement_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 +msgid "Statement Profile" +msgstr "Perfil de extracto" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "" +"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" +msgstr "" +"Modifique el formato de fecha para la columna %s. Valor: %s\n" +"\n" +"Compruebe por favor la línea con referencia: %s\n" +" \n" +"Detalles: %s" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#: field:account.statement.profile,import_type:0 +msgid "Type of import" +msgstr "Tipo de importación" + +#. module: account_statement_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 "" +"Marque esta casilla para lanzar automáticamente el completado en cada " +"archivo importado usando este perfil." + +#. module: account_statement_base_import +#: help:credit.statement.import,balance_check:0 +msgid "" +"Tic that box if you want OpenERP to control the start/end balance before " +"confirming a bank statement. If don't ticked, no balance control will be " +"done." +msgstr "" +"Marque esta casilla si quiere que el sistema controle el saldo inicial/final " +"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 +#, python-format +msgid "No Profile!" +msgstr "Sin perfil" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#: 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 +#: field:account.statement.profile,rec_log:0 +msgid "log" +msgstr "registro" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +msgid "Import Parameters Summary" +msgstr "Resumen de parámetros de importación" + +#. module: account_statement_base_import +#: field:credit.statement.import,balance_check:0 +msgid "Balance check" +msgstr "Comprobar saldo" + +#. module: account_statement_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 +#: 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 +#, 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 +#, 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 +#, 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 +#: 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 +#, python-format +msgid "Missing" +msgstr "Ausente" + +#. module: account_statement_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 "" +"Escoja aquí el método con el que quiere importar el extracto bancario para " +"este perfil." + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +msgid "Cancel" +msgstr "Cancelar" + +#. module: account_statement_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 " +"counterpart of the treasury/banking move." +msgstr "" +"Marque esta casilla si quiere usar la empresa de su institución financiera " +"en la contrapartida del movimiento de caja/banco." diff --git a/account_move_base_import/i18n/fr.po b/account_move_base_import/i18n/fr.po new file mode 100644 index 0000000000..a141853510 --- /dev/null +++ b/account_move_base_import/i18n/fr.po @@ -0,0 +1,303 @@ +# French translation for banking-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the banking-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: banking-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2014-01-21 11:58+0000\n" +"PO-Revision-Date: 2014-03-21 15:17+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: French \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-05-22 06:49+0000\n" +"X-Generator: Launchpad (build 17017)\n" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +#: model:ir.actions.act_window,name:account_statement_base_import.statement_importer_action +msgid "Import statement" +msgstr "Import de relevé" + +#. module: account_statement_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 +msgid "credit.statement.import" +msgstr "credit.statement.import" + +#. module: account_statement_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 +#, 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 +#, python-format +msgid "Nothing to import" +msgstr "" + +#. module: account_statement_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 +#, python-format +msgid "Column %s not present in file" +msgstr "Colonne %s non présente dans le fichier" + +#. module: account_statement_base_import +#: view:account.statement.profile:0 +#: model:ir.ui.menu,name:account_statement_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 +#, python-format +msgid "User Error" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Missing column!" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Invalid data" +msgstr "" + +#. module: account_statement_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 +#: field:credit.statement.import,partner_id:0 +msgid "Credit insitute partner" +msgstr "Organisme bancaire" + +#. module: account_statement_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 +#, python-format +msgid "The file is empty" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "" +"Value %s of column %s is not valid.\n" +" Please check the line with ref %s:\n" +" \n" +" 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 +#, 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 +#, python-format +msgid "" +" 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" +msgstr "" + +#. module: account_statement_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 +msgid "Statement Profile" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "" +"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" +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_base_import/parser/file_parser.py:192 +#, python-format +msgid "Date format is not valid" +msgstr "" + +#. module: account_statement_base_import +#: field:account.statement.profile,import_type:0 +msgid "Type of import" +msgstr "Type d'import" + +#. module: account_statement_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 +#: help:credit.statement.import,balance_check:0 +msgid "" +"Tic that box if you want OpenERP to control the start/end balance before " +"confirming a bank statement. If don't ticked, no balance control will be " +"done." +msgstr "" + +#. module: account_statement_base_import +#: code:addons/account_statement_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 +#, python-format +msgid "Date format is not valid." +msgstr "" + +#. module: account_statement_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 +#: field:account.statement.profile,rec_log:0 +msgid "log" +msgstr "journal" + +#. module: account_statement_base_import +#: view:credit.statement.import:0 +msgid "Import Parameters Summary" +msgstr "Résumé des paramètres d'import" + +#. module: account_statement_base_import +#: field:credit.statement.import,balance_check:0 +msgid "Balance check" +msgstr "Vérification des soldes" + +#. module: account_statement_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 +#: 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 +#, 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 +#, 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 +#, python-format +msgid "Statement ID %s have been imported with %s lines." +msgstr "" + +#. module: account_statement_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 +#, python-format +msgid "Missing" +msgstr "" + +#. module: account_statement_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 +#: view:credit.statement.import:0 +msgid "Cancel" +msgstr "Annulation" + +#. module: account_statement_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 " +"counterpart of the treasury/banking move." +msgstr "" diff --git a/account_move_base_import/models/__init__.py b/account_move_base_import/models/__init__.py new file mode 100644 index 0000000000..e945d363ac --- /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 0000000000..b703a7985e --- /dev/null +++ b/account_move_base_import/models/account_journal.py @@ -0,0 +1,282 @@ +# -*- 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'] + + def _get_import_type_selection(self): + """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): + """ Call method which can be inherited """ + return self._get_import_type_selection() + + 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( + __get_import_type_selection, + string='Type of import', + default='generic_csvxls_so', + required=True, + help="Choose here the method by which you want to import bank " + "statement for this profile.") + + 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='Force Receivable/Payable Account', + help="Choose a receivable account to force the default " + "debit/credit account (eg. an intermediat bank account " + "instead of default debitors).") + + used_for_completion = fields.Boolean( + string="Journal used for completion") + + rule_ids = fields.Many2many( + comodel_name='account.move.completion.rule', + string='Auto-completion rules', + rel='as_rul_st_prof_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 profile.") + + 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 _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 + total_amount = 0 + for row in parser.result_row_list: + global_commission_amount += float( + row.get('commission_amount', '0.0')) + total_amount += float( + row.get('amount', '0.0')) + total_amount += global_commission_amount + partner_id = self.partner_id.id + # Commission line + if global_commission_amount < 0.0: + 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) + # Counterpart line + if total_amount > 0.0: + receivable_account_id = self.receivable_account_id.id or False + counterpart_values = { + 'name': _('Counterpart line'), + 'date_maturity': parser.get_move_vals().get('date') or + fields.Date.today(), + 'debit': total_amount, + 'partner_id': partner_id, + 'move_id': move.id, + 'account_id': receivable_account_id, + 'already_completed': True, + } + move_line_obj.create(counterpart_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 values['credit'] > 0.0: + values['account_id'] = self.default_credit_account_id.id + else: + values['account_id'] = self.default_debit_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._write_extra_move_lines(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 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 0000000000..fc9bff3287 --- /dev/null +++ b/account_move_base_import/models/account_move.py @@ -0,0 +1,418 @@ +# -*- 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" + + def _get_functions(self): + """List of available methods for rules. + + Override this to add you own.""" + return [ + ('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)') + ] + + def __get_functions(self): + """ Call method which can be inherited """ + return self._get_functions() + + sequence = fields.Integer( + string='Sequence', + help="Lower means parsed first.") + name = fields.Char( + string='Name') + journal_ids = fields.Many2many( + comodel_name='account.journal', + rel='as_rul_st_prof_rel', + string='Related journals') + function_to_call = fields.Selection( + __get_functions, + 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} + 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 0000000000..818868bc2f --- /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 0000000000..1233bf9d5b --- /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_move_base_import/parser/file_parser.py b/account_move_base_import/parser/file_parser.py new file mode 100644 index 0000000000..6b3f48fb1e --- /dev/null +++ b/account_move_base_import/parser/file_parser.py @@ -0,0 +1,186 @@ +# -*- 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.tools.translate import _ +from openerp.exceptions import UserError +import tempfile +import datetime +from .parser import AccountMoveImportParser, UnicodeDictReader +try: + import xlrd +except: + raise Exception(_('Please install python lib xlrd')) + + +def float_or_zero(val): + """ Conversion function used to manage + empty string into float usecase""" + return float(val) if val else 0.0 + + +class FileParser(AccountMoveImportParser): + """Generic abstract class for defining parser for .csv, .xls or .xlsx file + format. + """ + + 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 + xlsx) + :param dict: extra_fields: extra fields to put into the conversion + dict. In the format {fieldname: fieldtype} + :param list: header : specify header fields if the csv file has no + header + """ + super(FileParser, self).__init__(journal, **kwargs) + if ftype in ('csv', 'xls', 'xlsx'): + self.ftype = ftype[0:3] + else: + 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() + self.fieldnames = header + self._datemode = 0 # used only for xls documents, + # 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.""" + return True + + def _pre(self, *args, **kwargs): + """No pre-treatment needed for this parser.""" + return True + + def _parse(self, *args, **kwargs): + """Launch the parsing through .csv, .xls or .xlsx depending on the + given ftype + """ + res = None + if self.ftype == 'csv': + res = self._parse_csv() + else: + res = self._parse_xls() + self.result_row_list = res + return True + + def _validate(self, *args, **kwargs): + """We check that all the key of the given file (means header) are + present in the validation key provided. Otherwise, we raise an + Exception. We skip the validation step if the file header is provided + separately (in the field: fieldnames). + """ + if self.fieldnames is None: + parsed_cols = self.result_row_list[0].keys() + for col in self.keys_to_validate: + if col not in parsed_cols: + raise UserError(_('Column %s not present in file') % col) + return True + + def _post(self, *args, **kwargs): + """Cast row type depending on the file format .csv or .xls after + parsing the file.""" + self.result_row_list = self._cast_rows(*args, **kwargs) + return True + + def _parse_csv(self): + """:return: list of dict from csv file (line/rows)""" + csv_file = tempfile.NamedTemporaryFile() + csv_file.write(self.filebuffer) + csv_file.flush() + with open(csv_file.name, 'rU') as fobj: + reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames, + dialect=self.dialect) + return list(reader) + + def _parse_xls(self): + """:return: dict of dict from xls/xlsx file (line/rows)""" + wb_file = tempfile.NamedTemporaryFile() + wb_file.write(self.filebuffer) + # We ensure that cursor is at beginig of file + wb_file.seek(0) + with xlrd.open_workbook(wb_file.name) as wb: + self._datemode = wb.datemode + sheet = wb.sheet_by_index(0) + header = sheet.row_values(0) + res = [] + for rownum in range(1, sheet.nrows): + res.append(dict(zip(header, sheet.row_values(rownum)))) + return res + + def _from_csv(self, result_set, conversion_rules): + """Handle the converstion from the dict and handle date format from + an .csv file. + """ + for line in result_set: + for rule in conversion_rules: + if conversion_rules[rule] == datetime.datetime: + try: + date_string = line[rule].split(' ')[0] + line[rule] = datetime.datetime.strptime(date_string, + '%Y-%m-%d') + except ValueError as err: + 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')), + line.get('ref', line), repr(err))) + else: + try: + line[rule] = conversion_rules[rule](line[rule]) + except Exception as err: + 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, + line.get('ref', line), repr(err))) + return result_set + + def _from_xls(self, result_set, conversion_rules): + """Handle the converstion from the dict and handle date format from + an .csv, .xls or .xlsx file. + """ + for line in result_set: + for rule in conversion_rules: + if conversion_rules[rule] == datetime.datetime: + try: + t_tuple = xlrd.xldate_as_tuple(line[rule], + self._datemode) + line[rule] = datetime.datetime(*t_tuple) + except Exception as err: + 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')), + line.get('ref', line), repr(err))) + else: + try: + line[rule] = conversion_rules[rule](line[rule]) + except Exception as err: + 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, + line.get('ref', line), repr(err))) + return result_set + + def _cast_rows(self, *args, **kwargs): + """Convert the self.result_row_list using the self.conversion_dict + providen. We call here _from_xls or _from_csv depending on the + self.ftype variable. + """ + func = getattr(self, '_from_%s' % self.ftype) + res = func(self.result_row_list, self.conversion_dict) + return res 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 0000000000..f2f7b5d061 --- /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_move_base_import/parser/parser.py b/account_move_base_import/parser/parser.py new file mode 100644 index 0000000000..cbed62f85f --- /dev/null +++ b/account_move_base_import/parser/parser.py @@ -0,0 +1,206 @@ +# -*- 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 csv +from openerp import _, fields + + +def UnicodeDictReader(utf8_data, **kwargs): + sniffer = csv.Sniffer() + pos = utf8_data.tell() + sample_data = utf8_data.read(2048) + utf8_data.seek(pos) + if not kwargs.get('dialect'): + dialect = sniffer.sniff(sample_data, delimiters=',;\t') + del kwargs['dialect'] + else: + dialect = kwargs.pop('dialect') + csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs) + for row in csv_reader: + yield dict([(unicode(key or '', 'utf-8'), + unicode(value or '', 'utf-8')) + for key, value in row.iteritems()]) + + +class AccountMoveImportParser(object): + + """ + Generic abstract class for defining parser for different files and + format to import in a bank statement. Inherit from it to create your + own. If your file is a .csv or .xls format, you should consider inheirt + from the FileParser instead. + """ + + def __init__(self, journal, *args, **kwargs): + # The name of the parser as it will be called + 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.journal = journal + self.move_date = None + self.move_name = None + self.move_ref = None + + @classmethod + def parser_for(cls, parser_name): + """Override this method for every new parser, so that + new_bank_statement_parser can return the good class from his name. + """ + return False + + def _decode_64b_stream(self): + """Decode self.filebuffer in base 64 and override it""" + self.filebuffer = base64.b64decode(self.filebuffer) + return True + + def _format(self, decode_base_64=True, **kwargs): + """Decode into base 64 if asked and Format the given filebuffer by + calling _custom_format method. + """ + if decode_base_64: + self._decode_64b_stream() + self._custom_format(kwargs) + return True + + def _custom_format(self, *args, **kwargs): + """Implement a method in your parser to convert format, encoding and so + on before starting to work on datas. Work on self.filebuffer + """ + return NotImplementedError + + def _pre(self, *args, **kwargs): + """Implement a method in your parser to make a pre-treatment on datas + before parsing them, like concatenate stuff, and so... Work on + self.filebuffer + """ + return NotImplementedError + + def _parse(self, *args, **kwargs): + """Implement a method in your parser to save the result of parsing + self.filebuffer in self.result_row_list instance property. + """ + return NotImplementedError + + def _validate(self, *args, **kwargs): + """Implement a method in your parser to validate the + self.result_row_list instance property and raise an error if not valid. + """ + return NotImplementedError + + def _post(self, *args, **kwargs): + """Implement a method in your parser to make some last changes on the + result of parsing the datas, like converting dates, computing + commission, ... + """ + return NotImplementedError + + 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.move_name or '/', + 'date': self.move_date or fields.Datetime.now(), + 'ref': self.move_ref or '/' + } + + 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 + 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, + } + """ + return NotImplementedError + + def parse(self, filebuffer, *args, **kwargs): + """This will be the method that will be called by wizard, button and so + to parse a filebuffer by calling successively all the private method + that need to be define for each parser. + Return: + [] of rows as {'key':value} + + Note: The row_list must contain only value that are present in the + account.bank.statement.line object !!! + """ + if filebuffer: + self.filebuffer = filebuffer + else: + raise Exception(_('No buffer file given.')) + self._format(*args, **kwargs) + self._pre(*args, **kwargs) + self._parse(*args, **kwargs) + self._validate(*args, **kwargs) + self._post(*args, **kwargs) + yield self.result_row_list + + +def itersubclasses(cls, _seen=None): + """ + itersubclasses(cls) + + Generator over all subclasses of a given class, in depth first order. + + >>> list(itersubclasses(int)) == [bool] + True + >>> class A(object): pass + >>> class B(A): pass + >>> class C(A): pass + >>> class D(B,C): pass + >>> class E(D): pass + >>> + >>> for cls in itersubclasses(A): + ... print(cls.__name__) + B + D + E + C + >>> # get ALL (new-style) classes currently defined + >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS + ['type', ...'tuple', ...] + """ + if not isinstance(cls, type): + raise TypeError('itersubclasses must be called with ' + 'new-style classes, not %.100r' % cls) + if _seen is None: + _seen = set() + try: + subs = cls.__subclasses__() + except TypeError: # fails only when cls is type + subs = cls.__subclasses__(cls) + for sub in subs: + if sub not in _seen: + _seen.add(sub) + yield sub + for sub in itersubclasses(sub, _seen): + yield sub + + +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(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 0000000000..f1bdb6c11d --- /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 0000000000..8dd265b6f0 --- /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 0000000000..5379f670bd --- /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_move_base_import/test/partner.yml b/account_move_base_import/test/partner.yml new file mode 100644 index 0000000000..5e2071003e --- /dev/null +++ b/account_move_base_import/test/partner.yml @@ -0,0 +1,5 @@ +- + I fill in the field Bank Statement Label in a Partner +- + !record {model: res.partner, id: base.res_partner_4}: + bank_statement_label: XXX66Z diff --git a/account_move_base_import/test/refund.yml b/account_move_base_import/test/refund.yml new file mode 100644 index 0000000000..2779ba73fc --- /dev/null +++ b/account_move_base_import/test/refund.yml @@ -0,0 +1,42 @@ +- + I create a "child" partner, to use in the invoice + (and have a different commercial_partner_id than itself) +- + !record {model: res.partner, id: res_partner_12_child}: + name: Child Partner + supplier: False + customer: True + is_company: False + parent_id: base.res_partner_12 +- + I create a customer refund to be found by the completion. +- + !record {model: account.invoice, id: refund_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.expenses_journal + partner_id: res_partner_12_child + type: 'out_refund' + reference_type: none +- + I confirm the refund +- + !workflow {model: account.invoice, action: invoice_open, ref: refund_for_completion_1} +- + I check that the refund state is "Open" +- + !assert {model: account.invoice, id: refund_for_completion_1}: + - state == 'open' +- + I check that it is given the number "RTEXJ/%Y/0001" +- + !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_move_base_import/test/supplier_invoice.yml b/account_move_base_import/test/supplier_invoice.yml new file mode 100644 index 0000000000..4ac8e87d1d --- /dev/null +++ b/account_move_base_import/test/supplier_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', + 'demo', + 'account_invoice_demo.yml'), + {}, 'init', False, 'test') +- + I check that my invoice is a supplier invoice +- + !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type}: + - type == 'in_invoice' +- + I add a reference to an existing supplier invoce +- + !python {model: account.invoice}: | + self.write(cr, uid, ref('account.demo_invoice_0'), { + 'reference': 'T2S12345' + }) +- + I check a second time that my invoice is still a supplier invoice +- + !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type 2}: + - type == 'in_invoice' +- + Now I confirm it +- + !workflow {model: account.invoice, action: invoice_open, ref: account.demo_invoice_0} +- + I check that the supplier number is there +- + !assert {model: account.invoice, id: account.demo_invoice_0, string: Check supplier number}: + - reference == 'T2S12345' +- + I check a third time that my invoice is still a supplier invoice +- + !assert {model: account.invoice, id: account.demo_invoice_0, string: Check invoice type 3}: + - type == 'in_invoice' diff --git a/account_move_base_import/tests/__init__.py b/account_move_base_import/tests/__init__.py new file mode 100644 index 0000000000..2a1509089e --- /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 0000000000..8e36ee109e --- /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 0000000000..27c3956045 --- /dev/null +++ b/account_move_base_import/tests/test_base_import.py @@ -0,0 +1,81 @@ +# -*- 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, + }) + + 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 0000000000..dacc737a91 --- /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 0000000000..d36962188a --- /dev/null +++ b/account_move_base_import/views/journal_view.xml @@ -0,0 +1,42 @@ + + + + account.journal.view + account.journal + + + + + + + + + + + + + + + + + + + + - - + + - - + + - account.move.completion.rule.view account.move.completion.rule
- + - + - - + +
- account.move.completion.rule.view account.move.completion.rule - + - + - Move Completion Rules account.move.completion.rule tree,form - - - + account.move - + - - + help="Account move that should be completed manually" + /> + -
diff --git a/account_move_base_import/views/journal_view.xml b/account_move_base_import/views/journal_view.xml index 99359fc314..676ef31f72 100644 --- a/account_move_base_import/views/journal_view.xml +++ b/account_move_base_import/views/journal_view.xml @@ -1,40 +1,60 @@ - + account.journal.view account.journal - + - - + + - + - - - + ('used_for_completion', '=', False)]}" + /> + + - - - - - + + + + + -