Remane import modules

pull/510/head
Matthieu Dietrich 2016-05-24 11:51:51 +02:00 committed by Florian da Costa
parent 16bacdcd5d
commit 1560419021
32 changed files with 3007 additions and 0 deletions

View File

@ -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
<https://github.com/OCA/bank-statement-reconcile/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 <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Joël Grand-Guillaume <joel.grandguillaume@camptocamp.com>
* Nicolas Bessi <nicolas.bessi@camptocamp.com>
* Laurent Mignon <laurent.mignon@acsone.eu>
* Sébastien Beau <sebastien.beau@akretion.com>
* Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
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.

View File

@ -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

View File

@ -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',
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<record id="bank_statement_completion_rule_2" model="account.move.completion.rule">
<field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
<field name="sequence">60</field>
<field name="function_to_call">get_from_name_and_partner_field</field>
</record>
<record id="bank_statement_completion_rule_3" model="account.move.completion.rule">
<field name="name">Match from line label (based on partner name)</field>
<field name="sequence">70</field>
<field name="function_to_call">get_from_name_and_partner_name</field>
</record>
<record id="bank_statement_completion_rule_4" model="account.move.completion.rule">
<field name="name">Match from line label (based on Invoice number)</field>
<field name="sequence">40</field>
<field name="function_to_call">get_from_name_and_invoice</field>
</record>
<record id="bank_statement_completion_rule_5" model="account.move.completion.rule">
<field name="name">Match from line label (based on Invoice Supplier number)</field>
<field name="sequence">45</field>
<field name="function_to_call">get_from_name_and_supplier_invoice</field>
</record>
</odoo>

View File

@ -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"
1 date amount commission_amount label
2 2011-03-07 13:45:14 118.4 -11.84 label a
3 2011-03-02 13:45:14 189 -15.12 label b
4 2011-03-02 17:45:14 189 -15.12 label c

Binary file not shown.

View File

@ -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 ""

View File

@ -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 <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: banking-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <pedro.baeza@gmail.com>\n"
"Language-Team: Spanish <es@li.org>\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."

View File

@ -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 <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: banking-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <EMAIL@ADDRESS>\n"
"Language-Team: French <fr@li.org>\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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).")

View File

@ -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

View File

@ -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

View File

@ -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,
}

View File

@ -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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_bank_st_cmpl_user account.move.completion.rule.user model_account_move_completion_rule account.group_account_user 1 0 0 0
3 access_account_bank_st_cmpl_manager account.move.completion.rule.manager model_account_move_completion_rule account.group_account_manager 1 1 1 1

View File

@ -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")

View File

@ -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')

View File

@ -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

View File

@ -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')

View File

@ -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'

View File

@ -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

View File

@ -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))

View File

@ -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")

View File

@ -0,0 +1,67 @@
<odoo>
<record id="view_move_importer_form" model="ir.ui.view">
<field name="name">account.move.view</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<field name="journal_id" position="after">
<field name="used_for_completion" invisible="1"/>
</field>
<button name="button_cancel" position="after">
<button name="button_auto_completion"
string="Auto Completion"
type="object"
class="oe_highlight"
groups="account.group_account_invoice"
attrs="{'invisible': ['|', ('used_for_completion','=',False), ('state','not in', ['draft'])]}"/>
</button>
<xpath expr="//field[@name='line_ids']/tree/field[@name='credit']" position="after">
<field name="already_completed"/>
</xpath>
<xpath expr="/form/sheet/notebook" position="inside">
<page string="Completion Logs" attrs="{'invisible':[('completion_logs','=',False)]}">
<field name="completion_logs" colspan="4" nolabel="1"/>
</page>
</xpath>
</field>
</record>
<record id="move_completion_rule_view_form" model="ir.ui.view">
<field name="name">account.move.completion.rule.view</field>
<field name="model">account.move.completion.rule</field>
<field name="arch" type="xml">
<form string="Move Completion Rule">
<group>
<field name="sequence"/>
<field name="name" select="1" />
<field name="function_to_call"/>
</group>
<separator colspan="4" string="Related Profiles"/>
<field name="journal_ids" nolabel="1" colspan="4"/>
</form>
</field>
</record>
<record id="move_completion_rule_view_tree" model="ir.ui.view">
<field name="name">account.move.completion.rule.view</field>
<field name="model">account.move.completion.rule</field>
<field name="arch" type="xml">
<tree string="Statement Completion Rule">
<field name="sequence"/>
<field name="name" select="1" />
<field name="journal_ids" />
<field name="function_to_call"/>
</tree>
</field>
</record>
<record id="action_move_completion_rule_tree" model="ir.actions.act_window">
<field name="name">Move Completion Rule</field>
<field name="res_model">account.move.completion.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem string="Move Completion Rule" action="action_move_completion_rule_tree"
id="menu_action_move_completion_rule_tree_menu" parent="account.account_management_menu"/>
</odoo>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="journal_importer_view_form" model="ir.ui.view">
<field name="name">account.journal.view</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<field name="loss_account_id" position="after">
<field name="used_for_import"/>
<field name="used_for_completion"/>
</field>
<notebook position="inside">
<page string="Import related infos" attrs="{'invisible': [('used_for_import', '=', False)]}">
<group>
<field name="launch_import_completion" attrs="{'invisible': ['|',
('used_for_import', '=', False),
('used_for_completion', '=', False)]}"/>
<field name="last_import_date" readonly="1"/>
<field name="import_type" attrs="{'required': [('used_for_import', '=', True)]}"/>
</group>
<group>
<field name="commission_account_id" attrs="{'required': [('used_for_import', '=', True)]}"/>
<field name="receivable_account_id" attrs="{'required': [('used_for_import', '=', True)]}"/>
<field name="partner_id" attrs="{'required': [('used_for_import', '=', True)]}"/>
</group>
<group>
<button name="%(account_statement_base_import.move_importer_action)d"
string="Import Bank Statement"
type="action" icon="gtk-ok"
colspan = "2"/>
</group>
</page>
<page string="Auto-Completion related infos" attrs="{'invisible': [('used_for_completion', '=', False)]}">
<group>
<separator colspan="4" string="Auto-Completion Rules"/>
<field name="rule_ids" colspan="4" nolabel="1"/>
</group>
</page>
</notebook>
</field>
</record>
</odoo>

View File

@ -0,0 +1,15 @@
<odoo>
<record id="bk_view_partner_form" model="ir.ui.view">
<field name="name">account_bank_statement_import.view.partner.form</field>
<field name="model">res.partner</field>
<field name="priority">20</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<field name="property_account_payable_id" position="after">
<field name="bank_statement_label"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,7 @@
# -*- 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 import_statement

View File

@ -0,0 +1,91 @@
# -*- 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)
"""
Wizard to import financial institute date in bank statement
"""
from openerp import _, api, fields, models
import os
class CreditPartnerStatementImporter(models.TransientModel):
_name = "credit.statement.import"
@api.model
def default_get(self, fields):
ctx = self._context
res = {}
if (ctx.get('active_model', False) == 'account.journal' and
ctx.get('active_ids', False)):
ids = ctx['active_ids']
assert len(ids) == 1, \
'You cannot use this on more than one journal !'
res['journal_id'] = ids[0]
values = self.onchange_journal_id(res['journal_id'])
res.update(values.get('value', {}))
return res
journal_id = fields.Many2one(
comodel_name='account.journal',
string='Import configuration parameter',
required=True)
input_statement = fields.Binary(
string='Statement file',
required=True)
partner_id = fields.Many2one(
comodel_name='res.partner',
string='Credit institute partner')
file_name = fields.Char()
receivable_account_id = fields.Many2one(
comodel_name='account.account',
string='Force Receivable/Payable Account')
commission_account_id = fields.Many2one(
comodel_name='account.account',
string='Commission account')
@api.multi
def onchange_journal_id(self, journal_id):
if journal_id:
journal = self.env['account.journal'].browse(journal_id)
return {
'value': {
'partner_id': journal.partner_id.id,
'receivable_account_id': journal.receivable_account_id.id,
'commission_account_id': journal.commission_account_id.id,
}
}
@api.multi
def _check_extension(self):
self.ensure_one()
(__, ftype) = os.path.splitext(self.file_name)
if not ftype:
# We do not use osv exception we do not want to have it logged
raise Exception(_('Please use a file with an extension'))
return ftype
@api.multi
def import_statement(self):
"""This Function import credit card agency statement"""
moves = self.env['account.move']
for importer in self:
journal = importer.journal_id
ftype = importer._check_extension()
moves |= journal.with_context(
file_name=importer.file_name).multi_move_import(
importer.input_statement,
ftype.replace('.', '')
)
xmlid = ('account', 'action_move_journal_line')
action = self.env['ir.actions.act_window'].for_xml_id(*xmlid)
if len(moves) > 1:
action['domain'] = [('id', 'in', moves.ids)]
else:
ref = self.env.ref('account.view_move_form')
action['views'] = [(ref.id, 'form')]
action['res_id'] = moves.id if moves else False
return action

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="move_importer_view" model="ir.ui.view">
<field name="name">credit.statement.import.config.view</field>
<field name="model">credit.statement.import</field>
<field name="arch" type="xml">
<form string="Import move">
<group colspan="4" >
<field name="journal_id" on_change="onchange_journal_id(journal_id)" domain="[('used_for_import', '=', True)]"/>
<field name="input_statement" filename="file_name" colspan="2"/>
<field name="file_name" colspan="2" invisible="1"/>
<separator string="Import Parameters Summary" colspan="4"/>
<field name="partner_id" readonly="1"/>
<field name="receivable_account_id" readonly="1"/>
<field name="commission_account_id" readonly="1"/>
</group>
<footer>
<button icon="gtk-ok" name="import_statement" string="Import statement" type="object" class="oe_highlight"/>
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<record id="move_importer_action" model="ir.actions.act_window">
<field name="name">Import Move</field>
<field name="res_model">credit.statement.import</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="move_importer_view"/>
<field name="target">new</field>
</record>
<menuitem id="move_importer_menu" name="Import Bank Statement" action="move_importer_action" parent="account.menu_finance_entries"/>
</odoo>